├── .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 |
9 |
--------------------------------------------------------------------------------
/.github/images/Logo-White.svg:
--------------------------------------------------------------------------------
1 |
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