Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(l2): add deposit watcher checks #1205

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions crates/l2/contracts/src/l1/CommonBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ contract CommonBridge is ICommonBridge, Ownable, ReentrancyGuard {
ON_CHAIN_PROPOSER = onChainProposer;
}

/// @inheritdoc ICommonBridge
function getDepositLogs() public view returns (bytes32[] memory) {
return depositLogs;
}

/// @inheritdoc ICommonBridge
function deposit(address to) public payable {
require(msg.value > 0, "CommonBridge: amount to deposit is zero");
Expand Down
5 changes: 5 additions & 0 deletions crates/l2/contracts/src/l1/interfaces/ICommonBridge.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ interface ICommonBridge {
uint256 indexed claimedAmount
);

/// @notice Method to retrieve all the deposit logs hashes.
/// @dev This method is used by the L2 L1_Watcher to get the remaining
/// deposit logs to be processed.
function getDepositLogs() external view returns (bytes32[] memory);

/// @notice Initializes the contract.
/// @dev This method is called only once after the contract is deployed.
/// @dev It sets the OnChainProposer address.
Expand Down
72 changes: 58 additions & 14 deletions crates/l2/proposer/l1_watcher.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use ethereum_rust_core::types::{Signable, Transaction};
use ethereum_rust_rpc::types::receipt::RpcLog;
use ethereum_rust_storage::Store;
use ethereum_types::{Address, BigEndianHash, H256, U256};
use keccak_hash::keccak;
use secp256k1::SecretKey;
use std::{cmp::min, ops::Mul, time::Duration};
use tokio::time::sleep;
Expand All @@ -20,11 +21,31 @@ use tracing::{debug, info, warn};
pub async fn start_l1_watcher(store: Store) {
let eth_config = EthConfig::from_env().expect("EthConfig::from_env()");
let watcher_config = L1WatcherConfig::from_env().expect("L1WatcherConfig::from_env()");
let sleep_duration = Duration::from_millis(watcher_config.check_interval_ms);
let mut l1_watcher = L1Watcher::new_from_config(watcher_config, eth_config);
loop {
let logs = l1_watcher.get_logs().await.expect("l1_watcher.get_logs()");
sleep(sleep_duration).await;

let logs = match l1_watcher.get_logs().await {
Ok(logs) => logs,
Err(error) => {
warn!("Error when getting logs from L1: {}", error);
continue;
}
};
if logs.is_empty() {
continue;
}

let pending_deposits_logs = match l1_watcher.get_pending_deposit_logs().await {
Ok(logs) => logs,
Err(error) => {
warn!("Error when getting L1 pending deposit logs: {}", error);
continue;
}
};
let _deposit_txs = l1_watcher
.process_logs(logs, &store)
.process_logs(logs, &pending_deposits_logs, &store)
.await
.expect("l1_watcher.process_logs()");
}
Expand All @@ -34,11 +55,9 @@ pub struct L1Watcher {
eth_client: EthClient,
address: Address,
topics: Vec<H256>,
check_interval: Duration,
max_block_step: U256,
last_block_fetched: U256,
l2_proposer_pk: SecretKey,
l2_proposer_address: Address,
}

impl L1Watcher {
Expand All @@ -47,14 +66,32 @@ impl L1Watcher {
eth_client: EthClient::new_from_config(eth_config),
address: watcher_config.bridge_address,
topics: watcher_config.topics,
check_interval: Duration::from_millis(watcher_config.check_interval_ms),
max_block_step: watcher_config.max_block_step,
last_block_fetched: U256::zero(),
l2_proposer_pk: watcher_config.l2_proposer_private_key,
l2_proposer_address: watcher_config.l2_proposer_address,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are not using this variable. Should we remove the variable from the watcher_config and delete the env variable in the .env.example?

}
}

pub async fn get_pending_deposit_logs(&self) -> Result<Vec<H256>, L1WatcherError> {
Ok(hex::decode(
&self
.eth_client
.call(
self.address,
Bytes::copy_from_slice(&[0x35, 0x6d, 0xa2, 0x49]),
Overrides::default(),
)
.await?[2..],
)
.map_err(|_| L1WatcherError::FailedToDeserializeLog("Not a valid hex string".to_string()))?
.chunks(32)
.map(H256::from_slice)
.collect::<Vec<H256>>()
.split_at(2) // Two first words are index and length abi encode
.1
.to_vec())
}

pub async fn get_logs(&mut self) -> Result<Vec<RpcLog>, L1WatcherError> {
let current_block = self.eth_client.get_block_number().await?;

Expand Down Expand Up @@ -91,25 +128,25 @@ impl L1Watcher {

self.last_block_fetched = new_last_block;

sleep(self.check_interval).await;

Ok(logs)
}

pub async fn process_logs(
&self,
logs: Vec<RpcLog>,
l1_deposit_logs: &[H256],
store: &Store,
) -> Result<Vec<H256>, L1WatcherError> {
if logs.is_empty() {
return Ok(Vec::new());
}

let mut deposit_txs = Vec::new();
let mut operator_nonce = store
.get_account_info(
self.eth_client.get_block_number().await?.as_u64(),
self.l2_proposer_address,
store
.get_latest_block_number()
.map_err(|e| L1WatcherError::FailedToRetrieveChainConfig(e.to_string()))?
.ok_or(L1WatcherError::FailedToRetrieveChainConfig(
"Last block is None".to_string(),
))?,
Address::zero(),
)
.map_err(|e| L1WatcherError::FailedToRetrieveDepositorAccountInfo(e.to_string()))?
.map(|info| info.nonce)
Expand All @@ -131,6 +168,13 @@ impl L1Watcher {
))
})?;

let mut value_bytes = [0u8; 32];
mint_value.to_big_endian(&mut value_bytes);
if !l1_deposit_logs.contains(&keccak([beneficiary.as_bytes(), &value_bytes].concat())) {
warn!("Deposit already processed (to: {beneficiary:#x}, value: {mint_value}), skipping.");
continue;
}

info!("Initiating mint transaction for {beneficiary:#x} with value {mint_value:#x}",);

let mut mint_transaction = self
Expand Down
13 changes: 12 additions & 1 deletion crates/l2/proposer/prover_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,27 @@ pub async fn start_prover_server(store: Store) {
.await
.unwrap();

info!("Sending verify transaction with tx hash: {tx_hash:#x}");

let mut retries = 1;
while eth_client
.get_transaction_receipt(tx_hash)
.await
.unwrap()
.is_none()
{
thread::sleep(Duration::from_secs(1));
retries += 1;
if retries > 10 {
error!("Couldn't find receipt for transaction {tx_hash:#x}");
panic!("Couldn't find receipt for transaction {tx_hash:#x}");
}
}

info!("Mocked verify transaction sent");
info!(
"Mocked verify transaction sent for block {}",
last_verified_block + 1
);
}
} else {
let mut prover_server = ProverServer::new_from_config(
Expand Down
Loading