Leí el código fuente de go-ethereum y me confundí acerca de cómo EVM encuentra la entrada de una función llamada. Como decía la especificación, el campo de datos en una transacción especifica la función y los argumentos. Entonces, según tengo entendido, la función específica debe ejecutarse en el EVM con datos de entrada relacionados en lugar de todo el código del contrato. Sin embargo, no puedo encontrar el código relacionado. En el ciclo de ejecución, la PC comienza desde 0:
pc = uint64(0) // program counter
for ; ; instrCount++ {
// Get the memory location of pc
op = contract.GetOp(pc)
// calculate the new memory size and gas price for the current executing opcode
newMemSize, cost, err = calculateGasAndSize(evm.env, contract, caller, op, statedb, mem, stack)
if err != nil {
return nil, err
}
// Use the calculated gas. When insufficient gas is present, use all gas and return an
// Out Of Gas error
if !contract.UseGas(cost) {
return nil, OutOfGasError
}
// Resize the memory calculated previously
mem.Resize(newMemSize.Uint64())
// Add a log message
if evm.cfg.Debug {
evm.logger.captureState(pc, op, contract.Gas, cost, mem, stack, contract, evm.env.Depth(), nil)
}
if opPtr := evm.jumpTable[op]; opPtr.valid {
if opPtr.fn != nil {
opPtr.fn(instruction{}, &pc, evm.env, contract, mem, stack)
} else {
switch op {
case PC:
opPc(instruction{data: new(big.Int).SetUint64(pc)}, &pc, evm.env, contract, mem, stack)
case JUMP:
if err := jump(pc, stack.pop()); err != nil {
return nil, err
}
continue
case JUMPI:
pos, cond := stack.pop(), stack.pop()
if cond.Cmp(common.BigTrue) >= 0 {
if err := jump(pc, pos); err != nil {
return nil, err
}
continue
}
case RETURN:
offset, size := stack.pop(), stack.pop()
ret := mem.GetPtr(offset.Int64(), size.Int64())
return ret, nil
case SUICIDE:
opSuicide(instruction{}, nil, evm.env, contract, mem, stack)
fallthrough
case STOP: // Stop the contract
return nil, nil
}
}
} else {
return nil, fmt.Errorf("Invalid opcode %x", op)
}
pc++
}
Entonces, ¿cómo ejecuta la EVM la función específica?
la función específica debe ejecutarse en EVM con datos de entrada relacionados en lugar de todo el código del contrato
Como se indica aquí , el ABI es una abstracción sobre el protocolo Ethereum y el EVM.
Para las funciones que se adhieren a la ABI, es trabajo del compilador producir el código de bytes EVM correcto.
Un ejemplo muy aproximado, solo con fines conceptuales, del código de bytes de "tabla de salto" que produciría un compilador EVM:
method_id = first 4 bytes of msg.data
if method_id == 0x25d8dcf2 jump to 0x11
if method_id == 0xaabbccdd jump to 0x22
if method_id == 0xffaaccee jump to 0x33
other code <- Solidity fallback function code could be here
0x11:
code for function with method id 0x25d8dcf2
0x22:
code for function with method id 0xaabbccdd
0x33:
code for function with method id 0xffaaccee
Puede ver que los primeros 4 bytes de msg.data
(Method ID es el término dado por ABI) se usan para verificar a qué función saltar y ejecutar.
A partir del ejemplo, también puede ver que puede hacer que su propio compilador genere un código de bytes con una lógica diferente (tal vez quiera usar los primeros 8 bytes de msg.data), pero las personas que llaman tendrían que seguir esas convenciones en lugar de simplemente usar un biblioteca como web3.js.
El ejemplo es de:
Estoy usando el código de ejemplo modificado de Syntax para llamar a los métodos de cambio de estado del contrato , guardándolo en C.sol:
contract C {
uint[] public numbers;
function initNumbers() {
numbers.push(1);
numbers.push(2);
}
function stateChanger(uint a) {
numbers.push(a);
}
}
A continuación se muestra el código binario compilado en formato hexadecimal, formateado en columnas de 80 caracteres. Puede ver las 3 firmas de función dentro del código binario marcado con '<' y '>'.
user@Kumquat:~$ solc --abi --hashes --bin C.sol
======= C =======
Binary:
6060604052610217806100126000396000f360606040526000357c01000000000000000000000000
0000000000000000000000000000000090048063<5a7dc897>1461004f578063<65060775>1461005e57
8063<d39fa233>146100765761004d565b005b61005c60048050506100a2565b005b61007460048080
35906020019091905050610181565b005b61008c60048080359060200190919050506101f2565b60
40518082815260200191505060405180910390f35b60006000508054806001018281815481835581
8115116100f4578183600052602060002091820191016100f391906100d5565b808211156100ef57
600081815060009055506001016100d5565b5090565b5b5050509190906000526020600020900160
005b6001909190915055506000600050805480600101828181548183558181151161016257818360
0052602060002091820191016101619190610143565b8082111561015d5760008181506000905550
600101610143565b5090565b5b5050509190906000526020600020900160005b6002909190915055
505b565b600060005080548060010182818154818355818115116101d35781836000526020600020
91820191016101d291906101b4565b808211156101ce57600081815060009055506001016101b456
5b5090565b5b5050509190906000526020600020900160005b83909190915055505b50565b600060
005081815481101561000257906000526020600020900160005b91509050548156
Function signatures:
5a7dc897: initNumbers()
65060775: stateChanger(uint256)
d39fa233: numbers(uint256)
Contract JSON ABI
[{"constant":false,"inputs":[],"name":"initNumbers","outputs":[],"type":"function"},
{"constant":false,"inputs":[{"name":"a","type":"uint256"}],"name":"stateChanger","outputs":[],"type":"function"},
{"constant":true,"inputs":[{"name":"","type":"uint256"}],"name":"numbers","outputs":[{"name":"","type":"uint256"}],"type":"function"}]
Así es como se calculan las firmas de función en geth
:
> web3.sha3('initNumbers()').substr(0,10)
"0x5a7dc897"
> web3.sha3('stateChanger(uint256)').substr(0,10)
"0x65060775"
> web3.sha3('numbers(uint256)').substr(0,10)
"0xd39fa233"
Como señala BokkyPooBah, la firma de la función se usa para encontrar el lugar en el código de bytes que contiene el código para el método llamado. El Ethereum ABI define cómo se pasan los argumentos. Los primeros cuatro bytes de la cadena de argumento proporcionada es la firma de la función, codificada con Keccak (SHA3). Se utiliza una instrucción similar a un interruptor para comparar los primeros cuatro bytes de la cadena de argumento proporcionada con los métodos disponibles para este contrato. Si no se encuentra ninguna coincidencia, los contratos generan una excepción (llamando a un código de operación que no existe) y la llamada al método falla. Si se encuentra una coincidencia , el contador del programa se mueve a ese método mediante el JUMPI
código de operación.
Consulte también ¿Qué es el contador del programa al comienzo de la ejecución de un método Ethereum?
rong jialei
ética
Kennet Celeste