Skip to content

Commit

Permalink
Add Auction Types (#1726)
Browse files Browse the repository at this point in the history
* Add Auction Types

Adds `BidTx` and friends to `v0_3`. The intention is to provide the
necessary elements for first round of Marketplace integration. Since
this is all hidden behind a version gate, our priority should be
to satisfy integration necessities.

---------

Co-authored-by: tbro <[email protected]>
  • Loading branch information
tbro and tbro authored Jul 18, 2024
1 parent fd9af0f commit ea25667
Show file tree
Hide file tree
Showing 5 changed files with 329 additions and 0 deletions.
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
271 changes: 271 additions & 0 deletions types/src/v0/impls/auction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
use crate::{
eth_signature_key::{EthKeyPair, SigningError},
v0_1::ValidatedState,
v0_3::{BidTx, BidTxBody, FullNetworkTx},
FeeAccount, FeeAmount, FeeError, FeeInfo, NamespaceId,
};
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 {
/// Proxy for `execute` method of each transaction variant.
pub fn execute(&self, state: &mut ValidatedState) -> Result<(), ExecutionError> {
match self {
Self::Bid(bid) => bid.execute(state),
}
}
}

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,
// TODO gas_price will come from config probably, but we
// can use any value for first round of integration
..Self::default()
}
}

/// Sign `BidTxBody` and return the signature.
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`.
/// ```
/// # use espresso_types::FeeAccount;
/// # use espresso_types::v0_3::BidTxBody;
///
/// 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
}
/// Instantiate a `BidTxBody` containing the values of `self`
/// with a new `url` field.
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 {
url: Url::from_str("https://sequencer:3939").unwrap(),
account: key.fee_account(),
gas_price: FeeAmount::default(),
bid_amount: FeeAmount::default(),
view: ViewNumber::genesis(),
namespaces: vec![nsid],
}
}
}
impl Default for BidTx {
fn default() -> Self {
BidTxBody::default()
.signed(&FeeAccount::test_key_pair())
.unwrap()
}
}

#[derive(Error, Debug, Eq, PartialEq)]
/// Failure cases of transaction execution
pub enum ExecutionError {
#[error("Invalid Signature")]
/// Transaction Signature could not be verified.
InvalidSignature,
#[error("Invalid Phase")]
/// Transaction submitted during incorrect Marketplace Phase
InvalidPhase,
#[error("FeeError: {0}")]
/// Insufficient funds or MerkleTree error.
FeeError(FeeError),
#[error("Could not resolve `ChainConfig`")]
/// Could not resolve `ChainConfig`.
UnresolvableChainConfig,
}

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

impl BidTx {
/// Execute `BidTx`.
/// * verify signature
/// * charge bid amount
/// * charge gas
pub fn execute(&self, state: &mut ValidatedState) -> Result<(), ExecutionError> {
self.verify()?;

// 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)?;

Ok(())
}
/// Charge Bid. Only winning bids are charged in JIT.
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);
};

// TODO change to `bid_recipient` when this logic is finally enabled
let recipient = chain_config.fee_recipient;
// Charge the bid amount
state
.charge_fee(FeeInfo::new(self.account(), self.amount()), recipient)
.map_err(ExecutionError::from)?;

// 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
}
/// Instantiate a `BidTx` containing the values of `self`
/// with a new `url` field on `body`.
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 account
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()
}
}

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/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
52 changes: 52 additions & 0 deletions types/src/v0/v0_3/auction.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
use crate::{FeeAccount, FeeAmount, NamespaceId};
use ethers::types::Signature;
use hotshot_types::data::ViewNumber;
use serde::{Deserialize, Serialize};
use url::Url;

#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
/// Wrapper enum for Full Network Transactions. Each transaction type
/// will be a variant of this enum.
pub enum FullNetworkTx {
Bid(BidTx),
}

#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
/// A transaction to bid for the sequencing rights of a namespace. It
/// is the `signed` form of `BidTxBody`. Expected usage is *build*
/// it by calling `signed` on `BidTxBody`.
pub struct BidTx {
pub(crate) body: BidTxBody,
pub(crate) signature: Signature,
}

/// A transaction body holding data required for bid submission.
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
pub struct BidTxBody {
/// Account responsible for the signature
pub(crate) account: FeeAccount,
/// Fee to be sequenced in the network. Different than the bid_amount fee
// FULL_NETWORK_GAS * MINIMUM_GAS_PRICE
pub(crate) gas_price: FeeAmount,
/// The bid amount designated in Wei. This is different than
/// the sequencing fee (gas price) for this transaction
pub(crate) bid_amount: FeeAmount,
/// The URL the HotShot leader will use to request a bundle
/// from this sequencer if they win the auction
pub(crate) url: Url,
/// The slot this bid is for
pub(crate) view: ViewNumber,
/// The set of namespace ids the sequencer is bidding for
pub(crate) namespaces: Vec<NamespaceId>,
}

/// The results of an Auction
#[derive(Debug, Clone, Eq, PartialEq, Deserialize, Serialize, Hash)]
pub struct AuctionResults {
/// view number the results are for
pub(crate) view_number: ViewNumber,
/// A list of the bid txs that won
pub(crate) winning_bids: Vec<BidTx>,
/// A list of reserve sequencers being used
pub(crate) reserve_bids: Vec<(NamespaceId, Url)>,
}
2 changes: 2 additions & 0 deletions types/src/v0/v0_3/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ pub use super::v0_1::{

pub const VERSION: Version = Version { major: 0, minor: 3 };

mod auction;
mod header;

pub use auction::{AuctionResults, BidTx, BidTxBody, FullNetworkTx};
pub use header::Header;

0 comments on commit ea25667

Please sign in to comment.