feat(bridge-withdrawer): PoC astria-bridge-withdrawer implementation#984
feat(bridge-withdrawer): PoC astria-bridge-withdrawer implementation#984
Conversation
## Summary implement `ethereum` module for astria-bridge-withdrawer. ## Background required to read from the astria withdrawal evm contract. ## Changes - implement `AstriaWithdrawer.sol` contract, put inside `ethereum/` dir as a foundry project - implement `ethereum` module which contains a `Watcher` that watches the contract for `Withdrawal` events - the events are sent to a `Batcher` which batches all events by block number and sends a batch of events to a sequencer handler (to be implemented later) ## Testing unit tests ## Related Issues #913
…er and ics20 withdrawals (#1101) ## Summary as title says ## Changes - update AstriaWithdrawer contract to have separate methods for sequencer and ics20 withdrawals `withdrawToSequencer` or `withdrawToOriginChain` - these methods now take different parameters and emit different events ## Testing unit tests
) ## Summary This adds the `Submitter` and merges in the `Watcher` related logic into a single `WithdrawService`. `Submitter` receives batches of rollup transactions that have been converted into sequencer `Action`s from the `Batcher` and does the following: 1. fetch the current nonce and create the `UnsignedTransaction` 2. sign the transaction 3. `broadcast_tx_commit` If submission to the sequencer fails for any reason (either in `CheckTx` or `DeliverTx`), the `Submitter` will stop, instead relying on the service's recovery process to reconstruct the batch from rollup transaction and resubmit. ## Background Withdrawal batches need to be submitted to the sequencer. ## Changes - Add `Submitter` - Add logic for converting the events reaped from rollup to `Action`s - a lot of random cleanup ## Testing How are these changes tested? ## Metrics - Some metrics we use in composer already since i copied over a good amount of submission logic: - NONCE_FETCH_COUNT - NONCE_FETCH_FAILURE_COUNT - NONCE_FETCH_LATENCY - CURRENT_NONCE - SEQUENCER_SUBMISSION_FAILURE_COUNT - SEQUENCER_SUBMISSION_LATENCY ## Related Issues Link any issues that are related, prefer full github links. closes <!-- list any issues closed here --> --------- Co-authored-by: elizabeth <elizabethjbinks@gmail.com>
SuperFluffy
left a comment
There was a problem hiding this comment.
I have skimmed the PR and left a few early comments.
Please provide blackbox tests for this service.
| /// It also contains a `return_address` field which may or may not be the same as the signer | ||
| /// of the packet. The funds will be returned to the `return_address` in the case of a timeout. | ||
| #[derive(Debug, Clone)] | ||
| #[derive(Debug, Clone, PartialEq, Eq)] |
There was a problem hiding this comment.
All fields should be private.
There was a problem hiding this comment.
all the other actions have pub fields - we should probably make constructors for them instead? but a bit out of scope for here
There was a problem hiding this comment.
can we make a follow up issue here? seems a general code quality thing
There was a problem hiding this comment.
Gonna let it pass here because the other actions also have their fields exposed. But yeah, in general I prefer to not leak that stuff. We should address this in a follow, prior to publishing this crate.
| /// Construct a `SequencerSigner` from a file. | ||
| /// | ||
| /// The file should contain a hex-encoded ed25519 secret key. | ||
| pub(super) fn from_path<P: AsRef<Path>>(path: P) -> eyre::Result<Self> { |
There was a problem hiding this comment.
| pub(super) fn from_path<P: AsRef<Path>>(path: P) -> eyre::Result<Self> { | |
| pub(super) fn try_from_path<P: AsRef<Path>>(path: P) -> eyre::Result<Self> { |
| }; | ||
| use sequencer_client::Address; | ||
|
|
||
| pub(super) struct SequencerSigner { |
There was a problem hiding this comment.
This type does not in fact sign anything as far as I can see? I would expect there to be a method SequencerSigner::sign.
| mod signer; | ||
|
|
||
| pub(super) struct Handle { | ||
| pub(super) batches_tx: mpsc::Sender<Batch>, |
There was a problem hiding this comment.
Make this private. No point having a handle if it just reveals its contents. Also, please don't overload abbreviations. In other parts of this crate _tx is used as transaction, whereas here it's used as sending
|
|
||
| impl Builder { | ||
| /// Instantiates an `Submitter`. | ||
| pub(crate) async fn build(self) -> eyre::Result<(super::Submitter, super::Handle)> { |
There was a problem hiding this comment.
- There are multiple errors in here that don't have context
- This constructor should ideally not be async
| let sequencer_cometbft_client = sequencer_client::HttpClient::new(&*cometbft_endpoint) | ||
| .context("failed constructing cometbft http client")?; | ||
|
|
||
| let actual_chain_id = get_sequencer_chain_id(sequencer_cometbft_client.clone()).await?; |
There was a problem hiding this comment.
This could probably be moved into a "runtime intializer" that runs before the select! loop
This would remove the need to make this method async.
joroshiba
left a comment
There was a problem hiding this comment.
Some general comments on source of withdrawal contract code, only change I really want to see on PR though is refactoring the github ci test logic. I want to be able to isolate the tests using all the foundry stuff in case it fails on us again.
There was a problem hiding this comment.
is this a generated file? Should it be excluded from the repo in .gitignore? Or added to the generated files list?
There was a problem hiding this comment.
this is generated by foundry - however we need it in the astria_withdrawer.rs module to do abigen for the contract, so i committed it
| @@ -0,0 +1,3 @@ | |||
| [submodule "crates/astria-bridge-withdrawer/ethereum/lib/forge-std"] | |||
There was a problem hiding this comment.
should we have the contracts outside of crates in the monorepo perhaps?
| @@ -0,0 +1,20 @@ | |||
| # anvil account 0 | |||
| PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | |||
There was a problem hiding this comment.
How are these env vars used with contract deployment? Is this foundry env var? Generally we want to put private keys in files.
There was a problem hiding this comment.
I agree. We should never leak private keys in plain text.
There was a problem hiding this comment.
this is anvil account 0 so it's a known private key. i can remove tho
| # The socket address at which the bridge service will server healthz, readyz, and status calls. | ||
| ASTRIA_BRIDGE_WITHDRAWER_API_ADDR=127.0.0.1:2450 | ||
|
|
||
| # Set to true to enable prometheus metrics. |
There was a problem hiding this comment.
| # Set to true to enable prometheus metrics. | |
| # Set to true to disable prometheus metrics. |
| use ethers::types::{ | ||
| TxHash, | ||
| U64, | ||
| }; |
There was a problem hiding this comment.
I think it would be a bit more clear further down if these types weren't "used" and instead fully specify the type path below. I was confused about why u64 was capitalized further down and it's good to have clarity on what type of txHash we are talking about (it's an EVM tx hash not a cometbft one)
There was a problem hiding this comment.
moved to the ethereum crate as this logic is eth-specific
| /// It also contains a `return_address` field which may or may not be the same as the signer | ||
| /// of the packet. The funds will be returned to the `return_address` in the case of a timeout. | ||
| #[derive(Debug, Clone)] | ||
| #[derive(Debug, Clone, PartialEq, Eq)] |
There was a problem hiding this comment.
can we make a follow up issue here? seems a general code quality thing
.github/workflows/test.yml
Outdated
| solc-select install 0.8.21 | ||
| solc-select use 0.8.21 | ||
| cargo nextest run --archive-file=archive.tar.zst -- --include-ignored | ||
| cargo nextest run --package astria-bridge-withdrawer -- --include-ignored |
There was a problem hiding this comment.
| cargo nextest run --package astria-bridge-withdrawer -- --include-ignored | |
| cargo nextest run --package astria-bridge-withdrawer -- --include-ignored |
| .metrics_addr(&cfg.metrics_http_listener_addr) | ||
| .service_name(env!("CARGO_PKG_NAME")); | ||
| } | ||
| metrics_init::register(); |
There was a problem hiding this comment.
Please register metrics the same way as all other services
There was a problem hiding this comment.
Where is this file used?
There was a problem hiding this comment.
this is used to deploy the ethereum contract and make calls to it - see the readme in this crate
| @@ -0,0 +1,20 @@ | |||
| # anvil account 0 | |||
| PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 | |||
There was a problem hiding this comment.
I agree. We should never leak private keys in plain text.
| # Address of cometbft/tendermint to request new block heights. | ||
| # 127.0.0.1:26657 is the default socket address at which cometbft | ||
| # serves RPCs. | ||
| ASTRIA_BRIDGE_WITHDRAWER_COMETBFT_ENDPOINT="http://127.0.0.1:26657" |
There was a problem hiding this comment.
Let's be clearer about this. This reads like it's the withdrawer's endpoint.
| ASTRIA_BRIDGE_WITHDRAWER_COMETBFT_ENDPOINT="http://127.0.0.1:26657" | |
| ASTRIA_BRIDGE_WITHDRAWER_SEQUENCER_COMETBFT_ENDPOINT="http://127.0.0.1:26657" |
| mod state; | ||
| mod submitter; | ||
|
|
||
| pub struct Service { |
There was a problem hiding this comment.
Call this WithdrawerBridge following all our other services.
There was a problem hiding this comment.
that cause a clippy name repetitions error since this module is already called withdrawer and this isn't a bridge, so i'd prefer not to name it that
| if let Ok((event, meta)) = item { | ||
| event_tx | ||
| .send((WithdrawalEvent::Ics20(event), meta)) | ||
| .await | ||
| .wrap_err("failed to send ics20 withdrawal event; receiver dropped?")?; | ||
| } else if let Err(e) = item { | ||
| return Err(e).wrap_err("failed to read from event stream; event stream closed?"); | ||
| } |
There was a problem hiding this comment.
As above, please simplify this.
| info!("batcher shutting down"); | ||
| break; | ||
| } | ||
| item = self.event_rx.recv() => { |
There was a problem hiding this comment.
This select! statement should break if self.event_rx.recv() == None because that means the channel is closed.
| let bytes = hex::decode(s).wrap_err("failed to parse ethereum address as hex")?; | ||
| let address: [u8; 20] = bytes | ||
| .try_into() | ||
| .map_err(|_| eyre!("invalid length for ethereum address, must be 20 bytes"))?; |
There was a problem hiding this comment.
report the actual number of bytes received
| /// It also contains a `return_address` field which may or may not be the same as the signer | ||
| /// of the packet. The funds will be returned to the `return_address` in the case of a timeout. | ||
| #[derive(Debug, Clone)] | ||
| #[derive(Debug, Clone, PartialEq, Eq)] |
There was a problem hiding this comment.
Gonna let it pass here because the other actions also have their fields exposed. But yeah, in general I prefer to not leak that stuff. We should address this in a follow, prior to publishing this crate.
| } | ||
|
|
||
| /// Queries the sequencer for the latest nonce with an exponential backoff | ||
| #[instrument(name = "get_latest_nonce", skip_all, fields(%address))] |
There was a problem hiding this comment.
name matches the function name. You can remove that attribute.
|
I would like a simple blackbox test here that shows that we can spin up the service, receive an event, and see it come out on the other side. |
|
@SuperFluffy comments are addressed, blackbox test will be done in a PR shortly |
joroshiba
left a comment
There was a problem hiding this comment.
Re-approving noting this is blocking further development and blackbox tests are actively being built, we have a tight timeline can't delay on the tests.
blackbox tests non-blocking for greenfield work, in progress but this PR is blocking other work
Summary
implement
ethereumandsubmittermodules for astria-bridge-withdrawer.Background
required to implement rollup bridge withdrawals.
Changes
ethereum:
AstriaWithdrawer.solcontract, put insideethereum/dir as a foundry projectethereummodule which contains aWatcherthat watches the contract forWithdrawaleventsBatcherwhich batches all events by block number and sends a batch of actions to a sequencer handlersequencer:
Testing
unit tests
manual testing:
just run+just run-cometbftin astria-sequencer)anvilin a terminal)export SEQUENCER_PRIVATE_KEY=2bd806c97f0e00af1a1fc3328fa763a9269723c8db8fac4f93af71db186d6e90 ./target/debug/astria-cli sequencer init-bridge-account --sequencer-url=http://localhost:26657 --rollup-name=astria --sequencer.chain-id=astriajust copy-envinastria-bridge-withdrawer, put the private key from above into a file, update .env to use thatastria-bridge-withdrawer/ethereum,cp local.env.example .envastria-bridge-withdrawer, updateASTRIA_BRIDGE_WITHDRAWER_ETHEREUM_CONTRACT_ADDRESSto be the contract address deployed in step 6.just runand you should see a withdrawal being submitted:Related Issues
#913
closes #1109