mirror of
https://github.com/serai-dex/serai.git
synced 2025-12-09 04:39:24 +00:00
Finish Ethereum ScannerFeed
This commit is contained in:
@@ -75,8 +75,8 @@ async fn main() {
|
||||
|
||||
bin::main_loop::<SetInitialKey, _, KeyGenParams, _>(
|
||||
db.clone(),
|
||||
Rpc { provider: provider.clone() },
|
||||
Scheduler::new(SmartContract { chain_id }),
|
||||
Rpc { db: db.clone(), provider: provider.clone() },
|
||||
Scheduler::<bin::Db>::new(SmartContract { chain_id }),
|
||||
TransactionPublisher::new(db, provider, {
|
||||
let relayer_hostname = env::var("ETHEREUM_RELAYER_HOSTNAME")
|
||||
.expect("ethereum relayer hostname wasn't specified")
|
||||
|
||||
@@ -20,8 +20,6 @@ pub(crate) struct Epoch {
|
||||
pub(crate) start: u64,
|
||||
// The hash of the last block within this Epoch.
|
||||
pub(crate) end_hash: [u8; 32],
|
||||
// The monotonic time for this Epoch.
|
||||
pub(crate) time: u64,
|
||||
}
|
||||
|
||||
impl Epoch {
|
||||
@@ -42,9 +40,9 @@ impl primitives::BlockHeader for Epoch {
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Debug)]
|
||||
pub(crate) struct FullEpoch {
|
||||
epoch: Epoch,
|
||||
instructions: Vec<EthereumInInstruction>,
|
||||
executed: Vec<Executed>,
|
||||
pub(crate) epoch: Epoch,
|
||||
pub(crate) instructions: Vec<EthereumInInstruction>,
|
||||
pub(crate) executed: Vec<Executed>,
|
||||
}
|
||||
|
||||
impl primitives::Block for FullEpoch {
|
||||
|
||||
@@ -8,3 +8,5 @@ pub(crate) const DAI: [u8; 20] =
|
||||
Ok(res) => res,
|
||||
Err(_) => panic!("invalid non-test DAI hex address"),
|
||||
};
|
||||
|
||||
pub(crate) const TOKENS: [[u8; 20]; 1] = [DAI];
|
||||
|
||||
@@ -50,8 +50,12 @@ impl<D: Db> TransactionPublisher<D> {
|
||||
if router.is_none() {
|
||||
let Some(router_actual) = Router::new(
|
||||
self.rpc.clone(),
|
||||
&PublicKey::new(InitialSeraiKey::get(&self.db).unwrap().0)
|
||||
.expect("initial key used by Serai wasn't representable on Ethereum"),
|
||||
&PublicKey::new(
|
||||
InitialSeraiKey::get(&self.db)
|
||||
.expect("publishing a transaction yet never confirmed a key")
|
||||
.0,
|
||||
)
|
||||
.expect("initial key used by Serai wasn't representable on Ethereum"),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use core::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::{sync::Arc, collections::HashSet};
|
||||
|
||||
use alloy_core::primitives::B256;
|
||||
use alloy_rpc_types_eth::{BlockTransactionsKind, BlockNumberOrTag};
|
||||
use alloy_transport::{RpcError, TransportErrorKind};
|
||||
use alloy_simple_request_transport::SimpleRequest;
|
||||
@@ -8,16 +9,26 @@ use alloy_provider::{Provider, RootProvider};
|
||||
|
||||
use serai_client::primitives::{NetworkId, Coin, Amount};
|
||||
|
||||
use serai_db::Db;
|
||||
|
||||
use scanner::ScannerFeed;
|
||||
|
||||
use crate::block::{Epoch, FullEpoch};
|
||||
use ethereum_schnorr::PublicKey;
|
||||
use ethereum_erc20::{TopLevelTransfer, Erc20};
|
||||
use ethereum_router::{Coin as EthereumCoin, InInstruction as EthereumInInstruction, Router};
|
||||
|
||||
use crate::{
|
||||
TOKENS, InitialSeraiKey,
|
||||
block::{Epoch, FullEpoch},
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Rpc {
|
||||
pub(crate) struct Rpc<D: Db> {
|
||||
pub(crate) db: D,
|
||||
pub(crate) provider: Arc<RootProvider<SimpleRequest>>,
|
||||
}
|
||||
|
||||
impl ScannerFeed for Rpc {
|
||||
impl<D: Db> ScannerFeed for Rpc<D> {
|
||||
const NETWORK: NetworkId = NetworkId::Ethereum;
|
||||
|
||||
// We only need one confirmation as Ethereum properly finalizes
|
||||
@@ -62,7 +73,22 @@ impl ScannerFeed for Rpc {
|
||||
&self,
|
||||
number: u64,
|
||||
) -> impl Send + Future<Output = Result<u64, Self::EphemeralError>> {
|
||||
async move { todo!("TODO") }
|
||||
async move {
|
||||
let header = self
|
||||
.provider
|
||||
.get_block(BlockNumberOrTag::Number(number).into(), BlockTransactionsKind::Hashes)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
TransportErrorKind::Custom(
|
||||
"asked for time of a block our node doesn't have".to_string().into(),
|
||||
)
|
||||
})?
|
||||
.header;
|
||||
// This is monotonic ever since the merge
|
||||
// https://github.com/ethereum/consensus-specs/blob/4afe39822c9ad9747e0f5635cca117c18441ec1b
|
||||
// /specs/bellatrix/beacon-chain.md?plain=1#L393-L394
|
||||
Ok(header.timestamp)
|
||||
}
|
||||
}
|
||||
|
||||
fn unchecked_block_header_by_number(
|
||||
@@ -104,25 +130,91 @@ impl ScannerFeed for Rpc {
|
||||
.header;
|
||||
|
||||
let end_hash = end_header.hash.into();
|
||||
let time = end_header.timestamp;
|
||||
|
||||
Ok(Epoch { prior_end_hash, start, end_hash, time })
|
||||
Ok(Epoch { prior_end_hash, start, end_hash })
|
||||
}
|
||||
}
|
||||
|
||||
#[rustfmt::skip] // It wants to improperly format the `async move` to a single line
|
||||
fn unchecked_block_by_number(
|
||||
&self,
|
||||
number: u64,
|
||||
) -> impl Send + Future<Output = Result<Self::Block, Self::EphemeralError>> {
|
||||
async move {
|
||||
todo!("TODO")
|
||||
let epoch = self.unchecked_block_header_by_number(number).await?;
|
||||
let mut instructions = vec![];
|
||||
let mut executed = vec![];
|
||||
|
||||
let Some(router) = Router::new(
|
||||
self.provider.clone(),
|
||||
&PublicKey::new(
|
||||
InitialSeraiKey::get(&self.db).expect("fetching a block yet never confirmed a key").0,
|
||||
)
|
||||
.expect("initial key used by Serai wasn't representable on Ethereum"),
|
||||
)
|
||||
.await?
|
||||
else {
|
||||
// The Router wasn't deployed yet so we cannot have any on-chain interactions
|
||||
// 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
|
||||
// TODO: This does as we can have top-level transfers to the router before it's deployed
|
||||
return Ok(FullEpoch { epoch, instructions, executed });
|
||||
};
|
||||
|
||||
let mut to_check = epoch.end_hash;
|
||||
while to_check != epoch.prior_end_hash {
|
||||
let to_check_block = self
|
||||
.provider
|
||||
.get_block(B256::from(to_check).into(), BlockTransactionsKind::Hashes)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
TransportErrorKind::Custom(
|
||||
format!(
|
||||
"ethereum node didn't have requested block: {}. was the node reset?",
|
||||
hex::encode(to_check)
|
||||
)
|
||||
.into(),
|
||||
)
|
||||
})?
|
||||
.header;
|
||||
|
||||
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: (id, u64::MAX),
|
||||
from,
|
||||
coin: EthereumCoin::Erc20(token),
|
||||
amount,
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
executed.append(&mut router.executed(to_check_block.number).await?);
|
||||
|
||||
to_check = *to_check_block.parent_hash;
|
||||
}
|
||||
|
||||
Ok(FullEpoch { epoch, instructions, executed })
|
||||
}
|
||||
}
|
||||
|
||||
fn dust(coin: Coin) -> Amount {
|
||||
assert_eq!(coin.network(), NetworkId::Ethereum);
|
||||
todo!("TODO")
|
||||
#[allow(clippy::inconsistent_digit_grouping)]
|
||||
match coin {
|
||||
// 5 USD if Ether is ~3300 USD
|
||||
Coin::Ether => Amount(1_500_00),
|
||||
// 5 DAI
|
||||
Coin::Dai => Amount(5_000_000_00),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn cost_to_aggregate(
|
||||
@@ -132,7 +224,7 @@ impl ScannerFeed for Rpc {
|
||||
) -> impl Send + Future<Output = Result<Amount, Self::EphemeralError>> {
|
||||
async move {
|
||||
assert_eq!(coin.network(), NetworkId::Ethereum);
|
||||
// TODO
|
||||
// There is no cost to aggregate as we receive to an account
|
||||
Ok(Amount(0))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@ use alloy_core::primitives::U256;
|
||||
|
||||
use serai_client::primitives::{NetworkId, Coin, Balance};
|
||||
|
||||
use serai_db::Db;
|
||||
|
||||
use primitives::Payment;
|
||||
use scanner::{KeyFor, AddressFor, EventualityFor};
|
||||
|
||||
@@ -32,15 +34,15 @@ fn balance_to_ethereum_amount(balance: Balance) -> U256 {
|
||||
pub(crate) struct SmartContract {
|
||||
pub(crate) chain_id: U256,
|
||||
}
|
||||
impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
||||
impl<D: Db> smart_contract_scheduler::SmartContract<Rpc<D>> for SmartContract {
|
||||
type SignableTransaction = Action;
|
||||
|
||||
fn rotate(
|
||||
&self,
|
||||
nonce: u64,
|
||||
retiring_key: KeyFor<Rpc>,
|
||||
new_key: KeyFor<Rpc>,
|
||||
) -> (Self::SignableTransaction, EventualityFor<Rpc>) {
|
||||
retiring_key: KeyFor<Rpc<D>>,
|
||||
new_key: KeyFor<Rpc<D>>,
|
||||
) -> (Self::SignableTransaction, EventualityFor<Rpc<D>>) {
|
||||
let action = Action::SetKey {
|
||||
chain_id: self.chain_id,
|
||||
nonce,
|
||||
@@ -52,9 +54,9 @@ impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
||||
fn fulfill(
|
||||
&self,
|
||||
nonce: u64,
|
||||
key: KeyFor<Rpc>,
|
||||
payments: Vec<Payment<AddressFor<Rpc>>>,
|
||||
) -> Vec<(Self::SignableTransaction, EventualityFor<Rpc>)> {
|
||||
key: KeyFor<Rpc<D>>,
|
||||
payments: Vec<Payment<AddressFor<Rpc<D>>>>,
|
||||
) -> Vec<(Self::SignableTransaction, EventualityFor<Rpc<D>>)> {
|
||||
let mut outs = Vec::with_capacity(payments.len());
|
||||
for payment in payments {
|
||||
outs.push((
|
||||
@@ -75,4 +77,4 @@ impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type Scheduler = smart_contract_scheduler::Scheduler<Rpc, SmartContract>;
|
||||
pub(crate) type Scheduler<D> = smart_contract_scheduler::Scheduler<Rpc<D>, SmartContract>;
|
||||
|
||||
Reference in New Issue
Block a user