Skip to content

Commit

Permalink
Merge branch 'main' into jh/dev-node-doc
Browse files Browse the repository at this point in the history
  • Loading branch information
ImJeremyHe authored Jul 19, 2024
2 parents 4ff6e8f + 332a452 commit 9997ff8
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 19 deletions.
2 changes: 1 addition & 1 deletion sequencer/src/api/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -374,7 +374,7 @@ impl Options {
let get_node_state = async move { state.node_state().await.clone() };
tasks.spawn(
"merklized state storage update loop",
update_state_storage_loop(ds, get_node_state, Ver::version()),
update_state_storage_loop(ds, get_node_state),
);
}

Expand Down
30 changes: 12 additions & 18 deletions sequencer/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use hotshot_query_service::{
types::HeightIndexed,
};
use jf_merkle_tree::{LookupResult, MerkleTreeScheme, ToTraversalPath, UniversalMerkleTreeScheme};
use vbs::version::Version;

use crate::{
api::data_source::CatchupDataSource, catchup::SqlStateCatchup,
Expand All @@ -27,14 +26,19 @@ async fn compute_state_update(
instance: &NodeState,
parent_leaf: &LeafQueryData<SeqTypes>,
proposed_leaf: &LeafQueryData<SeqTypes>,
version: Version,
) -> anyhow::Result<(ValidatedState, Delta)> {
let proposed_leaf = proposed_leaf.leaf();
let parent_leaf = parent_leaf.leaf();
let header = proposed_leaf.block_header();

// Check internal consistency.
let parent_header = parent_leaf.block_header();
ensure!(
state.chain_config.commit() == parent_header.chain_config().commit(),
"internal error! in-memory chain config {:?} does not match parent header {:?}",
state.chain_config,
parent_header.chain_config(),
);
ensure!(
state.block_merkle_tree.commitment() == parent_header.block_merkle_tree_root(),
"internal error! in-memory block tree {:?} does not match parent header {:?}",
Expand All @@ -49,7 +53,7 @@ async fn compute_state_update(
);

state
.apply_header(instance, parent_leaf, header, version)
.apply_header(instance, parent_leaf, header, header.version())
.await
}

Expand Down Expand Up @@ -132,14 +136,12 @@ async fn update_state_storage(
instance: &NodeState,
parent_leaf: &LeafQueryData<SeqTypes>,
proposed_leaf: &LeafQueryData<SeqTypes>,
version: Version,
) -> anyhow::Result<ValidatedState> {
let parent_chain_config = parent_state.chain_config;

let (state, delta) =
compute_state_update(parent_state, instance, parent_leaf, proposed_leaf, version)
.await
.context("computing state update")?;
let (state, delta) = compute_state_update(parent_state, instance, parent_leaf, proposed_leaf)
.await
.context("computing state update")?;

let mut storage = storage.write().await;
if let Err(err) = store_state_update(&mut *storage, proposed_leaf.height(), &state, delta).await
Expand Down Expand Up @@ -199,7 +201,6 @@ async fn store_genesis_state(
pub(crate) async fn update_state_storage_loop(
storage: Arc<RwLock<impl SequencerStateDataSource>>,
instance: impl Future<Output = NodeState>,
version: Version,
) -> anyhow::Result<()> {
let mut instance = instance.await;
instance.peers = Arc::new(SqlStateCatchup::new(storage.clone(), Default::default()));
Expand Down Expand Up @@ -240,15 +241,8 @@ pub(crate) async fn update_state_storage_loop(

while let Some(leaf) = leaves.next().await {
loop {
match update_state_storage(
&parent_state,
&storage,
&instance,
&parent_leaf,
&leaf,
version,
)
.await
match update_state_storage(&parent_state, &storage, &instance, &parent_leaf, &leaf)
.await
{
Ok(state) => {
parent_leaf = leaf;
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
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
4 changes: 4 additions & 0 deletions types/src/v0/impls/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,10 @@ impl ValidatedState {

/// Charge a fee to an account, transferring the funds to the fee recipient account.
pub fn charge_fee(&mut self, fee_info: FeeInfo, recipient: FeeAccount) -> Result<(), FeeError> {
if fee_info.amount == 0.into() {
return Ok(());
}

let fee_state = self.fee_merkle_tree.clone();

// Deduct the fee from the paying account.
Expand Down
Loading

0 comments on commit 9997ff8

Please sign in to comment.