diff --git a/sequencer/src/auction.rs b/sequencer/src/auction.rs index 9adfe9e95..34128d9c2 100644 --- a/sequencer/src/auction.rs +++ b/sequencer/src/auction.rs @@ -1,39 +1,41 @@ use crate::{ + eth_signature_key::EthKeyPair, state::{FeeAccount, FeeAmount}, NamespaceId, Transaction, ValidatedState, }; -use hotshot_types::data::ViewNumber; -use std::num::NonZeroU64; +use ark_serialize::CanonicalSerialize; +use committable::{Commitment, Committable}; +use derivative::Derivative; +use ethers::types::Signature; +use hotshot::types::SignatureKey; +use hotshot_types::{data::ViewNumber, traits::signature_key::BuilderSignatureKey}; +use rand::RngCore; +use serde::{Deserialize, Serialize}; +use std::{ + collections::{HashMap, HashSet}, + num::NonZeroU64, + str::FromStr, +}; use url::Url; -/// State Machine for Auction -#[derive(Clone, Copy)] -struct Auction { - /// current phase. - phase: AuctionPhase, - /// A structure to find which Phase should be the current one. - // This could probably be an enum with some restructuring. - possible_phases: [AuctionPhase; 3], -} - -impl Auction { - fn update(mut self, view: ViewNumber) { - self.phase = self - .possible_phases - .into_iter() - .find(|phase| (view > phase.start && view < phase.end)) - .unwrap(); - } - fn recv_bid(&self, view: ViewNumber, bid: BidTx) { - self.update(view); - match self.phase.kind { - AuctionPhaseKind::Bid => self.send_txn(bid.as_txn()), - _ => unimplemented!(), - } - } - fn send_txn(&self, txn: Transaction) { - unimplemented!(); - } +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)] +struct SequencerKey; + +// for MVP-0(-1) (JIT) +// Sum of all sequencing fee match current check +// builder signature no longer includes payload +// new fee info flag (fee or bid) + +pub struct MarketplaceResults { + /// Slot these results are for + slot: Slot, + /// Bids that did not win, intially all bids are added to this, + /// and then the winning bids are removed + pending_bids: Vec, + /// Map of winning sequencer public keys to the bundle of namespaces they bid for + winner_map: HashMap>, + /// Whether refunds have been processed for this slot + refunds_processed: bool, } // - needs to be configured in genesis block @@ -70,21 +72,42 @@ enum AuctionPhaseKind { Sequence, } -struct SignatureKey; -struct Slot; +#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Deserialize, Serialize, Hash)] +pub struct Slot(u64); +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)] +pub enum FullNetworkTx { + Bid(BidTx), +} -/// A transaction to bid for the sequencing rights of a namespace +impl FullNetworkTx { + pub fn execute(&self, state: &ValidatedState) -> Result<(), (ExecutionError, FullNetworkTx)> { + dbg!("execute"); + match self { + Self::Bid(bid) => bid.execute(state), + } + } +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)] struct BidTx { - /// Signature over all other data in this struct - signature: FeeAccount, + body: BidTxBody, + signature: Signature, +} + +/// A transaction to bid for the sequencing rights of a namespace +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)] +struct BidTxBody { + /// Account responsible for the signature + account: FeeAccount, /// Fee to be sequenced in the network. Different than the bid_amount fee // FULL_NETWORK_GAS * MINIMUM_GAS_PRICE gas_price: FeeAmount, /// The bid amount designated in Wei. This is different than /// the sequencing fee (gas price) for this transaction bid_amount: FeeAmount, + // TODO What is the correct type? /// The public key of this sequencer - public_key: SignatureKey, + public_key: SequencerKey, /// The URL the HotShot leader will use to request a bundle /// from this sequencer if they win the auction url: Url, @@ -94,28 +117,128 @@ struct BidTx { bundle: Vec, } -fn get_phase() -> AuctionPhaseKind { - AuctionPhaseKind::Bid +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)] +struct ShortBidTx { + signature: Signature, + /// Account responsible for the signature + account: FeeAccount, + url: Url, } -fn store_in_validated_state() { - unimplemented!(); +// TODO consider a committable derive macro +impl Committable for BidTxBody { + fn tag() -> String { + "BID_TX".to_string() + } + + fn commit(&self) -> Commitment { + let comm = committable::RawCommitmentBuilder::new(&Self::tag()) + .fixed_size_field("account", &self.account.to_fixed_bytes()) + .fixed_size_field("gas_price", &self.gas_price.to_fixed_bytes()) + .fixed_size_field("bid_amount", &self.bid_amount.to_fixed_bytes()) + .var_size_field("url", self.url.as_str().as_ref()) + .u64_field("slot", self.slot.0) + .var_size_field( + "bundle", + // TODO what is the correct way to serialize `Vec` + &bincode::serialize(&self.bundle.as_slice()).unwrap(), + ); + comm.finalize() + } } -impl BidTx { - // maybe better implemented as a From on Transaction - fn as_txn(&self) -> Transaction { - unimplemented!(); +impl Default for BidTxBody { + fn default() -> Self { + let message = ";)"; + let mut commitment = [0u8; 32]; + commitment[..message.len()].copy_from_slice(message.as_bytes()); + let (account, key) = FeeAccount::generated_from_seed_indexed([0; 32], 0); + let nsid = NamespaceId::from(999); + Self { + url: Url::from_str("htts://sequencer:3939/request_budle").unwrap(), + account, + public_key: SequencerKey, + gas_price: FeeAmount::default(), + bid_amount: FeeAmount::default(), + slot: Slot::default(), + bundle: vec![nsid], + } + } +} +impl Default for BidTx { + fn default() -> Self { + let body = BidTxBody::default(); + let commitment = body.commit(); + let (account, key) = FeeAccount::generated_from_seed_indexed([0; 32], 0); + let signature = FeeAccount::sign_builder_message(&key, &commitment.as_ref()).unwrap(); + Self { signature, body } } - // - fn execute(&self, state: ValidatedState) -> Option { +} + +#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)] +pub enum ExecutionError { + InvalidSignature, + InvalidPhase, +} + +// TODO consider moving common functionality to trait. +impl BidTx { + /// Executes `BidTx`. + /// * verify signature + /// * charge fee + /// * store state + // The rational behind the `Err` is to provide not only what + // failed, but for which varient. The entire Tx is probably + // overkill, but we can narrow down how much we want to know about + // Failed Tx in the future. Maybe we just want its name. + pub fn execute(&self, state: &ValidatedState) -> Result<(), (ExecutionError, FullNetworkTx)> { if get_phase() != AuctionPhaseKind::Bid { - return None; + return Err(( + ExecutionError::InvalidPhase, + FullNetworkTx::Bid(self.clone()), + )); } - store_in_validated_state(); + self.verify() + .map_err(|e| (e, FullNetworkTx::Bid(self.clone())))?; + + // self.charge().map_err; + store_in_marketplace_state(); + + // TODO what do we return in good result? + Ok(()) } + // fn charge(&self) {} + /// Verify signature + fn verify(&self) -> Result<(), ExecutionError> { + if !self + .body + .account + .validate_builder_signature(&self.signature, self.body.commit().as_ref()) + { + return Err(ExecutionError::InvalidSignature); + }; + + Ok(()) + } +} + +pub fn get_phase() -> AuctionPhaseKind { + AuctionPhaseKind::Bid +} + +fn store_in_marketplace_state() { + unimplemented!(); } +enum ExecutionResult { + Ok(T), + Err(E, K), +} + +// enum ExecutionResult { +// Bid(String), +// } + /// A solution to one auction of sequencing rights for namespaces struct AuctionResultTx { auction: AuctionId, @@ -124,12 +247,58 @@ struct AuctionResultTx { signature: Signature, } -// Not sure if needed +// Seems like sequencer could just check for refund flags on events so +// we probably don't need an explicit transaction type. struct BidRefundTx { nonce: Nonce, - txns_to_refund: Vec, + // I don't think we need the list b/c we have them in state + // txns_to_refund: Vec, signature: Signature, } /// Nonce for special (auction) transactions struct Nonce(u64); + +pub fn mock_full_network_txs() -> Vec { + let x = FullNetworkTx::Bid(BidTx::default()); + dbg!(&x); + vec![x] +} + +mod test { + use super::*; + + impl BidTx { + fn mock(account: FeeAccount, key: EthKeyPair) -> Self { + let body = BidTxBody::default(); + let commitment = body.commit(); + let signature = FeeAccount::sign_builder_message(&key, commitment.as_ref()).unwrap(); + Self { signature, body } + } + } + + #[test] + fn test_default_bidtx_body() { + let a = FeeAccount::default(); + + let message = ";)"; + let mut commitment = [0u8; 32]; + commitment[..message.len()].copy_from_slice(message.as_bytes()); + let key = FeeAccount::generated_from_seed_indexed([0; 32], 0).1; + let signature = FeeAccount::sign_builder_message(&key, &commitment).unwrap(); + let bid = BidTxBody::default(); + dbg!(&bid); + } + #[test] + fn test_mock_full_network_txs() { + let x = mock_full_network_txs(); + dbg!(&x); + } + #[test] + fn test_sign_and_verify_mock_bid() { + let account = FeeAccount::default(); + let key = FeeAccount::generated_from_seed_indexed([0; 32], 0).1; + let bidtx = BidTx::mock(account, key); + bidtx.verify().unwrap(); + } +} diff --git a/sequencer/src/header.rs b/sequencer/src/header.rs index 1ebb25b54..202f5202c 100644 --- a/sequencer/src/header.rs +++ b/sequencer/src/header.rs @@ -1,4 +1,7 @@ +use std::collections::HashSet; + use crate::{ + auction::{mock_full_network_txs, FullNetworkTx, Slot}, block::NsTable, chain_config::ResolvableChainConfig, eth_signature_key::BuilderSignature, @@ -95,6 +98,11 @@ pub struct Header { /// that `fee_info` is correct without relying on the signature. Thus, this signature is not /// included in the header commitment. pub builder_signature: Option, + // pub full_network_txs: Vec, + // /// refund flag set at the beginning of new slots + // /// In extreme cases, more than one slot may need to be refunded, + // /// hence this data structure + // pub refund_bids: HashSet, } impl Committable for Header { @@ -133,6 +141,14 @@ impl Committable for Header { } impl Header { + // TODO move to BlockHeader + pub fn get_full_network_txs(&self) -> Vec { + // TODO unmock + mock_full_network_txs() + } + pub fn get_refund_bids(&self) -> HashSet { + unimplemented!(); + } #[allow(clippy::too_many_arguments)] fn from_info( payload_commitment: VidCommitment, diff --git a/sequencer/src/state.rs b/sequencer/src/state.rs index 1adb449ea..2b84c8a5a 100644 --- a/sequencer/src/state.rs +++ b/sequencer/src/state.rs @@ -1,8 +1,14 @@ use crate::{ - api::data_source::CatchupDataSource, block::NsTableValidationError, catchup::SqlStateCatchup, - chain_config::BlockSize, chain_config::ResolvableChainConfig, eth_signature_key::EthKeyPair, - genesis::UpgradeType, persistence::ChainConfigPersistence, ChainConfig, Header, Leaf, - NodeState, SeqTypes, + api::data_source::CatchupDataSource, + auction::{ExecutionError, FullNetworkTx, MarketplaceResults, Slot}, + block::NsTableValidationError, + catchup::SqlStateCatchup, + chain_config::BlockSize, + chain_config::ResolvableChainConfig, + eth_signature_key::EthKeyPair, + genesis::UpgradeType, + persistence::ChainConfigPersistence, + ChainConfig, Header, Leaf, NodeState, SeqTypes, }; use anyhow::{bail, ensure, Context}; use ark_serialize::{ @@ -52,8 +58,8 @@ use sequencer_utils::{ impl_serde_from_string_or_integer, impl_to_fixed_bytes, ser::FromStringOrInteger, }; use serde::{Deserialize, Serialize}; -use std::sync::Arc; use std::time::Duration; +use std::{collections::HashMap, sync::Arc}; use std::{collections::HashSet, ops::Add, str::FromStr}; use thiserror::Error; use vbs::version::Version; @@ -77,6 +83,9 @@ pub struct ValidatedState { /// Fee Merkle Tree pub fee_merkle_tree: FeeMerkleTree, pub chain_config: ResolvableChainConfig, + // TODO + // /// Map of Marketplace Results. + // slot_map: HashMap, } #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq, Eq)] @@ -777,9 +786,15 @@ impl ValidatedState { // amount. // // Note that different from `charge_fee` fee for the bid (sent - // to fee_recipient) whilch the bid itself will be held in an + // to fee_recipient) which the bid itself will be held in an // escrow account. // charge_bids(&mut validated_state, bids_vec, fee_recipient, escrow_account); + + // or possibly we will call `charge_fee` twice, once as here + // and the 2nd will have escrow_account in recipient place + + // but where does the escrow account information come from? + // headers? state? charge_fee( &mut validated_state, &mut delta, @@ -953,6 +968,17 @@ impl HotShotState for ValidatedState { if parent_leaf.view_number().u64() % 10 == 0 { tracing::info!("validated and applied new header"); } + + if let Err((err, kind)) = apply_full_transactions( + &validated_state, + instance.chain_config, + proposed_header.get_full_network_txs(), + ) { + tracing::error!("Invalid Tx Error: {err:?}, kind: {kind:?}"); + // TODO review spec for conditions of BlockError + return Err(BlockError::InvalidBlockHeader); + } + Ok((validated_state, delta)) } /// Construct the state with the given block header. @@ -986,6 +1012,19 @@ impl HotShotState for ValidatedState { } } +fn apply_full_transactions( + validated_state: &ValidatedState, + chain_config: ChainConfig, + full_network_txs: Vec, +) -> Result<(), (ExecutionError, FullNetworkTx)> { + dbg!(&full_network_txs); + // proposed_header + // .get_full_network_txs() + full_network_txs + .iter() + .try_for_each(|tx| tx.execute(validated_state)) +} + // Required for TestableState #[cfg(any(test, feature = "testing"))] impl std::fmt::Display for ValidatedState { @@ -1053,10 +1092,9 @@ impl MerklizedState for BlockMerkleTree { CanonicalDeserialize, )] /// `FeeInfo` holds data related to builder fees. -pub(crate) struct FeeInfo { +pub struct FeeInfo { account: FeeAccount, amount: FeeAmount, - // TODO recepient: FeeAccount, } impl FeeInfo { pub fn new(account: impl Into, amount: impl Into) -> Self { @@ -1481,6 +1519,8 @@ impl FeeAccountProof { #[cfg(test)] mod test { + use crate::auction::mock_full_network_txs; + use super::*; use async_compatibility_layer::logging::{setup_backtrace, setup_logging}; use hotshot_types::vid::vid_scheme; @@ -1646,6 +1686,14 @@ mod test { ); } + #[test] + fn test_apply_full_tx() { + let state = ValidatedState::default(); + let txs = mock_full_network_txs(); + let (err, bid) = apply_full_transactions(&state, ChainConfig::default(), txs).unwrap_err(); + assert_eq!(ExecutionError::InvalidSignature, err); + } + #[test] fn test_fee_amount_serde_json_as_decimal() { let amt = FeeAmount::from(123);