mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 12:49:23 +00:00
Work on testing the Router
Completes the `Executed` enum in the router. Adds an `Escape` struct. Both are needed for testing purposes. Documents the gas constants in intent and reasoning. Adds modernized tests around key rotation and the escape hatch. Also updates the rest of the codebase which had accumulated errors.
This commit is contained in:
@@ -6,11 +6,13 @@
|
||||
static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> =
|
||||
zalloc::ZeroizingAlloc(std::alloc::System);
|
||||
|
||||
use core::time::Duration;
|
||||
use std::sync::Arc;
|
||||
|
||||
use alloy_core::primitives::U256;
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
use alloy_rpc_client::ClientBuilder;
|
||||
use alloy_provider::RootProvider;
|
||||
use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
use serai_client::validator_sets::primitives::Session;
|
||||
|
||||
@@ -62,10 +64,26 @@ async fn main() {
|
||||
ClientBuilder::default().transport(SimpleRequest::new(bin::url()), true),
|
||||
));
|
||||
|
||||
let chain_id = {
|
||||
let mut delay = Duration::from_secs(5);
|
||||
loop {
|
||||
match provider.get_chain_id().await {
|
||||
Ok(chain_id) => break chain_id,
|
||||
Err(e) => {
|
||||
log::error!("failed to fetch the chain ID on boot: {e:?}");
|
||||
tokio::time::sleep(delay).await;
|
||||
delay = (delay + Duration::from_secs(5)).max(Duration::from_secs(120));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
bin::main_loop::<SetInitialKey, _, KeyGenParams, _>(
|
||||
db.clone(),
|
||||
Rpc { db: db.clone(), provider: provider.clone() },
|
||||
Scheduler::<bin::Db>::new(SmartContract),
|
||||
Scheduler::<bin::Db>::new(SmartContract {
|
||||
chain_id: U256::from_le_slice(&chain_id.to_le_bytes()),
|
||||
}),
|
||||
TransactionPublisher::new(db, provider, {
|
||||
let relayer_hostname = env::var("ETHEREUM_RELAYER_HOSTNAME")
|
||||
.expect("ethereum relayer hostname wasn't specified")
|
||||
|
||||
@@ -99,6 +99,7 @@ impl primitives::Block for FullEpoch {
|
||||
let Some(expected) =
|
||||
eventualities.active_eventualities.remove(executed.nonce().to_le_bytes().as_slice())
|
||||
else {
|
||||
// TODO: Why is this a continue, not an assert?
|
||||
continue;
|
||||
};
|
||||
assert_eq!(
|
||||
|
||||
@@ -81,8 +81,8 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
|
||||
match self {
|
||||
Output::Output { key: _, instruction } => {
|
||||
let mut id = [0; 40];
|
||||
id[.. 32].copy_from_slice(&instruction.id.0);
|
||||
id[32 ..].copy_from_slice(&instruction.id.1.to_le_bytes());
|
||||
id[.. 32].copy_from_slice(&instruction.id.block_hash);
|
||||
id[32 ..].copy_from_slice(&instruction.id.index_within_block.to_le_bytes());
|
||||
OutputId(id)
|
||||
}
|
||||
// Yet upon Eventuality completions, we report a Change output to ensure synchrony per the
|
||||
@@ -97,7 +97,7 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
|
||||
|
||||
fn transaction_id(&self) -> Self::TransactionId {
|
||||
match self {
|
||||
Output::Output { key: _, instruction } => instruction.id.0,
|
||||
Output::Output { key: _, instruction } => instruction.transaction_hash,
|
||||
Output::Eventuality { key: _, nonce } => {
|
||||
let mut id = [0; 32];
|
||||
id[.. 8].copy_from_slice(&nonce.to_le_bytes());
|
||||
@@ -114,7 +114,7 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
|
||||
|
||||
fn presumed_origin(&self) -> Option<Address> {
|
||||
match self {
|
||||
Output::Output { key: _, instruction } => Some(Address::from(instruction.from)),
|
||||
Output::Output { key: _, instruction } => Some(Address::Address(*instruction.from.0)),
|
||||
Output::Eventuality { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,8 @@ use crate::{output::OutputId, machine::ClonableTransctionMachine};
|
||||
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
pub(crate) enum Action {
|
||||
SetKey { nonce: u64, key: PublicKey },
|
||||
Batch { nonce: u64, coin: Coin, fee: U256, outs: Vec<(Address, U256)> },
|
||||
SetKey { chain_id: U256, nonce: u64, key: PublicKey },
|
||||
Batch { chain_id: U256, nonce: u64, coin: Coin, fee: U256, outs: Vec<(Address, U256)> },
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
@@ -33,17 +33,25 @@ impl Action {
|
||||
|
||||
pub(crate) fn message(&self) -> Vec<u8> {
|
||||
match self {
|
||||
Action::SetKey { nonce, key } => Router::update_serai_key_message(*nonce, key),
|
||||
Action::Batch { nonce, coin, fee, outs } => {
|
||||
Router::execute_message(*nonce, *coin, *fee, OutInstructions::from(outs.as_ref()))
|
||||
Action::SetKey { chain_id, nonce, key } => {
|
||||
Router::update_serai_key_message(*chain_id, *nonce, key)
|
||||
}
|
||||
Action::Batch { chain_id, nonce, coin, fee, outs } => Router::execute_message(
|
||||
*chain_id,
|
||||
*nonce,
|
||||
*coin,
|
||||
*fee,
|
||||
OutInstructions::from(outs.as_ref()),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn eventuality(&self) -> Eventuality {
|
||||
Eventuality(match self {
|
||||
Self::SetKey { nonce, key } => Executed::SetKey { nonce: *nonce, key: key.eth_repr() },
|
||||
Self::Batch { nonce, .. } => {
|
||||
Self::SetKey { chain_id: _, nonce, key } => {
|
||||
Executed::NextSeraiKeySet { nonce: *nonce, key: key.eth_repr() }
|
||||
}
|
||||
Self::Batch { chain_id: _, nonce, .. } => {
|
||||
Executed::Batch { nonce: *nonce, message_hash: keccak256(self.message()) }
|
||||
}
|
||||
})
|
||||
@@ -77,6 +85,10 @@ impl SignableTransaction for Action {
|
||||
Err(io::Error::other("unrecognized Action type"))?;
|
||||
}
|
||||
|
||||
let mut chain_id = [0; 32];
|
||||
reader.read_exact(&mut chain_id)?;
|
||||
let chain_id = U256::from_be_bytes(chain_id);
|
||||
|
||||
let mut nonce = [0; 8];
|
||||
reader.read_exact(&mut nonce)?;
|
||||
let nonce = u64::from_le_bytes(nonce);
|
||||
@@ -88,10 +100,10 @@ impl SignableTransaction for Action {
|
||||
let key =
|
||||
PublicKey::from_eth_repr(key).ok_or_else(|| io::Error::other("invalid key in Action"))?;
|
||||
|
||||
Action::SetKey { nonce, key }
|
||||
Action::SetKey { chain_id, nonce, key }
|
||||
}
|
||||
1 => {
|
||||
let coin = Coin::read(reader)?;
|
||||
let coin = borsh::from_reader(reader)?;
|
||||
|
||||
let mut fee = [0; 32];
|
||||
reader.read_exact(&mut fee)?;
|
||||
@@ -111,22 +123,24 @@ impl SignableTransaction for Action {
|
||||
|
||||
outs.push((address, amount));
|
||||
}
|
||||
Action::Batch { nonce, coin, fee, outs }
|
||||
Action::Batch { chain_id, nonce, coin, fee, outs }
|
||||
}
|
||||
_ => unreachable!(),
|
||||
})
|
||||
}
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
match self {
|
||||
Self::SetKey { nonce, key } => {
|
||||
Self::SetKey { chain_id, nonce, key } => {
|
||||
writer.write_all(&[0])?;
|
||||
writer.write_all(&chain_id.to_be_bytes::<32>())?;
|
||||
writer.write_all(&nonce.to_le_bytes())?;
|
||||
writer.write_all(&key.eth_repr())
|
||||
}
|
||||
Self::Batch { nonce, coin, fee, outs } => {
|
||||
Self::Batch { chain_id, nonce, coin, fee, outs } => {
|
||||
writer.write_all(&[1])?;
|
||||
writer.write_all(&chain_id.to_be_bytes::<32>())?;
|
||||
writer.write_all(&nonce.to_le_bytes())?;
|
||||
coin.write(writer)?;
|
||||
borsh::BorshSerialize::serialize(coin, writer)?;
|
||||
writer.write_all(&fee.as_le_bytes())?;
|
||||
writer.write_all(&u32::try_from(outs.len()).unwrap().to_le_bytes())?;
|
||||
for (address, amount) in outs {
|
||||
@@ -167,9 +181,9 @@ impl primitives::Eventuality for Eventuality {
|
||||
}
|
||||
|
||||
fn read(reader: &mut impl io::Read) -> io::Result<Self> {
|
||||
Executed::read(reader).map(Self)
|
||||
Ok(Self(borsh::from_reader(reader)?))
|
||||
}
|
||||
fn write(&self, writer: &mut impl io::Write) -> io::Result<()> {
|
||||
self.0.write(writer)
|
||||
borsh::BorshSerialize::serialize(&self.0, writer)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ impl<D: Db> signers::TransactionPublisher<Transaction> for TransactionPublisher<
|
||||
let nonce = tx.0.nonce();
|
||||
// Convert from an Action (an internal representation of a signable event) to a TxLegacy
|
||||
let tx = match tx.0 {
|
||||
Action::SetKey { nonce: _, key } => router.update_serai_key(&key, &tx.1),
|
||||
Action::Batch { nonce: _, coin, fee, outs } => {
|
||||
Action::SetKey { chain_id: _, nonce: _, key } => router.update_serai_key(&key, &tx.1),
|
||||
Action::Batch { chain_id: _, nonce: _, coin, fee, outs } => {
|
||||
router.execute(coin, fee, OutInstructions::from(outs.as_ref()), &tx.1)
|
||||
}
|
||||
};
|
||||
|
||||
@@ -165,12 +165,14 @@ impl<D: Db> ScannerFeed for Rpc<D> {
|
||||
let mut instructions = router.in_instructions(block.number, &HashSet::from(TOKENS)).await?;
|
||||
|
||||
for token in TOKENS {
|
||||
for TopLevelTransfer { id, from, amount, data } in Erc20::new(provider.clone(), **token)
|
||||
.top_level_transfers(block.number, router.address())
|
||||
.await?
|
||||
for TopLevelTransfer { id, transaction_hash, from, amount, data } in
|
||||
Erc20::new(provider.clone(), **token)
|
||||
.top_level_transfers(block.number, router.address())
|
||||
.await?
|
||||
{
|
||||
instructions.push(EthereumInInstruction {
|
||||
id,
|
||||
transaction_hash,
|
||||
from,
|
||||
coin: EthereumCoin::Erc20(token),
|
||||
amount,
|
||||
@@ -179,7 +181,7 @@ impl<D: Db> ScannerFeed for Rpc<D> {
|
||||
}
|
||||
}
|
||||
|
||||
let executed = router.executed(block.number).await?;
|
||||
let executed = router.executed(block.number, block.number).await?;
|
||||
|
||||
Ok((instructions, executed))
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ fn balance_to_ethereum_amount(balance: Balance) -> U256 {
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SmartContract;
|
||||
pub(crate) struct SmartContract {
|
||||
pub(crate) chain_id: U256,
|
||||
}
|
||||
impl<D: Db> smart_contract_scheduler::SmartContract<Rpc<D>> for SmartContract {
|
||||
type SignableTransaction = Action;
|
||||
|
||||
@@ -46,8 +48,11 @@ impl<D: Db> smart_contract_scheduler::SmartContract<Rpc<D>> for SmartContract {
|
||||
_retiring_key: KeyFor<Rpc<D>>,
|
||||
new_key: KeyFor<Rpc<D>>,
|
||||
) -> (Self::SignableTransaction, EventualityFor<Rpc<D>>) {
|
||||
let action =
|
||||
Action::SetKey { nonce, key: PublicKey::new(new_key).expect("rotating to an invald key") };
|
||||
let action = Action::SetKey {
|
||||
chain_id: self.chain_id,
|
||||
nonce,
|
||||
key: PublicKey::new(new_key).expect("rotating to an invald key"),
|
||||
};
|
||||
(action.clone(), action.eventuality())
|
||||
}
|
||||
|
||||
@@ -133,6 +138,7 @@ impl<D: Db> smart_contract_scheduler::SmartContract<Rpc<D>> for SmartContract {
|
||||
}
|
||||
|
||||
res.push(Action::Batch {
|
||||
chain_id: self.chain_id,
|
||||
nonce,
|
||||
coin: coin_to_ethereum_coin(coin),
|
||||
fee: U256::try_from(total_gas).unwrap() * fee_per_gas,
|
||||
|
||||
Reference in New Issue
Block a user