├── .github
└── workflows
│ ├── benchmarks.yml
│ ├── release.yml
│ └── test.yml
├── .gitignore
├── .rustfmt.toml
├── Cargo.toml
├── LICENSE
├── README.md
├── bin
└── varro.rs
├── contracts
└── L2OutputOracle.json
├── logo
├── blurred.png
├── colosseum.jpeg
├── refcell.png
└── varro.png
├── src
├── builder.rs
├── cli.rs
├── client.rs
├── common.rs
├── config.rs
├── errors.rs
├── lib.rs
├── macros.rs
├── metrics.rs
├── pool.rs
├── proposals.rs
├── rollup.rs
└── telemetry.rs
├── tests
└── l1_client.rs
└── varrup
├── docker-compose.yml
├── install
├── start-varro.sh
├── varro-env.env
└── varrup
/.github/workflows/benchmarks.yml:
--------------------------------------------------------------------------------
1 | name: benchmarks
2 |
3 | on:
4 | workflow_bench:
5 |
6 | env:
7 | MAINNET_RPC_URL: ${{ secrets.MAINNET_RPC_URL }}
8 | GOERLI_RPC_URL: ${{ secrets.GOERLI_RPC_URL }}
9 |
10 | jobs:
11 | benches:
12 | runs-on: ubuntu-latest
13 | steps:
14 | - uses: actions/checkout@v3
15 | - uses: actions-rs/toolchain@v1
16 | with:
17 | profile: minimal
18 | toolchain: nightly
19 | override: true
20 | components: rustfmt
21 | - uses: Swatinem/rust-cache@v2
22 | - uses: actions-rs/cargo@v1
23 | with:
24 | command: bench
25 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | name: release
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build-mac-arm:
8 | runs-on: macos-latest
9 | steps:
10 | - name: checkout
11 | uses: actions/checkout@v1
12 |
13 | - name: install rust nightly
14 | uses: actions-rs/toolchain@v1
15 | with:
16 | toolchain: nightly
17 | default: true
18 | override: true
19 |
20 | - name: install target
21 | run: rustup target add aarch64-apple-darwin
22 |
23 | - uses: Swatinem/rust-cache@v2
24 |
25 | - name: build
26 | run: cargo build --package cli --release --target aarch64-apple-darwin
27 |
28 | - name: archive
29 | run: gtar -czvf "archon_darwin_arm64.tar.gz" -C ./target/aarch64-apple-darwin/release archon
30 |
31 | - name: generate tag name
32 | id: tag
33 | run: |
34 | echo "::set-output name=release_tag::nightly-${GITHUB_SHA}"
35 |
36 | - name: release
37 | uses: softprops/action-gh-release@v1
38 | with:
39 | tag_name: ${{ steps.tag.outputs.release_tag }}
40 | prerelease: true
41 | files: |
42 | archon_darwin_arm64.tar.gz
43 | env:
44 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
45 |
46 | build-mac-amd:
47 | runs-on: macos-latest
48 | steps:
49 | - name: checkout
50 | uses: actions/checkout@v1
51 |
52 | - name: install rust nightly
53 | uses: actions-rs/toolchain@v1
54 | with:
55 | toolchain: nightly
56 | default: true
57 | override: true
58 |
59 | - name: install target
60 | run: rustup target add x86_64-apple-darwin
61 |
62 | - uses: Swatinem/rust-cache@v2
63 |
64 | - name: build
65 | run: cargo build --package cli --release --target x86_64-apple-darwin
66 |
67 | - name: archive
68 | run: gtar -czvf "archon_darwin_amd64.tar.gz" -C ./target/x86_64-apple-darwin/release archon
69 |
70 | - name: generate tag name
71 | id: tag
72 | run: |
73 | echo "::set-output name=release_tag::nightly-${GITHUB_SHA}"
74 |
75 | - name: release
76 | uses: softprops/action-gh-release@v1
77 | with:
78 | tag_name: ${{ steps.tag.outputs.release_tag }}
79 | prerelease: true
80 | files: |
81 | archon_darwin_amd64.tar.gz
82 | env:
83 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
84 |
85 | build-linux-arm:
86 | runs-on: ubuntu-latest
87 | steps:
88 | - name: checkout
89 | uses: actions/checkout@v1
90 |
91 | - name: install rust nightly
92 | uses: actions-rs/toolchain@v1
93 | with:
94 | toolchain: nightly
95 | default: true
96 | override: true
97 |
98 | - name: install target
99 | run: rustup target add aarch64-unknown-linux-gnu
100 |
101 | - name: install dependencies
102 | run: |
103 | sudo apt-get update -y
104 | sudo apt-get install -y gcc-aarch64-linux-gnu
105 | echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> $GITHUB_ENV
106 |
107 | - uses: Swatinem/rust-cache@v2
108 |
109 | - name: build
110 | run: cargo build --package cli --release --target aarch64-unknown-linux-gnu
111 |
112 | - name: archive
113 | run: tar -czvf "archon_linux_arm64.tar.gz" -C ./target/aarch64-unknown-linux-gnu/release archon
114 |
115 | - name: generate tag name
116 | id: tag
117 | run: |
118 | echo "::set-output name=release_tag::nightly-${GITHUB_SHA}"
119 |
120 | - name: release
121 | uses: softprops/action-gh-release@v1
122 | with:
123 | tag_name: ${{ steps.tag.outputs.release_tag }}
124 | prerelease: true
125 | files: |
126 | archon_linux_arm64.tar.gz
127 | env:
128 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129 |
130 | build-linux-amd:
131 | runs-on: ubuntu-latest
132 | steps:
133 | - name: checkout
134 | uses: actions/checkout@v1
135 |
136 | - name: install rust nightly
137 | uses: actions-rs/toolchain@v1
138 | with:
139 | toolchain: nightly
140 | default: true
141 | override: true
142 |
143 | - name: install target
144 | run: rustup target add x86_64-unknown-linux-gnu
145 |
146 | - uses: Swatinem/rust-cache@v2
147 |
148 | - name: build
149 | run: cargo build --package cli --release --target x86_64-unknown-linux-gnu
150 |
151 | - name: archive
152 | run: tar -czvf "archon_linux_amd64.tar.gz" -C ./target/x86_64-unknown-linux-gnu/release archon
153 |
154 | - name: generate tag name
155 | id: tag
156 | run: |
157 | echo "::set-output name=release_tag::nightly-${GITHUB_SHA}"
158 |
159 | - name: release
160 | uses: softprops/action-gh-release@v1
161 | with:
162 | tag_name: ${{ steps.tag.outputs.release_tag }}
163 | prerelease: true
164 | files: |
165 | archon_linux_amd64.tar.gz
166 | env:
167 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
168 |
--------------------------------------------------------------------------------
/.github/workflows/test.yml:
--------------------------------------------------------------------------------
1 | name: test
2 |
3 | on:
4 | push:
5 | branches: [ "main" ]
6 | pull_request:
7 | branches: [ "main" ]
8 |
9 | env:
10 | L1_RPC_URL: ${{ secrets.L1_RPC_URL }}
11 | L2_RPC_URL: ${{ secrets.L2_RPC_URL }}
12 | ROLLUP_NODE_RPC_URL: ${{ secrets.ROLLUP_NODE_RPC_URL }}
13 |
14 | jobs:
15 | check:
16 | runs-on: ubuntu-latest
17 | steps:
18 | - uses: actions/checkout@v3
19 | - uses: actions-rs/toolchain@v1
20 | with:
21 | profile: minimal
22 | toolchain: nightly
23 | override: true
24 | - uses: Swatinem/rust-cache@v2
25 | - uses: actions-rs/cargo@v1
26 | with:
27 | command: check
28 |
29 | test:
30 | runs-on: ubuntu-latest
31 | steps:
32 | - uses: actions/checkout@v3
33 | - uses: actions-rs/toolchain@v1
34 | with:
35 | profile: minimal
36 | toolchain: nightly
37 | override: true
38 | - uses: Swatinem/rust-cache@v2
39 | - uses: actions-rs/cargo@v1
40 | with:
41 | command: test
42 | args: --all
43 |
44 | fmt:
45 | runs-on: ubuntu-latest
46 | steps:
47 | - uses: actions/checkout@v3
48 | - uses: actions-rs/toolchain@v1
49 | with:
50 | profile: minimal
51 | toolchain: nightly
52 | override: true
53 | components: rustfmt
54 | - uses: Swatinem/rust-cache@v2
55 | - uses: actions-rs/cargo@v1
56 | with:
57 | command: fmt
58 | args: --all -- --check
59 |
60 | clippy:
61 | runs-on: ubuntu-latest
62 | steps:
63 | - uses: actions/checkout@v3
64 | - uses: actions-rs/toolchain@v1
65 | with:
66 | profile: minimal
67 | toolchain: nightly
68 | override: true
69 | components: clippy
70 | - uses: Swatinem/rust-cache@v2
71 | - uses: actions-rs/cargo@v1
72 | with:
73 | command: clippy
74 | args: --all -- -D warnings
75 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Generated by Cargo
2 | # will have compiled files and executables
3 | /target/
4 |
5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
7 | Cargo.lock
8 |
9 | # These are backup files generated by rustfmt
10 | **/*.rs.bk
11 |
--------------------------------------------------------------------------------
/.rustfmt.toml:
--------------------------------------------------------------------------------
1 | max_width = 90 # changed
2 | normalize_comments = true # changed
3 | imports_layout = "Vertical" # changed
4 | imports_granularity = "Crate" # changed
5 | trailing_semicolon = false # changed
6 | edition = "2021" # changed
7 | use_try_shorthand = true # changed
8 | use_field_init_shorthand = true # changed
9 |
--------------------------------------------------------------------------------
/Cargo.toml:
--------------------------------------------------------------------------------
1 | [package]
2 | name = "varro"
3 | version = "0.1.0-alpha"
4 | edition = "2021"
5 | authors = ["refcell"]
6 | description = "A persistent, robust, and composable proposal service for rollup stacks."
7 | repository = "https://github.com/refcell/varro"
8 | license = "MIT"
9 | keywords = ["op-stack", "optimism", "varro", "node", "ethereum"]
10 | exclude = [ "logo", "varrup" ]
11 |
12 | [[bin]]
13 | name = "varro"
14 | path = "bin/varro.rs"
15 |
16 | [lib]
17 | crate-type = ["cdylib", "rlib"]
18 |
19 | [profile.release]
20 | strip = true
21 | opt-level = "z"
22 | lto = true
23 | codegen-units = 1
24 | panic = "abort"
25 |
26 | [dependencies]
27 | tokio = { version = "1.25.0", features = ["full"] }
28 | async-trait = "0.1.64"
29 | futures = "0.3.26"
30 | eyre = "0.6.8"
31 | hex = "0.4.3"
32 | libflate = "1.2.0"
33 |
34 | # Logging Telemetry
35 | chrono = "0.4.22"
36 | tracing = "0.1.36"
37 | ansi_term = "0.12.1"
38 | tracing-log = "0.1.3"
39 | tracing-subscriber = { version = "0.3.16", features = ["fmt", "env-filter", "ansi"] }
40 |
41 | # Serialization
42 | serde = { version = "1.0.152", features = [ "derive" ] }
43 | serde_json = "1.0.93"
44 |
45 | # Backend Crates
46 | sled = "0.34.7"
47 | uuid = { version = "1.3.0", features = ["v4"] }
48 | bytes = "1.4.0"
49 | reqwest = "0.11.14"
50 | jsonwebtoken = "8.2.0"
51 | rand = "0.8.5"
52 | home = "0.5.4"
53 |
54 | # CLI
55 | figment = { version = "0.10.8", features = ["toml", "env"] }
56 | ctrlc = "3.2.3"
57 | clap = { version = "3.2.18", features = ["derive", "env"] }
58 | dirs = "4.0.0"
59 | thiserror = "1.0.39"
60 | flate2 = { version = "1.0.25", features = ["zlib"] }
61 | once_cell = "1.17.1"
62 |
63 | # Ethers
64 | ethers-core = "1.0.2"
65 | ethers-providers = "1.0.2"
66 | ethers-middleware = "1.0.2"
67 | ethers-signers = "1.0.2"
68 | ethers-contract = { version = "1.0.2", features = ["abigen"] }
69 | toml = "0.7.3"
70 | tokio-stream = "0.1.12"
71 |
72 | [target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
73 | tracing-test = "0.2.4"
74 | criterion = { version = "0.4", features = [ "async_tokio", "plotters" ]}
75 | plotters = "0.3.4"
76 | tempfile = "3.4.0"
77 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 refcell.eth
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | [](https://github.com/refcell/varro/actions/workflows/test.yml) [](https://opensource.org/licenses/MIT) [](https://crates.io/crates/varro)
4 |
5 | `varro` is a persistent, robust, and composable proposal service for rollup stacks written in pure rust.
6 |
7 | > **Note**
8 | >
9 | > Varro is primarily tested against the [op-stack](https://stack.optimism.io/).
10 |
11 | ## Quickstart
12 |
13 | Varro provides an easy to use installer called `varrup`.
14 |
15 | To install `varro`, run: `curl https://raw.githubusercontent.com/refcell/varro/main/varrup/install | bash`.
16 |
17 | Additionally, you can run varro for [ethereum-optimism](https://github.com/ethereum-optimism) using the provided scripts in the `scripts` directory.
18 |
19 | ## Architecture
20 |
21 | At it's core, `varro` is a client. In [./src/client.rs](./src/client.rs), there is an `Varro` struct that is the core client/driver of the batcher.
22 |
23 | The `Varro` struct first _builds_ "stages" and then executes them as asynchronous threads. These stages split up the transformation of data types and handles channel transitions and metrics.
24 |
25 | The primary entrypoint for `varro` is [./src/client/rs](./src/client.rs) which is the `Varro` struct. `Varro` exposes methods for constructing proposals.
26 |
27 | ## Configuration
28 |
29 | The `varro` cli maintains a verbose menu for running a proposal service. To see a list of all available commands, run `varro --help`. This will print output similar to the following:
30 |
31 | ```bash
32 |
33 | ```
34 |
35 | ### Environment Variables
36 |
37 | The following environment variables are the default values for `varro`'s configuration.
38 |
39 | They can be overridden by setting the environment variable in the shell before running `varro`, or setting the associated flags when running the `varro` cli.
40 |
41 | ```env
42 |
43 | ```
44 |
45 | ## Specifications
46 |
47 | // TODO:
48 |
49 | ## Why "Varro"?
50 |
51 | The term "varro" comes from [Marcus Terentius Varro](https://en.wikipedia.org/wiki/Marcus_Terentius_Varro), an ancient Roman polymath and prolific author. Referred to the "great third light of Rome", Varro mirrors the role of the `varro` service in the rollup stack, handling the "inscription" of outputs to the settlement layer (eg, posting L2 output proposals to Ethereum).
52 |
53 | ## FAQ
54 |
55 | // TODO:
56 |
57 | ## Contributing
58 |
59 | All contributions are welcome. Before opening a PR, please submit an issue detailing the bug or feature. When opening a PR, please ensure that your contribution builds on the nightly rust toolchain, has been linted with `cargo fmt`, and contains tests when applicable.
60 |
61 | ## Disclaimer
62 |
63 | _This code is being provided as is. No guarantee, representation or warranty is being made, express or implied, as to the safety or correctness of the code. It has not been audited and as such there can be no assurance it will work as intended, and users may experience delays, failures, errors, omissions or loss of transmitted information. Nothing in this repo should be construed as investment advice or legal advice for any particular facts or circumstances and is not meant to replace competent counsel. It is strongly advised for you to contact a reputable attorney in your jurisdiction for any questions or concerns with respect thereto. The authors is not liable for any use of the foregoing, and users should proceed with caution and use at their own risk._
64 |
--------------------------------------------------------------------------------
/bin/varro.rs:
--------------------------------------------------------------------------------
1 | use clap::Parser;
2 | use eyre::Result;
3 |
4 | use varro::{
5 | builder::VarroBuilder,
6 | cli::Cli,
7 | config::Config,
8 | telemetry,
9 | };
10 |
11 | #[tokio::main]
12 | async fn main() -> Result<()> {
13 | telemetry::init(false)?;
14 | telemetry::register_shutdown();
15 |
16 | let cli = Cli::parse();
17 | let config = Config::try_from(cli)?;
18 | let builder = VarroBuilder::try_from(config)?;
19 | let mut varro = builder.build()?;
20 | match varro.start().await {
21 | Ok(_) => Ok(()),
22 | Err(e) => {
23 | tracing::error!(target: "varro", "Varro exited with error: {}", e);
24 | Err(e)
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/contracts/L2OutputOracle.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "inputs": [
4 | {
5 | "internalType": "uint256",
6 | "name": "_submissionInterval",
7 | "type": "uint256"
8 | },
9 | {
10 | "internalType": "uint256",
11 | "name": "_l2BlockTime",
12 | "type": "uint256"
13 | },
14 | {
15 | "internalType": "uint256",
16 | "name": "_startingBlockNumber",
17 | "type": "uint256"
18 | },
19 | {
20 | "internalType": "uint256",
21 | "name": "_startingTimestamp",
22 | "type": "uint256"
23 | },
24 | {
25 | "internalType": "address",
26 | "name": "_proposer",
27 | "type": "address"
28 | },
29 | {
30 | "internalType": "address",
31 | "name": "_challenger",
32 | "type": "address"
33 | }
34 | ],
35 | "stateMutability": "nonpayable",
36 | "type": "constructor"
37 | },
38 | {
39 | "anonymous": false,
40 | "inputs": [
41 | {
42 | "indexed": false,
43 | "internalType": "uint8",
44 | "name": "version",
45 | "type": "uint8"
46 | }
47 | ],
48 | "name": "Initialized",
49 | "type": "event"
50 | },
51 | {
52 | "anonymous": false,
53 | "inputs": [
54 | {
55 | "indexed": true,
56 | "internalType": "bytes32",
57 | "name": "outputRoot",
58 | "type": "bytes32"
59 | },
60 | {
61 | "indexed": true,
62 | "internalType": "uint256",
63 | "name": "l2OutputIndex",
64 | "type": "uint256"
65 | },
66 | {
67 | "indexed": true,
68 | "internalType": "uint256",
69 | "name": "l2BlockNumber",
70 | "type": "uint256"
71 | },
72 | {
73 | "indexed": false,
74 | "internalType": "uint256",
75 | "name": "l1Timestamp",
76 | "type": "uint256"
77 | }
78 | ],
79 | "name": "OutputProposed",
80 | "type": "event"
81 | },
82 | {
83 | "anonymous": false,
84 | "inputs": [
85 | {
86 | "indexed": true,
87 | "internalType": "uint256",
88 | "name": "prevNextOutputIndex",
89 | "type": "uint256"
90 | },
91 | {
92 | "indexed": true,
93 | "internalType": "uint256",
94 | "name": "newNextOutputIndex",
95 | "type": "uint256"
96 | }
97 | ],
98 | "name": "OutputsDeleted",
99 | "type": "event"
100 | },
101 | {
102 | "inputs": [],
103 | "name": "CHALLENGER",
104 | "outputs": [
105 | {
106 | "internalType": "address",
107 | "name": "",
108 | "type": "address"
109 | }
110 | ],
111 | "stateMutability": "view",
112 | "type": "function"
113 | },
114 | {
115 | "inputs": [],
116 | "name": "L2_BLOCK_TIME",
117 | "outputs": [
118 | {
119 | "internalType": "uint256",
120 | "name": "",
121 | "type": "uint256"
122 | }
123 | ],
124 | "stateMutability": "view",
125 | "type": "function"
126 | },
127 | {
128 | "inputs": [],
129 | "name": "PROPOSER",
130 | "outputs": [
131 | {
132 | "internalType": "address",
133 | "name": "",
134 | "type": "address"
135 | }
136 | ],
137 | "stateMutability": "view",
138 | "type": "function"
139 | },
140 | {
141 | "inputs": [],
142 | "name": "SUBMISSION_INTERVAL",
143 | "outputs": [
144 | {
145 | "internalType": "uint256",
146 | "name": "",
147 | "type": "uint256"
148 | }
149 | ],
150 | "stateMutability": "view",
151 | "type": "function"
152 | },
153 | {
154 | "inputs": [
155 | {
156 | "internalType": "uint256",
157 | "name": "_l2BlockNumber",
158 | "type": "uint256"
159 | }
160 | ],
161 | "name": "computeL2Timestamp",
162 | "outputs": [
163 | {
164 | "internalType": "uint256",
165 | "name": "",
166 | "type": "uint256"
167 | }
168 | ],
169 | "stateMutability": "view",
170 | "type": "function"
171 | },
172 | {
173 | "inputs": [
174 | {
175 | "internalType": "uint256",
176 | "name": "_l2OutputIndex",
177 | "type": "uint256"
178 | }
179 | ],
180 | "name": "deleteL2Outputs",
181 | "outputs": [],
182 | "stateMutability": "nonpayable",
183 | "type": "function"
184 | },
185 | {
186 | "inputs": [
187 | {
188 | "internalType": "uint256",
189 | "name": "_l2OutputIndex",
190 | "type": "uint256"
191 | }
192 | ],
193 | "name": "getL2Output",
194 | "outputs": [
195 | {
196 | "components": [
197 | {
198 | "internalType": "bytes32",
199 | "name": "outputRoot",
200 | "type": "bytes32"
201 | },
202 | {
203 | "internalType": "uint128",
204 | "name": "timestamp",
205 | "type": "uint128"
206 | },
207 | {
208 | "internalType": "uint128",
209 | "name": "l2BlockNumber",
210 | "type": "uint128"
211 | }
212 | ],
213 | "internalType": "struct Types.OutputProposal",
214 | "name": "",
215 | "type": "tuple"
216 | }
217 | ],
218 | "stateMutability": "view",
219 | "type": "function"
220 | },
221 | {
222 | "inputs": [
223 | {
224 | "internalType": "uint256",
225 | "name": "_l2BlockNumber",
226 | "type": "uint256"
227 | }
228 | ],
229 | "name": "getL2OutputAfter",
230 | "outputs": [
231 | {
232 | "components": [
233 | {
234 | "internalType": "bytes32",
235 | "name": "outputRoot",
236 | "type": "bytes32"
237 | },
238 | {
239 | "internalType": "uint128",
240 | "name": "timestamp",
241 | "type": "uint128"
242 | },
243 | {
244 | "internalType": "uint128",
245 | "name": "l2BlockNumber",
246 | "type": "uint128"
247 | }
248 | ],
249 | "internalType": "struct Types.OutputProposal",
250 | "name": "",
251 | "type": "tuple"
252 | }
253 | ],
254 | "stateMutability": "view",
255 | "type": "function"
256 | },
257 | {
258 | "inputs": [
259 | {
260 | "internalType": "uint256",
261 | "name": "_l2BlockNumber",
262 | "type": "uint256"
263 | }
264 | ],
265 | "name": "getL2OutputIndexAfter",
266 | "outputs": [
267 | {
268 | "internalType": "uint256",
269 | "name": "",
270 | "type": "uint256"
271 | }
272 | ],
273 | "stateMutability": "view",
274 | "type": "function"
275 | },
276 | {
277 | "inputs": [
278 | {
279 | "internalType": "uint256",
280 | "name": "_startingBlockNumber",
281 | "type": "uint256"
282 | },
283 | {
284 | "internalType": "uint256",
285 | "name": "_startingTimestamp",
286 | "type": "uint256"
287 | }
288 | ],
289 | "name": "initialize",
290 | "outputs": [],
291 | "stateMutability": "nonpayable",
292 | "type": "function"
293 | },
294 | {
295 | "inputs": [],
296 | "name": "latestBlockNumber",
297 | "outputs": [
298 | {
299 | "internalType": "uint256",
300 | "name": "",
301 | "type": "uint256"
302 | }
303 | ],
304 | "stateMutability": "view",
305 | "type": "function"
306 | },
307 | {
308 | "inputs": [],
309 | "name": "latestOutputIndex",
310 | "outputs": [
311 | {
312 | "internalType": "uint256",
313 | "name": "",
314 | "type": "uint256"
315 | }
316 | ],
317 | "stateMutability": "view",
318 | "type": "function"
319 | },
320 | {
321 | "inputs": [],
322 | "name": "nextBlockNumber",
323 | "outputs": [
324 | {
325 | "internalType": "uint256",
326 | "name": "",
327 | "type": "uint256"
328 | }
329 | ],
330 | "stateMutability": "view",
331 | "type": "function"
332 | },
333 | {
334 | "inputs": [],
335 | "name": "nextOutputIndex",
336 | "outputs": [
337 | {
338 | "internalType": "uint256",
339 | "name": "",
340 | "type": "uint256"
341 | }
342 | ],
343 | "stateMutability": "view",
344 | "type": "function"
345 | },
346 | {
347 | "inputs": [
348 | {
349 | "internalType": "bytes32",
350 | "name": "_outputRoot",
351 | "type": "bytes32"
352 | },
353 | {
354 | "internalType": "uint256",
355 | "name": "_l2BlockNumber",
356 | "type": "uint256"
357 | },
358 | {
359 | "internalType": "bytes32",
360 | "name": "_l1BlockHash",
361 | "type": "bytes32"
362 | },
363 | {
364 | "internalType": "uint256",
365 | "name": "_l1BlockNumber",
366 | "type": "uint256"
367 | }
368 | ],
369 | "name": "proposeL2Output",
370 | "outputs": [],
371 | "stateMutability": "payable",
372 | "type": "function"
373 | },
374 | {
375 | "inputs": [],
376 | "name": "startingBlockNumber",
377 | "outputs": [
378 | {
379 | "internalType": "uint256",
380 | "name": "",
381 | "type": "uint256"
382 | }
383 | ],
384 | "stateMutability": "view",
385 | "type": "function"
386 | },
387 | {
388 | "inputs": [],
389 | "name": "startingTimestamp",
390 | "outputs": [
391 | {
392 | "internalType": "uint256",
393 | "name": "",
394 | "type": "uint256"
395 | }
396 | ],
397 | "stateMutability": "view",
398 | "type": "function"
399 | },
400 | {
401 | "inputs": [],
402 | "name": "version",
403 | "outputs": [
404 | {
405 | "internalType": "string",
406 | "name": "",
407 | "type": "string"
408 | }
409 | ],
410 | "stateMutability": "view",
411 | "type": "function"
412 | }
413 | ]
--------------------------------------------------------------------------------
/logo/blurred.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refcell/varro/9cd8f8d901410effa122c3cadd54ff083cb22ee9/logo/blurred.png
--------------------------------------------------------------------------------
/logo/colosseum.jpeg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refcell/varro/9cd8f8d901410effa122c3cadd54ff083cb22ee9/logo/colosseum.jpeg
--------------------------------------------------------------------------------
/logo/refcell.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refcell/varro/9cd8f8d901410effa122c3cadd54ff083cb22ee9/logo/refcell.png
--------------------------------------------------------------------------------
/logo/varro.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/refcell/varro/9cd8f8d901410effa122c3cadd54ff083cb22ee9/logo/varro.png
--------------------------------------------------------------------------------
/src/builder.rs:
--------------------------------------------------------------------------------
1 | use std::time::Duration;
2 |
3 | use ethers_core::types::{
4 | Address,
5 | H256,
6 | };
7 | use eyre::Result;
8 |
9 | use crate::{
10 | client::Varro,
11 | config::Config,
12 | metrics::Metrics,
13 | rollup::RollupNode,
14 | L1Client,
15 | OutputOracleContract,
16 | };
17 |
18 | /// VarroBuilder
19 | ///
20 | /// A builder for the [Varro] client.
21 | #[derive(Debug, Default, Clone)]
22 | pub struct VarroBuilder {
23 | /// An L1 [L1Client]
24 | pub l1_client: Option,
25 | /// A [RollupNode] client
26 | pub rollup_node: Option,
27 | /// An output oracle contract
28 | pub output_oracle: Option>,
29 | /// Whether to use non-finalized L1 data to propose L2 blocks.
30 | pub allow_non_finalized: Option,
31 | /// The proposer
32 | pub proposer: Option,
33 | /// The proposer's private key used to send the output transactions.
34 | pub output_private_key: Option,
35 | /// The polling interval
36 | pub polling_interval: Option,
37 | /// An _optional_ metrics server
38 | pub metrics: Option,
39 | /// The transaction backoff
40 | pub tx_backoff: Option,
41 | }
42 |
43 | impl TryFrom for VarroBuilder {
44 | type Error = eyre::Report;
45 |
46 | fn try_from(conf: Config) -> std::result::Result {
47 | let output_oracle_contract = conf.get_output_oracle_contract()?;
48 | Ok(Self {
49 | l1_client: Some(conf.get_l1_client()?),
50 | rollup_node: Some(conf.get_rollup_node_client()?),
51 | output_oracle: Some(output_oracle_contract),
52 | allow_non_finalized: Some(conf.allow_non_finalized),
53 | proposer: Some(conf.output_oracle_address),
54 | output_private_key: Some(conf.get_output_private_key()?),
55 | polling_interval: Some(conf.polling_interval),
56 | metrics: None,
57 | tx_backoff: Some(conf.resubmission_timeout),
58 | })
59 | }
60 | }
61 |
62 | impl VarroBuilder {
63 | /// Creates a new [VarroBuilder].
64 | pub fn new() -> Self {
65 | Self::default()
66 | }
67 |
68 | /// Sets the metrics server for the [Varro] client.
69 | pub fn with_metrics(&mut self, metrics: Metrics) -> &mut Self {
70 | self.metrics = Some(metrics);
71 | self
72 | }
73 |
74 | /// Sets the L1 client for the [Varro] client.
75 | pub fn with_l1_client(&mut self, l1_client: L1Client) -> &mut Self {
76 | self.l1_client = Some(l1_client);
77 | self
78 | }
79 |
80 | /// Sets the rollup node for the [Varro] client.
81 | pub fn with_rollup_node(&mut self, rollup_node: RollupNode) -> &mut Self {
82 | self.rollup_node = Some(rollup_node);
83 | self
84 | }
85 |
86 | /// Sets the output oracle for the [Varro] client.
87 | pub fn with_output_oracle(&mut self, output_oracle: OutputOracleContract) -> &mut Self {
88 | self.output_oracle = Some(output_oracle);
89 | self
90 | }
91 |
92 | /// Sets whether to use non-finalized L1 data to propose L2 blocks.
93 | pub fn with_allow_non_finalized(&mut self, allow_non_finalized: bool) -> &mut Self {
94 | self.allow_non_finalized = Some(allow_non_finalized);
95 | self
96 | }
97 |
98 | /// Sets the proposer for the [Varro] client.
99 | pub fn with_proposer(&mut self, proposer: Address) -> &mut Self {
100 | self.proposer = Some(proposer);
101 | self
102 | }
103 |
104 | /// Sets the proposer's private key used to send the output transactions.
105 | /// for the [Varro] client.
106 | pub fn with_output_private_key(&mut self, output_private_key: H256) -> &mut Self {
107 | self.output_private_key = Some(output_private_key);
108 | self
109 | }
110 |
111 | /// Sets the polling interval for the [Varro] client.
112 | pub fn with_polling_interval(&mut self, polling_interval: Duration) -> &mut Self {
113 | self.polling_interval = Some(polling_interval);
114 | self
115 | }
116 |
117 | /// Sets the transaction backoff for the [Varro] client.
118 | pub fn with_tx_backoff(&mut self, tx_backoff: Duration) -> &mut Self {
119 | self.tx_backoff = Some(tx_backoff);
120 | self
121 | }
122 |
123 | /// Builds the [Varro] client.
124 | pub fn build(self) -> Result {
125 | Ok(Varro::try_from(self)?)
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/cli.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | fs::File,
3 | io::Write,
4 | str::FromStr,
5 | time::Duration,
6 | };
7 |
8 | use crate::{
9 | config::Config,
10 | errors::ConfigError,
11 | extract_env,
12 | };
13 | use clap::Parser;
14 | use dirs::home_dir;
15 | use ethers_core::types::Address;
16 |
17 | /// The [Varro] CLI
18 | #[derive(Parser)]
19 | pub struct Cli {
20 | /// The HTTP provider URL for L1.
21 | #[clap(short = 'l', long, env = "L1_RPC_URL")]
22 | l1_client_rpc_url: Option,
23 | /// The HTTP provider URL for the rollup node.
24 | #[clap(short = 'r', long, env = "ROLLUP_NODE_RPC_URL")]
25 | rollup_node_rpc_url: Option,
26 | /// The L2OutputOracle contract address.
27 | #[clap(short = 's', long, env = "OUTPUT_ORACLE_ADDRESS")]
28 | output_oracle_address: Option,
29 | /// The delay between querying L2 for more blocks and creating a new batch.
30 | #[clap(short = 'i', long, default_value = "2")]
31 | polling_interval: u64,
32 | /// The number of confirmations which to wait after appending new batches.
33 | #[clap(short = 'c', long, default_value = "2")]
34 | num_confirmation: u64,
35 | /// The number of [VarroError::NonceTooLow] errors required to give up on a
36 | /// tx at a particular nonce without receiving confirmation.
37 | #[clap(short = 'a', long, default_value = "2")]
38 | safe_abort_nonce_too_low: u64,
39 | /// The time to wait before resubmitting a transaction.
40 | #[clap(short = 'r', long, default_value = "2")]
41 | resubmission_timeout: u64,
42 | /// The HD seed used to derive the wallet private keys for both
43 | /// the sequencer and proposer. Must be used in conjunction with
44 | /// SequencerHDPath and ProposerHDPath.
45 | ///
46 | /// If not provided, a new mnemonic will be generated.
47 | #[clap(short = 'm', long, env = "MNEMONIC")]
48 | mnemonic: Option,
49 | /// The private key used for the L2 Output transactions.
50 | #[clap(short = 'k', long, env = "OUTPUT_PRIVATE_KEY")]
51 | output_private_key: Option,
52 | /// The derivation path used to obtain the private key for the output transactions.
53 | #[clap(short = 'p', long, default_value = "m/44'/60'/0'/0/0")]
54 | output_hd_path: String,
55 | /// Whether to use non-finalized L1 data to propose L2 blocks.
56 | #[clap(short = 'n', long)]
57 | allow_non_finalized: bool,
58 | }
59 |
60 | impl TryFrom for Config {
61 | type Error = ConfigError;
62 |
63 | fn try_from(cli: Cli) -> Result {
64 | let l1_client_rpc_url =
65 | cli.l1_client_rpc_url.unwrap_or(extract_env!("L1_RPC_URL"));
66 | let rollup_node_rpc_url = cli
67 | .rollup_node_rpc_url
68 | .unwrap_or(extract_env!("ROLLUP_NODE_RPC_URL"));
69 | let output_oracle_address = cli
70 | .output_oracle_address
71 | .unwrap_or(extract_env!("OUTPUT_ORACLE_ADDRESS"));
72 | let polling_interval = Duration::from_secs(cli.polling_interval);
73 | let num_confirmation = cli.num_confirmation;
74 | let safe_abort_nonce_too_low = cli.safe_abort_nonce_too_low;
75 | let resubmission_timeout = Duration::from_secs(cli.resubmission_timeout);
76 | let mnemonic = cli.mnemonic.unwrap_or_else(|| extract_env!("MNEMONIC"));
77 | let output_private_key = cli
78 | .output_private_key
79 | .unwrap_or_else(|| extract_env!("OUTPUT_PRIVATE_KEY"));
80 | let output_hd_path = cli.output_hd_path;
81 | let allow_non_finalized = cli.allow_non_finalized;
82 |
83 | let config = Config {
84 | l1_client_rpc_url,
85 | rollup_node_rpc_url,
86 | output_oracle_address: Address::from_str(&output_oracle_address).map_err(
87 | |_| ConfigError::InvalidOutputOracleAddress(output_oracle_address),
88 | )?,
89 | polling_interval,
90 | num_confirmation,
91 | safe_abort_nonce_too_low,
92 | resubmission_timeout,
93 | mnemonic,
94 | output_private_key,
95 | output_hd_path,
96 | allow_non_finalized,
97 | };
98 |
99 | // Save the config to disk
100 | let config_path = home_dir().unwrap().join(".varro/varro.toml");
101 | let mut file = File::create(&config_path).map_err(|_| {
102 | ConfigError::TomlFileCreation(config_path.to_string_lossy().to_string())
103 | })?;
104 | file.write_all(
105 | toml::to_string(&config)
106 | .map_err(|_| ConfigError::ConfigTomlConversion)?
107 | .as_bytes(),
108 | )
109 | .map_err(|_| ConfigError::TomlFileWrite)?;
110 |
111 | Ok(config)
112 | }
113 | }
114 |
--------------------------------------------------------------------------------
/src/client.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | sync::Arc,
3 | time::Duration,
4 | };
5 |
6 | use eyre::Result;
7 | use futures::lock::Mutex;
8 | use tokio::task::JoinHandle;
9 |
10 | use ethers_core::types::{
11 | Address,
12 | H256,
13 | };
14 |
15 | use tokio::sync::mpsc::{
16 | unbounded_channel,
17 | UnboundedReceiver,
18 | UnboundedSender,
19 | };
20 |
21 | use crate::{
22 | builder::VarroBuilder,
23 | metrics::Metrics,
24 | pool::TransactionPool,
25 | prelude::VarroBuilderError,
26 | rollup::{
27 | OutputResponse,
28 | RollupNode,
29 | SyncStatus,
30 | },
31 | L1Client,
32 | OutputOracleContract,
33 | proposals::ProposalManager,
34 | };
35 |
36 | /// Varro
37 | ///
38 | /// This is the primary varro client, responsible for orchestrating proposal submission.
39 | ///
40 | /// The [Varro] client should be constructed using the [crate::builder::VarroBuilder].
41 | /// The builder provides an ergonomic way to construct the [Varro] client, with sensible defaults.
42 | #[derive(Debug)]
43 | pub struct Varro {
44 | /// An L1 [L1Client]
45 | l1_client: Arc>,
46 | /// A [RollupNode] client
47 | rollup_node: Arc>,
48 | /// The output oracle contract address
49 | output_oracle_address: Address,
50 | /// An output oracle contract
51 | output_oracle: OutputOracleContract,
52 | /// Whether to use non-finalized L1 data to propose L2 blocks.
53 | allow_non_finalized: bool,
54 | /// The proposer
55 | #[allow(dead_code)]
56 | proposer: Address,
57 | /// The proposer's private key used to send the output transactions.
58 | output_private_key: H256,
59 | /// The polling interval
60 | polling_interval: Duration,
61 | /// An _optional_ metrics server
62 | metrics: Option,
63 | /// The proposal sender sends new [OutputResponse].
64 | proposal_sender: UnboundedSender,
65 | /// The proposal receiver listens for new [OutputResponse].
66 | proposal_receiver: UnboundedReceiver,
67 | /// The tx_pool sender sends new [OutputResponse].
68 | tx_pool_sender: UnboundedSender,
69 | /// The tx_pool receiver listens for new [OutputResponse].
70 | tx_pool_receiver: Option>,
71 | /// The transaction pool
72 | transaction_pool: Arc>,
73 | /// The transaction pool task handle
74 | tx_pool_task: Option>>,
75 | /// A handle for the metrics server
76 | metrics_handle: Option>>,
77 | /// A handle for the spawned [ProposalManager] task
78 | proposal_manager_handle: Option>>,
79 | /// A list of output roots submitted to the [TransactionPool].
80 | roots: Vec>,
81 | /// A backoff time for sending transaction to the [TransactionPool]
82 | tx_backoff: Duration,
83 | }
84 |
85 | impl TryFrom for Varro {
86 | type Error = VarroBuilderError;
87 |
88 | fn try_from(mut builder: VarroBuilder) -> std::result::Result {
89 | let l1_client = builder
90 | .l1_client
91 | .take()
92 | .ok_or(VarroBuilderError::MissingL1Client)?;
93 | let rollup_node = builder
94 | .rollup_node
95 | .take()
96 | .ok_or(VarroBuilderError::MissingRollupNode)?;
97 | let output_oracle = builder
98 | .output_oracle
99 | .take()
100 | .ok_or(VarroBuilderError::MissingOutputOracle)?;
101 | let proposer = builder
102 | .proposer
103 | .take()
104 | .ok_or(VarroBuilderError::MissingProposer)?;
105 | let output_private_key = builder
106 | .output_private_key
107 | .take()
108 | .ok_or(VarroBuilderError::MissingOutputPrivateKey)?;
109 | let polling_interval = builder
110 | .polling_interval
111 | .take()
112 | .ok_or(VarroBuilderError::MissingPollingInterval)?;
113 | let allow_non_finalized = match builder.allow_non_finalized {
114 | Some(allow_non_finalized) => allow_non_finalized,
115 | None => {
116 | tracing::warn!(target: "varro=client", "VarroBuilder was not provided 'allow_non_finalized', defaulting to false");
117 | false
118 | }
119 | };
120 | let tx_backoff = builder.tx_backoff.take().ok_or(VarroBuilderError::MissingTxBackoff)?;
121 | let output_oracle_address = output_oracle.address();
122 | let metrics = builder.metrics.take();
123 |
124 | let (proposal_sender, proposal_receiver) = unbounded_channel();
125 | let (tx_pool_sender, tx_pool_receiver) = unbounded_channel();
126 |
127 | Ok(Varro {
128 | l1_client: Arc::new(Mutex::new(l1_client)),
129 | rollup_node: Arc::new(Mutex::new(rollup_node)),
130 | output_oracle_address,
131 | output_oracle,
132 | allow_non_finalized,
133 | proposer,
134 | output_private_key,
135 | polling_interval,
136 | metrics,
137 | proposal_sender,
138 | proposal_receiver,
139 | tx_pool_sender,
140 | tx_pool_receiver: Some(tx_pool_receiver),
141 | transaction_pool: Arc::new(Mutex::new(TransactionPool::new())),
142 | tx_pool_task: None,
143 | metrics_handle: None,
144 | proposal_manager_handle: None,
145 | roots: vec![],
146 | tx_backoff
147 | })
148 | }
149 | }
150 |
151 | impl Varro {
152 | /// Starts the [Varro] client.
153 | pub async fn start(&mut self) {
154 | // Get the L1 Chain ID
155 | // let l1_chain_id = self.l1_client.lock().await.get_chainid().await?;
156 |
157 | // Start a metrics server
158 | self.start_metrics_server().await;
159 |
160 | // Spawn the transaction pool task
161 | self.start_transaction_pool().await;
162 |
163 | // Start the proposal manager to listen for new proposals
164 | self.start_proposal_manager().await;
165 |
166 | // Listen for new proposals and submit them to the transaction pool
167 | // This will block until the proposal receiver channel is closed
168 | self.listen_for_proposals().await;
169 | }
170 | }
171 |
172 | impl Varro {
173 | /// Derives the block number from a [RollupNode]'s [SyncStatus].
174 | pub fn get_block_number(allow_non_finalized: bool, sync_status: SyncStatus) -> u64 {
175 | // If we're not allowed to use non-finalized data,
176 | // then we can only use the finalized l2 block number
177 | if !allow_non_finalized {
178 | return sync_status.finalized_l2
179 | }
180 |
181 | // If "safe" blocks are allowed, use the l2 safe head
182 | sync_status.safe_l2
183 | }
184 |
185 | /// Spawns the transaction pool task
186 | async fn start_transaction_pool(&mut self) {
187 | let output_private_key = self.output_private_key;
188 | let l1_client = Arc::clone(&self.l1_client);
189 | let tx_pool_receiver = self.tx_pool_receiver.take();
190 | let tx_pool = Arc::clone(&self.transaction_pool);
191 | let tx_pool_handle = tokio::spawn(async move {
192 | tx_pool
193 | .lock()
194 | .await
195 | .start(l1_client, output_private_key, tx_pool_receiver)
196 | .await
197 | });
198 | self.tx_pool_task = Some(tx_pool_handle);
199 | }
200 |
201 | /// Shuts down associated tasks.
202 | pub async fn shutdown(&mut self) {
203 | // Shutdown the transaction pool
204 | if let Some(tx_pool_task) = &self.tx_pool_task {
205 | tx_pool_task.abort()
206 | }
207 |
208 | // Shutdown the metrics server
209 | if let Some(metrics_handle) = self.metrics_handle.take() {
210 | metrics_handle.abort()
211 | }
212 | }
213 |
214 | /// Starts the [ProposalManager] task
215 | async fn start_proposal_manager(&mut self) {
216 | let l1_client = Arc::clone(&self.l1_client);
217 | let rollup_node = Arc::clone(&self.rollup_node);
218 | let output_oracle_address = self.output_oracle_address;
219 | let allow_non_finalized = self.allow_non_finalized;
220 | let polling_interval = self.polling_interval;
221 | let proposal_sender = self.proposal_sender.clone();
222 | let proposer = self.proposer.clone();
223 | let proposal_manager_handle = tokio::spawn(async move {
224 | ProposalManager::start(
225 | l1_client,
226 | rollup_node,
227 | output_oracle_address,
228 | allow_non_finalized,
229 | polling_interval,
230 | proposal_sender,
231 | proposer,
232 | )
233 | .await
234 | });
235 | self.proposal_manager_handle = Some(proposal_manager_handle);
236 | }
237 |
238 | /// Starts the [Metrics] server
239 | async fn start_metrics_server(&mut self) {
240 | // Spawn the metrics server
241 | if let Some(mut metrics) = self.metrics.take() {
242 | let metrics_handle = tokio::task::spawn(async move {
243 | tracing::info!(target: "varro=client", "starting metrics server...");
244 | metrics.serve().await
245 | });
246 | self.metrics_handle = Some(metrics_handle);
247 | }
248 | }
249 |
250 | /// Listens for new proposals and submits them to the transaction pool.
251 | pub async fn listen_for_proposals(&mut self) {
252 | while let Some(proposal) = self.proposal_receiver.recv().await {
253 | tracing::debug!(target: "varro=client", "received proposal: {:?}", proposal);
254 | self.submit_proposal(proposal).await;
255 | }
256 | tracing::warn!(target: "varro=client", "proposal receiver channel closed, varro client shutting down...");
257 | }
258 |
259 | /// Submits an [OutputResponse] to the [TransactionPool].
260 | pub async fn submit_proposal(&mut self, proposal: OutputResponse) {
261 | tracing::info!(target: "varro=client", "received proposal: {:?}", proposal);
262 | tracing::info!(target: "varro=client", "submitting to transaction pool...");
263 |
264 | // Check if the proposal is already being sent
265 | if self.roots.contains(&proposal.output_root) {
266 | tracing::warn!(target: "varro=client", "proposal already being sent, skipping...");
267 | return;
268 | }
269 |
270 | // Track that we already submitted this proposal
271 | self.roots.push(proposal.output_root.clone());
272 |
273 | // Send proposal to transaction pool with backoff retries
274 | // This is to gracefully handle when the transaction pool is full
275 | while self.tx_pool_sender.send(proposal.clone()).is_err() {
276 | tracing::warn!(target: "varro=client", "transaction pool is full, retrying...");
277 | tokio::time::sleep(self.tx_backoff).await;
278 | }
279 | }
280 | }
281 |
--------------------------------------------------------------------------------
/src/common.rs:
--------------------------------------------------------------------------------
1 | #![allow(missing_docs)]
2 | //! Common Types and Utilies for Varro
3 |
4 | use ethers_contract::abigen;
5 | use eyre::Result;
6 |
7 | pub use ethers_providers::{
8 | Http,
9 | Middleware,
10 | Provider,
11 | };
12 |
13 | abigen!(OutputOracleContract, "contracts/L2OutputOracle.json");
14 |
15 | pub use OutputOracleContract;
16 |
17 | /// An enum of supported output versions for the [crate::rollup::OutputResponse] version.
18 | #[derive(Debug, Clone)]
19 | pub enum SupportedOutputVersion {
20 | /// Output version 1
21 | V1,
22 | }
23 |
24 | impl SupportedOutputVersion {
25 | /// Checks that the given output version is equal to the provided bytes version.
26 | pub fn equals(&self, other: &[u8]) -> bool {
27 | match (self, other.is_empty()) {
28 | // V1 is the only supported version for now - an empty Vec
29 | (Self::V1, true) => true,
30 | _ => false,
31 | }
32 | }
33 | }
34 |
35 | // TODO: Can we just use the Middleware trait from ethers?
36 | /// An L1Client is an [Http] [Provider] for Ethereum Mainnet.
37 | pub type L1Client = Provider;
38 |
39 | // TODO: properly construct an OutputOracle Contract
40 | /// An OutputOracle is a contract that provides the next block number to use for the next proposal.
41 | #[derive(Debug, Default, Clone)]
42 | pub struct OutputOracle {}
43 |
44 | impl OutputOracle {
45 | /// Get the next block number to use for the next proposal.
46 | pub async fn get_next_block_number(&self) -> Result {
47 | Ok(0)
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/config.rs:
--------------------------------------------------------------------------------
1 | use std::{
2 | str::FromStr,
3 | time::Duration,
4 | };
5 |
6 | use ethers_core::types::{
7 | Address,
8 | H256,
9 | };
10 | use ethers_providers::{
11 | Http,
12 | Provider,
13 | };
14 | use eyre::Result;
15 | use serde::{
16 | Deserialize,
17 | Serialize,
18 | };
19 |
20 | use crate::{
21 | errors::ConfigError,
22 | extract_env,
23 | rollup::RollupNode,
24 | OutputOracleContract,
25 | };
26 |
27 | /// A system configuration
28 | #[derive(Debug, Clone, Serialize, Deserialize)]
29 | pub struct Config {
30 | /// The L1 client rpc url.
31 | pub l1_client_rpc_url: String,
32 | /// The L2 client rollup node rpc url.
33 | /// Note: This is not explicitly the same as an "L2 client rpc url".
34 | /// The Rollup Node may expose a [RollupRPC] interface on a different port.
35 | /// This is the url that should be used for this field.
36 | pub rollup_node_rpc_url: String,
37 | /// The L2OutputOracle contract address.
38 | pub output_oracle_address: Address,
39 | /// The delay between querying L2 for more blocks and creating a new batch.
40 | pub polling_interval: Duration,
41 | /// The number of confirmations which to wait after appending new batches.
42 | pub num_confirmation: u64,
43 | /// The number of [VarroError::NonceTooLow] errors required to give up on a
44 | /// tx at a particular nonce without receiving confirmation.
45 | pub safe_abort_nonce_too_low: u64,
46 | /// The time to wait before resubmitting a transaction.
47 | pub resubmission_timeout: Duration,
48 | /// The HD seed used to derive the wallet private keys for both
49 | /// the sequencer and proposer. Must be used in conjunction with
50 | /// SequencerHDPath and ProposerHDPath.
51 | ///
52 | /// If not provided, a new mnemonic will be generated.
53 | pub mnemonic: String,
54 | /// The private key used for the L2 Output transactions.
55 | pub output_private_key: String,
56 | /// The HD path used to derive the wallet private key for the sequencer.
57 | pub output_hd_path: String,
58 | /// Whether to use non-finalized L1 data to propose L2 blocks.
59 | pub allow_non_finalized: bool,
60 | }
61 |
62 | impl Default for Config {
63 | fn default() -> Self {
64 | Self {
65 | l1_client_rpc_url: extract_env!("L1_CLIENT_RPC_URL"),
66 | rollup_node_rpc_url: extract_env!("ROLLUP_NODE_RPC_URL"),
67 | output_oracle_address: Address::from_str(&extract_env!(
68 | "OUTPUT_ORACLE_ADDRESS"
69 | ))
70 | .expect("invalid output oracle address"),
71 | polling_interval: Duration::from_secs(5),
72 | num_confirmation: 1,
73 | safe_abort_nonce_too_low: 3,
74 | resubmission_timeout: Duration::from_secs(60),
75 | mnemonic: extract_env!("MNEMONIC"),
76 | output_private_key: extract_env!("OUTPUT_PRIVATE_KEY"),
77 | output_hd_path: String::from("m/44'/60'/0'/0/0"),
78 | allow_non_finalized: false,
79 | }
80 | }
81 | }
82 |
83 | impl Config {
84 | /// Parses the output private key string into a 32-byte hash
85 | pub fn get_output_private_key(&self) -> Result {
86 | Ok(H256::from_str(&self.output_private_key).map_err(|_| {
87 | ConfigError::InvalidOutputPrivateKey(self.output_private_key.clone())
88 | })?)
89 | }
90 |
91 | /// Returns the output oracle address
92 | pub fn get_output_oracle_address(&self) -> Address {
93 | self.output_oracle_address
94 | }
95 |
96 | /// Constructs an L1 [Provider] using the configured L1 client rpc url.
97 | pub fn get_l1_client(&self) -> Result> {
98 | Ok(Provider::::try_from(&self.l1_client_rpc_url)
99 | .map_err(|_| ConfigError::InvalidL1ClientUrl)?)
100 | }
101 |
102 | /// Constructs a Rollup Node [RollupNode] using the configured rollup node rpc url.
103 | pub fn get_rollup_node_client(&self) -> Result {
104 | Ok(RollupNode::try_from(self.rollup_node_rpc_url.clone())
105 | .map_err(|_| ConfigError::InvalidRollupNodeRpcUrl)?)
106 | }
107 |
108 | /// Get the output oracle contract
109 | pub fn get_output_oracle_contract(&self) -> Result>> {
110 | let l1_client = self.get_l1_client()?;
111 | let output_oracle = OutputOracleContract::new(
112 | self.get_output_oracle_address(),
113 | l1_client.into(),
114 | );
115 | Ok(output_oracle)
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/errors.rs:
--------------------------------------------------------------------------------
1 | use thiserror::Error;
2 |
3 | /// [crate::client::Varro] Error
4 | #[derive(Debug, Error)]
5 | pub enum VarroError {
6 | /// Missing client
7 | #[error("missing client")]
8 | MissingClient,
9 | }
10 |
11 | /// A [crate::rollup::RollupNode] Error
12 | #[derive(Debug, Error)]
13 | pub enum RollupNodeError {
14 | /// Failed to create a new rollup node
15 | /// from the given rpc url.
16 | #[error("failed to create a new rollup node from the given rpc url: {0}")]
17 | RollupNodeInvalidUrl(String),
18 | }
19 |
20 | /// A [crate::config::Config] Error
21 | #[derive(Debug, Error)]
22 | pub enum ConfigError {
23 | /// L1 Client URL is invalid
24 | #[error("l1 client url is invalid")]
25 | InvalidL1ClientUrl,
26 | /// Rollup Node RPC URL is invalid
27 | #[error("rollup node rpc url is invalid")]
28 | InvalidRollupNodeRpcUrl,
29 | /// Failed to create toml file on disk
30 | #[error("failed to create toml file at {0}")]
31 | TomlFileCreation(String),
32 | /// Failed to translate the [crate::config::Config] to a toml object
33 | #[error("failed to translate the config to a toml object")]
34 | ConfigTomlConversion,
35 | /// Failed to write the [crate::config::Config] to the toml file
36 | #[error("failed to write the config to the toml file")]
37 | TomlFileWrite,
38 | /// An invalid output oracle address was provided
39 | #[error("an invalid output oracle address was provided: {0}")]
40 | InvalidOutputOracleAddress(String),
41 | /// Failed to parse the given output private key as a 32 byte hex string.
42 | #[error("failed to parse the given output private key as a 32 byte hex string: {0}")]
43 | InvalidOutputPrivateKey(String),
44 | }
45 |
46 | /// A [crate::client::VarroBuilder] Error
47 | #[derive(Debug, Error)]
48 | pub enum VarroBuilderError {
49 | /// An [crate::L1Client] is required to build a [Varro] client.
50 | #[error("an l1 client is required to build a varro client")]
51 | MissingL1Client,
52 | /// A [crate::rollup::RollupNode] is required to build a [Varro] client.
53 | #[error("an rollup node is required to build a varro client")]
54 | MissingRollupNode,
55 | /// An [crate::output::OutputOracle] is required to build a [Varro] client.
56 | #[error("an output oracle is required to build a varro client")]
57 | MissingOutputOracle,
58 | /// A [crate::proposer::Proposer] is required to build a [Varro] client.
59 | #[error("a proposer is required to build a varro client")]
60 | MissingProposer,
61 | /// An output private key is required to build a [Varro] client.
62 | #[error("an output private key is required to build a varro client")]
63 | MissingOutputPrivateKey,
64 | /// A polling interval is required to build a [Varro] client.
65 | #[error("a polling interval is required to build a varro client")]
66 | MissingPollingInterval,
67 | /// A backoff is required for resubmission of transactions
68 | #[error("a backoff is required for resubmission of transactions")]
69 | MissingTxBackoff,
70 | }
71 |
--------------------------------------------------------------------------------
/src/lib.rs:
--------------------------------------------------------------------------------
1 | #![doc=include_str!("../README.md")]
2 | #![warn(missing_docs)]
3 | #![warn(unused_extern_crates)]
4 | #![forbid(unsafe_code)]
5 | #![forbid(where_clauses_object_safety)]
6 |
7 | mod common;
8 | pub use common::*;
9 |
10 | pub mod telemetry;
11 | pub use telemetry::*;
12 |
13 | /// The core [Varro] client
14 | pub mod client;
15 |
16 | /// A Builder for the [Varro] client
17 | pub mod builder;
18 |
19 | /// Pool contains the logic to manage proposal transactions
20 | pub mod pool;
21 |
22 | /// Proposals holds the [ProposalManager] which
23 | /// listens for new [OutputResponse] proposals
24 | pub mod proposals;
25 |
26 | /// Configuration
27 | pub mod config;
28 |
29 | /// CLI parsing
30 | pub mod cli;
31 |
32 | /// The Rollup Node
33 | pub mod rollup;
34 |
35 | /// Common Errors
36 | pub mod errors;
37 |
38 | /// The metrics server
39 | pub mod metrics;
40 |
41 | /// Common internal macros
42 | pub(crate) mod macros;
43 |
44 | /// Re-export Archon Types
45 | pub mod prelude {
46 | pub use crate::{
47 | builder::*,
48 | client::*,
49 | common::*,
50 | config::*,
51 | errors::*,
52 | metrics::*,
53 | telemetry::*,
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/src/macros.rs:
--------------------------------------------------------------------------------
1 | /// This macro is used to read the value of an environment variable.
2 | /// If the environment variable is not set, the macro will panic.
3 | #[macro_export]
4 | macro_rules! extract_env {
5 | ($a:expr) => {
6 | std::env::var($a).unwrap_or_else(|_| panic!("{} is not set", $a))
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/metrics.rs:
--------------------------------------------------------------------------------
1 | use eyre::Result;
2 | use std::{
3 | io::prelude::*,
4 | net::{
5 | TcpListener,
6 | TcpStream,
7 | },
8 | };
9 |
10 | /// Metrics
11 | ///
12 | /// Serves metrics for the [crate::client::Varro] client.
13 | #[derive(Debug, Default, Clone)]
14 | pub struct Metrics {}
15 |
16 | impl Metrics {
17 | /// Constructs a new [Metrics] instance
18 | pub fn new() -> Self {
19 | Self {}
20 | }
21 |
22 | /// Serve a [TcpListener] to provide [crate::client::Varro] metrics.
23 | pub async fn serve(&mut self) -> Result<()> {
24 | let addr = "127.0.0.1:8082".to_string();
25 | let listener = TcpListener::bind(&addr)
26 | .map_err(|_| eyre::eyre!("Metrics failed to bind to {}", addr))?;
27 | for stream in listener.incoming().flatten() {
28 | self.handle_connection(stream)?;
29 | }
30 | Ok(())
31 | }
32 |
33 | /// Handle an incoming connection.
34 | pub fn handle_connection(&self, mut stream: TcpStream) -> Result<()> {
35 | let mut buffer = [0; 1024];
36 | let read_bytes = stream.read(&mut buffer)?;
37 | println!(
38 | "Request with {} bytes: {}",
39 | read_bytes,
40 | String::from_utf8_lossy(&buffer[..])
41 | );
42 | Ok(())
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/pool.rs:
--------------------------------------------------------------------------------
1 | #![allow(dead_code)]
2 | #![allow(unused_variables)]
3 |
4 | use ethers_middleware::SignerMiddleware;
5 | use ethers_signers::LocalWallet;
6 | use futures::lock::Mutex;
7 | use std::sync::Arc;
8 |
9 | use crate::{
10 | rollup::OutputResponse,
11 | L1Client,
12 | OutputOracle,
13 | OutputOracleContract,
14 | };
15 | use ethers_providers::Middleware;
16 | use ethers_core::{types::{
17 | TransactionReceipt,
18 | H256, Address, U256, transaction::eip2718::TypedTransaction,
19 | }, k256::ecdsa::SigningKey};
20 | use eyre::Result;
21 | use tokio::{
22 | sync::mpsc::UnboundedReceiver,
23 | task::JoinHandle,
24 | };
25 |
26 | /// TransactionPool
27 | ///
28 | /// The transaction pool is responsible for managing and sending [crate::rollup::OutputResponse]
29 | /// proposal transactions to the L1 network.
30 | #[derive(Debug, Default)]
31 | pub struct TransactionPool {
32 | /// The L1 client
33 | pub l1_client: Option,
34 | /// The output oracle
35 | pub output_oracle: Option,
36 | /// Transaction Construction Join Handles
37 | pub tx_construction_handles: Arc>>>>,
38 | /// The core transaction pool task
39 | pub core_task: Option>>,
40 | }
41 |
42 | impl TransactionPool {
43 | /// Constructs a new [TransactionPool].
44 | pub fn new() -> Self {
45 | Self::default()
46 | }
47 |
48 | /// Starts the transaction pool in a new thread.
49 | pub async fn start(
50 | &mut self,
51 | l1_client: Arc>,
52 | private_key: H256,
53 | receiver: Option>,
54 | ) -> Result<()> {
55 | let handles = Arc::clone(&self.tx_construction_handles);
56 | let mut receiver =
57 | receiver.ok_or(eyre::eyre!("Missing output response receiver"))?;
58 | // Spawn transaction pool in a separate task
59 | let main_handle = tokio::task::spawn(async move {
60 | while let Some(proposal) = receiver.recv().await {
61 | let l1_client = Arc::clone(&l1_client);
62 | let sub_task = tokio::task::spawn(async move {
63 | let tx = TransactionPool::construct_proposal_tx(
64 | proposal,
65 | Arc::clone(&l1_client),
66 | &private_key,
67 | )
68 | .await?;
69 | let receipt = TransactionPool::send_transaction(
70 | Arc::clone(&l1_client),
71 | &private_key,
72 | tx,
73 | )
74 | .await?;
75 | tracing::info!(target: "varro=pool", "Sent transaction: {:?}", receipt);
76 | Ok(())
77 | });
78 | handles.lock().await.push(sub_task);
79 | }
80 | Ok(())
81 | });
82 | self.core_task = Some(main_handle);
83 |
84 | Ok(())
85 | }
86 |
87 | /// Constructs a [TypedTransaction] for a given [OutputResponse] proposal.
88 | ///
89 | /// Returns an [eyre::Result] containing the constructed [TypedTransaction].
90 | pub async fn construct_proposal_tx(
91 | proposal: OutputResponse,
92 | l1_client: Arc>,
93 | private_key: &H256,
94 | ) -> Result {
95 | tracing::debug!(target: "varro=pool", "Constructing proposal: {:?}", proposal);
96 |
97 | let unwrapped_client = l1_client.lock().await;
98 | let signing_key = SigningKey::from_bytes(&private_key.0)?;
99 | let wallet = LocalWallet::from(signing_key);
100 | let client = SignerMiddleware::new(unwrapped_client.to_owned(), wallet);
101 | let client = Arc::new(client);
102 |
103 | // TODO: get the output oracle address from the config (pass it into this function)
104 | let output_oracle_address = Address::zero();
105 | let output_oracle_contract = OutputOracleContract::new(output_oracle_address, client);
106 |
107 | let tx = output_oracle_contract.propose_l2_output(
108 | H256::from_slice(proposal.output_root.as_slice()).into(),
109 | U256::from(proposal.block_ref.number),
110 | // TODO: `current_l1` should contain the l1 hash and the l1 number
111 | // TODO: this should be the current l1 hash
112 | // proposal.sync_status.current_l1,
113 | H256::zero().into(),
114 | U256::from(proposal.sync_status.current_l1),
115 | );
116 |
117 | Ok(tx.tx)
118 | }
119 |
120 | /// Sends a [TypedTransaction] to the L1 network through an [L1Client].
121 | ///
122 | /// Returns an [eyre::Result] containing the [TransactionReceipt] for the sent transaction.
123 | pub async fn send_transaction(
124 | l1_client: Arc>,
125 | private_key: &H256,
126 | tx: TypedTransaction,
127 | ) -> Result