Skip to content

Commit

Permalink
Add marketplace types
Browse files Browse the repository at this point in the history
`BidTx` as a varian of `FullNetworkTx`. Includes some tests.

Closes #1633
  • Loading branch information
tbro committed Jul 11, 2024
1 parent f6e1930 commit cc8f35f
Show file tree
Hide file tree
Showing 13 changed files with 394 additions and 17 deletions.
2 changes: 2 additions & 0 deletions sequencer/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,7 @@ mod test {
max_block_size: 30000.into(),
base_fee: 1.into(),
fee_recipient: FeeAccount::default(),
bid_recipient: FeeAccount::default(),
fee_contract: Some(Address::default())
}
);
Expand Down Expand Up @@ -267,6 +268,7 @@ mod test {
max_block_size: 30000.into(),
base_fee: 1.into(),
fee_recipient: FeeAccount::default(),
bid_recipient: FeeAccount::default(),
fee_contract: None,
}
);
Expand Down
3 changes: 3 additions & 0 deletions types/src/eth_signature_key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ impl EthKeyPair {
let signing_key: &SigningKey = derived_priv_key.as_ref();
Ok(signing_key.clone().into())
}
pub fn random() -> EthKeyPair {
SigningKey::random(&mut rand::thread_rng()).into()
}

pub fn fee_account(&self) -> FeeAccount {
self.fee_account
Expand Down
1 change: 1 addition & 0 deletions types/src/reference_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ fn reference_chain_config() -> ChainConfig {
base_fee: 0.into(),
fee_contract: Some(Default::default()),
fee_recipient: Default::default(),
bid_recipient: Default::default(),
}
}

Expand Down
284 changes: 284 additions & 0 deletions types/src/v0/impls/auction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,284 @@
use crate::{
eth_signature_key::{EthKeyPair, SigningError},
v0_3::{BidTx, BidTxBody, FullNetworkTx},
FeeAccount, FeeAmount, FeeError, FeeInfo, NamespaceId, ValidatedState,
};
use committable::{Commitment, Committable};
use ethers::types::Signature;
use hotshot_types::{
data::ViewNumber,
traits::{
auction_results_provider::HasUrl, node_implementation::ConsensusTime,
signature_key::BuilderSignatureKey,
},
};
use std::str::FromStr;
use thiserror::Error;
use url::Url;

impl FullNetworkTx {
pub fn execute(
&self,
state: &mut ValidatedState,
) -> Result<(), (ExecutionError, FullNetworkTx)> {
match self {
Self::Bid(bid) => bid.execute(state),
}
}
}

// TODO consider a committable derive macro
impl Committable for BidTxBody {
fn tag() -> String {
"BID_TX".to_string()
}

fn commit(&self) -> Commitment<Self> {
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("view", self.view.u64())
.var_size_field("namespaces", &bincode::serialize(&self.namespaces).unwrap());
comm.finalize()
}
}

impl BidTxBody {
/// Construct a new `BidTxBody`.
pub fn new(
account: FeeAccount,
bid: FeeAmount,
view: ViewNumber,
namespaces: Vec<NamespaceId>,
url: Url,
) -> Self {
Self {
account,
bid_amount: bid,
view,
namespaces,
url,
..Self::default()
}
}

/// Sign `BidTxBody` and return the signature.
pub fn sign(&self, key: &EthKeyPair) -> Result<Signature, SigningError> {
FeeAccount::sign_builder_message(key, self.commit().as_ref())
}
/// Sign Body and return a `BidTx`. This is the expected way to obtain a `BidTx`.
/// ```
/// let key = FeeAccount::test_key_pair();
/// BidTxBody::default().signed(&key).unwrap();
/// ```
pub fn signed(self, key: &EthKeyPair) -> Result<BidTx, SigningError> {
let signature = self.sign(key)?;
let bid = BidTx {
body: self,
signature,
};
Ok(bid)
}

/// Get account submitting the bid
pub fn account(&self) -> FeeAccount {
self.account
}
/// Get amount of bid
pub fn amount(&self) -> FeeAmount {
self.bid_amount
}
/// Update `url` field on a previously instantiated `BidTxBody`.
pub fn with_url(self, url: Url) -> Self {
Self { url, ..self }
}
}

impl Default for BidTxBody {
fn default() -> Self {
let key = FeeAccount::test_key_pair();
let nsid = NamespaceId::from(999u64);
Self {
// TODO url will be builder_url, needs to be passed in from somewhere
url: Url::from_str("https://sequencer:3939").unwrap(),
account: key.fee_account(),
public_key: FeeAccount::default(),
gas_price: FeeAmount::default(),
bid_amount: FeeAmount::default(),
view: ViewNumber::genesis(),
namespaces: vec![nsid],
}
}
}
impl Default for BidTx {
fn default() -> Self {
let body = BidTxBody::default();
let key = FeeAccount::test_key_pair();
let signature = FeeAccount::sign_builder_message(&key, body.commit().as_ref()).unwrap();
Self { signature, body }
}
}

#[derive(Error, Debug, Eq, PartialEq)]
/// Failure cases of transaction execution
pub enum ExecutionError {
#[error("Invalid Signature")]
InvalidSignature,
#[error("Invalid Phase")]
InvalidPhase,
#[error("FeeError: {0}")]
FeeError(FeeError),
#[error("Could not resolve `ChainConfig`")]
UnresolvableChainConfig,
}

impl From<FeeError> for ExecutionError {
fn from(e: FeeError) -> Self {
Self::FeeError(e)
}
}

// TODO consider moving common functionality to trait.
impl BidTx {
/// Execute `BidTx`.
/// * verify signature
/// * charge fee
// The rational behind the `Err` is to provide not only what
// failed, but for which variant. 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: &mut ValidatedState,
) -> Result<(), (ExecutionError, FullNetworkTx)> {
self.verify()
.map_err(|e| (e, FullNetworkTx::Bid(self.clone())))?;

// In JIT sequencer only receives winning bids. In AOT all
// bids are charged as received (losing bids are refunded). In
// any case we can charge the bids and gas during execution.
self.charge(state)
.map_err(|e| (e, FullNetworkTx::Bid(self.clone())))?;

// TODO what do we return in good result?
Ok(())
}
/// Charge Bid. Only winning bids are charged in JIT (I think).
fn charge(&self, state: &mut ValidatedState) -> Result<(), ExecutionError> {
// As the code is currently organized, I think chain_config
// will always be resolved here. But let's guard against the
// error in case code is shifted around in the future.
let Some(chain_config) = state.chain_config.resolve() else {
return Err(ExecutionError::UnresolvableChainConfig);
};

let recipient = chain_config.bid_recipient;
// Charge the bid amount
state
.charge_fee(FeeInfo::new(self.account(), self.amount()), recipient)
.map_err(ExecutionError::from)?;

// TODO are gas and bid funded to same recipient? Possibly
// gas would be funded to recipient sequencing
// fee recipient?
// Charge the the gas amount
state
.charge_fee(FeeInfo::new(self.account(), self.gas_price()), recipient)
.map_err(ExecutionError::from)?;

Ok(())
}
/// Cryptographic signature verification
fn verify(&self) -> Result<(), ExecutionError> {
self.body
.account
.validate_builder_signature(&self.signature, self.body.commit().as_ref())
.then_some(())
.ok_or(ExecutionError::InvalidSignature)
}
/// Return the body of the transaction
pub fn body(self) -> BidTxBody {
self.body
}
/// Update `url` field on a previously instantiated `BidTxBody`.
pub fn with_url(self, url: Url) -> Self {
let body = self.body.with_url(url);
Self { body, ..self }
}
/// get gas price
pub fn gas_price(&self) -> FeeAmount {
self.body.gas_price
}
/// get bid amount
pub fn amount(&self) -> FeeAmount {
self.body.bid_amount
}
/// get bid amount
pub fn account(&self) -> FeeAccount {
self.body.account
}
}

impl HasUrl for BidTx {
/// Get the `url` field from the body.
fn url(&self) -> Url {
self.body.url()
}
}

impl HasUrl for BidTxBody {
/// Get the cloned `url` field.
fn url(&self) -> Url {
self.url.clone()
}
}

pub fn mock_full_network_txs(key: Option<EthKeyPair>) -> Vec<FullNetworkTx> {
// if no key is supplied, use `test_key_pair`. Since default `BidTxBody` is
// signed with `test_key_pair`, it will verify successfully
let key = key.unwrap_or_else(FeeAccount::test_key_pair);
vec![FullNetworkTx::Bid(BidTx::mock(key))]
}

mod test {
use super::*;

impl BidTx {
pub fn mock(key: EthKeyPair) -> Self {
BidTxBody::default().signed(&key).unwrap()
}
}

#[test]
fn test_mock_bid_tx_sign_and_verify() {
let key = FeeAccount::test_key_pair();
let bidtx = BidTx::mock(key);
bidtx.verify().unwrap();
}

#[test]
fn test_mock_bid_tx_charge() {
let mut state = ValidatedState::default();
let key = FeeAccount::test_key_pair();
let bidtx = BidTx::mock(key);
bidtx.charge(&mut state).unwrap();
}

#[test]
fn test_bid_tx_construct() {
let key_pair = EthKeyPair::random();
BidTxBody::new(
key_pair.fee_account(),
FeeAmount::from(1),
ViewNumber::genesis(),
vec![NamespaceId::from(999u64)],
Url::from_str("https://sequencer:3131").unwrap(),
)
.signed(&key_pair)
.unwrap()
.verify()
.unwrap();
}
}
1 change: 1 addition & 0 deletions types/src/v0/impls/chain_config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ impl Default for ChainConfig {
base_fee: 0.into(),
fee_contract: None,
fee_recipient: Default::default(),
bid_recipient: Default::default(),
}
}
}
Expand Down
16 changes: 6 additions & 10 deletions types/src/v0/impls/fee_info.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// use crate::SeqTypes;

use std::str::FromStr;

use crate::{
eth_signature_key::EthKeyPair, v0_3::IterableFeeInfo, AccountQueryData, FeeAccount,
FeeAccountProof, FeeAmount, FeeInfo, FeeMerkleCommitment, FeeMerkleProof, FeeMerkleTree,
SeqTypes,
};
use anyhow::{bail, ensure, Context};
use ark_serialize::{
CanonicalDeserialize, CanonicalSerialize, Compress, Read, SerializationError, Valid, Validate,
Expand All @@ -24,14 +25,9 @@ use num_traits::CheckedSub;
use sequencer_utils::{
impl_serde_from_string_or_integer, impl_to_fixed_bytes, ser::FromStringOrInteger,
};
use std::str::FromStr;
use thiserror::Error;

use crate::{
eth_signature_key::EthKeyPair, v0_3::IterableFeeInfo, AccountQueryData, FeeAccount,
FeeAccountProof, FeeAmount, FeeInfo, FeeMerkleCommitment, FeeMerkleProof, FeeMerkleTree,
SeqTypes,
};

/// Possible charge fee failures
#[derive(Error, Debug, Eq, PartialEq)]
pub enum FeeError {
Expand Down
7 changes: 6 additions & 1 deletion types/src/v0/impls/header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use vbs::version::Version;
use crate::{
v0::header::{EitherOrVersion, VersionedHeader},
v0_1, v0_2,
v0_3::{self, IterableFeeInfo},
v0_3::{self, FullNetworkTx, IterableFeeInfo},
BlockMerkleCommitment, BlockSize, BuilderSignature, ChainConfig, FeeAccount, FeeAmount,
FeeInfo, FeeMerkleCommitment, Header, L1BlockInfo, L1Snapshot, Leaf, NamespaceId, NodeState,
NsTable, NsTableValidationError, ResolvableChainConfig, SeqTypes, UpgradeType, ValidatedState,
Expand Down Expand Up @@ -128,6 +128,11 @@ impl Committable for Header {
}

impl Header {
pub fn get_full_network_txs(&self) -> Vec<FullNetworkTx> {
// TODO unmock
super::auction::mock_full_network_txs(None)
}

pub fn version(&self) -> Version {
match self {
Self::V1(_) => Version { major: 0, minor: 1 },
Expand Down
1 change: 1 addition & 0 deletions types/src/v0/impls/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub use super::*;

mod auction;
mod block;
mod chain_config;
mod fee_info;
Expand Down
Loading

0 comments on commit cc8f35f

Please sign in to comment.