Skip to content
This repository has been archived by the owner on Jan 7, 2021. It is now read-only.

Latest commit

 

History

History
710 lines (596 loc) · 22.9 KB

README.md

File metadata and controls

710 lines (596 loc) · 22.9 KB

Polyjuice

This is the deprecated standalone polyjuice implementation. See https://github.com/nervosnetwork/godwoken-polyjuice for godwoken polyjuice.

Nervos CKB is built on the cell model, which is a generalized version of the UTXO model. There seems to be a belief in the blockchain world that UTXO model is hard to program on, while the account model is easier for developers. Although cell model is a descendant of UTXO model, it is perfectly possible to build account model on top of cell model. The secret here also lies in abstraction. While at the lower level UTXO-style design can help achieve parallelism, at the higher level an abstraction layer can expose exactly an account model to the everyday developers.

That is also just our claim, as engineers we all know the famous quote "Talk is cheap. Show me the code." Following this principle, we designed and built polyjuice, which is an Ethereum compatible layer on top of Nervos CKB. Ethereum, up to this day, is probably the most used and flexible account model based blockchain. By polyjuice we want to showcase that it is perfectly possible to use account model on Nervos CKB. The flexibility here actually enables countless opportunities.

Features

  • Contract creation
  • Contract destruction
  • Contract call contract
  • Contract logs
  • Read block information from contract
  • Value transfer

Polyjuice use evmone as the EVM implementation in both generator and validator, all opcodes (if none is missing) are supported.

A short tutorial

NOTE : The tutorial currently only tested on Ubuntu 18.04.

Here we provide a short tutorial performing the following operations on a polyjuice on CKB setup:

  • Simple contract creation
  • Calling contract
  • Reading storage data from a contract

Throughout the tutorial, we will work with the following 2 accounts:

  • Account A
    • private key: d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc
    • CKB address: ckt1qyqvsv5240xeh85wvnau2eky8pwrhh4jr8ts8vyj37
    • Ethereum address: 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7
  • Account B
    • private key: 3066aa42bfa95c6d033edfad9d1efb871991fd26f56270fedc171559823bee77
    • CKB address: ckt1qyqgjagv5f8xq9syxd38v2ga3dczszqy67psu2y8r4
    • Ethereum address: 0x89750ca24e601604336276291d8b70280804d783

Note that Ethereum address is also CKB secp256k1 sighash lock args.

Setting up CKB

You need to download latest CKB from github release page. For convenience, we will launch a dev chain locally and work from there.

To initialize the dev chain, you can just use the init command:

$ ckb init --chain dev --ba-arg 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7

Now we can launch CKB and the miner:

$ ckb run

# in a different terminal
$ ckb miner

Install ckb-cli / jq

Since we need to sign secp256k1 sighash locked inputs in polyjuice generated transaction, we need a little help from ckb-cli. You need to build a special version of ckb-cli (for support type-id and skip check to-address argument), and put in your $PATH, so polyjuice can find it.

$ git clone -b skip-check-to-address https://github.com/TheWaWaR/ckb-cli
$ cd ckb-cli
$ cargo install --locked -f --path .

Some actions depend on jq to show/edit json information. You may install jq by:

$ sudo apt install jq -y

Setting up polyjuice

Now we are ready to setup polyjuice:

$ git clone https://github.com/nervosnetwork/polyjuice
$ cd polyjuice
$ git submodule update --init --recursive --progress
$ cargo build --release

Build c contracts:

$ cd c
$ make all-via-docker
$ cd ..

It will build a validator for running in polyjuice type script, and a generator for generating CKB transaction.

Before deploy contracts we better save privkey to a file for convenience (NOTE: this is insecure):

echo "d00c06bfd800d27397002dca6fb0993d5ba6399b4238b2f29ee9deb97593d2bc" > privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7

Then we use ckb-cli to deploy validator to dev chain:

$ ckb-cli wallet transfer \
        --privkey-path privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \
        --to-address ckt1qyqgjagv5f8xq9syxd38v2ga3dczszqy67psu2y8r4 \
        --tx-fee 0.01 \
        --capacity 300000 \
        --to-data-path ./c/build/validator

# The transaction where validator contract's code located
0x1111000000000000000000000000000000000000000000000000000000000000

Since lock script is required for every cell and we want anyone can use the contract, here we deploy an always success contract for polyjuice cell's lock script:

$ ckb-cli wallet transfer \
        --privkey-path privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \
        --to-address ckt1qyqgjagv5f8xq9syxd38v2ga3dczszqy67psu2y8r4 \
        --tx-fee 0.001 \
        --capacity 600 \
        --to-data 0x7f454c460201010000000000000000000200f3000100000078000100000000004000000000000000980000000000000005000000400038000100400003000200010000000500000000000000000000000000010000000000000001000000000082000000000000008200000000000000001000000000000001459308d00573000000002e7368737472746162002e74657874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000010000000600000000000000780001000000000078000000000000000a0000000000000000000000000000000200000000000000000000000000000001000000030000000000000000000000000000000000000082000000000000001100000000000000000000000000000001000000000000000000000000000000

# The transaction where always success contract's code located
0x2222000000000000000000000000000000000000000000000000000000000000

Since we need an EoA(Externally Owned Account) account to send the transaction, we deploy a anyone-can-pay contract for EoA cell's lock script, for convenience we can use a pre-compiled binary in tests directory:

$ ckb-cli wallet transfer \
        --privkey-path privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \
        --to-address ckt1qyqdfjzl8ju2vfwjtl4mttx6me09hayzfldq8m3a0y \
        --tx-fee 0.01 \
        --capacity 60000 \
        --to-data-path ./tests/anyone_can_pay

# The transaction hash
0x3333000000000000000000000000000000000000000000000000000000000000

Running polyjuice require a config file to tell polyjuice the validator/always_success/anyone-can-pay contract's out point and code hash. We use ckb-cli to calculate the code hash:

# validator's code hash
$ ckb-cli util blake2b --binary-path ./c/build/validator
0xaaaa000000000000000000000000000000000000000000000000000000000000

# always_success's code hash
$ ckb-cli util blake2b --binary-hex 0x7f454c460201010000000000000000000200f3000100000078000100000000004000000000000000980000000000000005000000400038000100400003000200010000000500000000000000000000000000010000000000000001000000000082000000000000008200000000000000001000000000000001459308d00573000000002e7368737472746162002e74657874000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b000000010000000600000000000000780001000000000078000000000000000a0000000000000000000000000000000200000000000000000000000000000001000000030000000000000000000000000000000000000082000000000000001100000000000000000000000000000001000000000000000000000000000000
0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5

# anyone-can-pay's code hash
$ ckb-cli util blake2b --binary-path ./tests/anyone_can_pay
0xbbbb000000000000000000000000000000000000000000000000000000000000

Then generate the config file:

$ VALIDATOR_TX_HASH=0x1111000000000000000000000000000000000000000000000000000000000000
$ VALIDATOR_CODE_HASH=0xaaaa000000000000000000000000000000000000000000000000000000000000
$ ALWAYS_SUCCESS_TX_HASH=0x2222000000000000000000000000000000000000000000000000000000000000
$ ANYONE_CAN_PAY_TX_HASH=0x3333000000000000000000000000000000000000000000000000000000000000
$ ANYONE_CAN_PAY_CODE_HASH=0xbbbb000000000000000000000000000000000000000000000000000000000000

$ cat > run_config.json << _RUN_CONFIG_
{
    "type_dep": {
        "out_point": {
            "tx_hash": "${VALIDATOR_TX_HASH}",
            "index": "0x0"
        },
        "dep_type": "code"
    },
    "type_script": {
        "code_hash": "${VALIDATOR_CODE_HASH}",
        "hash_type": "data",
        "args": "0x"
    },
    "lock_dep": {
        "out_point": {
            "tx_hash": "${ALWAYS_SUCCESS_TX_HASH}",
            "index": "0x0"
        },
        "dep_type": "code"
    },
    "lock_script": {
        "code_hash": "0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5",
        "hash_type": "data",
        "args": "0x"
    },
    "eoa_lock_dep": {
        "out_point": {
            "tx_hash": "${ANYONE_CAN_PAY_TX_HASH}",
            "index": "0x0"
        },
        "dep_type": "code"
    },
    "eoa_lock_script": {
        "code_hash": "${ANYONE_CAN_PAY_CODE_HASH}",
        "hash_type": "data",
        "args": "0x"
    }
}
_RUN_CONFIG_

Then start polyjuice:

RUST_LOG=polyjuice=debug ./target/release/polyjuice run \
  --generator ./c/build/generator \
  --config ./run_config.json

Interacting though RPC API

We will use curl to interact with polyjuice. Default RPC server listen address is localhost:8214.

Create EoA account

Fisrt we need an EoA account to send transaction:

$ ./target/release/polyjuice new-eoa-account -k privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 --balance 10000.0

[lock-arg]: 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7
[Command]: ckb-cli wallet transfer --privkey-path privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 --to-address ckt1q293q9zd4ggsz5h83hgq9dz0g2fr3ja7uhnzq53qtlwx587zc4az4jpj324umxu73ej0h3txcsu9cw77kgvaway9qy9 --capacity 10126.0 --tx-fee 0.001 --type-id --skip-check-to-address
tx-hash: 0x082a2c796a11b476be1ba8bbb8fab17fca7a1cd2167d58b5f21c62c52cabd4a9, output-index: 0
[type_args]: a1b4eb8bf37c6894c11029ae7f3d542aea3bc0ddca1ce6a3580e07b536dc9cad
[lock_args]: c8328aabcd9b9e8e64fbc566c4385c3bdeb219d7
0xb16ac6204aef494c411ed9dcfd6909f8c2d74527

The EoA account address is int the last line, which is 0xb16ac6204aef494c411ed9dcfd6909f8c2d74527.

Create contract

Then, let's create an ERC20 contract:

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "create",
    "params": ["0xb16ac6204aef494c411ed9dcfd6909f8c2d74527", "0x<the ERC20 contract binary>", 0]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq .result > tx-receipt.json
{
  "entrance_contract": "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
  "created_addresses": [
    "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691"
  ],
  "destructed_addresses": [],
  "logs": [],
  "return_data": "0x6060604 ...... 806500029",
  "tx_hash": "0x1111111111111111111111111111111111111111111111111111111111111111",
  "tx": {
    "cell_deps": [
      {
        "dep_type": "dep_group",
        "out_point": {
          "index": "0x0",
          "tx_hash": "0xace5ea83c478bb866edf122ff862085789158f5cbff155b7bb5f13058555b708"
        }
      },
      {
        "dep_type": "code",
        "out_point": {
          "index": "0x0",
          "tx_hash": "0xc6c27c3a371425011b3b20697e16342359746f9203cc29a0613dd6166c830a94"
        }
      },
      {
        "dep_type": "code",
        "out_point": {
          "index": "0x0",
          "tx_hash": "0x301a76aeafdefe55d822ff7b25373591438cfd7055d21285e9389b76e3e92c4b"
        }
      }
    ],
    "header_deps": [],
    "inputs": [
      {
        "previous_output": {
          "index": "0x1",
          "tx_hash": "0x6260fb79e81e8196233c32ee61392bf44332fd2883d889c81f22f03c0bbe7077"
        },
        "since": "0x0"
      }
    ],
    "outputs": [
      {
        "capacity": "0x4a817c800",
        "lock": {
          "args": "0x",
          "code_hash": "0x28e83a1277d48add8e72fadaa9248559e1b632bab2bd60b27955ebc4c03800a5",
          "hash_type": "data"
        },
        "type": {
          "args": "0x804988b0f61a786082cbed278d46a7e727954eca",
          "code_hash": "0x76aa92a7045c92289a0c1dbd74585d5a2b7660aa796f25d92052a6b2f705f181",
          "hash_type": "data"
        }
      },
      {
        "capacity": "0x1bc15206fd1b0a20",
        "lock": {
          "args": "0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7",
          "code_hash": "0x9bd7e06f3ecf4be0f2fcd2188b23f1b9fcc88e5d4b65a8637b17723bbda3cce8",
          "hash_type": "type"
        },
        "type": null
      }
    ],
    "outputs_data": [
      "0x6cb306547af5f7e64e4f01d3624de0ad36e1625029f73f5d25529600e682388ae09ecb04e6e61109e7d392c7dc7fc110e8edf83ae661bb2074cef29a2dab3c77",
      "0x"
    ],
    "version": "0x0",
    "witnesses": [
      "0x5a150000 ...... f94c48fe00000000"
    ]
  }
}

The entrance_contract field which is 0xfe68578683eb8deee4de1aca6c1ba8847c6d7691 here contrains the ERC20 contract address we will create. The following actions will require this value as argument.

Then we sign the transaction use polyjuice:

$ ./target/release/polyjuice sign-tx \
  --privkey privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 \
  --tx-receipt tx-receipt.json \
  --output signed-tx.json

The last part is send the transaction to CKB use ckb-cli:

$ ckb-cli tx send --tx-file signed-tx.json --skip-check
0xedcede37f52fc402e021e17bf1cc1eb1b64cd4611e82dbe071440857ed375055

Query the information of contract

The contract metadata:

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "get_contracts",
    "params": [0, null]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq
{
  "jsonrpc": "2.0",
  "result": [
    {
      "address": "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
      "block_number": 14,
      "code": "0x608060405260043 ... 9f64736f6c63430006060033",
      "code_hash": "0x8e92ee4326804b8c5b911ad1cf31b1b44269a1f89453329b5162e5b04ac2eade",
      "destructed": false,
      "output_index": 0,
      "tx_hash": "0xedcede37f52fc402e021e17bf1cc1eb1b64cd4611e82dbe071440857ed375055"
    }
  ],
  "id": 2
}

The contract change:

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "get_change",
    "params": ["0xfe68578683eb8deee4de1aca6c1ba8847c6d7691", null]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq
{
  "jsonrpc": "2.0",
  "result": {
    "address": "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
    "is_create": true,
    "logs": [],
    "new_storage": [
      [
        "0xc883bc0d49add18e7c46e11b87235c3df58a5051abcb763ed021382a2fbd0a61",
        "0x000000000000000000000000000000000000000204fce5e3e250261100000000"
      ],
      [
        "0x0000000000000000000000000000000000000000000000000000000000000002",
        "0x0000000000000000000000000000000000000000000000000000000000000012"
      ],
      [
        "0x0000000000000000000000000000000000000000000000000000000000000003",
        "0x000000000000000000000000000000000000000204fce5e3e250261100000000"
      ],
      [
        "0x0000000000000000000000000000000000000000000000000000000000000000",
        "0x455243323000000000000000000000000000000000000000000000000000000a"
      ],
      [
        "0x0000000000000000000000000000000000000000000000000000000000000001",
        "0x455243323000000000000000000000000000000000000000000000000000000a"
      ]
    ],
    "number": 14,
    "output_index": 0,
    "tx_hash": "0xedcede37f52fc402e021e17bf1cc1eb1b64cd4611e82dbe071440857ed375055",
    "tx_index": 1,
    "tx_origin": "0xb16ac6204aef494c411ed9dcfd6909f8c2d74527"
  },
  "id": 2
}

Call a contract

Before we make some changes to the contract let's query the balance of the ERC20 token issuer.

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "static_call",
    "params": [
        "0xb16ac6204aef494c411ed9dcfd6909f8c2d74527",
        "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
        "0x70a08231000000000000000000000000b16ac6204aef494c411ed9dcfd6909f8c2d74527"
    ]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq
{
  "jsonrpc": "2.0",
  "result": {
    "logs": [],
    "return_data": "0x000000000000000000000000000000000000000204fce5e3e250261100000000"
  },
  "id": 2
}

We need create another EoA account to transfer:

./target/release/polyjuice new-eoa-account -k privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 --balance 10000.0
[lock-arg]: 0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7
[Command]: ckb-cli wallet transfer --privkey-path privkey-0xc8328aabcd9b9e8e64fbc566c4385c3bdeb219d7 --to-address ckt1q293q9zd4ggsz5h83hgq9dz0g2fr3ja7uhnzq53qtlwx587zc4az4jpj324umxu73ej0h3txcsu9cw77kgvaway9qy9 --capacity 10126.0 --tx-fee 0.001 --type-id --skip-check-to-address
tx-hash: 0xaaba80fc391641fc8590435335f2962d47b9caa181d408594ad61acfa668bad9, output-index: 0
[type_args]: fc7514e6465efe5af146cb61aaf3d259896b05848ca0f40ebdf666053dc265c1
[lock_args]: c8328aabcd9b9e8e64fbc566c4385c3bdeb219d7
0x3d2a2c5afeb6ba873844581245325d7cbc890313

Now, let's transfer 555 ERC20 token from 0xb16ac6204aef494c411ed9dcfd6909f8c2d74527 to 0x3d2a2c5afeb6ba873844581245325d7cbc890313. The args generated from ethabi will be 0xa9059cbb0000000000000000000000003d2a2c5afeb6ba873844581245325d7cbc890313000000000000000000000000000000000000000000000000000000000000022b.

First, we create a CKB transaction use polyjuice:

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "call",
    "params": [
        "0xb16ac6204aef494c411ed9dcfd6909f8c2d74527",
        "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
        "0xa9059cbb0000000000000000000000003d2a2c5afeb6ba873844581245325d7cbc890313000000000000000000000000000000000000000000000000000000000000022b",
        0
    ]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq .result > tx-receipt.json
{
  "entrance_contract": "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
  "created_addresses": [],
  "destructed_addresses": [],
  "logs": [
    {
      "address": "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
      "data": "0x000000000000000000000000000000000000000000000000000000000000022b",
      "topics": [
        "0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef",
        "0x000000000000000000000000b16ac6204aef494c411ed9dcfd6909f8c2d74527",
        "0x0000000000000000000000003d2a2c5afeb6ba873844581245325d7cbc890313"
      ]
    }
  ],
  "return_data": null,
  "tx": { ... },
  "tx_hash": "0x1111111111111111111111111111111111111111111111111111111111111111"
}

Then we sign the transaction use polyjuice sign-tx and send the transaction use ckb-cli tx send.

Then we query the balance of 0xb16ac6204aef494c411ed9dcfd6909f8c2d74527 again:

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "static_call",
    "params": [
        "0xb16ac6204aef494c411ed9dcfd6909f8c2d74527",
        "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
        "0x70a08231000000000000000000000000b16ac6204aef494c411ed9dcfd6909f8c2d74527"
    ]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq
{
  "jsonrpc": "2.0",
  "result": {
    "logs": [],
    "return_data": "0x000000000000000000000000000000000000000204fce5e3e2502610fffffdd5"
  },
  "id": 2
}

We can see the balance changed:

From:
0x000000000000000000000000000000000000000204fce5e3e250261100000000

To:
0x000000000000000000000000000000000000000204fce5e3e2502610fffffdd5

And query the balance of 0x3d2a2c5afeb6ba873844581245325d7cbc890313:

echo '{
    "id": 2,
    "jsonrpc": "2.0",
    "method": "static_call",
    "params": [
        "0xb16ac6204aef494c411ed9dcfd6909f8c2d74527",
        "0xfe68578683eb8deee4de1aca6c1ba8847c6d7691",
        "0x70a082310000000000000000000000003d2a2c5afeb6ba873844581245325d7cbc890313"
    ]
}' \
| tr -d '\n' \
| curl -s -H 'content-type: application/json' -d @- http://localhost:8214 \
| jq
{
  "jsonrpc": "2.0",
  "result": {
    "logs": [],
    "return_data": "0x000000000000000000000000000000000000000000000000000000000000022b"
  },
  "id": 2
}

The JSON-RPC API

RPC methods:

/// Create a contract
fn create(sender: H160, code: Bytes, value: u64) -> TransactionReceipt;

/// Call a contract
fn call(sender: H160, contract_address: H160, input: Bytes, value: u64) -> TransactionReceipt;

/// Static call a contract
fn static_call(sender: H160, contract_address: H160, input: Bytes) -> StaticCallResponse;

/// Get the code of a contract
fn get_code(contract_address: H160) -> ContractCodeJson;

/// Get contract list
fn get_contracts(from_block: u64, to_block: Option<u64>) -> Vec<ContractMetaJson>;

/// Get contract change record
fn get_change(contract_address: H160, block_number: Option<u64>) -> ContractChangeJson;

/// Get contract execution logs
fn get_logs(
  from_block: u64,
  to_block: Option<u64>,
  address: Option<H160>,
  filter_topics: Option<Vec<H256>>,
  limit: Option<u32>,
) -> Vec<LogInfo>;

/// Get balance of an account
fn get_balance(&self, address: H160) -> u64;

Response data structures:

struct TransactionReceipt {
    tx: CkbTransaction,
    tx_hash: H256,
    entrance_contract: H160,
    /// The newly created contract's address
    created_addresses: Vec<H160>,
    /// Destructed contract addresses
    destructed_addresses: Vec<H160>,
    logs: Vec<LogEntry>,
    return_data: Option<Bytes>,
}

struct StaticCallResponse {
    return_data: Bytes,
    logs: Vec<LogEntry>,
}

struct ContractMetaJson {
    /// The block where the contract created
    block_number: u64,
    /// The contract address
    address: H160,

    /// The contract code
    code: Bytes,
    /// The contract code hash
    code_hash: H256,
    /// The hash of the transaction where the contract created
    tx_hash: H256,
    /// The output index of the transaction where the contract created
    output_index: u32,
    /// If the contract is destructed
    destructed: bool,
}

struct ContractChangeJson {
    tx_origin: H160,
    address: H160,
    /// Block number
    number: u64,
    /// Transaction index in current block
    tx_index: u32,
    /// Output index in current transaction
    output_index: u32,
    tx_hash: H256,
    new_storage: Vec<(H256, H256)>,
    logs: Vec<(Vec<H256>, Bytes)>,
    /// The change is create the contract
    is_create: bool,
}

struct ContractCodeJson {
    code: Bytes,
    /// The hash of the transaction where the contract created
    tx_hash: H256,
    /// The output index of the transaction where the contract created
    output_index: u32,
}

struct LogInfo {
    block_number: u64,
    tx_index: u32,
    log: LogEntry,
}

struct LogEntry {
    address: H160,
    topics: Vec<H256>,
    data: Bytes,
}