mirror of
https://github.com/serai-dex/serai.git
synced 2025-01-12 05:44:53 +00:00
Don't track deployment block in the Router
This technically has a TOCTOU where we sync an Epoch's metadata (signifying we did sync to that point), then check if the Router was deployed, yet at that very moment the node resets to genesis. By ensuring the Router is deployed, we avoid this (and don't need to track the deployment block in-contract). Also uses a JoinSet to sync the 32 blocks in parallel.
This commit is contained in:
parent
7e4c59a0a3
commit
554c5778e4
3 changed files with 50 additions and 64 deletions
|
@ -7,9 +7,6 @@ import "Schnorr.sol";
|
||||||
|
|
||||||
// _ is used as a prefix for internal functions and smart-contract-scoped variables
|
// _ is used as a prefix for internal functions and smart-contract-scoped variables
|
||||||
contract Router {
|
contract Router {
|
||||||
// The block at which this contract was deployed.
|
|
||||||
uint256 private _deploymentBlock;
|
|
||||||
|
|
||||||
// Nonce is incremented for each command executed, preventing replays
|
// Nonce is incremented for each command executed, preventing replays
|
||||||
uint256 private _nonce;
|
uint256 private _nonce;
|
||||||
|
|
||||||
|
@ -66,8 +63,6 @@ contract Router {
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(bytes32 initialSeraiKey) _updateSeraiKeyAtEndOfFn(0, initialSeraiKey) {
|
constructor(bytes32 initialSeraiKey) _updateSeraiKeyAtEndOfFn(0, initialSeraiKey) {
|
||||||
_deploymentBlock = block.number;
|
|
||||||
|
|
||||||
// We consumed nonce 0 when setting the initial Serai key
|
// We consumed nonce 0 when setting the initial Serai key
|
||||||
_nonce = 1;
|
_nonce = 1;
|
||||||
// Nonces are incremented by 1 upon account creation, prior to any code execution, per EIP-161
|
// Nonces are incremented by 1 upon account creation, prior to any code execution, per EIP-161
|
||||||
|
@ -235,10 +230,6 @@ contract Router {
|
||||||
return _nonce;
|
return _nonce;
|
||||||
}
|
}
|
||||||
|
|
||||||
function deploymentBlock() external view returns (uint256) {
|
|
||||||
return _deploymentBlock;
|
|
||||||
}
|
|
||||||
|
|
||||||
function smartContractNonce() external view returns (uint256) {
|
function smartContractNonce() external view returns (uint256) {
|
||||||
return _smartContractNonce;
|
return _smartContractNonce;
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ use alloy_consensus::TxLegacy;
|
||||||
|
|
||||||
use alloy_sol_types::{SolValue, SolConstructor, SolCall, SolEvent};
|
use alloy_sol_types::{SolValue, SolConstructor, SolCall, SolEvent};
|
||||||
|
|
||||||
use alloy_rpc_types_eth::{TransactionInput, TransactionRequest, Filter};
|
use alloy_rpc_types_eth::Filter;
|
||||||
use alloy_transport::{TransportErrorKind, RpcError};
|
use alloy_transport::{TransportErrorKind, RpcError};
|
||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_provider::{Provider, RootProvider};
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
@ -296,23 +296,6 @@ impl Router {
|
||||||
self.1
|
self.1
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fetch the block this contract was deployed at.
|
|
||||||
pub async fn deployment_block(&self) -> Result<u64, RpcError<TransportErrorKind>> {
|
|
||||||
let call = TransactionRequest::default()
|
|
||||||
.to(self.address())
|
|
||||||
.input(TransactionInput::new(abi::deploymentBlockCall::new(()).abi_encode().into()));
|
|
||||||
let bytes = self.0.call(&call).await?;
|
|
||||||
let deployment_block = abi::deploymentBlockCall::abi_decode_returns(&bytes, true)
|
|
||||||
.map_err(|e| {
|
|
||||||
TransportErrorKind::Custom(
|
|
||||||
format!("node returned a non-u256 for function returning u256: {e:?}").into(),
|
|
||||||
)
|
|
||||||
})?
|
|
||||||
._0;
|
|
||||||
|
|
||||||
Ok(deployment_block.try_into().unwrap())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the message to be signed in order to update the key for Serai.
|
/// Get the message to be signed in order to update the key for Serai.
|
||||||
pub fn update_serai_key_message(chain_id: U256, nonce: u64, key: &PublicKey) -> Vec<u8> {
|
pub fn update_serai_key_message(chain_id: U256, nonce: u64, key: &PublicKey) -> Vec<u8> {
|
||||||
(
|
(
|
||||||
|
|
|
@ -2,20 +2,23 @@ use core::future::Future;
|
||||||
use std::{sync::Arc, collections::HashSet};
|
use std::{sync::Arc, collections::HashSet};
|
||||||
|
|
||||||
use alloy_core::primitives::B256;
|
use alloy_core::primitives::B256;
|
||||||
use alloy_rpc_types_eth::{BlockTransactionsKind, BlockNumberOrTag};
|
use alloy_rpc_types_eth::{Header, BlockTransactionsKind, BlockNumberOrTag};
|
||||||
use alloy_transport::{RpcError, TransportErrorKind};
|
use alloy_transport::{RpcError, TransportErrorKind};
|
||||||
use alloy_simple_request_transport::SimpleRequest;
|
use alloy_simple_request_transport::SimpleRequest;
|
||||||
use alloy_provider::{Provider, RootProvider};
|
use alloy_provider::{Provider, RootProvider};
|
||||||
|
|
||||||
use serai_client::primitives::{NetworkId, Coin, Amount};
|
use serai_client::primitives::{NetworkId, Coin, Amount};
|
||||||
|
|
||||||
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
use serai_db::Db;
|
use serai_db::Db;
|
||||||
|
|
||||||
use scanner::ScannerFeed;
|
use scanner::ScannerFeed;
|
||||||
|
|
||||||
use ethereum_schnorr::PublicKey;
|
use ethereum_schnorr::PublicKey;
|
||||||
use ethereum_erc20::{TopLevelTransfer, Erc20};
|
use ethereum_erc20::{TopLevelTransfer, Erc20};
|
||||||
use ethereum_router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction, Router};
|
#[rustfmt::skip]
|
||||||
|
use ethereum_router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction, Executed, Router};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
TOKENS, ETHER_DUST, DAI_DUST, InitialSeraiKey,
|
TOKENS, ETHER_DUST, DAI_DUST, InitialSeraiKey,
|
||||||
|
@ -141,8 +144,6 @@ impl<D: Db> ScannerFeed for Rpc<D> {
|
||||||
) -> impl Send + Future<Output = Result<Self::Block, Self::EphemeralError>> {
|
) -> impl Send + Future<Output = Result<Self::Block, Self::EphemeralError>> {
|
||||||
async move {
|
async move {
|
||||||
let epoch = self.unchecked_block_header_by_number(number).await?;
|
let epoch = self.unchecked_block_header_by_number(number).await?;
|
||||||
let mut instructions = vec![];
|
|
||||||
let mut executed = vec![];
|
|
||||||
|
|
||||||
let Some(router) = Router::new(
|
let Some(router) = Router::new(
|
||||||
self.provider.clone(),
|
self.provider.clone(),
|
||||||
|
@ -153,16 +154,42 @@ impl<D: Db> ScannerFeed for Rpc<D> {
|
||||||
)
|
)
|
||||||
.await?
|
.await?
|
||||||
else {
|
else {
|
||||||
// The Router wasn't deployed yet so we cannot have any on-chain interactions
|
Err(TransportErrorKind::Custom("router wasn't deployed on-chain yet".to_string().into()))?
|
||||||
// If the Router has been deployed by the block we've synced to, it won't have any events
|
|
||||||
// for these blocks anways, so this doesn't risk a consensus split
|
|
||||||
return Ok(FullEpoch { epoch, instructions, executed });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let router_deployment_block = router.deployment_block().await?;
|
async fn sync_block(
|
||||||
|
provider: Arc<RootProvider<SimpleRequest>>,
|
||||||
|
router: Router,
|
||||||
|
block: Header,
|
||||||
|
) -> Result<(Vec<EthereumInInstruction>, Vec<Executed>), RpcError<TransportErrorKind>> {
|
||||||
|
let mut instructions = router.in_instructions(block.number, &HashSet::from(TOKENS)).await?;
|
||||||
|
|
||||||
// TODO: Use a LocalSet and handle all these in parallel
|
for token in TOKENS {
|
||||||
|
for TopLevelTransfer { id, from, amount, data } in Erc20::new(provider.clone(), token)
|
||||||
|
.top_level_transfers(block.number, router.address())
|
||||||
|
.await?
|
||||||
|
{
|
||||||
|
instructions.push(EthereumInInstruction {
|
||||||
|
id,
|
||||||
|
from,
|
||||||
|
coin: EthereumCoin::Erc20(token),
|
||||||
|
amount,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let executed = router.executed(block.number).await?;
|
||||||
|
|
||||||
|
Ok((instructions, executed))
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use JoinSet here to minimize the latency of the variety of requests we make. For each
|
||||||
|
// JoinError that may occur, we unwrap it as no underlying tasks should panic
|
||||||
|
let mut join_set = JoinSet::new();
|
||||||
let mut to_check = epoch.end_hash;
|
let mut to_check = epoch.end_hash;
|
||||||
|
// TODO: This makes 32 sequential requests. We should run them in parallel using block
|
||||||
|
// nunbers
|
||||||
while to_check != epoch.prior_end_hash {
|
while to_check != epoch.prior_end_hash {
|
||||||
let to_check_block = self
|
let to_check_block = self
|
||||||
.provider
|
.provider
|
||||||
|
@ -179,34 +206,19 @@ impl<D: Db> ScannerFeed for Rpc<D> {
|
||||||
})?
|
})?
|
||||||
.header;
|
.header;
|
||||||
|
|
||||||
// If this is before the Router was deployed, move on
|
// Update the next block to check
|
||||||
if to_check_block.number < router_deployment_block {
|
|
||||||
// This is sa
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
instructions.append(
|
|
||||||
&mut router.in_instructions(to_check_block.number, &HashSet::from(TOKENS)).await?,
|
|
||||||
);
|
|
||||||
for token in TOKENS {
|
|
||||||
for TopLevelTransfer { id, from, amount, data } in
|
|
||||||
Erc20::new(self.provider.clone(), token)
|
|
||||||
.top_level_transfers(to_check_block.number, router.address())
|
|
||||||
.await?
|
|
||||||
{
|
|
||||||
instructions.push(EthereumInInstruction {
|
|
||||||
id,
|
|
||||||
from,
|
|
||||||
coin: EthereumCoin::Erc20(token),
|
|
||||||
amount,
|
|
||||||
data,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executed.append(&mut router.executed(to_check_block.number).await?);
|
|
||||||
|
|
||||||
to_check = *to_check_block.parent_hash;
|
to_check = *to_check_block.parent_hash;
|
||||||
|
|
||||||
|
// Spawn a task to sync this block
|
||||||
|
join_set.spawn(sync_block(self.provider.clone(), router.clone(), to_check_block));
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut instructions = vec![];
|
||||||
|
let mut executed = vec![];
|
||||||
|
while let Some(instructions_and_executed) = join_set.join_next().await {
|
||||||
|
let (mut these_instructions, mut these_executed) = instructions_and_executed.unwrap()?;
|
||||||
|
instructions.append(&mut these_instructions);
|
||||||
|
executed.append(&mut these_executed);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(FullEpoch { epoch, instructions, executed })
|
Ok(FullEpoch { epoch, instructions, executed })
|
||||||
|
|
Loading…
Reference in a new issue