Have the TransactionPublisher build a TxLegacy from Transaction

This commit is contained in:
Luke Parker 2024-09-18 15:50:21 -04:00
parent 98c3f75fa2
commit a717ae9ea7
6 changed files with 75 additions and 33 deletions

1
Cargo.lock generated
View file

@ -8334,6 +8334,7 @@ dependencies = [
"alloy-rpc-client", "alloy-rpc-client",
"alloy-rpc-types-eth", "alloy-rpc-types-eth",
"alloy-simple-request-transport", "alloy-simple-request-transport",
"alloy-transport",
"borsh", "borsh",
"ciphersuite", "ciphersuite",
"const-hex", "const-hex",

View file

@ -35,6 +35,7 @@ alloy-rlp = { version = "0.3", default-features = false }
alloy-consensus = { version = "0.3", default-features = false } alloy-consensus = { version = "0.3", default-features = false }
alloy-rpc-types-eth = { version = "0.3", default-features = false } alloy-rpc-types-eth = { version = "0.3", default-features = false }
alloy-transport = { version = "0.3", default-features = false }
alloy-simple-request-transport = { path = "../../networks/ethereum/alloy-simple-request-transport", default-features = false } alloy-simple-request-transport = { path = "../../networks/ethereum/alloy-simple-request-transport", default-features = false }
alloy-rpc-client = { version = "0.3", default-features = false } alloy-rpc-client = { version = "0.3", default-features = false }
alloy-provider = { version = "0.3", default-features = false } alloy-provider = { version = "0.3", default-features = false }

View file

@ -30,14 +30,13 @@ use publisher::TransactionPublisher;
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let db = bin::init(); let db = bin::init();
let feed = {
let provider = Arc::new(RootProvider::new( let provider = Arc::new(RootProvider::new(
ClientBuilder::default().transport(SimpleRequest::new(bin::url()), true), ClientBuilder::default().transport(SimpleRequest::new(bin::url()), true),
)); ));
Rpc { provider }
};
let chain_id = loop { let chain_id = loop {
match feed.provider.get_chain_id().await { match provider.get_chain_id().await {
Ok(chain_id) => break U256::try_from(chain_id).unwrap(), Ok(chain_id) => break U256::try_from(chain_id).unwrap(),
Err(e) => { Err(e) => {
log::error!("couldn't connect to the Ethereum node for the chain ID: {e:?}"); log::error!("couldn't connect to the Ethereum node for the chain ID: {e:?}");
@ -48,9 +47,9 @@ async fn main() {
bin::main_loop::<_, KeyGenParams, _>( bin::main_loop::<_, KeyGenParams, _>(
db, db,
feed.clone(), Rpc { provider: provider.clone() },
Scheduler::new(SmartContract { chain_id }), Scheduler::new(SmartContract { chain_id }),
TransactionPublisher::new({ TransactionPublisher::new(provider, {
let relayer_hostname = env::var("ETHEREUM_RELAYER_HOSTNAME") let relayer_hostname = env::var("ETHEREUM_RELAYER_HOSTNAME")
.expect("ethereum relayer hostname wasn't specified") .expect("ethereum relayer hostname wasn't specified")
.to_string(); .to_string();

View file

@ -1,6 +1,6 @@
use std::io; use std::io;
use ciphersuite::{Ciphersuite, Secp256k1}; use ciphersuite::{group::GroupEncoding, Ciphersuite, Secp256k1};
use alloy_core::primitives::U256; use alloy_core::primitives::U256;
@ -59,7 +59,10 @@ impl AsMut<[u8]> for OutputId {
} }
#[derive(Clone, PartialEq, Eq, Debug)] #[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct Output(pub(crate) EthereumInInstruction); pub(crate) struct Output {
pub(crate) key: <Secp256k1 as Ciphersuite>::G,
pub(crate) instruction: EthereumInInstruction,
}
impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output { impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
type Id = OutputId; type Id = OutputId;
type TransactionId = [u8; 32]; type TransactionId = [u8; 32];
@ -71,40 +74,43 @@ impl ReceivedOutput<<Secp256k1 as Ciphersuite>::G, Address> for Output {
fn id(&self) -> Self::Id { fn id(&self) -> Self::Id {
let mut id = [0; 40]; let mut id = [0; 40];
id[.. 32].copy_from_slice(&self.0.id.0); id[.. 32].copy_from_slice(&self.instruction.id.0);
id[32 ..].copy_from_slice(&self.0.id.1.to_le_bytes()); id[32 ..].copy_from_slice(&self.instruction.id.1.to_le_bytes());
OutputId(id) OutputId(id)
} }
fn transaction_id(&self) -> Self::TransactionId { fn transaction_id(&self) -> Self::TransactionId {
self.0.id.0 self.instruction.id.0
} }
fn key(&self) -> <Secp256k1 as Ciphersuite>::G { fn key(&self) -> <Secp256k1 as Ciphersuite>::G {
todo!("TODO") self.key
} }
fn presumed_origin(&self) -> Option<Address> { fn presumed_origin(&self) -> Option<Address> {
Some(Address::from(self.0.from)) Some(Address::from(self.instruction.from))
} }
fn balance(&self) -> Balance { fn balance(&self) -> Balance {
let coin = coin_to_serai_coin(&self.0.coin).unwrap_or_else(|| { let coin = coin_to_serai_coin(&self.instruction.coin).unwrap_or_else(|| {
panic!( panic!(
"mapping coin from an EthereumInInstruction with coin {}, which we don't handle.", "mapping coin from an EthereumInInstruction with coin {}, which we don't handle.",
"this never should have been yielded" "this never should have been yielded"
) )
}); });
Balance { coin, amount: amount_to_serai_amount(coin, self.0.amount) } Balance { coin, amount: amount_to_serai_amount(coin, self.instruction.amount) }
} }
fn data(&self) -> &[u8] { fn data(&self) -> &[u8] {
&self.0.data &self.instruction.data
} }
fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> { fn write<W: io::Write>(&self, writer: &mut W) -> io::Result<()> {
self.0.write(writer) writer.write_all(self.key.to_bytes().as_ref())?;
self.instruction.write(writer)
} }
fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> { fn read<R: io::Read>(reader: &mut R) -> io::Result<Self> {
EthereumInInstruction::read(reader).map(Self) let key = Secp256k1::read_G(reader)?;
let instruction = EthereumInInstruction::read(reader)?;
Ok(Self { key, instruction })
} }
} }

View file

@ -1,34 +1,68 @@
use core::future::Future; use core::future::Future;
use std::sync::Arc;
use crate::transaction::Transaction; use alloy_transport::{TransportErrorKind, RpcError};
use alloy_simple_request_transport::SimpleRequest;
use alloy_provider::RootProvider;
use tokio::sync::{RwLockReadGuard, RwLock};
use ethereum_schnorr::PublicKey;
use ethereum_router::{OutInstructions, Router};
use crate::transaction::{Action, Transaction};
#[derive(Clone)] #[derive(Clone)]
pub(crate) struct TransactionPublisher { pub(crate) struct TransactionPublisher {
initial_serai_key: PublicKey,
rpc: Arc<RootProvider<SimpleRequest>>,
router: Arc<RwLock<Option<Router>>>,
relayer_url: String, relayer_url: String,
} }
impl TransactionPublisher { impl TransactionPublisher {
pub(crate) fn new(relayer_url: String) -> Self { pub(crate) fn new(rpc: Arc<RootProvider<SimpleRequest>>, relayer_url: String) -> Self {
Self { relayer_url } Self { initial_serai_key: todo!("TODO"), rpc, router: Arc::new(RwLock::new(None)), relayer_url }
}
// This will always return Ok(Some(_)) or Err(_), never Ok(None)
async fn router(&self) -> Result<RwLockReadGuard<'_, Option<Router>>, RpcError<TransportErrorKind>> {
let router = self.router.read().await;
// If the router is None, find it on-chain
if router.is_none() {
drop(router);
let mut router = self.router.write().await;
// Check again if it's None in case a different task already did this
if router.is_none() {
let Some(router_actual) = Router::new(self.rpc.clone(), &self.initial_serai_key).await? else {
Err(TransportErrorKind::Custom("publishing transaction yet couldn't find router on chain. was our node reset?".to_string().into()))?
};
*router = Some(router_actual);
}
return Ok(router.downgrade());
}
Ok(router)
} }
} }
impl signers::TransactionPublisher<Transaction> for TransactionPublisher { impl signers::TransactionPublisher<Transaction> for TransactionPublisher {
type EphemeralError = (); type EphemeralError = RpcError<TransportErrorKind>;
fn publish( fn publish(
&self, &self,
tx: Transaction, tx: Transaction,
) -> impl Send + Future<Output = Result<(), Self::EphemeralError>> { ) -> impl Send + Future<Output = Result<(), Self::EphemeralError>> {
// Convert from an Action (an internal representation of a signable event) to a TxLegacy
/* TODO
match tx.0 {
Action::SetKey { chain_id: _, nonce: _, key } => self.router.update_serai_key(key, tx.1),
Action::Batch { chain_id: _, nonce: _, outs } => self.router.execute(outs, tx.1),
}
*/
async move { async move {
// Convert from an Action (an internal representation of a signable event) to a TxLegacy
let router = self.router().await?;
let router = router.as_ref().unwrap();
let tx = match tx.0 {
Action::SetKey { chain_id: _, nonce: _, key } => router.update_serai_key(&key, &tx.1),
Action::Batch { chain_id: _, nonce: _, outs } => router.execute(OutInstructions::from(outs.as_ref()), &tx.1),
};
/* /*
use tokio::{ use tokio::{
io::{AsyncReadExt, AsyncWriteExt}, io::{AsyncReadExt, AsyncWriteExt},

View file

@ -68,6 +68,7 @@ impl smart_contract_scheduler::SmartContract<Rpc> for SmartContract {
// TODO: Per-batch gas limit // TODO: Per-batch gas limit
// TODO: Create several batches // TODO: Create several batches
// TODO: Handle fees
let action = Action::Batch { chain_id: self.chain_id, nonce, outs }; let action = Action::Batch { chain_id: self.chain_id, nonce, outs };
vec![(action.clone(), action.eventuality())] vec![(action.clone(), action.eventuality())]