He estado luchando un poco con los métodos de llamada que cambian el estado del contrato. Tomemos, por ejemplo, el siguiente contrato:
contract C {
uint[] numbers;
function initNumbers() {
numbers.push(1);
numbers.push(2);
}
function stateChanger(uint a) {
numbers.push(a);
}
}
Después de la implementación, el método initNumbers() debe llamarse/enviarse como una transacción:
c.initNumbers({from:eth.accounts[0],gas:400000});
debido al hecho de que cambia el estado (ejecuta operaciones de escritura) en la cadena de bloques.
¿Cómo es posible llamar al segundo método que tiene un argumento pero también cambia el estado (lo almacena en el contrato)?
Intenté lo siguiente pero obtuve una excepción BigNumber :
c.stateChanger({from:web3.eth.accounts[0],gas:400000}).call(3);
El seguimiento de la pila fue:
/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:1209
throw error;
^
BigNumber Error: new BigNumber() not a number: [object Object]
at raise (/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:1177:25)
at /usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:1165:33
at new BigNumber (/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:193:67)
at new BigNumber (/usr/lib/node_modules/web3/node_modules/bignumber.js/bignumber.js:203:25)
at toBigNumber (/usr/lib/node_modules/web3/lib/utils/utils.js:367:12)
at Object.toTwosComplement (/usr/lib/node_modules/web3/lib/utils/utils.js:378:21)
at formatInputInt [as _inputFormatter] (/usr/lib/node_modules/web3/lib/solidity/formatters.js:40:38)
at SolidityType.encode (/usr/lib/node_modules/web3/lib/solidity/type.js:179:17)
at /usr/lib/node_modules/web3/lib/solidity/coder.js:86:29
at Array.map (native)
Aquí está su función ligeramente modificada agregando public
para numbers
que podamos ver los datos:
contract C {
uint[] public numbers;
function initNumbers() {
numbers.push(1);
numbers.push(2);
}
function stateChanger(uint a) {
numbers.push(a);
}
}
Usando el stripCrLf
script en Cómo cargar el archivo fuente de Solidity en geth , aplané el código fuente usando el siguiente comando:
user@Kumquat:~$ echo "var cSource='`stripCrLf C.sol`'"
var cSource='contract C { uint[] public numbers; function initNumbers() { numbers.push(1); numbers.push(2); } function stateChanger(uint a) { numbers.push(a); } }'
Estoy ejecutando una red de desarrollo usando el siguiente comando (tengo mi contraseña en passwordfile
):
user@Kumquat:~$ geth --datadir ~/devdata --dev --mine --minerthreads 1 --unlock 0 --password ~/passwordfile console
Pego el código aplanado en la geth
línea de comando:
> var cSource='contract C { uint[] public numbers; function initNumbers() { numbers.push(1); numbers.push(2); } function stateChanger(uint a) { numbers.push(a); } }'
undefined
Y compila el código usando el siguiente comando:
> var cCompiled = web3.eth.compile.solidity(cSource);
Version: 0.3.5-0/RelWithDebInfo-Linux/g++/Interpreter
path: /usr/bin/solc
undefined
Aquí está la interfaz binaria de la aplicación:
> cCompiled.C.info.abiDefinition
[{
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"
}]
Implemente el contrato en la cadena de bloques:
> var cContract = web3.eth.contract(cCompiled.C.info.abiDefinition);
undefined
> var c = cContract.new({
from:web3.eth.accounts[0],
data: cCompiled.C.code, gas: 400000},
function(e, contract) {
if (!e) {
if (!contract.address) {
console.log("Contract transaction send: TransactionHash: " +
contract.transactionHash + " waiting to be mined...");
} else {
console.log("Contract mined! Address: " + contract.address);
console.log(contract);
}
}
})
Contract mined! Address: 0x672807a8c0f72a52d759942e86cfe33264e73934
Llame a su initNumbers()
:
> c.initNumbers({from:eth.accounts[0], gas: 400000})
"0x3f21e1cdb636a2cf291cd9296282a4e9c4f4ed57c65a13fedc937a97203c3a75"
...
> c.numbers(0)
1
> c.numbers(1)
2
> c.numbers(2)
0
stateChanger(...)
- Método 1> c.stateChanger(3, {from:eth.accounts[0], gas: 400000})
"0x3ecf3aa6464f93d5b062ad01b76b5dbc4302c190baf70d157a4d7607d4c7c749"
...
> c.numbers(2)
3
> c.numbers(3)
0
stateChanger(...)
- Método 2> c.stateChanger.sendTransaction(4, {from: eth.accounts[0], gas: 400000})
"0xe1d9c57ca55dbdf446e55602570888ff5efce97a91ddc5c9575d10c7c9a1f0c8"
...
> c.numbers(3)
4
> c.numbers(4)
0
stateChanger(...)
- Método 3sendTransaction(...)
Y lo siguiente es cómo usar el formato de datos sin procesar .
Encuentra la firma de la función. Tenga en cuenta que el uint
parámetro es en realidad un uint256
(consulte el formato ABI anterior):
> web3.sha3('stateChanger(uint256)').substr(0, 10)
"0x65060775"
Aquí hay 5 codificados en hexadecimal y rellenados a 32 bytes:
0x0000000000000000000000000000000000000000000000000000000000000005
Une los datos, eliminando el 0x
de la cadena hexadecimal (5):
0x650607750000000000000000000000000000000000000000000000000000000000000005
Aquí está cómo sendTransaction(...)
. El to:
parámetro es la dirección del contrato extraído:
> var result = web3.eth.sendTransaction({
from: eth.accounts[0],
to: "0x672807a8c0f72a52d759942e86cfe33264e73934",
data: "0x650607750000000000000000000000000000000000000000000000000000000000000005",
gas: 400000}
)
undefined
> result
"0x420d4256718b4326967677da2e460a1d361a62f3ceee386e1313d25b12c0f610"
...
> c.numbers(4)
5
> c.numbers(5)
0
estimateGas(...)
Ya que hemos elaborado los datos para enviar con sendTransaction(...)
arriba.
Aquí está el gas utilizado por la sendTransaction(...)
llamada anterior:
> eth.getTransactionReceipt(result).gasUsed
46888
Puede usar el mismo formato de datos para estimar el gas usando la estimateGas(...)
llamada:
var result = web3.eth.estimateGas({
from: eth.accounts[0],
to: "0x672807a8c0f72a52d759942e86cfe33264e73934",
data: "0x650607750000000000000000000000000000000000000000000000000000000000000005",
gas: 400000}
)
undefined
> result
46888
Establezca debug.verbosity(7)
y observe cómo se elimina la transacción del grupo de transacciones después de que se ejecute:
> debug.verbosity(7)
...
I0730 00:13:43.918828 core/tx_pool.go:547] removed tx (
TX(07c3c717c87ce1e2b31ba144b6b751d66c90a8a69375478a5ef4e60a6f42996c)
Contract: false
From: a7857047907d53a2e494d5f311b4b586dc6a96d2
To: 672807a8c0f72a52d759942e86cfe33264e73934
Nonce: 35
GasPrice: 20000000000
GasLimit 400000
Value: 0
Data: 0x650607750000000000000000000000000000000000000000000000000000000000000004
V: 0x1c
R: 0x75ff67a969670a0f35ae415f2fa541446a71fa9b7d8ad6247ad12fd7ae3986a
S: 0x7150424ed1698792cdcf1253a79f2474e4df411610db97e36240bab50af6c80c
Hex: f889238504a817c80083061a8094672807a8c0f72a52d759942e86cfe33264e7393480a46506077500000000000000000000000000000000000000000000000000000000000000041ca0075ff67a969670a0f35ae415f2fa541446a71fa9b7d8ad6247ad12fd7ae3986aa07150424ed1698792cdcf1253a79f2474e4df411610db97e36240bab50af6c80c
) from pool: low tx nonce or out of funds
Los diferentes métodos de llamada ( c.stateChanger(3, {from:eth.accounts[0], gas: 400000})
y c.stateChanger.sendTransaction(4, {from: eth.accounts[0], gas: 400000})
) se convierten al sendTransaction(...)
formato.
Mi respuesta es similar a la de Bokky:
cContract = web3.eth.contract(cContractABI).at(cContractEthAddress);
cContract.initAddrs({from:web3.eth.accounts[0],gas:utils.DEFAULT_TX_COST});
donde cContract es un objeto Javascript que define un contrato. Una nota clave es asegurarse de que la cuenta que invoca el método de cambio de estado siempre tenga suficiente saldo; de lo contrario, la invocación del método se cancelará y Ether se perderá.
Sebí
stripCrLf
porq9f
Sebí
Juan Ignacio Pérez Sacristán
kitsune