├── .github ├── images │ ├── Logo-Black.svg │ └── Logo-White.svg ├── pull_request_template.md └── workflows │ ├── docker.yml │ └── rust.yml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── RELEASE-TEMPLATE.md ├── accumulator ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── package_it.sh ├── publish_npm.sh └── src │ ├── error.rs │ ├── full.rs │ ├── lib.rs │ ├── light.rs │ ├── proof.rs │ ├── tree.rs │ ├── utils.rs │ └── wasm.rs ├── agents ├── kathy │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── kathy.rs │ │ ├── main.rs │ │ └── settings.rs ├── processor │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── main.rs │ │ ├── processor.rs │ │ ├── prover_sync.rs │ │ ├── push.rs │ │ └── settings.rs ├── relayer │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── main.rs │ │ ├── relayer.rs │ │ └── settings.rs ├── updater │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ │ ├── main.rs │ │ ├── produce.rs │ │ ├── settings.rs │ │ ├── submit.rs │ │ └── updater.rs └── watcher │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── README.md │ └── src │ ├── main.rs │ ├── settings.rs │ └── watcher.rs ├── build.sh ├── chains ├── README.md ├── nomad-ethereum │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── abis │ │ ├── Home.abi.json │ │ ├── Replica.abi.json │ │ └── XAppConnectionManager.abi.json │ ├── build.rs │ └── src │ │ ├── bindings │ │ ├── home.rs │ │ ├── mod.rs │ │ ├── replica.rs │ │ └── xappconnectionmanager.rs │ │ ├── error.rs │ │ ├── gas.rs │ │ ├── gelato.rs │ │ ├── home.rs │ │ ├── lib.rs │ │ ├── macros.rs │ │ ├── replica.rs │ │ ├── retrying.rs │ │ ├── signer.rs │ │ ├── submitter.rs │ │ ├── utils.rs │ │ └── xapp.rs └── nomad-substrate │ ├── CHANGELOG.md │ ├── Cargo.toml │ ├── metadata │ ├── .gitignore │ ├── avail.dev.metadata.scale │ └── avail.metadata.scale │ └── src │ ├── client.rs │ ├── configs │ ├── avail.rs │ └── mod.rs │ ├── decodings.rs │ ├── error.rs │ ├── home.rs │ ├── lib.rs │ ├── macros.rs │ ├── replica.rs │ ├── signer.rs │ ├── utils.rs │ └── xapp.rs ├── configuration ├── .gitignore ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── build.rs ├── configs │ ├── .gitignore │ ├── availDemo.json │ └── test.json ├── data │ ├── definitions.ts │ └── types.rs ├── package_it.sh ├── publish_cargo.sh ├── publish_npm.sh └── src │ ├── agent │ ├── kathy.rs │ ├── logging.rs │ ├── mod.rs │ ├── processor.rs │ ├── relayer.rs │ ├── signer.rs │ ├── updater.rs │ └── watcher.rs │ ├── bridge.rs │ ├── builtin.rs │ ├── chains │ ├── ethereum │ │ ├── mod.rs │ │ └── submitter │ │ │ ├── gelato.rs │ │ │ └── mod.rs │ ├── mod.rs │ └── substrate │ │ └── mod.rs │ ├── core.rs │ ├── gas │ ├── defaults.rs │ └── mod.rs │ ├── lib.rs │ ├── network.rs │ ├── secrets.rs │ ├── traits │ ├── env.rs │ └── mod.rs │ ├── utils.rs │ └── wasm │ ├── bindings.rs │ ├── mod.rs │ └── types.rs ├── fixtures ├── env.external ├── env.partial ├── env.test ├── env.test-agents ├── env.test-gelato-default ├── env.test-killswitch ├── env.test-killswitch-builtin ├── env.test-local-signer-default ├── env.test-local-signer-mixed ├── env.test-multi-vm ├── external_config.json ├── external_secrets.json ├── killswitch_config.json ├── killswitch_secrets.testing.yaml ├── merkle.json └── test_secrets.json ├── nomad-base ├── CHANGELOG.md ├── Cargo.toml ├── bin │ └── example.rs ├── src │ ├── agent.rs │ ├── bin │ │ └── secrets_template.rs │ ├── contract_sync │ │ ├── metrics.rs │ │ ├── mod.rs │ │ └── schema.rs │ ├── error.rs │ ├── home.rs │ ├── indexer.rs │ ├── lib.rs │ ├── macros.rs │ ├── metrics.rs │ ├── nomad_db.rs │ ├── replica.rs │ ├── schema.rs │ ├── settings │ │ ├── chains.rs │ │ ├── macros.rs │ │ ├── mod.rs │ │ └── trace │ │ │ ├── fmt.rs │ │ │ ├── mod.rs │ │ │ └── span_metrics.rs │ ├── submitter.rs │ └── xapp.rs └── tests │ └── signer_integration.rs ├── nomad-core ├── CHANGELOG.md ├── Cargo.toml ├── bin │ ├── lib_test_output.rs │ ├── proof_output.rs │ └── utils_test_output.rs └── src │ ├── aws.rs │ ├── chain.rs │ ├── db │ ├── iterator.rs │ ├── mod.rs │ └── typed_db.rs │ ├── lib.rs │ ├── models │ ├── home.rs │ ├── mod.rs │ └── replica.rs │ ├── test_output.rs │ ├── test_utils.rs │ ├── traits │ ├── encode.rs │ ├── home.rs │ ├── indexer.rs │ ├── mod.rs │ ├── replica.rs │ ├── signer.rs │ └── xapp.rs │ ├── types │ ├── failure.rs │ ├── messages.rs │ ├── mod.rs │ └── update.rs │ └── utils.rs ├── nomad-test ├── CHANGELOG.md ├── Cargo.toml └── src │ ├── lib.rs │ ├── mocks │ ├── home.rs │ ├── indexer.rs │ ├── mod.rs │ ├── replica.rs │ └── xapp.rs │ └── test_utils.rs ├── nomad-types ├── CHANGELOG.md ├── Cargo.toml └── src │ ├── error.rs │ ├── lib.rs │ └── macros.rs ├── provision_kms_keys.py ├── release.sh ├── rust-toolchain └── tools ├── CHANGELOG.md ├── README.md ├── balance-exporter ├── Cargo.toml └── src │ └── main.rs ├── killswitch ├── Cargo.toml └── src │ ├── errors.rs │ ├── killswitch.rs │ ├── main.rs │ ├── secrets.rs │ └── settings.rs ├── kms-cli ├── Cargo.toml └── src │ └── main.rs └── nomad-cli ├── Cargo.toml ├── README.md └── src ├── commands.rs ├── main.rs ├── replicas.rs ├── rpc.rs └── subcommands ├── db_state.rs ├── mod.rs └── prove.rs /.github/images/Logo-Black.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/images/Logo-White.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 11 | 12 | ## Motivation 13 | 14 | 19 | 20 | ## Solution 21 | 22 | 26 | 27 | ## PR Checklist 28 | 29 | - [ ] Added Tests 30 | - [ ] Updated Documentation 31 | - [ ] Updated CHANGELOG.md for the appropriate package 32 | - [ ] Ran PR in local/dev/staging 33 | -------------------------------------------------------------------------------- /.github/workflows/docker.yml: -------------------------------------------------------------------------------- 1 | # Documentation 2 | # https://github.com/docker/metadata-action 3 | # https://github.com/docker/login-action#google-container-registry-gcr 4 | # https://github.com/docker/build-push-action 5 | name: Build Docker container 6 | on: 7 | push: 8 | branches: 9 | - '**' 10 | tags: 11 | - 'v*' 12 | 13 | jobs: 14 | build-docker: 15 | runs-on: ubuntu-22.04 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions-rs/toolchain@v1 20 | with: 21 | toolchain: stable 22 | - uses: Swatinem/rust-cache@v1 23 | with: 24 | # Add a key to prevent rust cache collision with rust.yml workflows 25 | key: 'release' 26 | 27 | - name: Build agents (release) 28 | run: cargo build --release 29 | 30 | - name: Docker metadata 31 | id: meta 32 | uses: docker/metadata-action@v3 33 | with: 34 | # list of Docker images to use as base name for tags 35 | images: gcr.io/nomad-xyz/nomad-agent 36 | # generate Docker tags based on the following events/attributes 37 | tags: | 38 | type=semver,pattern={{version}} 39 | type=semver,pattern={{major}}.{{minor}} 40 | type=semver,pattern={{major}} 41 | type=ref,event=branch 42 | type=ref,event=pr 43 | type=sha 44 | 45 | - name: Login to Docker repository 46 | uses: docker/login-action@v1 47 | with: 48 | registry: gcr.io 49 | username: _json_key 50 | password: ${{ secrets.GCLOUD_SERVICE_KEY }} 51 | 52 | - name: Build and push container 53 | uses: docker/build-push-action@v2 54 | with: 55 | context: . 56 | push: true 57 | tags: ${{ steps.meta.outputs.tags }} 58 | labels: ${{ steps.meta.outputs.labels }} 59 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | # Allows you to run this workflow manually from the Actions tab 12 | workflow_dispatch: 13 | 14 | env: 15 | CARGO_TERM_COLOR: always 16 | 17 | jobs: 18 | build: 19 | runs-on: ubuntu-22.04 20 | steps: 21 | - uses: actions/checkout@v2 22 | - uses: actions-rs/toolchain@v1 23 | with: 24 | toolchain: stable 25 | - uses: Swatinem/rust-cache@v1 26 | 27 | - name: Build agents 28 | run: cargo build --locked --workspace --all-features --verbose 29 | 30 | test: 31 | runs-on: ubuntu-22.04 32 | steps: 33 | - uses: actions/checkout@v2 34 | - uses: actions-rs/toolchain@v1 35 | with: 36 | toolchain: stable 37 | - uses: Swatinem/rust-cache@v1 38 | 39 | - name: Run tests 40 | run: cargo test --locked --workspace --all-features --verbose 41 | 42 | lint: 43 | runs-on: ubuntu-22.04 44 | steps: 45 | - uses: actions/checkout@v2 46 | - uses: actions-rs/toolchain@v1 47 | with: 48 | toolchain: stable 49 | - uses: Swatinem/rust-cache@v1 50 | 51 | - name: Rustfmt 52 | run: cargo fmt --all -- --check 53 | 54 | - name: Clippy 55 | run: cargo clippy --workspace --all-features -- -D warnings 56 | 57 | wasm-build: 58 | runs-on: ubuntu-22.04 59 | steps: 60 | - uses: actions/checkout@v2 61 | - uses: actions-rs/toolchain@v1 62 | with: 63 | toolchain: stable 64 | - uses: Swatinem/rust-cache@v1 65 | - uses: jetli/wasm-pack-action@v0.3.0 66 | - name: wasm-configuration 67 | run: cd configuration && bash package_it.sh 68 | - name: wasm-accumulator 69 | run: cd accumulator && bash package_it.sh 70 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | db 3 | processordb 4 | updaterdb 5 | relayerdb 6 | kathydb 7 | *.env 8 | .DS_Store -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | cargo-features = ["edition2021"] 2 | 3 | [workspace] 4 | resolver = "2" 5 | 6 | members = [ 7 | "accumulator", 8 | "nomad-types", 9 | "nomad-core", 10 | "nomad-base", 11 | "nomad-test", 12 | "chains/nomad-ethereum", 13 | "chains/nomad-substrate", 14 | "configuration", 15 | "agents/kathy", 16 | "agents/updater", 17 | "agents/relayer", 18 | "agents/watcher", 19 | "agents/processor", 20 | "tools/kms-cli", 21 | "tools/nomad-cli", 22 | "tools/balance-exporter", 23 | "tools/killswitch", 24 | ] 25 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:22.04 2 | 3 | ENV TARGET_DIR='target' 4 | WORKDIR /app 5 | 6 | RUN apt-get update \ 7 | && apt-get install -y libssl-dev ca-certificates \ 8 | && chmod 777 /app \ 9 | && mkdir /usr/share/nomad \ 10 | && chmod 1000 /usr/share/nomad 11 | 12 | COPY ${TARGET_DIR}/release/updater \ 13 | ${TARGET_DIR}/release/relayer \ 14 | ${TARGET_DIR}/release/watcher \ 15 | ${TARGET_DIR}/release/processor \ 16 | ${TARGET_DIR}/release/kathy \ 17 | ${TARGET_DIR}/release/kms-cli \ 18 | ${TARGET_DIR}/release/nomad-cli \ 19 | ${TARGET_DIR}/release/killswitch ./ 20 | 21 | USER 1000 22 | CMD ["./watcher"] -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2018 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /RELEASE-TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Build information 2 | 3 | Built using `rustc ()` 4 | 5 | ## Upgrade Instructions 6 | 7 | If the agent configuration interface or DB schema has changed such that it is not backwards compatible, please enumerate instructions for upgrading agent (e.g. change environment variables, delete agent DB, etc). Otherwise, please explicitly document that the release is backwards compatible and no additional steps are required. 8 | 9 | ## New Features 10 | 11 | Enumerate all features added to each crate, listing them for each crate separately. 12 | 13 | Example: 14 | 15 | - `nomad-core` 16 | - Features in `nomad-core`... 17 | - `nomad-base` 18 | - Features in `nomad-base`... 19 | 20 | ## Fixes 21 | 22 | Enumerate all fixes/patches added to each crate, listing them for each crate separately. 23 | 24 | Example: 25 | 26 | - `nomad-core` 27 | - Fixes in `nomad-core`... 28 | - `nomad-base` 29 | - Fixes in `nomad-base`... 30 | -------------------------------------------------------------------------------- /accumulator/.gitignore: -------------------------------------------------------------------------------- 1 | ts -------------------------------------------------------------------------------- /accumulator/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - Adds deserialization implementation for generic arrays to allow `LightMerkle` to derive `Serialize/Deserialize` 6 | - adds a changelog 7 | -------------------------------------------------------------------------------- /accumulator/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "accumulator" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["James Prestwich ", "The Nomad Developers "] 6 | description = "Nomad sparse merle tree" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | [lib] 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [dependencies] 20 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", default-features = false } 21 | sha3 = "0.9.1" 22 | thiserror = "1.0.30" 23 | serde = {version = "1.0", features = ["derive"]} 24 | affix = "0.1.2" 25 | once_cell = "1.8.0" 26 | 27 | [target.'cfg(target_arch = "wasm32")'.dependencies] 28 | wee_alloc = "0.4.5" 29 | js-sys = "0.3.56" 30 | wasm-bindgen = { version = "0.2.79", features = ["serde-serialize"] } 31 | affix = "0.1.2" -------------------------------------------------------------------------------- /accumulator/README.md: -------------------------------------------------------------------------------- 1 | # Nomad Accumulator 2 | 3 | A set of accumulator-related tooling for Nomad development. This crate contains 4 | a full incremental sparse merkle tree, as well as a lightweight tree which 5 | stores only the leading branch. The full tree is suitable for proving, while the 6 | light tree is suitable only for verifiying proofs. 7 | 8 | ### Interface 9 | 10 | We use constant generics for fixed-depth trees. Trees can be instantiated with 11 | any depth, but it is NOT RECOMMENDED to create very deep trees. 12 | 13 | We provide two trees structures: 14 | 15 | - `LightMerkle` 16 | - This tree stores only the leading branch, and may be used as a verifier. 17 | - Ingested leaves are discarded. 18 | - In-memory size is constant. 19 | - `Tree` 20 | - This tree stores all leaves and may be used as a prover 21 | - Ingested leaves are kept in memory. 22 | - In-memory size grows with each leaf. 23 | 24 | We provide a single `Proof` struct. It may be produced 25 | by `Tree::::prove` and verified with `Tree::::verify` or with 26 | `LightMerkle::::verify`. 27 | 28 | For convenient use in our own crates, we have aliased the depth 32 trees as 29 | `NomadTree` and `NomadLightMerkle`. 30 | 31 | ```rust 32 | use accumulator::{Tree, Proof, Merkle, MerkleProof}; 33 | use ethers::prelude::H256; 34 | 35 | let mut tree: Tree<16> = Default::default(); 36 | tree.ingest(H256::zero()); 37 | 38 | // Error 39 | let proof: Proof<16> = tree.prove(0).unwrap(); 40 | tree.verify(&proof).unwrap(); 41 | ``` 42 | 43 | ### Wasm Bindings 44 | 45 | We also expose a WASM interface. Limitations: 46 | 47 | - The WASM bindings expose only the proving tree, and proof structs. 48 | - WASM bindings do not yet support const generics 49 | - Instead we expose trees of depth 2, 4, 8, 16, and 32 50 | - e.g. `Tree16` is a depth 16 tree, and creates and verifies `Proof16` 51 | - WASM-bindings are not yet published on npm 52 | -------------------------------------------------------------------------------- /accumulator/package_it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # change these :) 5 | SCOPE="nomad-xyz" 6 | PKG_NAME="accumulator" 7 | 8 | # Check if jq is installed 9 | if ! [ -x "$(command -v jq)" ]; then 10 | echo "jq is not installed" >& 2 11 | echo "how do you live with yourself" 12 | exit 1 13 | fi 14 | 15 | 16 | # Check if jq is installed 17 | if ! [ -x "$(command -v wasm-pack)" ]; then 18 | echo "wasm-pack is not installed" >& 2 19 | exit 1 20 | fi 21 | # Cleanup any old generated code 22 | rm -rf ./ts 23 | mkdir -p ./ts 24 | 25 | # build 26 | wasm-pack build --target browser --scope $SCOPE --out-dir ts/web --out-name $PKG_NAME 27 | wasm-pack build --target nodejs --scope $SCOPE --out-dir ts/node --out-name $PKG_NAME 28 | 29 | # use the browser package.json as a base 30 | mv ts/web/package.json ts/ 31 | # copy in licenses from repo root 32 | cp ../LICENSE* ts/ 33 | # Get the README in the root, delete the spare 34 | mv ts/web/README* ts/ 35 | 36 | # clean redundant files 37 | rm ts/node/README* 38 | rm ts/node/package.json 39 | 40 | # set the package.json main key (affects how nodejs loads this) 41 | cat ts/package.json | jq --arg main "node/$PKG_NAME.js" '.main = $main'> TMP_FILE && mv TMP_FILE ts/package.json 42 | 43 | # set the package.json browser key (affects how bundlers load this) 44 | cat ts/package.json | jq --arg browser "web/$PKG_NAME.js" '.browser = $browser'> TMP_FILE && mv TMP_FILE ts/package.json 45 | 46 | # set the package.json module key (affects how bundlers load this) 47 | cat ts/package.json | jq --arg m "web/$PKG_NAME.js" '.module = $m' > TMP_FILE && mv TMP_FILE ts/package.json 48 | 49 | # set the package.json name key (correct module name) 50 | cat ts/package.json | jq --arg n "@$SCOPE/$PKG_NAME" '.name = $n' > TMP_FILE && mv TMP_FILE ts/package.json 51 | 52 | # set the package.json types key 53 | cat ts/package.json | jq --arg types "web/$PKG_NAME.d.ts" '.types = $types' > TMP_FILE && mv TMP_FILE ts/package.json 54 | 55 | # empty the package.json files list 56 | cat ts/package.json | jq '.files = []' > TMP_FILE && mv TMP_FILE ts/package.json 57 | 58 | # add each web file to the package.json files list 59 | for F in "web/$PKG_NAME""_bg.wasm" "web/$PKG_NAME""_bg.d.ts" "web/$PKG_NAME.js" "web/$PKG_NAME.d.ts" "web/$PKG_NAME""_bg.js" 60 | do 61 | cat ts/package.json | jq --arg f "$F" '.files += [$f]' > TMP_FILE && mv TMP_FILE ts/package.json 62 | done 63 | 64 | # add each node file to the package.json files list 65 | for F in "node/$PKG_NAME""_bg.wasm" "node/$PKG_NAME""_bg.d.ts" "node/$PKG_NAME.js" "node/$PKG_NAME.d.ts" 66 | do 67 | cat ts/package.json | jq --arg f "$F" '.files += [$f]' > TMP_FILE && mv TMP_FILE ts/package.json 68 | done -------------------------------------------------------------------------------- /accumulator/publish_npm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cargo clippy --target wasm32-unknown-unknown 5 | cargo test 6 | 7 | ./package_it.sh 8 | cd ts 9 | npm publish -------------------------------------------------------------------------------- /accumulator/src/error.rs: -------------------------------------------------------------------------------- 1 | use ethers::prelude::H256; 2 | use thiserror; 3 | 4 | /// Tree Errors 5 | #[derive(Debug, thiserror::Error, Clone, Copy)] 6 | pub enum ProvingError { 7 | /// Index is above tree max size 8 | #[error("Requested proof for index above u32::MAX: {0}")] 9 | IndexTooHigh(usize), 10 | /// Requested proof for a zero element 11 | #[error("Requested proof for a zero element. Requested: {index}. Tree has: {count}")] 12 | ZeroProof { 13 | /// The index requested 14 | index: usize, 15 | /// The number of leaves 16 | count: usize, 17 | }, 18 | } 19 | 20 | /// Tree Errors 21 | #[derive(Debug, thiserror::Error, Clone, Copy)] 22 | pub enum VerifyingError { 23 | /// Failed proof verification 24 | #[error("Proof verification failed. Root is {expected}, produced is {actual}")] 25 | #[allow(dead_code)] 26 | VerificationFailed { 27 | /// The expected root (this tree's current root) 28 | expected: H256, 29 | /// The root produced by branch evaluation 30 | actual: H256, 31 | }, 32 | } 33 | 34 | /// Error type for merkle tree ops. 35 | #[derive(Debug, PartialEq, Clone, Copy, thiserror::Error)] 36 | pub enum IngestionError { 37 | /// Trying to push in a leaf 38 | #[error("Trying to push in a leaf")] 39 | LeafReached, 40 | /// No more space in the MerkleTree 41 | #[error("No more space in the MerkleTree")] 42 | MerkleTreeFull, 43 | /// MerkleTree is invalid 44 | #[error("MerkleTree is invalid")] 45 | Invalid, 46 | /// Incorrect Depth provided 47 | #[error("Incorrect Depth provided")] 48 | DepthTooSmall, 49 | } 50 | -------------------------------------------------------------------------------- /accumulator/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![warn(missing_docs)] 3 | #![warn(missing_debug_implementations)] 4 | #![warn(missing_copy_implementations)] 5 | 6 | /// A full incremental merkle. Suitable for running off-chain. 7 | pub mod full; 8 | 9 | /// Hashing utils 10 | pub mod utils; 11 | 12 | /// Common error types for the merkle trees. 13 | pub mod error; 14 | 15 | /// A lightweight incremental merkle, suitable for running on-chain. Stores O 16 | /// (1) data 17 | pub mod light; 18 | /// Merkle Proof struct 19 | pub mod proof; 20 | 21 | /// A full incremental merkle tree. Suitable for proving. 22 | pub mod tree; 23 | 24 | #[cfg(target_arch = "wasm32")] 25 | /// Wasm bindings for common operations 26 | pub mod wasm; 27 | 28 | #[cfg(target_arch = "wasm32")] 29 | #[cfg_attr(target_arch = "wasm32", global_allocator)] 30 | static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; 31 | 32 | use ethers::{core::types::H256, prelude::U256}; 33 | use once_cell::sync::Lazy; 34 | 35 | /// Tree depth 36 | pub const TREE_DEPTH: usize = 32; 37 | /// A Nomad protocol standard-depth tree 38 | pub type NomadTree = tree::Tree; 39 | /// An incremental Nomad protocol standard-depth tree 40 | pub type NomadLightMerkle = light::LightMerkle; 41 | /// A Nomad protocol standard-depth proof 42 | pub type NomadProof = proof::Proof; 43 | 44 | const EMPTY_SLICE: &[H256] = &[]; 45 | 46 | pub use error::*; 47 | use full::*; 48 | pub use light::*; 49 | pub use proof::*; 50 | pub use tree::*; 51 | 52 | pub use utils::*; 53 | 54 | /// A cache of the zero hashes for each layer of the tree. 55 | pub static ZERO_HASHES: Lazy<[H256; TREE_DEPTH + 1]> = Lazy::new(|| { 56 | let mut hashes = [H256::zero(); TREE_DEPTH + 1]; 57 | for i in 0..TREE_DEPTH { 58 | hashes[i + 1] = hash_concat(hashes[i], hashes[i]); 59 | } 60 | hashes 61 | }); 62 | 63 | /// A merkle proof 64 | pub trait MerkleProof { 65 | /// Calculate the merkle root of this proof's branch 66 | fn root(&self) -> H256; 67 | } 68 | 69 | /// A simple trait for merkle-based accumulators 70 | pub trait Merkle: std::fmt::Debug + Default { 71 | /// A proof of some leaf in this tree 72 | type Proof: MerkleProof; 73 | 74 | /// The maximum number of elements the tree can ingest 75 | fn max_elements() -> U256; 76 | 77 | /// The number of elements currently in the tree 78 | fn count(&self) -> usize; 79 | 80 | /// Calculate the root hash of this Merkle tree. 81 | fn root(&self) -> H256; 82 | 83 | /// Get the tree's depth. 84 | fn depth(&self) -> usize; 85 | 86 | /// Push a leaf to the tree 87 | fn ingest(&mut self, element: H256) -> Result; 88 | 89 | /// Verify a proof against this tree's root. 90 | fn verify(&self, proof: &Self::Proof) -> Result<(), VerifyingError> { 91 | let actual = proof.root(); 92 | let expected = self.root(); 93 | if expected == actual { 94 | Ok(()) 95 | } else { 96 | Err(VerifyingError::VerificationFailed { expected, actual }) 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /accumulator/src/proof.rs: -------------------------------------------------------------------------------- 1 | use crate::{merkle_root_from_branch, MerkleProof}; 2 | use ethers::prelude::H256; 3 | 4 | /// A merkle proof object. The leaf, its path to the root, and its index in the 5 | /// tree. 6 | #[derive(Debug, Clone, Copy, serde::Deserialize, serde::Serialize, PartialEq)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct Proof { 9 | /// The leaf 10 | pub leaf: H256, 11 | /// The index 12 | pub index: usize, 13 | /// The merkle branch 14 | #[serde(with = "const_array_serde")] 15 | pub path: [H256; N], 16 | } 17 | 18 | mod const_array_serde { 19 | use super::H256; 20 | use serde::{ser::SerializeSeq, Deserialize, Deserializer, Serializer}; 21 | 22 | pub fn serialize(item: &[H256; N], serializer: S) -> Result 23 | where 24 | S: Serializer, 25 | { 26 | let mut seq = serializer.serialize_seq(Some(N))?; 27 | for i in item { 28 | seq.serialize_element(i)?; 29 | } 30 | seq.end() 31 | } 32 | 33 | pub fn deserialize<'de, D, const N: usize>(d: D) -> Result<[H256; N], D::Error> 34 | where 35 | D: Deserializer<'de>, 36 | { 37 | let v: Vec = Deserialize::deserialize(d)?; 38 | if v.len() != N { 39 | Err(serde::de::Error::custom(format!( 40 | "Expected a sequence with {} elements. Got {} elements", 41 | N, 42 | v.len() 43 | ))) 44 | } else { 45 | let mut h: [H256; N] = [Default::default(); N]; 46 | h.copy_from_slice(&v[..N]); 47 | Ok(h) 48 | } 49 | } 50 | } 51 | 52 | impl MerkleProof for Proof { 53 | /// Calculate the merkle root produced by evaluating the proof 54 | fn root(&self) -> H256 { 55 | merkle_root_from_branch(self.leaf, self.path.as_ref(), N, self.index) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /accumulator/src/tree.rs: -------------------------------------------------------------------------------- 1 | use crate::{full::MerkleTree, IngestionError, LightMerkle, Merkle, Proof, ProvingError}; 2 | use ethers::{core::types::H256, prelude::U256}; 3 | 4 | /// A simplified interface for a full sparse merkle tree 5 | #[derive(Debug, PartialEq)] 6 | pub struct Tree { 7 | count: usize, 8 | tree: Box, 9 | } 10 | 11 | impl Default for Tree { 12 | fn default() -> Self { 13 | Self::from_leaves(&[]) 14 | } 15 | } 16 | 17 | impl Merkle for Tree { 18 | type Proof = Proof; 19 | 20 | /// Return the maximum number of leaves in this tree 21 | fn max_elements() -> U256 { 22 | crate::utils::max_leaves(N) 23 | } 24 | 25 | fn count(&self) -> usize { 26 | self.count 27 | } 28 | 29 | fn root(&self) -> H256 { 30 | self.tree.hash() 31 | } 32 | 33 | fn depth(&self) -> usize { 34 | N 35 | } 36 | 37 | fn ingest(&mut self, element: H256) -> Result { 38 | self.count += 1; 39 | self.tree.push_leaf(element, N)?; 40 | Ok(self.tree.hash()) 41 | } 42 | } 43 | 44 | impl Tree { 45 | /// Instantiate a new tree with a known depth and a starting leaf-set 46 | pub fn from_leaves(leaves: &[H256]) -> Self { 47 | Self { 48 | count: leaves.len(), 49 | tree: Box::new(MerkleTree::create(leaves, N)), 50 | } 51 | } 52 | 53 | /// Calculate the initital root of a tree of this depth 54 | pub fn initial_root() -> H256 { 55 | LightMerkle::::default().root() 56 | } 57 | 58 | /// Return the leaf at `index` and a Merkle proof of its inclusion. 59 | /// 60 | /// The Merkle proof is in "bottom-up" order, starting with a leaf node 61 | /// and moving up the tree. Its length will be exactly equal to `depth`. 62 | pub fn prove(&self, index: usize) -> Result, ProvingError> { 63 | if index > 2usize.pow(N.try_into().unwrap()) - 1 { 64 | return Err(ProvingError::IndexTooHigh(index)); 65 | } 66 | 67 | let count = self.count(); 68 | if index >= count { 69 | return Err(ProvingError::ZeroProof { index, count }); 70 | } 71 | 72 | let (leaf, nodes) = self.tree.generate_proof(index, N); 73 | debug_assert_eq!(nodes.len(), N); 74 | let mut path = [H256::default(); N]; 75 | path.copy_from_slice(&nodes[..N]); 76 | Ok(Proof { leaf, index, path }) 77 | } 78 | } 79 | 80 | impl From for Tree 81 | where 82 | T: AsRef<[H256]>, 83 | { 84 | fn from(t: T) -> Self { 85 | Self::from_leaves(t.as_ref()) 86 | } 87 | } 88 | 89 | impl std::iter::FromIterator for Tree { 90 | /// Will panic if the tree fills 91 | fn from_iter>(iter: I) -> Self { 92 | let mut prover = Self::default(); 93 | prover.extend(iter); 94 | prover 95 | } 96 | } 97 | 98 | impl std::iter::Extend for Tree { 99 | /// Will panic if the tree fills 100 | fn extend>(&mut self, iter: I) { 101 | for i in iter { 102 | self.ingest(i).expect("!tree full"); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /accumulator/src/utils.rs: -------------------------------------------------------------------------------- 1 | use sha3::{Digest, Keccak256}; 2 | 3 | use ethers::core::types::{H256, U256}; 4 | 5 | /// Return the keccak256 digest of the preimage 6 | pub fn hash(preimage: impl AsRef<[u8]>) -> H256 { 7 | H256::from_slice(Keccak256::digest(preimage.as_ref()).as_slice()) 8 | } 9 | 10 | /// Return the keccak256 disgest of the concatenation of the arguments 11 | pub fn hash_concat(left: impl AsRef<[u8]>, right: impl AsRef<[u8]>) -> H256 { 12 | H256::from_slice( 13 | Keccak256::new() 14 | .chain(left.as_ref()) 15 | .chain(right.as_ref()) 16 | .finalize() 17 | .as_slice(), 18 | ) 19 | } 20 | 21 | /// Max number of leaves in a tree 22 | pub(crate) fn max_leaves(n: usize) -> U256 { 23 | U256::from(2).pow(n.into()) - 1 24 | } 25 | -------------------------------------------------------------------------------- /agents/kathy/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - fix: instrument futures, not joinhandles 6 | 7 | ### agents@1.1.0 8 | 9 | - make \*Settings::new async for optionally fetching config from a remote url 10 | - add test for remote config fetch 11 | - add bootup-only tracing subscriber 12 | - add environment variable overrides for agent configuration 13 | - add tests for agent environment variable overrides 14 | - remove `enabled` flag from agents project-wide 15 | 16 | ### agents@1.0.0 17 | 18 | - bumps version for first release 19 | - adds a changelog 20 | -------------------------------------------------------------------------------- /agents/kathy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kathy" 3 | version = "1.1.0" 4 | authors = ["James Prestwich ", "Luke Tchang "] 5 | edition = "2021" 6 | description = "Nomad kathy agent" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 15 | 16 | [dependencies] 17 | async-trait = { version = "0.1.42", default-features = false } 18 | color-eyre = "0.6.0" 19 | config = "0.10" 20 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 21 | futures-util = "0.3.12" 22 | log = "0.4.13" 23 | serde = {version = "1.0", features = ["derive"]} 24 | serde_json = {version = "1.0", default-features = false} 25 | thiserror = {version = "1.0.22", default-features = false} 26 | tokio = {version = "1.0.1", features = ["rt", "macros"]} 27 | tracing = "0.1.35" 28 | tracing-futures = "0.2.5" 29 | tracing-subscriber = "0.3.14" 30 | rand = "0.8.3" 31 | prometheus = "0.12" 32 | nomad-xyz-configuration = { path = "../../configuration" } 33 | 34 | nomad-types = { path = "../../nomad-types" } 35 | nomad-base = { path = "../../nomad-base" } 36 | nomad-core = { path = "../../nomad-core" } 37 | affix = "0.1.2" 38 | 39 | [dev-dependencies] 40 | dotenv = "0.15.0" 41 | serial_test = "0.6.0" 42 | nomad-test = { path = "../../nomad-test" } 43 | -------------------------------------------------------------------------------- /agents/kathy/README.md: -------------------------------------------------------------------------------- 1 | ## Kathy Agent 2 | 3 | Kathy is strictly used in development and staging environments for testing purposes. The role of kathy is to enqueue randomly generated cross-chain messages to the home contract. 4 | -------------------------------------------------------------------------------- /agents/kathy/src/main.rs: -------------------------------------------------------------------------------- 1 | //! Kathy is chatty. She sends random messages to random recipients 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn(missing_docs)] 5 | #![warn(unused_extern_crates)] 6 | 7 | mod kathy; 8 | mod settings; 9 | 10 | use crate::{kathy::Kathy, settings::KathySettings as Settings}; 11 | use color_eyre::Result; 12 | use nomad_base::NomadAgent; 13 | 14 | use tracing::info_span; 15 | use tracing_subscriber::prelude::*; 16 | 17 | #[tokio::main(flavor = "current_thread")] 18 | async fn main() -> Result<()> { 19 | color_eyre::install()?; 20 | 21 | // sets the subscriber for this scope only 22 | let _bootup_guard = tracing_subscriber::FmtSubscriber::builder() 23 | .json() 24 | .with_level(true) 25 | .set_default(); 26 | 27 | let span = info_span!("KathyBootup"); 28 | let _span = span.enter(); 29 | 30 | let settings = Settings::new().await?; 31 | let agent = Kathy::from_settings(settings).await?; 32 | 33 | drop(_span); 34 | drop(span); 35 | 36 | let _tracing_guard = agent.start_tracing(agent.metrics().span_duration()); 37 | 38 | let _ = agent.metrics().run_http_server(); 39 | 40 | agent.run_all().await??; 41 | Ok(()) 42 | } 43 | -------------------------------------------------------------------------------- /agents/processor/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - use `std::fmt::Display` to log contracts 6 | - fix: instrument futures, not joinhandles 7 | 8 | ### agents@1.1.0 9 | 10 | - make \*Settings::new async for optionally fetching config from a remote url 11 | - add bootup-only tracing subscriber 12 | - bug: add check for empty intersection of specified and subsidized 13 | - refactor: processor now uses global AWS client when proof pushing is enabled 14 | - prevent processor from retrying messages it has previously attempted to 15 | process 16 | - improve prove/process tracing 17 | - add environment variable overrides for agent configuration 18 | - add tests for agent environment variable overrides 19 | - remove `enabled` flag from agents project-wide 20 | 21 | ### agents@1.0.0 22 | 23 | - bumps version for first release 24 | - adds a changelog 25 | -------------------------------------------------------------------------------- /agents/processor/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "processor" 3 | version = "1.1.0" 4 | authors = ["Luke Tchang ", "James Prestwich "] 5 | edition = "2021" 6 | description = "Nomad processor agent" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | [dependencies] 15 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 16 | config = "0.10" 17 | serde = "1.0.120" 18 | serde_json = { version = "1.0.61", default-features = false } 19 | log = "0.4.13" 20 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 21 | thiserror = { version = "1.0.22", default-features = false } 22 | async-trait = { version = "0.1.42", default-features = false } 23 | futures-util = "0.3.12" 24 | color-eyre = "0.6.0" 25 | tracing = "0.1.35" 26 | tracing-futures = "0.2.5" 27 | tracing-subscriber = "0.3.14" 28 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 29 | affix = "0.1.2" 30 | prometheus = "0.12" 31 | rusoto_s3 = "0.48.0" 32 | rusoto_core = "0.48.0" 33 | 34 | nomad-xyz-configuration = { path = "../../configuration" } 35 | nomad-types = { path = "../../nomad-types" } 36 | nomad-core = { path = "../../nomad-core" } 37 | nomad-base = { path = "../../nomad-base" } 38 | 39 | [dev-dependencies] 40 | nomad-test = { path = "../../nomad-test" } 41 | dotenv = "0.15.0" 42 | serial_test = "0.6.0" -------------------------------------------------------------------------------- /agents/processor/README.md: -------------------------------------------------------------------------------- 1 | ## Processor Agent 2 | 3 | The processor proves the validity of pending messages and sends them to end recipients. 4 | 5 | It is an off-chain actor that does the following: 6 | 7 | - Observe the home 8 | - Maintain local merkle tree with all leaves 9 | - Observe 1 or more replicas 10 | - Maintain list of messages corresponding to each leaf 11 | - Generate and submit merkle proofs for pending (unproven) messages 12 | - Dispatch proven messages to end recipients 13 | -------------------------------------------------------------------------------- /agents/processor/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The processor observes replicas for updates and proves + processes them 2 | //! 3 | //! At a regular interval, the processor polls Replicas for updates. 4 | //! If there are updates, the processor submits a proof of their 5 | //! validity and processes on the Replica's chain 6 | 7 | #![forbid(unsafe_code)] 8 | #![warn(missing_docs)] 9 | #![warn(unused_extern_crates)] 10 | 11 | mod processor; 12 | mod prover_sync; 13 | mod push; 14 | mod settings; 15 | 16 | use color_eyre::Result; 17 | use tracing::info_span; 18 | 19 | use crate::{processor::Processor, settings::ProcessorSettings as Settings}; 20 | use nomad_base::NomadAgent; 21 | 22 | use tracing_subscriber::prelude::*; 23 | 24 | #[tokio::main(flavor = "current_thread")] 25 | async fn main() -> Result<()> { 26 | color_eyre::install()?; 27 | 28 | // sets the subscriber for this scope only 29 | let _bootup_guard = tracing_subscriber::FmtSubscriber::builder() 30 | .json() 31 | .with_level(true) 32 | .set_default(); 33 | 34 | let span = info_span!("ProcessorBootup"); 35 | let _span = span.enter(); 36 | 37 | let settings = Settings::new().await?; 38 | let agent = Processor::from_settings(settings).await?; 39 | 40 | drop(_span); 41 | drop(span); 42 | 43 | let _tracing_guard = agent.start_tracing(agent.metrics().span_duration()); 44 | 45 | let _ = agent.metrics().run_http_server(); 46 | 47 | agent.run_all().await??; 48 | Ok(()) 49 | } 50 | -------------------------------------------------------------------------------- /agents/processor/src/settings.rs: -------------------------------------------------------------------------------- 1 | //! Configuration 2 | 3 | use nomad_base::decl_settings; 4 | use nomad_xyz_configuration::agent::processor::ProcessorConfig; 5 | 6 | decl_settings!(Processor, ProcessorConfig,); 7 | 8 | #[cfg(test)] 9 | mod test { 10 | use super::*; 11 | use nomad_base::{get_remotes_from_env, NomadAgent}; 12 | use nomad_test::test_utils; 13 | use nomad_xyz_configuration::AgentSecrets; 14 | 15 | #[tokio::test] 16 | #[serial_test::serial] 17 | async fn it_builds_settings_from_env() { 18 | test_utils::run_test_with_env("../../fixtures/env.test", || async move { 19 | let run_env = dotenv::var("RUN_ENV").unwrap(); 20 | let agent_home = dotenv::var("AGENT_HOME_NAME").unwrap(); 21 | 22 | let settings = ProcessorSettings::new().await.unwrap(); 23 | 24 | let config = nomad_xyz_configuration::get_builtin(&run_env).unwrap(); 25 | 26 | let remotes = get_remotes_from_env!(agent_home, config); 27 | let mut networks = remotes.clone(); 28 | networks.insert(agent_home.clone()); 29 | 30 | let secrets = AgentSecrets::from_env(&networks).unwrap(); 31 | 32 | settings 33 | .base 34 | .validate_against_config_and_secrets( 35 | crate::Processor::AGENT_NAME, 36 | &agent_home, 37 | &remotes, 38 | config, 39 | &secrets, 40 | ) 41 | .unwrap(); 42 | 43 | let agent_config = &config.agent().get("ethereum").unwrap().processor; 44 | assert_eq!(settings.agent.interval, agent_config.interval); 45 | assert_eq!(settings.agent.allowed, agent_config.allowed); 46 | assert_eq!(settings.agent.denied, agent_config.denied); 47 | assert_eq!( 48 | settings.agent.subsidized_remotes, 49 | agent_config.subsidized_remotes 50 | ); 51 | assert_eq!(settings.agent.s3, agent_config.s3); 52 | }) 53 | .await 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /agents/relayer/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - use `std::fmt::Display` to log contracts 6 | - fix: instrument futures, not joinhandles 7 | 8 | ### agents@1.1.0 9 | 10 | - make \*Settings::new async for optionally fetching config from a remote url 11 | - relayer checks replica updater addresses match, errors channel if otherwise 12 | - add bootup-only tracing subscriber 13 | - add environment variable overrides for agent configuration 14 | - add tests for agent environment variable overrides 15 | - remove `enabled` flag from agents project-wide 16 | 17 | ### agents@1.0.0 18 | 19 | - bumps version for first release 20 | - adds a changelog 21 | -------------------------------------------------------------------------------- /agents/relayer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "relayer" 3 | version = "1.1.0" 4 | authors = ["Luke Tchang ", "James Prestwich "] 5 | edition = "2021" 6 | description = "Nomad relayer agent" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | [dependencies] 15 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 16 | config = "0.10" 17 | serde = "1.0.120" 18 | serde_json = { version = "1.0.61", default-features = false } 19 | log = "0.4.13" 20 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 21 | thiserror = { version = "1.0.22", default-features = false } 22 | async-trait = { version = "0.1.42", default-features = false } 23 | futures-util = "0.3.12" 24 | color-eyre = "0.6.0" 25 | tracing = "0.1.35" 26 | tracing-futures = "0.2.5" 27 | tracing-subscriber = "0.3.14" 28 | nomad-xyz-configuration = { path = "../../configuration" } 29 | 30 | nomad-core = { path = "../../nomad-core" } 31 | nomad-base = { path = "../../nomad-base" } 32 | affix = "0.1.2" 33 | 34 | prometheus = "0.12" 35 | 36 | [dev-dependencies] 37 | tokio-test = "0.4.0" 38 | serial_test = "0.6.0" 39 | nomad-test = { path = "../../nomad-test" } 40 | dotenv = "0.15.0" -------------------------------------------------------------------------------- /agents/relayer/README.md: -------------------------------------------------------------------------------- 1 | ## Relayer Agent 2 | 3 | The relayer forwards updates from the home to one or more replicas. 4 | 5 | It is an off-chain actor that does the following: 6 | 7 | - Observe the home 8 | - Observe 1 or more replicas 9 | - Polls home for new signed updates (since replica's current root) and submits them to replica 10 | - Polls replica for confirmable updates (that have passed their optimistic time window) and confirms if available (updating replica's current root) 11 | -------------------------------------------------------------------------------- /agents/relayer/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The relayer forwards signed updates from the home to chain to replicas 2 | //! 3 | //! At a regular interval, the relayer polls Home for signed updates and 4 | //! submits them as updates with a pending timelock on the replica. 5 | 6 | #![forbid(unsafe_code)] 7 | #![warn(missing_docs)] 8 | #![warn(unused_extern_crates)] 9 | 10 | mod relayer; 11 | mod settings; 12 | 13 | use crate::{relayer::Relayer, settings::RelayerSettings as Settings}; 14 | use color_eyre::Result; 15 | use nomad_base::NomadAgent; 16 | 17 | use tracing::info_span; 18 | use tracing_subscriber::prelude::*; 19 | 20 | #[tokio::main(flavor = "current_thread")] 21 | async fn main() -> Result<()> { 22 | color_eyre::install()?; 23 | 24 | // sets the subscriber for this scope only 25 | let _bootup_guard = tracing_subscriber::FmtSubscriber::builder() 26 | .json() 27 | .with_level(true) 28 | .set_default(); 29 | 30 | let span = info_span!("RelayerBootup"); 31 | let _span = span.enter(); 32 | 33 | let settings = Settings::new().await?; 34 | let agent = Relayer::from_settings(settings).await?; 35 | 36 | drop(_span); 37 | drop(span); 38 | 39 | let _tracing_guard = agent.start_tracing(agent.metrics().span_duration()); 40 | 41 | let _ = agent.metrics().run_http_server(); 42 | 43 | agent.run_all().await??; 44 | Ok(()) 45 | } 46 | -------------------------------------------------------------------------------- /agents/relayer/src/settings.rs: -------------------------------------------------------------------------------- 1 | //! Configuration 2 | 3 | use nomad_base::decl_settings; 4 | use nomad_xyz_configuration::agent::relayer::RelayerConfig; 5 | 6 | decl_settings!(Relayer, RelayerConfig,); 7 | 8 | #[cfg(test)] 9 | mod test { 10 | use super::*; 11 | use nomad_base::{get_remotes_from_env, NomadAgent}; 12 | use nomad_test::test_utils; 13 | use nomad_xyz_configuration::AgentSecrets; 14 | 15 | #[tokio::test] 16 | #[serial_test::serial] 17 | async fn it_builds_settings_from_env() { 18 | test_utils::run_test_with_env("../../fixtures/env.test", || async move { 19 | let run_env = dotenv::var("RUN_ENV").unwrap(); 20 | let agent_home = dotenv::var("AGENT_HOME_NAME").unwrap(); 21 | 22 | let settings = RelayerSettings::new().await.unwrap(); 23 | 24 | let config = nomad_xyz_configuration::get_builtin(&run_env).unwrap(); 25 | 26 | let remotes = get_remotes_from_env!(agent_home, config); 27 | let mut networks = remotes.clone(); 28 | networks.insert(agent_home.clone()); 29 | 30 | let secrets = AgentSecrets::from_env(&networks).unwrap(); 31 | 32 | settings 33 | .base 34 | .validate_against_config_and_secrets( 35 | crate::Relayer::AGENT_NAME, 36 | &agent_home, 37 | &remotes, 38 | config, 39 | &secrets, 40 | ) 41 | .unwrap(); 42 | 43 | let interval = config.agent().get("ethereum").unwrap().relayer.interval; 44 | assert_eq!(settings.agent.interval, interval); 45 | }) 46 | .await 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /agents/updater/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - fix: instrument futures, not joinhandles 6 | 7 | ### agents@1.1.0 8 | 9 | - make \*Settings::new async for optionally fetching config from a remote url 10 | - add bootup-only tracing subscriber 11 | - add environment variable overrides for agent configuration 12 | - add tests for agent environment variable overrides 13 | - remove `enabled` flag from agents project-wide 14 | 15 | ### agents@1.0.0 16 | 17 | - bumps version for first release 18 | - adds a changelog 19 | -------------------------------------------------------------------------------- /agents/updater/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "updater" 3 | version = "1.1.0" 4 | authors = ["James Prestwich ", "Luke Tchang "] 5 | edition = "2021" 6 | description = "Nomad updater agent" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | [dependencies] 15 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 16 | config = "0.11.0" 17 | serde = "1.0.120" 18 | serde_json = { version = "1.0.61", default-features = false } 19 | log = "0.4.13" 20 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 21 | thiserror = { version = "1.0.22", default-features = false } 22 | async-trait = { version = "0.1.42", default-features = false } 23 | futures-util = "0.3.12" 24 | color-eyre = "0.6.0" 25 | tracing = "0.1.35" 26 | tracing-futures = "0.2.5" 27 | tracing-subscriber = "0.3.14" 28 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 29 | nomad-xyz-configuration = { path = "../../configuration" } 30 | 31 | nomad-core = { path = "../../nomad-core" } 32 | nomad-base = { path = "../../nomad-base" } 33 | nomad-ethereum = { path = "../../chains/nomad-ethereum" } 34 | affix = "0.1.2" 35 | 36 | prometheus = "0.12" 37 | warp = "0.3" 38 | hex = "0.4.3" 39 | 40 | [dev-dependencies] 41 | mockall = "0.9.1" 42 | nomad-test = { path = "../../nomad-test" } 43 | dotenv = "0.15.0" 44 | serial_test = "0.6.0" -------------------------------------------------------------------------------- /agents/updater/README.md: -------------------------------------------------------------------------------- 1 | ## Updater Agent 2 | 3 | The updater is responsible for signing attestations of new roots. 4 | 5 | It is an off-chain actor that does the following: 6 | 7 | - Observe the home chain contract 8 | - Sign attestations to new roots 9 | - Publish the signed attestation to the home chain 10 | -------------------------------------------------------------------------------- /agents/updater/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The updater signs updates and submits them to the home chain. 2 | //! 3 | //! This updater polls the Home for queued updates at a regular interval. 4 | //! It signs them and submits them back to the home chain. 5 | 6 | #![forbid(unsafe_code)] 7 | #![warn(missing_docs)] 8 | #![warn(unused_extern_crates)] 9 | 10 | mod produce; 11 | mod settings; 12 | mod submit; 13 | mod updater; 14 | 15 | use crate::{settings::UpdaterSettings as Settings, updater::Updater}; 16 | use color_eyre::Result; 17 | use nomad_base::NomadAgent; 18 | 19 | use tracing::info_span; 20 | use tracing_subscriber::prelude::*; 21 | 22 | #[tokio::main(flavor = "current_thread")] 23 | async fn main() -> Result<()> { 24 | color_eyre::install()?; 25 | 26 | // sets the subscriber for this scope only 27 | let _bootup_guard = tracing_subscriber::FmtSubscriber::builder() 28 | .json() 29 | .with_level(true) 30 | .set_default(); 31 | 32 | let span = info_span!("UpdaterBootup"); 33 | let _span = span.enter(); 34 | 35 | let settings = Settings::new().await?; 36 | let agent = Updater::from_settings(settings).await?; 37 | 38 | drop(_span); 39 | drop(span); 40 | 41 | let _tracing_guard = agent.start_tracing(agent.metrics().span_duration()); 42 | 43 | let _ = agent.metrics().run_http_server(); 44 | 45 | agent.run_all().await??; 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /agents/updater/src/settings.rs: -------------------------------------------------------------------------------- 1 | //! Configuration 2 | use nomad_base::decl_settings; 3 | use nomad_xyz_configuration::agent::updater::UpdaterConfig; 4 | 5 | decl_settings!(Updater, UpdaterConfig,); 6 | 7 | #[cfg(test)] 8 | mod test { 9 | use super::*; 10 | use nomad_base::{get_remotes_from_env, NomadAgent}; 11 | use nomad_test::test_utils; 12 | use nomad_xyz_configuration::AgentSecrets; 13 | 14 | #[tokio::test] 15 | #[serial_test::serial] 16 | async fn it_builds_settings_from_env() { 17 | test_utils::run_test_with_env("../../fixtures/env.test", || async move { 18 | let run_env = dotenv::var("RUN_ENV").unwrap(); 19 | let agent_home = dotenv::var("AGENT_HOME_NAME").unwrap(); 20 | 21 | let settings = UpdaterSettings::new().await.unwrap(); 22 | 23 | let config = nomad_xyz_configuration::get_builtin(&run_env).unwrap(); 24 | 25 | let remotes = get_remotes_from_env!(agent_home, config); 26 | let mut networks = remotes.clone(); 27 | networks.insert(agent_home.clone()); 28 | 29 | let secrets = AgentSecrets::from_env(&networks).unwrap(); 30 | 31 | settings 32 | .base 33 | .validate_against_config_and_secrets( 34 | crate::Updater::AGENT_NAME, 35 | &agent_home, 36 | &remotes, 37 | config, 38 | &secrets, 39 | ) 40 | .unwrap(); 41 | 42 | let interval = config.agent().get("ethereum").unwrap().updater.interval; 43 | assert_eq!(settings.agent.interval, interval); 44 | assert_eq!(settings.base.attestation_signer, secrets.attestation_signer); 45 | }) 46 | .await 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /agents/updater/src/submit.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | 3 | use nomad_base::{CachingHome, NomadDB}; 4 | use nomad_core::Common; 5 | use prometheus::IntCounter; 6 | use std::time::Duration; 7 | 8 | use color_eyre::Result; 9 | use tokio::{task::JoinHandle, time::sleep}; 10 | use tracing::{info, info_span, Instrument}; 11 | 12 | pub(crate) struct UpdateSubmitter { 13 | home: Arc, 14 | db: NomadDB, 15 | interval_seconds: u64, 16 | finalization_seconds: u64, 17 | submitted_update_count: IntCounter, 18 | } 19 | 20 | impl UpdateSubmitter { 21 | pub(crate) fn new( 22 | home: Arc, 23 | db: NomadDB, 24 | interval_seconds: u64, 25 | finalization_seconds: u64, 26 | submitted_update_count: IntCounter, 27 | ) -> Self { 28 | Self { 29 | home, 30 | db, 31 | interval_seconds, 32 | finalization_seconds, 33 | submitted_update_count, 34 | } 35 | } 36 | 37 | pub(crate) fn spawn(self) -> JoinHandle> { 38 | let span = info_span!("UpdateSubmitter"); 39 | 40 | tokio::spawn(async move { 41 | // start from the chain state 42 | let mut committed_root = self.home.committed_root().await?; 43 | 44 | loop { 45 | sleep(Duration::from_secs(self.interval_seconds)).await; 46 | 47 | // if we have produced an update building off the committed root 48 | // submit it 49 | if let Some(signed) = self.db.retrieve_produced_update(committed_root)? { 50 | let hex_signature = format!("0x{}", hex::encode(signed.signature.to_vec())); 51 | info!( 52 | previous_root = ?signed.update.previous_root, 53 | new_root = ?signed.update.new_root, 54 | hex_signature = %hex_signature, 55 | "Submitting update to chain" 56 | ); 57 | 58 | // Submit update and let the home indexer pick up the 59 | // update once it is confirmed state in the chain 60 | let tx = self.home.update(&signed).await?; 61 | 62 | self.submitted_update_count.inc(); 63 | 64 | // Continue from local state 65 | committed_root = signed.update.new_root; 66 | 67 | // Sleep for finality x blocktime seconds to wait for 68 | // timelag reader to catch up 69 | info!( 70 | tx_hash = ?tx.txid, 71 | sleep = self.finalization_seconds, 72 | "Submitted update with tx hash {:?}. Sleeping before next tx submission.", tx.txid, 73 | ); 74 | sleep(Duration::from_secs(self.finalization_seconds)).await; 75 | } else { 76 | info!( 77 | committed_root = ?committed_root, 78 | "No produced update to submit for committed_root.", 79 | ) 80 | } 81 | } 82 | } 83 | .instrument(span) 84 | ) 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /agents/watcher/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - Add English description to XCM error log, change to use `Display` 6 | - fix: instrument futures, not joinhandles 7 | 8 | ### agents@1.1.0 9 | 10 | - make \*Settings::new async for optionally fetching config from a remote url 11 | - add bootup-only tracing subscriber 12 | - add environment variable overrides for agent configuration 13 | - add tests for agent environment variable overrides 14 | - remove `enabled` flag from agents project-wide 15 | 16 | ### agents@1.0.0 17 | 18 | - bumps version for first release 19 | - adds a changelog 20 | -------------------------------------------------------------------------------- /agents/watcher/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "watcher" 3 | version = "1.1.0" 4 | authors = ["Luke Tchang ", "James Prestwich "] 5 | edition = "2021" 6 | description = "Nomad watcher agent" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | [dependencies] 15 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 16 | config = "0.10" 17 | serde = "1.0.120" 18 | serde_json = { version = "1.0.61", default-features = false } 19 | log = "0.4.13" 20 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 21 | thiserror = { version = "1.0.22", default-features = false } 22 | async-trait = { version = "0.1.42", default-features = false } 23 | futures-util = "0.3.12" 24 | color-eyre = "0.6.0" 25 | tracing = "0.1.35" 26 | tracing-futures = "0.2.5" 27 | tracing-subscriber = "0.3.14" 28 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 29 | prometheus = "0.12" 30 | nomad-xyz-configuration = { path = "../../configuration" } 31 | 32 | nomad-core = { path = "../../nomad-core" } 33 | nomad-base = { path = "../../nomad-base" } 34 | nomad-ethereum = { path = "../../chains/nomad-ethereum" } 35 | affix = "0.1.2" 36 | 37 | [dev-dependencies] 38 | tokio-test = "0.4.0" 39 | nomad-test = { path = "../../nomad-test" } 40 | dotenv = "0.15.0" 41 | serial_test = "0.6.0" -------------------------------------------------------------------------------- /agents/watcher/README.md: -------------------------------------------------------------------------------- 1 | ## Watcher Agent 2 | 3 | The watcher observes the Updater's interactions with the Home contract (by watching the Home contract) and reacts to malicious or faulty attestations. It also observes any number of replicas to ensure the Updater does not bypass the Home and go straight to a replica. 4 | 5 | It is an off-chain actor that does the following: 6 | 7 | - Observe the home 8 | - Observe 1 or more replicas 9 | - Maintain a DB of seen updates 10 | - Submit double-update proofs 11 | - Submit invalid update proofs 12 | - If configured, issue an emergency halt transaction 13 | -------------------------------------------------------------------------------- /agents/watcher/src/main.rs: -------------------------------------------------------------------------------- 1 | //! The watcher observes the home and replicas for double update fraud. 2 | //! 3 | //! At a regular interval, the watcher polls Home and Replicas for signed 4 | //! updates and checks them against its local DB of updates for fraud. It 5 | //! checks for double updates on both the Home and Replicas and fraudulent 6 | //! updates on just the Replicas by verifying Replica updates on the Home. 7 | 8 | #![forbid(unsafe_code)] 9 | #![warn(missing_docs)] 10 | #![warn(unused_extern_crates)] 11 | 12 | mod settings; 13 | mod watcher; 14 | 15 | use crate::{settings::WatcherSettings as Settings, watcher::Watcher}; 16 | use color_eyre::Result; 17 | use nomad_base::NomadAgent; 18 | 19 | use tracing::info_span; 20 | use tracing_subscriber::prelude::*; 21 | 22 | #[tokio::main(flavor = "current_thread")] 23 | async fn main() -> Result<()> { 24 | color_eyre::install()?; 25 | 26 | // sets the subscriber for this scope only 27 | let _bootup_guard = tracing_subscriber::FmtSubscriber::builder() 28 | .json() 29 | .with_level(true) 30 | .set_default(); 31 | 32 | let span = info_span!("WatcherBootup"); 33 | let _span = span.enter(); 34 | 35 | let settings = Settings::new().await?; 36 | let agent = Watcher::from_settings(settings).await?; 37 | 38 | drop(_span); 39 | drop(span); 40 | 41 | let _tracing_guard = agent.start_tracing(agent.metrics().span_duration()); 42 | 43 | let _ = agent.metrics().run_http_server(); 44 | 45 | agent.run_all().await??; 46 | Ok(()) 47 | } 48 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | docker build -t gcr.io/nomad-xyz/nomad-agent:$1 . -------------------------------------------------------------------------------- /chains/README.md: -------------------------------------------------------------------------------- 1 | To add a new chain: 2 | 3 | - add a new crate to this directory, implementing the appropriate traits. 4 | - add a new enum variant in `nomad_base::Homes` using the new crate and fix all the compilation errors. 5 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - Add `EthereumError` error enum to wrap ethers and gelato errors (ethereum-specific) 6 | - Make existing contract and indexer methods return `Result<_, EthereumError>` now instead of using old `nomad_core::ChainCommunicationError` 7 | - impl `std::fmt::Display` for `EthereumHome` and `EthereumReplica` 8 | - use gelato-sdk as a github dep rather than a crate 9 | - adds a changelog 10 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-ethereum" 3 | version = "0.1.0" 4 | authors = ["Erin Hales "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | # Main block 11 | serde = "1.0.120" 12 | serde_json = { version = "1.0.61", default-features = false } 13 | async-trait = { version = "0.1.42", default-features = false } 14 | tracing = "0.1.35" 15 | color-eyre = "0.6.0" 16 | anyhow = "1" 17 | num = "0.4" 18 | tokio = "1.7.1" 19 | hex = "0.4.3" 20 | prometheus = "0.12" 21 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 22 | futures-util = "0.3.12" 23 | tracing-futures = "0.2.5" 24 | url = "2.2.2" 25 | thiserror = "1.0.30" 26 | reqwest = { version = "0.11.10", features = ["json"]} 27 | once_cell = "1.8.0" 28 | 29 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["abigen"] } 30 | ethers-core = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 31 | ethers-providers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 32 | ethers-signers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["aws"] } 33 | ethers-contract = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features=["legacy"] } 34 | 35 | gelato-sdk = { git = "https://github.com/nomad-xyz/gelato-sdk", branch = "main" } 36 | 37 | nomad-xyz-configuration = { path = "../../configuration" } 38 | nomad-types = { path = "../../nomad-types" } 39 | nomad-core = { path = "../../nomad-core" } 40 | 41 | [build-dependencies] 42 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["abigen"] } 43 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/build.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | ffi::OsStr, 3 | fs::File, 4 | io::Write, 5 | path::{Path, PathBuf}, 6 | }; 7 | 8 | use ethers::contract::Abigen; 9 | 10 | static ABI_DIR: &str = "./abis"; 11 | static BINDINGS_DIR: &str = "./src/bindings"; 12 | 13 | fn main() { 14 | println!("cargo:rerun-if-changed={}", ABI_DIR); 15 | 16 | clean_old_bindings(); 17 | 18 | let mut mod_file = create_mod_rs(); 19 | 20 | let mut names: Vec = std::fs::read_dir(ABI_DIR) 21 | .expect("could not read ABI folder") 22 | .filter_map(Result::ok) 23 | .map(|entry| entry.path()) 24 | .filter(|path| path.extension().and_then(OsStr::to_str) == Some("json")) 25 | .map(|contract_path| { 26 | println!("Generating bindings for {:?}", &contract_path); 27 | bindgen(&contract_path) 28 | }) 29 | .collect(); 30 | 31 | // generate modfile in happy alphabetical order 32 | names.sort(); 33 | 34 | for name in names.iter() { 35 | writeln!(mod_file, "pub(crate) mod {};", name).expect("failed to write to modfile"); 36 | } 37 | } 38 | 39 | fn create_mod_rs() -> File { 40 | let mod_file_path = PathBuf::from(&format!("{}/mod.rs", BINDINGS_DIR)); 41 | let mut mod_file = std::fs::File::create(&mod_file_path).expect("could not create modfile"); 42 | writeln!(mod_file, "#![allow(clippy::all)]").unwrap(); 43 | mod_file 44 | } 45 | 46 | fn clean_old_bindings() { 47 | std::fs::remove_dir_all(BINDINGS_DIR).expect("could not delete old bindings"); 48 | std::fs::create_dir_all(BINDINGS_DIR).expect("could not create bindings dir"); 49 | } 50 | 51 | fn bindgen(contract_path: &Path) -> String { 52 | println!("path {:?}", contract_path); 53 | // contract name is the first 54 | let contract_name = contract_path 55 | .file_name() 56 | .and_then(OsStr::to_str) 57 | .expect("conract filename not valid unicode stop doing dumb stuff.") 58 | .split('.') 59 | .next() 60 | .expect("missing extension in path"); 61 | 62 | let module_name = contract_name.to_lowercase(); 63 | 64 | let bindings = Abigen::new( 65 | contract_name, 66 | contract_path.to_str().expect("valid utf8 path"), 67 | ) 68 | .expect("could not instantiate Abigen") 69 | .generate() 70 | .expect("could not generate bindings"); 71 | 72 | bindings 73 | .write_to_file(format!("./src/bindings/{}.rs", &module_name)) 74 | .expect("could not write bindings to file"); 75 | 76 | module_name 77 | } 78 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/src/bindings/mod.rs: -------------------------------------------------------------------------------- 1 | #![allow(clippy::all)] 2 | pub(crate) mod home; 3 | pub(crate) mod replica; 4 | pub(crate) mod xappconnectionmanager; 5 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::gelato::GelatoError; 2 | use ethers::core::types::H256; 3 | use ethers::prelude::{ContractError, Middleware, ProviderError}; 4 | use std::error::Error as StdError; 5 | 6 | /// Ethereum-specific error wrapper 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum EthereumError { 9 | /// Ethers provider error 10 | #[error("{0}")] 11 | ProviderError(#[from] ProviderError), 12 | /// Ethers contract error 13 | #[error("{0}")] 14 | ContractError(Box), 15 | /// Middleware error 16 | #[error("{0}")] 17 | MiddlewareError(Box), 18 | /// Gelato client error 19 | #[error("{0}")] 20 | GelatoError(#[from] GelatoError), 21 | /// A transaction was dropped from the mempool 22 | #[error("Transaction dropped from mempool {0:?}")] 23 | DroppedError(H256), 24 | /// Transaction was not executed successfully 25 | #[error("Transaction was not executed successfully {0:?}")] 26 | TxNotExecuted(H256), 27 | /// Any other error 28 | #[error("{0}")] 29 | CustomError(#[from] Box), 30 | } 31 | 32 | impl From> for EthereumError 33 | where 34 | M: Middleware + 'static, 35 | { 36 | fn from(e: ContractError) -> Self { 37 | Self::ContractError(e.into()) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/src/gas.rs: -------------------------------------------------------------------------------- 1 | use ethers::providers::{FromErr, Middleware}; 2 | use ethers::types::{transaction::eip2718::TypedTransaction, BlockId, U256}; 3 | use std::fmt; 4 | use thiserror::Error; 5 | 6 | /// Closure that will be used for gas calculation. Takes existing gas 7 | type GasPolicy = Box U256 + Send + Sync>; 8 | 9 | /// Middleware used for adjusting gas using predefined policy 10 | pub struct GasAdjusterMiddleware { 11 | inner: M, 12 | gas_price_policy: GasPolicy, 13 | } 14 | 15 | impl fmt::Debug for GasAdjusterMiddleware 16 | where 17 | M: Middleware, 18 | { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | f.debug_struct("GasAdjusterMiddleware") 21 | .field("inner", &self.inner) 22 | .finish() 23 | } 24 | } 25 | 26 | impl GasAdjusterMiddleware 27 | where 28 | M: Middleware, 29 | { 30 | /// Instantiates the gas multiplier middleware. Policy takes gas 31 | /// estimate to calculates new gas which will be used for transaction 32 | pub fn new(inner: M, gas_price_policy: GasPolicy) -> Self { 33 | Self { 34 | inner, 35 | gas_price_policy, 36 | } 37 | } 38 | 39 | pub fn with_default_policy(inner: M, chain_id: u64) -> Self { 40 | // 1.5x gas price for ethereum, 2x elsewhere 41 | let is_ethereum = chain_id == 1; 42 | let gas_price_policy = move |price| { 43 | if is_ethereum { 44 | price + price / 2 45 | } else { 46 | price * 2 47 | } 48 | }; 49 | 50 | Self::new(inner, Box::new(gas_price_policy)) 51 | } 52 | } 53 | 54 | #[derive(Error, Debug)] 55 | /// Thrown when an error happens at the Gas Multiplier Middleware 56 | pub enum GasAdjusterMiddlewareError { 57 | /// Thrown when the internal middleware errors 58 | #[error("{0}")] 59 | MiddlewareError(M::Error), 60 | } 61 | 62 | /// Convert inner Middleware error into GasAdjusterMiddlewareError 63 | impl FromErr for GasAdjusterMiddlewareError { 64 | fn from(src: M::Error) -> Self { 65 | GasAdjusterMiddlewareError::MiddlewareError(src) 66 | } 67 | } 68 | 69 | #[async_trait::async_trait] 70 | impl Middleware for GasAdjusterMiddleware 71 | where 72 | M: Middleware, 73 | { 74 | type Error = GasAdjusterMiddlewareError; 75 | type Provider = M::Provider; 76 | type Inner = M; 77 | 78 | fn inner(&self) -> &M { 79 | &self.inner 80 | } 81 | 82 | async fn fill_transaction( 83 | &self, 84 | tx: &mut TypedTransaction, 85 | block: Option, 86 | ) -> Result<(), Self::Error> { 87 | self.inner 88 | .fill_transaction(tx, block) 89 | .await 90 | .map_err(FromErr::from)?; 91 | 92 | let adjusted_price = self.get_gas_price().await?; 93 | tx.set_gas_price(adjusted_price); 94 | 95 | Ok(()) 96 | } 97 | 98 | async fn get_gas_price(&self) -> Result { 99 | self.inner() 100 | .get_gas_price() 101 | .await 102 | .map(&self.gas_price_policy) 103 | .map_err(FromErr::from) 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Interfaces to the ethereum contracts 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn(missing_docs)] 5 | #![warn(unused_extern_crates)] 6 | 7 | use color_eyre::eyre::Result; 8 | use ethers::prelude::*; 9 | use nomad_core::*; 10 | use nomad_xyz_configuration::{ 11 | Connection, ConnectionManagerGasLimits, HomeGasLimits, ReplicaGasLimits, 12 | }; 13 | use num::Num; 14 | use std::sync::Arc; 15 | 16 | #[macro_use] 17 | mod macros; 18 | 19 | /// Errors 20 | mod error; 21 | pub use error::*; 22 | 23 | /// Retrying Provider 24 | mod retrying; 25 | pub use retrying::{RetryingProvider, RetryingProviderError}; 26 | 27 | /// Gelato client types 28 | mod gelato; 29 | pub use gelato::*; 30 | 31 | /// Chain submitter 32 | mod submitter; 33 | pub use submitter::*; 34 | 35 | /// EthereumSigners 36 | mod signer; 37 | pub use signer::*; 38 | 39 | /// Contract binding 40 | #[cfg(not(doctest))] 41 | pub(crate) mod bindings; 42 | 43 | /// Home abi 44 | #[cfg(not(doctest))] 45 | mod home; 46 | 47 | /// Replica abi 48 | #[cfg(not(doctest))] 49 | mod replica; 50 | 51 | /// XAppConnectionManager abi 52 | #[cfg(not(doctest))] 53 | mod xapp; 54 | 55 | /// Gas increasing Middleware 56 | mod gas; 57 | 58 | /// Utilities 59 | mod utils; 60 | 61 | #[cfg(not(doctest))] 62 | pub use crate::{home::*, replica::*, xapp::*}; 63 | 64 | #[allow(dead_code)] 65 | /// A live connection to an ethereum-compatible chain. 66 | pub struct Chain { 67 | creation_metadata: Connection, 68 | ethers: ethers::providers::Provider, 69 | } 70 | 71 | boxed_indexer!( 72 | make_home_indexer, 73 | EthereumHomeIndexer, 74 | HomeIndexer, 75 | ); 76 | boxed_indexer!( 77 | make_replica_indexer, 78 | EthereumReplicaIndexer, 79 | CommonIndexer, 80 | ); 81 | 82 | boxed_contract!( 83 | make_home, 84 | EthereumHome, 85 | Home, 86 | gas: Option 87 | ); 88 | boxed_contract!( 89 | make_replica, 90 | EthereumReplica, 91 | Replica, 92 | gas: Option 93 | ); 94 | boxed_contract!( 95 | make_conn_manager, 96 | EthereumConnectionManager, 97 | ConnectionManager, 98 | gas: Option 99 | ); 100 | 101 | #[async_trait::async_trait] 102 | impl nomad_core::Chain for Chain { 103 | async fn query_balance(&self, addr: nomad_core::Address) -> Result { 104 | let balance = format!( 105 | "{:x}", 106 | self.ethers 107 | .get_balance( 108 | NameOrAddress::Address(H160::from_slice(&addr.0[..])), 109 | Some(BlockId::Number(BlockNumber::Latest)) 110 | ) 111 | .await? 112 | ); 113 | 114 | Ok(nomad_core::Balance(num::BigInt::from_str_radix( 115 | &balance, 16, 116 | )?)) 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/src/submitter.rs: -------------------------------------------------------------------------------- 1 | use crate::{EthereumError, SingleChainGelatoClient}; 2 | use color_eyre::Result; 3 | use ethers::prelude::*; 4 | use ethers::types::transaction::eip2718::TypedTransaction; 5 | use nomad_core::TxOutcome; 6 | use std::sync::Arc; 7 | 8 | /// Component responsible for submitting transactions to the chain. Can 9 | /// sign/submit locally or use a transaction relay service. 10 | #[derive(Debug, Clone)] 11 | pub enum SubmitterClient { 12 | /// Sign/submit txs locally 13 | Local(Arc), 14 | /// Pass meta txs to Gelato relay service 15 | Gelato(Arc>), 16 | } 17 | 18 | impl From> for SubmitterClient { 19 | fn from(client: Arc) -> Self { 20 | Self::Local(client) 21 | } 22 | } 23 | 24 | impl From> for SubmitterClient { 25 | fn from(client: SingleChainGelatoClient) -> Self { 26 | Self::Gelato(client.into()) 27 | } 28 | } 29 | 30 | /// Chain submitter 31 | #[derive(Debug)] 32 | pub struct TxSubmitter { 33 | /// Tx submitter client 34 | pub client: SubmitterClient, 35 | } 36 | 37 | impl TxSubmitter 38 | where 39 | M: Middleware + 'static, 40 | { 41 | /// Create new TxSubmitter from submitter 42 | pub fn new(client: SubmitterClient) -> Self { 43 | Self { client } 44 | } 45 | 46 | /// Submit transaction to chain 47 | pub async fn submit( 48 | &self, 49 | domain: u32, 50 | contract_address: Address, 51 | tx: impl Into, 52 | ) -> Result { 53 | let tx: TypedTransaction = tx.into(); 54 | 55 | match &self.client { 56 | SubmitterClient::Local(client) => report_tx!(tx, client,), 57 | SubmitterClient::Gelato(client) => Ok(client 58 | .submit_blocking(domain, contract_address, &tx) 59 | .await?), 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /chains/nomad-ethereum/src/utils.rs: -------------------------------------------------------------------------------- 1 | use ethers::prelude::TransactionReceipt; 2 | use nomad_core::TxOutcome; 3 | 4 | use crate::EthereumError; 5 | 6 | /// Try to convert ethers `TransactionReceipt` into `TxOutcome`. We use this 7 | /// function instead of `From for TxOutcome` because 8 | /// TxOutcome belongs to `nomad-core`. 9 | pub fn try_transaction_receipt_to_tx_outcome( 10 | receipt: TransactionReceipt, 11 | ) -> Result { 12 | if receipt.status.unwrap().low_u32() == 1 { 13 | Ok(TxOutcome { 14 | txid: receipt.transaction_hash, 15 | }) 16 | } else { 17 | Err(EthereumError::TxNotExecuted(receipt.transaction_hash)) 18 | } 19 | } 20 | 21 | #[cfg(test)] 22 | mod test { 23 | use ethers::prelude::{TransactionReceipt, U64}; 24 | 25 | use super::*; 26 | 27 | #[tokio::test] 28 | async fn turning_transaction_receipt_into_tx_outcome() { 29 | let receipt = TransactionReceipt { 30 | status: Some(U64::from(0)), 31 | ..Default::default() 32 | }; 33 | let tx_outcome: Result = 34 | try_transaction_receipt_to_tx_outcome(receipt); 35 | assert!( 36 | tx_outcome.is_err(), 37 | "Turning failed transaction receipt into errored tx outcome not succeeded" 38 | ); 39 | 40 | let receipt = TransactionReceipt { 41 | status: Some(U64::from(1)), 42 | ..Default::default() 43 | }; 44 | let tx_outcome: Result = 45 | try_transaction_receipt_to_tx_outcome(receipt); 46 | assert!( 47 | tx_outcome.is_ok(), 48 | "Turning succeeded transaction receipt into successful tx outcome not succeeded" 49 | ); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /chains/nomad-substrate/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - Update `update` method with new max index field 6 | - `produce_update` checks that tree has at least 1 element (bug fix) 7 | - Add timelag functionality to `NomadOnlineClient` which wraps storage fetches with timelagged fetches 8 | - Add methods to macros.rs to allow for configuring Substrate objects from configuration conf objects 9 | - Add initial `nomad-substrate` home implementation and replica/xapp stubs 10 | - Return `SubstrateError` for fetches/calls as well as indexing 11 | - Add `SubstrateError` type which wraps all subxt and scale-value deserialization errors (substrate-specific) 12 | -------------------------------------------------------------------------------- /chains/nomad-substrate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-substrate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | authors = ["Luke Tchang "] 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | # subxt = { git = "https://github.com/maticnetwork/subxt.git", branch = "main" } 11 | subxt = "0.23.0" 12 | affix = "0.1.2" 13 | async-std = { version = "1.9.0", features = ["attributes", "tokio1"] } 14 | async-trait = { version = "0.1.42", default-features = false } 15 | color-eyre = "0.6.0" 16 | sp-keyring = "6.0.0" 17 | env_logger = "0.9.0" 18 | futures = "0.3.13" 19 | codec = { package = "parity-scale-codec", version = "3.0.0", default-features = false, features = ["derive", "full", "bit-vec"] } 20 | hex = "0.4.3" 21 | serde = { version = "1.0", features = ["derive"] } 22 | serde_json = "1" 23 | parity-util-mem = "0.11.0" 24 | scale-info = { version = "2.0.0", features = ["bit-vec"] } 25 | tokio = { version = "1.10", features = ["macros", "rt-multi-thread", "time", "parking_lot"] } 26 | tracing = "0.1.35" 27 | tracing-futures = "0.2.5" 28 | thiserror = "1.0.30" 29 | once_cell = "1.8.0" 30 | primitive-types = { git = "https://github.com/paritytech/parity-common.git", branch = "master", features = ["serde"] } 31 | 32 | nomad-xyz-configuration = { path = "../../configuration" } 33 | nomad-core = { path = "../../nomad-core" } 34 | nomad-types = { path = "../../nomad-types" } 35 | 36 | ethers-core = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 37 | -------------------------------------------------------------------------------- /chains/nomad-substrate/metadata/.gitignore: -------------------------------------------------------------------------------- 1 | *.scale 2 | !avail.metadata.scale 3 | !avail.dev.metadata.scale -------------------------------------------------------------------------------- /chains/nomad-substrate/metadata/avail.dev.metadata.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-xyz/rust/58558d7883dfca9e2fb80ecb91da9979bc2c7388/chains/nomad-substrate/metadata/avail.dev.metadata.scale -------------------------------------------------------------------------------- /chains/nomad-substrate/metadata/avail.metadata.scale: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-xyz/rust/58558d7883dfca9e2fb80ecb91da9979bc2c7388/chains/nomad-substrate/metadata/avail.metadata.scale -------------------------------------------------------------------------------- /chains/nomad-substrate/src/configs/mod.rs: -------------------------------------------------------------------------------- 1 | /// Avail 2 | pub mod avail; 3 | pub use avail::*; 4 | -------------------------------------------------------------------------------- /chains/nomad-substrate/src/decodings.rs: -------------------------------------------------------------------------------- 1 | use nomad_core::accumulator::{self, arrays, TREE_DEPTH}; 2 | use primitive_types::{H160, H256}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// Substrate-specific Nomad states. Does not include an uninitialized state. 6 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 7 | pub enum NomadState { 8 | /// Contract is active 9 | Active, 10 | /// Contract has failed 11 | Failed, 12 | } 13 | 14 | impl Default for NomadState { 15 | fn default() -> Self { 16 | Self::Active 17 | } 18 | } 19 | 20 | /// Wrapper for accomodating oddities of scale-value encoding of H256 primitives. 21 | /// Need wrapper type to match the shape of the scale encoded value. 22 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 23 | pub(crate) struct H256Wrapper([H256; 1]); 24 | 25 | impl std::ops::Deref for H256Wrapper { 26 | type Target = [u8; 32]; 27 | fn deref(&self) -> &Self::Target { 28 | &self.0[0].0 29 | } 30 | } 31 | 32 | impl From for ethers_core::types::H256 { 33 | fn from(wrapper: H256Wrapper) -> Self { 34 | let bytes = *wrapper; 35 | bytes.into() 36 | } 37 | } 38 | 39 | /// Wrapper for accomodating oddities of scale-value encoding of H160 primitives. 40 | /// Need wrapper type to match the shape of the scale encoded value. 41 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 42 | pub(crate) struct H160Wrapper([H160; 1]); 43 | 44 | impl std::ops::Deref for H160Wrapper { 45 | type Target = [u8; 20]; 46 | fn deref(&self) -> &Self::Target { 47 | &self.0[0].0 48 | } 49 | } 50 | 51 | impl From for ethers_core::types::H160 { 52 | fn from(wrapper: H160Wrapper) -> Self { 53 | let bytes = *wrapper; 54 | bytes.into() 55 | } 56 | } 57 | 58 | /// NomadBase struct stored in Substrate home 59 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 60 | pub(crate) struct NomadBase { 61 | /// State 62 | pub state: NomadState, 63 | /// Local domain 64 | pub local_domain: u32, 65 | /// Committed root 66 | pub committed_root: H256Wrapper, 67 | /// Updater 68 | pub updater: H160Wrapper, 69 | } 70 | 71 | /// An incremental merkle tree wrapper that uses the H256Wrapper type. 72 | /// Accomodates oddities of the scale value encoding of H256. 73 | #[derive(Debug, Clone, Copy, Serialize, Deserialize)] 74 | pub struct NomadLightMerkleWrapper { 75 | #[serde(with = "arrays")] 76 | branch: [H256Wrapper; TREE_DEPTH], 77 | count: usize, 78 | } 79 | 80 | impl From for accumulator::NomadLightMerkle { 81 | fn from(wrapper: NomadLightMerkleWrapper) -> Self { 82 | let branch: [ethers_core::types::H256; TREE_DEPTH] = wrapper.branch.map(|w| w.into()); 83 | accumulator::NomadLightMerkle::new(branch, wrapper.count) 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /chains/nomad-substrate/src/error.rs: -------------------------------------------------------------------------------- 1 | use ethers_core::types::H256; 2 | use std::error::Error as StdError; 3 | use subxt::{ext::scale_value, Error as SubxtError}; 4 | 5 | /// Substrate-specific error wrapper 6 | #[derive(Debug, thiserror::Error)] 7 | pub enum SubstrateError { 8 | /// A transaction was not executed successfully 9 | #[error("Transaction was not executed successfully {0:?}")] 10 | TxNotExecuted(H256), 11 | /// Substrate provider error 12 | #[error("{0}")] 13 | ProviderError(#[from] SubxtError), 14 | /// Scale value deserialization error 15 | #[error("{0}")] 16 | DeserializationError(#[from] scale_value::serde::DeserializerError), 17 | /// Any other error 18 | #[error("{0}")] 19 | CustomError(#[from] Box), 20 | } 21 | -------------------------------------------------------------------------------- /chains/nomad-substrate/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Interfaces to the substrate chains 2 | 3 | #![forbid(unsafe_code)] 4 | #![warn(missing_docs)] 5 | #![warn(unused_extern_crates)] 6 | 7 | /// Substrate home 8 | pub mod home; 9 | pub use home::*; 10 | 11 | /// Substrate replica 12 | pub mod replica; 13 | pub use replica::*; 14 | 15 | /// Substrate xapp connection manager 16 | pub mod xapp; 17 | pub use xapp::*; 18 | 19 | mod configs; 20 | pub use configs::*; 21 | 22 | /// Intermediate representations needed to decode data from substrate chain 23 | mod decodings; 24 | 25 | mod client; 26 | pub use client::*; 27 | 28 | mod signer; 29 | pub use signer::*; 30 | 31 | #[macro_use] 32 | mod macros; 33 | pub use macros::*; 34 | 35 | mod utils; 36 | pub use utils::*; 37 | 38 | mod error; 39 | pub use error::*; 40 | 41 | use ::nomad_core::{Home, HomeIndexer}; 42 | use std::str::FromStr; 43 | 44 | boxed_signing_object!( 45 | make_avail_home, 46 | Avail, 47 | SubstrateHome, 48 | Home, 49 | ); 50 | 51 | boxed_indexer!( 52 | make_avail_home_indexer, 53 | Avail, 54 | SubstrateHomeIndexer, 55 | HomeIndexer, 56 | ); 57 | 58 | #[derive(Debug, Clone)] 59 | pub(crate) enum SubstrateChains { 60 | Avail, 61 | } 62 | 63 | impl FromStr for SubstrateChains { 64 | type Err = (); 65 | 66 | fn from_str(s: &str) -> Result { 67 | match s.to_lowercase().as_ref() { 68 | "avail" => Ok(Self::Avail), 69 | _ => panic!("Unknown substrate chain: {}", s), 70 | } 71 | } 72 | } 73 | 74 | /// Substrate signer 75 | pub type SubstrateSigner = dyn subxt::tx::Signer + Send + Sync; 76 | 77 | /// Make substrate home object 78 | pub async fn make_home( 79 | conn: nomad_xyz_configuration::Connection, 80 | name: &str, 81 | domain: u32, 82 | submitter_conf: Option, 83 | timelag: Option, 84 | ) -> color_eyre::Result>> { 85 | let chain: SubstrateChains = name 86 | .parse() 87 | .unwrap_or_else(|_| panic!("Unrecognized chain name: {}", name)); 88 | 89 | match chain { 90 | SubstrateChains::Avail => { 91 | make_avail_home(conn, name, domain, submitter_conf, timelag).await 92 | } 93 | } 94 | } 95 | 96 | /// Make substrate home object 97 | pub async fn make_home_indexer( 98 | conn: nomad_xyz_configuration::Connection, 99 | name: &str, 100 | timelag: Option, 101 | ) -> color_eyre::Result>> { 102 | let chain: SubstrateChains = name 103 | .parse() 104 | .unwrap_or_else(|_| panic!("Unrecognized chain name: {}", name)); 105 | 106 | match chain { 107 | SubstrateChains::Avail => make_avail_home_indexer(conn, timelag).await, 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /chains/nomad-substrate/src/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::SubstrateError; 2 | use ethers_core::types::H256; 3 | use nomad_core::SignedUpdate; 4 | use subxt::{ 5 | client::OnlineClientT, 6 | ext::scale_value::{Primitive, Value}, 7 | tx::TxEvents, 8 | tx::TxInBlock, 9 | Config, 10 | }; 11 | 12 | /// Try to convert `TxInBlock` to `TxEvents`, which can only happen if tx 13 | /// in block succeeds. Attempt to catch module errors as determinstic reverts. 14 | pub async fn try_tx_in_block_to_successful_tx_events( 15 | tx_in_block: TxInBlock, 16 | ) -> Result, SubstrateError> 17 | where 18 | T: Config, 19 | C: OnlineClientT, 20 | ::Hash: Into, 21 | { 22 | // Try to detect reverting txs that were submitted to chain 23 | tx_in_block.wait_for_success().await.map_err(|err| { 24 | if let subxt::Error::Runtime(subxt::error::DispatchError::Module(_)) = err { 25 | return SubstrateError::TxNotExecuted(tx_in_block.extrinsic_hash().into()); 26 | } 27 | 28 | SubstrateError::ProviderError(err) 29 | }) 30 | } 31 | 32 | /// Format signed update into scale value format 33 | pub fn format_signed_update_value(signed_update: &SignedUpdate) -> Value { 34 | let SignedUpdate { update, signature } = signed_update; 35 | 36 | let r_bytes = signature.r.0; 37 | let s_bytes = signature.s.0; 38 | 39 | Value::named_composite([ 40 | ( 41 | "update", 42 | Value::named_composite([ 43 | ("home_domain", Value::u128(update.home_domain as u128)), 44 | ( 45 | "previous_root", 46 | Value::primitive(Primitive::U256(update.previous_root.into())), 47 | ), 48 | ( 49 | "new_root", 50 | Value::primitive(Primitive::U256(update.new_root.into())), 51 | ), 52 | ]), 53 | ), 54 | ( 55 | "signature", 56 | Value::named_composite([ 57 | ( 58 | "r", 59 | Value::unnamed_composite([ 60 | Value::u128(r_bytes[0] as u128), 61 | Value::u128(r_bytes[1] as u128), 62 | Value::u128(r_bytes[2] as u128), 63 | Value::u128(r_bytes[3] as u128), 64 | ]), 65 | ), 66 | ( 67 | "s", 68 | Value::unnamed_composite([ 69 | Value::u128(s_bytes[0] as u128), 70 | Value::u128(s_bytes[1] as u128), 71 | Value::u128(s_bytes[2] as u128), 72 | Value::u128(s_bytes[3] as u128), 73 | ]), 74 | ), 75 | ("v", Value::u128(signature.v as u128)), 76 | ]), 77 | ), 78 | ]) 79 | } 80 | -------------------------------------------------------------------------------- /configuration/.gitignore: -------------------------------------------------------------------------------- 1 | ts -------------------------------------------------------------------------------- /configuration/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-xyz-configuration" 3 | version = "2.0.1" 4 | edition = "2021" 5 | authors = ["James Prestwich ", "The Nomad Developers "] 6 | description = "Nomad project configuration file utilities" 7 | repository = "https://github.com/nomad-xyz/rust" 8 | license = "MIT OR Apache-2.0" 9 | exclude = [ 10 | "*.sh", 11 | ".git*" 12 | ] 13 | 14 | [lib] 15 | crate-type = ["cdylib", "rlib"] 16 | 17 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 18 | 19 | [build-dependencies] 20 | eyre = "0.6.6" 21 | reqwest = "0.11.10" 22 | tokio = { version = "1.18.2", features = ["rt", "macros"] } 23 | 24 | [dependencies] 25 | affix = "0.1.2" 26 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 27 | eyre = "0.6.6" 28 | once_cell = "1.9.0" 29 | serde = "1.0.136" 30 | serde_json = "1.0.78" 31 | serde_yaml = "0.8.23" 32 | nomad-types = { path = "../nomad-types" } 33 | tracing = "0.1.35" 34 | 35 | [target.'cfg(not(target_arch = "wasm32"))'.dependencies] 36 | reqwest = "0.11.10" 37 | 38 | [target.'cfg(target_arch = "wasm32")'.dependencies] 39 | wee_alloc = "0.4.5" 40 | js-sys = "0.3.56" 41 | wasm-bindgen = { version = "0.2.79", features = ["serde-serialize"] } 42 | 43 | [dev-dependencies] 44 | dotenv = "0.15.0" 45 | serial_test = "0.6.0" 46 | 47 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] 48 | nomad-test = { path = "../nomad-test" } 49 | -------------------------------------------------------------------------------- /configuration/README.md: -------------------------------------------------------------------------------- 1 | ## Nomad Config file 2 | 3 | This is a crate for working with nomad configuration files. These config files 4 | contain information about the state of Nomad deployments. 5 | 6 | It also includes an auto-generated TS/WASM library. 7 | 8 | ### Design Notes 9 | 10 | The core library is mostly a JSON config file format. We define Rust structs 11 | and TS types for all parts of this config. 12 | 13 | In TS, the object is a native JS object. It is _not_ a reference to a wasm type. 14 | Assignment and access can be done as normal. However, we have also exported 15 | functions that perform consistency-critical operations like `addNetwork` and 16 | `addCore`. We strongly recommend using these instead of assigning to the 17 | relevant sections. 18 | 19 | ### Usage 20 | 21 | #### Typescript 22 | 23 | ```typescript 24 | import * as configuration from "@nomad-xyz/configuration" 25 | 26 | const config = configuration.getBuiltin("production") 27 | 28 | console.log(`Environment: ${config.environment}`) 29 | ``` 30 | 31 | #### Rust 32 | 33 | // TODO 34 | 35 | ### Building 36 | 37 | - `$ cargo build` 38 | 39 | To build the wasm library: 40 | 41 | - [Install wasm-pack](https://rustwasm.github.io/wasm-pack/installer/) 42 | - `$ ./package-it.sh` 43 | 44 | `wasm-pack` docs are found [here](https://rustwasm.github.io/wasm-pack/book/). 45 | 46 | ### Testing 47 | 48 | - `$ cargo test` 49 | 50 | ### Documenting 51 | 52 | - `$ cargo docs --open` 53 | 54 | ### Releasing 55 | 56 | #### Prepare Release 57 | - Update Changelog from unreleased to next version 58 | - Bump package version in `cargo.toml` to `` 59 | - Run the tests locally: `cargo test` 60 | - Make a PR and merge it 61 | 62 | #### Release / Publish 63 | - Tag newly-merged commit: `git tag -s @nomad-xyz/configuration@` 64 | - Push tags: `git push --tags` 65 | - Publish to NPM: `./publish_npm.sh` 66 | 67 | ### Development note 68 | 69 | To work around some `wasm-bindgen` limitations, we currently (unfortunately) 70 | have to manually define TS types for the rust structs. These are found in the 71 | `data` directory. When a rust struct is updated or added, the corresponding 72 | definitions should be added in `data/definitions.ts` and `data/types.rs`. At 73 | compile-time these files are combind to `src/wasm/types.rs`. 74 | 75 | In the future it'd be cool to auto-generate this code :) 76 | -------------------------------------------------------------------------------- /configuration/configs/.gitignore: -------------------------------------------------------------------------------- 1 | *.json 2 | !test.json 3 | !availDemo.json -------------------------------------------------------------------------------- /configuration/data/types.rs: -------------------------------------------------------------------------------- 1 | #[wasm_bindgen] 2 | extern "C" { 3 | #[wasm_bindgen(typescript_type = "AppConfig")] 4 | pub type AppConfig; 5 | 6 | #[wasm_bindgen(typescript_type = "NomadLocator")] 7 | pub type NomadLocator; 8 | 9 | #[wasm_bindgen(typescript_type = "LogConfig")] 10 | pub type LogConfig; 11 | 12 | #[wasm_bindgen(typescript_type = "IndexConfig")] 13 | pub type IndexConfig; 14 | 15 | #[wasm_bindgen(typescript_type = "BaseAgentConfig")] 16 | pub type BaseAgentConfig; 17 | 18 | #[wasm_bindgen(typescript_type = "AgentConfig")] 19 | pub type AgentConfig; 20 | 21 | #[wasm_bindgen(typescript_type = "Proxy")] 22 | pub type Proxy; 23 | 24 | #[wasm_bindgen(typescript_type = "EthereumCoreDeploymentInfo")] 25 | pub type EthereumCoreDeploymentInfo; 26 | 27 | #[wasm_bindgen(typescript_type = "SubstrateCoreDeploymentInfo")] 28 | pub type SubstrateCoreDeploymentInfo; 29 | 30 | #[wasm_bindgen(typescript_type = "CoreDeploymentInfo")] 31 | pub type CoreDeploymentInfo; 32 | 33 | #[wasm_bindgen(typescript_type = "DeployedCustomToken")] 34 | pub type DeployedCustomToken; 35 | 36 | #[wasm_bindgen(typescript_type = "EthereumBridgeDeploymentInfo")] 37 | pub type EthereumBridgeDeploymentInfo; 38 | 39 | #[wasm_bindgen(typescript_type = "SubstrateBridgeDeploymentInfo")] 40 | pub type SubstrateBridgeDeploymentInfo; 41 | 42 | #[wasm_bindgen(typescript_type = "BridgeDeploymentInfo")] 43 | pub type BridgeDeploymentInfo; 44 | 45 | #[wasm_bindgen(typescript_type = "Governance")] 46 | pub type Governance; 47 | 48 | #[wasm_bindgen(typescript_type = "ContractConfig")] 49 | pub type ContractConfig; 50 | 51 | #[wasm_bindgen(typescript_type = "NetworkSpecs")] 52 | pub type NetworkSpecs; 53 | 54 | #[wasm_bindgen(typescript_type = "CustomTokenSpecifier")] 55 | pub type CustomTokenSpecifier; 56 | 57 | #[wasm_bindgen(typescript_type = "AccountantInfo")] 58 | pub type AccountantInfo; 59 | 60 | #[wasm_bindgen(typescript_type = "BridgeConfiguration")] 61 | pub type BridgeConfiguration; 62 | 63 | #[wasm_bindgen(typescript_type = "Domain")] 64 | pub type Domain; 65 | 66 | #[wasm_bindgen(typescript_type = "NetworkInfo")] 67 | pub type NetworkInfo; 68 | 69 | #[wasm_bindgen(typescript_type = "HomeUpdateGasLimit")] 70 | pub type HomeUpdateGasLimit; 71 | 72 | #[wasm_bindgen(typescript_type = "HomeGasLimits")] 73 | pub type HomeGasLimits; 74 | 75 | #[wasm_bindgen(typescript_type = "ReplicaGasLimits")] 76 | pub type ReplicaGasLimits; 77 | 78 | #[wasm_bindgen(typescript_type = "ConnectionManagerGasLimits")] 79 | pub type ConnectionManagerGasLimits; 80 | 81 | #[wasm_bindgen(typescript_type = "CoreGasConfig")] 82 | pub type CoreGasConfig; 83 | 84 | #[wasm_bindgen(typescript_type = "BridgeRouterGasLimits")] 85 | pub type BridgeRouterGasLimits; 86 | 87 | #[wasm_bindgen(typescript_type = "EthHelperGasLimits")] 88 | pub type EthHelperGasLimits; 89 | 90 | #[wasm_bindgen(typescript_type = "BridgeGasConfig")] 91 | pub type BridgeGasConfig; 92 | 93 | #[wasm_bindgen(typescript_type = "NomadGasConfig")] 94 | pub type NomadGasConfig; 95 | 96 | #[wasm_bindgen(typescript_type = "S3Config")] 97 | pub type S3Config; 98 | 99 | #[wasm_bindgen(typescript_type = "NomadConfig")] 100 | pub type NomadConfig; 101 | } 102 | -------------------------------------------------------------------------------- /configuration/package_it.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | # change these :) 5 | SCOPE="nomad-xyz" 6 | PKG_NAME="configuration" 7 | 8 | # Check if jq is installed 9 | if ! [ -x "$(command -v jq)" ]; then 10 | echo "jq is not installed" >& 2 11 | echo "how do you live with yourself" 12 | exit 1 13 | fi 14 | 15 | 16 | # Check if jq is installed 17 | if ! [ -x "$(command -v wasm-pack)" ]; then 18 | echo "wasm-pack is not installed" >& 2 19 | exit 1 20 | fi 21 | # Cleanup any old generated code 22 | rm -rf ./ts 23 | mkdir -p ./ts 24 | 25 | # build 26 | wasm-pack build --target browser --scope $SCOPE --out-dir ts/web --out-name $PKG_NAME 27 | wasm-pack build --target nodejs --scope $SCOPE --out-dir ts/node --out-name $PKG_NAME 28 | 29 | # use the browser package.json as a base 30 | mv ts/web/package.json ts/ 31 | # copy in licenses from repo root 32 | cp ../LICENSE* ts/ 33 | # Get the README in the root, delete the spare 34 | mv ts/web/README* ts/ 35 | 36 | # clean redundant files 37 | rm ts/node/README* 38 | rm ts/node/package.json 39 | 40 | # set the package.json main key (affects how nodejs loads this) 41 | cat ts/package.json | jq --arg main "node/$PKG_NAME.js" '.main = $main'> TMP_FILE && mv TMP_FILE ts/package.json 42 | 43 | # set the package.json browser key (affects how bundlers load this) 44 | cat ts/package.json | jq --arg browser "web/$PKG_NAME.js" '.browser = $browser'> TMP_FILE && mv TMP_FILE ts/package.json 45 | 46 | # set the package.json module key (affects how bundlers load this) 47 | cat ts/package.json | jq --arg m "web/$PKG_NAME.js" '.module = $m' > TMP_FILE && mv TMP_FILE ts/package.json 48 | 49 | # set the package.json name key (correct module name) 50 | cat ts/package.json | jq --arg n "@$SCOPE/$PKG_NAME" '.name = $n' > TMP_FILE && mv TMP_FILE ts/package.json 51 | 52 | # set the package.json types key 53 | cat ts/package.json | jq --arg types "web/$PKG_NAME.d.ts" '.types = $types' > TMP_FILE && mv TMP_FILE ts/package.json 54 | 55 | # empty the package.json files list 56 | cat ts/package.json | jq '.files = []' > TMP_FILE && mv TMP_FILE ts/package.json 57 | 58 | # add each web file to the package.json files list 59 | for F in "web/$PKG_NAME""_bg.wasm" "web/$PKG_NAME""_bg.d.ts" "web/$PKG_NAME.js" "web/$PKG_NAME.d.ts" "web/$PKG_NAME""_bg.js" 60 | do 61 | cat ts/package.json | jq --arg f "$F" '.files += [$f]' > TMP_FILE && mv TMP_FILE ts/package.json 62 | done 63 | 64 | # add each node file to the package.json files list 65 | for F in "node/$PKG_NAME""_bg.wasm" "node/$PKG_NAME""_bg.d.ts" "node/$PKG_NAME.js" "node/$PKG_NAME.d.ts" 66 | do 67 | cat ts/package.json | jq --arg f "$F" '.files += [$f]' > TMP_FILE && mv TMP_FILE ts/package.json 68 | done -------------------------------------------------------------------------------- /configuration/publish_cargo.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cargo clippy 5 | cargo test 6 | 7 | cargo publish -------------------------------------------------------------------------------- /configuration/publish_npm.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cargo clippy --target wasm32-unknown-unknown 5 | cargo test 6 | 7 | ./package_it.sh 8 | cd ts 9 | npm publish -------------------------------------------------------------------------------- /configuration/src/agent/logging.rs: -------------------------------------------------------------------------------- 1 | //! Agent configuration types 2 | 3 | /// Rpc Styles 4 | #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq)] 5 | #[serde(rename_all = "lowercase")] 6 | pub enum RpcStyles { 7 | /// Ethereum 8 | Ethereum, 9 | /// Substrate 10 | Substrate, 11 | } 12 | 13 | impl std::fmt::Display for RpcStyles { 14 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 15 | let style = match self { 16 | RpcStyles::Ethereum => "ethereum", 17 | RpcStyles::Substrate => "substrate", 18 | }; 19 | 20 | write!(f, "{}", style) 21 | } 22 | } 23 | 24 | impl Default for RpcStyles { 25 | fn default() -> Self { 26 | RpcStyles::Ethereum 27 | } 28 | } 29 | 30 | /// Basic tracing configuration 31 | #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, Eq, PartialEq)] 32 | #[serde(rename_all = "camelCase")] 33 | pub enum LogStyle { 34 | /// Pretty print 35 | Pretty, 36 | /// JSON 37 | Json, 38 | /// Compact 39 | Compact, 40 | /// Default style 41 | #[serde(other)] 42 | Full, 43 | } 44 | 45 | impl Default for LogStyle { 46 | fn default() -> Self { 47 | LogStyle::Full 48 | } 49 | } 50 | 51 | /// Logging level 52 | #[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq)] 53 | #[serde(rename_all = "camelCase")] 54 | pub enum LogLevel { 55 | /// Off 56 | Off, 57 | /// Error 58 | Error, 59 | /// Warn 60 | Warn, 61 | /// Debug 62 | Debug, 63 | /// Trace 64 | Trace, 65 | /// Info 66 | #[serde(other)] 67 | Info, 68 | } 69 | 70 | impl Default for LogLevel { 71 | fn default() -> Self { 72 | LogLevel::Info 73 | } 74 | } 75 | 76 | /// Logger configuration 77 | #[derive(Debug, Copy, Clone, serde::Serialize, serde::Deserialize, PartialEq)] 78 | #[serde(rename_all = "camelCase")] 79 | pub struct LogConfig { 80 | /// fmt specifier 81 | pub fmt: LogStyle, 82 | /// level specifier 83 | pub level: LogLevel, 84 | } 85 | 86 | impl Default for LogConfig { 87 | fn default() -> Self { 88 | Self { 89 | fmt: LogStyle::Pretty, 90 | level: LogLevel::Trace, 91 | } 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod test { 97 | use serde_json::json; 98 | 99 | use super::RpcStyles; 100 | 101 | #[test] 102 | fn it_deserializes_rpc_styles() { 103 | let serialized = serde_json::to_value(&RpcStyles::Ethereum).unwrap(); 104 | 105 | let val = json! { "ethereum" }; 106 | assert_eq!(val, serialized); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /configuration/src/agent/mod.rs: -------------------------------------------------------------------------------- 1 | //! Agent configuration (logging, intervals, addresses, etc). 2 | //! 3 | //! All structs defined in this module include public data only. The real agent 4 | //! settings blocks are separate/different from these {Agent}Config blocks and 5 | //! can contain signers. Functionality of these config blocks is minimized to 6 | //! just the data itself. 7 | 8 | mod logging; 9 | pub use logging::*; 10 | 11 | mod signer; 12 | pub use signer::*; 13 | 14 | pub mod kathy; 15 | pub mod processor; 16 | pub mod relayer; 17 | pub mod updater; 18 | pub mod watcher; 19 | 20 | use std::path::PathBuf; 21 | 22 | use self::{ 23 | kathy::KathyConfig, processor::ProcessorConfig, relayer::RelayerConfig, updater::UpdaterConfig, 24 | watcher::WatcherConfig, 25 | }; 26 | 27 | /// Full agent configuration 28 | #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] 29 | #[serde(rename_all = "camelCase")] 30 | pub struct AgentConfig { 31 | /// RPC specifier 32 | pub rpc_style: RpcStyles, 33 | /// Path to the DB 34 | pub db: PathBuf, 35 | /// Metrics port 36 | pub metrics: Option, 37 | /// Logging configuration 38 | pub logging: LogConfig, 39 | /// Updater configuration 40 | pub updater: UpdaterConfig, 41 | /// Relayer configuration 42 | pub relayer: RelayerConfig, 43 | /// Processor configuration 44 | pub processor: ProcessorConfig, 45 | /// Watcher configuration 46 | pub watcher: WatcherConfig, 47 | /// Kathy configuration 48 | pub kathy: KathyConfig, 49 | } 50 | 51 | #[macro_export] 52 | /// Creates environment variable override block for overriding non-base settings 53 | /// Use of `$self_`: https://veykril.github.io/tlborm/decl-macros/minutiae/identifiers.html 54 | macro_rules! decl_env_overrides { 55 | ($name:ident {$self_:ident, $block:block}) => { 56 | affix::paste! { 57 | impl EnvOverridablePrivate for [<$name Config>] { 58 | fn load_env_overrides_private(&mut $self_) $block 59 | } 60 | } 61 | }; 62 | ($name:ident $block:block) => { 63 | affix::paste! { 64 | impl EnvOverridablePrivate for [<$name Config>] {} 65 | } 66 | }; 67 | } 68 | 69 | #[macro_export] 70 | /// Creates agent config block on that comes with interval and enabled by 71 | /// default 72 | macro_rules! decl_config { 73 | ( 74 | $name:ident { 75 | $($(#[$tags:meta])* $prop:ident: $type:ty,)* 76 | } 77 | ) => { 78 | affix::paste! { 79 | pub(self) trait EnvOverridablePrivate { 80 | fn load_env_overrides_private(&mut self) {} 81 | } 82 | 83 | #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] 84 | #[serde(rename_all = "camelCase")] 85 | #[doc = "Config for `" $name "`"] 86 | #[allow(missing_copy_implementations)] 87 | pub struct [<$name Config>] { 88 | $( 89 | $(#[$tags])* 90 | pub $prop: $type, 91 | )* 92 | /// Agent interval 93 | pub interval: u64, 94 | } 95 | 96 | impl [<$name Config>] { 97 | /// Override config with environment variables if present 98 | pub fn load_env_overrides(&mut self) { 99 | if let Ok(var) = std::env::var(std::stringify!([<$name:upper _INTERVAL>])) { 100 | self.interval = var 101 | .parse::() 102 | .expect(std::stringify!([invalid <$name:upper _INTERVAL> value])); 103 | } 104 | self.load_env_overrides_private(); 105 | } 106 | } 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /configuration/src/agent/relayer.rs: -------------------------------------------------------------------------------- 1 | //! Relayer public configuration 2 | 3 | use crate::{decl_config, decl_env_overrides}; 4 | 5 | decl_config!(Relayer {}); 6 | decl_env_overrides!(Relayer {}); 7 | -------------------------------------------------------------------------------- /configuration/src/agent/updater.rs: -------------------------------------------------------------------------------- 1 | //! Updater public configuration 2 | 3 | use crate::{decl_config, decl_env_overrides}; 4 | 5 | decl_config!(Updater {}); 6 | decl_env_overrides!(Updater {}); 7 | -------------------------------------------------------------------------------- /configuration/src/agent/watcher.rs: -------------------------------------------------------------------------------- 1 | //! Watcher public configuration 2 | 3 | use crate::{decl_config, decl_env_overrides}; 4 | 5 | // Current watcher setup is home-centric, meaning one watcher will watch the 6 | // home and flag fraud on any corresponding replica chains. We assume the 7 | // watcher has permissions over connection managers on each replica chain for 8 | // now. This is likely to change in the future. 9 | decl_config!(Watcher {}); 10 | decl_env_overrides!(Watcher {}); 11 | -------------------------------------------------------------------------------- /configuration/src/builtin.rs: -------------------------------------------------------------------------------- 1 | //! Pre-set configs bundled with the lib 2 | 3 | use std::collections::HashMap; 4 | 5 | use eyre::Context; 6 | use once_cell::sync::OnceCell; 7 | 8 | use crate::NomadConfig; 9 | 10 | // built-in config objects 11 | static TEST_JSON: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/test.json")); 12 | static DEVELOPMENT_JSON: &str = include_str!(concat!( 13 | env!("CARGO_MANIFEST_DIR"), 14 | "/configs/development.json" 15 | )); 16 | static STAGING_JSON: &str = 17 | include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/configs/staging.json")); 18 | static PRODUCTION_JSON: &str = include_str!(concat!( 19 | env!("CARGO_MANIFEST_DIR"), 20 | "/configs/production.json" 21 | )); 22 | static BUILTINS: OnceCell>> = OnceCell::new(); 23 | 24 | fn deser(name: &str, json: &str) -> NomadConfig { 25 | serde_json::from_str(json) 26 | .wrap_err_with(|| format!("Configuration {}.json is malformed", name)) 27 | .unwrap() 28 | } 29 | 30 | /// Get a built-in config object 31 | pub fn get_builtin(name: &str) -> Option<&NomadConfig> { 32 | let builtins = BUILTINS.get_or_init(|| { 33 | let mut map: HashMap<_, _> = Default::default(); 34 | 35 | map.insert("test", Default::default()); 36 | map.insert("development", Default::default()); 37 | map.insert("staging", Default::default()); 38 | map.insert("production", Default::default()); 39 | map 40 | }); 41 | 42 | Some(builtins.get(name)?.get_or_init(|| match name { 43 | "test" => deser("test", TEST_JSON), 44 | "development" => deser("development", DEVELOPMENT_JSON), 45 | "staging" => deser("staging", STAGING_JSON), 46 | "production" => deser("production", PRODUCTION_JSON), 47 | _ => panic!("unknown builtin {}", name), 48 | })) 49 | } 50 | 51 | #[cfg(test)] 52 | mod tests { 53 | use super::*; 54 | 55 | #[test] 56 | fn test_loads_builtins() { 57 | dbg!(get_builtin("test")); 58 | } 59 | 60 | #[test] 61 | fn test_validates() { 62 | dbg!(get_builtin("test") 63 | .expect("config not found") 64 | .validate() 65 | .expect("invalid config")); 66 | } 67 | 68 | #[test] 69 | fn development_loads_builtins() { 70 | dbg!(get_builtin("development")); 71 | } 72 | 73 | #[test] 74 | fn development_validates() { 75 | dbg!(get_builtin("development") 76 | .expect("config not found") 77 | .validate() 78 | .expect("invalid config")); 79 | } 80 | 81 | #[test] 82 | fn staging_loads_builtins() { 83 | dbg!(get_builtin("staging")); 84 | } 85 | 86 | #[test] 87 | fn staging_validates() { 88 | dbg!(get_builtin("staging") 89 | .expect("config not found") 90 | .validate() 91 | .expect("invalid config")); 92 | } 93 | 94 | #[test] 95 | fn production_loads_builtins() { 96 | dbg!(get_builtin("production")); 97 | } 98 | 99 | #[test] 100 | fn production_validates() { 101 | dbg!(get_builtin("production") 102 | .expect("config not found") 103 | .validate() 104 | .expect("invalid config")); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /configuration/src/chains/ethereum/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum/EVM configuration types 2 | 3 | mod submitter; 4 | pub use submitter::*; 5 | -------------------------------------------------------------------------------- /configuration/src/chains/ethereum/submitter/gelato.rs: -------------------------------------------------------------------------------- 1 | use crate::agent::SignerConf; 2 | 3 | /// Configuration for tx submission through Gelato relay 4 | #[derive(Debug, Clone, PartialEq, serde::Deserialize)] 5 | #[serde(rename_all = "camelCase")] 6 | pub struct GelatoConf { 7 | /// Sponsor signer configuration 8 | pub sponsor: SignerConf, 9 | /// Address of fee token 10 | pub fee_token: String, 11 | } 12 | 13 | impl GelatoConf { 14 | /// Build GelatoConf from env. Looks for default configuration if 15 | /// network-specific not defined. 16 | pub fn from_env(network: &str) -> Option { 17 | Self::from_full_prefix(network).or_else(|| Self::from_full_prefix("DEFAULT")) 18 | } 19 | 20 | fn from_full_prefix(network: &str) -> Option { 21 | if let Some(sponsor) = SignerConf::from_env(Some("GELATO_SPONSOR"), Some(network)) { 22 | if let Ok(fee_token) = std::env::var(&format!("{}_GELATO_FEETOKEN", network)) { 23 | return Some(Self { sponsor, fee_token }); 24 | } 25 | } 26 | 27 | None 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /configuration/src/chains/ethereum/submitter/mod.rs: -------------------------------------------------------------------------------- 1 | //! Ethereum tx submitter types 2 | 3 | use crate::agent::SignerConf; 4 | use crate::TxSubmitterConf as BaseTxSubmitterConf; 5 | use std::str::FromStr; 6 | 7 | mod gelato; 8 | pub use gelato::*; 9 | 10 | /// Ethereum submitter type 11 | #[derive(Copy, Clone, Debug, PartialEq)] 12 | pub enum SubmitterType { 13 | /// Local sign/submit 14 | Local, 15 | /// Gelato 16 | Gelato, 17 | } 18 | 19 | impl FromStr for SubmitterType { 20 | type Err = (); 21 | 22 | fn from_str(s: &str) -> Result { 23 | match s.to_lowercase().as_ref() { 24 | "local" => Ok(Self::Local), 25 | "gelato" => Ok(Self::Gelato), 26 | _ => panic!("Unknown SubmitterType"), 27 | } 28 | } 29 | } 30 | 31 | /// Local or relay-based transaction submission 32 | #[derive(Debug, Clone, PartialEq, serde::Deserialize)] 33 | #[serde(tag = "submitterType", content = "submitter", rename_all = "camelCase")] 34 | pub enum TxSubmitterConf { 35 | /// Signer configuration for local signer 36 | Local(SignerConf), 37 | /// Gelato configuration for Gelato relay 38 | Gelato(GelatoConf), 39 | } 40 | 41 | impl From for TxSubmitterConf { 42 | fn from(conf: BaseTxSubmitterConf) -> Self { 43 | match conf { 44 | BaseTxSubmitterConf::Ethereum(conf) => conf, 45 | _ => panic!("Should never compile"), 46 | } 47 | } 48 | } 49 | 50 | impl TxSubmitterConf { 51 | /// Build ethereum TxSubmitterConf from env. Looks for default submitter 52 | /// type if network-specific not defined. 53 | pub fn from_env(network: &str) -> Option { 54 | let submitter_type = crate::utils::network_or_default_from_env(network, "SUBMITTER_TYPE")?; 55 | 56 | match SubmitterType::from_str(&submitter_type).unwrap() { 57 | SubmitterType::Local => { 58 | let signer_conf = SignerConf::from_env(Some("TXSIGNER"), Some(network))?; 59 | Some(Self::Local(signer_conf)) 60 | } 61 | SubmitterType::Gelato => { 62 | let gelato_conf = GelatoConf::from_env(network)?; 63 | Some(Self::Gelato(gelato_conf)) 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /configuration/src/chains/substrate/mod.rs: -------------------------------------------------------------------------------- 1 | //! Substrate tx submitter types 2 | 3 | use crate::agent::SignerConf; 4 | use crate::TxSubmitterConf as BaseTxSubmitterConf; 5 | use std::str::FromStr; 6 | 7 | /// Substrate submitter type 8 | #[derive(Copy, Clone, Debug, PartialEq)] 9 | pub enum SubmitterType { 10 | /// Local sign/submit 11 | Local, 12 | } 13 | 14 | impl FromStr for SubmitterType { 15 | type Err = (); 16 | 17 | fn from_str(s: &str) -> Result { 18 | match s.to_lowercase().as_ref() { 19 | "local" => Ok(Self::Local), 20 | _ => panic!("Unknown SubmitterType"), 21 | } 22 | } 23 | } 24 | 25 | /// Local or relay-based transaction submission 26 | #[derive(Debug, Clone, PartialEq, serde::Deserialize)] 27 | #[serde(tag = "submitterType", content = "submitter", rename_all = "camelCase")] 28 | pub enum TxSubmitterConf { 29 | /// Signer configuration for local signer 30 | Local(SignerConf), 31 | } 32 | 33 | impl From for TxSubmitterConf { 34 | fn from(conf: BaseTxSubmitterConf) -> Self { 35 | match conf { 36 | BaseTxSubmitterConf::Substrate(conf) => conf, 37 | _ => panic!("Should never compile"), 38 | } 39 | } 40 | } 41 | 42 | impl TxSubmitterConf { 43 | /// Build Substrate TxSubmitterConf from env. Looks for default submitter 44 | /// type if network-specific not defined. 45 | pub fn from_env(network: &str) -> Option { 46 | let submitter_type = crate::utils::network_or_default_from_env(network, "SUBMITTER_TYPE")?; 47 | 48 | match SubmitterType::from_str(&submitter_type).unwrap() { 49 | SubmitterType::Local => { 50 | let signer_conf = SignerConf::from_env(Some("TXSIGNER"), Some(network))?; 51 | Some(Self::Local(signer_conf)) 52 | } 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /configuration/src/core.rs: -------------------------------------------------------------------------------- 1 | //! Nomad Contract location configuration 2 | 3 | use std::collections::HashMap; 4 | 5 | use nomad_types::deser_nomad_u32; 6 | use nomad_types::{NomadIdentifier, Proxy}; 7 | 8 | /// Evm Core Contracts 9 | #[derive(Default, Debug, Clone, serde::Serialize, serde::Deserialize)] 10 | #[serde(rename_all = "camelCase")] 11 | pub struct EthereumCoreDeploymentInfo { 12 | /// Contract Deploy Height 13 | #[serde(default, deserialize_with = "deser_nomad_u32")] 14 | pub deploy_height: u32, 15 | /// UBC address 16 | pub upgrade_beacon_controller: NomadIdentifier, 17 | /// XApp Connection Manager address 18 | pub x_app_connection_manager: NomadIdentifier, 19 | /// Updater Manager address 20 | pub updater_manager: NomadIdentifier, 21 | /// Governance router proxy details 22 | pub governance_router: Proxy, 23 | /// Home Proxy details 24 | pub home: Proxy, 25 | /// Replica proxy details. Note these are the EVM replicas of remote domain. 26 | /// These are not the remote replicas of this domain 27 | pub replicas: HashMap, 28 | } 29 | 30 | /// Empty Substrate contracts 31 | #[derive(Default, Copy, Debug, Clone, serde::Serialize, serde::Deserialize)] 32 | #[serde(rename_all = "camelCase")] 33 | pub struct SubstrateCoreDeploymentInfo { 34 | /// Contract Deploy Height 35 | #[serde(default, deserialize_with = "deser_nomad_u32")] 36 | pub deploy_height: u32, 37 | // TODO: add replicas for substrate rollout v2 38 | } 39 | 40 | /// Core Contract abstract 41 | #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] 42 | #[serde(untagged)] 43 | pub enum CoreDeploymentInfo { 44 | /// EVM Core 45 | Ethereum(EthereumCoreDeploymentInfo), 46 | /// Substrate core 47 | Substrate(SubstrateCoreDeploymentInfo), 48 | } 49 | 50 | impl CoreDeploymentInfo { 51 | /// Get an iterator over the replicas present in this deploy 52 | pub fn replicas(&self) -> impl Iterator { 53 | match self { 54 | CoreDeploymentInfo::Ethereum(contracts) => contracts.replicas.keys(), 55 | CoreDeploymentInfo::Substrate(_) => { 56 | unimplemented!("Replicas do not exist in Substrate implementations") 57 | } 58 | } 59 | } 60 | 61 | /// True if the contracts contain a replica of the specified network. 62 | pub fn has_replica(&self, name: &str) -> bool { 63 | match self { 64 | CoreDeploymentInfo::Ethereum(contracts) => contracts.replicas.contains_key(name), 65 | CoreDeploymentInfo::Substrate(_) => { 66 | unimplemented!("Replicas do not exist in Substrate implementations") 67 | } 68 | } 69 | } 70 | 71 | /// Locate the replica of the specified network (if known) 72 | pub fn replica_of(&self, home_network: &str) -> Option { 73 | match self { 74 | CoreDeploymentInfo::Ethereum(contracts) => { 75 | contracts.replicas.get(home_network).map(|n| n.proxy) 76 | } 77 | CoreDeploymentInfo::Substrate(_) => { 78 | unimplemented!("Replicas do not exist in Substrate implementations") 79 | } 80 | } 81 | } 82 | } 83 | 84 | impl Default for CoreDeploymentInfo { 85 | fn default() -> Self { 86 | CoreDeploymentInfo::Ethereum(Default::default()) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /configuration/src/gas/defaults.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | BridgeGasConfig, BridgeRouterGasLimits, ConnectionManagerGasLimits, CoreGasConfig, 3 | EthHelperGasLimits, HomeGasLimits, HomeUpdateGasLimit, NomadGasConfig, ReplicaGasLimits, 4 | }; 5 | 6 | pub const EVM_DEFAULT: NomadGasConfig = NomadGasConfig { 7 | core: CoreGasConfig { 8 | home: HomeGasLimits { 9 | update: HomeUpdateGasLimit { 10 | per_message: 10_000, 11 | base: 100_000, 12 | }, 13 | improper_update: HomeUpdateGasLimit { 14 | per_message: 10_000, 15 | base: 100_000, 16 | }, 17 | double_update: 200_000, 18 | }, 19 | replica: ReplicaGasLimits { 20 | update: 140_000, 21 | prove: 200_000, 22 | process: 1_700_000, 23 | prove_and_process: 1_900_000, 24 | double_update: 200_000, 25 | }, 26 | connection_manager: ConnectionManagerGasLimits { 27 | owner_unenroll_replica: 120_000, 28 | unenroll_replica: 120_000, 29 | }, 30 | }, 31 | bridge: BridgeGasConfig { 32 | bridge_router: BridgeRouterGasLimits { send: 500_000 }, 33 | eth_helper: EthHelperGasLimits { 34 | send: 800_000, 35 | send_to_evm_like: 800_000, 36 | }, 37 | }, 38 | }; 39 | -------------------------------------------------------------------------------- /configuration/src/traits/env.rs: -------------------------------------------------------------------------------- 1 | /// Implemented by structs overridable through environment variables 2 | pub trait EnvOverridable { 3 | /// Override self.fields through env vars 4 | fn load_env_overrides(&mut self); 5 | } 6 | 7 | /// Given optional prefix and network, concatenate values to create full prefix. 8 | /// If both prefix and network provided, structure is 9 | /// network_prefix_postfix. If no network provided, structure is 10 | /// prefix_postfix. If no prefix, structure is network_postfix. Panic if no 11 | /// network or prefix. 12 | pub fn full_prefix(prefix: Option<&str>, network: Option<&str>) -> String { 13 | if let Some(prefix) = prefix { 14 | if let Some(network) = network { 15 | format!("{}_{}", network, prefix) 16 | } else { 17 | prefix.to_owned() 18 | } 19 | } else { 20 | if let Some(network) = network { 21 | return network.to_owned(); 22 | } 23 | 24 | panic!("Cannot call from_env without providing a prefix or network"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /configuration/src/traits/mod.rs: -------------------------------------------------------------------------------- 1 | //! Nomad configuration traits 2 | 3 | mod env; 4 | pub use env::*; 5 | -------------------------------------------------------------------------------- /configuration/src/utils.rs: -------------------------------------------------------------------------------- 1 | /// Get specified env var with format network_var OR get default_var if 2 | /// network-specific not present. 3 | pub fn network_or_default_from_env(network: &str, var: &str) -> Option { 4 | let mut value = std::env::var(&format!( 5 | "{}_{}", 6 | network.to_uppercase(), 7 | var.to_uppercase() 8 | )) 9 | .ok(); 10 | 11 | if value.is_none() { 12 | value = std::env::var(&format!("DEFAULT_{}", var.to_uppercase())).ok(); 13 | } 14 | 15 | value 16 | } 17 | -------------------------------------------------------------------------------- /configuration/src/wasm/bindings.rs: -------------------------------------------------------------------------------- 1 | //! Functions exported to Wasm 2 | 3 | use wasm_bindgen::prelude::*; 4 | 5 | use eyre::WrapErr; 6 | 7 | use crate::wasm::types::*; 8 | 9 | macro_rules! deser { 10 | ($val:ident, $expected:ty) => {{ 11 | let val = $val 12 | .into_serde::<$expected>() 13 | .wrap_err(format!( 14 | "Error while deserializing Javascript object to {}", 15 | stringify!($expected) 16 | )) 17 | .map_err(format_errs)?; 18 | val 19 | }}; 20 | } 21 | 22 | macro_rules! deser_config { 23 | ($val:ident) => {{ 24 | let config = deser!($val, crate::NomadConfig); 25 | config 26 | }}; 27 | } 28 | 29 | macro_rules! to_js_val { 30 | ($item:expr) => { 31 | JsValue::from_serde(&$item) 32 | .map(Into::into) 33 | .wrap_err("Error serializing value for return to Javascript") 34 | .map_err(format_errs) 35 | }; 36 | } 37 | 38 | macro_rules! ret_config { 39 | ($config:expr) => { 40 | to_js_val!($config) 41 | }; 42 | } 43 | 44 | type JsResult = std::result::Result; 45 | 46 | /// Convert any display type into a string for javascript errors 47 | fn format_errs(e: impl std::fmt::Display) -> wasm_bindgen::prelude::JsValue { 48 | format!("{:#}", e).into() 49 | } 50 | 51 | /// Get a built-in config 52 | #[wasm_bindgen(js_name = getBuiltin)] 53 | pub fn get_builtin(name: &str) -> JsResult { 54 | ret_config!(crate::builtin::get_builtin(name) 55 | .ok_or_else(|| eyre::eyre!("No builtin config found for environment named {}", name)) 56 | .map_err(format_errs)? 57 | .clone()) 58 | } 59 | 60 | /// Syntactically validate a config. Throw an error if invalid 61 | #[wasm_bindgen(js_name = validateConfig)] 62 | pub fn validate_config(val: &NomadConfig) -> JsResult { 63 | deser_config!(val); 64 | Ok(JsValue::NULL) 65 | } 66 | 67 | /// Make a new blank config 68 | #[wasm_bindgen(js_name = blankConfig)] 69 | pub fn blank_config() -> NomadConfig { 70 | to_js_val!(crate::NomadConfig::default()).unwrap() 71 | } 72 | 73 | /// Parse a json string into a config 74 | #[wasm_bindgen(js_name = configFromString)] 75 | pub fn config_from_string(s: &str) -> JsResult { 76 | let config = serde_json::from_str::(s) 77 | .wrap_err("Unable to deserialize config from string") 78 | .map_err(format_errs)?; 79 | 80 | ret_config!(config) 81 | } 82 | 83 | /// Add a network to the config 84 | #[wasm_bindgen(js_name = addNetwork)] 85 | pub fn add_domain(config: &NomadConfig, domain: &Domain) -> JsResult { 86 | let mut config = deser_config!(config); 87 | let domain = deser!(domain, crate::network::Domain); 88 | config.add_domain(domain).map_err(format_errs)?; 89 | ret_config!(config) 90 | } 91 | 92 | /// Add a network to the config 93 | #[wasm_bindgen(js_name = addCore)] 94 | pub fn add_core( 95 | config: &NomadConfig, 96 | name: &str, 97 | core: &CoreDeploymentInfo, 98 | ) -> JsResult { 99 | let mut config = deser_config!(config); 100 | let core = deser!(core, crate::core::CoreDeploymentInfo); 101 | config.add_core(name, core).map_err(format_errs)?; 102 | ret_config!(config) 103 | } 104 | 105 | /// Add a network to the config 106 | #[wasm_bindgen(js_name = addBridge)] 107 | pub fn add_bridge( 108 | config: &NomadConfig, 109 | name: &str, 110 | bridge: &BridgeDeploymentInfo, 111 | ) -> JsResult { 112 | let mut config = deser_config!(config); 113 | let bridge = deser!(bridge, crate::bridge::BridgeDeploymentInfo); 114 | config.add_bridge(name, bridge).map_err(format_errs)?; 115 | ret_config!(config) 116 | } 117 | -------------------------------------------------------------------------------- /configuration/src/wasm/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod bindings; 2 | 3 | pub mod types; 4 | -------------------------------------------------------------------------------- /fixtures/env.external: -------------------------------------------------------------------------------- 1 | # Matches fixtures/external_config.json 2 | 3 | AGENT_HOME_NAME=ethereum 4 | AGENT_REPLICA_0_NAME=polygon 5 | 6 | ETHEREUM_RPCSTYLE=ethereum 7 | POLYGON_RPCSTYLE=ethereum 8 | 9 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 10 | POLYGON_CONNECTION_URL=https://polygon-rpc.com 11 | 12 | ETHEREUM_SUBMITTER_TYPE=local 13 | ETHEREUM_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 14 | 15 | POLYGON_SUBMITTER_TYPE=gelato 16 | POLYGON_GELATO_SPONSOR_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 17 | POLYGON_GELATO_FEETOKEN=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 18 | 19 | ATTESTATION_SIGNER_ID=dummy_id 20 | -------------------------------------------------------------------------------- /fixtures/env.partial: -------------------------------------------------------------------------------- 1 | # Only runs Ethereum <> Moonbeam from test config 2 | 3 | RUN_ENV=test 4 | AGENT_HOME_NAME=ethereum 5 | AGENT_REPLICA_0_NAME=moonbeam 6 | 7 | ETHEREUM_RPCSTYLE=ethereum 8 | MOONBEAM_RPCSTYLE=ethereum 9 | 10 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 11 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 12 | 13 | ETHEREUM_SUBMITTER_TYPE=local 14 | ETHEREUM_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 15 | 16 | MOONBEAM_SUBMITTER_TYPE=gelato 17 | MOONBEAM_GELATO_SPONSOR_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 18 | MOONBEAM_GELATO_FEETOKEN=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 19 | 20 | ATTESTATION_SIGNER_ID=dummy_id 21 | -------------------------------------------------------------------------------- /fixtures/env.test: -------------------------------------------------------------------------------- 1 | # Matches configuration/configs/test.json 2 | 3 | RUST_BACKTRACE=1 4 | 5 | RUN_ENV=test 6 | AGENT_HOME_NAME=ethereum 7 | AGENT_REPLICAS_ALL=true 8 | 9 | ETHEREUM_RPCSTYLE=ethereum 10 | MOONBEAM_RPCSTYLE=ethereum 11 | EVMOS_RPCSTYLE=ethereum 12 | AVAIL_RPCSTYLE=substrate 13 | 14 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 15 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 16 | EVMOS_CONNECTION_URL=https://eth.bd.evmos.org:8545 17 | AVAIL_CONNECTION_URL=https://rpc.polkadot.io 18 | 19 | ETHEREUM_SUBMITTER_TYPE=local 20 | ETHEREUM_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 21 | 22 | MOONBEAM_SUBMITTER_TYPE=gelato 23 | MOONBEAM_GELATO_SPONSOR_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 24 | MOONBEAM_GELATO_FEETOKEN=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 25 | 26 | EVMOS_SUBMITTER_TYPE=local 27 | EVMOS_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 28 | 29 | AVAIL_SUBMITTER_TYPE=local 30 | AVAIL_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 31 | 32 | ATTESTATION_SIGNER_ID=dummy_id 33 | -------------------------------------------------------------------------------- /fixtures/env.test-agents: -------------------------------------------------------------------------------- 1 | # Agent environment var overrides 2 | 3 | KATHY_CHAT_MESSAGES="Chat message 1,Chat message 2,Chat message 3" 4 | KATHY_INTERVAL=999 5 | KATHY_ENABLED=true 6 | 7 | PROCESSOR_ALLOWED=0x1111111111111111111111111111111111111111111111111111111111111111,0x1111111111111111111111111111111111111111111111111111111111111112,0x1111111111111111111111111111111111111111111111111111111111111113 8 | PROCESSOR_DENIED=0x1111111111111111111111111111111111111111111111111111111111111111,0x1111111111111111111111111111111111111111111111111111111111111112,0x1111111111111111111111111111111111111111111111111111111111111113 9 | PROCESSOR_SUBSIDIZED_REMOTES=chain1,chain2,chain3 10 | PROCESSOR_S3_BUCKET=aws-bucket 11 | PROCESSOR_S3_REGION=region-1 12 | PROCESSOR_INTERVAL=999 13 | PROCESSOR_ENABLED=true 14 | 15 | # For settings tests 16 | 17 | RUN_ENV=test 18 | AGENT_HOME_NAME=ethereum 19 | AGENT_REPLICAS_ALL=true 20 | 21 | DEFAULT_RPCSTYLE=ethereum 22 | 23 | DEFAULT_SUBMITTER_TYPE=local 24 | 25 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 26 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 27 | EVMOS_CONNECTION_URL=https://eth.bd.evmos.org:8545 28 | 29 | DEFAULT_TXSIGNER_ID=default_id 30 | 31 | ATTESTATION_SIGNER_ID=dummy_id 32 | -------------------------------------------------------------------------------- /fixtures/env.test-gelato-default: -------------------------------------------------------------------------------- 1 | RUN_ENV=test 2 | AGENT_HOME_NAME=ethereum 3 | AGENT_REPLICAS_ALL=true 4 | 5 | DEFAULT_RPCSTYLE=ethereum 6 | AVAIL_RPCSTYLE=substrate 7 | 8 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 9 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 10 | EVMOS_CONNECTION_URL=https://eth.bd.evmos.org:8545 11 | AVAIL_CONNECTION_URL=https://rpc.polkadot.io 12 | 13 | DEFAULT_SUBMITTER_TYPE=gelato 14 | DEFAULT_GELATO_SPONSOR_ID=default_id 15 | DEFAULT_GELATO_FEETOKEN=0x1234 16 | 17 | AVAIL_SUBMITTER_TYPE=local 18 | AVAIL_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 19 | 20 | ATTESTATION_SIGNER_ID=dummy_id 21 | -------------------------------------------------------------------------------- /fixtures/env.test-killswitch: -------------------------------------------------------------------------------- 1 | CONFIG_PATH=../../fixtures/killswitch_config.json 2 | DEFAULT_RPCSTYLE=ethereum 3 | DEFAULT_SUBMITTER_TYPE=local 4 | 5 | EVMOSTESTNET_CONNECTION_URL=https://eth.bd.evmos.dev:8545.bad.url 6 | GOERLI_CONNECTION_URL=https://goerli-light.eth.linkpool.io.bad.url 7 | POLYGONMUMBAI_CONNECTION_URL=https://rpc-mumbai.maticvigil.com.bad.url 8 | RINKEBY_CONNECTION_URL=https://rinkeby-light.eth.linkpool.io.bad.url 9 | 10 | EVMOSTESTNET_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 11 | GOERLI_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 12 | POLYGONMUMBAI_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 13 | RINKEBY_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 14 | 15 | EVMOSTESTNET_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 16 | GOERLI_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 17 | POLYGONMUMBAI_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 18 | RINKEBY_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 19 | -------------------------------------------------------------------------------- /fixtures/env.test-killswitch-builtin: -------------------------------------------------------------------------------- 1 | RUN_ENV=test 2 | DEFAULT_RPCSTYLE=ethereum 3 | DEFAULT_SUBMITTER_TYPE=local 4 | 5 | MOONBEAM_CONNECTION_URL=https://eth.bd.evmos.dev:8545.bad.url 6 | ETHEREUM_CONNECTION_URL=https://goerli-light.eth.linkpool.io.bad.url 7 | AVAIL_CONNECTION_URL=https://rpc-mumbai.maticvigil.com.bad.url 8 | EVMOS_CONNECTION_URL=https://rinkeby-light.eth.linkpool.io.bad.url 9 | 10 | MOONBEAM_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 11 | ETHEREUM_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 12 | AVAIL_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 13 | EVMOS_TXSIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 14 | 15 | MOONBEAM_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 16 | ETHEREUM_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 17 | AVAIL_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 18 | EVMOS_ATTESTATION_SIGNER_KEY=0x0000000000000000000000000000000000000000000000000000000000000123 19 | -------------------------------------------------------------------------------- /fixtures/env.test-local-signer-default: -------------------------------------------------------------------------------- 1 | # Matches configuration/configs/test.json 2 | 3 | RUN_ENV=test 4 | AGENT_HOME_NAME=ethereum 5 | AGENT_REPLICAS_ALL=true 6 | 7 | DEFAULT_RPCSTYLE=ethereum 8 | AVAIL_RPCSTYLE=substrate 9 | 10 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 11 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 12 | EVMOS_CONNECTION_URL=https://eth.bd.evmos.org:8545 13 | AVAIL_CONNECTION_URL=https://rpc.polkadot.io 14 | 15 | DEFAULT_SUBMITTER_TYPE=local 16 | DEFAULT_TXSIGNER_ID=default_id 17 | 18 | AVAIL_SUBMITTER_TYPE=local 19 | AVAIL_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 20 | 21 | ATTESTATION_SIGNER_ID=dummy_id 22 | -------------------------------------------------------------------------------- /fixtures/env.test-local-signer-mixed: -------------------------------------------------------------------------------- 1 | # Matches configuration/configs/test.json 2 | 3 | RUN_ENV=test 4 | AGENT_HOME_NAME=ethereum 5 | AGENT_REPLICAS_ALL=true 6 | 7 | DEFAULT_RPCSTYLE=ethereum 8 | EVMOS_RPCSTYLE=ethereum 9 | AVAIL_RPCSTYLE=substrate 10 | 11 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 12 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 13 | EVMOS_CONNECTION_URL=https://eth.bd.evmos.org:8545 14 | AVAIL_CONNECTION_URL=https://rpc.polkadot.io 15 | 16 | DEFAULT_SUBMITTER_TYPE=local 17 | 18 | ETHEREUM_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 19 | 20 | MOONBEAM_TXSIGNER_ID=moonbeam_id 21 | 22 | DEFAULT_TXSIGNER_ID=default_id 23 | 24 | AVAIL_SUBMITTER_TYPE=local 25 | AVAIL_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 26 | 27 | ATTESTATION_SIGNER_ID=dummy_id 28 | -------------------------------------------------------------------------------- /fixtures/env.test-multi-vm: -------------------------------------------------------------------------------- 1 | # Matches configuration/configs/test.json 2 | 3 | RUST_BACKTRACE=1 4 | 5 | RUN_ENV=test 6 | AGENT_HOME_NAME=ethereum 7 | AGENT_REPLICAS_ALL=true 8 | 9 | ETHEREUM_RPCSTYLE=ethereum 10 | MOONBEAM_RPCSTYLE=ethereum 11 | EVMOS_RPCSTYLE=ethereum 12 | AVAIL_RPCSTYLE=substrate 13 | 14 | ETHEREUM_CONNECTION_URL=https://main-light.eth.linkpool.io/ 15 | MOONBEAM_CONNECTION_URL=https://rpc.api.moonbeam.network 16 | EVMOS_CONNECTION_URL=https://eth.bd.evmos.org:8545 17 | AVAIL_CONNECTION_URL=https://rpc.polkadot.io 18 | 19 | ETHEREUM_SUBMITTER_TYPE=local 20 | ETHEREUM_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 21 | 22 | MOONBEAM_SUBMITTER_TYPE=gelato 23 | MOONBEAM_GELATO_SPONSOR_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 24 | MOONBEAM_GELATO_FEETOKEN=0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE 25 | 26 | EVMOS_SUBMITTER_TYPE=local 27 | EVMOS_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 28 | 29 | AVAIL_SUBMITTER_TYPE=local 30 | AVAIL_TXSIGNER_KEY=0x1111111111111111111111111111111111111111111111111111111111111111 31 | 32 | ATTESTATION_SIGNER_ID=dummy_id 33 | -------------------------------------------------------------------------------- /fixtures/external_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "__COMMENT__": "matches configuration/configs/test.json", 3 | "rpcs": { 4 | "ethereum": { 5 | "rpcStyle": "ethereum", 6 | "connection": "https://main-light.eth.linkpool.io/" 7 | }, 8 | "polygon": { 9 | "rpcStyle": "ethereum", 10 | "connection": "wss://polygon-rpc.com" 11 | } 12 | }, 13 | "txSubmitters": { 14 | "ethereum": { 15 | "rpcStyle": "ethereum", 16 | "submitterType": "local", 17 | "submitter": "0x1111111111111111111111111111111111111111111111111111111111111111" 18 | }, 19 | "polygon": { 20 | "rpcStyle": "ethereum", 21 | "submitterType": "gelato", 22 | "submitter": { 23 | "sponsor": "0x1111111111111111111111111111111111111111111111111111111111111111", 24 | "feeToken": "0xabc" 25 | } 26 | } 27 | }, 28 | "attestationSigner": { 29 | "id": "hello", 30 | "region": "world" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /fixtures/killswitch_secrets.testing.yaml: -------------------------------------------------------------------------------- 1 | # Equivalent to `CONFIG_URL` 2 | configUrl: "" 3 | 4 | # Equivalent to `CONFIG_PATH`. Included for testing only 5 | configPath: fixtures/killswitch_config.json 6 | 7 | # Equivalent to the set of `_CONNECTION_URL` 8 | connectionUrls: 9 | RINKEBY_CONNECTION_URL: https://rinkeby-light.eth.linkpool.io.bad.url 10 | POLYGONMUMBAI_CONNECTION_URL: https://rpc-mumbai.maticvigil.com.bad.url 11 | GOERLI_CONNECTION_URL: https://goerli-light.eth.linkpool.io.bad.url 12 | EVMOSTESTNET_CONNECTION_URL: https://eth.bd.evmos.dev:8545.bad.url 13 | 14 | # Equivalent to the set of `_TXSIGNER_ID` 15 | txsignerIds: 16 | RINKEBY_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 17 | POLYGONMUMBAI_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 18 | GOERLI_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 19 | EVMOSTESTNET_TXSIGNER_ID: 00000000-0000-0000-0000-000000000000 20 | 21 | # Equivalent to the set of `_ATTESTATION_SIGNER_ID` 22 | attestationSignerIds: 23 | RINKEBY_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 24 | POLYGONMUMBAI_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 25 | GOERLI_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 26 | EVMOSTESTNET_ATTESTATION_SIGNER_ID: 00000000-0000-0000-0000-000000000000 27 | -------------------------------------------------------------------------------- /fixtures/test_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "__COMMENT__": "matches configuration/configs/test.json", 3 | "rpcs": { 4 | "ethereum": { 5 | "rpcStyle": "ethereum", 6 | "connection": "https://main-light.eth.linkpool.io/" 7 | }, 8 | "moonbeam": { 9 | "rpcStyle": "ethereum", 10 | "connection": "wss://rpc.api.moonbeam.network" 11 | }, 12 | "evmos": { 13 | "rpcStyle": "ethereum", 14 | "connection": "https://eth.bd.evmos.org:8545" 15 | }, 16 | "avail": { 17 | "rpcStyle": "substrate", 18 | "connection": "https://rpc.polkadot.io" 19 | } 20 | }, 21 | "txSubmitters": { 22 | "ethereum": { 23 | "rpcStyle": "ethereum", 24 | "submitterType": "local", 25 | "submitter": "0x1111111111111111111111111111111111111111111111111111111111111111" 26 | }, 27 | "moonbeam": { 28 | "rpcStyle": "ethereum", 29 | "submitterType": "gelato", 30 | "submitter": { 31 | "sponsor": "0x1111111111111111111111111111111111111111111111111111111111111111", 32 | "feeToken": "0xabc" 33 | } 34 | }, 35 | "evmos": { 36 | "rpcStyle": "ethereum", 37 | "submitterType": "local", 38 | "submitter": "0x1111111111111111111111111111111111111111111111111111111111111111" 39 | }, 40 | "avail": { 41 | "rpcStyle": "substrate", 42 | "submitterType": "local", 43 | "submitter": "0x1111111111111111111111111111111111111111111111111111111111111111" 44 | } 45 | }, 46 | "attestationSigner": { 47 | "id": "hello", 48 | "region": "world" 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /nomad-base/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | - fix: instrument futures, not joinhandles 4 | 5 | ### Unreleased 6 | 7 | - Have both Home/Replica and Home/Replica indexers return `Self::Error` 8 | - Add `Home` and `HomeIndexer` support for Substrate variants as well as allowing configuration of Substrate objects 9 | - Add `AttestationSigner` type alias that wraps `EthereumSigner` 10 | - Add new `ChainCommunicationError` to wrap `nomad_ethereum::EthereumError` and `nomad_substrate::SubstrateError` 11 | - Add implementations for converting chain-specific error enums into `ChainCommunicationError` to catch reverts 12 | - un-nest, simplify & add event to setup code for determining which replicas to 13 | run 14 | - un-nest, simplify & add event to setup code for config source discovery 15 | - emit event for source of config loaded at bootup 16 | - implement `std::fmt::Display` for `Home` and `Replica` enums 17 | - add home and remote labels to contract sync metrics for event differentiation 18 | - add `CONFIG_URL` check to `decl_settings` to optionally fetch config from a remote url 19 | - prometheus metrics accepts port by env var 20 | - bug: add checks for empty replica name arrays in `NomadAgent::run_many` and 21 | `NomadAgent::run_all` 22 | - add `previously_attempted` to the DB schema 23 | - remove `enabled` flag from agents project-wide 24 | - adds a changelog 25 | -------------------------------------------------------------------------------- /nomad-base/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-base" 3 | version = "0.1.0" 4 | authors = ["James Prestwich "] 5 | edition = "2021" 6 | 7 | 8 | [dependencies] 9 | # Main block 10 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = { version = "1.0", default-features = false } 13 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 14 | thiserror = { version = "1.0.22", default-features = false } 15 | async-trait = { version = "0.1.42", default-features = false } 16 | futures-util = "0.3.12" 17 | color-eyre = "0.6.0" 18 | tracing = "0.1.35" 19 | tracing-futures = "0.2.5" 20 | tracing-error = "0.2.0" 21 | tracing-subscriber = { version = "0.3.14", features = ["json"] } 22 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 23 | mockall = "0.10.2" 24 | rand = "0.8.3" 25 | 26 | nomad-xyz-configuration = { path = "../configuration" } 27 | nomad-types = { path = "../nomad-types" } 28 | nomad-core = { path = "../nomad-core" } 29 | nomad-ethereum = { path = "../chains/nomad-ethereum"} 30 | nomad-substrate = { path = "../chains/nomad-substrate"} 31 | nomad-test = { path = "../nomad-test" } 32 | affix = "0.1.2" 33 | 34 | prometheus = "0.12" 35 | 36 | warp = "0.3" 37 | 38 | # these versions are important! 39 | tracing-opentelemetry = "0.13.0" 40 | opentelemetry = { version = "0.14.0", features = ["rt-tokio"] } 41 | rusoto_core = "0.48.0" 42 | rusoto_kms = "0.48.0" 43 | once_cell = "1.8.0" 44 | 45 | [[example]] 46 | name = "example" 47 | path = "./bin/example.rs" 48 | 49 | [[bin]] 50 | name = "secrets-template" 51 | path = "./src/bin/secrets_template.rs" -------------------------------------------------------------------------------- /nomad-base/bin/example.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::Result; 2 | 3 | // use nomad_base::{decl_settings, NomadAgent}; 4 | 5 | // /// Example agent 6 | // pub struct Example; 7 | 8 | // // Example settings block containing addition agent-specific fields 9 | // #[derive(Debug, Clone, serde::Deserialize)] 10 | // pub struct ExampleConfig { 11 | // interval: u64, 12 | // } 13 | 14 | // // Generate ExampleSettings which includes base and agent-specific settings 15 | // decl_settings!(Example, ExampleConfig,); 16 | 17 | // /// An example main function for any agent that implemented Default 18 | // async fn _example_main(settings: ExampleSettings) -> Result<()> 19 | // where 20 | // NA: NomadAgent + Sized + 'static, 21 | // { 22 | // // Instantiate an agent 23 | // let oa = NA::from_settings(settings).await?; 24 | // oa.start_tracing(oa.metrics().span_duration())?; 25 | 26 | // // Use the agent to run a number of replicas 27 | // oa.run_all().await? 28 | // } 29 | 30 | // /// Read settings from the config file and set up reporting and logging based 31 | // /// on the settings 32 | // #[allow(dead_code)] 33 | // fn setup() -> Result { 34 | // color_eyre::install()?; 35 | 36 | // let settings = ExampleSettings::new()?; 37 | 38 | // Ok(settings) 39 | // } 40 | 41 | // #[allow(dead_code)] 42 | fn main() -> Result<()> { 43 | // let _settings = setup()?; 44 | // tokio::runtime::Builder::new_current_thread() 45 | // .enable_all() 46 | // .build() 47 | // .unwrap() 48 | // .block_on(_example_main(settings))?; 49 | 50 | Ok(()) 51 | } 52 | -------------------------------------------------------------------------------- /nomad-base/src/bin/secrets_template.rs: -------------------------------------------------------------------------------- 1 | use serde_json::json; 2 | use std::{fs::OpenOptions, io::Write}; 3 | 4 | #[allow(dead_code)] 5 | fn main() { 6 | let env = std::env::var("RUN_ENV").expect("missing RUN_ENV env var"); 7 | let config = nomad_xyz_configuration::get_builtin(&env).expect("!config"); 8 | 9 | let mut template = json!({ 10 | "rpcs": {}, 11 | "transactionSigners": {}, 12 | "attestationSigner": { 13 | "type": "hexKey", 14 | "key": "", 15 | }, 16 | }); 17 | 18 | for network in config.networks.iter() { 19 | let rpc_style = config.agent().get(network).expect("!agent").rpc_style; 20 | template["rpcs"].as_object_mut().unwrap().insert( 21 | network.to_owned(), 22 | json!({ 23 | "rpcStyle": serde_json::to_string(&rpc_style).expect("!rpcStyle"), 24 | "connection": { 25 | "type": "http", 26 | "url": "" 27 | }, 28 | }), 29 | ); 30 | 31 | template["transactionSigners"] 32 | .as_object_mut() 33 | .unwrap() 34 | .insert( 35 | network.to_owned(), 36 | json!({ 37 | "type": "hexKey", 38 | "key": "" 39 | }), 40 | ); 41 | } 42 | 43 | let mut file = OpenOptions::new() 44 | .write(true) 45 | .create(true) 46 | .truncate(true) 47 | .open("secrets.json") 48 | .expect("Failed to open/create file"); 49 | 50 | file.write_all(template.to_string().as_bytes()) 51 | .expect("Failed to write to file"); 52 | } 53 | -------------------------------------------------------------------------------- /nomad-base/src/contract_sync/metrics.rs: -------------------------------------------------------------------------------- 1 | use crate::CoreMetrics; 2 | use prometheus::{HistogramVec, IntGaugeVec}; 3 | use std::sync::Arc; 4 | 5 | /// Struct encapsulating prometheus metrics used by the ContractSync. 6 | #[derive(Debug, Clone)] 7 | pub struct ContractSyncMetrics { 8 | /// Most recently indexed block height (label values differentiate updates 9 | /// vs. messages) 10 | pub indexed_height: IntGaugeVec, 11 | /// Histogram of latencies from update emit to store 12 | pub store_event_latency: HistogramVec, 13 | /// Events stored into DB (label values differentiate updates vs. messages) 14 | pub stored_events: IntGaugeVec, 15 | } 16 | 17 | impl ContractSyncMetrics { 18 | /// Instantiate a new ContractSyncMetrics object. 19 | pub fn new(metrics: Arc) -> Self { 20 | let indexed_height = metrics 21 | .new_int_gauge_vec( 22 | "contract_sync_block_height", 23 | "Height of a recently observed block", 24 | &["data_type", "home", "replica", "agent"], 25 | ) 26 | .expect("failed to register block_height metric"); 27 | let store_event_latency = metrics 28 | .new_histogram( 29 | "contract_sync_store_event_latency", 30 | "Latency between event emit and event store in db.", 31 | &["data_type", "home", "replica", "agent"], 32 | &[ 33 | 0.0, 10.0, 20.0, 30.0, 40.0, 50.0, 60.0, 70.0, 80.0, 90.0, 100.0, 120.0, 140.0, 34 | 160.0, 180.0, 200.0, 250.0, 300.0, 350.0, 400.0, 450.0, 500.0, 1000.0, 2000.0, 35 | ], 36 | ) 37 | .expect("failed to register store_event_latency metric"); 38 | 39 | let stored_events = metrics 40 | .new_int_gauge_vec( 41 | "contract_sync_stored_events", 42 | "Number of events stored into db", 43 | &["data_type", "home", "replica", "agent"], 44 | ) 45 | .expect("failed to register stored_events metric"); 46 | 47 | ContractSyncMetrics { 48 | indexed_height, 49 | store_event_latency, 50 | stored_events, 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /nomad-base/src/contract_sync/schema.rs: -------------------------------------------------------------------------------- 1 | use crate::NomadDB; 2 | use color_eyre::Result; 3 | use nomad_core::db::DbError; 4 | 5 | static UPDATES_LAST_BLOCK_END: &str = "updates_last_block"; 6 | static MESSAGES_LAST_BLOCK_END: &str = "messages_last_block"; 7 | 8 | pub(crate) trait CommonContractSyncDB { 9 | fn store_update_latest_block_end(&self, latest_block: u32) -> Result<(), DbError>; 10 | fn retrieve_update_latest_block_end(&self) -> Option; 11 | } 12 | 13 | pub(crate) trait HomeContractSyncDB { 14 | fn store_message_latest_block_end(&self, latest_block: u32) -> Result<(), DbError>; 15 | fn retrieve_message_latest_block_end(&self) -> Option; 16 | } 17 | 18 | impl CommonContractSyncDB for NomadDB { 19 | fn store_update_latest_block_end(&self, latest_block: u32) -> Result<(), DbError> { 20 | self.store_encodable("", UPDATES_LAST_BLOCK_END, &latest_block) 21 | } 22 | 23 | fn retrieve_update_latest_block_end(&self) -> Option { 24 | self.retrieve_decodable("", UPDATES_LAST_BLOCK_END) 25 | .expect("db failure") 26 | } 27 | } 28 | 29 | impl HomeContractSyncDB for NomadDB { 30 | fn store_message_latest_block_end(&self, latest_block: u32) -> Result<(), DbError> { 31 | self.store_encodable("", MESSAGES_LAST_BLOCK_END, &latest_block) 32 | } 33 | 34 | fn retrieve_message_latest_block_end(&self) -> Option { 35 | self.retrieve_decodable("", MESSAGES_LAST_BLOCK_END) 36 | .expect("db failure") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /nomad-base/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This repo contains a simple framework for building Nomad agents. 2 | //! It has common utils and tools for configuring the app, interacting with the 3 | //! smart contracts, etc. 4 | //! 5 | //! Implementations of the `Home` and `Replica` traits on different chains 6 | //! ought to live here. 7 | 8 | #![forbid(unsafe_code)] 9 | #![warn(missing_docs)] 10 | #![warn(unused_extern_crates)] 11 | 12 | /// Settings for building agent 13 | pub mod settings; 14 | pub use settings::*; 15 | 16 | /// Base trait for an agent 17 | mod agent; 18 | pub use agent::*; 19 | 20 | #[doc(hidden)] 21 | #[cfg_attr(tarpaulin, skip)] 22 | #[macro_use] 23 | mod macros; 24 | pub use macros::*; 25 | 26 | /// DB related utilities 27 | mod nomad_db; 28 | pub use nomad_db::*; 29 | 30 | /// Base errors 31 | mod error; 32 | pub use error::*; 33 | 34 | /// Home type 35 | mod home; 36 | pub use home::*; 37 | 38 | /// Replica type 39 | mod replica; 40 | pub use replica::*; 41 | /// XAppConnectionManager type 42 | mod xapp; 43 | pub use xapp::*; 44 | 45 | mod metrics; 46 | pub use metrics::*; 47 | 48 | mod contract_sync; 49 | pub use contract_sync::*; 50 | 51 | mod indexer; 52 | pub use indexer::*; 53 | 54 | mod submitter; 55 | pub use submitter::*; 56 | 57 | /// Re-export signer trait for attestation signer. 58 | pub use ethers::signers::Signer; 59 | 60 | /// Attestation signer. Always uses Ethereum-style signing. 61 | pub type AttestationSigner = nomad_ethereum::EthereumSigners; 62 | -------------------------------------------------------------------------------- /nomad-base/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | /// Shortcut for aborting a joinhandle and then awaiting and discarding its result 3 | macro_rules! cancel_task { 4 | ($task:ident) => { 5 | #[allow(unused_must_use)] 6 | { 7 | $task.abort(); 8 | $task.await; 9 | } 10 | }; 11 | } 12 | 13 | #[macro_export] 14 | /// Shortcut for implementing agent traits 15 | macro_rules! impl_as_ref_core { 16 | ($agent:ident) => { 17 | impl AsRef for $agent { 18 | fn as_ref(&self) -> &nomad_base::AgentCore { 19 | &self.core 20 | } 21 | } 22 | }; 23 | } 24 | 25 | #[macro_export] 26 | /// Declare a new agent struct with the additional fields 27 | macro_rules! decl_agent { 28 | ( 29 | $(#[$outer:meta])* 30 | $name:ident{ 31 | $($prop:ident: $type:ty,)* 32 | }) => { 33 | 34 | $(#[$outer])* 35 | #[derive(Debug)] 36 | pub struct $name { 37 | $($prop: $type,)* 38 | core: nomad_base::AgentCore, 39 | } 40 | 41 | $crate::impl_as_ref_core!($name); 42 | }; 43 | } 44 | 45 | #[macro_export] 46 | /// Declare a new channel block 47 | /// ### Usage 48 | /// 49 | /// ```ignore 50 | /// decl_channel!(Relayer { 51 | /// updates_relayed_counts: prometheus::IntCounterVec, 52 | /// interval: u64, 53 | /// }); 54 | /// ``` 55 | macro_rules! decl_channel { 56 | ( 57 | $name:ident { 58 | $($(#[$tags:meta])* $prop:ident: $type:ty,)* 59 | } 60 | ) => { 61 | affix::paste! { 62 | #[derive(Debug, Clone)] 63 | #[doc = "Channel for `" $name "`"] 64 | pub struct [<$name Channel>] { 65 | pub(crate) base: nomad_base::ChannelBase, 66 | $( 67 | $(#[$tags])* 68 | pub(crate) $prop: $type, 69 | )* 70 | } 71 | 72 | impl AsRef for [<$name Channel>] { 73 | fn as_ref(&self) -> &nomad_base::ChannelBase { 74 | &self.base 75 | } 76 | } 77 | 78 | impl [<$name Channel>] { 79 | pub fn home(&self) -> Arc { 80 | self.as_ref().home.clone() 81 | } 82 | 83 | pub fn replica(&self) -> Arc { 84 | self.as_ref().replica.clone() 85 | } 86 | 87 | pub fn db(&self) -> nomad_base::NomadDB { 88 | self.as_ref().db.clone() 89 | } 90 | } 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /nomad-base/src/schema.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-xyz/rust/58558d7883dfca9e2fb80ecb91da9979bc2c7388/nomad-base/src/schema.rs -------------------------------------------------------------------------------- /nomad-base/src/settings/trace/mod.rs: -------------------------------------------------------------------------------- 1 | /// Configure a `tracing_subscriber::fmt` Layer outputting to stdout 2 | pub mod fmt; 3 | 4 | mod span_metrics; 5 | pub use span_metrics::TimeSpanLifetime; 6 | -------------------------------------------------------------------------------- /nomad-base/src/settings/trace/span_metrics.rs: -------------------------------------------------------------------------------- 1 | use tokio::time::Instant; 2 | use tracing::{span, Subscriber}; 3 | use tracing_subscriber::{layer::Context, registry::LookupSpan, Layer}; 4 | 5 | /// Records span lifetime into a prometheus histogram. 6 | pub struct TimeSpanLifetime { 7 | histogram: prometheus::HistogramVec, 8 | } 9 | 10 | impl TimeSpanLifetime { 11 | /// Constructor. 12 | pub fn new(histogram: prometheus::HistogramVec) -> Self { 13 | Self { histogram } 14 | } 15 | } 16 | 17 | struct SpanTiming { 18 | start: Instant, 19 | } 20 | 21 | impl Layer for TimeSpanLifetime 22 | where 23 | S: Subscriber + for<'a> LookupSpan<'a>, 24 | { 25 | fn on_new_span(&self, _: &span::Attributes<'_>, id: &span::Id, ctx: Context<'_, S>) { 26 | match ctx.span(id) { 27 | Some(span) => span.extensions_mut().insert(SpanTiming { 28 | start: Instant::now(), 29 | }), 30 | None => unreachable!(), 31 | } 32 | } 33 | 34 | fn on_close(&self, id: span::Id, ctx: Context) { 35 | let now = Instant::now(); 36 | match ctx.span(&id) { 37 | Some(span) => { 38 | let exts = span.extensions(); 39 | let timing = exts 40 | .get::() 41 | .expect("bug: didn't insert SpanTiming"); 42 | self.histogram 43 | .with_label_values(&[span.name(), span.metadata().target()]) 44 | .observe((now - timing.start).as_secs_f64()); 45 | } 46 | None => unreachable!(), 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /nomad-base/src/submitter.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /nomad-base/tests/signer_integration.rs: -------------------------------------------------------------------------------- 1 | use nomad_base::{AttestationSigner, Signer}; 2 | use nomad_core::FromSignerConf; 3 | use nomad_xyz_configuration::agent::SignerConf; 4 | 5 | #[tokio::test] 6 | async fn signer_auths() { 7 | if let Some(signer_conf) = SignerConf::from_env(Some("TEST_KMS"), None) { 8 | let signer = AttestationSigner::try_from_signer_conf(&signer_conf) 9 | .await 10 | .unwrap(); 11 | let message = "hello world"; 12 | signer.sign_message(message).await.unwrap(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /nomad-core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - Remove `Signers` enum in favor of breaking into separate `EthereumSigners` and `SubstrateSigners` types for submitting txs 6 | - Remove `ChainCommunication` in favor of new `ChainCommunicationError` error wrapper in `nomad-base` 7 | - Have `Home`, `Common`, and `ConnectionManager` traits return associated type errors instead of legacy `nomad_core::ChainCommunicationError` 8 | - require `Common: std::fmt::Display` 9 | - refactor: Add IRSA credentials to client instantiation 10 | - implement `Encode` and `Decode` for `bool` 11 | - adds a changelog 12 | -------------------------------------------------------------------------------- /nomad-core/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-core" 3 | version = "0.1.0" 4 | authors = ["James Prestwich "] 5 | edition = "2021" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [dependencies] 10 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", default-features = false, features = ['legacy'] } 11 | ethers-signers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features=["aws"] } 12 | ethers-providers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features=["ws", "rustls"] } 13 | subxt = "0.23.0" 14 | hex = "0.4.3" 15 | sha3 = "0.9.1" 16 | thiserror = "*" 17 | async-trait = { version = "0.1.42", default-features = false } 18 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 19 | tracing = "0.1.35" 20 | tracing-futures = "0.2.5" 21 | serde = {version = "1.0", features = ["derive"]} 22 | serde_json = {version = "1.0"} 23 | color-eyre = "0.6.0" 24 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 25 | prometheus = "0.12.0" 26 | bytes = { version = "1", features = ["serde"]} 27 | rusoto_core = "0.48.0" 28 | rusoto_kms = "0.48.0" 29 | rusoto_credential = "0.48.0" 30 | once_cell = "1.8.0" 31 | num = { version="0", features=["serde"] } 32 | 33 | accumulator = { path = "../accumulator" } 34 | nomad-types = { path = "../nomad-types" } 35 | nomad-xyz-configuration = { path = "../configuration" } 36 | rusoto_sts = "0.48.0" 37 | 38 | [dev-dependencies] 39 | tokio = { version = "1.0.1", features = ["rt", "time", "macros"] } 40 | 41 | [features] 42 | output = [] 43 | 44 | [[bin]] 45 | name = "proof_output" 46 | path = "bin/proof_output.rs" 47 | 48 | [[bin]] 49 | name = "lib_test_output" 50 | path = "bin/lib_test_output.rs" 51 | 52 | [[bin]] 53 | name = "utils_test_output" 54 | path = "bin/utils_test_output.rs" 55 | -------------------------------------------------------------------------------- /nomad-core/bin/lib_test_output.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "output")] 2 | use nomad_core::test_output::output_functions::*; 3 | 4 | fn main() { 5 | #[cfg(feature = "output")] 6 | { 7 | output_signed_updates(); 8 | output_signed_failure_notifications(); 9 | output_message_and_leaf(); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /nomad-core/bin/proof_output.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "output")] 2 | use nomad_core::test_output::output_functions::*; 3 | 4 | fn main() { 5 | #[cfg(feature = "output")] 6 | { 7 | output_merkle_proof(); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /nomad-core/bin/utils_test_output.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "output")] 2 | use nomad_core::test_output::output_functions::*; 3 | 4 | fn main() { 5 | #[cfg(feature = "output")] 6 | { 7 | output_home_domain_hashes(); 8 | output_destination_and_nonces(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /nomad-core/src/aws.rs: -------------------------------------------------------------------------------- 1 | use rusoto_core::{ 2 | credential::{AutoRefreshingProvider, ProvideAwsCredentials}, 3 | Client, HttpClient, 4 | }; 5 | use rusoto_kms::KmsClient; 6 | use rusoto_sts::WebIdentityProvider; 7 | use tokio::sync::OnceCell; 8 | 9 | static CLIENT: OnceCell = OnceCell::const_new(); 10 | static KMS_CLIENT: OnceCell = OnceCell::const_new(); 11 | 12 | // Try to get an irsa provider 13 | #[tracing::instrument] 14 | async fn try_irsa_provider() -> Option> { 15 | let irsa_provider = WebIdentityProvider::from_k8s_env(); 16 | 17 | // if there are no IRSA credentials this will error 18 | let result = irsa_provider.credentials().await; 19 | 20 | if result.is_err() { 21 | tracing::debug!(error = %result.as_ref().unwrap_err(), "Error in irsa provider instantiation"); 22 | } 23 | 24 | result 25 | .ok() 26 | .and_then(|_| AutoRefreshingProvider::new(irsa_provider).ok()) 27 | } 28 | 29 | /// Get a shared AWS client with credentials 30 | /// 31 | /// Credential precedence is as follows 32 | /// 1. IRSA 33 | /// 2. IAM 34 | /// 3. environment 35 | /// 4. Conf file 36 | pub async fn get_client() -> &'static Client { 37 | CLIENT 38 | .get_or_init(|| async { 39 | match try_irsa_provider().await { 40 | Some(credentials_provider) => { 41 | let dispatcher = HttpClient::new().unwrap(); 42 | Client::new_with(credentials_provider, dispatcher) 43 | } 44 | // if the IRSA provider returned no creds, use the default 45 | // credentials chain 46 | None => Client::shared(), 47 | } 48 | }) 49 | .await 50 | } 51 | 52 | /// Get a shared KMS client 53 | pub async fn get_kms_client() -> &'static KmsClient { 54 | KMS_CLIENT 55 | .get_or_init(|| async { 56 | let client = get_client().await.clone(); 57 | 58 | KmsClient::new_with_client(client, Default::default()) 59 | }) 60 | .await 61 | } 62 | -------------------------------------------------------------------------------- /nomad-core/src/chain.rs: -------------------------------------------------------------------------------- 1 | #![allow(missing_docs)] 2 | 3 | use color_eyre::eyre::Result; 4 | use nomad_types::NomadIdentifier; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | #[derive(Debug, Clone, Deserialize, Serialize)] 8 | pub struct Address(pub bytes::Bytes); 9 | 10 | #[derive(Debug, Clone, Deserialize, Serialize)] 11 | pub struct Balance(pub num::BigInt); 12 | 13 | #[derive(Debug, Clone, Serialize, Deserialize)] 14 | pub struct ContractLocator { 15 | pub name: String, 16 | pub domain: u32, 17 | pub address: NomadIdentifier, 18 | } 19 | impl std::fmt::Display for ContractLocator { 20 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 21 | write!( 22 | f, 23 | "{}[@{}]+contract:0x{:x}", 24 | self.name, self.domain, *self.address 25 | ) 26 | } 27 | } 28 | 29 | #[async_trait::async_trait] 30 | pub trait Chain { 31 | /// Query the balance on a chain 32 | async fn query_balance(&self, addr: Address) -> Result; 33 | } 34 | -------------------------------------------------------------------------------- /nomad-core/src/db/iterator.rs: -------------------------------------------------------------------------------- 1 | use crate::{Decode, Encode}; 2 | use rocksdb::DBIterator; 3 | use std::marker::PhantomData; 4 | 5 | /// An iterator over a prefix that deserializes values 6 | pub struct PrefixIterator<'a, V> { 7 | iter: DBIterator<'a>, 8 | prefix: &'a [u8], 9 | _phantom: PhantomData<*const V>, 10 | } 11 | 12 | impl<'a, V> PrefixIterator<'a, V> { 13 | /// Return new prefix iterator 14 | pub fn new(iter: DBIterator<'a>, prefix: &'a [u8]) -> Self { 15 | Self { 16 | iter, 17 | prefix, 18 | _phantom: PhantomData, 19 | } 20 | } 21 | } 22 | 23 | impl<'a, V> Iterator for PrefixIterator<'a, V> 24 | where 25 | V: Encode + Decode, 26 | { 27 | type Item = V; 28 | 29 | fn next(&mut self) -> Option { 30 | let prefix = self.prefix; 31 | self.iter 32 | .find(|(k, _)| k.strip_prefix(prefix).is_some()) 33 | .map(|(_, v)| v.to_vec()) 34 | .map(|v| V::read_from(&mut v.as_slice()).expect("!corrupt")) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /nomad-core/src/db/typed_db.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | db::{DbError, DB}, 3 | Decode, Encode, 4 | }; 5 | use color_eyre::Result; 6 | 7 | /// DB handle for storing data tied to a specific type/entity. 8 | /// 9 | /// Key structure: ```__``` 10 | #[derive(Debug, Clone)] 11 | pub struct TypedDB { 12 | entity: String, 13 | db: DB, 14 | } 15 | 16 | impl AsRef for TypedDB { 17 | fn as_ref(&self) -> &DB { 18 | &self.db 19 | } 20 | } 21 | 22 | impl TypedDB { 23 | /// Instantiate new `TypedDB` 24 | pub fn new(entity: String, db: DB) -> Self { 25 | Self { entity, db } 26 | } 27 | 28 | fn full_prefix(&self, prefix: impl AsRef<[u8]>) -> Vec { 29 | let mut full_prefix = vec![]; 30 | full_prefix.extend(self.entity.as_ref() as &[u8]); 31 | full_prefix.extend("_".as_bytes()); 32 | full_prefix.extend(prefix.as_ref()); 33 | full_prefix 34 | } 35 | 36 | /// Store encodable value 37 | pub fn store_encodable( 38 | &self, 39 | prefix: impl AsRef<[u8]>, 40 | key: impl AsRef<[u8]>, 41 | value: &V, 42 | ) -> Result<(), DbError> { 43 | self.db 44 | .store_encodable(self.full_prefix(prefix), key, value) 45 | } 46 | 47 | /// Retrieve decodable value 48 | pub fn retrieve_decodable( 49 | &self, 50 | prefix: impl AsRef<[u8]>, 51 | key: impl AsRef<[u8]>, 52 | ) -> Result, DbError> { 53 | self.db.retrieve_decodable(self.full_prefix(prefix), key) 54 | } 55 | 56 | /// Store encodable kv pair 57 | pub fn store_keyed_encodable( 58 | &self, 59 | prefix: impl AsRef<[u8]>, 60 | key: &K, 61 | value: &V, 62 | ) -> Result<(), DbError> { 63 | self.db 64 | .store_keyed_encodable(self.full_prefix(prefix), key, value) 65 | } 66 | 67 | /// Retrieve decodable value given encodable key 68 | pub fn retrieve_keyed_decodable( 69 | &self, 70 | prefix: impl AsRef<[u8]>, 71 | key: &K, 72 | ) -> Result, DbError> { 73 | self.db 74 | .retrieve_keyed_decodable(self.full_prefix(prefix), key) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /nomad-core/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Nomad. OPTimistic Interchain Communication 2 | //! 3 | //! This crate contains core primitives, traits, and types for Nomad 4 | //! implementations. 5 | 6 | #![warn(missing_docs)] 7 | #![warn(unused_extern_crates)] 8 | #![forbid(unsafe_code)] 9 | #![forbid(where_clauses_object_safety)] 10 | 11 | pub use accumulator; 12 | 13 | /// AWS global state and init 14 | pub mod aws; 15 | 16 | /// DB related utilities 17 | pub mod db; 18 | 19 | /// Model instantatiations of the on-chain structures 20 | pub mod models { 21 | /// A simple Home chain Nomad implementation 22 | mod home; 23 | 24 | /// A simple Replica chain Nomad implementation 25 | mod replica; 26 | 27 | pub use self::{home::*, replica::*}; 28 | } 29 | 30 | /// Async Traits for Homes & Replicas for use in applications 31 | mod traits; 32 | pub use traits::*; 33 | 34 | /// Utilities to match contract values 35 | pub mod utils; 36 | 37 | /// Testing utilities 38 | pub mod test_utils; 39 | 40 | /// Core nomad system data structures 41 | mod types; 42 | pub use types::*; 43 | 44 | /// Test functions that output json files for Solidity tests 45 | #[cfg(feature = "output")] 46 | pub mod test_output; 47 | 48 | mod chain; 49 | pub use chain::*; 50 | 51 | pub use nomad_types::NomadIdentifier; 52 | 53 | use ethers::core::types::{SignatureError, H256}; 54 | 55 | /// Enum for validity of a list (of updates or messages) 56 | #[derive(Debug)] 57 | pub enum ListValidity { 58 | /// Empty list 59 | Empty, 60 | /// Valid list 61 | Valid, 62 | /// Invalid list 63 | Invalid, 64 | } 65 | 66 | /// Error types for Nomad 67 | #[derive(Debug, thiserror::Error)] 68 | pub enum NomadError { 69 | /// Signature Error pasthrough 70 | #[error(transparent)] 71 | SignatureError(#[from] SignatureError), 72 | /// Update does not build off the current root 73 | #[error("Update has wrong current root. Expected: {expected}. Got: {actual}.")] 74 | WrongCurrentRoot { 75 | /// The provided root 76 | actual: H256, 77 | /// The current root 78 | expected: H256, 79 | }, 80 | /// Update specifies a new root that is not in the queue. This is an 81 | /// improper update and is slashable 82 | #[error("Update has unknown new root: {0}")] 83 | UnknownNewRoot(H256), 84 | /// IO error from Read/Write usage 85 | #[error(transparent)] 86 | IoError(#[from] std::io::Error), 87 | } 88 | -------------------------------------------------------------------------------- /nomad-core/src/models/mod.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomad-xyz/rust/58558d7883dfca9e2fb80ecb91da9979bc2c7388/nomad-core/src/models/mod.rs -------------------------------------------------------------------------------- /nomad-core/src/models/replica.rs: -------------------------------------------------------------------------------- 1 | use crate::{NomadError, SignedUpdate}; 2 | use ethers::core::types::{Address, H256, U256}; 3 | 4 | /// Waiting state 5 | #[derive(Debug, Clone, Copy, Default)] 6 | pub struct Waiting { 7 | root: H256, 8 | } 9 | 10 | /// Pending update state 11 | #[allow(dead_code)] 12 | #[derive(Debug, Clone, Copy)] 13 | pub struct Pending { 14 | root: H256, 15 | new_root: H256, 16 | timeout: U256, 17 | } 18 | 19 | /// Failed state 20 | #[derive(Debug, Clone, Copy)] 21 | pub struct Failed {} 22 | 23 | /// The Replica-chain Nomad object 24 | #[derive(Debug, Clone, Copy, Default)] 25 | pub struct Replica { 26 | remote: u32, 27 | local: u32, 28 | updater: Address, 29 | optimistic_wait: U256, 30 | state: S, 31 | } 32 | 33 | impl Replica { 34 | /// SLIP-44 id of the Home chain 35 | pub fn remote(&self) -> u32 { 36 | self.remote 37 | } 38 | 39 | /// SLIP-44 id of this Replica chain 40 | pub fn local(&self) -> u32 { 41 | self.local 42 | } 43 | 44 | /// Ethereum address of the updater 45 | 46 | pub fn updater(&self) -> Address { 47 | self.updater 48 | } 49 | 50 | /// The number of seconds to wait before optimistically accepting an update 51 | pub fn wait(&self) -> U256 { 52 | self.optimistic_wait 53 | } 54 | 55 | /// Current state 56 | pub fn state(&self) -> &S { 57 | &self.state 58 | } 59 | 60 | fn check_sig(&self, update: &SignedUpdate) -> Result<(), NomadError> { 61 | update.verify(self.updater) 62 | } 63 | 64 | /// Notify Replica of double update, and set to failed 65 | pub fn double_update( 66 | self, 67 | first: &SignedUpdate, 68 | second: &SignedUpdate, 69 | ) -> Result, Self> { 70 | if first == second || self.check_sig(first).is_err() || self.check_sig(second).is_err() { 71 | Err(self) 72 | } else { 73 | Ok(Replica { 74 | remote: self.remote, 75 | local: self.local, 76 | updater: self.updater, 77 | optimistic_wait: self.optimistic_wait, 78 | state: Failed {}, 79 | }) 80 | } 81 | } 82 | } 83 | 84 | impl Replica { 85 | /// Get the current root 86 | pub fn root(&self) -> H256 { 87 | self.state().root 88 | } 89 | 90 | /// Instantiate a new Replica. 91 | pub fn init(remote: u32, local: u32, updater: Address, optimistic_wait: U256) -> Self { 92 | Self { 93 | remote, 94 | local, 95 | updater, 96 | optimistic_wait, 97 | state: Waiting::default(), 98 | } 99 | } 100 | 101 | /// Submit an update 102 | pub fn update( 103 | self, 104 | update: &SignedUpdate, 105 | now: impl FnOnce() -> U256, 106 | ) -> Result, Self> { 107 | #[allow(clippy::question_mark)] 108 | if self.check_sig(update).is_err() { 109 | return Err(self); 110 | } 111 | 112 | Ok(Replica { 113 | remote: self.remote, 114 | local: self.local, 115 | updater: self.updater, 116 | optimistic_wait: self.optimistic_wait, 117 | state: Pending { 118 | root: self.state.root, 119 | new_root: update.update.new_root, 120 | timeout: now() + self.optimistic_wait, 121 | }, 122 | }) 123 | } 124 | } 125 | 126 | impl Replica { 127 | /// Get the current root 128 | pub fn root(&self) -> H256 { 129 | self.state().root 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /nomad-core/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use crate::accumulator::NomadProof; 2 | use ethers::core::types::H256; 3 | use std::{fs::File, io::Read, path::PathBuf}; 4 | 5 | /// Struct representing a single merkle test case 6 | #[derive(serde::Deserialize, serde::Serialize)] 7 | #[serde(rename_all = "camelCase")] 8 | pub struct MerkleTestCase { 9 | /// Test case name 10 | pub test_name: String, 11 | /// Leaves of merkle tree 12 | pub leaves: Vec, 13 | /// Proofs for leaves in tree 14 | pub proofs: Vec, 15 | /// Root of tree 16 | pub expected_root: H256, 17 | } 18 | 19 | /// Find a vector file assuming that a git checkout exists 20 | // TODO: look instead for the workspace `Cargo.toml`? use a cargo env var? 21 | pub fn find_test_fixtures(final_component: &str) -> PathBuf { 22 | let cwd = std::env::current_dir().expect("no cwd?"); 23 | let git_dir = cwd 24 | .ancestors() // . ; ../ ; ../../ ; ... 25 | .find(|d| d.join(".git").is_dir()) 26 | .expect("could not find .git somewhere! confused about workspace layout"); 27 | 28 | git_dir.join("fixtures").join(final_component) 29 | } 30 | 31 | /// Reads merkle test case json file and returns a vector of `MerkleTestCase`s 32 | pub fn load_merkle_test_json() -> Vec { 33 | let mut file = File::open(find_test_fixtures("merkle.json")).unwrap(); 34 | let mut data = String::new(); 35 | file.read_to_string(&mut data).unwrap(); 36 | serde_json::from_str(&data).unwrap() 37 | } 38 | -------------------------------------------------------------------------------- /nomad-core/src/traits/indexer.rs: -------------------------------------------------------------------------------- 1 | //! An Indexer provides a common interface for bubbling up chain-specific 2 | //! event-data to another entity (e.g. a `ContractSync`). For example, the only 3 | //! way to retrieve data such as the chain's latest block number or a list of 4 | //! updates/messages emitted within a certain block range by calling out to a 5 | //! chain-specific library and provider (e.g. ethers::provider). A 6 | //! chain-specific home or replica should implement one or both of the Indexer 7 | //! traits (CommonIndexer or HomeIndexer) to provide an common interface which 8 | //! other entities can retrieve this chain-specific info. 9 | 10 | use async_trait::async_trait; 11 | use color_eyre::Result; 12 | use std::error::Error as StdError; 13 | 14 | use crate::{RawCommittedMessage, SignedUpdateWithMeta}; 15 | 16 | /// Interface for Common contract indexer. Interface that allows for other 17 | /// entities to retrieve chain-specific data from a home or replica. 18 | #[async_trait] 19 | pub trait CommonIndexer: Send + Sync + std::fmt::Debug { 20 | /// Chain-specific error type 21 | type Error: StdError + Send + Sync; 22 | 23 | /// Get chain's latest block number 24 | async fn get_block_number(&self) -> Result; 25 | 26 | /// Fetch sequentially sorted list of updates between blocks `from` and `to` 27 | async fn fetch_sorted_updates( 28 | &self, 29 | from: u32, 30 | to: u32, 31 | ) -> Result, Self::Error>; 32 | } 33 | 34 | /// Interface for Home contract indexer. Interface for allowing other 35 | /// entities to retrieve chain-specific data from a home. 36 | #[async_trait] 37 | pub trait HomeIndexer: CommonIndexer + Send + Sync + std::fmt::Debug { 38 | /// Fetch list of messages between blocks `from` and `to`. 39 | async fn fetch_sorted_messages( 40 | &self, 41 | _from: u32, 42 | _to: u32, 43 | ) -> Result, ::Error>; 44 | } 45 | -------------------------------------------------------------------------------- /nomad-core/src/traits/mod.rs: -------------------------------------------------------------------------------- 1 | mod encode; 2 | mod home; 3 | mod indexer; 4 | mod replica; 5 | mod signer; 6 | mod xapp; 7 | 8 | use async_trait::async_trait; 9 | use color_eyre::Result; 10 | use ethers::core::types::H256; 11 | use std::{error::Error as StdError, fmt::Display}; 12 | 13 | use crate::{db::DbError, SignedUpdate}; 14 | 15 | pub use encode::*; 16 | pub use home::*; 17 | pub use indexer::*; 18 | pub use replica::*; 19 | pub use signer::*; 20 | pub use xapp::*; 21 | 22 | /// Box std error with send + sync 23 | pub type BoxStdError = Box; 24 | 25 | /// Contract states 26 | #[derive(Debug, PartialEq, Eq)] 27 | pub enum State { 28 | /// Contract uninitialized 29 | Uninitialized, 30 | /// Contract is active 31 | Active, 32 | /// Contract has failed 33 | Failed, 34 | } 35 | 36 | /// Returned by `check_double_update` if double update exists 37 | #[derive(Debug, Clone, PartialEq)] 38 | pub struct DoubleUpdate(pub SignedUpdate, pub SignedUpdate); 39 | 40 | impl Display for DoubleUpdate { 41 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 42 | write!(f, "DoubleUpdate {{ ")?; 43 | write!(f, "left: {} ", &self.0)?; 44 | write!(f, "right: {} ", &self.1)?; 45 | write!(f, " }}") 46 | } 47 | } 48 | 49 | /// The result of a transaction 50 | #[derive(Debug, Clone, Copy)] 51 | pub struct TxOutcome { 52 | /// The txid 53 | pub txid: H256, 54 | // TODO: more? What can be abstracted across all chains? 55 | } 56 | 57 | /// Interface for attributes shared by Home and Replica 58 | #[async_trait] 59 | pub trait Common: Sync + Send + std::fmt::Debug + std::fmt::Display { 60 | /// Chain-specific error type 61 | type Error: StdError + Send + Sync; 62 | 63 | /// Return an identifier (not necessarily unique) for the chain this 64 | /// contract is running on. 65 | fn name(&self) -> &str; 66 | 67 | /// Get the status of a transaction. 68 | async fn status(&self, txid: H256) -> Result, Self::Error>; 69 | 70 | /// Fetch the current updater value 71 | async fn updater(&self) -> Result; 72 | 73 | /// Fetch the current state. 74 | async fn state(&self) -> Result; 75 | 76 | /// Fetch the current root. 77 | async fn committed_root(&self) -> Result; 78 | 79 | /// Submit a signed update for inclusion 80 | async fn update(&self, update: &SignedUpdate) -> Result; 81 | 82 | /// Submit a double update for slashing 83 | async fn double_update(&self, double: &DoubleUpdate) -> Result; 84 | } 85 | 86 | /// Interface for retrieving event data emitted by both the home and replica 87 | #[async_trait] 88 | pub trait CommonEvents: Common + Send + Sync + std::fmt::Debug { 89 | /// Fetch the first signed update building off of `old_root`. If `old_root` 90 | /// was never accepted or has never been updated, this will return `Ok(None )`. 91 | /// This should fetch events from the chain API 92 | async fn signed_update_by_old_root( 93 | &self, 94 | old_root: H256, 95 | ) -> Result, DbError>; 96 | 97 | /// Fetch the first signed update with a new root of `new_root`. If update 98 | /// has not been produced, this will return `Ok(None)`. This should fetch 99 | /// events from the chain API 100 | async fn signed_update_by_new_root( 101 | &self, 102 | new_root: H256, 103 | ) -> Result, DbError>; 104 | } 105 | -------------------------------------------------------------------------------- /nomad-core/src/traits/replica.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use color_eyre::Result; 3 | use ethers::core::types::H256; 4 | 5 | use crate::{ 6 | accumulator::NomadProof, 7 | traits::{Common, TxOutcome}, 8 | NomadMessage, 9 | }; 10 | 11 | /// The status of a message in the replica 12 | pub enum MessageStatus { 13 | /// Message is unknown 14 | None, 15 | /// Message has been proven but not processed 16 | Proven(H256), 17 | /// Message has been processed 18 | Processed, 19 | } 20 | impl From for MessageStatus { 21 | fn from(status: H256) -> Self { 22 | if status.is_zero() { 23 | return MessageStatus::None; 24 | } 25 | if status == H256::from_low_u64_be(2) { 26 | return MessageStatus::Processed; 27 | } 28 | MessageStatus::Proven(status) 29 | } 30 | } 31 | 32 | impl From<[u8; 32]> for MessageStatus { 33 | fn from(status: [u8; 32]) -> Self { 34 | let status: H256 = status.into(); 35 | status.into() 36 | } 37 | } 38 | 39 | /// Interface for on-chain replicas 40 | #[async_trait] 41 | pub trait Replica: Common + Send + Sync + std::fmt::Debug { 42 | /// Return the replica domain ID 43 | fn local_domain(&self) -> u32; 44 | 45 | /// Return the domain of the replica's linked home 46 | async fn remote_domain(&self) -> Result::Error>; 47 | 48 | /// Dispatch a transaction to prove inclusion of some leaf in the replica. 49 | async fn prove(&self, proof: &NomadProof) -> Result::Error>; 50 | 51 | /// Trigger processing of a message 52 | async fn process(&self, message: &NomadMessage) -> Result::Error>; 53 | 54 | /// Prove a leaf in the replica and then process its message 55 | async fn prove_and_process( 56 | &self, 57 | message: &NomadMessage, 58 | proof: &NomadProof, 59 | ) -> Result::Error> { 60 | self.prove(proof).await?; 61 | 62 | Ok(self.process(message).await?) 63 | } 64 | 65 | /// Fetch the status of a message 66 | async fn message_status(&self, leaf: H256) -> Result::Error>; 67 | 68 | /// Fetch the confirmation time for a specific root 69 | async fn acceptable_root(&self, root: H256) -> Result::Error>; 70 | } 71 | -------------------------------------------------------------------------------- /nomad-core/src/traits/signer.rs: -------------------------------------------------------------------------------- 1 | use async_trait::async_trait; 2 | use color_eyre::Result; 3 | use ethers::prelude::{Signature, Signer}; 4 | use nomad_xyz_configuration::agent::SignerConf; 5 | 6 | /// Extension of ethers signer trait 7 | #[async_trait] 8 | pub trait SignerExt: Signer { 9 | /// Sign message without eip 155 10 | async fn sign_message_without_eip_155>( 11 | &self, 12 | message: S, 13 | ) -> Result::Error> { 14 | let mut signature = self.sign_message(message).await?; 15 | signature.v = 28 - (signature.v % 2); 16 | Ok(signature) 17 | } 18 | } 19 | 20 | impl SignerExt for T where T: Signer {} 21 | 22 | /// Interface for instantiating a chain-specific signer from a `SignerConf` 23 | /// object. 24 | #[async_trait] 25 | pub trait FromSignerConf: Sized { 26 | /// Instantiate `Self` from a `SignerConf` object 27 | async fn try_from_signer_conf(conf: &SignerConf) -> Result; 28 | } 29 | -------------------------------------------------------------------------------- /nomad-core/src/traits/xapp.rs: -------------------------------------------------------------------------------- 1 | use crate::{traits::TxOutcome, SignedFailureNotification}; 2 | use async_trait::async_trait; 3 | use nomad_types::NomadIdentifier; 4 | use std::error::Error as StdError; 5 | 6 | /// Interface for on-chain XAppConnectionManager 7 | #[async_trait] 8 | pub trait ConnectionManager: Send + Sync + std::fmt::Debug { 9 | /// Chain-specific error type 10 | type Error: StdError + Send + Sync; 11 | 12 | /// Return the contract's local domain ID 13 | fn local_domain(&self) -> u32; 14 | 15 | /// Returns true if provided address is enrolled replica 16 | async fn is_replica(&self, address: NomadIdentifier) -> Result; 17 | 18 | /// Returns permission for address at given domain 19 | async fn watcher_permission( 20 | &self, 21 | address: NomadIdentifier, 22 | domain: u32, 23 | ) -> Result; 24 | 25 | /// onlyOwner function. Enrolls replica at given domain chain. 26 | async fn owner_enroll_replica( 27 | &self, 28 | replica: NomadIdentifier, 29 | domain: u32, 30 | ) -> Result; 31 | 32 | /// onlyOwner function. Unenrolls replica. 33 | async fn owner_unenroll_replica( 34 | &self, 35 | replica: NomadIdentifier, 36 | ) -> Result; 37 | 38 | /// onlyOwner function. Sets contract's home to provided home. 39 | async fn set_home(&self, home: NomadIdentifier) -> Result; 40 | 41 | /// onlyOwner function. Sets permission for watcher at given domain. 42 | async fn set_watcher_permission( 43 | &self, 44 | watcher: NomadIdentifier, 45 | domain: u32, 46 | access: bool, 47 | ) -> Result; 48 | 49 | /// Unenroll the replica at the given domain provided an updater address 50 | /// and `SignedFailureNotification` from a watcher 51 | async fn unenroll_replica( 52 | &self, 53 | signed_failure: &SignedFailureNotification, 54 | ) -> Result; 55 | } 56 | -------------------------------------------------------------------------------- /nomad-core/src/types/failure.rs: -------------------------------------------------------------------------------- 1 | use crate::{utils::home_domain_hash, NomadError, SignerExt}; 2 | use ethers::{ 3 | prelude::{Address, Signature}, 4 | types::H256, 5 | utils::hash_message, 6 | }; 7 | use ethers_signers::Signer; 8 | use nomad_types::NomadIdentifier; 9 | use sha3::{Digest, Keccak256}; 10 | 11 | /// Failure notification produced by watcher 12 | #[derive(Debug, Clone, Copy, PartialEq)] 13 | pub struct FailureNotification { 14 | /// Domain of failed home 15 | pub home_domain: u32, 16 | /// Failed home's updater 17 | pub updater: NomadIdentifier, 18 | } 19 | 20 | impl FailureNotification { 21 | fn signing_hash(&self) -> H256 { 22 | H256::from_slice( 23 | Keccak256::new() 24 | .chain(home_domain_hash(self.home_domain)) 25 | .chain(self.home_domain.to_be_bytes()) 26 | .chain(self.updater.as_ref()) 27 | .finalize() 28 | .as_slice(), 29 | ) 30 | } 31 | 32 | fn prepended_hash(&self) -> H256 { 33 | hash_message(self.signing_hash()) 34 | } 35 | 36 | /// Sign an `FailureNotification` using the specified signer 37 | pub async fn sign_with(self, signer: &S) -> Result 38 | where 39 | S: Signer, 40 | { 41 | let signature = signer 42 | .sign_message_without_eip_155(self.signing_hash()) 43 | .await?; 44 | Ok(SignedFailureNotification { 45 | notification: self, 46 | signature, 47 | }) 48 | } 49 | } 50 | 51 | /// Signed failure notification produced by watcher 52 | #[derive(Debug, Clone, Copy, PartialEq)] 53 | pub struct SignedFailureNotification { 54 | /// Failure notification 55 | pub notification: FailureNotification, 56 | /// Signature 57 | pub signature: Signature, 58 | } 59 | 60 | impl SignedFailureNotification { 61 | /// Recover the Ethereum address of the signer 62 | pub fn recover(&self) -> Result { 63 | Ok(self.signature.recover(self.notification.prepended_hash())?) 64 | } 65 | 66 | /// Check whether a message was signed by a specific address 67 | pub fn verify(&self, signer: Address) -> Result<(), NomadError> { 68 | Ok(self 69 | .signature 70 | .verify(self.notification.prepended_hash(), signer)?) 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /nomad-core/src/types/messages.rs: -------------------------------------------------------------------------------- 1 | use ethers::{types::H256, utils::keccak256}; 2 | 3 | use crate::{utils, Decode, Encode, NomadError}; 4 | 5 | const NOMAD_MESSAGE_PREFIX_LEN: usize = 76; 6 | 7 | /// A full Nomad message between chains 8 | #[derive(Debug, Default, Clone)] 9 | pub struct NomadMessage { 10 | /// 4 SLIP-44 ID 11 | pub origin: u32, 12 | /// 32 Address in home convention 13 | pub sender: H256, 14 | /// 4 Count of all previous messages to destination 15 | pub nonce: u32, 16 | /// 4 SLIP-44 ID 17 | pub destination: u32, 18 | /// 32 Address in destination convention 19 | pub recipient: H256, 20 | /// 0+ Message contents 21 | pub body: Vec, 22 | } 23 | 24 | /// A partial Nomad message between chains 25 | #[derive(Debug, Default, Clone)] 26 | pub struct Message { 27 | /// 4 SLIP-44 ID 28 | pub destination: u32, 29 | /// 32 Address in destination convention 30 | pub recipient: H256, 31 | /// 0+ Message contents 32 | pub body: Vec, 33 | } 34 | 35 | impl Encode for NomadMessage { 36 | fn write_to(&self, writer: &mut W) -> std::io::Result 37 | where 38 | W: std::io::Write, 39 | { 40 | writer.write_all(&self.origin.to_be_bytes())?; 41 | writer.write_all(self.sender.as_ref())?; 42 | writer.write_all(&self.nonce.to_be_bytes())?; 43 | writer.write_all(&self.destination.to_be_bytes())?; 44 | writer.write_all(self.recipient.as_ref())?; 45 | writer.write_all(&self.body)?; 46 | Ok(NOMAD_MESSAGE_PREFIX_LEN + self.body.len()) 47 | } 48 | } 49 | 50 | impl Decode for NomadMessage { 51 | fn read_from(reader: &mut R) -> Result 52 | where 53 | R: std::io::Read, 54 | { 55 | let mut origin = [0u8; 4]; 56 | reader.read_exact(&mut origin)?; 57 | 58 | let mut sender = H256::zero(); 59 | reader.read_exact(sender.as_mut())?; 60 | 61 | let mut nonce = [0u8; 4]; 62 | reader.read_exact(&mut nonce)?; 63 | 64 | let mut destination = [0u8; 4]; 65 | reader.read_exact(&mut destination)?; 66 | 67 | let mut recipient = H256::zero(); 68 | reader.read_exact(recipient.as_mut())?; 69 | 70 | let mut body = vec![]; 71 | reader.read_to_end(&mut body)?; 72 | 73 | Ok(Self { 74 | origin: u32::from_be_bytes(origin), 75 | sender, 76 | destination: u32::from_be_bytes(destination), 77 | recipient, 78 | nonce: u32::from_be_bytes(nonce), 79 | body, 80 | }) 81 | } 82 | } 83 | 84 | impl NomadMessage { 85 | /// Convert the message to a leaf 86 | pub fn to_leaf(&self) -> H256 { 87 | keccak256(self.to_vec()).into() 88 | } 89 | 90 | /// Get the encoded destination + nonce 91 | pub fn destination_and_nonce(&self) -> u64 { 92 | utils::destination_and_nonce(self.destination, self.nonce) 93 | } 94 | } 95 | 96 | impl std::fmt::Display for NomadMessage { 97 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 98 | write!( 99 | f, 100 | "NomadMessage {}->{}:{}", 101 | self.origin, self.destination, self.nonce, 102 | ) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /nomad-core/src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod failure; 2 | mod messages; 3 | mod update; 4 | 5 | pub use failure::*; 6 | pub use messages::*; 7 | pub use update::*; 8 | -------------------------------------------------------------------------------- /nomad-core/src/utils.rs: -------------------------------------------------------------------------------- 1 | use ethers::core::types::H256; 2 | use sha3::{Digest, Keccak256}; 3 | 4 | /// Computes hash of home domain concatenated with "NOMAD" 5 | pub fn home_domain_hash(home_domain: u32) -> H256 { 6 | H256::from_slice( 7 | Keccak256::new() 8 | .chain(home_domain.to_be_bytes()) 9 | .chain("NOMAD".as_bytes()) 10 | .finalize() 11 | .as_slice(), 12 | ) 13 | } 14 | 15 | /// Destination and destination-specific nonce combined in single field ( 16 | /// (destination << 32) & nonce) 17 | pub fn destination_and_nonce(destination: u32, nonce: u32) -> u64 { 18 | assert!(destination < u32::MAX); 19 | assert!(nonce < u32::MAX); 20 | ((destination as u64) << 32) | nonce as u64 21 | } 22 | -------------------------------------------------------------------------------- /nomad-test/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - Add new `MockError` type to account for changes making `ChainCommunication` a VM-specific wrapper 6 | - implement `Display` for mock contracts 7 | - add helper fn for testing with an http mock response 8 | - add mockito for mocking http responses 9 | - adds a changelog 10 | -------------------------------------------------------------------------------- /nomad-test/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-test" 3 | version = "0.1.0" 4 | authors = ["Luke Tchang "] 5 | edition = "2021" 6 | 7 | [dependencies] 8 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 9 | config = "0.10" 10 | serde = { version = "1.0", features = ["derive"] } 11 | serde_json = { version = "1.0", default-features = false } 12 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 13 | thiserror = { version = "1.0.22", default-features = false } 14 | async-trait = { version = "0.1.42", default-features = false } 15 | futures-util = "0.3.12" 16 | color-eyre = "0.6.0" 17 | mockall = "0.10.2" 18 | rand = "0.8.3" 19 | rocksdb = { git = "https://github.com/rust-rocksdb/rust-rocksdb" } 20 | dotenv = "0.15.0" 21 | tracing = "0.1.35" 22 | prometheus = "0.12.0" 23 | mockito = "0.31.0" 24 | 25 | nomad-xyz-configuration = { path = "../configuration" } 26 | nomad-core = { path = "../nomad-core" } 27 | nomad-ethereum = { path = "../chains/nomad-ethereum"} 28 | -------------------------------------------------------------------------------- /nomad-test/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Nomad. OPTimistic Interchain Communication 2 | //! 3 | //! This crate contains mocks and utilities for testing Nomad agents. 4 | 5 | #![forbid(unsafe_code)] 6 | #![cfg_attr(test, warn(missing_docs))] 7 | #![warn(unused_extern_crates)] 8 | #![forbid(where_clauses_object_safety)] 9 | 10 | /// Mock contracts 11 | pub mod mocks; 12 | pub use mocks::MockError; 13 | 14 | /// Testing utilities 15 | pub mod test_utils; 16 | -------------------------------------------------------------------------------- /nomad-test/src/mocks/indexer.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use crate::MockError; 4 | use async_trait::async_trait; 5 | use color_eyre::Result; 6 | use mockall::*; 7 | 8 | use nomad_core::*; 9 | 10 | mock! { 11 | pub Indexer { 12 | pub fn _get_block_number(&self) -> Result {} 13 | 14 | pub fn _fetch_sorted_updates(&self, from: u32, to: u32) -> Result, MockError> {} 15 | 16 | pub fn _fetch_sorted_messages(&self, from: u32, to: u32) -> Result, MockError> {} 17 | } 18 | } 19 | 20 | impl std::fmt::Debug for MockIndexer { 21 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 22 | write!(f, "MockIndexer") 23 | } 24 | } 25 | 26 | #[async_trait] 27 | impl CommonIndexer for MockIndexer { 28 | type Error = MockError; 29 | 30 | async fn get_block_number(&self) -> Result { 31 | self._get_block_number() 32 | } 33 | 34 | async fn fetch_sorted_updates( 35 | &self, 36 | from: u32, 37 | to: u32, 38 | ) -> Result, Self::Error> { 39 | self._fetch_sorted_updates(from, to) 40 | } 41 | } 42 | 43 | #[async_trait] 44 | impl HomeIndexer for MockIndexer { 45 | async fn fetch_sorted_messages( 46 | &self, 47 | from: u32, 48 | to: u32, 49 | ) -> Result, ::Error> { 50 | self._fetch_sorted_messages(from, to) 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /nomad-test/src/mocks/mod.rs: -------------------------------------------------------------------------------- 1 | /// Mock home contract 2 | pub mod home; 3 | 4 | /// Mock replica contract 5 | pub mod replica; 6 | 7 | /// Mock indexer 8 | pub mod indexer; 9 | 10 | /// Mock connection manager contract 11 | pub mod xapp; 12 | 13 | pub use home::MockHomeContract; 14 | pub use indexer::MockIndexer; 15 | pub use replica::MockReplicaContract; 16 | pub use xapp::MockConnectionManagerContract; 17 | 18 | #[derive(Debug, thiserror::Error)] 19 | #[error("Mock error")] 20 | pub struct MockError; 21 | -------------------------------------------------------------------------------- /nomad-test/src/mocks/xapp.rs: -------------------------------------------------------------------------------- 1 | #![allow(non_snake_case)] 2 | 3 | use async_trait::async_trait; 4 | use mockall::*; 5 | 6 | use nomad_core::*; 7 | 8 | use super::MockError; 9 | 10 | mock! { 11 | pub ConnectionManagerContract { 12 | pub fn _local_domain(&self) -> u32 {} 13 | 14 | pub fn _is_replica(&self, address: NomadIdentifier) -> Result {} 15 | 16 | pub fn _watcher_permission( 17 | &self, 18 | address: NomadIdentifier, 19 | domain: u32, 20 | ) -> Result {} 21 | 22 | pub fn _owner_enroll_replica( 23 | &self, 24 | replica: NomadIdentifier, 25 | domain: u32, 26 | ) -> Result {} 27 | 28 | pub fn _owner_unenroll_replica( 29 | &self, 30 | replica: NomadIdentifier, 31 | ) -> Result {} 32 | 33 | pub fn _set_home(&self, home: NomadIdentifier) -> Result {} 34 | 35 | pub fn _set_watcher_permission( 36 | &self, 37 | watcher: NomadIdentifier, 38 | domain: u32, 39 | access: bool, 40 | ) -> Result {} 41 | 42 | pub fn _unenroll_replica( 43 | &self, 44 | signed_failure: &SignedFailureNotification, 45 | ) -> Result {} 46 | } 47 | } 48 | 49 | impl std::fmt::Debug for MockConnectionManagerContract { 50 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 51 | write!(f, "MockConnectionManagerContract") 52 | } 53 | } 54 | 55 | #[async_trait] 56 | impl ConnectionManager for MockConnectionManagerContract { 57 | type Error = MockError; 58 | 59 | fn local_domain(&self) -> u32 { 60 | self._local_domain() 61 | } 62 | 63 | async fn is_replica(&self, address: NomadIdentifier) -> Result { 64 | self._is_replica(address) 65 | } 66 | 67 | async fn watcher_permission( 68 | &self, 69 | address: NomadIdentifier, 70 | domain: u32, 71 | ) -> Result { 72 | self._watcher_permission(address, domain) 73 | } 74 | 75 | async fn owner_enroll_replica( 76 | &self, 77 | replica: NomadIdentifier, 78 | domain: u32, 79 | ) -> Result { 80 | self._owner_enroll_replica(replica, domain) 81 | } 82 | 83 | async fn owner_unenroll_replica( 84 | &self, 85 | replica: NomadIdentifier, 86 | ) -> Result { 87 | self._owner_unenroll_replica(replica) 88 | } 89 | 90 | async fn set_home(&self, home: NomadIdentifier) -> Result { 91 | self._set_home(home) 92 | } 93 | 94 | async fn set_watcher_permission( 95 | &self, 96 | watcher: NomadIdentifier, 97 | domain: u32, 98 | access: bool, 99 | ) -> Result { 100 | self._set_watcher_permission(watcher, domain, access) 101 | } 102 | 103 | async fn unenroll_replica( 104 | &self, 105 | signed_failure: &SignedFailureNotification, 106 | ) -> Result { 107 | self._unenroll_replica(signed_failure) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /nomad-test/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | use futures_util::FutureExt; 2 | use mockito; 3 | use nomad_core::db::DB; 4 | use rand::distributions::Alphanumeric; 5 | use rand::{thread_rng, Rng}; 6 | use std::path::Path; 7 | use std::{env, future::Future, panic}; 8 | 9 | use rocksdb::Options; 10 | 11 | /// Sets up a db 12 | pub fn setup_db(db_path: String) -> DB { 13 | let mut opts = Options::default(); 14 | opts.create_if_missing(true); 15 | rocksdb::DB::open(&opts, db_path) 16 | .expect("Failed to open db path") 17 | .into() 18 | } 19 | 20 | /// Runs test for a db 21 | pub async fn run_test_db(test: T) 22 | where 23 | T: FnOnce(DB) -> Fut + panic::UnwindSafe, 24 | Fut: Future, 25 | { 26 | // RocksDB only allows one unique db handle to be open at a time. Because 27 | // `cargo test` is multithreaded by default, we use random db pathnames to 28 | // avoid collisions between 2+ threads 29 | let rand_path: String = thread_rng() 30 | .sample_iter(&Alphanumeric) 31 | .take(8) 32 | .map(char::from) 33 | .collect(); 34 | let result = { 35 | let db = setup_db(rand_path.clone()); 36 | 37 | let func = panic::AssertUnwindSafe(async { test(db).await }); 38 | func.catch_unwind().await 39 | }; 40 | let _ = rocksdb::DB::destroy(&Options::default(), rand_path); 41 | assert!(result.is_ok()) 42 | } 43 | 44 | pub async fn run_test_with_env(path: impl AsRef, test: T) 45 | where 46 | T: FnOnce() -> Fut + panic::UnwindSafe, 47 | Fut: Future, 48 | { 49 | let result = { 50 | dotenv::from_filename(path).unwrap(); 51 | let func = panic::AssertUnwindSafe(async { test().await }); 52 | func.catch_unwind().await 53 | }; 54 | 55 | clear_env_vars(); 56 | assert!(result.is_ok()) 57 | } 58 | 59 | pub fn run_test_with_env_sync(path: impl AsRef, test: T) 60 | where 61 | T: FnOnce() + panic::UnwindSafe, 62 | { 63 | let result = panic::catch_unwind(panic::AssertUnwindSafe(|| { 64 | dotenv::from_filename(path).unwrap(); 65 | test() 66 | })); 67 | 68 | clear_env_vars(); 69 | assert!(result.is_ok()) 70 | } 71 | 72 | /// Run test with a mock http server response 73 | pub async fn run_test_with_http_response(response_body: impl AsRef<[u8]>, test: T) 74 | where 75 | T: FnOnce(String) -> Fut + panic::UnwindSafe, 76 | Fut: Future, 77 | { 78 | let result = { 79 | let url = mockito::server_url(); 80 | let _m = mockito::mock("GET", "/") 81 | .with_status(200) 82 | .with_header("content-type", "application/json") 83 | .with_body(response_body) 84 | .create(); 85 | let func = panic::AssertUnwindSafe(async { test(url).await }); 86 | func.catch_unwind().await 87 | }; 88 | 89 | assert!(result.is_ok()) 90 | } 91 | 92 | pub fn clear_env_vars() { 93 | let env_vars = env::vars(); 94 | for (key, _) in env_vars { 95 | env::remove_var(key); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /nomad-types/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - refactor: `NomadIdentifier` now uses shorter serialization if top 12 bytes 6 | are empty 7 | -------------------------------------------------------------------------------- /nomad-types/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-types" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 10 | 11 | serde = { version = "1.0", features = ["derive"] } 12 | serde_json = { version = "1.0", default-features = false } 13 | thiserror = "*" 14 | prometheus = "0.12" 15 | affix = "0.1.2" 16 | color-eyre = "0.6.0" 17 | hex = "0.4.3" -------------------------------------------------------------------------------- /nomad-types/src/error.rs: -------------------------------------------------------------------------------- 1 | use crate::NomadIdentifier; 2 | 3 | /// DB Error type 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum NomadTypeError { 6 | /// Failed to perform conversion to 20 byte address 7 | #[error("Failed to convert 32 byte address into 20 byte address: {0}")] 8 | AddressConversionError(NomadIdentifier), 9 | } 10 | -------------------------------------------------------------------------------- /nomad-types/src/macros.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | /// Implement deser_nomad_number for a uint type 3 | macro_rules! impl_deser_nomad_number { 4 | ($($u:ident),*) => { 5 | $(affix::paste! { 6 | #[doc = "Permissive deserialization of numbers. Allows numbers, hex strings, and decimal strings"] 7 | pub fn []<'de, D>(deserializer: D) -> Result<$u, D::Error> 8 | where 9 | D: serde::Deserializer<'de>, 10 | { 11 | struct NumberOrNumberStringVisitor; 12 | 13 | impl<'de> serde::de::Visitor<'de> for NumberOrNumberStringVisitor { 14 | type Value = $u; 15 | 16 | fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { 17 | formatter 18 | .write_str("an integer, a decimal string, or a 0x-prepended hexadecimal string") 19 | } 20 | 21 | fn visit_u64(self, v: u64) -> Result 22 | where 23 | E: serde::de::Error, 24 | { 25 | Ok(v.try_into().expect(&std::format!("failed to cast u64 into {}", stringify!($u)))) 26 | } 27 | 28 | fn visit_str(self, v: &str) -> Result 29 | where 30 | E: serde::de::Error, 31 | { 32 | if let Ok(res) = v.parse() { 33 | return Ok(res); 34 | } 35 | 36 | if let Some(stripped) = v.strip_prefix("0x") { 37 | if stripped.is_empty() { 38 | return Ok(0); 39 | } 40 | if let Ok(res) = $u::from_str_radix(stripped, 16) { 41 | return Ok(res); 42 | } 43 | } 44 | 45 | Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)) 46 | } 47 | } 48 | 49 | deserializer.deserialize_any(NumberOrNumberStringVisitor) 50 | } 51 | })* 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | docker push gcr.io/nomad-xyz/nomad-agent:$1 -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "1.62" 3 | profile = "default" 4 | -------------------------------------------------------------------------------- /tools/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### Unreleased 4 | 5 | - adds the ability for killswitch to auto-configure 6 | - makes killswitch output human readable 7 | - makes killswitch execute transactions in parallel to improve speed 8 | - adds a killswitch binary that allows for the manual shutdown of bridge channels 9 | - adds tests and test fixtures for killswitch 10 | - adds a readme, including documentation for killswitch 11 | - adds a changelog 12 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | ## Tools 2 | 3 | ### Killswitch 4 | 5 | - Kills bridge channels manually in effectively the same way as Watcher 6 | - Can either kill all configured networks or all inbound to a single network -------------------------------------------------------------------------------- /tools/balance-exporter/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "balance-exporter" 3 | version = "0.1.0" 4 | edition = "2021" 5 | description = "Polls chains for nomad contract wallet balances and reports them in OpenMetrics format" 6 | authors = ["Illusory Systems Inc. "] 7 | license = "Apache-2.0" 8 | 9 | [dependencies] 10 | tokio = "1" 11 | futures = "0.3" 12 | 13 | ethers-core = {git = "https://github.com/gakonst/ethers-rs", branch = "master"} 14 | 15 | metrics = "0" 16 | metrics-exporter-prometheus = "0" 17 | serde_json = "1" 18 | serde = "1" 19 | color-eyre = "0" 20 | clap = { version = "3.1.6", features = ["cargo"] } 21 | human-panic = "1" 22 | 23 | nomad-base = { path = "../../nomad-base" } 24 | 25 | # SMELL: reaching into the implementation details. abstract eventually. 26 | nomad-ethereum = { path = "../../chains/nomad-ethereum" } 27 | -------------------------------------------------------------------------------- /tools/killswitch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "killswitch" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "4.0", features = ["derive"] } 10 | tokio = { version = "1.0.1", features = ["rt", "macros"] } 11 | futures-util = "0.3.12" 12 | thiserror = { version = "1.0.22", default-features = false } 13 | serde = "1.0.120" 14 | serde_json = { version = "1.0.61", default-features = false } 15 | serde_yaml = "0.9.14" 16 | rusoto_core = "0.48.0" 17 | rusoto_s3 = "0.48.0" 18 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 19 | nomad-core = { path = "../../nomad-core" } 20 | nomad-base = { path = "../../nomad-base" } 21 | nomad-xyz-configuration = { path = "../../configuration" } 22 | nomad-ethereum = { path = "../../chains/nomad-ethereum" } 23 | 24 | [dev-dependencies] 25 | rusoto_mock = "0.48.0" 26 | assert_matches = "1.5" 27 | serial_test = "0.6.0" 28 | nomad-test = { path = "../../nomad-test" } 29 | nomad-types = { path = "../../nomad-types" } 30 | -------------------------------------------------------------------------------- /tools/killswitch/src/errors.rs: -------------------------------------------------------------------------------- 1 | use nomad_base::ChainCommunicationError; 2 | use nomad_ethereum::EthereumSignersError; 3 | use rusoto_core::{credential::CredentialsError, RusotoError}; 4 | use rusoto_s3::GetObjectError; 5 | use serde_yaml::Error as YamlError; 6 | use std::io::Error as IOError; 7 | 8 | /// `Error` for KillSwitch 9 | #[derive(Debug, thiserror::Error)] 10 | pub enum Error { 11 | /// Cannot find local AWS credentials 12 | #[error("BadCredentials: Cannot find AWS credentials: {0}")] 13 | BadCredentials(#[source] CredentialsError), 14 | /// Yaml ser/de error 15 | #[error("YamlBadDeser: Error converting to/from yaml: {0}")] 16 | YamlBadDeser(#[source] YamlError), 17 | /// Rusto S3 fetch object error 18 | #[error("RusotoGetObject: Error fetching object from AWS S3: {0}")] 19 | RusotoGetObject(#[source] RusotoError), 20 | /// Generic IO error 21 | #[error("BadIO: Error reading or writing from stream: {0}")] 22 | BadIO(#[source] IOError), 23 | /// No configuration env var 24 | #[error( 25 | "NoConfigVar: No configuration found. Set CONFIG_URL or CONFIG_PATH environment variable" 26 | )] 27 | NoConfigVar, 28 | /// Bad configuration env var 29 | #[error("BadConfigVar: Unable to load config from: {0}")] 30 | BadConfigVar(String), 31 | /// No killable networks found 32 | #[error("NoNetworks: No available networks in config to kill")] 33 | NoNetworks, 34 | /// RPC config missing 35 | #[error("MissingRPC: No rpc config found for: {0}")] 36 | MissingRPC(String), 37 | /// Tx submitter config missing 38 | #[error("MissingTxSubmitterConf: No transaction submitter config found for: {0}")] 39 | MissingTxSubmitterConf(String), 40 | /// Attestation signer config missing 41 | #[error("MissingAttestationSignerConf: No attestation signer config found for: {0}")] 42 | MissingAttestationSignerConf(String), 43 | /// Home bad init 44 | #[error("HomeInit: Home init failed: {0}")] 45 | HomeInit(String), 46 | /// Connection manager bad init 47 | #[error("ConnectionManagerInit: Connection manager init failed: {0}")] 48 | ConnectionManagerInit(String), 49 | /// Attestation signer bad init 50 | #[error("AttestationSignerInit: Attestation signer init failed: {0}")] 51 | AttestationSignerInit(String), 52 | /// Can't get updater address 53 | #[error("UpdaterAddress: Error getting updater address: {0}")] 54 | UpdaterAddress(#[source] ChainCommunicationError), 55 | /// Attestation signer failed to sign 56 | #[error("AttestationSignerFailed: Attestation signer failed: {0}")] 57 | AttestationSignerFailed(#[source] EthereumSignersError), 58 | /// Unenrollment failure 59 | #[error("UnenrollmentFailed: Unenrollment failed: {0}")] 60 | UnenrollmentFailed(#[source] ChainCommunicationError), 61 | } 62 | -------------------------------------------------------------------------------- /tools/kms-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "kms-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | clap = { version = "3.1.6", features = ["derive"] } 10 | color-eyre = "0.6.0" 11 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 12 | ethers-signers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["aws"] } 13 | hex = "0.4.3" 14 | once_cell = "1.8.0" 15 | rusoto_core = "0.48.0" 16 | rusoto_kms = "0.48.0" 17 | tokio = "1.9.0" 18 | serde_json = "1.0.66" 19 | -------------------------------------------------------------------------------- /tools/nomad-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nomad-cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | color-eyre = "0.6.0" 8 | ethers = { git = "https://github.com/gakonst/ethers-rs", branch = "master" } 9 | ethers-signers = { git = "https://github.com/gakonst/ethers-rs", branch = "master", features = ["aws"] } 10 | hex = "0.4.3" 11 | once_cell = "1.8.0" 12 | rusoto_core = "0.48.0" 13 | rusoto_kms = "0.48.0" 14 | tokio = "1.9.0" 15 | serde_json = "1.0.66" 16 | structopt = "0.3.23" 17 | 18 | nomad-ethereum = { path = "../../chains/nomad-ethereum" } 19 | nomad-core = { path = "../../nomad-core" } 20 | nomad-base = { path = "../../nomad-base" } 21 | -------------------------------------------------------------------------------- /tools/nomad-cli/README.md: -------------------------------------------------------------------------------- 1 | ## Prover CLI 2 | 3 | This CLI directly accesses a synced or partially-synced processor DB, retrieves 4 | messages and proofs, and dispatches them to the contracts. 5 | 6 | ### Usage 7 | 8 | - `cargo run --bin prove-cli` 9 | - `--leaf-index` - specify the leaf to prove 10 | - `--leaf-hash` - specify the leaf to prove 11 | - if both are specified `--leaf-index` takes precedence 12 | - `--rpc` specify the RPC endpoint 13 | - `--key` specify the hex key to use to sign txns 14 | - in future versions this will be an env var or a node or aws signer 15 | - `--db` specify the filepath to the DB 16 | - `--address` specify the Replica address to submit to 17 | 18 | ### Example 19 | 20 | Submit a proof of leaf 23 in SOME tree to celo. 21 | 22 | - `cargo run --bin prove-cli --leaf-index 23 --rpc "https://forno.celo.org" --key $FUNDED_CELO_PRIVKEY --db ../dbs/whatever --address 0x1234..abcd` 23 | -------------------------------------------------------------------------------- /tools/nomad-cli/src/commands.rs: -------------------------------------------------------------------------------- 1 | use structopt::StructOpt; 2 | 3 | use crate::subcommands::{db_state::DbStateCommand, prove::ProveCommand}; 4 | 5 | #[derive(StructOpt)] 6 | pub enum Commands { 7 | /// Prove a message on a home for a given leaf index 8 | Prove(ProveCommand), 9 | /// Print the processor's db state 10 | DbState(DbStateCommand), 11 | } 12 | -------------------------------------------------------------------------------- /tools/nomad-cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use color_eyre::Result; 2 | use structopt::StructOpt; 3 | 4 | mod commands; 5 | mod replicas; 6 | mod rpc; 7 | mod subcommands; 8 | 9 | use commands::Commands; 10 | 11 | #[tokio::main] 12 | async fn main() -> Result<()> { 13 | let command = Commands::from_args(); 14 | 15 | match command { 16 | Commands::Prove(prove) => prove.run().await, 17 | Commands::DbState(db_state) => db_state.run().await, 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tools/nomad-cli/src/replicas.rs: -------------------------------------------------------------------------------- 1 | use ethers::prelude::H160; 2 | 3 | const CELO: u32 = 1667591279; 4 | const ETH: u32 = 6648936; 5 | const POLY: u32 = 1886350457; 6 | 7 | pub(crate) fn address_by_domain_pair(origin: u32, destination: u32) -> Option { 8 | let addr = match (origin, destination) { 9 | (ETH, CELO) => "0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97", 10 | (CELO, ETH) => "0x07b5B57b08202294E657D51Eb453A189290f6385", 11 | (ETH, POLY) => "0xf25C5932bb6EFc7afA4895D9916F2abD7151BF97", 12 | (POLY, ETH) => "0x7725EadaC5Ee986CAc8317a1d2fB16e59e079E8b", 13 | (CELO, POLY) => "0x681Edb6d52138cEa8210060C309230244BcEa61b", 14 | (POLY, CELO) => "0x681Edb6d52138cEa8210060C309230244BcEa61b", 15 | _ => return None, 16 | }; 17 | Some(addr.parse().unwrap()) 18 | } 19 | -------------------------------------------------------------------------------- /tools/nomad-cli/src/rpc.rs: -------------------------------------------------------------------------------- 1 | use std::convert::TryFrom; 2 | 3 | use ethers::prelude::{Http, Provider}; 4 | 5 | fn domain_to_env(domain: u32) -> Option<&'static str> { 6 | match domain { 7 | 6648936 => Some("OPT_BASE_REPLICAS_ETHEREUM_CONNECTION_URL"), 8 | 1667591279 => Some("OPT_BASE_REPLICAS_CELO_CONNECTION_URL"), 9 | 1886350457 => Some("OPT_BASE_REPLICAS_POLYGON_CONNECTION_URL"), 10 | _ => None, 11 | } 12 | } 13 | 14 | pub(crate) fn fetch_rpc_connection(domain: u32) -> Option> { 15 | std::env::var(domain_to_env(domain)?) 16 | .map(|rpc| TryFrom::try_from(rpc).expect("Invalid RPC url")) 17 | .ok() 18 | } 19 | -------------------------------------------------------------------------------- /tools/nomad-cli/src/subcommands/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod db_state; 2 | pub mod prove; 3 | 4 | pub use db_state::*; 5 | pub use prove::*; 6 | --------------------------------------------------------------------------------