Files
serai/processor/ethereum/src/primitives/block.rs

139 lines
4.8 KiB
Rust
Raw Normal View History

use std::collections::HashMap;
Smash the singular `Ciphersuite` trait into multiple This helps identify where the various functionalities are used, or rather, not used. The `Ciphersuite` trait present in `patches/ciphersuite`, facilitating the entire FCMP++ tree, only requires the markers _and_ canonical point decoding. I've opened a PR to upstream such a trait into `group` (https://github.com/zkcrypto/group/pull/68). `WrappedGroup` is still justified for as long as `Group::generator` exists. Moving `::generator()` to its own trait, on an independent structure (upstream) would be massively appreciated. @tarcieri also wanted to update from `fn generator()` to `const GENERATOR`, which would encourage further discussion on https://github.com/zkcrypto/group/issues/32 and https://github.com/zkcrypto/group/issues/45, which have been stagnant. The `Id` trait is occasionally used yet really should be first off the chopping block. Finally, `WithPreferredHash` is only actually used around a third of the time, which more than justifies it being a separate trait. --- Updates `dalek_ff_group::Scalar` to directly re-export `curve25519_dalek::Scalar`, as without issue. `dalek_ff_group::RistrettoPoint` also could be replaced with an export of `curve25519_dalek::RistrettoPoint`, yet the coordinator relies on how we implemented `Hash` on it for the hell of it so it isn't worth it at this time. `dalek_ff_group::EdwardsPoint` can't be replaced for an re-export of `curve25519_dalek::SubgroupPoint` as it doesn't implement `zeroize`, `subtle` traits within a released, non-yanked version. Relevance to https://github.com/serai-dex/serai/issues/201 and https://github.com/dalek-cryptography/curve25519-dalek/issues/811#issuecomment-3247732746. Also updates the `Ristretto` ciphersuite to prefer `Blake2b-512` over `SHA2-512`. In order to maintain compliance with FROST's IETF standard, `modular-frost` defines its own ciphersuite for Ristretto which still uses `SHA2-512`.
2025-09-03 12:25:37 -04:00
use ciphersuite::*;
2025-08-25 09:17:29 -04:00
use ciphersuite_kp256::Secp256k1;
use serai_client::networks::ethereum::Address;
use primitives::{ReceivedOutput, EventualityTracker};
use ethereum_router::{InInstruction as EthereumInInstruction, Executed};
use crate::{output::Output, transaction::Eventuality};
// We interpret 32-block Epochs as singular blocks.
// There's no reason for further accuracy when these will all finalize at the same time.
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) struct Epoch {
// The hash of the block which ended the prior Epoch.
pub(crate) prior_end_hash: [u8; 32],
// The hash of the last block within this Epoch.
pub(crate) end_hash: [u8; 32],
}
impl primitives::BlockHeader for Epoch {
fn id(&self) -> [u8; 32] {
self.end_hash
}
fn parent(&self) -> [u8; 32] {
self.prior_end_hash
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub(crate) struct FullEpoch {
2024-09-19 02:41:07 -04:00
pub(crate) epoch: Epoch,
/// The unordered list of `InInstruction`s within this epoch
2024-09-19 02:41:07 -04:00
pub(crate) instructions: Vec<EthereumInInstruction>,
pub(crate) executed: Vec<Executed>,
}
impl primitives::Block for FullEpoch {
type Header = Epoch;
Smash the singular `Ciphersuite` trait into multiple This helps identify where the various functionalities are used, or rather, not used. The `Ciphersuite` trait present in `patches/ciphersuite`, facilitating the entire FCMP++ tree, only requires the markers _and_ canonical point decoding. I've opened a PR to upstream such a trait into `group` (https://github.com/zkcrypto/group/pull/68). `WrappedGroup` is still justified for as long as `Group::generator` exists. Moving `::generator()` to its own trait, on an independent structure (upstream) would be massively appreciated. @tarcieri also wanted to update from `fn generator()` to `const GENERATOR`, which would encourage further discussion on https://github.com/zkcrypto/group/issues/32 and https://github.com/zkcrypto/group/issues/45, which have been stagnant. The `Id` trait is occasionally used yet really should be first off the chopping block. Finally, `WithPreferredHash` is only actually used around a third of the time, which more than justifies it being a separate trait. --- Updates `dalek_ff_group::Scalar` to directly re-export `curve25519_dalek::Scalar`, as without issue. `dalek_ff_group::RistrettoPoint` also could be replaced with an export of `curve25519_dalek::RistrettoPoint`, yet the coordinator relies on how we implemented `Hash` on it for the hell of it so it isn't worth it at this time. `dalek_ff_group::EdwardsPoint` can't be replaced for an re-export of `curve25519_dalek::SubgroupPoint` as it doesn't implement `zeroize`, `subtle` traits within a released, non-yanked version. Relevance to https://github.com/serai-dex/serai/issues/201 and https://github.com/dalek-cryptography/curve25519-dalek/issues/811#issuecomment-3247732746. Also updates the `Ristretto` ciphersuite to prefer `Blake2b-512` over `SHA2-512`. In order to maintain compliance with FROST's IETF standard, `modular-frost` defines its own ciphersuite for Ristretto which still uses `SHA2-512`.
2025-09-03 12:25:37 -04:00
type Key = <Secp256k1 as WrappedGroup>::G;
type Address = Address;
type Output = Output;
type Eventuality = Eventuality;
fn id(&self) -> [u8; 32] {
self.epoch.end_hash
}
fn scan_for_outputs_unordered(
&self,
latest_active_key: Self::Key,
key: Self::Key,
) -> Vec<Self::Output> {
// Only return these outputs for the latest key
if latest_active_key != key {
return vec![];
}
// Associate all outputs with the latest active key
// We don't associate these with the current key within the SC as that'll cause outputs to be
// marked for forwarding if the SC is delayed to actually rotate
let mut outputs: Vec<_> = self
.instructions
.iter()
.cloned()
.map(|instruction| Output::Output { key, instruction })
.collect();
/*
The scanner requires a change output be associated with every Eventuality that came from
fulfilling payments, unless said Eventuality descends from an Eventuality meeting that
requirement from the same fulfillment. This ensures we have a fully populated Eventualities
set by the time we process the block which has an Eventuality.
Accordingly, for any block with an Eventuality completion, we claim there's a Change output
so that the block is flagged. Ethereum doesn't actually have Change outputs, yet the scanner
won't report them to Substrate, and the Smart Contract scheduler will drop any/all outputs
passed to it (handwaving their balances as present within the Smart Contract).
*/
if !self.executed.is_empty() {
outputs.push(Output::Eventuality { key, nonce: self.executed.first().unwrap().nonce() });
}
outputs
}
#[allow(clippy::type_complexity)]
fn check_for_eventuality_resolutions(
&self,
eventualities: &mut EventualityTracker<Self::Eventuality>,
) -> HashMap<
<Self::Output as ReceivedOutput<Self::Key, Self::Address>>::TransactionId,
Self::Eventuality,
> {
let mut res = HashMap::new();
for executed in &self.executed {
2025-01-24 17:13:36 -05:00
let Some(mut expected) =
eventualities.active_eventualities.remove(executed.nonce().to_le_bytes().as_slice())
else {
// TODO: Why is this a continue, not an assert?
continue;
};
2025-01-24 17:13:36 -05:00
// If this is a Batch Eventuality, we didn't know how the OutInstructions would resolve at
// time of creation. Copy the results from the actual transaction into the expectation
if let (Executed::Batch { results, .. }, Executed::Batch { results: expected_results, .. }) =
(executed, &mut expected.0)
{
*expected_results = results.clone();
}
assert_eq!(
executed,
&expected.0,
"Router emitted distinct event for nonce {}",
executed.nonce()
);
/*
The transaction ID is used to determine how internal outputs from this transaction should
be handled (if they were actually internal or if they were just to an internal address).
The Ethereum integration doesn't use internal addresses, and only uses internal outputs to
flag a block as having an Eventuality. Those internal outputs will always be scanned, and
while they may be dropped/kept by this ID, the scheduler will then always drop them.
Accordingly, we have free reign as to what to set the transaction ID to.
We set the ID to the nonce as it's the most helpful value and unique barring someone
2025-01-24 17:13:36 -05:00
finding the preimage for this as a hash.
*/
let mut tx_id = [0; 32];
tx_id[.. 8].copy_from_slice(executed.nonce().to_le_bytes().as_slice());
res.insert(tx_id, expected);
}
res
}
}