Search code examples
rskrootstock

Possible to create deployment transaction with empty 'tx.data' on RSK?


There is a transaction on RSK Testnet, 0xf3b1d43850523d45b4c84c5098ff0cf6bb74d1eb350b9574315433544f990390, where tx.to is the zero address, and tx.data is also zero. However, it shows that this was a deployment transaction, and that a contract was created at this address.

It seems like (instead of a contract deployment) this should be a "burn" transaction, where RBTC is sent to the zero address.

How is this possible, and how does this work?


Additional detail, eth_getTransactionByHash:

$ curl https://public-node.testnet.rsk.co     -X POST -H "Content-Type: application/json"     --data '{"jsonrpc":"2.0","method":"eth_getTransactionByHash","params":["0xf3b1d43850523d45b4c84c5098ff0cf6bb74d1eb350b9574315433544f990390"],"id":1}' | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "hash": "0xf3b1d43850523d45b4c84c5098ff0cf6bb74d1eb350b9574315433544f990390",
    "nonce": "0xdbd",
    "blockHash": "0x0ecc95ab88bb1d72e8d83d015e9e31e65230c3035809db4476a28e56d22ef11c",
    "blockNumber": "0x12f7d9",
    "transactionIndex": "0xe",
    "from": "0x1bb2b1beeda1fb25ee5da9cae6c0f12ced831128",
    "to": null,
    "gas": "0x186a0",
    "gasPrice": "0x3dfd242",
    "value": "0x1558df2903f400",
    "input": "0x",
    "v": "0x61",
    "r": "0x976033c43bed3a37cde8808bcf32930d396f3c035d8fe21246dd8ef9dbade200",
    "s": "0x3777a1fa46829fe8fbb976b464ccccb3d2d2c255379bbf878efcbb5140351fcd"
  }
}
   "from": "0x1bb2b1beeda1fb25ee5da9cae6c0f12ced831128",
    "to": null,
    "value": "0x1558df2903f400",
    "input": "0x",

Additional detail, eth_getTransactionByHash:

$ curl https://public-node.testnet.rsk.co     -X POST -H "Content-Type: application/json"     --data '{"jsonrpc":"2.0","method":"eth_getTransactionReceipt","params":["0xf3b1d43850523d45b4c84c5098ff0cf6bb74d1eb350b9574315433544f990390"],"id":1}' | jq
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "transactionHash": "0xf3b1d43850523d45b4c84c5098ff0cf6bb74d1eb350b9574315433544f990390",
    "transactionIndex": "0xe",
    "blockHash": "0x0ecc95ab88bb1d72e8d83d015e9e31e65230c3035809db4476a28e56d22ef11c",
    "blockNumber": "0x12f7d9",
    "cumulativeGasUsed": "0xd1f15",
    "gasUsed": "0xcf08",
    "contractAddress": "0x3c9c7b9f43ad1ffe46beb4f58232157fb26f88c0",
    "logs": [],
    "from": "0x1bb2b1beeda1fb25ee5da9cae6c0f12ced831128",
    "to": null,
    "status": "0x1",
    "logsBloom": "0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
  }
}

Relevant parts

    "contractAddress": "0x3c9c7b9f43ad1ffe46beb4f58232157fb26f88c0",
    "logs": [],
    "from": "0x1bb2b1beeda1fb25ee5da9cae6c0f12ced831128",
    "to": null,
    "status": "0x1",

Solution

  • A contract without code is simply an account that was created by CREATE or CREATE2, but without specifying any data to put as initializer (which is the piece of code that when executed returns the code to install in the contract). When this happens, an empty smart contract is deployed.

    In RSK, contracts are identified internally when they have a "storage node root" in the trie. Every time a contract is deployed (with empty code or not), the storage root node is created in the RSK trie below the account node. RSK internally does not identify contracts when they have code or not. Note that this is a difference in the RSK consensus when compared to Ethereum consensus. This consensus difference is reflected in RSKj network node, which is different from geth (Ethereum most used network node).

    When RSK creates an account, it creates a small portion of the trie that stores the storage data (even if there is no data yet). RSK pre-creates a single node in the storage trie (a very compact one, storing a single byte). This dummy node has several purposes. One is to be able to easily compute the storage size, or the storage hash digest (both fields are embedded in the node). When you call isContract() in the code, the RSK node looks for this "dummy" storage root node and returns true if it is present. It does not check the existence of non-empty code.


    Additional notes regarding smart contracts and isContract():

    Currently isContract() is not called by consensus code except by the CODEHASH opcode, and CODEHASH returns the same result (KECCAK_256_OF_EMPTY_ARRAY) if the code is empty, and if isContract() returns false, so we may say that alternate definition of isContract() has no "visible" consequences from outside the node (i.e. by performing Web3-RPC calls). The RSK node behaves exactly as the Ethereum node.

    However it was called by consensus code prior the CODEHASH opcode was fixed, as specified in RSKIP-169 "Rectify EXTCODEHASH implementation". Therefore there may have been visible consequences of isContract() in blocks before RSKIP-169 was activated.