├── .dockerignore ├── .github ├── ISSUE_TEMPLATE │ └── bug-report.yaml ├── actions │ ├── docker-push │ │ └── action.yaml │ ├── install-tools │ │ └── action.yaml │ └── setup-cache │ │ └── action.yaml └── workflows │ ├── add-bug-tracker.yaml │ └── ci.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── Makefile ├── README.md ├── docker ├── builder.ci.Dockerfile ├── builder.local.Dockerfile └── runner.Dockerfile ├── docs ├── README.md ├── architecture.md ├── checkpointing.md ├── demos │ └── milestone-1 │ │ ├── Fendermint_Demo.pdf │ │ ├── README.md │ │ └── fendermint-demo.sh ├── diagrams │ ├── .gitignore │ ├── Makefile │ ├── README.md │ ├── checkpointing.png │ └── checkpointing.puml ├── images │ └── IPC with Tendermint Core.jpg ├── ipc.md ├── localnet.md ├── running.md └── tendermint.md ├── fendermint ├── abci │ ├── Cargo.toml │ ├── README.md │ ├── examples │ │ └── kvstore.rs │ └── src │ │ ├── application.rs │ │ ├── lib.rs │ │ └── util.rs ├── app │ ├── Cargo.toml │ ├── config │ │ ├── default.toml │ │ └── test.toml │ ├── options │ │ ├── Cargo.toml │ │ ├── examples │ │ │ └── network.rs │ │ └── src │ │ │ ├── eth.rs │ │ │ ├── genesis.rs │ │ │ ├── key.rs │ │ │ ├── lib.rs │ │ │ ├── parse.rs │ │ │ ├── rpc.rs │ │ │ └── run.rs │ ├── settings │ │ ├── Cargo.toml │ │ └── src │ │ │ ├── eth.rs │ │ │ ├── fvm.rs │ │ │ ├── lib.rs │ │ │ └── resolver.rs │ └── src │ │ ├── app.rs │ │ ├── cmd │ │ ├── eth.rs │ │ ├── genesis.rs │ │ ├── key.rs │ │ ├── mod.rs │ │ ├── rpc.rs │ │ └── run.rs │ │ ├── ipc.rs │ │ ├── lib.rs │ │ ├── main.rs │ │ ├── store.rs │ │ └── tmconv.rs ├── crypto │ ├── Cargo.toml │ └── src │ │ └── lib.rs ├── eth │ ├── api │ │ ├── Cargo.toml │ │ ├── README.md │ │ ├── examples │ │ │ └── ethers.rs │ │ └── src │ │ │ ├── apis │ │ │ ├── eth.rs │ │ │ ├── mod.rs │ │ │ ├── net.rs │ │ │ └── web3.rs │ │ │ ├── cache.rs │ │ │ ├── client.rs │ │ │ ├── conv │ │ │ ├── from_eth.rs │ │ │ ├── from_fvm.rs │ │ │ ├── from_tm.rs │ │ │ └── mod.rs │ │ │ ├── error.rs │ │ │ ├── filters.rs │ │ │ ├── gas │ │ │ ├── mod.rs │ │ │ └── output.rs │ │ │ ├── handlers │ │ │ ├── http.rs │ │ │ ├── mod.rs │ │ │ └── ws.rs │ │ │ ├── lib.rs │ │ │ └── state.rs │ └── hardhat │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── rocksdb │ ├── Cargo.toml │ └── src │ │ ├── blockstore.rs │ │ ├── kvstore.rs │ │ ├── lib.rs │ │ ├── namespaces.rs │ │ └── rocks │ │ ├── config.rs │ │ ├── error.rs │ │ └── mod.rs ├── rpc │ ├── Cargo.toml │ ├── examples │ │ └── simplecoin.rs │ └── src │ │ ├── client.rs │ │ ├── lib.rs │ │ ├── message.rs │ │ ├── query.rs │ │ ├── response.rs │ │ └── tx.rs ├── storage │ ├── Cargo.toml │ └── src │ │ ├── im.rs │ │ ├── lib.rs │ │ └── testing.rs ├── testing │ ├── .gitignore │ ├── Cargo.toml │ ├── Makefile │ ├── README.md │ ├── contract-test │ │ ├── Cargo.toml │ │ ├── src │ │ │ ├── ipc │ │ │ │ ├── mod.rs │ │ │ │ ├── registry.rs │ │ │ │ └── subnet.rs │ │ │ └── lib.rs │ │ └── tests │ │ │ ├── smt_staking.rs │ │ │ └── staking │ │ │ ├── machine.rs │ │ │ ├── mod.rs │ │ │ └── state.rs │ ├── contracts │ │ ├── SimpleCoin.abi │ │ ├── SimpleCoin.bin │ │ ├── SimpleCoin.bin-runtime │ │ ├── SimpleCoin.signatures │ │ ├── SimpleCoin.sol │ │ └── SimpleCoin_storage.json │ ├── scripts │ │ ├── ci.env │ │ ├── common.env │ │ ├── common.toml │ │ └── fendermint.toml │ ├── smoke-test │ │ ├── Cargo.toml │ │ ├── Makefile.toml │ │ ├── scripts │ │ │ ├── init.sh │ │ │ └── smoke.env │ │ └── src │ │ │ └── lib.rs │ ├── snapshot-test │ │ ├── Cargo.toml │ │ ├── Makefile.toml │ │ ├── scripts │ │ │ ├── init.sh │ │ │ ├── node-1.env │ │ │ ├── node-2.env │ │ │ ├── node-3.env │ │ │ └── snapshot.env │ │ └── src │ │ │ └── lib.rs │ └── src │ │ ├── arb │ │ ├── address.rs │ │ ├── cid.rs │ │ ├── message.rs │ │ ├── mod.rs │ │ ├── subnetid.rs │ │ └── token.rs │ │ ├── golden.rs │ │ ├── lib.rs │ │ └── smt.rs └── vm │ ├── actor_interface │ ├── Cargo.toml │ └── src │ │ ├── account.rs │ │ ├── burntfunds.rs │ │ ├── cron.rs │ │ ├── diamond.rs │ │ ├── eam.rs │ │ ├── ethaccount.rs │ │ ├── evm.rs │ │ ├── init.rs │ │ ├── ipc.rs │ │ ├── lib.rs │ │ ├── multisig.rs │ │ ├── placeholder.rs │ │ ├── reward.rs │ │ └── system.rs │ ├── core │ ├── Cargo.toml │ └── src │ │ ├── chainid.rs │ │ ├── lib.rs │ │ └── timestamp.rs │ ├── encoding │ ├── Cargo.toml │ └── src │ │ └── lib.rs │ ├── genesis │ ├── Cargo.toml │ ├── golden │ │ └── genesis │ │ │ ├── cbor │ │ │ ├── genesis.cbor │ │ │ └── genesis.txt │ │ │ └── json │ │ │ ├── genesis.json │ │ │ └── genesis.txt │ ├── src │ │ ├── arb.rs │ │ └── lib.rs │ └── tests │ │ └── golden.rs │ ├── interpreter │ ├── Cargo.toml │ └── src │ │ ├── bytes.rs │ │ ├── chain.rs │ │ ├── fvm │ │ ├── broadcast.rs │ │ ├── bundle.rs │ │ ├── check.rs │ │ ├── checkpoint.rs │ │ ├── exec.rs │ │ ├── externs.rs │ │ ├── genesis.rs │ │ ├── mod.rs │ │ ├── query.rs │ │ ├── state │ │ │ ├── check.rs │ │ │ ├── exec.rs │ │ │ ├── fevm.rs │ │ │ ├── genesis.rs │ │ │ ├── ipc.rs │ │ │ ├── mod.rs │ │ │ ├── query.rs │ │ │ └── snapshot.rs │ │ ├── store │ │ │ ├── memory.rs │ │ │ └── mod.rs │ │ └── topdown.rs │ │ ├── lib.rs │ │ └── signed.rs │ ├── message │ ├── Cargo.toml │ ├── golden │ │ ├── chain │ │ │ ├── ipc_bottom_up_exec.cbor │ │ │ ├── ipc_bottom_up_exec.txt │ │ │ ├── ipc_bottom_up_resolve.cbor │ │ │ ├── ipc_bottom_up_resolve.txt │ │ │ ├── ipc_top_down.cbor │ │ │ ├── ipc_top_down.txt │ │ │ ├── signed.cbor │ │ │ └── signed.txt │ │ ├── fvm │ │ │ ├── message.cbor │ │ │ ├── message.cid │ │ │ └── message.txt │ │ └── query │ │ │ ├── request │ │ │ ├── actor_state.cbor │ │ │ ├── actor_state.txt │ │ │ ├── ipld.cbor │ │ │ └── ipld.txt │ │ │ └── response │ │ │ ├── actor_state.cbor │ │ │ └── actor_state.txt │ ├── src │ │ ├── chain.rs │ │ ├── conv │ │ │ ├── from_eth.rs │ │ │ ├── from_fvm.rs │ │ │ └── mod.rs │ │ ├── ipc.rs │ │ ├── lib.rs │ │ ├── query.rs │ │ └── signed.rs │ └── tests │ │ └── golden.rs │ ├── resolver │ ├── Cargo.toml │ └── src │ │ ├── ipld.rs │ │ ├── lib.rs │ │ └── pool.rs │ ├── snapshot │ ├── Cargo.toml │ ├── golden │ │ └── manifest │ │ │ ├── cbor │ │ │ ├── manifest.cbor │ │ │ └── manifest.txt │ │ │ └── json │ │ │ ├── manifest.json │ │ │ └── manifest.txt │ ├── src │ │ ├── car │ │ │ ├── chunker.rs │ │ │ ├── mod.rs │ │ │ └── streamer.rs │ │ ├── client.rs │ │ ├── error.rs │ │ ├── lib.rs │ │ ├── manager.rs │ │ ├── manifest.rs │ │ └── state.rs │ └── tests │ │ └── golden.rs │ └── topdown │ ├── Cargo.toml │ └── src │ ├── cache.rs │ ├── convert.rs │ ├── error.rs │ ├── finality │ ├── fetch.rs │ ├── mod.rs │ └── null.rs │ ├── lib.rs │ ├── proxy.rs │ ├── sync │ ├── mod.rs │ ├── pointers.rs │ ├── syncer.rs │ └── tendermint.rs │ └── toggle.rs ├── infra ├── .env ├── Cargo.toml ├── Makefile.toml ├── docker-compose.yml ├── run.sh └── scripts │ ├── cometbft.toml │ ├── docker.toml │ ├── ethapi.toml │ ├── fendermint.toml │ ├── genesis.toml │ ├── node.toml │ ├── subnet.toml │ ├── testnet.toml │ └── testnode.toml ├── rust-toolchain.toml ├── rustfmt.toml └── scripts ├── add_license.sh └── copyright.txt /.dockerignore: -------------------------------------------------------------------------------- 1 | target 2 | builtin-actors 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: IPC Issue Template 2 | description: Use this template to report bugs and other issues 3 | labels: [bug] 4 | body: 5 | - type: dropdown 6 | id: issue-type 7 | attributes: 8 | label: Issue type 9 | description: What type of issue would you like to report? 10 | multiple: false 11 | options: 12 | - Bug 13 | - Build/Install 14 | - Performance 15 | - Support 16 | - Feature Request 17 | - Documentation Bug 18 | - Documentation Request 19 | - Others 20 | validations: 21 | required: true 22 | 23 | - type: dropdown 24 | id: latest 25 | attributes: 26 | label: Have you reproduced the bug with the latest dev version? 27 | description: We suggest attempting to reproducing the bug with the dev branch 28 | options: 29 | - "Yes" 30 | - "No" 31 | validations: 32 | required: true 33 | 34 | - type: input 35 | id: version 36 | attributes: 37 | label: Version 38 | placeholder: e.g. v0.4.0 39 | validations: 40 | required: true 41 | - type: dropdown 42 | id: Code 43 | attributes: 44 | label: Custom code 45 | options: 46 | - "Yes" 47 | - "No" 48 | validations: 49 | required: true 50 | - type: input 51 | id: OS 52 | attributes: 53 | label: OS platform and distribution 54 | placeholder: e.g., Linux Ubuntu 16.04 55 | - type: textarea 56 | id: what-happened 57 | attributes: 58 | label: Describe the issue 59 | description: Also tell us, what did you expect to happen? 60 | placeholder: | 61 | This is where you get to tell us what went wrong, when doing so, please try to provide a clear and concise description of the bug with all related information: 62 | * What you were doing when you experienced the bug? What are you trying to build? 63 | * Any *error* messages and logs you saw, *where* you saw them, and what you believe may have caused them (if you have any ideas). 64 | * What is the expected behaviour? Links to the code? 65 | validations: 66 | required: true 67 | - type: textarea 68 | id: repro-steps 69 | attributes: 70 | label: Repro steps 71 | description: Provide the minimum necessary steps to reproduce the problem. 72 | placeholder: Tell us what you see! 73 | validations: 74 | required: true 75 | - type: textarea 76 | id: logs 77 | attributes: 78 | label: Relevant log output 79 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 80 | render: shell 81 | -------------------------------------------------------------------------------- /.github/actions/docker-push/action.yaml: -------------------------------------------------------------------------------- 1 | 2 | # Useful links: 3 | # https://docs.github.com/en/packages/managing-github-packages-using-github-actions-workflows/publishing-and-installing-a-package-with-github-actions#upgrading-a-workflow-that-accesses-a-registry-using-a-personal-access-token 4 | # https://docs.github.com/en/actions/using-workflows/reusing-workflows 5 | # https://stackoverflow.com/a/71489231 6 | 7 | name: Docker Push 8 | description: "Publish the Docker image to Github Container Registry" 9 | inputs: 10 | repo-token: 11 | description: "secrets.GITHUB_TOKEN; used for logging in with ghcr.io" 12 | required: true 13 | repo-owner: 14 | description: "github.repository_owner; used as the organisation for the Docker image" 15 | required: true 16 | image-name: 17 | description: "Docker image name" 18 | required: true 19 | 20 | runs: 21 | using: "composite" 22 | 23 | steps: 24 | - name: Log in to registry 25 | shell: bash 26 | run: echo "${{ inputs.repo-token }}" | docker login ghcr.io -u $ --password-stdin 27 | 28 | - name: Push image 29 | shell: bash 30 | run: | 31 | IMAGE_ID=ghcr.io/${{ inputs.repo-owner }}/${{ inputs.image-name }} 32 | 33 | # This changes all uppercase characters to lowercase. 34 | IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') 35 | 36 | # This strips the git ref prefix from the version. 37 | VERSION=$(echo "${{ github.ref }}" | sed -e 's,.*/\(.*\),\1,') 38 | 39 | # This strips the "v" prefix from the tag name. 40 | [[ "${{ github.ref }}" == "refs/tags/"* ]] && VERSION=$(echo $VERSION | sed -e 's/^v//') 41 | 42 | # This uses the Docker `latest` tag convention. 43 | [ "$VERSION" == "main" ] && VERSION=latest 44 | 45 | echo IMAGE_ID=$IMAGE_ID 46 | echo VERSION=$VERSION 47 | 48 | docker tag ${{ inputs.image-name }} $IMAGE_ID:$VERSION 49 | docker push $IMAGE_ID:$VERSION 50 | -------------------------------------------------------------------------------- /.github/actions/install-tools/action.yaml: -------------------------------------------------------------------------------- 1 | name: Install Tools 2 | description: "Install platform dependencies and tools" 3 | inputs: 4 | repo-token: 5 | description: "secrets.GITHUB_TOKEN" 6 | required: true 7 | rust: 8 | description: "Rust toolchain name" 9 | required: true 10 | 11 | runs: 12 | using: "composite" 13 | 14 | steps: 15 | - name: Install Rust 16 | uses: dtolnay/rust-toolchain@master 17 | with: 18 | targets: wasm32-unknown-unknown 19 | toolchain: ${{ inputs.rust }} 20 | components: rustfmt,clippy 21 | 22 | - name: Install Cargo Make 23 | uses: davidB/rust-cargo-make@v1 24 | 25 | # Protobuf compiler required by libp2p-core 26 | - name: Install Protoc 27 | uses: arduino/setup-protoc@v1 28 | with: 29 | repo-token: ${{ inputs.repo-token }} 30 | 31 | # For compiling Solidity contracts 32 | # - name: Install Foundry 33 | # uses: foundry-rs/foundry-toolchain@v1 34 | 35 | - name: 'Install jq' 36 | uses: dcarbone/install-jq-action@v2.1.0 37 | 38 | # See https://github.com/docker/setup-buildx-action 39 | - name: Set up Docker Buildx 40 | uses: docker/setup-buildx-action@v3 41 | -------------------------------------------------------------------------------- /.github/actions/setup-cache/action.yaml: -------------------------------------------------------------------------------- 1 | name: Setup Cache 2 | description: "Setup Rust artifact caching" 3 | inputs: 4 | cache-prefix: 5 | description: "Caching key used for matching caches" 6 | required: true 7 | cache-suffix: 8 | description: "Caching suffix used to make keys unique and force uploads" 9 | required: true 10 | 11 | runs: 12 | using: "composite" 13 | 14 | steps: 15 | - name: Setup sccache 16 | # uses: hanabi1224/sccache-action@v1.2.0 # https://github.com/hanabi1224/sccache-action used by Forest. 17 | uses: consensus-shipyard/sccache-action@fendermint 18 | with: 19 | release-name: v0.3.1 20 | cache-key: ${{ inputs.cache-prefix }} 21 | cache-suffix: ${{ inputs.cache-suffix }} 22 | # We should never have to update a cache as long as we use unique suffixes, it happens automatically. 23 | cache-update: false 24 | # Don't append the date to the cache, the unique suffix should take care of it. 25 | cache-date: false 26 | -------------------------------------------------------------------------------- /.github/workflows/add-bug-tracker.yaml: -------------------------------------------------------------------------------- 1 | name: Add bugs to tracker 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | - labeled 8 | 9 | jobs: 10 | add-to-project: 11 | name: Add issue to tracker 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/add-to-project@v0.5.0 15 | with: 16 | project-url: https://github.com/orgs/consensus-shipyard/projects/3 17 | github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} 18 | labeled: bug -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | builtin-actors 3 | docker/.artifacts 4 | docker/Dockerfile 5 | cometbft 6 | test-network 7 | .idea 8 | .make 9 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 2 | 3 | http://www.apache.org/licenses/LICENSE-2.0 4 | 5 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 6 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /docker/builder.local.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # Builder 4 | FROM rust:bookworm as builder 5 | 6 | RUN apt-get update && \ 7 | apt-get install -y build-essential clang cmake protobuf-compiler && \ 8 | rm -rf /var/lib/apt/lists/* 9 | 10 | WORKDIR /app 11 | 12 | COPY . . 13 | 14 | # Mounting speeds up local builds, but it doesn't get cached between builds on CI. 15 | # OTOH it seems like one platform build can be blocked trying to acquire a lock on the build directory, 16 | # so for cross builds this is probably not a good idea. 17 | RUN --mount=type=cache,target=target \ 18 | --mount=type=cache,target=$RUSTUP_HOME,from=rust,source=$RUSTUP_HOME \ 19 | --mount=type=cache,target=$CARGO_HOME,from=rust,source=$CARGO_HOME \ 20 | cargo install --locked --root output --path fendermint/app 21 | -------------------------------------------------------------------------------- /docker/runner.Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | 3 | # The builder and runner are in separate Dockerfile so that we can use different caching strategies 4 | # in the builder depending on whether we are building on CI or locally, but they are concatenated 5 | # just before the build. 6 | 7 | FROM debian:bookworm-slim 8 | 9 | RUN apt-get update && \ 10 | apt-get install -y libssl3 ca-certificates && \ 11 | rm -rf /var/lib/apt/lists/* 12 | 13 | ENV FM_HOME_DIR=/fendermint 14 | ENV HOME=$FM_HOME_DIR 15 | WORKDIR $FM_HOME_DIR 16 | 17 | EXPOSE 26658 18 | 19 | ENTRYPOINT ["fendermint"] 20 | CMD ["run"] 21 | 22 | STOPSIGNAL SIGTERM 23 | 24 | ENV FM_ABCI__LISTEN__HOST=0.0.0.0 25 | ENV FM_ETH__LISTEN__HOST=0.0.0.0 26 | 27 | COPY docker/.artifacts/bundle.car $FM_HOME_DIR/bundle.car 28 | COPY docker/.artifacts/contracts $FM_HOME_DIR/contracts 29 | COPY --from=builder /app/fendermint/app/config $FM_HOME_DIR/config 30 | COPY --from=builder /app/output/bin/fendermint /usr/local/bin/fendermint 31 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | ## Docs 2 | 3 | The following documentation should help getting oriented: 4 | 5 | * [Architecture Overview](./architecture.md) 6 | * [Getting started with Tendermint](./tendermint.md) 7 | * [Running Fendermint](./running.md) 8 | * [Checkpointing](./checkpointing.md) 9 | * [Running IPC infrastructure](./ipc.md) 10 | 11 | You can also check out the previous demos: 12 | * [Milestone-1 Demo](./demos/milestone-1/README.md) 13 | -------------------------------------------------------------------------------- /docs/checkpointing.md: -------------------------------------------------------------------------------- 1 | # Checkpointing 2 | 3 | Bottom-up checkpoints are periodically submitted to the parent subnet, carrying: 4 | * bottom-up messages 5 | * the next highest configuration number adopted form the validator changesets observed on the parent 6 | * a multi-sig from the current validator set 7 | * the identity of the checkpointed block height 8 | 9 | The high level steps are implemented in the [checkpoint](../fendermint/vm/interpreter/src/fvm/checkpoint.rs) module, 10 | which calls various methods on the [Gateway actor](https://github.com/consensus-shipyard/ipc-solidity-actors/tree/dev/src/gateway), 11 | but the end-to-end flow also relies on a working [IPC Agent](https://github.com/consensus-shipyard/ipc/) 12 | and potentially the [IPLD Resolver](https://github.com/consensus-shipyard/ipc-ipld-resolver). 13 | 14 | The following diagram illustrates the sequence of events in detail: 15 | 16 | ![Checkpointing](diagrams/checkpointing.png) 17 | 18 | The above scenario assumes that the parent subnet is running Lotus, where we are restricted to using Solidity actors, 19 | and therefore the relayers include all bottom-up messages in their transaction, which creates redundancy but makes the 20 | messages trivially available for execution. 21 | 22 | If both the parent and the child were Fendermint nodes, we'd have the option to use the IPLD Resolver to only include the CID 23 | of the messages in the relayed checkpoint messages, and let Fendermint make sure the data is available before proposing it 24 | for execution. 25 | -------------------------------------------------------------------------------- /docs/demos/milestone-1/Fendermint_Demo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/consensus-shipyard/fendermint/72f676595f4b411e673bf2e65375d6c13b67fd4a/docs/demos/milestone-1/Fendermint_Demo.pdf -------------------------------------------------------------------------------- /docs/demos/milestone-1/README.md: -------------------------------------------------------------------------------- 1 | # Milestone 1 Demo 2 | 3 | This demo shows that FVM has been integrated into an ABCI application. It doesn't contain anything IPC specific. 4 | 5 | * [Overview slides](./Fendermint_Demo.pdf) 6 | * [Overview video](https://drive.google.com/file/d/1fv1rVp9cbGuQho5jIqUCHSziJpmId57y/view?usp=sharing) 7 | * [Demo video](https://drive.google.com/file/d/1-UvOk0qb3nQQQd2SczW6uWFRIMpBDUyB/view?usp=sharing) 8 | * [Demo script](./fendermint-demo.sh) 9 | 10 | This demo was preformed with [Tendermint Core v0.37.0-rc2](https://github.com/tendermint/tendermint/blob/v0.37.0-rc2/docs/introduction/install.md) (with `git checkout v0.37.0-rc2`), before we switched to CometBFT, so we ran `tendermint` commands, not `cometbft`. 11 | -------------------------------------------------------------------------------- /docs/demos/milestone-1/fendermint-demo.sh: -------------------------------------------------------------------------------- 1 | #0 2 | cargo install --locked --path fendermint/app 3 | 4 | #1 5 | 6 | mkdir test-network 7 | 8 | #2 9 | fendermint genesis --genesis-file test-network/genesis.json new --network-name test --base-fee 1000 --timestamp 1680101412 10 | 11 | #3 12 | cat test-network/genesis.json 13 | 14 | #4 15 | mkdir test-network/keys 16 | for NAME in alice bob charlie dave; do 17 | fendermint key gen --out-dir test-network/keys --name $NAME; 18 | done 19 | 20 | #5 21 | ls test-network/keys 22 | cat test-network/keys/alice.pk 23 | 24 | #6 25 | fendermint \ 26 | genesis --genesis-file test-network/genesis.json \ 27 | add-account --public-key test-network/keys/alice.pk \ 28 | --balance 1000000000000000000 29 | 30 | #7 31 | fendermint \ 32 | genesis --genesis-file test-network/genesis.json \ 33 | add-multisig --public-key test-network/keys/bob.pk \ 34 | --public-key test-network/keys/charlie.pk \ 35 | --public-key test-network/keys/dave.pk \ 36 | --threshold 2 --vesting-start 0 --vesting-duration 1000000 \ 37 | --balance 3000000000000000000 38 | 39 | #8 40 | cat test-network/genesis.json | jq .accounts 41 | 42 | #9 43 | fendermint \ 44 | genesis --genesis-file test-network/genesis.json \ 45 | add-validator --public-key test-network/keys/bob.pk --power 1 46 | 47 | #10 48 | rm -rf ~/.tendermint 49 | tendermint init 50 | 51 | #11 52 | fendermint \ 53 | genesis --genesis-file test-network/genesis.json \ 54 | into-tendermint --out ~/.tendermint/config/genesis.json 55 | 56 | #12 57 | cat ~/.tendermint/config/genesis.json 58 | 59 | #13 60 | fendermint \ 61 | key into-tendermint --secret-key test-network/keys/bob.sk \ 62 | --out ~/.tendermint/config/priv_validator_key.json 63 | 64 | #14 65 | cat ~/.tendermint/config/priv_validator_key.json 66 | cat test-network/keys/bob.pk 67 | 68 | #15 69 | make actor-bundle 70 | 71 | #16 72 | rm -rf ~/.fendermint/data 73 | mkdir -p ~/.fendermint/data 74 | cp -r ./fendermint/app/config ~/.fendermint/config 75 | cp ./builtin-actors/output/bundle.car ~/.fendermint/bundle.car 76 | 77 | #17 78 | fendermint run 79 | 80 | #18 81 | tendermint unsafe-reset-all 82 | tendermint start 83 | 84 | #19 85 | ALICE_ADDR=$(fendermint key address --public-key test-network/keys/alice.pk) 86 | BOB_ADDR=$(fendermint key address --public-key test-network/keys/bob.pk) 87 | 88 | #20 89 | fendermint rpc query actor-state --address $ALICE_ADDR 90 | 91 | #21 92 | STATE_CID=$(fendermint rpc query actor-state --address $ALICE_ADDR | jq -r .state.state) 93 | fendermint rpc query ipld --cid $STATE_CID 94 | 95 | #22 96 | fendermint rpc transfer --secret-key test-network/keys/alice.sk --to $BOB_ADDR --sequence 0 --value 1000 97 | 98 | #23 99 | fendermint rpc query actor-state --address $BOB_ADDR | jq .state.balance 100 | 101 | #24 102 | make ../builtin-actors 103 | fendermint \ 104 | rpc fevm --secret-key test-network/keys/alice.sk --sequence 1 \ 105 | create --contract ../builtin-actors/actors/evm/tests/contracts/SimpleCoin.bin 106 | 107 | #25 108 | fendermint \ 109 | rpc fevm --secret-key test-network/keys/alice.sk --sequence 2 \ 110 | invoke --contract \ 111 | --method f8b2cb4f --method-args 000000000000000000000000ff00000000000000000000000000000000000064 112 | 113 | #26 114 | cargo run -p fendermint_rpc --release \ 115 | --example simplecoin -- \ 116 | --secret-key test-network/keys/alice.sk --verbose 117 | -------------------------------------------------------------------------------- /docs/diagrams/.gitignore: -------------------------------------------------------------------------------- 1 | plantuml.jar 2 | -------------------------------------------------------------------------------- /docs/diagrams/Makefile: -------------------------------------------------------------------------------- 1 | PUMLS = $(shell find . -type f -name "*.puml") 2 | PNGS = $(PUMLS:.puml=.png) 3 | PUML_VER=1.2023.2 4 | 5 | .PHONY: all 6 | all: diagrams 7 | 8 | .PHONY: diagrams 9 | diagrams: $(PNGS) 10 | 11 | plantuml.jar: 12 | wget -O $@ https://github.com/plantuml/plantuml/releases/download/v$(PUML_VER)/plantuml-$(PUML_VER).jar --no-check-certificate --quiet 13 | 14 | %.png: plantuml.jar %.puml 15 | @# Using pipelining to preserve file names. 16 | cat $*.puml | java -DPLANTUML_LIMIT_SIZE=8192 -jar plantuml.jar -pipe > $*.png 17 | -------------------------------------------------------------------------------- /docs/diagrams/README.md: -------------------------------------------------------------------------------- 1 | # Diagrams 2 | 3 | This directory contains [PlantUML](https://plantuml.com/) diagrams which are turned into images ready to be embedded into docs. 4 | 5 | To render the images, run the following command: 6 | 7 | ```shell 8 | make diagrams 9 | ``` 10 | 11 | ## Automation 12 | 13 | Adding the following script to `.git/hooks/pre-commit` automatically renders and checks in the images when we commit changes to their source diagrams. CI should also check that there are no uncommitted changes. 14 | 15 | ```bash 16 | #!/usr/bin/env bash 17 | 18 | # If any command fails, exit immediately with that command's exit status 19 | set -eo pipefail 20 | 21 | # Redirect output to stderr. 22 | exec 1>&2 23 | 24 | if git diff --cached --name-only --diff-filter=d | grep .puml 25 | then 26 | make diagrams 27 | git add docs/diagrams/*.png 28 | fi 29 | ``` 30 | -------------------------------------------------------------------------------- /docs/diagrams/checkpointing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/consensus-shipyard/fendermint/72f676595f4b411e673bf2e65375d6c13b67fd4a/docs/diagrams/checkpointing.png -------------------------------------------------------------------------------- /docs/images/IPC with Tendermint Core.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/consensus-shipyard/fendermint/72f676595f4b411e673bf2e65375d6c13b67fd4a/docs/images/IPC with Tendermint Core.jpg -------------------------------------------------------------------------------- /docs/localnet.md: -------------------------------------------------------------------------------- 1 | # Local Testnets 2 | 3 | Setting up a Fendermint local testnet is a way to get started quickly with IPC. 4 | 5 | This guide offers two flavours: 6 | 7 | - A single node deployment: useful for developing smart contracts and testing the APIs. 8 | - A 4 node testnet: useful for testing consensus, checkpointing, and more. 9 | 10 | ## Prerequisites 11 | 12 | On Linux (links and instructions for Ubuntu): 13 | 14 | - Install Docker. See [instructions](https://docs.docker.com/engine/install/ubuntu/). 15 | - Install Rust. See [instructions](https://www.rust-lang.org/tools/install). 16 | - Install cargo-make: `cargo install --force cargo-make`. 17 | 18 | ## Docker images 19 | 20 | These commands will pull various Docker images from remote repositories, including `fendermint:latest`, by default. 21 | 22 | - To override which Fendermint Docker image to pull, set the `FM_DOCKER_TAG` env variable to the desired tag. 23 | - To use a local Fendermint image, set the `FM_PULL_SKIP` env variable to some value, e.g. `FM_PULL_SKIP=true`. 24 | 25 | ## Single node deployment 26 | 27 | To run IPC in the local rootnet just perform the following: 28 | 29 | ```bash 30 | cargo make --makefile ./infra/Makefile.toml testnode 31 | ``` 32 | 33 | It will create three docker containers (cometbft, fendermint, and eth-api). 34 | 35 | To stop run the following: 36 | ```bash 37 | cargo make --makefile ./infra/Makefile.toml testnode-down 38 | ``` 39 | 40 | ## Local 4-nodes deployment 41 | 42 | To run IPC in the local rootnet with 4 nodes perform the following command: 43 | 44 | ```bash 45 | cargo make --makefile ./infra/Makefile.toml testnet 46 | ``` 47 | 48 | To stop the network: 49 | 50 | ```bash 51 | cargo make --makefile ./infra/Makefile.toml testnet-down 52 | ``` 53 | 54 | The testnet contains four logical nodes. Each node consists of cometbft, fendermint, and ethapi containers. 55 | The Docker internal network is `192.167.10.0/24`. 56 | 57 | The Ethereum API is accessible on the following endpoints on the Docker internal network: 58 | 59 | - `192.167.10.10:8545` or `ethapi-node0:8545` 60 | - `192.167.10.11:8545` or `ethapi-node1:8545` 61 | - `192.167.10.12:8545` or `ethapi-node2:8545` 62 | - `192.167.10.13:8545` or `ethapi-node3:8545` 63 | 64 | And on the following endpoints from the host machine: 65 | 66 | - `127.0.0.1:8545` 67 | - `127.0.0.1:8546` 68 | - `127.0.0.1:8547` 69 | - `127.0.0.1:8548` 70 | 71 | ## What's happening behind the scenes 72 | 73 | > For a 4-node deployment. 74 | 75 | The deployment process performs the following steps: 76 | 77 | - Remove all Docker containers, files, networks, etc. from any previous deployments. 78 | - Create all necessary directories. 79 | - Initialize CometBFT testnet by creating `config` and `data` directories using `cometbft` tools. 80 | - Read CometBFT nodes private keys, derive node IDs and store them in `config.toml` for each node. 81 | - Create the `genesis` file for Fendermint. 82 | - Share the genesis among all Fendermint nodes. 83 | - Run Fendermint application in 4 containers. 84 | - Run CometBFT in 4 containers. 85 | - Run Eth API in 4 containers. 86 | -------------------------------------------------------------------------------- /fendermint/abci/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_abci" 3 | description = "ABCI++ adapter using tendermint-rs and tower-abci" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | async-trait = { workspace = true } 11 | futures = { workspace = true } 12 | tower = "0.4" 13 | 14 | tower-abci = { workspace = true } 15 | tendermint = { workspace = true } 16 | 17 | 18 | [dev-dependencies] 19 | async-stm = { workspace = true } 20 | im = { workspace = true } 21 | structopt = "0.3" 22 | tokio = { workspace = true } 23 | tracing = { workspace = true } 24 | tracing-subscriber = { workspace = true } 25 | -------------------------------------------------------------------------------- /fendermint/abci/README.md: -------------------------------------------------------------------------------- 1 | # ABCI Adapter 2 | 3 | This library borrows from `tendermint-rs/abci` to define an async `Application` trait, and adapts it to the interface `tower-abci` expects, so that we can use the `Server` in `tower-abci` to serve requests coming from Tendermint Core. 4 | 5 | ## Example 6 | 7 | See the [kvstore](./examples/kvstore.rs) for using it. To try, you'll need [tendermint](../../docs/tendermint.md). 8 | 9 | Start the `kvstore`: 10 | 11 | ```shell 12 | cargo run --example kvstore 13 | ``` 14 | 15 | Start `tendermint`: 16 | 17 | ```shell 18 | tendermint unsafe-reset-all && tendermint start 19 | ``` 20 | 21 | Send a transaction: 22 | 23 | ```shell 24 | curl -s 'localhost:26657/broadcast_tx_commit?tx="foo=bar"' 25 | ``` 26 | 27 | Send a query: 28 | 29 | ```console 30 | $ curl -s 'localhost:26657/abci_query?data="foo"' | jq -r ".result.response.value | @base64d" 31 | bar 32 | ``` 33 | 34 | If all goes well, logs should look like this: 35 | 36 | ```console 37 | ❯ cargo run --example kvstore 38 | 39 | Finished dev [unoptimized + debuginfo] target(s) in 0.10s 40 | Running `target/debug/examples/kvstore` 41 | 2023-01-13T11:35:50.444279Z INFO tower_abci::server: starting ABCI server addr="127.0.0.1:26658" 42 | 2023-01-13T11:35:50.444411Z INFO tower_abci::server: bound tcp listener local_addr=127.0.0.1:26658 43 | 2023-01-13T11:35:54.099202Z INFO tower_abci::server: listening for requests 44 | 2023-01-13T11:35:54.099353Z INFO tower_abci::server: listening for requests 45 | 2023-01-13T11:35:54.099766Z INFO tower_abci::server: listening for requests 46 | 2023-01-13T11:35:54.099836Z INFO tower_abci::server: listening for requests 47 | 2023-01-13T11:35:55.200926Z INFO kvstore: commit retain_height=block::Height(0) 48 | 2023-01-13T11:35:56.237514Z INFO kvstore: commit retain_height=block::Height(1) 49 | 2023-01-13T11:35:57.323522Z INFO kvstore: commit retain_height=block::Height(2) 50 | 2023-01-13T11:35:58.309203Z INFO kvstore: update key="foo" value="bar" 51 | 2023-01-13T11:35:58.314724Z INFO kvstore: commit retain_height=block::Height(3) 52 | ``` 53 | -------------------------------------------------------------------------------- /fendermint/abci/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | mod application; 4 | 5 | pub use application::{AbciResult, Application, ApplicationService}; 6 | pub mod util; 7 | -------------------------------------------------------------------------------- /fendermint/abci/src/util.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | /// Take the first transactions until the first one that would exceed the maximum limit. 5 | /// 6 | /// The function does not skip or reorder transaction even if a later one would stay within the limit. 7 | pub fn take_until_max_size>(txs: Vec, max_tx_bytes: usize) -> Vec { 8 | let mut size: usize = 0; 9 | let mut out = Vec::new(); 10 | for tx in txs { 11 | let bz: &[u8] = tx.as_ref(); 12 | if size.saturating_add(bz.len()) > max_tx_bytes { 13 | break; 14 | } 15 | size += bz.len(); 16 | out.push(tx); 17 | } 18 | out 19 | } 20 | -------------------------------------------------------------------------------- /fendermint/app/config/test.toml: -------------------------------------------------------------------------------- 1 | # These setting are overlayed over `default.toml` in unit tests 2 | # to exercise parsing values that are not viable as defaults. 3 | 4 | [validator_key] 5 | path = "dummy.sk" 6 | kind = "ethereum" 7 | 8 | [resolver] 9 | subnet_id = "/r31415926" 10 | 11 | [resolver.membership] 12 | static_subnets = [ 13 | "/r31415926/f2xwzbdu7z5sam6hc57xxwkctciuaz7oe5omipwbq", 14 | ] 15 | 16 | [resolver.discovery] 17 | static_addresses = [ 18 | "/ip4/198.51.100.0/tcp/4242/p2p/QmYyQSo1c1Ym7orWxLYvCrM2EmxFTANf8wXmmE7DWjhx5N", 19 | "/ip6/2604:1380:2000:7a00::1/udp/4001/quic/p2p/QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb", 20 | "/ip4/192.0.2.0/tcp/5002/p2p/QmdPU7PfRyKehdrP5A3WqmjyD6bhVpU1mLGKppa2FjGDjZ/p2p-circuit/p2p/QmVT6GYwjeeAF5TR485Yc58S3xRF5EFsZ5YAF4VcP3URHt", 21 | ] 22 | 23 | [resolver.connection] 24 | listen_addr = "/ip4/198.51.100.2/tcp/1234" 25 | -------------------------------------------------------------------------------- /fendermint/app/options/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_app_options" 3 | description = "Fendermint CLI options" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | bytes = { workspace = true } 14 | clap = { workspace = true } 15 | hex = { workspace = true } 16 | num-traits = { workspace = true } 17 | tendermint-rpc = { workspace = true } 18 | tracing = { workspace = true } 19 | 20 | cid = { workspace = true } 21 | fvm_ipld_encoding = { workspace = true } 22 | fvm_shared = { workspace = true } 23 | ipc-sdk = { workspace = true } 24 | primitives = { workspace = true } 25 | url = { workspace = true } 26 | 27 | 28 | fendermint_vm_actor_interface = { path = "../../vm/actor_interface" } 29 | -------------------------------------------------------------------------------- /fendermint/app/options/examples/network.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Examples of passing CLI options. Some are tricky and require some values to be parsed first. 4 | //! These examples are here so we have an easier way to test them than having to compile the app. 5 | //! 6 | //! ```text 7 | //! cargo run --example network -- --help 8 | //! cargo run --example network -- --network 1 genesis --genesis-file ./genesis.json ipc gateway --subnet-id /r123/t0456 -b 10 -t 10 -c 1.5 -f 10 -m 65 9 | //! FVM_NETWORK=testnet cargo run --example network -- genesis --genesis-file ./genesis.json ipc gateway --subnet-id /r123/t0456 -b 10 -t 10 -c 1.5 -f 10 -m 65 10 | //! ``` 11 | 12 | use clap::Parser; 13 | 14 | use fendermint_app_options::{GlobalOptions, Options}; 15 | 16 | pub fn main() { 17 | let opts: GlobalOptions = GlobalOptions::parse(); 18 | println!("command: {:?}", opts.cmd); 19 | 20 | let n = opts.global.network; 21 | println!("setting current network: {n:?}"); 22 | fvm_shared::address::set_current_network(n); 23 | 24 | let opts: Options = Options::parse(); 25 | 26 | println!("{opts:?}"); 27 | } 28 | -------------------------------------------------------------------------------- /fendermint/app/options/src/eth.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use clap::{Args, Subcommand}; 5 | use tendermint_rpc::{Url, WebSocketClientUrl}; 6 | 7 | #[derive(Args, Debug)] 8 | pub struct EthArgs { 9 | #[command(subcommand)] 10 | pub command: EthCommands, 11 | } 12 | 13 | #[derive(Subcommand, Debug, Clone)] 14 | pub enum EthCommands { 15 | /// Run the Ethereum JSON-RPC facade. 16 | Run { 17 | /// The URL of the Tendermint node's RPC endpoint. 18 | #[arg( 19 | long, 20 | short, 21 | default_value = "http://127.0.0.1:26657", 22 | env = "TENDERMINT_RPC_URL" 23 | )] 24 | http_url: Url, 25 | 26 | /// The URL of the Tendermint node's WebSocket endpoint. 27 | #[arg( 28 | long, 29 | short, 30 | default_value = "ws://127.0.0.1:26657/websocket", 31 | env = "TENDERMINT_WS_URL" 32 | )] 33 | ws_url: WebSocketClientUrl, 34 | 35 | /// Seconds to wait between trying to connect to the websocket. 36 | #[arg(long, short = 'd', default_value = "5")] 37 | connect_retry_delay: u64, 38 | }, 39 | } 40 | -------------------------------------------------------------------------------- /fendermint/app/options/src/key.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use std::path::PathBuf; 5 | 6 | use clap::{Args, Subcommand}; 7 | 8 | #[derive(Subcommand, Debug)] 9 | pub enum KeyCommands { 10 | /// Generate a new Secp256k1 key pair and export them to files in base64 format. 11 | Gen(KeyGenArgs), 12 | /// Convert a secret key file from base64 into the format expected by Tendermint. 13 | IntoTendermint(KeyIntoTendermintArgs), 14 | /// Convert a public key file from base64 into an f1 Address format an print it to STDOUT. 15 | Address(KeyAddressArgs), 16 | /// Get the peer ID corresponding to a node ID and its network address and print it to a local file. 17 | AddPeer(AddPeer), 18 | /// Converts a hex encoded Ethereum private key into a Base64 encoded Fendermint keypair. 19 | #[clap(alias = "eth-to-fendermint")] 20 | FromEth(KeyFromEthArgs), 21 | /// Converts a Base64 encoded Fendermint private key into a hex encoded Ethereum secret key, public key and address (20 bytes). 22 | IntoEth(KeyIntoEthArgs), 23 | } 24 | 25 | #[derive(Args, Debug)] 26 | pub struct KeyArgs { 27 | #[command(subcommand)] 28 | pub command: KeyCommands, 29 | } 30 | 31 | #[derive(Args, Debug)] 32 | pub struct AddPeer { 33 | /// The path to a CometBFT node key file. 34 | #[arg(long, short = 'n')] 35 | pub node_key_file: PathBuf, 36 | /// The path to a temporal local file where the peer IDs will be added. 37 | /// The file will be created if it doesn't exist. 38 | #[arg(long, short)] 39 | pub local_peers_file: PathBuf, 40 | /// The target CometBFT node network interface in the following format `IP:Port`. 41 | /// For example: `192.168.10.7:26656`. 42 | #[arg(long, short)] 43 | pub network_addr: String, 44 | } 45 | 46 | #[derive(Args, Debug)] 47 | pub struct KeyGenArgs { 48 | /// Name used to distinguish the files from other exported keys. 49 | #[arg(long, short)] 50 | pub name: String, 51 | /// Directory to export the key files to; it must exist. 52 | #[arg(long, short, default_value = ".")] 53 | pub out_dir: PathBuf, 54 | } 55 | 56 | #[derive(Args, Debug)] 57 | pub struct KeyFromEthArgs { 58 | /// Path to the file that stores the private key (hex format) 59 | #[arg(long, short)] 60 | pub secret_key: PathBuf, 61 | /// Name used to distinguish the files from other exported keys. 62 | #[arg(long, short)] 63 | pub name: String, 64 | /// Directory to export the key files to; it must exist. 65 | #[arg(long, short, default_value = ".")] 66 | pub out_dir: PathBuf, 67 | } 68 | 69 | #[derive(Args, Debug)] 70 | pub struct KeyIntoEthArgs { 71 | /// Path to the file that stores the private key (base64 format) 72 | #[arg(long, short)] 73 | pub secret_key: PathBuf, 74 | /// Name used to distinguish the files from other exported keys. 75 | #[arg(long, short)] 76 | pub name: String, 77 | /// Directory to export the key files to; it must exist. 78 | #[arg(long, short, default_value = ".")] 79 | pub out_dir: PathBuf, 80 | } 81 | 82 | #[derive(Args, Debug)] 83 | pub struct KeyIntoTendermintArgs { 84 | /// Path to the secret key we want to convert to Tendermint format. 85 | #[arg(long, short)] 86 | pub secret_key: PathBuf, 87 | /// Output file name for the Tendermint private validator key JSON file. 88 | #[arg(long, short)] 89 | pub out: PathBuf, 90 | } 91 | 92 | #[derive(Args, Debug)] 93 | pub struct KeyAddressArgs { 94 | /// Path to the public key we want to convert to f1 format. 95 | #[arg(long, short)] 96 | pub public_key: PathBuf, 97 | } 98 | -------------------------------------------------------------------------------- /fendermint/app/options/src/parse.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use std::str::FromStr; 5 | 6 | use bytes::Bytes; 7 | use cid::Cid; 8 | use num_traits::{FromPrimitive, Num}; 9 | 10 | use fvm_shared::{ 11 | address::{set_current_network, Address, Network}, 12 | bigint::BigInt, 13 | econ::TokenAmount, 14 | version::NetworkVersion, 15 | }; 16 | 17 | /// Decimals for filecoin in nano 18 | const FIL_AMOUNT_NANO_DIGITS: u32 = 9; 19 | 20 | pub fn parse_network_version(s: &str) -> Result { 21 | let nv: u32 = s 22 | .parse() 23 | .map_err(|_| format!("`{s}` isn't a network version"))?; 24 | if nv >= 18 { 25 | Ok(NetworkVersion::from(nv)) 26 | } else { 27 | Err("the minimum network version is 18".to_owned()) 28 | } 29 | } 30 | 31 | pub fn parse_token_amount(s: &str) -> Result { 32 | BigInt::from_str_radix(s, 10) 33 | .map_err(|e| format!("not a token amount: {e}")) 34 | .map(TokenAmount::from_atto) 35 | } 36 | 37 | pub fn parse_full_fil(s: &str) -> Result { 38 | let f: Result = s.parse(); 39 | if f.is_err() { 40 | return Err("input not a token amount".to_owned()); 41 | } 42 | 43 | let nano = f64::trunc(f.unwrap() * (10u64.pow(FIL_AMOUNT_NANO_DIGITS) as f64)); 44 | Ok(TokenAmount::from_nano(nano as u128)) 45 | } 46 | 47 | pub fn parse_cid(s: &str) -> Result { 48 | Cid::from_str(s).map_err(|e| format!("error parsing CID: {e}")) 49 | } 50 | 51 | pub fn parse_address(s: &str) -> Result { 52 | match s.chars().next() { 53 | Some('f') => set_current_network(Network::Mainnet), 54 | Some('t') => set_current_network(Network::Testnet), 55 | _ => (), 56 | } 57 | Address::from_str(s).map_err(|e| format!("error parsing address: {e}")) 58 | } 59 | 60 | pub fn parse_bytes(s: &str) -> Result { 61 | match hex::decode(s) { 62 | Ok(bz) => Ok(Bytes::from(bz)), 63 | Err(e) => Err(format!("error parsing raw bytes as hex: {e}")), 64 | } 65 | } 66 | 67 | /// Parse a percentage value [0-100] 68 | pub fn parse_percentage(s: &str) -> Result 69 | where 70 | T: Num + FromStr + PartialOrd + TryFrom, 71 | ::Err: std::fmt::Display, 72 | >::Error: std::fmt::Debug, 73 | { 74 | match T::from_str(s) { 75 | Ok(p) if p > T::zero() && p <= T::try_from(100u8).unwrap() => Ok(p), 76 | Ok(_) => Err("percentage out of range".to_owned()), 77 | Err(e) => Err(format!("error parsing as percentage: {e}")), 78 | } 79 | } 80 | 81 | /// Parse the FVM network and set the global value. 82 | pub fn parse_network(s: &str) -> Result { 83 | match s.to_lowercase().as_str() { 84 | "main" | "mainnet" | "f" => Ok(Network::Mainnet), 85 | "test" | "testnet" | "t" => Ok(Network::Testnet), 86 | n => { 87 | let n: u8 = n 88 | .parse() 89 | .map_err(|e| format!("expected 0 or 1 for network: {e}"))?; 90 | 91 | let n = Network::from_u8(n).ok_or_else(|| format!("unexpected network: {s}"))?; 92 | 93 | Ok(n) 94 | } 95 | } 96 | } 97 | 98 | pub fn parse_eth_address(s: &str) -> Result { 99 | match primitives::EthAddress::from_str(s) { 100 | Ok(a) => Ok(a.into()), 101 | Err(e) => Err(format!("not a valid ethereum address: {e}")), 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /fendermint/app/options/src/run.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use clap::Args; 5 | 6 | #[derive(Args, Debug)] 7 | pub struct RunArgs; 8 | -------------------------------------------------------------------------------- /fendermint/app/settings/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_app_settings" 3 | description = "Fendermint configuration settings. The defaults are in fendermint/app/config/default.toml" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | 10 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 11 | 12 | [dependencies] 13 | anyhow = { workspace = true } 14 | config = { workspace = true } 15 | dirs = { workspace = true } 16 | multiaddr = { workspace = true } 17 | serde = { workspace = true } 18 | serde_with = { workspace = true } 19 | tendermint-rpc = { workspace = true } 20 | 21 | fvm_shared = { workspace = true } 22 | fvm_ipld_encoding = { workspace = true } 23 | ipc-sdk = { workspace = true } 24 | ipc-provider = { workspace = true } 25 | 26 | fendermint_vm_encoding = { path = "../../vm/encoding" } 27 | fendermint_vm_topdown = { path = "../../vm/topdown" } 28 | -------------------------------------------------------------------------------- /fendermint/app/settings/src/eth.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use fvm_shared::econ::TokenAmount; 5 | use serde::Deserialize; 6 | use serde_with::{serde_as, DurationSeconds}; 7 | use std::time::Duration; 8 | 9 | use crate::{IsHumanReadable, SocketAddress}; 10 | 11 | /// Ethereum API facade settings. 12 | #[serde_as] 13 | #[derive(Debug, Deserialize, Clone)] 14 | pub struct EthSettings { 15 | pub listen: SocketAddress, 16 | #[serde_as(as = "DurationSeconds")] 17 | pub filter_timeout: Duration, 18 | pub cache_capacity: usize, 19 | pub gas: GasOpt, 20 | } 21 | 22 | #[serde_as] 23 | #[derive(Debug, Clone, Deserialize)] 24 | pub struct GasOpt { 25 | /// Minimum gas fee in atto. 26 | #[serde_as(as = "IsHumanReadable")] 27 | pub min_gas_premium: TokenAmount, 28 | pub num_blocks_max_prio_fee: u64, 29 | pub max_fee_hist_size: u64, 30 | } 31 | -------------------------------------------------------------------------------- /fendermint/app/settings/src/fvm.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use fvm_shared::econ::TokenAmount; 5 | use serde::Deserialize; 6 | use serde_with::serde_as; 7 | 8 | use crate::IsHumanReadable; 9 | 10 | #[serde_as] 11 | #[derive(Debug, Deserialize, Clone)] 12 | pub struct FvmSettings { 13 | /// Overestimation rate applied to gas estimations to ensure that the 14 | /// message goes through 15 | pub gas_overestimation_rate: f64, 16 | /// Gas search step increase used to find the optimal gas limit. 17 | /// It determines how fine-grained we want the gas estimation to be. 18 | pub gas_search_step: f64, 19 | /// Indicate whether transactions should be fully executed during the checks performed 20 | /// when they are added to the mempool, or just the most basic ones are performed. 21 | /// 22 | /// Enabling this option is required to fully support "pending" queries in the Ethereum API, 23 | /// otherwise only the nonces and balances are projected into a partial state. 24 | pub exec_in_check: bool, 25 | 26 | /// Gas fee used when broadcasting transactions. 27 | #[serde_as(as = "IsHumanReadable")] 28 | pub gas_fee_cap: TokenAmount, 29 | /// Gas premium used when broadcasting transactions. 30 | #[serde_as(as = "IsHumanReadable")] 31 | pub gas_premium: TokenAmount, 32 | } 33 | -------------------------------------------------------------------------------- /fendermint/app/src/cmd/eth.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use std::time::Duration; 5 | 6 | use anyhow::Context; 7 | use fendermint_eth_api::HybridClient; 8 | 9 | use crate::{ 10 | cmd, 11 | options::eth::{EthArgs, EthCommands}, 12 | settings::eth::EthSettings, 13 | }; 14 | 15 | cmd! { 16 | EthArgs(self, settings: EthSettings) { 17 | match self.command.clone() { 18 | EthCommands::Run { ws_url, http_url, connect_retry_delay } => { 19 | 20 | let (client, driver) = HybridClient::new(http_url, ws_url, Duration::from_secs(connect_retry_delay)).context("failed to create HybridClient")?; 21 | 22 | let driver_handle = tokio::spawn(async move { driver.run().await }); 23 | 24 | let result = run(settings, client).await; 25 | 26 | // Await the driver's termination to ensure proper connection closure. 27 | let _ = driver_handle.await; 28 | result 29 | } 30 | } 31 | } 32 | } 33 | 34 | /// Run the Ethereum API facade. 35 | async fn run(settings: EthSettings, client: HybridClient) -> anyhow::Result<()> { 36 | let gas = fendermint_eth_api::GasOpt { 37 | min_gas_premium: settings.gas.min_gas_premium, 38 | num_blocks_max_prio_fee: settings.gas.num_blocks_max_prio_fee, 39 | max_fee_hist_size: settings.gas.max_fee_hist_size, 40 | }; 41 | fendermint_eth_api::listen( 42 | settings.listen, 43 | client, 44 | settings.filter_timeout, 45 | settings.cache_capacity, 46 | gas, 47 | ) 48 | .await 49 | } 50 | -------------------------------------------------------------------------------- /fendermint/app/src/ipc.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! IPC related execution 4 | 5 | use crate::app::{AppState, AppStoreKey}; 6 | use crate::{App, BlockHeight}; 7 | use fendermint_storage::{Codec, Encode, KVReadable, KVStore, KVWritable}; 8 | use fendermint_vm_interpreter::fvm::state::ipc::GatewayCaller; 9 | use fendermint_vm_interpreter::fvm::state::FvmStateParams; 10 | use fendermint_vm_interpreter::fvm::store::ReadOnlyBlockstore; 11 | use fendermint_vm_topdown::sync::ParentFinalityStateQuery; 12 | use fendermint_vm_topdown::IPCParentFinality; 13 | use fvm_ipld_blockstore::Blockstore; 14 | use std::sync::Arc; 15 | 16 | /// Queries the LATEST COMMITTED parent finality from the storage 17 | pub struct AppParentFinalityQuery 18 | where 19 | SS: Blockstore + 'static, 20 | S: KVStore, 21 | { 22 | /// The app to get state 23 | app: App, 24 | gateway_caller: GatewayCaller>>, 25 | } 26 | 27 | impl AppParentFinalityQuery 28 | where 29 | S: KVStore 30 | + Codec 31 | + Encode 32 | + Encode 33 | + Codec, 34 | DB: KVWritable + KVReadable + 'static + Clone, 35 | SS: Blockstore + 'static + Clone, 36 | { 37 | pub fn new(app: App) -> Self { 38 | Self { 39 | app, 40 | gateway_caller: GatewayCaller::default(), 41 | } 42 | } 43 | } 44 | 45 | impl ParentFinalityStateQuery for AppParentFinalityQuery 46 | where 47 | S: KVStore 48 | + Codec 49 | + Encode 50 | + Encode 51 | + Codec, 52 | DB: KVWritable + KVReadable + 'static + Clone, 53 | SS: Blockstore + 'static + Clone, 54 | { 55 | fn get_latest_committed_finality(&self) -> anyhow::Result> { 56 | let maybe_exec_state = self.app.new_read_only_exec_state()?; 57 | 58 | let finality = if let Some(mut exec_state) = maybe_exec_state { 59 | let finality = self 60 | .gateway_caller 61 | .get_latest_parent_finality(&mut exec_state)?; 62 | Some(finality) 63 | } else { 64 | None 65 | }; 66 | 67 | Ok(finality) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /fendermint/app/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | mod app; 4 | mod ipc; 5 | mod store; 6 | mod tmconv; 7 | 8 | pub use app::{App, AppConfig}; 9 | pub use ipc::AppParentFinalityQuery; 10 | pub use store::{AppStore, BitswapBlockstore}; 11 | 12 | // Different type from `ChainEpoch` just because we might use epoch in a more traditional sense for checkpointing. 13 | pub type BlockHeight = u64; 14 | 15 | pub const VERSION: &str = env!("CARGO_PKG_VERSION"); 16 | pub const APP_VERSION: u64 = 0; 17 | -------------------------------------------------------------------------------- /fendermint/crypto/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_crypto" 3 | description = "Cryptographic utilities" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | # Defined here so no other crate keeps a direct reference to it. 11 | libsecp256k1 = "0.7" 12 | 13 | rand = { workspace = true } 14 | zeroize = { workspace = true } 15 | -------------------------------------------------------------------------------- /fendermint/crypto/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use rand::Rng; 5 | use zeroize::{Zeroize, ZeroizeOnDrop, Zeroizing}; 6 | 7 | pub use libsecp256k1::{PublicKey, RecoveryId, Signature}; 8 | 9 | /// Create a new key and make sure the wrapped public key is normalized, 10 | /// which is to ensure the results look the same after a serialization roundtrip. 11 | pub fn normalize_public_key(pk: PublicKey) -> PublicKey { 12 | let mut aff: libsecp256k1::curve::Affine = pk.into(); 13 | aff.x.normalize(); 14 | aff.y.normalize(); 15 | PublicKey::try_from(aff).unwrap() 16 | } 17 | 18 | /// Wrapper around a [libsecp256k1::SecretKey] that implements [Zeroize]. 19 | #[derive(Debug, Clone, PartialEq, Eq)] 20 | pub struct SecretKey(libsecp256k1::SecretKey); 21 | 22 | impl SecretKey { 23 | pub fn sign(&self, bz: &[u8; 32]) -> (libsecp256k1::Signature, libsecp256k1::RecoveryId) { 24 | libsecp256k1::sign(&libsecp256k1::Message::parse(bz), &self.0) 25 | } 26 | 27 | pub fn random(rng: &mut R) -> Self { 28 | Self(libsecp256k1::SecretKey::random(rng)) 29 | } 30 | 31 | pub fn public_key(&self) -> PublicKey { 32 | PublicKey::from_secret_key(&self.0) 33 | } 34 | 35 | pub fn serialize(&self) -> Zeroizing<[u8; libsecp256k1::util::SECRET_KEY_SIZE]> { 36 | Zeroizing::new(self.0.serialize()) 37 | } 38 | } 39 | 40 | impl Zeroize for SecretKey { 41 | fn zeroize(&mut self) { 42 | let mut sk = libsecp256k1::SecretKey::default(); 43 | std::mem::swap(&mut self.0, &mut sk); 44 | let mut sk: libsecp256k1::curve::Scalar = sk.into(); 45 | sk.0.zeroize(); 46 | } 47 | } 48 | 49 | impl Drop for SecretKey { 50 | fn drop(&mut self) { 51 | self.zeroize() 52 | } 53 | } 54 | 55 | impl ZeroizeOnDrop for SecretKey {} 56 | 57 | impl TryFrom> for SecretKey { 58 | type Error = libsecp256k1::Error; 59 | 60 | fn try_from(mut value: Vec) -> Result { 61 | let sk = libsecp256k1::SecretKey::parse_slice(&value)?; 62 | value.zeroize(); 63 | Ok(Self(sk)) 64 | } 65 | } 66 | 67 | impl From for SecretKey { 68 | fn from(value: libsecp256k1::SecretKey) -> Self { 69 | Self(value) 70 | } 71 | } 72 | 73 | impl From<&SecretKey> for PublicKey { 74 | fn from(value: &SecretKey) -> Self { 75 | value.public_key() 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /fendermint/eth/api/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_eth_api" 3 | description = "Ethereum JSON-RPC facade" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | anyhow = { workspace = true } 11 | async-trait = { workspace = true } 12 | axum = { workspace = true } 13 | ethers-core = { workspace = true } 14 | erased-serde = { workspace = true } 15 | futures = { workspace = true } 16 | hex = { workspace = true } 17 | jsonrpc-v2 = { workspace = true } 18 | lazy_static = { workspace = true } 19 | lru_time_cache = { workspace = true } 20 | paste = { workspace = true } 21 | serde = { workspace = true } 22 | rand = { workspace = true } 23 | serde_json = { workspace = true } 24 | tracing = { workspace = true } 25 | tendermint = { workspace = true } 26 | tendermint-rpc = { workspace = true } 27 | tokio = { workspace = true } 28 | 29 | cid = { workspace = true } 30 | fvm_shared = { workspace = true } 31 | fvm_ipld_encoding = { workspace = true } 32 | 33 | fendermint_crypto = { path = "../../crypto" } 34 | fendermint_rpc = { path = "../../rpc" } 35 | fendermint_vm_actor_interface = { path = "../../vm/actor_interface" } 36 | fendermint_vm_message = { path = "../../vm/message" } 37 | 38 | [dev-dependencies] 39 | async-trait = { workspace = true } 40 | clap = { workspace = true } 41 | ethers = { workspace = true, features = ["abigen"] } 42 | hex = { workspace = true } 43 | lazy_static = { workspace = true } 44 | rand = { workspace = true } 45 | tracing = { workspace = true } 46 | tracing-subscriber = { workspace = true } 47 | quickcheck = { workspace = true } 48 | quickcheck_macros = { workspace = true } 49 | thiserror = { workspace = true } 50 | 51 | fendermint_testing = { path = "../../testing", features = ["arb"] } 52 | fendermint_vm_message = { path = "../../vm/message", features = ["arb"] } 53 | -------------------------------------------------------------------------------- /fendermint/eth/api/README.md: -------------------------------------------------------------------------------- 1 | # Ethereum API Facade 2 | 3 | The `fendermint_eth_api` crate implements some of the [Ethereum JSON-RPC](https://ethereum.org/en/developers/docs/apis/json-rpc) methods. The up-to-date list of which methods are implemented can be gleaned from the [API registration](./src/apis/mod.rs). 4 | 5 | The API is tested for basic type lineup during the `make e2e` tests via the [ethers example](./examples/ethers.rs). 6 | 7 | The relevant specification is [FIP-55](https://github.com/filecoin-project/FIPs/blob/master/FIPS/fip-0055.md). -------------------------------------------------------------------------------- /fendermint/eth/api/src/apis/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | // See https://ethereum.org/en/developers/docs/apis/json-rpc/#json-rpc-methods 5 | // and https://ethereum.github.io/execution-apis/api-documentation/ 6 | 7 | use crate::HybridClient; 8 | use jsonrpc_v2::{MapRouter, ServerBuilder}; 9 | use paste::paste; 10 | 11 | mod eth; 12 | mod net; 13 | mod web3; 14 | 15 | macro_rules! with_methods { 16 | ($server:ident, $module:ident, { $($method:ident),* }) => { 17 | paste!{ 18 | $server 19 | $(.with_method( 20 | stringify!([< $module _ $method >]), 21 | $module :: [< $method:snake >] :: 22 | ))* 23 | } 24 | }; 25 | } 26 | 27 | pub fn register_methods(server: ServerBuilder) -> ServerBuilder { 28 | // This is the list of eth methods. Apart from these Lotus implements 1 method from web3, 29 | // while Ethermint does more across web3, debug, miner, net, txpool, and personal. 30 | // The unimplemented ones are commented out, to make it easier to see where we're at. 31 | let server = with_methods!(server, eth, { 32 | accounts, 33 | blockNumber, 34 | call, 35 | chainId, 36 | // eth_coinbase 37 | // eth_compileLLL 38 | // eth_compileSerpent 39 | // eth_compileSolidity 40 | estimateGas, 41 | feeHistory, 42 | maxPriorityFeePerGas, 43 | gasPrice, 44 | getBalance, 45 | getBlockByHash, 46 | getBlockByNumber, 47 | getBlockTransactionCountByHash, 48 | getBlockTransactionCountByNumber, 49 | getBlockReceipts, 50 | getCode, 51 | // eth_getCompilers 52 | getFilterChanges, 53 | getFilterLogs, 54 | getLogs, 55 | getStorageAt, 56 | getTransactionByBlockHashAndIndex, 57 | getTransactionByBlockNumberAndIndex, 58 | getTransactionByHash, 59 | getTransactionCount, 60 | getTransactionReceipt, 61 | getUncleByBlockHashAndIndex, 62 | getUncleByBlockNumberAndIndex, 63 | getUncleCountByBlockHash, 64 | getUncleCountByBlockNumber, 65 | // eth_getWork 66 | // eth_hashrate 67 | // eth_mining 68 | newBlockFilter, 69 | newFilter, 70 | newPendingTransactionFilter, 71 | protocolVersion, 72 | sendRawTransaction, 73 | // eth_sendTransaction 74 | // eth_sign 75 | // eth_signTransaction 76 | // eth_submitHashrate 77 | // eth_submitWork 78 | syncing, 79 | uninstallFilter, 80 | subscribe, 81 | unsubscribe 82 | }); 83 | 84 | let server = with_methods!(server, web3, { 85 | clientVersion, 86 | sha3 87 | }); 88 | 89 | with_methods!(server, net, { 90 | version, 91 | listening, 92 | peerCount 93 | }) 94 | } 95 | 96 | /// Indicate whether a method requires a WebSocket connection. 97 | pub fn is_streaming_method(method: &str) -> bool { 98 | method == "eth_subscribe" 99 | } 100 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/apis/net.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use anyhow::Context; 5 | use ethers_core::types as et; 6 | use fendermint_rpc::query::QueryClient; 7 | use fendermint_vm_message::query::FvmQueryHeight; 8 | use tendermint_rpc::endpoint::net_info; 9 | use tendermint_rpc::Client; 10 | 11 | use crate::{JsonRpcData, JsonRpcResult}; 12 | 13 | /// The current FVM network version. 14 | /// 15 | /// Same as eth_protocolVersion 16 | pub async fn version(data: JsonRpcData) -> JsonRpcResult 17 | where 18 | C: Client + Sync + Send, 19 | { 20 | let res = data.client.state_params(FvmQueryHeight::default()).await?; 21 | let version: u32 = res.value.network_version.into(); 22 | Ok(version.to_string()) 23 | } 24 | 25 | /// Returns true if client is actively listening for network connections. 26 | pub async fn listening(data: JsonRpcData) -> JsonRpcResult 27 | where 28 | C: Client + Sync + Send, 29 | { 30 | let res: net_info::Response = data 31 | .tm() 32 | .net_info() 33 | .await 34 | .context("failed to fetch net_info")?; 35 | 36 | Ok(res.listening) 37 | } 38 | 39 | /// Returns true if client is actively listening for network connections. 40 | pub async fn peer_count(data: JsonRpcData) -> JsonRpcResult 41 | where 42 | C: Client + Sync + Send, 43 | { 44 | let res: net_info::Response = data 45 | .tm() 46 | .net_info() 47 | .await 48 | .context("failed to fetch net_info")?; 49 | 50 | Ok(et::U64::from(res.n_peers)) 51 | } 52 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/apis/web3.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use anyhow::Context; 5 | use cid::multihash::MultihashDigest; 6 | use jsonrpc_v2::Params; 7 | use tendermint::abci; 8 | use tendermint_rpc::Client; 9 | 10 | use crate::{JsonRpcData, JsonRpcResult}; 11 | 12 | /// Returns the current client version. 13 | pub async fn client_version(data: JsonRpcData) -> JsonRpcResult 14 | where 15 | C: Client + Sync + Send, 16 | { 17 | let res: abci::response::Info = data 18 | .tm() 19 | .abci_info() 20 | .await 21 | .context("failed to fetch info")?; 22 | 23 | let version = format!("{}/{}/{}", res.data, res.version, res.app_version); 24 | 25 | Ok(version) 26 | } 27 | 28 | /// Returns Keccak-256 (not the standardized SHA3-256) of the given data. 29 | /// 30 | /// Expects the data as hex encoded string and returns it as such. 31 | pub async fn sha3( 32 | _data: JsonRpcData, 33 | Params((input,)): Params<(String,)>, 34 | ) -> JsonRpcResult 35 | where 36 | C: Client + Sync + Send, 37 | { 38 | let input = input.strip_prefix("0x").unwrap_or(&input); 39 | let input = hex::decode(input).context("failed to decode input as hex")?; 40 | let output = cid::multihash::Code::Keccak256.digest(&input); 41 | let output = hex::encode(output.digest()); 42 | Ok(output) 43 | } 44 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/cache.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use std::sync::{Arc, Mutex}; 5 | 6 | use anyhow::Context; 7 | use fendermint_rpc::client::FendermintClient; 8 | use fendermint_rpc::query::QueryClient; 9 | use fendermint_vm_message::query::FvmQueryHeight; 10 | use fvm_shared::{ 11 | address::{Address, Payload}, 12 | ActorID, 13 | }; 14 | use lru_time_cache::LruCache; 15 | use tendermint_rpc::Client; 16 | 17 | /// Facilitate Ethereum address <-> Actor ID lookups. 18 | #[derive(Clone)] 19 | pub struct AddressCache { 20 | client: FendermintClient, 21 | addr_to_id: Arc>>, 22 | id_to_addr: Arc>>, 23 | } 24 | 25 | impl AddressCache 26 | where 27 | C: Client + Sync + Send, 28 | { 29 | pub fn new(client: FendermintClient, capacity: usize) -> Self { 30 | Self { 31 | client, 32 | addr_to_id: Arc::new(Mutex::new(LruCache::with_capacity(capacity))), 33 | id_to_addr: Arc::new(Mutex::new(LruCache::with_capacity(capacity))), 34 | } 35 | } 36 | 37 | pub async fn lookup_id(&self, addr: &Address) -> anyhow::Result> { 38 | if let Ok(id) = addr.id() { 39 | return Ok(Some(id)); 40 | } 41 | 42 | if let Some(id) = self.get_id(addr) { 43 | return Ok(Some(id)); 44 | } 45 | 46 | // Using committed height because pending could change. 47 | let res = self 48 | .client 49 | .actor_state(addr, FvmQueryHeight::Committed) 50 | .await 51 | .context("failed to lookup actor state")?; 52 | 53 | if let Some((id, _)) = res.value { 54 | self.set_id(*addr, id); 55 | if let Payload::Delegated(_) = addr.payload() { 56 | self.set_addr(id, *addr) 57 | } 58 | return Ok(Some(id)); 59 | } 60 | tracing::info!( 61 | addr = addr.to_string(), 62 | height = res.height.value(), 63 | "actor not found" 64 | ); 65 | Ok(None) 66 | } 67 | 68 | /// Look up the delegated address of an ID, if any. 69 | pub async fn lookup_addr(&self, id: &ActorID) -> anyhow::Result> { 70 | if let Some(addr) = self.get_addr(id) { 71 | return Ok(Some(addr)); 72 | } 73 | 74 | let res = self 75 | .client 76 | .actor_state(&Address::new_id(*id), FvmQueryHeight::Committed) 77 | .await 78 | .context("failed to lookup actor state")?; 79 | 80 | if let Some((_, actor_state)) = res.value { 81 | if let Some(addr) = actor_state.delegated_address { 82 | self.set_addr(*id, addr); 83 | self.set_id(addr, *id); 84 | return Ok(Some(addr)); 85 | } 86 | } 87 | tracing::info!(id, height = res.height.value(), "actor not found"); 88 | Ok(None) 89 | } 90 | 91 | fn get_id(&self, addr: &Address) -> Option { 92 | let mut c = self.addr_to_id.lock().unwrap(); 93 | c.get(addr).cloned() 94 | } 95 | 96 | fn set_id(&self, addr: Address, id: ActorID) { 97 | let mut c = self.addr_to_id.lock().unwrap(); 98 | c.insert(addr, id); 99 | } 100 | 101 | fn get_addr(&self, id: &ActorID) -> Option
{ 102 | let mut c = self.id_to_addr.lock().unwrap(); 103 | c.get(id).cloned() 104 | } 105 | 106 | fn set_addr(&self, id: ActorID, addr: Address) { 107 | let mut c = self.id_to_addr.lock().unwrap(); 108 | c.insert(id, addr); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/conv/from_eth.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | //! Helper methods to convert between Ethereum and FVM data formats. 5 | 6 | use ethers_core::types::transaction::eip2718::TypedTransaction; 7 | 8 | pub use fendermint_vm_message::conv::from_eth::*; 9 | use fvm_shared::{error::ExitCode, message::Message}; 10 | 11 | use crate::{error, JsonRpcResult}; 12 | 13 | pub fn to_fvm_message(tx: TypedTransaction, accept_legacy: bool) -> JsonRpcResult { 14 | match tx { 15 | TypedTransaction::Eip1559(ref tx) => { 16 | Ok(fendermint_vm_message::conv::from_eth::to_fvm_message(tx)?) 17 | } 18 | TypedTransaction::Legacy(_) if accept_legacy => { 19 | // legacy transactions are only accepted for gas estimation purposes 20 | // (when accept_legacy is explicitly set) 21 | // eth_sendRawTransaction should fail for legacy transactions. 22 | // For this purpose it os OK to not set `max_fee_per_gas` and 23 | // `max_priority_fee_per_gas`. Legacy transactions don't include 24 | // that information 25 | Ok(fendermint_vm_message::conv::from_eth::to_fvm_message( 26 | &tx.into(), 27 | )?) 28 | } 29 | TypedTransaction::Legacy(_) | TypedTransaction::Eip2930(_) => error( 30 | ExitCode::USR_ILLEGAL_ARGUMENT, 31 | "unexpected transaction type", 32 | ), 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/conv/from_fvm.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | //! Helper methods to convert between FVM and Ethereum data formats. 5 | 6 | pub use fendermint_vm_message::conv::from_fvm::*; 7 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/conv/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | pub mod from_eth; 5 | pub mod from_fvm; 6 | pub mod from_tm; 7 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use fvm_shared::error::ExitCode; 5 | use serde::Serialize; 6 | 7 | #[derive(Debug, Clone)] 8 | pub struct JsonRpcError { 9 | pub code: i64, 10 | pub message: String, 11 | pub data: Option, 12 | } 13 | 14 | impl From for JsonRpcError { 15 | fn from(value: anyhow::Error) -> Self { 16 | Self { 17 | code: 0, 18 | message: format!("{:#}", value), 19 | data: None, 20 | } 21 | } 22 | } 23 | 24 | impl From for JsonRpcError { 25 | fn from(value: tendermint_rpc::Error) -> Self { 26 | Self { 27 | code: 0, 28 | message: format!("Tendermint RPC error: {value}"), 29 | data: None, 30 | } 31 | } 32 | } 33 | 34 | impl From for jsonrpc_v2::Error { 35 | fn from(value: JsonRpcError) -> Self { 36 | Self::Full { 37 | code: value.code, 38 | message: value.message, 39 | data: value.data.map(|d| { 40 | let d: Box = Box::new(d); 41 | d 42 | }), 43 | } 44 | } 45 | } 46 | 47 | pub fn error(exit_code: ExitCode, msg: impl ToString) -> Result { 48 | Err(JsonRpcError { 49 | code: exit_code.value().into(), 50 | message: msg.to_string(), 51 | data: None, 52 | }) 53 | } 54 | 55 | pub fn error_with_data( 56 | exit_code: ExitCode, 57 | msg: impl ToString, 58 | data: E, 59 | ) -> Result { 60 | let data = match serde_json::to_value(data) { 61 | Ok(v) => v, 62 | Err(e) => serde_json::Value::String(format!("failed to serialize error data: {e}")), 63 | }; 64 | Err(JsonRpcError { 65 | code: exit_code.value().into(), 66 | message: msg.to_string(), 67 | data: Some(data), 68 | }) 69 | } 70 | 71 | impl std::fmt::Display for JsonRpcError { 72 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 73 | write!(f, "{} (code: {})", self.message, self.code) 74 | } 75 | } 76 | 77 | impl std::error::Error for JsonRpcError {} 78 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/gas/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use fvm_shared::{ 4 | bigint::{BigInt, Zero}, 5 | econ::TokenAmount, 6 | message::Message, 7 | }; 8 | 9 | // Copy of https://github.com/filecoin-project/ref-fvm/blob/fvm%40v3.3.1/fvm/src/gas/outputs.rs 10 | mod output; 11 | 12 | // https://github.com/filecoin-project/lotus/blob/6cc506f5cf751215be6badc94a960251c6453202/node/impl/full/eth.go#L2220C41-L2228 13 | pub fn effective_gas_price(msg: &Message, base_fee: &TokenAmount, gas_used: i64) -> TokenAmount { 14 | let out = output::GasOutputs::compute( 15 | gas_used.try_into().expect("gas should be u64 convertible"), 16 | msg.gas_limit, 17 | base_fee, 18 | &msg.gas_fee_cap, 19 | &msg.gas_premium, 20 | ); 21 | 22 | let total_spend = out.base_fee_burn + out.miner_tip + out.over_estimation_burn; 23 | 24 | if gas_used > 0 { 25 | TokenAmount::from_atto(total_spend.atto() / TokenAmount::from_atto(gas_used).atto()) 26 | } else { 27 | TokenAmount::from_atto(0) 28 | } 29 | } 30 | 31 | // https://github.com/filecoin-project/lotus/blob/9e4f1a4d23ad72ab191754d4f432e4dc754fce1b/chain/types/message.go#L227 32 | pub fn effective_gas_premium(msg: &Message, base_fee: &TokenAmount) -> TokenAmount { 33 | let available = if msg.gas_fee_cap < *base_fee { 34 | TokenAmount::from_atto(0) 35 | } else { 36 | msg.gas_fee_cap.clone() - base_fee 37 | }; 38 | if msg.gas_premium < available { 39 | return msg.gas_premium.clone(); 40 | } 41 | available 42 | } 43 | 44 | // finds 55th percntile instead of median to put negative pressure on gas price 45 | // Rust implementation of: 46 | // https://github.com/consensus-shipyard/lotus/blob/156f5556b3ecc042764d76308dca357da3adfb4d/node/impl/full/gas.go#L144 47 | pub fn median_gas_premium( 48 | prices: &mut Vec<(TokenAmount, i64)>, 49 | block_gas_target: i64, 50 | ) -> TokenAmount { 51 | // Sort in descending order based on premium 52 | prices.sort_by(|a, b| b.0.cmp(&a.0)); 53 | let blocks = prices.len() as i64; 54 | 55 | let mut at = block_gas_target * blocks / 2; 56 | at += block_gas_target * blocks / (2 * 20); 57 | 58 | let mut prev1 = TokenAmount::zero(); 59 | let mut prev2 = TokenAmount::zero(); 60 | 61 | for (price, limit) in prices.iter() { 62 | prev2 = prev1.clone(); 63 | prev1 = price.clone(); 64 | at -= limit; 65 | if at < 0 { 66 | break; 67 | } 68 | } 69 | 70 | let mut premium = prev1; 71 | 72 | if prev2 != TokenAmount::zero() { 73 | premium += &prev2; 74 | premium.div_ceil(BigInt::from(2)); 75 | } 76 | 77 | premium 78 | } 79 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/handlers/http.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // Copyright 2019-2022 ChainSafe Systems 3 | // SPDX-License-Identifier: Apache-2.0, MIT 4 | 5 | // Based on https://github.com/ChainSafe/forest/blob/v0.8.2/node/rpc/src/rpc_http_handler.rs 6 | 7 | use axum::http::{HeaderMap, StatusCode}; 8 | use axum::response::IntoResponse; 9 | use jsonrpc_v2::{RequestObject, ResponseObjects}; 10 | use serde::Deserialize; 11 | 12 | use crate::{apis, AppState}; 13 | 14 | type ResponseHeaders = [(&'static str, &'static str); 1]; 15 | 16 | const RESPONSE_HEADERS: ResponseHeaders = [("content-type", "application/json-rpc;charset=utf-8")]; 17 | 18 | /// The Ethereum API implementations accept `{}` or `[{}, {}, ...]` as requests, 19 | /// with the expectation of as many responses. 20 | /// 21 | /// `jsonrpc_v2` has a type named `RequestKind` but it's not `Deserialize`. 22 | #[derive(Deserialize)] 23 | #[serde(untagged)] 24 | pub enum RequestKind { 25 | One(RequestObject), 26 | Many(Vec), 27 | } 28 | 29 | /// Handle JSON-RPC calls. 30 | pub async fn handle( 31 | _headers: HeaderMap, 32 | axum::extract::State(state): axum::extract::State, 33 | axum::Json(request): axum::Json, 34 | ) -> impl IntoResponse { 35 | // NOTE: Any authorization can come here. 36 | let response = match request { 37 | RequestKind::One(request) => { 38 | if let Err(response) = check_request(&request) { 39 | return response; 40 | } 41 | state.rpc_server.handle(request).await 42 | } 43 | RequestKind::Many(requests) => { 44 | for request in requests.iter() { 45 | if let Err(response) = check_request(request) { 46 | return response; 47 | } 48 | } 49 | state.rpc_server.handle(requests).await 50 | } 51 | }; 52 | debug_response(&response); 53 | json_response(&response) 54 | } 55 | 56 | fn debug_response(response: &ResponseObjects) { 57 | let debug = |r| { 58 | tracing::debug!( 59 | response = serde_json::to_string(r).unwrap_or_else(|e| e.to_string()), 60 | "RPC response" 61 | ); 62 | }; 63 | match response { 64 | ResponseObjects::Empty => {} 65 | ResponseObjects::One(r) => { 66 | debug(r); 67 | } 68 | ResponseObjects::Many(rs) => { 69 | for r in rs { 70 | debug(r); 71 | } 72 | } 73 | } 74 | } 75 | 76 | fn json_response(response: &ResponseObjects) -> (StatusCode, ResponseHeaders, std::string::String) { 77 | match serde_json::to_string(response) { 78 | Ok(json) => (StatusCode::OK, RESPONSE_HEADERS, json), 79 | Err(err) => { 80 | let msg = err.to_string(); 81 | tracing::error!(error = msg, "RPC to JSON failure"); 82 | (StatusCode::INTERNAL_SERVER_ERROR, RESPONSE_HEADERS, msg) 83 | } 84 | } 85 | } 86 | 87 | fn check_request( 88 | request: &RequestObject, 89 | ) -> Result<(), (StatusCode, ResponseHeaders, std::string::String)> { 90 | tracing::debug!(?request, "RPC request"); 91 | let method = request.method_ref().to_owned(); 92 | 93 | if apis::is_streaming_method(&method) { 94 | Err(( 95 | StatusCode::BAD_REQUEST, 96 | RESPONSE_HEADERS, 97 | format!("'{method}' is only available through WebSocket"), 98 | )) 99 | } else { 100 | Ok(()) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | pub mod http; 5 | pub mod ws; 6 | -------------------------------------------------------------------------------- /fendermint/eth/api/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use anyhow::anyhow; 5 | use axum::routing::{get, post}; 6 | use fvm_shared::econ::TokenAmount; 7 | use jsonrpc_v2::Data; 8 | use std::{net::ToSocketAddrs, sync::Arc, time::Duration}; 9 | 10 | mod apis; 11 | mod cache; 12 | mod client; 13 | mod conv; 14 | mod error; 15 | mod filters; 16 | mod gas; 17 | mod handlers; 18 | mod state; 19 | 20 | pub use client::{HybridClient, HybridClientDriver}; 21 | 22 | use error::{error, JsonRpcError}; 23 | use state::JsonRpcState; 24 | 25 | /// This is passed to every method handler. It's generic in the client type to facilitate testing with mocks. 26 | type JsonRpcData = Data>; 27 | type JsonRpcServer = Arc>; 28 | type JsonRpcResult = Result; 29 | 30 | /// This is the state we will pass to [axum] so that we can extract it in handlers. 31 | #[derive(Clone)] 32 | pub struct AppState { 33 | pub rpc_server: JsonRpcServer, 34 | pub rpc_state: Arc>, 35 | } 36 | 37 | #[derive(Debug, Clone)] 38 | pub struct GasOpt { 39 | pub min_gas_premium: TokenAmount, 40 | pub num_blocks_max_prio_fee: u64, 41 | pub max_fee_hist_size: u64, 42 | } 43 | 44 | /// Start listening to JSON-RPC requests. 45 | pub async fn listen( 46 | listen_addr: A, 47 | client: HybridClient, 48 | filter_timeout: Duration, 49 | cache_capacity: usize, 50 | gas_opt: GasOpt, 51 | ) -> anyhow::Result<()> { 52 | if let Some(listen_addr) = listen_addr.to_socket_addrs()?.next() { 53 | let rpc_state = Arc::new(JsonRpcState::new( 54 | client, 55 | filter_timeout, 56 | cache_capacity, 57 | gas_opt, 58 | )); 59 | let rpc_server = make_server(rpc_state.clone()); 60 | let app_state = AppState { 61 | rpc_server, 62 | rpc_state, 63 | }; 64 | let router = make_router(app_state); 65 | let server = axum::Server::try_bind(&listen_addr)?.serve(router.into_make_service()); 66 | 67 | tracing::info!(?listen_addr, "bound Ethereum API"); 68 | server.await?; 69 | Ok(()) 70 | } else { 71 | Err(anyhow!("failed to convert to any socket address")) 72 | } 73 | } 74 | 75 | /// Register method handlers with the JSON-RPC server construct. 76 | fn make_server(state: Arc>) -> JsonRpcServer { 77 | let server = jsonrpc_v2::Server::new().with_data(Data(state)); 78 | let server = apis::register_methods(server); 79 | server.finish() 80 | } 81 | 82 | /// Register routes in the `axum` HTTP router to handle JSON-RPC and WebSocket calls. 83 | fn make_router(state: AppState) -> axum::Router { 84 | axum::Router::new() 85 | .route("/", post(handlers::http::handle)) 86 | .route("/", get(handlers::ws::handle)) 87 | .with_state(state) 88 | } 89 | -------------------------------------------------------------------------------- /fendermint/eth/hardhat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_eth_hardhat" 3 | description = "Utilities to deal with Hardhat build artifacts" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = { workspace = true } 13 | ethers-core = { workspace = true } 14 | hex = { workspace = true } 15 | serde = { workspace = true } 16 | serde_json = { workspace = true } 17 | -------------------------------------------------------------------------------- /fendermint/rocksdb/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_rocksdb" 3 | description = "Implement the KVStore abstraction for RocksDB" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | num_cpus = "1.14" 13 | rocksdb = { version = "0.21", features = ["multi-threaded-cf"] } 14 | anyhow = { workspace = true } 15 | fendermint_storage = { path = "../storage", optional = true, features = ["testing"] } 16 | serde = { workspace = true } 17 | thiserror = { workspace = true } 18 | 19 | cid = { workspace = true, optional = true } 20 | fvm_ipld_blockstore = { workspace = true, optional = true } 21 | 22 | [dev-dependencies] 23 | tempfile = { workspace = true } 24 | quickcheck = { workspace = true } 25 | fvm_ipld_encoding = { workspace = true } 26 | 27 | [features] 28 | default = ["lz4", "blockstore", "kvstore"] 29 | blockstore = ["fvm_ipld_blockstore", "cid"] 30 | kvstore = ["fendermint_storage"] 31 | 32 | lz4 = ["rocksdb/lz4"] 33 | # snappy = ["rocksdb/snappy"] 34 | # zlib = ["rocksdb/zlib"] 35 | # bzip2 = ["rocksdb/bzip2"] 36 | # zstd = ["rocksdb/zstd"] 37 | -------------------------------------------------------------------------------- /fendermint/rocksdb/src/blockstore.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | // Copyright 2022-2023 Protocol Labs 4 | // SPDX-License-Identifier: Apache-2.0, MIT 5 | use anyhow::anyhow; 6 | use cid::Cid; 7 | use fvm_ipld_blockstore::Blockstore; 8 | use rocksdb::{BoundColumnFamily, OptimisticTransactionDB, WriteBatchWithTransaction}; 9 | 10 | use crate::RocksDb; 11 | 12 | impl Blockstore for RocksDb { 13 | fn get(&self, k: &Cid) -> anyhow::Result>> { 14 | Ok(self.read(k.to_bytes())?) 15 | } 16 | 17 | fn put_keyed(&self, k: &Cid, block: &[u8]) -> anyhow::Result<()> { 18 | Ok(self.write(k.to_bytes(), block)?) 19 | } 20 | 21 | // Called by the BufferedBlockstore during flush. 22 | fn put_many_keyed(&self, blocks: I) -> anyhow::Result<()> 23 | where 24 | Self: Sized, 25 | D: AsRef<[u8]>, 26 | I: IntoIterator, 27 | { 28 | let mut batch = WriteBatchWithTransaction::::default(); 29 | for (cid, v) in blocks.into_iter() { 30 | let k = cid.to_bytes(); 31 | let v = v.as_ref(); 32 | batch.put(k, v); 33 | } 34 | // This function is used in `fvm_ipld_car::load_car` 35 | // It reduces time cost of loading mainnet snapshot 36 | // by ~10% by not writing to WAL(write ahead log). 37 | // Ok(self.db.write_without_wal(batch)?) 38 | 39 | // For some reason with the `write_without_wal` version if I restart the application 40 | // it doesn't find the manifest root. 41 | Ok(self.db.write(batch)?) 42 | } 43 | } 44 | 45 | /// A [`Blockstore`] implementation that writes to a specific namespace, not the default like above. 46 | #[derive(Clone)] 47 | pub struct NamespaceBlockstore { 48 | db: Arc, 49 | ns: String, 50 | } 51 | 52 | impl NamespaceBlockstore { 53 | pub fn new(db: RocksDb, ns: String) -> anyhow::Result { 54 | // All namespaces are pre-created during open. 55 | if !db.has_cf_handle(&ns) { 56 | Err(anyhow!("namespace {ns} does not exist!")) 57 | } else { 58 | Ok(Self { db: db.db, ns }) 59 | } 60 | } 61 | 62 | // Unfortunately there doesn't seem to be a way to avoid having to 63 | // clone another instance for each operation :( 64 | fn cf(&self) -> anyhow::Result> { 65 | self.db 66 | .cf_handle(&self.ns) 67 | .ok_or_else(|| anyhow!("namespace {} does not exist!", self.ns)) 68 | } 69 | } 70 | 71 | impl Blockstore for NamespaceBlockstore { 72 | fn get(&self, k: &Cid) -> anyhow::Result>> { 73 | Ok(self.db.get_cf(&self.cf()?, k.to_bytes())?) 74 | } 75 | 76 | fn put_keyed(&self, k: &Cid, block: &[u8]) -> anyhow::Result<()> { 77 | Ok(self.db.put_cf(&self.cf()?, k.to_bytes(), block)?) 78 | } 79 | 80 | // Called by the BufferedBlockstore during flush. 81 | fn put_many_keyed(&self, blocks: I) -> anyhow::Result<()> 82 | where 83 | Self: Sized, 84 | D: AsRef<[u8]>, 85 | I: IntoIterator, 86 | { 87 | let cf = self.cf()?; 88 | let mut batch = WriteBatchWithTransaction::::default(); 89 | for (cid, v) in blocks.into_iter() { 90 | let k = cid.to_bytes(); 91 | let v = v.as_ref(); 92 | batch.put_cf(&cf, k, v); 93 | } 94 | Ok(self.db.write(batch)?) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /fendermint/rocksdb/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | mod rocks; 4 | 5 | #[cfg(feature = "blockstore")] 6 | pub mod blockstore; 7 | #[cfg(feature = "kvstore")] 8 | mod kvstore; 9 | 10 | pub mod namespaces; 11 | 12 | pub use rocks::{Error as RocksDbError, RocksDb, RocksDbConfig}; 13 | -------------------------------------------------------------------------------- /fendermint/rocksdb/src/namespaces.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | /// List all column families to help keep them unique. 5 | /// 6 | /// # Example 7 | /// 8 | /// ``` 9 | /// use fendermint_rocksdb::namespaces; 10 | /// 11 | /// namespaces!(MySpace { foo, bar }); 12 | /// 13 | /// let ms = MySpace::default(); 14 | /// let nss = ms.values(); 15 | /// let ns_foo = &ms.foo; 16 | /// ``` 17 | #[macro_export] 18 | macro_rules! namespaces { 19 | ($name:ident { $($col:ident),* }) => { 20 | struct $name { 21 | pub $($col: String),+ 22 | } 23 | 24 | impl Default for $name { 25 | fn default() -> Self { 26 | Self { 27 | $($col: stringify!($col).to_owned()),+ 28 | } 29 | } 30 | } 31 | 32 | impl $name { 33 | /// List column family names, all of which are required for re-opening the databasae. 34 | pub fn values(&self) -> Vec<&str> { 35 | vec![$(self.$col.as_ref()),+] 36 | } 37 | } 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /fendermint/rocksdb/src/rocks/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // Copyright 2019-2022 ChainSafe Systems 3 | // SPDX-License-Identifier: Apache-2.0, MIT 4 | 5 | use thiserror::Error; 6 | 7 | /// Database error 8 | #[derive(Debug, Error)] 9 | pub enum Error { 10 | #[error(transparent)] 11 | Database(#[from] rocksdb::Error), 12 | #[error("{0}")] 13 | Other(String), 14 | } 15 | -------------------------------------------------------------------------------- /fendermint/rpc/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_rpc" 3 | description = "Utilities working with the tendermint_rpc library to provide an API facade for Fendermint" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | anyhow = { workspace = true } 11 | async-trait = { workspace = true } 12 | base64 = { workspace = true } 13 | bytes = { workspace = true } 14 | prost = { workspace = true } 15 | serde = { workspace = true } 16 | tendermint = { workspace = true } 17 | tendermint-rpc = { workspace = true } 18 | tendermint-proto = { workspace = true } 19 | tracing = { workspace = true } 20 | 21 | cid = { workspace = true } 22 | fvm_ipld_encoding = { workspace = true } 23 | fvm_shared = { workspace = true } 24 | 25 | fendermint_crypto = { path = "../crypto" } 26 | fendermint_vm_actor_interface = { path = "../vm/actor_interface" } 27 | fendermint_vm_message = { path = "../vm/message" } 28 | 29 | [dev-dependencies] 30 | clap = { workspace = true } 31 | ethers = { workspace = true, features = ["abigen"] } 32 | hex = { workspace = true } 33 | lazy_static = { workspace = true } 34 | serde_json = { workspace = true } 35 | tokio = { workspace = true } 36 | tracing = { workspace = true } 37 | tracing-subscriber = { workspace = true } 38 | 39 | fendermint_vm_genesis = { path = "../vm/genesis" } 40 | -------------------------------------------------------------------------------- /fendermint/rpc/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use base64::{ 5 | alphabet, 6 | engine::{DecodePaddingMode, GeneralPurpose, GeneralPurposeConfig}, 7 | }; 8 | 9 | pub mod client; 10 | pub mod message; 11 | pub mod query; 12 | pub mod response; 13 | pub mod tx; 14 | 15 | pub use client::FendermintClient; 16 | pub use query::QueryClient; 17 | pub use tx::TxClient; 18 | 19 | /// A [`base64::Engine`] using the [`alphabet::STANDARD`] base64 alphabet 20 | /// padding bytes when writing but requireing no padding when reading. 21 | const B64_ENGINE: base64::engine::GeneralPurpose = GeneralPurpose::new( 22 | &alphabet::STANDARD, 23 | GeneralPurposeConfig::new() 24 | .with_encode_padding(true) 25 | .with_decode_padding_mode(DecodePaddingMode::Indifferent), 26 | ); 27 | -------------------------------------------------------------------------------- /fendermint/rpc/src/response.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use anyhow::{anyhow, Context}; 4 | use base64::Engine; 5 | use bytes::Bytes; 6 | use fendermint_vm_actor_interface::eam::{self, CreateReturn}; 7 | use fvm_ipld_encoding::{BytesDe, RawBytes}; 8 | use tendermint::abci::response::DeliverTx; 9 | 10 | /// Parse what Tendermint returns in the `data` field of [`DeliverTx`] into bytes. 11 | /// Somewhere along the way it replaces them with the bytes of a Base64 encoded string, 12 | /// and `tendermint_rpc` does not undo that wrapping. 13 | pub fn decode_data(data: &Bytes) -> anyhow::Result { 14 | let b64 = String::from_utf8(data.to_vec()).context("error parsing data as base64 string")?; 15 | let data = base64::engine::general_purpose::STANDARD 16 | .decode(b64) 17 | .context("error parsing base64 to bytes")?; 18 | Ok(RawBytes::from(data)) 19 | } 20 | 21 | /// Apply the encoding that Tendermint does to the bytes inside [`DeliverTx`]. 22 | pub fn encode_data(data: &[u8]) -> Bytes { 23 | let b64 = base64::engine::general_purpose::STANDARD.encode(data); 24 | let bz = b64.as_bytes(); 25 | Bytes::copy_from_slice(bz) 26 | } 27 | 28 | /// Parse what Tendermint returns in the `data` field of [`DeliverTx`] as raw bytes. 29 | /// 30 | /// Only call this after the `code` of both [`DeliverTx`] and [`CheckTx`] have been inspected! 31 | pub fn decode_bytes(deliver_tx: &DeliverTx) -> anyhow::Result { 32 | decode_data(&deliver_tx.data) 33 | } 34 | 35 | /// Parse what Tendermint returns in the `data` field of [`DeliverTx`] as [`CreateReturn`]. 36 | pub fn decode_fevm_create(deliver_tx: &DeliverTx) -> anyhow::Result { 37 | let data = decode_data(&deliver_tx.data)?; 38 | fvm_ipld_encoding::from_slice::(&data) 39 | .map_err(|e| anyhow!("error parsing as CreateReturn: {e}")) 40 | } 41 | 42 | /// Parse what Tendermint returns in the `data` field of [`DeliverTx`] as raw ABI return value. 43 | pub fn decode_fevm_invoke(deliver_tx: &DeliverTx) -> anyhow::Result> { 44 | let data = decode_data(&deliver_tx.data)?; 45 | decode_fevm_return_data(data) 46 | } 47 | 48 | /// Parse what is in the `return_data` field, which is `RawBytes` containing IPLD encoded bytes, into the really raw content. 49 | pub fn decode_fevm_return_data(data: RawBytes) -> anyhow::Result> { 50 | // Some calls like transfers between Ethereum accounts don't return any data. 51 | if data.is_empty() { 52 | return Ok(data.into()); 53 | } 54 | 55 | // This is the data return by the FEVM itself, not something wrapping another piece, 56 | // that is, it's as if it was returning `CreateReturn`, it's returning `RawBytes` encoded as IPLD. 57 | fvm_ipld_encoding::from_slice::(&data) 58 | .map(|bz| bz.0) 59 | .map_err(|e| anyhow!("failed to deserialize bytes returned by FEVM method invocation: {e}")) 60 | } 61 | -------------------------------------------------------------------------------- /fendermint/storage/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_storage" 3 | description = "KV store abstraction for non-blockstore use." 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | im = { version = "15.1.0", optional = true } 13 | quickcheck = { workspace = true, optional = true } 14 | thiserror = { workspace = true } 15 | 16 | [dev-dependencies] 17 | quickcheck_macros = { workspace = true } 18 | serde = { workspace = true } 19 | fvm_ipld_encoding = { workspace = true } 20 | 21 | [features] 22 | default = ["inmem"] 23 | inmem = ["im", "testing"] 24 | testing = ["quickcheck"] 25 | -------------------------------------------------------------------------------- /fendermint/testing/.gitignore: -------------------------------------------------------------------------------- 1 | **/test-data 2 | -------------------------------------------------------------------------------- /fendermint/testing/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_testing" 3 | description = "Testing utilities" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | serde = { workspace = true, optional = true } 13 | serde_json = { workspace = true, optional = true } 14 | arbitrary = { workspace = true, optional = true } 15 | ethers = { workspace = true, optional = true } 16 | hex = { workspace = true, optional = true } 17 | lazy_static = { workspace = true } 18 | num-bigint = { workspace = true, optional = true } 19 | quickcheck = { workspace = true, optional = true } 20 | arbtest = { workspace = true, optional = true } 21 | rand = { workspace = true, optional = true } 22 | 23 | cid = { workspace = true, optional = true } 24 | fvm_ipld_encoding = { workspace = true, optional = true } 25 | fvm_shared = { workspace = true, optional = true, features = ["arb"] } 26 | ipc-sdk = { workspace = true, optional = true } 27 | 28 | [dev-dependencies] 29 | arbitrary = { workspace = true } 30 | 31 | fendermint_testing = { path = ".", features = ["smt"] } 32 | 33 | [features] 34 | default = [] 35 | smt = ["arbitrary", "arbtest"] 36 | golden = ["quickcheck", "hex", "serde", "serde_json", "cid", "fvm_ipld_encoding"] 37 | arb = [ 38 | "quickcheck", 39 | "rand", 40 | "cid", 41 | "fvm_shared", 42 | "ipc-sdk", 43 | "arbitrary", 44 | "num-bigint/arbitrary", 45 | "ethers", 46 | ] 47 | -------------------------------------------------------------------------------- /fendermint/testing/Makefile: -------------------------------------------------------------------------------- 1 | TEST_CONTRACTS_DIR = contracts 2 | TEST_CONTRACTS_SOL = $(shell find $(TEST_CONTRACTS_DIR) -type f -name "*.sol") 3 | TEST_CONTRACTS_BIN = $(TEST_CONTRACTS_SOL:.sol=.bin) 4 | 5 | .PHONY: all 6 | all: \ 7 | test-contracts 8 | 9 | # Compile all Solidity test contracts. 10 | # This could also be achieved with https://docs.rs/ethers/latest/ethers/solc/ 11 | .PHONY: test-contracts 12 | test-contracts: $(TEST_CONTRACTS_BIN) 13 | 14 | # Compile a Solidity test contract 15 | $(TEST_CONTRACTS_DIR)/%.bin: $(TEST_CONTRACTS_DIR)/%.sol | solc 16 | solc --bin --bin-runtime --abi --storage-layout --hashes --overwrite $< -o $(TEST_CONTRACTS_DIR) 17 | 18 | # Requirements checks. 19 | 20 | .PHONY: solc 21 | solc: 22 | @if [ -z "$(shell which solc)" ]; then \ 23 | echo "Please install solc, the Solidity compiler. See https://github.com/crytic/solc-select"; \ 24 | exit 1; \ 25 | fi 26 | -------------------------------------------------------------------------------- /fendermint/testing/README.md: -------------------------------------------------------------------------------- 1 | # Testing 2 | 3 | The `fendermint_testing` crate (ie. the current directory) provides some reusable utilities that can be imported into _other_ tests. These are behind feature flags: 4 | 5 | * `golden`: helper functions for writing tests with golden files 6 | * `arb`: provides `quickcheck::Arbitrary` instances for some things which are problematic in the FVM library, such as `Address` and `TokenAmount`. 7 | * `smt`: small framework for State Machine Testing (a.k.a. Model Testing) 8 | 9 | 10 | # End to end tests 11 | 12 | Beyond this, for no other reason than code organisation, the directory has sub-projects, which contain actual tests. 13 | 14 | For example the [smoke-test](./smoke-test/) is a a crate that uses `cargo make` to start a local stack with Tendermint and Fendermint running in Docker, and run some integration tests, which can be found in the [Makefile.toml](./smoke-test/Makefile.toml). 15 | 16 | To run these, either `cd` into that directory and run them from there, or run all from the root using `make e2e`, which also builds the docker images. 17 | -------------------------------------------------------------------------------- /fendermint/testing/contract-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "contract-test" 3 | description = "Model based testing for smart contracts" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = { workspace = true } 13 | ethers = { workspace = true } 14 | fvm = { workspace = true } 15 | fvm_shared = { workspace = true } 16 | fvm_ipld_blockstore = { workspace = true } 17 | hex = { workspace = true } 18 | rand = { workspace = true } 19 | tendermint-rpc = { workspace = true } 20 | 21 | ipc-sdk = { workspace = true } 22 | ipc_actors_abis = { workspace = true } 23 | 24 | fendermint_testing = { path = "..", features = ["smt", "arb"] } 25 | fendermint_crypto = { path = "../../crypto" } 26 | fendermint_vm_actor_interface = { path = "../../vm/actor_interface" } 27 | fendermint_vm_core = { path = "../../vm/core" } 28 | fendermint_vm_genesis = { path = "../../vm/genesis" } 29 | fendermint_vm_message = { path = "../../vm/message" } 30 | fendermint_vm_interpreter = { path = "../../vm/interpreter", features = ["bundle"] } 31 | 32 | 33 | [dev-dependencies] 34 | arbitrary = { workspace = true } 35 | arbtest = { workspace = true } 36 | rand = { workspace = true } 37 | tokio = { workspace = true } 38 | -------------------------------------------------------------------------------- /fendermint/testing/contract-test/src/ipc/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | pub mod registry; 5 | pub mod subnet; 6 | -------------------------------------------------------------------------------- /fendermint/testing/contract-test/src/ipc/registry.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use anyhow::Context; 5 | use fendermint_vm_actor_interface::eam::EthAddress; 6 | use fendermint_vm_actor_interface::init::builtin_actor_eth_addr; 7 | use fendermint_vm_actor_interface::ipc::SUBNETREGISTRY_ACTOR_ID; 8 | use fendermint_vm_interpreter::fvm::state::fevm::{ContractCaller, MockProvider}; 9 | use fendermint_vm_interpreter::fvm::state::FvmExecState; 10 | use fvm_ipld_blockstore::Blockstore; 11 | use fvm_shared::ActorID; 12 | use ipc_actors_abis::subnet_registry_diamond::SubnetRegistryDiamondErrors; 13 | 14 | pub use ipc_actors_abis::register_subnet_facet::{ 15 | ConstructorParams as SubnetConstructorParams, RegisterSubnetFacet, 16 | }; 17 | 18 | #[derive(Clone)] 19 | pub struct RegistryCaller { 20 | addr: EthAddress, 21 | register: ContractCaller, SubnetRegistryDiamondErrors>, 22 | } 23 | 24 | impl Default for RegistryCaller { 25 | fn default() -> Self { 26 | Self::new(SUBNETREGISTRY_ACTOR_ID) 27 | } 28 | } 29 | 30 | impl RegistryCaller { 31 | pub fn new(actor_id: ActorID) -> Self { 32 | let addr = builtin_actor_eth_addr(actor_id); 33 | Self { 34 | addr, 35 | register: ContractCaller::new(addr, RegisterSubnetFacet::new), 36 | } 37 | } 38 | 39 | pub fn addr(&self) -> EthAddress { 40 | self.addr 41 | } 42 | } 43 | 44 | impl RegistryCaller { 45 | /// Create a new instance of the built-in subnet implemetation. 46 | /// 47 | /// Returns the address of the deployed contract. 48 | pub fn new_subnet( 49 | &self, 50 | state: &mut FvmExecState, 51 | params: SubnetConstructorParams, 52 | ) -> anyhow::Result { 53 | let addr = self 54 | .register 55 | .call(state, |c| c.new_subnet_actor(params)) 56 | .context("failed to create new subnet")?; 57 | Ok(EthAddress(addr.0)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /fendermint/testing/contract-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use anyhow::{anyhow, Context}; 5 | use std::sync::Arc; 6 | 7 | use fendermint_vm_genesis::Genesis; 8 | use fendermint_vm_interpreter::{ 9 | fvm::{ 10 | bundle::{bundle_path, contracts_path}, 11 | state::{FvmExecState, FvmGenesisState}, 12 | store::memory::MemoryBlockstore, 13 | FvmGenesisOutput, FvmMessageInterpreter, 14 | }, 15 | GenesisInterpreter, 16 | }; 17 | use fvm::engine::MultiEngine; 18 | 19 | pub mod ipc; 20 | 21 | pub async fn init_exec_state( 22 | multi_engine: Arc, 23 | genesis: Genesis, 24 | ) -> anyhow::Result<(FvmExecState, FvmGenesisOutput)> { 25 | let bundle = std::fs::read(bundle_path()).context("failed to read bundle")?; 26 | let store = MemoryBlockstore::new(); 27 | 28 | let state = FvmGenesisState::new(store, multi_engine, &bundle) 29 | .await 30 | .context("failed to create state")?; 31 | 32 | let (client, _) = 33 | tendermint_rpc::MockClient::new(tendermint_rpc::MockRequestMethodMatcher::default()); 34 | 35 | let interpreter = FvmMessageInterpreter::new(client, None, contracts_path(), 1.05, 1.05, false); 36 | 37 | let (state, out) = interpreter 38 | .init(state, genesis) 39 | .await 40 | .context("failed to create actors")?; 41 | 42 | let state = state 43 | .into_exec_state() 44 | .map_err(|_| anyhow!("should be in exec stage"))?; 45 | 46 | Ok((state, out)) 47 | } 48 | -------------------------------------------------------------------------------- /fendermint/testing/contract-test/tests/staking/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use arbitrary::{Arbitrary, Unstructured}; 5 | use fendermint_testing::arb::ArbTokenAmount; 6 | use fvm_shared::{bigint::Integer, econ::TokenAmount}; 7 | 8 | pub mod machine; 9 | pub mod state; 10 | 11 | fn choose_amount(u: &mut Unstructured<'_>, max: &TokenAmount) -> arbitrary::Result { 12 | if max.is_zero() { 13 | Ok(TokenAmount::from_atto(0)) 14 | } else { 15 | let tokens = ArbTokenAmount::arbitrary(u)?.0; 16 | Ok(TokenAmount::from_atto(tokens.atto().mod_floor(max.atto()))) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /fendermint/testing/contracts/SimpleCoin.abi: -------------------------------------------------------------------------------- 1 | [{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getBalance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"getBalanceInEth","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendCoin","outputs":[{"internalType":"bool","name":"sufficient","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] -------------------------------------------------------------------------------- /fendermint/testing/contracts/SimpleCoin.bin: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b506127106000803273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550610549806100656000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b91906102d1565b6100d6565b60405161006d919061036f565b60405180910390f35b610090600480360381019061008b91906102fa565b6100f4565b60405161009d9190610354565b60405180910390f35b6100c060048036038101906100bb91906102d1565b61025f565b6040516100cd919061036f565b60405180910390f35b600060026100e38361025f565b6100ed91906103e0565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061043a565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e8919061038a565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c919061036f565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000813590506102b6816104e5565b92915050565b6000813590506102cb816104fc565b92915050565b6000602082840312156102e357600080fd5b60006102f1848285016102a7565b91505092915050565b6000806040838503121561030d57600080fd5b600061031b858286016102a7565b925050602061032c858286016102bc565b9150509250929050565b61033f81610480565b82525050565b61034e816104ac565b82525050565b60006020820190506103696000830184610336565b92915050565b60006020820190506103846000830184610345565b92915050565b6000610395826104ac565b91506103a0836104ac565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156103d5576103d46104b6565b5b828201905092915050565b60006103eb826104ac565b91506103f6836104ac565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561042f5761042e6104b6565b5b828202905092915050565b6000610445826104ac565b9150610450836104ac565b925082821015610463576104626104b6565b5b828203905092915050565b60006104798261048c565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6104ee8161046e565b81146104f957600080fd5b50565b610505816104ac565b811461051057600080fd5b5056fea2646970667358221220d11fa32f457b8122876db665f066909bc482ee02a7ea3972d5a2de406fecf85b64736f6c63430008020033 -------------------------------------------------------------------------------- /fendermint/testing/contracts/SimpleCoin.bin-runtime: -------------------------------------------------------------------------------- 1 | 608060405234801561001057600080fd5b50600436106100415760003560e01c80637bd703e81461004657806390b98a1114610076578063f8b2cb4f146100a6575b600080fd5b610060600480360381019061005b91906102d1565b6100d6565b60405161006d919061036f565b60405180910390f35b610090600480360381019061008b91906102fa565b6100f4565b60405161009d9190610354565b60405180910390f35b6100c060048036038101906100bb91906102d1565b61025f565b6040516100cd919061036f565b60405180910390f35b600060026100e38361025f565b6100ed91906103e0565b9050919050565b6000816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000205410156101455760009050610259565b816000803373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1681526020019081526020016000206000828254610193919061043a565b92505081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546101e8919061038a565b925050819055508273ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef8460405161024c919061036f565b60405180910390a3600190505b92915050565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b6000813590506102b6816104e5565b92915050565b6000813590506102cb816104fc565b92915050565b6000602082840312156102e357600080fd5b60006102f1848285016102a7565b91505092915050565b6000806040838503121561030d57600080fd5b600061031b858286016102a7565b925050602061032c858286016102bc565b9150509250929050565b61033f81610480565b82525050565b61034e816104ac565b82525050565b60006020820190506103696000830184610336565b92915050565b60006020820190506103846000830184610345565b92915050565b6000610395826104ac565b91506103a0836104ac565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156103d5576103d46104b6565b5b828201905092915050565b60006103eb826104ac565b91506103f6836104ac565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff048311821515161561042f5761042e6104b6565b5b828202905092915050565b6000610445826104ac565b9150610450836104ac565b925082821015610463576104626104b6565b5b828203905092915050565b60006104798261048c565b9050919050565b60008115159050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6104ee8161046e565b81146104f957600080fd5b50565b610505816104ac565b811461051057600080fd5b5056fea2646970667358221220d11fa32f457b8122876db665f066909bc482ee02a7ea3972d5a2de406fecf85b64736f6c63430008020033 -------------------------------------------------------------------------------- /fendermint/testing/contracts/SimpleCoin.signatures: -------------------------------------------------------------------------------- 1 | f8b2cb4f: getBalance(address) 2 | 7bd703e8: getBalanceInEth(address) 3 | 90b98a11: sendCoin(address,uint256) 4 | -------------------------------------------------------------------------------- /fendermint/testing/contracts/SimpleCoin.sol: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: MIT 2 | pragma solidity >=0.4.2; 3 | 4 | contract SimpleCoin { 5 | mapping(address => uint256) balances; 6 | 7 | event Transfer(address indexed _from, address indexed _to, uint256 _value); 8 | 9 | constructor() { 10 | balances[tx.origin] = 10000; 11 | } 12 | 13 | function sendCoin( 14 | address receiver, 15 | uint256 amount 16 | ) public returns (bool sufficient) { 17 | if (balances[msg.sender] < amount) return false; 18 | balances[msg.sender] -= amount; 19 | balances[receiver] += amount; 20 | emit Transfer(msg.sender, receiver, amount); 21 | return true; 22 | } 23 | 24 | function getBalanceInEth(address addr) public view returns (uint256) { 25 | return getBalance(addr) * 2; 26 | } 27 | 28 | function getBalance(address addr) public view returns (uint256) { 29 | return balances[addr]; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /fendermint/testing/contracts/SimpleCoin_storage.json: -------------------------------------------------------------------------------- 1 | {"storage":[{"astId":5,"contract":"contracts/SimpleCoin.sol:SimpleCoin","label":"balances","offset":0,"slot":"0","type":"t_mapping(t_address,t_uint256)"}],"types":{"t_address":{"encoding":"inplace","label":"address","numberOfBytes":"20"},"t_mapping(t_address,t_uint256)":{"encoding":"mapping","key":"t_address","label":"mapping(address => uint256)","numberOfBytes":"32","value":"t_uint256"},"t_uint256":{"encoding":"inplace","label":"uint256","numberOfBytes":"32"}}} -------------------------------------------------------------------------------- /fendermint/testing/scripts/ci.env: -------------------------------------------------------------------------------- 1 | CMT_WAIT_MILLIS = 20000 2 | # Help debug any issues with simplecoin by logging requests and responses. 3 | VERBOSITY = "--verbose" 4 | -------------------------------------------------------------------------------- /fendermint/testing/scripts/common.env: -------------------------------------------------------------------------------- 1 | CMT_DOCKER_IMAGE="cometbft/cometbft:v0.37.x" 2 | FM_DOCKER_IMAGE="fendermint:latest" 3 | CMT_CONTAINER_NAME="${NETWORK_NAME}-cometbft" 4 | FM_CONTAINER_NAME="${NETWORK_NAME}-fendermint" 5 | ETHAPI_CONTAINER_NAME="${NETWORK_NAME}-ethapi" 6 | CMT_P2P_HOST_PORT=26656 7 | CMT_RPC_HOST_PORT=26657 8 | ETHAPI_HOST_PORT=8545 9 | FM_NETWORK=main 10 | FM_LOG_LEVEL=info 11 | ETHAPI_LOG_LEVEL=debug 12 | # If this wasn't present, any wait task is skipped. 13 | CARGO_MAKE_WAIT_MILLISECONDS=5000 14 | # This wait time seems to work locally. 15 | CMT_WAIT_MILLIS=10000 16 | # Keep example logs to a minimum. 17 | VERBOSITY="" 18 | TEST_SCRIPTS_DIR="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/fendermint/testing/${TEST_DIR}/scripts" 19 | TEST_DATA_DIR="${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/fendermint/testing/${TEST_DIR}/test-data" 20 | NODE_NAME=node-0 21 | CMT_DIR=${TEST_DATA_DIR}/${NODE_NAME}/cometbft 22 | BASE_DIR=${TEST_DATA_DIR} 23 | ENV_FILE=${BASE_DIR}/.env 24 | -------------------------------------------------------------------------------- /fendermint/testing/scripts/common.toml: -------------------------------------------------------------------------------- 1 | # smoke-test infrastructure: 2 | # cargo install cargo-make 3 | # 4 | # cd fendermint/testing/smoke-test 5 | # - then - 6 | # cargo make --profile ci 7 | # - or - 8 | # cargo make setup 9 | # cargo make test 10 | # docker logs smoke-ethapi 11 | # cargo make teardown 12 | 13 | extend = [ 14 | { path = "../../../infra/scripts/docker.toml" }, 15 | { path = "../../../infra/scripts/cometbft.toml" }, 16 | { path = "../../../infra/scripts/ethapi.toml" }, 17 | { path = "../../../infra/scripts/fendermint.toml" }, 18 | { path = "./fendermint.toml" }, 19 | ] 20 | 21 | 22 | # Define the following in test specific `Makefile.toml` files, 23 | # where `env.env` defines `NETWORK_NAME` and `TEST_DIR`, expected by `common.env`: 24 | # env_files = [ 25 | # { path = "./env.env" }, 26 | # { path = "../Makefile/common.env" }, 27 | # { path = "../Makefile/ci.env", profile = "ci" }, 28 | # ] 29 | 30 | 31 | [tasks.default] 32 | clear = true 33 | run_task = { name = [ 34 | "setup", 35 | "test", 36 | ], fork = true, cleanup_task = "teardown" } 37 | 38 | # Waiting before starting the Eth API for the CometBFT websocket to start listening. 39 | [tasks.setup] 40 | dependencies = [ 41 | "test-data-dir", 42 | "test-data-env", 43 | "test-node-dir", 44 | "docker-network-create", 45 | "cometbft-init", 46 | "fendermint-init", 47 | "fendermint-start", 48 | "cometbft-start", 49 | "ethapi-start", 50 | "cometbft-wait", 51 | "fendermint-logs", 52 | "cometbft-logs", 53 | "ethapi-logs", 54 | ] 55 | 56 | [tasks.test] 57 | clear = true 58 | dependencies = [] 59 | 60 | [tasks.teardown] 61 | # `dependencies` doesn't seem to work with `cleanup_task`. 62 | run_task = { name = [ 63 | "cometbft-destroy", 64 | "fendermint-destroy", 65 | "ethapi-destroy", 66 | "docker-network-rm", 67 | "test-data-dir-rm", 68 | ] } 69 | 70 | 71 | [tasks.test-data-dir] 72 | script = """ 73 | mkdir -p ${TEST_DATA_DIR}/keys; 74 | cp -r ${TEST_SCRIPTS_DIR} ${TEST_DATA_DIR}/scripts 75 | """ 76 | 77 | [tasks.test-data-dir-rm] 78 | script = """ 79 | rm -rf ${TEST_DATA_DIR} 80 | """ 81 | 82 | [tasks.test-data-env] 83 | script = """ 84 | touch ${TEST_DATA_DIR}/.env 85 | """ 86 | 87 | [tasks.test-node-dir] 88 | script = """ 89 | mkdir -p ${TEST_DATA_DIR}/${NODE_NAME}/fendermint/data/logs; 90 | mkdir -p ${TEST_DATA_DIR}/${NODE_NAME}/cometbft; 91 | """ 92 | 93 | [tasks.test-node-dir-rm] 94 | script = """ 95 | rm -rf ${TEST_DATA_DIR}/${NODE_NAME} 96 | """ 97 | -------------------------------------------------------------------------------- /fendermint/testing/scripts/fendermint.toml: -------------------------------------------------------------------------------- 1 | 2 | [tasks.fendermint-init] 3 | extend = "fendermint-run" 4 | env = { "ENTRY" = "/data/scripts/init.sh", "FLAGS" = "-a STDOUT -a STDERR --rm" } 5 | -------------------------------------------------------------------------------- /fendermint/testing/smoke-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "smoke-test" 3 | description = "Provides some end-to-end integration testing between Fendermint and a full Tendermint node." 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /fendermint/testing/smoke-test/Makefile.toml: -------------------------------------------------------------------------------- 1 | # smoke-test infrastructure: 2 | # cargo install cargo-make 3 | # 4 | # cd fendermint/testing/smoke-test 5 | # - then - 6 | # cargo make --profile ci 7 | # - or - 8 | # cargo make setup 9 | # cargo make test 10 | # docker logs smoke-ethapi 11 | # cargo make teardown 12 | 13 | extend = [ 14 | { path = "../scripts/common.toml" }, 15 | ] 16 | 17 | env_files = [ 18 | { path = "./scripts/smoke.env" }, 19 | { path = "../scripts/common.env" }, 20 | { path = "../scripts/ci.env", profile = "ci" }, 21 | ] 22 | 23 | [tasks.test-data-env] 24 | script = """ 25 | cat << EOF > ${TEST_DATA_DIR}/.env 26 | CMT_P2P_MAX_NUM_OUTBOUND_PEERS=0 27 | CMT_CONSENSUS_TIMEOUT_COMMIT=1s 28 | EOF 29 | """ 30 | 31 | [tasks.test] 32 | clear = true 33 | dependencies = ["simplecoin-example", "ethers-example"] 34 | 35 | 36 | [tasks.simplecoin-example] 37 | # Using --release in the hope that it can reuse artifacts compiled earlier for the docker build. 38 | script = """ 39 | cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} 40 | cargo run -p fendermint_rpc --release --example simplecoin -- \ 41 | --secret-key fendermint/testing/smoke-test/test-data/keys/alice.sk \ 42 | ${VERBOSITY} 43 | """ 44 | 45 | 46 | [tasks.ethers-example] 47 | script = """ 48 | cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY} 49 | cargo run -p fendermint_eth_api --release --example ethers -- \ 50 | --secret-key-from fendermint/testing/smoke-test/test-data/keys/emily.sk \ 51 | --secret-key-to fendermint/testing/smoke-test/test-data/keys/eric.sk 52 | """ 53 | -------------------------------------------------------------------------------- /fendermint/testing/smoke-test/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Create test artifacts, which is basically the Tendermint genesis file. 6 | 7 | KEYS_DIR=/data/keys 8 | CMT_DIR=/data/${NODE_NAME}/cometbft 9 | GENESIS_FILE=/data/genesis.json 10 | 11 | # Create a genesis file 12 | fendermint \ 13 | genesis --genesis-file $GENESIS_FILE \ 14 | new \ 15 | --chain-name $FM_CHAIN_NAME \ 16 | --base-fee 1000 \ 17 | --timestamp 1680101412 \ 18 | --power-scale 0 19 | 20 | # Create test keys 21 | mkdir -p $KEYS_DIR 22 | for NAME in alice bob charlie dave; do 23 | fendermint key gen --out-dir $KEYS_DIR --name $NAME; 24 | done 25 | 26 | # Create an account 27 | fendermint \ 28 | genesis --genesis-file $GENESIS_FILE \ 29 | add-account --public-key $KEYS_DIR/alice.pk \ 30 | --balance 1000 31 | 32 | # Create a multisig account 33 | fendermint \ 34 | genesis --genesis-file $GENESIS_FILE \ 35 | add-multisig --public-key $KEYS_DIR/bob.pk \ 36 | --public-key $KEYS_DIR/charlie.pk \ 37 | --public-key $KEYS_DIR/dave.pk \ 38 | --threshold 2 --vesting-start 0 --vesting-duration 1000000 \ 39 | --balance 3000 40 | 41 | # Create some Ethereum accounts 42 | for NAME in emily eric; do 43 | fendermint key gen --out-dir $KEYS_DIR --name $NAME; 44 | fendermint key into-eth --out-dir $KEYS_DIR --secret-key $KEYS_DIR/$NAME.sk --name $NAME-eth; 45 | fendermint \ 46 | genesis --genesis-file $GENESIS_FILE \ 47 | add-account --public-key $KEYS_DIR/$NAME.pk \ 48 | --balance 1000 \ 49 | --kind ethereum 50 | done 51 | 52 | # Add a validator 53 | fendermint \ 54 | genesis --genesis-file $GENESIS_FILE \ 55 | add-validator --public-key $KEYS_DIR/bob.pk --power 1 56 | 57 | # Enable IPC with some dummy values to test contract deployment. 58 | fendermint \ 59 | genesis --genesis-file $GENESIS_FILE \ 60 | ipc gateway \ 61 | --subnet-id /r0 \ 62 | --bottom-up-check-period 10 \ 63 | --min-collateral 1 \ 64 | --msg-fee 10 \ 65 | --majority-percentage 66 66 | 67 | # Convert FM genesis to CMT 68 | fendermint \ 69 | genesis --genesis-file $GENESIS_FILE \ 70 | into-tendermint --out $CMT_DIR/config/genesis.json 71 | 72 | # Convert FM validator key to CMT 73 | fendermint \ 74 | key into-tendermint --secret-key $KEYS_DIR/bob.sk \ 75 | --out $CMT_DIR/config/priv_validator_key.json 76 | -------------------------------------------------------------------------------- /fendermint/testing/smoke-test/scripts/smoke.env: -------------------------------------------------------------------------------- 1 | NETWORK_NAME="smoke" 2 | TEST_DIR="smoke-test" 3 | -------------------------------------------------------------------------------- /fendermint/testing/smoke-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Run some tests against a pair of Fendermint and Tendermint docker containers running locally. 4 | //! 5 | //! Example: 6 | //! 7 | //! ```text 8 | //! cd fendermint/testing/smoke-test 9 | //! cargo make 10 | //! ``` 11 | //! 12 | //! Make sure you installed cargo-make by running `cargo install cargo-make` first. 13 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snapshot-test" 3 | description = "End-to-end integration testing multiple nodes exercising snapshots and state sync" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/scripts/init.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | # Create test artifacts, which is basically the Tendermint genesis file. 6 | 7 | KEYS_DIR=/data/keys 8 | CMT_DIR=/data/${NODE_NAME}/cometbft 9 | GENESIS_FILE=/data/genesis.json 10 | 11 | # Create a genesis file 12 | fendermint \ 13 | genesis --genesis-file $GENESIS_FILE \ 14 | new \ 15 | --chain-name $FM_CHAIN_NAME \ 16 | --base-fee 1000 \ 17 | --timestamp 1680101412 \ 18 | --power-scale 0 19 | 20 | # Create some validators 21 | mkdir -p $KEYS_DIR 22 | for NAME in victoria veronica vivienne; do 23 | fendermint key gen --out-dir $KEYS_DIR --name $NAME; 24 | 25 | # Create Ethereum accounts for them. 26 | fendermint \ 27 | genesis --genesis-file $GENESIS_FILE \ 28 | add-account --public-key $KEYS_DIR/$NAME.pk \ 29 | --balance 1000 \ 30 | --kind ethereum 31 | 32 | # Convert FM validator key to CMT 33 | fendermint \ 34 | key into-tendermint --secret-key $KEYS_DIR/$NAME.sk \ 35 | --out $KEYS_DIR/$NAME.priv_validator_key.json 36 | done 37 | 38 | # Add a validator 39 | VALIDATOR_NAME=victoria 40 | 41 | fendermint \ 42 | genesis --genesis-file $GENESIS_FILE \ 43 | add-validator --public-key $KEYS_DIR/$VALIDATOR_NAME.pk --power 1 44 | 45 | # Convert FM genesis to CMT 46 | fendermint \ 47 | genesis --genesis-file $GENESIS_FILE \ 48 | into-tendermint --out $CMT_DIR/config/genesis.json 49 | 50 | # Copy the default validator key 51 | cp $KEYS_DIR/$VALIDATOR_NAME.priv_validator_key.json \ 52 | $CMT_DIR/config/priv_validator_key.json 53 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/scripts/node-1.env: -------------------------------------------------------------------------------- 1 | SEED_NODE_NAME=node-0 2 | SEED_CMT_CONTAINER_NAME=snapshot-cometbft 3 | NODE_NAME=node-1 4 | ENV_FILE=${TEST_DATA_DIR}/${NODE_NAME}/.env 5 | FM_CONTAINER_NAME=snapshot-fendermint-1 6 | CMT_CONTAINER_NAME=snapshot-cometbft-1 7 | CMT_DIR=${TEST_DATA_DIR}/${NODE_NAME}/cometbft 8 | CMT_P2P_HOST_PORT=26156 9 | CMT_RPC_HOST_PORT=26157 10 | CMT_WAIT_MILLIS=20000 11 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/scripts/node-2.env: -------------------------------------------------------------------------------- 1 | SEED_NODE_NAME=node-0 2 | SEED_CMT_CONTAINER_NAME=snapshot-cometbft 3 | NODE_NAME=node-2 4 | ENV_FILE=${TEST_DATA_DIR}/${NODE_NAME}/.env 5 | FM_CONTAINER_NAME=snapshot-fendermint-2 6 | CMT_CONTAINER_NAME=snapshot-cometbft-2 7 | CMT_DIR=${TEST_DATA_DIR}/${NODE_NAME}/cometbft 8 | CMT_P2P_HOST_PORT=26256 9 | CMT_RPC_HOST_PORT=26257 10 | CMT_WAIT_MILLIS=20000 11 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/scripts/node-3.env: -------------------------------------------------------------------------------- 1 | SEED_NODE_NAME=node-1 2 | SEED_CMT_CONTAINER_NAME=snapshot-cometbft-1 3 | NODE_NAME=node-3 4 | ENV_FILE=${TEST_DATA_DIR}/${NODE_NAME}/.env 5 | FM_CONTAINER_NAME=snapshot-fendermint-3 6 | CMT_CONTAINER_NAME=snapshot-cometbft-3 7 | CMT_DIR=${TEST_DATA_DIR}/${NODE_NAME}/cometbft 8 | CMT_P2P_HOST_PORT=26356 9 | CMT_RPC_HOST_PORT=26357 10 | CMT_WAIT_MILLIS=20000 11 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/scripts/snapshot.env: -------------------------------------------------------------------------------- 1 | NETWORK_NAME="snapshot" 2 | TEST_DIR="snapshot-test" 3 | -------------------------------------------------------------------------------- /fendermint/testing/snapshot-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Run tests against multiple Fendermint+CometBFT docker container pairs locally: 4 | //! 0. The default `snapshot-fendermint` and `snapshot-cometbft` pair 5 | //! 1. A `snapshot-cometbft-1` and `snapshot-cometbft-2`, using `scripts/node-1.env` and `node-2`.env, 6 | //! syncing with the default node from genesis on a block-by-block basis, and clear out their history 7 | //! to force others who sync with them to use snapshots. 8 | //! 2. A `snapshot-cometbft-3` using `scripts/node-3.env`, 9 | //! which syncs with `node-1` and `node-2` using snapshots (a.k.a. state sync). 10 | //! 11 | //! Note that CometBFT state sync requires 2 RPC servers, which is why we need 3 nodes. 12 | //! 13 | //! See and 14 | //! 15 | //! Examples: 16 | //! 17 | //! 1. All in one go 18 | //! ```text 19 | //! cd fendermint/testing/snapshot-test 20 | //! cargo make 21 | //! ``` 22 | //! 23 | //! 2. One by one 24 | //! ```text 25 | //! cd fendermint/testing/snapshot-test 26 | //! cargo make setup 27 | //! cargo make node-1 setup 28 | //! cargo make node-2 setup 29 | //! cargo make node-3 setup 30 | //! docker logs snapshot-cometbft-3 31 | //! cargo make snapshot-teardown 32 | //! cargo make teardown 33 | //! ``` 34 | //! 35 | //! Make sure you installed cargo-make by running `cargo install cargo-make` first. 36 | -------------------------------------------------------------------------------- /fendermint/testing/src/arb/address.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use fvm_shared::address::Address; 4 | use quickcheck::{Arbitrary, Gen}; 5 | 6 | pub use crate::arb::cid::ArbCid; 7 | pub use crate::arb::subnetid::ArbSubnetID; 8 | 9 | /// Unfortunately an arbitrary `DelegatedAddress` can be inconsistent with bytes that do not correspond to its length. 10 | #[derive(Clone, Debug)] 11 | pub struct ArbAddress(pub Address); 12 | 13 | impl Arbitrary for ArbAddress { 14 | fn arbitrary(g: &mut Gen) -> Self { 15 | let addr = Address::arbitrary(g); 16 | let bz = addr.to_bytes(); 17 | Self(Address::from_bytes(&bz).unwrap()) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /fendermint/testing/src/arb/message.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use fvm_shared::message::Message; 4 | use quickcheck::{Arbitrary, Gen}; 5 | 6 | use super::{ArbAddress, ArbTokenAmount}; 7 | 8 | #[derive(Clone, Debug)] 9 | pub struct ArbMessage(pub Message); 10 | 11 | impl Arbitrary for ArbMessage { 12 | fn arbitrary(g: &mut Gen) -> Self { 13 | let mut message = Message::arbitrary(g); 14 | message.gas_fee_cap = ArbTokenAmount::arbitrary(g).0; 15 | message.gas_premium = ArbTokenAmount::arbitrary(g).0; 16 | message.value = ArbTokenAmount::arbitrary(g).0; 17 | message.to = ArbAddress::arbitrary(g).0; 18 | message.from = ArbAddress::arbitrary(g).0; 19 | Self(message) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /fendermint/testing/src/arb/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | mod address; 5 | mod cid; 6 | mod message; 7 | mod subnetid; 8 | mod token; 9 | 10 | pub use crate::arb::address::ArbAddress; 11 | pub use crate::arb::cid::ArbCid; 12 | pub use crate::arb::message::ArbMessage; 13 | pub use crate::arb::subnetid::{ArbSubnetAddress, ArbSubnetID}; 14 | pub use crate::arb::token::ArbTokenAmount; 15 | -------------------------------------------------------------------------------- /fendermint/testing/src/arb/subnetid.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use fvm_shared::address::Address; 4 | use ipc_sdk::subnet_id::SubnetID; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct ArbSubnetAddress(pub Address); 8 | 9 | #[derive(Debug, Clone)] 10 | pub struct ArbSubnetID(pub SubnetID); 11 | 12 | impl quickcheck::Arbitrary for ArbSubnetID { 13 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 14 | let child_count = usize::arbitrary(g) % 4; 15 | 16 | let children = (0..child_count) 17 | .map(|_| ArbSubnetAddress::arbitrary(g).0) 18 | .collect::>(); 19 | 20 | Self(SubnetID::new(u64::arbitrary(g), children)) 21 | } 22 | } 23 | 24 | impl arbitrary::Arbitrary<'_> for ArbSubnetID { 25 | fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { 26 | let child_count = usize::arbitrary(u)? % 4; 27 | 28 | let children = (0..child_count) 29 | .map(|_| Ok(ArbSubnetAddress::arbitrary(u)?.0)) 30 | .collect::, _>>()?; 31 | 32 | Ok(Self(SubnetID::new(u64::arbitrary(u)?, children))) 33 | } 34 | } 35 | 36 | impl quickcheck::Arbitrary for ArbSubnetAddress { 37 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 38 | let addr = if bool::arbitrary(g) { 39 | Address::new_id(u64::arbitrary(g)) 40 | } else { 41 | // Only expecting EAM managed delegated addresses. 42 | let subaddr: [u8; 20] = std::array::from_fn(|_| u8::arbitrary(g)); 43 | Address::new_delegated(10, &subaddr).unwrap() 44 | }; 45 | Self(addr) 46 | } 47 | } 48 | 49 | impl arbitrary::Arbitrary<'_> for ArbSubnetAddress { 50 | fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { 51 | let addr = if bool::arbitrary(u)? { 52 | Address::new_id(u64::arbitrary(u)?) 53 | } else { 54 | // Only expecting EAM managed delegated addresses. 55 | let mut subaddr = [0u8; 20]; 56 | for b in &mut subaddr { 57 | *b = u8::arbitrary(u)?; 58 | } 59 | Address::new_delegated(10, &subaddr).unwrap() 60 | }; 61 | Ok(Self(addr)) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /fendermint/testing/src/arb/token.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use ethers::types::U256; 4 | use fvm_shared::{ 5 | bigint::{BigInt, Integer, Sign, MAX_BIGINT_SIZE}, 6 | econ::TokenAmount, 7 | }; 8 | use lazy_static::lazy_static; 9 | use quickcheck::Gen; 10 | use std::str::FromStr; 11 | 12 | lazy_static! { 13 | /// The max below is taken from https://github.com/filecoin-project/ref-fvm/blob/fvm%40v3.0.0-alpha.24/shared/src/bigint/bigint_ser.rs#L80-L81 14 | static ref MAX_BIGINT: BigInt = 15 | BigInt::new(Sign::Plus, vec![u32::MAX; MAX_BIGINT_SIZE / 4 - 1]); 16 | 17 | static ref MAX_U256: BigInt = BigInt::from_str(&U256::MAX.to_string()).unwrap(); 18 | 19 | /// `fvm_shared::sys::TokenAmount` is limited to `u128` range. 20 | static ref MAX_U128: BigInt = BigInt::from(u128::MAX); 21 | 22 | // Restrict maximum token value to what we can actually pass to Ethereum. 23 | static ref MAX_ATTO: BigInt = MAX_BIGINT.clone().min(MAX_U128.clone()); 24 | } 25 | 26 | #[derive(Clone, Debug)] 27 | /// Unfortunately an arbitrary `TokenAmount` is not serializable if it has more than 128 bytes, we get "BigInt too large" error. 28 | pub struct ArbTokenAmount(pub TokenAmount); 29 | 30 | impl quickcheck::Arbitrary for ArbTokenAmount { 31 | fn arbitrary(g: &mut Gen) -> Self { 32 | let tokens = TokenAmount::arbitrary(g); 33 | let atto = tokens.atto(); 34 | let atto = atto.mod_floor(&MAX_ATTO); 35 | Self(TokenAmount::from_atto(atto)) 36 | } 37 | } 38 | 39 | impl arbitrary::Arbitrary<'_> for ArbTokenAmount { 40 | fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result { 41 | // Using double because the way it's generated is base don vectors, 42 | // and they are often empty when the `size` parameter is small. 43 | let atto = BigInt::arbitrary(u)? + BigInt::arbitrary(u)?; 44 | let atto = atto.mod_floor(&MAX_ATTO); 45 | Ok(Self(TokenAmount::from_atto(atto))) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /fendermint/testing/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | #[cfg(feature = "arb")] 4 | pub mod arb; 5 | #[cfg(feature = "golden")] 6 | pub mod golden; 7 | #[cfg(feature = "smt")] 8 | pub mod smt; 9 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_actor_interface" 3 | description = "Re-export interfaces of built-in actors, either copied versions or from direct project reference." 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = { workspace = true } 13 | ethers = { workspace = true } 14 | hex = { workspace = true } 15 | lazy_static = { workspace = true } 16 | merkle-tree-rs = { workspace = true } 17 | paste = { workspace = true } 18 | serde = { workspace = true } 19 | serde_tuple = { workspace = true } 20 | tracing = { workspace = true } 21 | 22 | cid = { workspace = true } 23 | fvm_shared = { workspace = true } 24 | fvm_ipld_encoding = { workspace = true } 25 | fvm_ipld_hamt = { workspace = true } 26 | fvm_ipld_blockstore = { workspace = true } 27 | 28 | fil_actors_evm_shared = { workspace = true } 29 | ipc_actors_abis = { workspace = true } 30 | ipc-sdk = { workspace = true } 31 | 32 | # The following is disabled so its dependency on an earlier version of fvm_shared doesn't 33 | # stop us from using the latest version of the FVM. It can be re-enabled if there are more 34 | # hardcoded method hashes than what we care to maintain, but currently there is only one. 35 | # frc42_dispatch = { workspace = true } 36 | 37 | fendermint_vm_genesis = { path = "../genesis" } 38 | fendermint_crypto = { path = "../../crypto" } 39 | 40 | [dev-dependencies] 41 | ethers-core = { workspace = true } 42 | quickcheck = { workspace = true } 43 | quickcheck_macros = { workspace = true } 44 | rand = { workspace = true } 45 | 46 | fendermint_vm_genesis = { path = "../genesis", features = ["arb"] } 47 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/account.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use fvm_ipld_encoding::tuple::*; 4 | use fvm_shared::address::Address; 5 | 6 | define_code!(ACCOUNT { code_id: 4 }); 7 | 8 | /// State includes the address for the actor 9 | #[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] 10 | pub struct State { 11 | pub address: Address, 12 | } 13 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/burntfunds.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | // The burnt funds actor is just an Account actor. 5 | 6 | define_id!(BURNT_FUNDS { id: 99 }); 7 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/cron.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use fvm_ipld_encoding::tuple::*; 4 | use fvm_shared::address::Address; 5 | use fvm_shared::MethodNum; 6 | use fvm_shared::METHOD_CONSTRUCTOR; 7 | 8 | define_singleton!(CRON { id: 3, code_id: 3 }); 9 | 10 | /// Cron actor methods available. 11 | #[repr(u64)] 12 | pub enum Method { 13 | Constructor = METHOD_CONSTRUCTOR, 14 | EpochTick = 2, 15 | } 16 | 17 | /// Cron actor state which holds entries to call during epoch tick 18 | #[derive(Default, Serialize_tuple, Deserialize_tuple, Clone, Debug)] 19 | pub struct State { 20 | /// Entries is a set of actors (and corresponding methods) to call during EpochTick. 21 | pub entries: Vec, 22 | } 23 | 24 | #[derive(Clone, PartialEq, Eq, Debug, Serialize_tuple, Deserialize_tuple)] 25 | pub struct Entry { 26 | /// The actor to call (ID address) 27 | pub receiver: Address, 28 | /// The method number to call (must accept empty parameters) 29 | pub method_num: MethodNum, 30 | } 31 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/diamond.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Helper data structures to declare diamond pattern contracts. 4 | 5 | // See https://medium.com/@MarqyMarq/how-to-implement-the-diamond-standard-69e87dae44e6 6 | 7 | use std::collections::HashMap; 8 | 9 | use ethers::abi::Abi; 10 | use fvm_shared::ActorID; 11 | 12 | #[derive(Clone, Debug)] 13 | pub struct EthFacet { 14 | pub name: &'static str, 15 | pub abi: Abi, 16 | } 17 | 18 | /// Top level Ethereum contract with a pre-determined ID. 19 | #[derive(Clone, Debug)] 20 | pub struct EthContract { 21 | /// Pre-determined ID for the contract. 22 | /// 23 | /// 0 means the contract will get a dynamic ID. 24 | pub actor_id: ActorID, 25 | pub abi: Abi, 26 | /// List of facets if the contract is using the diamond pattern. 27 | pub facets: Vec, 28 | } 29 | 30 | pub type EthContractMap = HashMap<&'static str, EthContract>; 31 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/ethaccount.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | define_code!(ETHACCOUNT { code_id: 16 }); 5 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! The modules in this crate a thin interfaces to builtin-actors, 4 | //! so that the rest of the system doesn't have to copy-paste things 5 | //! such as actor IDs, method numbers, method parameter data types. 6 | //! 7 | //! This is similar to how the FVM library contains copies for actors 8 | //! it assumes to be deployed, like the init-actor. There, it's to avoid 9 | //! circular project dependencies. Here, we have the option to reference 10 | //! the actor projects directly and re-export what we need, or to copy 11 | //! the relevant pieces of code. By limiting this choice to this crate, 12 | //! the rest of the application can avoid ad-hoc magic numbers. 13 | //! 14 | //! The actor IDs can be found in [singletons](https://github.com/filecoin-project/builtin-actors/blob/master/runtime/src/builtin/singletons.rs), 15 | //! while the code IDs are in [builtins](https://github.com/filecoin-project/builtin-actors/blob/master/runtime/src/runtime/builtins.rs) 16 | 17 | /// Something we can use for empty state, similar to how the FVM uses `EMPTY_ARR_CID`. 18 | pub const EMPTY_ARR: [(); 0] = [(); 0]; // Based on how it's done in `Tester`. 19 | 20 | macro_rules! define_code { 21 | ($name:ident { code_id: $code_id:literal }) => { 22 | paste::paste! { 23 | /// Position of the actor in the builtin actor bundle manifest. 24 | pub const [<$name _ACTOR_CODE_ID>]: u32 = $code_id; 25 | } 26 | }; 27 | } 28 | 29 | macro_rules! define_id { 30 | ($name:ident { id: $id:literal }) => { 31 | paste::paste! { 32 | pub const [<$name _ACTOR_ID>]: fvm_shared::ActorID = $id; 33 | pub const [<$name _ACTOR_ADDR>]: fvm_shared::address::Address = fvm_shared::address::Address::new_id([<$name _ACTOR_ID>]); 34 | } 35 | }; 36 | } 37 | 38 | macro_rules! define_singleton { 39 | ($name:ident { id: $id:literal, code_id: $code_id:literal }) => { 40 | define_id!($name { id: $id }); 41 | define_code!($name { code_id: $code_id }); 42 | }; 43 | } 44 | 45 | pub mod account; 46 | pub mod burntfunds; 47 | pub mod cron; 48 | pub mod diamond; 49 | pub mod eam; 50 | pub mod ethaccount; 51 | pub mod evm; 52 | pub mod init; 53 | pub mod ipc; 54 | pub mod multisig; 55 | pub mod placeholder; 56 | pub mod reward; 57 | pub mod system; 58 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/multisig.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use cid::Cid; 4 | use fvm_ipld_blockstore::Blockstore; 5 | use fvm_ipld_encoding::tuple::*; 6 | use fvm_ipld_hamt::Hamt; 7 | use fvm_shared::{address::Address, clock::ChainEpoch, econ::TokenAmount, ActorID, HAMT_BIT_WIDTH}; 8 | use serde::{Deserialize, Serialize}; 9 | 10 | define_code!(MULTISIG { code_id: 9 }); 11 | 12 | /// Transaction ID type 13 | #[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, Hash, Eq, PartialEq, PartialOrd)] 14 | #[serde(transparent)] 15 | pub struct TxnID(pub i64); 16 | 17 | /// Multisig actor state 18 | #[derive(Serialize_tuple, Deserialize_tuple, Debug, Clone)] 19 | pub struct State { 20 | pub signers: Vec
, 21 | pub num_approvals_threshold: u64, 22 | pub next_tx_id: TxnID, 23 | 24 | // Linear unlock 25 | pub initial_balance: TokenAmount, 26 | pub start_epoch: ChainEpoch, 27 | pub unlock_duration: ChainEpoch, 28 | 29 | pub pending_txs: Cid, 30 | } 31 | 32 | impl State { 33 | pub fn new( 34 | store: &BS, 35 | signers: Vec, 36 | threshold: u64, 37 | start: ChainEpoch, 38 | duration: ChainEpoch, 39 | balance: TokenAmount, 40 | ) -> anyhow::Result { 41 | let empty_map_cid = Hamt::<_, ()>::new_with_bit_width(store, HAMT_BIT_WIDTH).flush()?; 42 | let state = Self { 43 | signers: signers.into_iter().map(Address::new_id).collect(), 44 | num_approvals_threshold: threshold, 45 | next_tx_id: Default::default(), 46 | initial_balance: balance, 47 | start_epoch: start, 48 | unlock_duration: duration, 49 | pending_txs: empty_map_cid, 50 | }; 51 | Ok(state) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/placeholder.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Placeholders can be used for delegated address types. 4 | //! The FVM automatically creates one if the recipient of a transaction 5 | //! doesn't exist. Then, the executor replaces the code later based on 6 | //! the namespace in the delegated address. 7 | 8 | define_code!(PLACEHOLDER { code_id: 13 }); 9 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/reward.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | // The reward actor is a singleton, but for now let's just use a 5 | // simple account, instead of the one in the built-in actors library, 6 | // because that has too many Filecoin mainnet specific things. 7 | 8 | define_id!(REWARD { id: 2 }); 9 | -------------------------------------------------------------------------------- /fendermint/vm/actor_interface/src/system.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use cid::Cid; 4 | use fvm_ipld_encoding::tuple::*; 5 | use fvm_shared::address::Address; 6 | use lazy_static::lazy_static; 7 | 8 | use crate::eam::EthAddress; 9 | 10 | define_singleton!(SYSTEM { id: 0, code_id: 1 }); 11 | 12 | lazy_static! { 13 | /// The Ethereum null-address 0x00..00 can also be used to identify the system actor. 14 | pub static ref SYSTEM_ACTOR_ETH_ADDR: Address = EthAddress::default().into(); 15 | } 16 | 17 | /// Check whether the address is one of those identifying the system actor. 18 | pub fn is_system_addr(addr: &Address) -> bool { 19 | *addr == SYSTEM_ACTOR_ADDR || *addr == *SYSTEM_ACTOR_ETH_ADDR 20 | } 21 | 22 | /// System actor state. 23 | #[derive(Default, Deserialize_tuple, Serialize_tuple, Debug, Clone)] 24 | pub struct State { 25 | // builtin actor registry: Vec<(String, Cid)> 26 | pub builtin_actors: Cid, 27 | } 28 | -------------------------------------------------------------------------------- /fendermint/vm/core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_core" 3 | description = "Core data structures shared between genesis and messages" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | fnv = { workspace = true } 11 | lazy_static = { workspace = true } 12 | regex = { workspace = true } 13 | serde = { workspace = true } 14 | thiserror = { workspace = true } 15 | arbitrary = { workspace = true, optional = true } 16 | quickcheck = { workspace = true, optional = true } 17 | 18 | cid = { workspace = true } 19 | fvm_shared = { workspace = true } 20 | 21 | [dev-dependencies] 22 | quickcheck = { workspace = true } 23 | quickcheck_macros = { workspace = true } 24 | -------------------------------------------------------------------------------- /fendermint/vm/core/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | pub mod chainid; 4 | mod timestamp; 5 | 6 | pub use timestamp::Timestamp; 7 | -------------------------------------------------------------------------------- /fendermint/vm/core/src/timestamp.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Unix timestamp (in seconds). 6 | #[derive(Clone, Debug, Copy, Serialize, Deserialize, PartialEq, Eq)] 7 | pub struct Timestamp(pub u64); 8 | 9 | impl Timestamp { 10 | pub fn as_secs(&self) -> i64 { 11 | self.0 as i64 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /fendermint/vm/encoding/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_encoding" 3 | description = "Utilities for encoding some of the primitive FVM types with serde" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | 10 | [dependencies] 11 | serde = { workspace = true } 12 | serde_with = { workspace = true } 13 | num-traits = { workspace = true } 14 | 15 | cid = { workspace = true } 16 | fvm_shared = { workspace = true } 17 | ipc-sdk = { workspace = true } 18 | -------------------------------------------------------------------------------- /fendermint/vm/genesis/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_genesis" 3 | description = "Genesis data used to initialize the FVM state when the chain is created" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = { workspace = true } 13 | serde = { workspace = true } 14 | serde_with = { workspace = true } 15 | num-traits = { workspace = true } 16 | arbitrary = { workspace = true, optional = true } 17 | quickcheck = { workspace = true, optional = true } 18 | rand = { workspace = true, optional = true } 19 | tendermint = { workspace = true } 20 | 21 | cid = { workspace = true, optional = true } 22 | fvm_shared = { workspace = true } 23 | ipc-sdk = { workspace = true } 24 | 25 | fendermint_crypto = { path = "../../crypto" } 26 | fendermint_testing = { path = "../../testing", optional = true } 27 | fendermint_vm_core = { path = "../core" } 28 | fendermint_vm_encoding = { path = "../encoding" } 29 | 30 | [dev-dependencies] 31 | quickcheck = { workspace = true } 32 | quickcheck_macros = { workspace = true } 33 | hex = { workspace = true } 34 | serde_json = { workspace = true } 35 | 36 | # Enable arb on self for tests. 37 | fendermint_vm_genesis = { path = ".", features = ["arb"] } 38 | fendermint_testing = { path = "../../testing", features = ["golden"] } 39 | fvm_ipld_encoding = { workspace = true } 40 | 41 | [features] 42 | default = [] 43 | arb = [ 44 | "arbitrary", 45 | "quickcheck", 46 | "fvm_shared/arb", 47 | "fendermint_testing/arb", 48 | "rand", 49 | "cid", 50 | ] 51 | -------------------------------------------------------------------------------- /fendermint/vm/genesis/tests/golden.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | /// JSON based test in case we want to configure the Genesis by hand. 5 | mod json { 6 | use fendermint_testing::golden_json; 7 | use fendermint_vm_genesis::Genesis; 8 | use quickcheck::Arbitrary; 9 | golden_json! { "genesis/json", genesis, Genesis::arbitrary } 10 | } 11 | 12 | /// CBOR based tests in case we have to grab Genesis from on-chain storage. 13 | mod cbor { 14 | use fendermint_testing::golden_cbor; 15 | use fendermint_vm_genesis::Genesis; 16 | use quickcheck::Arbitrary; 17 | golden_cbor! { "genesis/cbor", genesis, Genesis::arbitrary } 18 | } 19 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_interpreter" 3 | description = "Execute messages using the FVM" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | fendermint_vm_actor_interface = { path = "../actor_interface" } 13 | fendermint_vm_core = { path = "../core" } 14 | fendermint_vm_encoding = { path = "../encoding" } 15 | fendermint_vm_genesis = { path = "../genesis" } 16 | fendermint_vm_message = { path = "../message" } 17 | fendermint_vm_resolver = { path = "../resolver" } 18 | fendermint_vm_topdown = { path = "../topdown" } 19 | fendermint_crypto = { path = "../../crypto" } 20 | fendermint_eth_hardhat = { path = "../../eth/hardhat" } 21 | fendermint_rpc = { path = "../../rpc" } 22 | ipc_actors_abis = { workspace = true } 23 | 24 | ipc-sdk = { workspace = true } 25 | 26 | async-trait = { workspace = true } 27 | async-stm = { workspace = true } 28 | anyhow = { workspace = true } 29 | ethers = { workspace = true } 30 | hex = { workspace = true } 31 | num-traits = { workspace = true } 32 | serde = { workspace = true } 33 | serde_with = { workspace = true } 34 | serde_json = { workspace = true } 35 | tendermint = { workspace = true } 36 | tendermint-rpc = { workspace = true } 37 | tracing = { workspace = true } 38 | thiserror = { workspace = true } 39 | 40 | cid = { workspace = true } 41 | fvm = { workspace = true } 42 | fvm_shared = { workspace = true } 43 | fvm_ipld_blockstore = { workspace = true } 44 | fvm_ipld_encoding = { workspace = true } 45 | fvm_ipld_car = { workspace = true } 46 | 47 | futures-core = { workspace = true } 48 | futures-util = { workspace = true } 49 | libipld = { workspace = true } 50 | tokio = { workspace = true } 51 | pin-project = { workspace = true } 52 | tokio-stream = { workspace = true } 53 | tokio-util = { workspace = true } 54 | 55 | [dev-dependencies] 56 | quickcheck = { workspace = true } 57 | quickcheck_macros = { workspace = true } 58 | tempfile = { workspace = true } 59 | 60 | fvm = { workspace = true, features = ["arb", "testing"] } 61 | fendermint_vm_genesis = { path = "../genesis", features = ["arb"] } 62 | 63 | [features] 64 | default = [] 65 | bundle = [] 66 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/bundle.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use std::path::{Path, PathBuf}; 5 | use std::str::FromStr; 6 | 7 | fn workspace_dir() -> PathBuf { 8 | let output = std::process::Command::new(env!("CARGO")) 9 | .arg("locate-project") 10 | .arg("--workspace") 11 | .arg("--message-format=plain") 12 | .output() 13 | .unwrap() 14 | .stdout; 15 | let cargo_path = Path::new(std::str::from_utf8(&output).unwrap().trim()); 16 | cargo_path.parent().unwrap().to_path_buf() 17 | } 18 | 19 | /// Path to the builtin-actor bundle, indended to be used in tests. 20 | pub fn bundle_path() -> PathBuf { 21 | let bundle_path = std::env::var("FM_BUILTIN_ACTORS_BUNDLE").unwrap_or_else(|_| { 22 | workspace_dir() 23 | .join("./builtin-actors/output/bundle.car") 24 | .to_string_lossy() 25 | .into_owned() 26 | }); 27 | 28 | PathBuf::from_str(&bundle_path).expect("malformed bundle path") 29 | } 30 | 31 | /// Path to the Solidity contracts, intended to be used in tests. 32 | pub fn contracts_path() -> PathBuf { 33 | let contracts_path = std::env::var("FM_CONTRACTS_DIR").unwrap_or_else(|_| { 34 | workspace_dir() 35 | .join("../ipc-solidity-actors/out") 36 | .to_string_lossy() 37 | .into_owned() 38 | }); 39 | 40 | PathBuf::from_str(&contracts_path).expect("malformed contracts path") 41 | } 42 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/externs.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use cid::Cid; 4 | use fvm::externs::{Chain, Consensus, Externs, Rand}; 5 | use fvm_shared::clock::ChainEpoch; 6 | 7 | /// Dummy externs - these are related to Expected Consensus, 8 | /// which I believe we have nothing to do with. 9 | pub struct FendermintExterns; 10 | 11 | impl Rand for FendermintExterns { 12 | fn get_chain_randomness( 13 | &self, 14 | _pers: i64, 15 | _round: ChainEpoch, 16 | _entropy: &[u8], 17 | ) -> anyhow::Result<[u8; 32]> { 18 | todo!("might need randomness") 19 | } 20 | 21 | fn get_beacon_randomness( 22 | &self, 23 | _pers: i64, 24 | _round: ChainEpoch, 25 | _entropy: &[u8], 26 | ) -> anyhow::Result<[u8; 32]> { 27 | unimplemented!("not expecting to use the beacon") 28 | } 29 | } 30 | 31 | impl Consensus for FendermintExterns { 32 | fn verify_consensus_fault( 33 | &self, 34 | _h1: &[u8], 35 | _h2: &[u8], 36 | _extra: &[u8], 37 | ) -> anyhow::Result<(Option, i64)> { 38 | unimplemented!("not expecting to use consensus faults") 39 | } 40 | } 41 | 42 | impl Chain for FendermintExterns { 43 | fn get_tipset_cid(&self, _epoch: ChainEpoch) -> anyhow::Result { 44 | unimplemented!("not expecting to use tipsets") 45 | } 46 | } 47 | 48 | impl Externs for FendermintExterns {} 49 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/state/check.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use anyhow::{anyhow, Context}; 5 | 6 | use cid::Cid; 7 | use fendermint_vm_core::chainid::HasChainID; 8 | use fvm::state_tree::StateTree; 9 | use fvm_ipld_blockstore::Blockstore; 10 | use fvm_shared::chainid::ChainID; 11 | 12 | use crate::fvm::store::ReadOnlyBlockstore; 13 | 14 | /// A state we create for the execution of all the messages in a block. 15 | pub struct FvmCheckState 16 | where 17 | DB: Blockstore + 'static, 18 | { 19 | state_tree: StateTree>, 20 | chain_id: ChainID, 21 | } 22 | 23 | impl FvmCheckState 24 | where 25 | DB: Blockstore + 'static, 26 | { 27 | pub fn new(blockstore: DB, state_root: Cid, chain_id: ChainID) -> anyhow::Result { 28 | // Sanity check that the blockstore contains the supplied state root. 29 | if !blockstore 30 | .has(&state_root) 31 | .context("failed to load initial state-root")? 32 | { 33 | return Err(anyhow!( 34 | "blockstore doesn't have the initial state-root {}", 35 | state_root 36 | )); 37 | } 38 | 39 | // Create a new state tree from the supplied root. 40 | let state_tree = { 41 | let bstore = ReadOnlyBlockstore::new(blockstore); 42 | StateTree::new_from_root(bstore, &state_root)? 43 | }; 44 | 45 | let state = Self { 46 | state_tree, 47 | chain_id, 48 | }; 49 | 50 | Ok(state) 51 | } 52 | 53 | pub fn state_tree_mut(&mut self) -> &mut StateTree> { 54 | &mut self.state_tree 55 | } 56 | } 57 | 58 | impl HasChainID for FvmCheckState 59 | where 60 | DB: Blockstore + 'static, 61 | { 62 | fn chain_id(&self) -> ChainID { 63 | self.chain_id 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/state/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | mod check; 5 | mod exec; 6 | pub mod fevm; 7 | mod genesis; 8 | pub mod ipc; 9 | mod query; 10 | pub mod snapshot; 11 | 12 | use std::sync::Arc; 13 | 14 | pub use check::FvmCheckState; 15 | pub use exec::{BlockHash, FvmExecState, FvmStateParams, FvmUpdatableParams}; 16 | pub use genesis::{empty_state_tree, FvmGenesisState}; 17 | pub use query::FvmQueryState; 18 | 19 | use super::store::ReadOnlyBlockstore; 20 | 21 | /// We use full state even for checking, to support certain client scenarios. 22 | pub type CheckStateRef = Arc>>>>; 23 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/store/memory.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use std::{ 5 | collections::HashMap, 6 | sync::{Arc, RwLock}, 7 | }; 8 | 9 | use anyhow::Result; 10 | use cid::Cid; 11 | use fvm_ipld_blockstore::Blockstore; 12 | 13 | /// An in-memory blockstore that can be shared between threads, 14 | /// unlike [fvm_ipld_blockstore::memory::MemoryBlockstore]. 15 | #[derive(Debug, Default, Clone)] 16 | pub struct MemoryBlockstore { 17 | blocks: Arc>>>, 18 | } 19 | 20 | impl MemoryBlockstore { 21 | pub fn new() -> Self { 22 | Self::default() 23 | } 24 | } 25 | 26 | impl Blockstore for MemoryBlockstore { 27 | fn has(&self, k: &Cid) -> Result { 28 | let guard = self.blocks.read().unwrap(); 29 | Ok(guard.contains_key(k)) 30 | } 31 | 32 | fn get(&self, k: &Cid) -> Result>> { 33 | let guard = self.blocks.read().unwrap(); 34 | Ok(guard.get(k).cloned()) 35 | } 36 | 37 | fn put_keyed(&self, k: &Cid, block: &[u8]) -> Result<()> { 38 | let mut guard = self.blocks.write().unwrap(); 39 | guard.insert(*k, block.into()); 40 | Ok(()) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/store/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use cid::Cid; 4 | use fvm::EMPTY_ARR_CID; 5 | use fvm_ipld_blockstore::Blockstore; 6 | 7 | pub mod memory; 8 | 9 | #[derive(Clone)] 10 | pub struct ReadOnlyBlockstore(DB); 11 | 12 | impl ReadOnlyBlockstore { 13 | pub fn new(store: DB) -> Self { 14 | Self(store) 15 | } 16 | } 17 | 18 | impl Blockstore for ReadOnlyBlockstore 19 | where 20 | DB: Blockstore, 21 | { 22 | fn get(&self, k: &Cid) -> anyhow::Result>> { 23 | self.0.get(k) 24 | } 25 | 26 | fn put_keyed(&self, k: &Cid, block: &[u8]) -> anyhow::Result<()> { 27 | // The FVM inserts this each time to make sure it exists. 28 | if *k == *EMPTY_ARR_CID { 29 | return self.0.put_keyed(k, block); 30 | } 31 | panic!("never intended to use put on the read-only blockstore") 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /fendermint/vm/interpreter/src/fvm/topdown.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Topdown finality related util functions 4 | 5 | use crate::chain::TopDownFinalityProvider; 6 | use crate::fvm::state::ipc::GatewayCaller; 7 | use crate::fvm::state::FvmExecState; 8 | use crate::fvm::FvmApplyRet; 9 | use anyhow::Context; 10 | use fendermint_vm_topdown::{BlockHeight, IPCParentFinality, ParentViewProvider}; 11 | use fvm_ipld_blockstore::Blockstore; 12 | use ipc_sdk::cross::CrossMsg; 13 | 14 | use super::state::ipc::tokens_to_mint; 15 | 16 | /// Commit the parent finality. Returns the height that the previous parent finality is committed and 17 | /// the committed finality itself. If there is no parent finality committed, genesis epoch is returned. 18 | pub async fn commit_finality( 19 | gateway_caller: &GatewayCaller, 20 | state: &mut FvmExecState, 21 | finality: IPCParentFinality, 22 | provider: &TopDownFinalityProvider, 23 | ) -> anyhow::Result<(BlockHeight, Option)> 24 | where 25 | DB: Blockstore + Sync + Send + 'static, 26 | { 27 | let (prev_height, prev_finality) = 28 | if let Some(prev_finality) = gateway_caller.commit_parent_finality(state, finality)? { 29 | (prev_finality.height, Some(prev_finality)) 30 | } else { 31 | (provider.genesis_epoch()?, None) 32 | }; 33 | 34 | tracing::debug!( 35 | "commit finality parsed: prev_height {prev_height}, prev_finality: {prev_finality:?}" 36 | ); 37 | 38 | Ok((prev_height, prev_finality)) 39 | } 40 | 41 | /// Execute the top down messages implicitly. Before the execution, mint to the gateway of the funds 42 | /// transferred in the messages, and increase the circulating supply with the incoming value. 43 | pub async fn execute_topdown_msgs( 44 | gateway_caller: &GatewayCaller, 45 | state: &mut FvmExecState, 46 | messages: Vec, 47 | ) -> anyhow::Result 48 | where 49 | DB: Blockstore + Sync + Send + 'static, 50 | { 51 | let minted_tokens = tokens_to_mint(&messages); 52 | 53 | gateway_caller 54 | .mint_to_gateway(state, minted_tokens.clone()) 55 | .context("failed to mint to gateway")?; 56 | 57 | state.update_circ_supply(|circ_supply| { 58 | *circ_supply += minted_tokens; 59 | }); 60 | 61 | gateway_caller.apply_cross_messages(state, messages) 62 | } 63 | -------------------------------------------------------------------------------- /fendermint/vm/message/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_message" 3 | description = "Messages (transactions) received on chain and passed on to the FVM" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | anyhow = { workspace = true } 11 | blake2b_simd = { workspace = true } 12 | ethers-core = { workspace = true } 13 | lazy_static = { workspace = true } 14 | thiserror = { workspace = true } 15 | serde = { workspace = true } 16 | serde_tuple = { workspace = true } 17 | serde_with = { workspace = true } 18 | num-traits = { workspace = true } 19 | 20 | arbitrary = { workspace = true, optional = true } 21 | quickcheck = { workspace = true, optional = true } 22 | rand = { workspace = true, optional = true } 23 | 24 | cid = { workspace = true } 25 | fvm_shared = { workspace = true } 26 | fvm_ipld_encoding = { workspace = true } 27 | ipc-sdk = { workspace = true } 28 | 29 | fendermint_crypto = { path = "../../crypto" } 30 | fendermint_vm_encoding = { path = "../encoding" } 31 | fendermint_vm_actor_interface = { path = "../actor_interface" } 32 | fendermint_testing = { path = "../../testing", optional = true } 33 | 34 | [dev-dependencies] 35 | ethers = { workspace = true } 36 | hex = { workspace = true } 37 | quickcheck = { workspace = true } 38 | quickcheck_macros = { workspace = true } 39 | 40 | 41 | # Enable arb on self for tests. 42 | # Ideally we could do this with `#[cfg(any(test, feature = "arb"))]`, 43 | # however in that case all the extra dependencies would not kick in, 44 | # and we'd have to repeat all those dependencies. 45 | fendermint_vm_message = { path = ".", features = ["arb"] } 46 | fendermint_testing = { path = "../../testing", features = ["golden"] } 47 | 48 | [features] 49 | arb = [ 50 | "arbitrary", 51 | "quickcheck", 52 | "fvm_shared/arb", 53 | "cid/arb", 54 | "rand", 55 | "fendermint_testing/arb", 56 | ] 57 | -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/ipc_bottom_up_exec.cbor: -------------------------------------------------------------------------------- 1 | a163497063a16c426f74746f6d557045786563a2676d657373616765a4697375626e65745f6964821ba1656b9a6ab736e08156040a888c4e06372e45158764a4a11d1ce500f820527a666865696768741a6f9a42ea756e6578745f76616c696461746f725f7365745f69641bbc4d488f39d39de672626f74746f6d5f75705f6d65737361676573d82a583e0001bb1890e30a36e50c4b971c3b52ec39ea0c120cc36c6214347e5b4f3428218d54bbfb42a5cf602e01aff7cfd8ac07f856dccec3275ca245e676ed16ae6b6365727469666963617465a16a7369676e61747572657385a26976616c696461746f724a00d2a4e297d084abea14697369676e61747572654101a26976616c696461746f724a00d8d3eac7c4afefcb61697369676e61747572654102a26976616c696461746f724a00eefad6e88299a7ae46697369676e617475726546020101ffa8f6a26976616c696461746f7255020a01ce813bce190139aef784736d2800e38b3d9c697369676e6174757265420277a26976616c696461746f725501a1c9bf8d8cb7568dc5f50cff4d9a4facd855d0de697369676e617475726543020172 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/ipc_bottom_up_exec.txt: -------------------------------------------------------------------------------- 1 | Ipc(BottomUpExec(CertifiedMessage { message: BottomUpCheckpoint { subnet_id: SubnetID { root: 11629819923713701600, children: [Address { payload: Delegated(DelegatedAddress { namespace: 10, length: 20, buffer: [136, 140, 78, 6, 55, 46, 69, 21, 135, 100, 164, 161, 29, 28, 229, 0, 248, 32, 82, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) }] }, height: 1872380650, next_validator_set_id: 13568581032324865510, bottom_up_messages: Cid(bag5rrehdbi3okdcls4odwuxmhhvayeqmynwgefbupznu6nbieggvjo73iks46yboagx7pt6yvqd7qvw4z3bsoxfcixthn3iwvy) }, certificate: MultiSig { signatures: [ValidatorSignature { validator: Address { payload: ID(1501013850784830034) }, signature: Signature { sig_type: Secp256k1, bytes: [] } }, ValidatorSignature { validator: Address { payload: ID(7032297684660300248) }, signature: Signature { sig_type: BLS, bytes: [] } }, ValidatorSignature { validator: Address { payload: ID(5070099664076127598) }, signature: Signature { sig_type: BLS, bytes: [1, 1, 255, 168, 246] } }, ValidatorSignature { validator: Address { payload: Actor([10, 1, 206, 129, 59, 206, 25, 1, 57, 174, 247, 132, 115, 109, 40, 0, 227, 139, 61, 156]) }, signature: Signature { sig_type: BLS, bytes: [119] } }, ValidatorSignature { validator: Address { payload: Secp256k1([161, 201, 191, 141, 140, 183, 86, 141, 197, 245, 12, 255, 77, 154, 79, 172, 216, 85, 208, 222]) }, signature: Signature { sig_type: BLS, bytes: [1, 114] } }] } })) -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/ipc_bottom_up_resolve.cbor: -------------------------------------------------------------------------------- 1 | a163497063a16f426f74746f6d55705265736f6c7665a2676d657373616765a6676d657373616765a2676d657373616765a4697375626e65745f6964821b7c71defc6a386e78834b00f5e3aee78080a0bf9f0156040a761b9e23622b966af8ef2d2d57dcb7856e5e54504a0088d9d5c1a3abc3d351666865696768741ae3e9fc60756e6578745f76616c696461746f725f7365745f69641b11fc1e77ae60cb5072626f74746f6d5f75705f6d65737361676573d82a5823001220469cd40705ee3724f1e8e598b1f50226efa85a5e902e1e1a3a02bceb1205b0656b6365727469666963617465a16a7369676e61747572657383a26976616c696461746f72550224c06e79929caadd2f1f96ff55f701a146058596697369676e617475726542021fa26976616c696461746f725502005c2e87b0e761927a2ab10f0001dcc782ff5fff697369676e6174757265460163b0f80048a26976616c696461746f725502de2d802ebfcc1d7701c5dcdc00018d140f936068697369676e61747572654601d96f8c01b96772656c61796572583103430fcc5b7c76bb455d9a000baccd63f6f4ff99e401f86200211f46783d001cf77ffe8d2c5fe3ff42091a71af1aa15ad26873657175656e63651bd6fb372fc464a1be696761735f6c696d69741b5bf160e4530c90af6b6761735f6665655f636170587d00ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6c31e54ec6bd7d22c46631c1c2f505d00a79229be88bd39d73e5d150b1b8cfaa3ad78d19ea1281c36b6761735f7072656d69756d583100323429ee0653953f35328d8494d19c67967d867390896a46927e881ba2d75b89d436fbd61724c8b013ad3c251aa06d9f697369676e61747572654901005fe047ff3b3b4d -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/ipc_bottom_up_resolve.txt: -------------------------------------------------------------------------------- 1 | Ipc(BottomUpResolve(SignedRelayedMessage { message: RelayedMessage { message: CertifiedMessage { message: BottomUpCheckpoint { subnet_id: SubnetID { root: 8967193508766576248, children: [Address { payload: ID(11492764036801212917) }, Address { payload: Delegated(DelegatedAddress { namespace: 10, length: 20, buffer: [118, 27, 158, 35, 98, 43, 150, 106, 248, 239, 45, 45, 87, 220, 183, 133, 110, 94, 84, 80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) }, Address { payload: ID(5883686119324085384) }] }, height: 3823762528, next_validator_set_id: 1295944292151380816, bottom_up_messages: Cid(QmT6HwwYSVw1NxZHDFRBVpgXHoP1BZsp8egrUGJLKvBKBz) }, certificate: MultiSig { signatures: [ValidatorSignature { validator: Address { payload: Actor([36, 192, 110, 121, 146, 156, 170, 221, 47, 31, 150, 255, 85, 247, 1, 161, 70, 5, 133, 150]) }, signature: Signature { sig_type: BLS, bytes: [31] } }, ValidatorSignature { validator: Address { payload: Actor([0, 92, 46, 135, 176, 231, 97, 146, 122, 42, 177, 15, 0, 1, 220, 199, 130, 255, 95, 255]) }, signature: Signature { sig_type: Secp256k1, bytes: [99, 176, 248, 0, 72] } }, ValidatorSignature { validator: Address { payload: Actor([222, 45, 128, 46, 191, 204, 29, 119, 1, 197, 220, 220, 0, 1, 141, 20, 15, 147, 96, 104]) }, signature: Signature { sig_type: Secp256k1, bytes: [217, 111, 140, 1, 185] } }] } }, relayer: Address { payload: BLS([67, 15, 204, 91, 124, 118, 187, 69, 93, 154, 0, 11, 172, 205, 99, 246, 244, 255, 153, 228, 1, 248, 98, 0, 33, 31, 70, 120, 61, 0, 28, 247, 127, 254, 141, 44, 95, 227, 255, 66, 9, 26, 113, 175, 26, 161, 90, 210]) }, sequence: 15491036021568872894, gas_limit: 6625183060600852655, gas_fee_cap: TokenAmount(41855804968213567224547853478906320725054875457247406540771499545716837934567817284890561672488119458109166910841919797858872862722356017328064756151166307827869405370407152286801072676024887272960758522802096518222997167129660169469158860048690764605453004790204472697535710106459.730900083939639747), gas_premium: TokenAmount(7727066607978473919987999013363901971034861126626277477615296751585842935993863628627762990636417.915060831698054559) }, signature: Signature { sig_type: Secp256k1, bytes: [0, 95, 224, 71, 255, 59, 59, 77] } })) -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/ipc_top_down.cbor: -------------------------------------------------------------------------------- 1 | a163497063a16b546f70446f776e45786563a266686569676874006a626c6f636b5f6861736880 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/ipc_top_down.txt: -------------------------------------------------------------------------------- 1 | Ipc(TopDownExec(ParentFinality { height: 0, block_hash: [] })) -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/signed.cbor: -------------------------------------------------------------------------------- 1 | a1665369676e6564828a1bffffffffffffffff5502ff6c5decc43f0657d653dfc888db04f96a0c39a75501c98d2f1c75ceb5912aff7f00b5a60032813a75ac1b114a444e81516e02584100bda3b9d4d5d7731b9cf74b18969735bf1133c8b7e5b792483199253ce8df09fcd989e63e36a4fa8865b53a4af1811f5a0dafc5eaa46d8e9a00b372a92989bf901be773d122bb50522a5841007082baf7d61bb3beb54f278f89485c04c21ab5ac8a02fd40294ed359a75dee43189e6e1f7b1076fee80135431910f79e56547cb850f34a7defed565e2a0620fe5100e2165f5473fcbb6c25b7c9f62f9e08a31bdca1f9db3b94acf8428701420104 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/chain/signed.txt: -------------------------------------------------------------------------------- 1 | Signed(SignedMessage { message: Message { version: 18446744073709551615, from: Address { payload: Secp256k1([201, 141, 47, 28, 117, 206, 181, 145, 42, 255, 127, 0, 181, 166, 0, 50, 129, 58, 117, 172]) }, to: Address { payload: Actor([255, 108, 93, 236, 196, 63, 6, 87, 214, 83, 223, 200, 136, 219, 4, 249, 106, 12, 57, 167]) }, sequence: 1245883350889098754, value: TokenAmount(9932229375525565691377146959193765155762615947792137979058505954726871205960593142920721455420987050970777344255324126610211082198096402.32226385693414184), method_num: 15898262879587314936, params: RawBytes { 8701 }, gas_limit: 16677903792184775210, gas_fee_cap: TokenAmount(5892661686900812402916430579778595043239110941386249057983897934879165380375437615565885206289661643046275694223485224682524740007133170.002394836476764414), gas_premium: TokenAmount(300521691098253062104.478788388166437027) }, signature: Signature { sig_type: Secp256k1, bytes: [4] } }) -------------------------------------------------------------------------------- /fendermint/vm/message/golden/fvm/message.cbor: -------------------------------------------------------------------------------- 1 | 8a1b33884b011fd74a91583103ff5d465bc44b017bef29964bc0bc6cc84b7207033c03285e1872ad006b3da38da39223ff72b3f74902168ed748a60782583604adb5ece58cd986a41dcbf731d6b59f3496aac962e042c98bdd6b69ab279a38667d64c19bd8b9db609764980c133d0186eb1a01bc6e1b8afe2f3af5d4b9684900f909674792abf6791b9a59f60bc759fdb65821004636280b3a37fec660739b79da958f5ddfad510da03684c22f93a50c9038b23458190022e46edbc2d0ced0aeb9589fc8097adeae29fe81ddc42c3f1ba23fe4736808338944ac8ff874 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/fvm/message.cid: -------------------------------------------------------------------------------- 1 | 0171a0e40220533f119b5d3ee07e1f4a7b3f21a0b5c28ee2a87cc554a2857adbcf37f2ebfbd2 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/fvm/message.txt: -------------------------------------------------------------------------------- 1 | Message { version: 3713300360968227473, from: Address { payload: Delegated(DelegatedAddress { namespace: 2109965875153672877, length: 44, buffer: [203, 247, 49, 214, 181, 159, 52, 150, 170, 201, 98, 224, 66, 201, 139, 221, 107, 105, 171, 39, 154, 56, 102, 125, 100, 193, 155, 216, 185, 219, 96, 151, 100, 152, 12, 19, 61, 1, 134, 235, 26, 1, 188, 110, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] }) }, to: Address { payload: BLS([255, 93, 70, 91, 196, 75, 1, 123, 239, 41, 150, 75, 192, 188, 108, 200, 75, 114, 7, 3, 60, 3, 40, 94, 24, 114, 173, 0, 107, 61, 163, 141, 163, 146, 35, 255, 114, 179, 247, 73, 2, 22, 142, 215, 72, 166, 7, 130]) }, sequence: 10015494551597529448, value: TokenAmount(17.944987747335534201), method_num: 11691314341994836873, params: RawBytes { ac8ff874 }, gas_limit: 11122191285217787318, gas_fee_cap: TokenAmount(31757585514871112913072579344521071329437603387853566530475.694921303272763956), gas_premium: TokenAmount(855557112755549049555464473455842432394.361919810497162303) } -------------------------------------------------------------------------------- /fendermint/vm/message/golden/query/request/actor_state.cbor: -------------------------------------------------------------------------------- 1 | a16a4163746f725374617465550100c44d3544d7fef86bfa4ec69096050100b70f11 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/query/request/actor_state.txt: -------------------------------------------------------------------------------- 1 | ActorState(Address { payload: Secp256k1([0, 196, 77, 53, 68, 215, 254, 248, 107, 250, 78, 198, 144, 150, 5, 1, 0, 183, 15, 17]) }) -------------------------------------------------------------------------------- /fendermint/vm/message/golden/query/request/ipld.cbor: -------------------------------------------------------------------------------- 1 | a16449706c64d82a582300122023d404cce8344faaf4c618fc73867998946c9b074ca10d5d37b654a186d67d4b -------------------------------------------------------------------------------- /fendermint/vm/message/golden/query/request/ipld.txt: -------------------------------------------------------------------------------- 1 | Ipld(Cid(QmQkWVLf1rDgKJKd3G3G4v5V92q8JhoSTDQR2QDDgXUaKU)) -------------------------------------------------------------------------------- /fendermint/vm/message/golden/query/response/actor_state.cbor: -------------------------------------------------------------------------------- 1 | a564636f6465d82a582300122086f2a6fa7d8fe1d53e08a2c67ef12ef6db77fedd6fb226f4274e42e967babb6d657374617465d82a5823001220ca8da62b1bc99224a0aeb7c8084d599e73d9a5a178ba22a00028d0a8b58f45e16873657175656e63651bfb6d7403eeb008af6762616c616e6365584100f51bc89d095b7349acf640d64d6ab3626af87a6fdfb058eac20a7d653ab35cb812f7e2f81d977fe9eb52fdadd27f138b269215958b8093b34caa7d54559423ef7164656c6567617465645f616464726573735831037aac7201ba99bde23adf83c6c4755500ffc48707223e8c714a2447ac7ea8d9112b7002422f91ac3b56775d995ef91377 -------------------------------------------------------------------------------- /fendermint/vm/message/golden/query/response/actor_state.txt: -------------------------------------------------------------------------------- 1 | ActorState { code: Cid(QmXRRwhd1nN8zzfwsDhwQU5VeYf96rzb5NrJYr2bgz59HW), state: Cid(QmbyLLet5eDJxHPgXbRr146mgwtGWFkii3HvrqXDJpRkGY), sequence: 18117264436219611311, balance: TokenAmount(12837375353358104575764595313077765395794546135086142370656331495972793855553867421435159587158883947028703783392634027814199088685207472.895591517170967535), delegated_address: Some(Address { payload: BLS([122, 172, 114, 1, 186, 153, 189, 226, 58, 223, 131, 198, 196, 117, 85, 0, 255, 196, 135, 7, 34, 62, 140, 113, 74, 36, 71, 172, 126, 168, 217, 17, 43, 112, 2, 66, 47, 145, 172, 59, 86, 119, 93, 153, 94, 249, 19, 119]) }) } -------------------------------------------------------------------------------- /fendermint/vm/message/src/chain.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use crate::{ipc::IpcMessage, signed::SignedMessage}; 6 | 7 | /// The different kinds of messages that can appear in blocks, ie. the transactions 8 | /// we can receive from Tendermint through the ABCI. 9 | /// 10 | /// Unlike Filecoin, we don't have `Unsigned` messages here. In Filecoin, the messages 11 | /// signed by BLS signatures are aggregated to the block level, and their original 12 | /// signatures are stripped from the messages, to save space. Tendermint Core will 13 | /// not do this for us (perhaps with ABCI++ Vote Extensions we could do it), though. 14 | #[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)] 15 | pub enum ChainMessage { 16 | /// A message that can be passed on to the FVM as-is. 17 | Signed(SignedMessage), 18 | 19 | /// Messages involved in InterPlanetaryConsensus, which are basically top-down and bottom-up 20 | /// checkpoints that piggy-back on the Tendermint voting mechanism for finality and CID resolution. 21 | /// 22 | /// Possible mechanisms include: 23 | /// * Proposing "for resolution" - A message with a CID proposed for async resolution. These would be bottom-up 24 | /// messages that need to be relayed, so they also include some relayer identity and signature, for rewards. 25 | /// * Proposing "for execution" - A message with a CID with proven availability and finality, ready to be executed. 26 | /// Such messages are proposed by the validators themselves, and their execution might trigger rewards for others. 27 | /// 28 | /// Because of the involvement of data availability voting and CID resolution, these messages require support 29 | /// from the application, which is why they are handled in a special way. 30 | Ipc(IpcMessage), 31 | } 32 | 33 | #[cfg(feature = "arb")] 34 | mod arb { 35 | 36 | use super::ChainMessage; 37 | use crate::{ipc::IpcMessage, signed::SignedMessage}; 38 | 39 | impl quickcheck::Arbitrary for ChainMessage { 40 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 41 | match u8::arbitrary(g) % 2 { 42 | 0 => ChainMessage::Signed(SignedMessage::arbitrary(g)), 43 | _ => ChainMessage::Ipc(IpcMessage::arbitrary(g)), 44 | } 45 | } 46 | } 47 | } 48 | 49 | #[cfg(test)] 50 | mod tests { 51 | use crate::chain::ChainMessage; 52 | use quickcheck_macros::quickcheck; 53 | 54 | #[quickcheck] 55 | fn chain_message_cbor(value0: ChainMessage) { 56 | let repr = fvm_ipld_encoding::to_vec(&value0).expect("failed to encode"); 57 | let value1: ChainMessage = 58 | fvm_ipld_encoding::from_slice(repr.as_ref()).expect("failed to decode"); 59 | 60 | assert_eq!(value1, value0) 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /fendermint/vm/message/src/conv/mod.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | pub mod from_eth; 5 | pub mod from_fvm; 6 | 7 | #[cfg(test)] 8 | pub mod tests { 9 | use fendermint_crypto::{PublicKey, SecretKey}; 10 | use fendermint_testing::arb::{ArbMessage, ArbTokenAmount}; 11 | use fendermint_vm_actor_interface::{ 12 | eam::{self, EthAddress}, 13 | evm, 14 | }; 15 | use fvm_ipld_encoding::{BytesSer, RawBytes}; 16 | use fvm_shared::{address::Address, bigint::Integer, econ::TokenAmount, message::Message}; 17 | use rand::{rngs::StdRng, SeedableRng}; 18 | 19 | use super::from_fvm::MAX_U256; 20 | 21 | #[derive(Clone, Debug)] 22 | struct EthDelegatedAddress(Address); 23 | 24 | impl quickcheck::Arbitrary for EthDelegatedAddress { 25 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 26 | let mut subaddr: [u8; 20] = std::array::from_fn(|_| u8::arbitrary(g)); 27 | while EthAddress(subaddr).is_masked_id() { 28 | subaddr[0] = u8::arbitrary(g); 29 | } 30 | Self(Address::new_delegated(eam::EAM_ACTOR_ID, &subaddr).unwrap()) 31 | } 32 | } 33 | 34 | #[derive(Clone, Debug)] 35 | struct EthTokenAmount(TokenAmount); 36 | 37 | impl quickcheck::Arbitrary for EthTokenAmount { 38 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 39 | let t = ArbTokenAmount::arbitrary(g).0; 40 | let (_, t) = t.atto().div_mod_floor(&MAX_U256); 41 | Self(TokenAmount::from_atto(t)) 42 | } 43 | } 44 | 45 | /// Message that only contains data which can survive a roundtrip. 46 | #[derive(Clone, Debug)] 47 | pub struct EthMessage(pub Message); 48 | 49 | impl quickcheck::Arbitrary for EthMessage { 50 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 51 | let mut m = ArbMessage::arbitrary(g).0; 52 | m.version = 0; 53 | m.method_num = evm::Method::InvokeContract as u64; 54 | m.from = EthDelegatedAddress::arbitrary(g).0; 55 | m.to = EthDelegatedAddress::arbitrary(g).0; 56 | m.value = EthTokenAmount::arbitrary(g).0; 57 | m.gas_fee_cap = EthTokenAmount::arbitrary(g).0; 58 | m.gas_premium = EthTokenAmount::arbitrary(g).0; 59 | // The random bytes will fail to deserialize. 60 | // With the EVM we expect them to be IPLD serialized bytes. 61 | m.params = 62 | RawBytes::serialize(BytesSer(m.params.bytes())).expect("failedto serialize params"); 63 | Self(m) 64 | } 65 | } 66 | 67 | #[derive(Debug, Clone)] 68 | pub struct KeyPair { 69 | pub sk: SecretKey, 70 | pub pk: PublicKey, 71 | } 72 | 73 | impl quickcheck::Arbitrary for KeyPair { 74 | fn arbitrary(g: &mut quickcheck::Gen) -> Self { 75 | let seed = u64::arbitrary(g); 76 | let mut rng = StdRng::seed_from_u64(seed); 77 | let sk = SecretKey::random(&mut rng); 78 | let pk = sk.public_key(); 79 | Self { sk, pk } 80 | } 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /fendermint/vm/message/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | use cid::{multihash, multihash::MultihashDigest, Cid}; 4 | use fvm_ipld_encoding::{to_vec, Error as IpldError, DAG_CBOR}; 5 | use serde::Serialize; 6 | 7 | pub mod chain; 8 | pub mod conv; 9 | pub mod ipc; 10 | pub mod query; 11 | pub mod signed; 12 | 13 | /// Calculate the CID using Blake2b256 digest and DAG_CBOR. 14 | /// 15 | /// This used to be part of the `Cbor` trait, which is deprecated. 16 | pub fn cid(value: &T) -> Result { 17 | let bz = to_vec(value)?; 18 | let digest = multihash::Code::Blake2b256.digest(&bz); 19 | let cid = Cid::new_v1(DAG_CBOR, digest); 20 | Ok(cid) 21 | } 22 | -------------------------------------------------------------------------------- /fendermint/vm/message/tests/golden.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | /// Examples of `ChainMessage`, which is what the client has to send, 5 | /// or at least what appears in blocks. 6 | mod chain { 7 | use fendermint_testing::golden_cbor; 8 | use fendermint_vm_message::{chain::ChainMessage, ipc::IpcMessage}; 9 | use quickcheck::Arbitrary; 10 | 11 | golden_cbor! { "chain", signed, |g| { 12 | loop { 13 | if let msg @ ChainMessage::Signed(_) = ChainMessage::arbitrary(g) { 14 | return msg 15 | } 16 | } 17 | } 18 | } 19 | 20 | golden_cbor! { "chain", ipc_bottom_up_resolve, |g| { 21 | loop { 22 | if let msg @ ChainMessage::Ipc(IpcMessage::BottomUpResolve(_)) = ChainMessage::arbitrary(g) { 23 | return msg 24 | } 25 | } 26 | } 27 | } 28 | 29 | golden_cbor! { "chain", ipc_bottom_up_exec, |g| { 30 | loop { 31 | if let msg @ ChainMessage::Ipc(IpcMessage::BottomUpExec(_)) = ChainMessage::arbitrary(g) { 32 | return msg 33 | } 34 | } 35 | } 36 | } 37 | 38 | golden_cbor! { "chain", ipc_top_down, |g| { 39 | loop { 40 | if let msg @ ChainMessage::Ipc(IpcMessage::TopDownExec(_)) = ChainMessage::arbitrary(g) { 41 | return msg 42 | } 43 | } 44 | } 45 | } 46 | } 47 | 48 | /// Examples of FVM messages, which is what the client needs to sign. 49 | mod fvm { 50 | use fendermint_testing::golden_cid; 51 | use fendermint_vm_message::signed::SignedMessage; 52 | use quickcheck::Arbitrary; 53 | 54 | golden_cid! { "fvm", message, |g| SignedMessage::arbitrary(g).message, |m| SignedMessage::cid(m).unwrap() } 55 | } 56 | 57 | /// Examples of query requests the client needs to send, and client responses it will receive. 58 | mod query { 59 | mod request { 60 | use fendermint_testing::golden_cbor; 61 | use fendermint_vm_message::query::FvmQuery; 62 | use quickcheck::Arbitrary; 63 | 64 | golden_cbor! { "query/request", ipld, |g| { 65 | loop { 66 | if let msg @ FvmQuery::Ipld(_) = FvmQuery::arbitrary(g) { 67 | return msg 68 | } 69 | } 70 | }} 71 | 72 | golden_cbor! { "query/request", actor_state, |g| { 73 | loop { 74 | if let msg @ FvmQuery::ActorState { .. } = FvmQuery::arbitrary(g) { 75 | return msg 76 | } 77 | } 78 | }} 79 | } 80 | 81 | mod response { 82 | use fendermint_testing::golden_cbor; 83 | use fendermint_vm_message::query::ActorState; 84 | use quickcheck::Arbitrary; 85 | 86 | golden_cbor! { "query/response", actor_state, |g| { 87 | ActorState::arbitrary(g) 88 | }} 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /fendermint/vm/resolver/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_resolver" 3 | description = "Resolve IPLD content in messages" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | async-stm = { workspace = true } 13 | im = { workspace = true } 14 | tracing = { workspace = true } 15 | tokio = { workspace = true } 16 | 17 | cid = { workspace = true } 18 | ipc-sdk = { workspace = true } 19 | ipc_ipld_resolver = { workspace = true } 20 | 21 | [dev-dependencies] 22 | tokio = { workspace = true } 23 | -------------------------------------------------------------------------------- /fendermint/vm/resolver/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | pub mod ipld; 5 | pub mod pool; 6 | -------------------------------------------------------------------------------- /fendermint/vm/snapshot/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_snapshot" 3 | description = "Produce and consume ledger snapshots" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] 12 | anyhow = { workspace = true } 13 | async-stm = { workspace = true } 14 | cid = { workspace = true } 15 | dircpy = { workspace = true } 16 | futures = { workspace = true } 17 | im = { workspace = true } 18 | multihash = { workspace = true } 19 | sha2 = { workspace = true } 20 | serde = { workspace = true } 21 | serde_json = { workspace = true } 22 | tempfile = { workspace = true } 23 | tracing = { workspace = true } 24 | thiserror = { workspace = true } 25 | tokio = { workspace = true } 26 | tokio-util = { workspace = true } 27 | 28 | arbitrary = { workspace = true, optional = true } 29 | quickcheck = { workspace = true, optional = true } 30 | 31 | tendermint = { workspace = true } 32 | tendermint-rpc = { workspace = true } 33 | 34 | fvm_ipld_blockstore = { workspace = true } 35 | fvm_ipld_car = { workspace = true } 36 | fvm_ipld_encoding = { workspace = true } 37 | fvm_shared = { workspace = true, optional = true, features = ["arb"] } 38 | 39 | fendermint_vm_interpreter = { path = "../interpreter" } 40 | fendermint_vm_core = { path = "../core", optional = true } 41 | fendermint_testing = { path = "../../testing", features = ["arb"], optional = true } 42 | 43 | [dev-dependencies] 44 | fvm = { workspace = true } 45 | fendermint_testing = { path = "../../testing", features = ["golden"] } 46 | fendermint_vm_interpreter = { path = "../interpreter", features = ["bundle"] } 47 | fendermint_vm_genesis = { path = "../genesis", features = ["arb"] } 48 | fendermint_vm_snapshot = { path = ".", features = ["arb"] } 49 | 50 | [features] 51 | default = [] 52 | arb = [ 53 | "arbitrary", 54 | "quickcheck", 55 | "fvm_shared/arb", 56 | "fendermint_vm_core", 57 | "fendermint_testing/arb", 58 | ] 59 | -------------------------------------------------------------------------------- /fendermint/vm/snapshot/golden/manifest/cbor/manifest.cbor: -------------------------------------------------------------------------------- 1 | a66c626c6f636b5f6865696768741a99e5f1a76473697a651b9a0ed59575887285666368756e6b731af71159e768636865636b73756d7840453243304636444136464643463335413334334546304545394445343436353436374143443530344338463237313243364142323034343543383341313932416c73746174655f706172616d73a76a73746174655f726f6f74d82a58230012202a0ab732b4e9d85ef7dc25303b64ab527c25a4d77815ebb579f396ec6caccad36974696d657374616d701bdf399b7bb39519486f6e6574776f726b5f76657273696f6e1affffffff68626173655f66656551005eb4d601fc663685053ee078217bd77f6b636972635f737570706c795100ffffffffffffffffb5b5b138edf6bed168636861696e5f69641b000a9a18ec6436676b706f7765725f7363616c65206776657273696f6e1a33c9bad2 -------------------------------------------------------------------------------- /fendermint/vm/snapshot/golden/manifest/cbor/manifest.txt: -------------------------------------------------------------------------------- 1 | SnapshotManifest { block_height: 2581983655, size: 11101044969413571205, chunks: 4145109479, checksum: Hash::Sha256(E2C0F6DA6FFCF35A343EF0EE9DE4465467ACD504C8F2712C6AB20445C83A192A), state_params: FvmStateParams { state_root: Cid(QmRAmJvPSFPjeHkVJyPktbmM2SRHURjbM7xs7JRD1zCjWJ), timestamp: Timestamp(16085058499726612808), network_version: NetworkVersion(4294967295), base_fee: TokenAmount(125886385631315495367.993087794916087679), circ_supply: TokenAmount(340282366920938463458.021429707776900817), chain_id: 2984181602989671, power_scale: -1 }, version: 868858578 } -------------------------------------------------------------------------------- /fendermint/vm/snapshot/golden/manifest/json/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "block_height": 18446744073709551615, 3 | "size": 11344242012067624990, 4 | "chunks": 22076, 5 | "checksum": "A3B844BB3068947681E591126B1AAC925B7BF1BB56BA6DB77D87745365B0949E", 6 | "state_params": { 7 | "state_root": "QmYbxwhLej3Te1etMuFqWb3Gwy7CpVaXAe5deWmqrphMhg", 8 | "timestamp": 1, 9 | "network_version": 4294967295, 10 | "base_fee": "299246354255658060378714945246048246606", 11 | "circ_supply": "93362016975129332347987662062653906832", 12 | "chain_id": 503525136242505, 13 | "power_scale": 0 14 | }, 15 | "version": 0 16 | } 17 | -------------------------------------------------------------------------------- /fendermint/vm/snapshot/golden/manifest/json/manifest.txt: -------------------------------------------------------------------------------- 1 | SnapshotManifest { block_height: 18446744073709551615, size: 11344242012067624990, chunks: 22076, checksum: Hash::Sha256(A3B844BB3068947681E591126B1AAC925B7BF1BB56BA6DB77D87745365B0949E), state_params: FvmStateParams { state_root: Cid(QmYbxwhLej3Te1etMuFqWb3Gwy7CpVaXAe5deWmqrphMhg), timestamp: Timestamp(1), network_version: NetworkVersion(4294967295), base_fee: TokenAmount(299246354255658060378.714945246048246606), circ_supply: TokenAmount(93362016975129332347.987662062653906832), chain_id: 503525136242505, power_scale: 0 }, version: 0 } 2 | -------------------------------------------------------------------------------- /fendermint/vm/snapshot/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use fendermint_vm_interpreter::fvm::state::snapshot::SnapshotVersion; 5 | 6 | /// Possible errors with snapshots. 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum SnapshotError { 9 | #[error("incompatible snapshot version: {0}")] 10 | IncompatibleVersion(SnapshotVersion), 11 | #[error("IO error: {0}")] 12 | IoError(#[from] std::io::Error), 13 | #[error("there is no ongoing snapshot download")] 14 | NoDownload, 15 | #[error("unexpected chunk index; expected {0}, got {1}")] 16 | UnexpectedChunk(u32, u32), 17 | #[error("wrong checksum; expected {0}, got {1}")] 18 | WrongChecksum(tendermint::Hash, tendermint::Hash), 19 | } 20 | -------------------------------------------------------------------------------- /fendermint/vm/snapshot/src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | mod car; 4 | mod client; 5 | mod error; 6 | mod manager; 7 | mod manifest; 8 | mod state; 9 | 10 | /// The file name to export the CAR to. 11 | const SNAPSHOT_FILE_NAME: &str = "snapshot.car"; 12 | 13 | /// The file name in snapshot directories that contains the manifest. 14 | const MANIFEST_FILE_NAME: &str = "manifest.json"; 15 | 16 | /// Name of the subdirectory where `{idx}.part` files are stored within a snapshot. 17 | const PARTS_DIR_NAME: &str = "parts"; 18 | 19 | pub use client::SnapshotClient; 20 | pub use error::SnapshotError; 21 | pub use manager::{SnapshotManager, SnapshotParams}; 22 | pub use manifest::SnapshotManifest; 23 | pub use state::SnapshotItem; 24 | -------------------------------------------------------------------------------- /fendermint/vm/snapshot/tests/golden.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | /// JSON based test so we can parse data from the disk where it's nice to be human readable. 5 | mod json { 6 | use fendermint_testing::golden_json; 7 | use fendermint_vm_snapshot::SnapshotManifest; 8 | use quickcheck::Arbitrary; 9 | golden_json! { "manifest/json", manifest, SnapshotManifest::arbitrary } 10 | } 11 | 12 | /// CBOR based test to make sure we can parse data in network format and we also cover the state params. 13 | mod cbor { 14 | use fendermint_testing::golden_cbor; 15 | use fendermint_vm_snapshot::SnapshotManifest; 16 | use quickcheck::Arbitrary; 17 | golden_cbor! { "manifest/cbor", manifest, SnapshotManifest::arbitrary } 18 | } 19 | -------------------------------------------------------------------------------- /fendermint/vm/topdown/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_vm_topdown" 3 | description = "The top down checkpoint mechanism for ipc protocol integration" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | [dependencies] 10 | tokio = { workspace = true } 11 | anyhow = { workspace = true } 12 | ipc-provider = { workspace = true } 13 | ipc-sdk = { workspace = true } 14 | async-trait = { workspace = true } 15 | tracing = { workspace = true } 16 | serde = { workspace = true } 17 | serde_json = { workspace = true } 18 | cid = { workspace = true } 19 | fvm_ipld_encoding = { workspace = true } 20 | num-traits = { workspace = true } 21 | async-stm = { workspace = true } 22 | thiserror = { workspace = true } 23 | fvm_shared = { workspace = true } 24 | ipc_actors_abis = { workspace = true } 25 | ethers = { workspace = true} 26 | tendermint-rpc = { workspace = true } 27 | 28 | [dev-dependencies] 29 | tracing-subscriber = { workspace = true } 30 | clap = { workspace = true } 31 | -------------------------------------------------------------------------------- /fendermint/vm/topdown/src/convert.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! Handles the type conversion to ethers contract types 4 | 5 | use crate::IPCParentFinality; 6 | use anyhow::anyhow; 7 | use ethers::types::U256; 8 | use ipc_actors_abis::{gateway_getter_facet, gateway_router_facet}; 9 | 10 | impl TryFrom for gateway_router_facet::ParentFinality { 11 | type Error = anyhow::Error; 12 | 13 | fn try_from(value: IPCParentFinality) -> Result { 14 | if value.block_hash.len() != 32 { 15 | return Err(anyhow!("invalid block hash length, expecting 32")); 16 | } 17 | 18 | let mut block_hash = [0u8; 32]; 19 | block_hash.copy_from_slice(&value.block_hash[0..32]); 20 | 21 | Ok(Self { 22 | height: U256::from(value.height), 23 | block_hash, 24 | }) 25 | } 26 | } 27 | 28 | impl From for IPCParentFinality { 29 | fn from(value: gateway_getter_facet::ParentFinality) -> Self { 30 | IPCParentFinality { 31 | height: value.height.as_u64(), 32 | block_hash: value.block_hash.to_vec(), 33 | } 34 | } 35 | } 36 | 37 | impl From for IPCParentFinality { 38 | fn from(value: gateway_router_facet::ParentFinality) -> Self { 39 | IPCParentFinality { 40 | height: value.height.as_u64(), 41 | block_hash: value.block_hash.to_vec(), 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /fendermint/vm/topdown/src/error.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use crate::{BlockHeight, SequentialAppendError}; 5 | use thiserror::Error; 6 | 7 | /// The errors for top down checkpointing 8 | #[derive(Error, Debug, Eq, PartialEq, Clone)] 9 | pub enum Error { 10 | #[error("Incoming items are not order sequentially")] 11 | NotSequential, 12 | #[error("The parent view update with block height is not sequential: {0:?}")] 13 | NonSequentialParentViewInsert(SequentialAppendError), 14 | #[error("Parent chain reorg detected")] 15 | ParentChainReorgDetected, 16 | #[error("Cannot query parent at height {1}: {0}")] 17 | CannotQueryParent(String, BlockHeight), 18 | } 19 | -------------------------------------------------------------------------------- /fendermint/vm/topdown/src/sync/pointers.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | 4 | use crate::{BlockHash, BlockHeight}; 5 | use ethers::utils::hex; 6 | use std::fmt::{Display, Formatter}; 7 | 8 | #[derive(Clone, Debug)] 9 | pub(crate) struct SyncPointers { 10 | tail: Option<(BlockHeight, BlockHash)>, 11 | head: BlockHeight, 12 | } 13 | 14 | impl SyncPointers { 15 | pub fn new(head: BlockHeight) -> Self { 16 | Self { tail: None, head } 17 | } 18 | 19 | pub fn head(&self) -> BlockHeight { 20 | self.head 21 | } 22 | 23 | pub fn tail(&self) -> Option<(BlockHeight, BlockHash)> { 24 | self.tail.clone() 25 | } 26 | 27 | pub fn advance_head(&mut self) { 28 | self.head += 1; 29 | } 30 | 31 | pub fn set_tail(&mut self, height: BlockHeight, hash: BlockHash) { 32 | self.tail = Some((height, hash)); 33 | } 34 | } 35 | 36 | impl Display for SyncPointers { 37 | fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { 38 | if let Some((height, hash)) = &self.tail { 39 | write!( 40 | f, 41 | "{{tail: {{height: {}, hash: {}}}, head: {}}}", 42 | height, 43 | hex::encode(hash), 44 | self.head 45 | ) 46 | } else { 47 | write!(f, "{{tail: None, head: {}}}", self.head) 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /fendermint/vm/topdown/src/sync/tendermint.rs: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | //! The tendermint aware syncer 4 | 5 | use crate::proxy::ParentQueryProxy; 6 | use crate::sync::syncer::LotusParentSyncer; 7 | use crate::sync::ParentFinalityStateQuery; 8 | use anyhow::Context; 9 | 10 | /// Tendermint aware syncer 11 | pub(crate) struct TendermintAwareSyncer { 12 | inner: LotusParentSyncer, 13 | tendermint_client: C, 14 | } 15 | 16 | impl TendermintAwareSyncer 17 | where 18 | T: ParentFinalityStateQuery + Send + Sync + 'static, 19 | C: tendermint_rpc::Client + Send + Sync + 'static, 20 | P: ParentQueryProxy + Send + Sync + 'static, 21 | { 22 | pub fn new(inner: LotusParentSyncer, tendermint_client: C) -> Self { 23 | Self { 24 | inner, 25 | tendermint_client, 26 | } 27 | } 28 | 29 | pub async fn sync(&mut self) -> anyhow::Result<()> { 30 | if self.is_syncing_peer().await? { 31 | tracing::debug!("syncing with peer, skip parent finality syncing this round"); 32 | return Ok(()); 33 | } 34 | self.inner.sync().await 35 | } 36 | 37 | async fn is_syncing_peer(&self) -> anyhow::Result { 38 | let status: tendermint_rpc::endpoint::status::Response = self 39 | .tendermint_client 40 | .status() 41 | .await 42 | .context("failed to get Tendermint status")?; 43 | Ok(status.sync_info.catching_up) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /infra/.env: -------------------------------------------------------------------------------- 1 | COMETBFT_VERSION=v0.37.x -------------------------------------------------------------------------------- /infra/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "fendermint_infra" 3 | description = "Workflows for the deployment of Fendermint infrastructure" 4 | version = "0.1.0" 5 | authors.workspace = true 6 | edition.workspace = true 7 | license.workspace = true 8 | 9 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 10 | 11 | [dependencies] -------------------------------------------------------------------------------- /infra/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | fendermint-node: 3 | container_name: fendermint-node${NODE_ID} 4 | user: ${UID}:${GID} 5 | image: "fendermint:latest" 6 | environment: 7 | - FM_DATA_DIR=/data/fendermint/data 8 | - FM_CHAIN_NAME=$NETWORK_NAME 9 | - TENDERMINT_RPC_URL=http://cometbft-node${NODE_ID}:26657 10 | - LOG_LEVEL=info 11 | - FM_NETWORK=$FM_NETWORK 12 | volumes: 13 | - $BASE_DIR/node${NODE_ID}:/data 14 | networks: 15 | testnet: 16 | ipv4_address: ${FMT_NODE_ADDR} 17 | 18 | cometbft-node: 19 | container_name: cometbft-node${NODE_ID} 20 | user: ${UID}:${GID} 21 | image: "cometbft/cometbft:${COMETBFT_VERSION}" 22 | ports: 23 | - "${PORT1}-${PORT2}:26656-26657" 24 | environment: 25 | - ID=${NODE_ID} 26 | - LOG=${LOG:-cometbft-node${NODE_ID}.log} 27 | - CMT_PROXY_APP=tcp://fendermint-node${NODE_ID}:26658 28 | - CMT_P2P_PEX=true 29 | - CMT_P2P_PERSISTENT_PEERS="${CMT_P2P_PERSISTENT_PEERS}" 30 | volumes: 31 | - $BASE_DIR/node${NODE_ID}/cometbft:/cometbft 32 | healthcheck: 33 | test: curl --fail http://localhost:26657 || exit 1 34 | interval: 8s 35 | timeout: 10s 36 | retries: 20 37 | networks: 38 | testnet: 39 | ipv4_address: ${CMT_NODE_ADDR} 40 | 41 | ethapi-node: 42 | container_name: ethapi-node${NODE_ID} 43 | user: ${UID}:${GID} 44 | image: "fendermint:latest" 45 | command: "eth run" 46 | environment: 47 | - TENDERMINT_RPC_URL=http://cometbft-node${NODE_ID}:26657 48 | - TENDERMINT_WS_URL=ws://cometbft-node${NODE_ID}:26657/websocket 49 | - LOG_LEVEL=debug 50 | - RUST_BACKTRACE=1 51 | ports: 52 | - ${PORT3}:8545 53 | volumes: 54 | - $BASE_DIR/node${NODE_ID}:/data 55 | depends_on: 56 | cometbft-node: 57 | condition: service_healthy 58 | networks: 59 | testnet: 60 | ipv4_address: ${ETHAPI_NODE_ADDR} 61 | 62 | networks: 63 | testnet: 64 | name: ${NETWORK_NAME} 65 | external: true 66 | -------------------------------------------------------------------------------- /infra/run.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | if [ $# -ne 1 ]; then 4 | echo "usage: $0 (start|stop)" 5 | exit 1 6 | fi 7 | 8 | if [ -z ${NETWORK_NAME} ]; then 9 | echo "NETWORK_NAME variable is not set"; 10 | exit 1 11 | fi 12 | 13 | export NETWORK_NAME 14 | 15 | PORT1=26656 16 | PORT2=26657 17 | PORT3=8545 18 | 19 | ACTION= 20 | 21 | case $1 in 22 | start) ACTION="up -d" ;; 23 | stop) ACTION="down" ;; 24 | *) 25 | echo "usage: $0 (start|stop)" 26 | exit 1 27 | ;; 28 | esac 29 | 30 | if [ "$1" == "start" ]; then 31 | # we need to remove the network with the same name 32 | # because that network might me created without subnet with necessary IP address space 33 | docker network rm -f ${NETWORK_NAME} 34 | docker network create --subnet 192.167.10.0/16 ${NETWORK_NAME} 35 | fi 36 | 37 | for i in $(seq 0 3); do 38 | export NODE_ID=${i} 39 | export PORT1 40 | export PORT2 41 | export PORT3 42 | export CMT_NODE_ADDR=192.167.10.$((${i}+2)) 43 | export FMT_NODE_ADDR=192.167.10.$((${i}+6)) 44 | export ETHAPI_NODE_ADDR=192.167.10.$((${i}+10)) 45 | docker compose -f ./infra/docker-compose.yml -p testnet_node_${i} $ACTION & 46 | PORT1=$((PORT1+3)) 47 | PORT2=$((PORT2+3)) 48 | PORT3=$((PORT3+1)) 49 | done 50 | 51 | wait $(jobs -p) 52 | 53 | if [ "$1" == "stop" ]; then 54 | docker network rm -f ${NETWORK_NAME} 55 | fi 56 | -------------------------------------------------------------------------------- /infra/scripts/docker.toml: -------------------------------------------------------------------------------- 1 | [tasks.docker-network-create] 2 | command = "docker" 3 | args = ["network", "create", "${NETWORK_NAME}"] 4 | ignore_errors = true 5 | 6 | [tasks.docker-network-rm] 7 | command = "docker" 8 | args = ["network", "rm", "${NETWORK_NAME}"] 9 | ignore_errors = true 10 | 11 | [tasks.docker-stop] 12 | command = "docker" 13 | args = ["stop", "${CONTAINER_NAME}"] 14 | ignore_errors = true 15 | 16 | [tasks.docker-rm] 17 | command = "docker" 18 | args = ["rm", "--force", "${CONTAINER_NAME}"] 19 | ignore_errors = true 20 | 21 | [tasks.docker-destroy] 22 | run_task = { name = [ 23 | "docker-stop", 24 | "docker-rm", 25 | ] } 26 | 27 | [tasks.docker-logs] 28 | command = "docker" 29 | args = ["logs", "${CONTAINER_NAME}"] 30 | ignore_errors = true 31 | -------------------------------------------------------------------------------- /infra/scripts/ethapi.toml: -------------------------------------------------------------------------------- 1 | [tasks.ethapi-run] 2 | script = """ 3 | docker run \ 4 | ${FLAGS} \ 5 | --name ${ETHAPI_CONTAINER_NAME} \ 6 | --init \ 7 | --user $(id -u) \ 8 | --network ${NETWORK_NAME} \ 9 | --publish ${ETHAPI_HOST_PORT}:8545 \ 10 | --env TENDERMINT_RPC_URL=http://${CMT_CONTAINER_NAME}:26657 \ 11 | --env TENDERMINT_WS_URL=ws://${CMT_CONTAINER_NAME}:26657/websocket \ 12 | --env LOG_LEVEL=${ETHAPI_LOG_LEVEL} \ 13 | --env RUST_BACKTRACE=1 \ 14 | ${FM_DOCKER_IMAGE} \ 15 | ${CMD} 16 | """ 17 | dependencies = ["docker-network-create"] 18 | 19 | [tasks.ethapi-start] 20 | extend = "ethapi-run" 21 | env = { "CMD" = "eth run", "FLAGS" = "-d" } 22 | 23 | [tasks.ethapi-destroy] 24 | env = { "CONTAINER_NAME" = "${ETHAPI_CONTAINER_NAME}" } 25 | run_task = "docker-destroy" 26 | 27 | [tasks.ethapi-logs] 28 | extend = "docker-logs" 29 | env = { "CONTAINER_NAME" = "${ETHAPI_CONTAINER_NAME}" } 30 | -------------------------------------------------------------------------------- /infra/scripts/genesis.toml: -------------------------------------------------------------------------------- 1 | [tasks.genesis-new] 2 | extend = "fendermint-tool" 3 | env = { "ENTRY" = "fendermint", "CMD" = "genesis --genesis-file /data/genesis.json new --chain-name ${NETWORK_NAME} --base-fee ${BASE_FEE} --timestamp ${TIMESTAMP} --power-scale ${POWER_SCALE}" } 4 | 5 | ## Takes: 6 | ## - KEYS_SUBDIR: directory under /data where to store the key. 7 | ## - KEY_NAME: name of the key. 8 | [tasks.genesis-new-key] 9 | extend = "fendermint-tool" 10 | env = { "ENTRY" = "fendermint", "CMD" = "key gen --out-dir /data/${NODE_NAME}/${KEYS_SUBDIR} --name ${KEY_NAME}" } 11 | script.pre = "mkdir -p ${BASE_DIR}/${NODE_NAME}/${KEYS_SUBDIR}" 12 | script.post = "chmod 600 ${BASE_DIR}/${NODE_NAME}/${KEYS_SUBDIR}/${KEY_NAME}.sk" 13 | 14 | [tasks.genesis-new-accounts] 15 | dependencies = ["genesis-new-account-f1", "genesis-new-account-eth"] 16 | 17 | [tasks.genesis-new-account-f1] 18 | extend = "fendermint-tool" 19 | env = { "ENTRY" = "fendermint", "CMD" = "genesis --genesis-file /data/genesis.json add-account --public-key /data/${NODE_NAME}/${PUB_KEY_PATH} --balance ${BALANCE}" } 20 | 21 | [tasks.genesis-new-account-eth] 22 | extend = "fendermint-tool" 23 | env = { "ENTRY" = "fendermint", "CMD" = "genesis --genesis-file /data/genesis.json add-account --kind ethereum --public-key /data/${NODE_NAME}/${PUB_KEY_PATH} --balance ${BALANCE}" } 24 | 25 | [tasks.genesis-add-validator] 26 | extend = "fendermint-tool" 27 | env = { "ENTRY" = "fendermint", "CMD" = "genesis --genesis-file /data/genesis.json add-validator --public-key /data/${NODE_NAME}/${PUB_KEY_PATH} --power 1" } 28 | 29 | [tasks.genesis-new-gateway] 30 | extend = "fendermint-tool" 31 | env = { "ENTRY" = "fendermint", "CMD" = """genesis --genesis-file /data/genesis.json ipc gateway --subnet-id /r0 \ 32 | --bottom-up-check-period 10 \ 33 | --msg-fee 10 \ 34 | --majority-percentage 67 \ 35 | --min-collateral 1""" } 36 | 37 | [tasks.genesis-write] 38 | extend = "fendermint-tool" 39 | env = { "ENTRY" = "fendermint", "CMD" = "genesis --genesis-file /data/genesis.json into-tendermint --out /data/genesis.committed.json" } 40 | script.post = "cp ${BASE_DIR}/genesis.committed.json ${CMT_DIR}/config/genesis.json" 41 | -------------------------------------------------------------------------------- /infra/scripts/node.toml: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # Node helpers 3 | ######################################################################################################################## 4 | 5 | [tasks.node-init] 6 | dependencies = [ 7 | "node-clear", 8 | "node-mkdir", 9 | ] 10 | 11 | [tasks.node-clear] 12 | script = """ 13 | echo clearing all IPC data 14 | rm -rf ${BASE_DIR} 15 | """ 16 | 17 | [tasks.node-mkdir] 18 | script = """ 19 | echo creating directories: $BASE_DIR $FM_DIR $CMT_DIR 20 | mkdir -p $BASE_DIR 21 | mkdir -p $FM_DIR 22 | mkdir -p $CMT_DIR 23 | touch $ENV_FILE 24 | """ 25 | -------------------------------------------------------------------------------- /infra/scripts/testnet.toml: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # Testnet 3 | ######################################################################################################################## 4 | [tasks.testnet] 5 | dependencies = [ 6 | "testnet-down", 7 | "testnet-init", 8 | "fendermint-deps", 9 | "testnet-up", 10 | ] 11 | 12 | [tasks.testnet-up] 13 | script = """ 14 | if [ -z $GID ]; then GID=$(id -g); fi 15 | if [ -z $UID ]; then UID=$(id -u); fi 16 | export UID 17 | export GID 18 | export CMT_P2P_PERSISTENT_PEERS=`cat $BASE_DIR/peers` 19 | export SUBNET_ID=$SUBNET_ID 20 | export BASE_DIR=$BASE_DIR 21 | ./infra/run.sh start 22 | """ 23 | 24 | [tasks.testnet-down] 25 | script = """ 26 | export CMT_P2P_PERSISTENT_PEERS="UNDEFINED" 27 | if [ -z $GID ]; then GID=$(id -g); fi 28 | if [ -z $UID ]; then UID=$(id -u); fi 29 | export UID 30 | export GID 31 | ./infra/run.sh stop 32 | """ 33 | 34 | [tasks.testnet-init] 35 | dependencies = [ 36 | "testnet-clear", 37 | "fendermint-pull", 38 | "docker-network-create", 39 | "cometbft-pull", 40 | "testnet-mkdir", 41 | "genesis-new", 42 | "testnet-init-nodes", 43 | "genesis-write", 44 | "testnet-copy-genesis", 45 | "testnet-setup-persistent-peers", 46 | ] 47 | 48 | [tasks.testnet-init-nodes] 49 | script_runner = "@duckscript" 50 | script = """ 51 | nodes = range 0 4 52 | 53 | for i in ${nodes} 54 | NUMBER = set ${i} 55 | NODE_NAME = set "node${NUMBER}" 56 | 57 | mkdir ${BASE_DIR}/${NODE_NAME} 58 | mkdir ${BASE_DIR}/${NODE_NAME}/fendermint 59 | mkdir ${BASE_DIR}/${NODE_NAME}/cometbft 60 | 61 | set_env NODE_NAME ${NODE_NAME} 62 | set_env NUMBER ${NUMBER} 63 | 64 | cm_run_task testnet-cometbft-init 65 | cm_run_task genesis-new-key 66 | cm_run_task genesis-new-accounts 67 | cm_run_task genesis-add-validator 68 | 69 | IP_LAST = calc ${NUMBER}+2 70 | NETWORK_ADDR = set "192.167.10.${IP_LAST}:26656" 71 | set_env NETWORK_ADDR ${NETWORK_ADDR} 72 | cm_run_task testnet-add-peer 73 | 74 | cm_run_task testnode-export-keys 75 | end 76 | 77 | release ${nodes} 78 | """ 79 | 80 | [tasks.testnet-clear] 81 | script = """ 82 | echo clearing all IPC data 83 | rm -rf ${BASE_DIR} 84 | """ 85 | 86 | [tasks.testnet-mkdir] 87 | script = """ 88 | mkdir -p ${BASE_DIR} 89 | """ 90 | 91 | [tasks.testnet-cometbft-init] 92 | extend = "cometbft-init" 93 | env = { "CMD" = "init", "NETWORK_NAME" = "${NETWORK_NAME}", "CMT_DIR" = "${BASE_DIR}/${NODE_NAME}/cometbft", "CMT_CONTAINER_NAME" = "cometbft-node${NUMBER}", "FLAGS" = "-a STDOUT -a STDERR --rm" } 94 | 95 | [tasks.testnet-add-peer] 96 | extend = "fendermint-tool" 97 | env = { "ENTRY" = "fendermint", "CMD" = """key add-peer \ 98 | --node-key-file /data/${NODE_NAME}/${COMETBFT_SUBDIR}/config/node_key.json \ 99 | --network-addr ${NETWORK_ADDR} \ 100 | --local-peers-file /data/peers \ 101 | """ } 102 | 103 | [tasks.testnet-setup-persistent-peers] 104 | script = """ 105 | unset CMT_P2P_PERSISTENT_PEERS 106 | export CMT_P2P_PERSISTENT_PEERS=`cat $BASE_DIR/peers` 107 | echo Persistent peers: $CMT_P2P_PERSISTENT_PEERS 108 | 109 | for i in $(seq 0 3); do 110 | sed -i'' -e "s|persistent_peers = \\"\\"|persistent_peers = \\"$CMT_P2P_PERSISTENT_PEERS\\"|" $BASE_DIR/node${i}/cometbft/config/config.toml 111 | done 112 | """ 113 | 114 | [tasks.testnet-copy-genesis] 115 | script = """ 116 | for i in $(seq 0 3); do 117 | cp $BASE_DIR/genesis.committed.json $BASE_DIR/node${i}/cometbft/config/genesis.json 118 | done 119 | """ 120 | -------------------------------------------------------------------------------- /infra/scripts/testnode.toml: -------------------------------------------------------------------------------- 1 | ######################################################################################################################## 2 | # Testnode 3 | ######################################################################################################################## 4 | [tasks.testnode] 5 | workspace = false 6 | dependencies = [ 7 | "testnode-down", 8 | "fendermint-pull", 9 | "testnode-init", 10 | "docker-network-create", 11 | "cometbft-init", 12 | "fendermint-deps", 13 | "testnode-config", 14 | "fendermint-start", 15 | "cometbft-start", 16 | "cometbft-wait", 17 | "ethapi-start", 18 | "testnode-report", 19 | ] 20 | 21 | [tasks.testnode-init] 22 | dependencies = [ 23 | "node-clear", 24 | "node-mkdir", 25 | ] 26 | 27 | [tasks.testnode-clear] 28 | script = """ 29 | echo clearing all IPC data 30 | rm -rf ${BASE_DIR} 31 | """ 32 | 33 | [tasks.testnode-mkdir] 34 | script = """ 35 | echo creating directories: $BASE_DIR $FM_DIR $CMT_DIR 36 | mkdir -p $BASE_DIR 37 | mkdir -p $FM_DIR 38 | 39 | mkdir -p $CMT_DIR 40 | """ 41 | 42 | [tasks.testnode-restart] 43 | run_task = { name = [ 44 | "cometbft-stop", 45 | "fendermint-stop", 46 | "ethapi-stop", 47 | "fendermint-start", 48 | "cometbft-start", 49 | "cometbft-wait", 50 | "ethapi-start", 51 | ] } 52 | 53 | [tasks.testnode-down] 54 | # `dependencies` doesn't seem to work with `cleanup_task`. 55 | run_task = { name = [ 56 | "cometbft-destroy", 57 | "fendermint-destroy", 58 | "ethapi-destroy", 59 | "docker-network-rm", 60 | ] } 61 | 62 | # This task create all necessary data structures to run Fendermint: 63 | # the genesis file with necessary entities and cryptographic keys. 64 | [tasks.testnode-config] 65 | dependencies = [ 66 | "genesis-new", 67 | "genesis-new-key", 68 | "genesis-new-accounts", 69 | "genesis-add-validator", 70 | "genesis-new-gateway", 71 | "genesis-write", 72 | "testnode-export-keys", 73 | ] 74 | 75 | [tasks.testnode-export-keys] 76 | extend = "fendermint-tool" 77 | env = { "ENTRY" = "fendermint", "CMD" = "key into-tendermint --secret-key /data/${NODE_NAME}/${PRIV_KEY_PATH} --out /data/${NODE_NAME}/${COMETBFT_SUBDIR}/config/priv_validator_key.json" } 78 | script.post = "chmod 600 ${BASE_DIR}/${NODE_NAME}/${COMETBFT_SUBDIR}/config/priv_validator_key.json" 79 | 80 | [tasks.testnode-report] 81 | script = """cat << EOF 82 | ############################ 83 | # # 84 | # Testnode ready! 🚀 # 85 | # # 86 | ############################ 87 | 88 | Eth API: 89 | \thttp://0.0.0.0:8545 90 | 91 | Accounts: 92 | $(jq -r '.accounts[] | "\t\\(.meta.Account.owner): \\(.balance) coin units"' ${BASE_DIR}/genesis.json) 93 | 94 | Private key (hex ready to import in MetaMask): 95 | \t$(cat ${BASE_DIR}/${NODE_NAME}/${PRIV_KEY_PATH} | base64 -d | xxd -p -c 1000000) 96 | 97 | Note: both accounts use the same private key @ ${BASE_DIR}/${PRIV_KEY_PATH} 98 | 99 | Chain ID: 100 | \t$(curl -s --location --request POST 'http://localhost:8545/' --header 'Content-Type: application/json' --data-raw '{ "jsonrpc":"2.0", "method":"eth_chainId", "params":[], "id":1 }' | jq -r '.result' | xargs printf "%d") 101 | 102 | Fendermint API: 103 | \thttp://localhost:26658 104 | 105 | CometBFT API: 106 | \thttp://0.0.0.0:26657 107 | EOF 108 | """ 109 | -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["clippy", "llvm-tools", "rustfmt"] 4 | targets = ["wasm32-unknown-unknown"] 5 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | 3 | # Ignore auto-generated files. 4 | ignore = ["fendermint/vm/ipc_actors"] 5 | -------------------------------------------------------------------------------- /scripts/add_license.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # Checks if the source code contains required license and adds it if necessary. 4 | # Returns 1 if there was a missing license, 0 otherwise. 5 | COPYRIGHT_TXT=$(dirname $0)/copyright.txt 6 | 7 | # Any year is fine. We can update the year as a single PR in all files that have it up to last year. 8 | PAT_PL=".*// Copyright 202(1|2)-202\d Protocol Labs.*" 9 | PAT_SPDX="/*// SPDX-License-Identifier: Apache-2.0, MIT.*" 10 | 11 | # Look at enough lines so that we can include multiple copyright holders. 12 | LINES=4 13 | 14 | # Ignore auto-generated code. 15 | IGNORE=( 16 | "fendermint/vm/ipc_actors" 17 | ); 18 | 19 | ignore() { 20 | file=$1 21 | for path in $IGNORE; do 22 | if echo "$file" | grep -q "$path"; then 23 | return 0 24 | fi 25 | done 26 | return 1 27 | } 28 | 29 | ret=0 30 | 31 | 32 | # NOTE: When files are moved/split/deleted, the following queries would find and recreate them in the original place. 33 | # To avoid that, first commit the changes, then run the linter; that way only the new places are affected. 34 | 35 | # Look for files without headers. 36 | for file in $(git grep --cached -Il '' -- '*.rs'); do 37 | if ignore "$file"; then 38 | continue 39 | fi 40 | header=$(head -$LINES "$file") 41 | if ! echo "$header" | grep -q -P "$PAT_SPDX"; then 42 | echo "$file was missing header" 43 | cat $COPYRIGHT_TXT "$file" > temp 44 | mv temp "$file" 45 | ret=1 46 | fi 47 | done 48 | 49 | # Look for changes that don't have the new copyright holder. 50 | for file in $(git diff --diff-filter=d --name-only origin/main -- '*.rs'); do 51 | if ignore "$file"; then 52 | continue 53 | fi 54 | header=$(head -$LINES "$file") 55 | if ! echo "$header" | grep -q -P "$PAT_PL"; then 56 | echo "$file was missing Protocol Labs" 57 | head -1 $COPYRIGHT_TXT > temp 58 | cat "$file" >> temp 59 | mv temp "$file" 60 | ret=1 61 | fi 62 | done 63 | 64 | exit $ret 65 | -------------------------------------------------------------------------------- /scripts/copyright.txt: -------------------------------------------------------------------------------- 1 | // Copyright 2022-2023 Protocol Labs 2 | // SPDX-License-Identifier: Apache-2.0, MIT 3 | --------------------------------------------------------------------------------