diff --git a/Cargo.lock b/Cargo.lock index f75cd63860..f66ac2907c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -7102,6 +7102,7 @@ dependencies = [ "tari_common_types", "tari_core", "tari_dan_common_types", + "tari_engine_types", "tari_utilities", "thiserror", "tonic 0.6.2", diff --git a/applications/tari_validator_node/src/bootstrap.rs b/applications/tari_validator_node/src/bootstrap.rs index b2a96d47b5..f028caeb79 100644 --- a/applications/tari_validator_node/src/bootstrap.rs +++ b/applications/tari_validator_node/src/bootstrap.rs @@ -80,7 +80,7 @@ use crate::{ services::{ comms_peer_provider::CommsPeerProvider, mempool, - mempool::{FeeTransactionValidator, MempoolHandle, TemplateExistsValidator, Validator}, + mempool::{FeeTransactionValidator, InputRefsValidator, MempoolHandle, TemplateExistsValidator, Validator}, messaging, messaging::DanMessageReceivers, networking, @@ -185,6 +185,8 @@ pub async fn spawn_services( if !config.validator_node.no_fees { validator = validator.and_then(FeeTransactionValidator).boxed(); } + let after_executed_validator = InputRefsValidator::new(); + let (tx_executed_transaction, rx_executed_transaction) = mpsc::channel(10); let scanner = SubstateScanner::new(epoch_manager.clone(), validator_node_client_factory.clone()); let substate_resolver = TariSubstateResolver::new(state_store.clone(), scanner); @@ -197,6 +199,7 @@ pub async fn spawn_services( payload_processor.clone(), substate_resolver.clone(), validator, + after_executed_validator, state_store.clone(), ); handles.push(join_handle); diff --git a/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs b/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs index 8eff5bb5ef..e3e3f6d841 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/initializer.rs @@ -38,7 +38,7 @@ use crate::{ substate_resolver::SubstateResolverError, }; -pub fn spawn( +pub fn spawn( new_transactions: mpsc::Receiver, outbound: OutboundMessaging, tx_executed_transactions: mpsc::Sender, @@ -47,10 +47,12 @@ pub fn spawn( transaction_executor: TExecutor, substate_resolver: TSubstateResolver, validator: TValidator, + after_executed_validator: TExecutedValidator, state_store: SqliteStateStore, ) -> (MempoolHandle, JoinHandle>) where TValidator: Validator + Send + Sync + 'static, + TExecutedValidator: Validator + Send + Sync + 'static, TExecutor: TransactionExecutor + Clone + Send + Sync + 'static, TSubstateResolver: SubstateResolver + Clone + Send + Sync + 'static, { @@ -66,6 +68,7 @@ where transaction_executor, substate_resolver, validator, + after_executed_validator, state_store, ); let handle = MempoolHandle::new(tx_mempool_request); diff --git a/applications/tari_validator_node/src/p2p/services/mempool/mod.rs b/applications/tari_validator_node/src/p2p/services/mempool/mod.rs index 9f83480886..6ff542079d 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/mod.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/mod.rs @@ -26,9 +26,6 @@ pub use handle::{MempoolHandle, MempoolRequest}; mod initializer; pub use initializer::spawn; -mod and_then; -pub use and_then::AndThen; - mod error; mod executor; mod service; diff --git a/applications/tari_validator_node/src/p2p/services/mempool/service.rs b/applications/tari_validator_node/src/p2p/services/mempool/service.rs index 3981743224..792458e98d 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/service.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/service.rs @@ -20,22 +20,29 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use std::{collections::HashSet, fmt::Display, iter, sync::Arc}; +use std::{ + collections::{HashMap, HashSet}, + fmt::Display, + iter, + sync::Arc, +}; use futures::{future::BoxFuture, stream::FuturesUnordered, FutureExt, StreamExt}; use log::*; use tari_comms::NodeIdentity; use tari_dan_app_utilities::transaction_executor::{TransactionExecutor, TransactionProcessorError}; -use tari_dan_common_types::{Epoch, ShardId}; +use tari_dan_common_types::{Epoch, NodeAddressable, ShardId}; use tari_dan_engine::runtime::ConsensusContext; use tari_dan_p2p::{DanMessage, OutboundService}; use tari_dan_storage::{ - consensus_models::{ExecutedTransaction, TransactionRecord}, + consensus_models::{Block, Command, ExecutedTransaction, TransactionRecord, ValidatorFee}, StateStore, StateStoreWriteTransaction, }; +use tari_engine_types::{epoch::Epoch, fee_claim::FeeClaimAddress, instruction::Instruction}; use tari_epoch_manager::{base_layer::EpochManagerHandle, EpochManagerReader}; use tari_state_store_sqlite::SqliteStateStore; +use tari_template_lib::{models::Amount, prelude::RistrettoPublicKeyBytes}; use tari_transaction::{Transaction, TransactionId}; use tokio::sync::{mpsc, oneshot}; @@ -56,7 +63,7 @@ use crate::{ const LOG_TARGET: &str = "tari::validator_node::mempool::service"; #[derive(Debug)] -pub struct MempoolService { +pub struct MempoolService { transactions: HashSet, pending_executions: FuturesUnordered>>, new_transactions: mpsc::Receiver, @@ -65,15 +72,18 @@ pub struct MempoolService { tx_executed_transactions: mpsc::Sender, epoch_manager: EpochManagerHandle, node_identity: Arc, - validator: TValidator, + before_execute_validator: TValidator, + after_execute_validator: TExecutedValidator, transaction_executor: TExecutor, substate_resolver: TSubstateResolver, state_store: SqliteStateStore, } -impl MempoolService +impl + MempoolService where TValidator: Validator, + TExecutedValidator: Validator, TExecutor: TransactionExecutor + Clone + Send + Sync + 'static, TSubstateResolver: SubstateResolver + Clone + Send + Sync + 'static, { @@ -86,7 +96,8 @@ where node_identity: Arc, transaction_executor: TExecutor, substate_resolver: TSubstateResolver, - validator: TValidator, + before_execute_validator: TValidator, + after_execute_validator: TExecutedValidator, state_store: SqliteStateStore, ) -> Self { Self { @@ -100,7 +111,8 @@ where node_identity, transaction_executor, substate_resolver, - validator, + before_execute_validator, + after_execute_validator, state_store, } } @@ -177,7 +189,7 @@ where return Ok(()); } - self.validator.validate(&transaction).await?; + self.before_execute_validator.validate(&transaction).await?; self.state_store .with_write_tx(|tx| tx.transactions_insert(&transaction))?; @@ -197,7 +209,7 @@ where info!(target: LOG_TARGET, "🎱 New transaction in mempool"); self.transactions.insert(*transaction.id()); let current_epoch = self.epoch_manager.current_epoch().await?; - self.queue_transaction_for_execution(transaction, current_epoch); + self.queue_transaction_for_execution(transaction, current_epoch)?; } else { info!( target: LOG_TARGET, @@ -217,15 +229,76 @@ where Ok(()) } - fn queue_transaction_for_execution(&mut self, transaction: Transaction, current_epoch: Epoch) { + fn queue_transaction_for_execution( + &mut self, + transaction: Transaction, + current_epoch: Epoch, + ) -> Result<(), MempoolError> { let substate_resolver = self.substate_resolver.clone(); let executor = self.transaction_executor.clone(); let consensus_context = ConsensusContext { current_epoch: current_epoch.as_u64(), + permitted_fee_claims: self.get_permitted_fee_claims_for(&transaction)?, + signer: RistrettoPublicKeyBytes::from_bytes(&transaction.signer_public_key().as_bytes()).unwrap(), }; self.pending_executions .push(execute_transaction(transaction, substate_resolver, executor, consensus_context).boxed()); + + Ok(()) + } + + async fn get_permitted_fee_claims_for( + &self, + transaction: &Transaction, + ) -> Result, MempoolError> { + let claim_instructions = transaction + .instructions() + .iter() + .filter_map(|instruction| { + if let Some(Instruction::ClaimValidatorFees { + epoch, + validator_public_key, + }) = instruction + { + Some((Epoch(*epoch), validator_public_key.clone())) + } else { + None + } + }) + .collect::>(); + + if claim_instructions.is_empty() { + return Ok(HashMap::new()); + } + + // let mut claims = Vec::with_capacity(claim_instructions.len()); + // for (epoch, validator) in claim_instructions { + // let vn = self.epoch_manager.get_validator_node(*epoch, &validator).await?; + // // TODO: add this when we add the delegated claim public key to the validator node registration + // // An alternative approach is to allow the validator to sign a message and return it to a allowlisted + // wallet // if transactions.signer_public_key() != v.delegated_claim_public_key { + // // return Err(MempoolError::SignerNotPermittedToClaimFees{epoch, signer, validator}) + // // } + // claims.push((epoch, vn.shard_key)); + // } + + let mut permitted_fee_claims = HashMap::with_capacity(claims.len()); + self.state_store.with_read_tx(|tx| { + for (epoch, proposed_by) in claim_instructions { + let validator_fee = ValidatorFee::get_validator_fee_for_epoch(tx, epoch, &proposed_by)?; + if validator_fee > 0 { + permitted_fee_claims.insert( + epoch, + Amount::try_from(validator_fee).expect("Fee greater than Amount::MAX"), + ); + } + } + + Ok::<_, MempoolError>(()) + })?; + + Ok(permitted_fee_claims) } async fn handle_execution_complete( @@ -245,8 +318,7 @@ where executed.result().finalize.result, executed.execution_time() ); - // We refuse to process the transaction if any input_refs are downed - self.check_input_refs(&executed)?; + self.after_execute_validator.validate(&executed).await?; // Fill the outputs that were missing so that we can propagate to output shards self.fill_outputs(&mut executed); @@ -287,22 +359,6 @@ where Ok(()) } - fn check_input_refs(&self, executed: &ExecutedTransaction) -> Result<(), MempoolError> { - let Some(diff) = executed.result().finalize.result.accept().cloned() else { - return Ok(()); - }; - - let is_input_refs_downed = diff - .down_iter() - .map(|(s, v)| ShardId::from_address(s, *v)) - .any(|s| executed.transaction().input_refs().contains(&s)); - - if is_input_refs_downed { - return Err(MempoolError::InputRefsDowned); - } - Ok(()) - } - fn fill_outputs(&mut self, executed: &mut ExecutedTransaction) { let Some(diff) = executed.result().finalize.result.accept().cloned() else { return; diff --git a/applications/tari_validator_node/src/p2p/services/mempool/and_then.rs b/applications/tari_validator_node/src/p2p/services/mempool/validator/and_then.rs similarity index 82% rename from applications/tari_validator_node/src/p2p/services/mempool/and_then.rs rename to applications/tari_validator_node/src/p2p/services/mempool/validator/and_then.rs index b690522b8f..68fced0e8c 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/and_then.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/validator/and_then.rs @@ -1,9 +1,9 @@ -// Copyright 2023 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause use async_trait::async_trait; -use crate::p2p::services::mempool::Validator; +use super::Validator; pub struct AndThen { first: A, diff --git a/applications/tari_validator_node/src/p2p/services/mempool/validator/fee.rs b/applications/tari_validator_node/src/p2p/services/mempool/validator/fee.rs new file mode 100644 index 0000000000..d6d69c40e1 --- /dev/null +++ b/applications/tari_validator_node/src/p2p/services/mempool/validator/fee.rs @@ -0,0 +1,22 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use async_trait::async_trait; +use tari_transaction::Transaction; + +use crate::p2p::services::mempool::{MempoolError, Validator}; + +#[derive(Debug)] +pub struct FeeTransactionValidator; + +#[async_trait] +impl Validator for FeeTransactionValidator { + type Error = MempoolError; + + async fn validate(&self, transaction: &Transaction) -> Result<(), MempoolError> { + if transaction.fee_instructions().is_empty() { + return Err(MempoolError::NoFeeInstructions); + } + Ok(()) + } +} diff --git a/applications/tari_validator_node/src/p2p/services/mempool/validator/input_refs.rs b/applications/tari_validator_node/src/p2p/services/mempool/validator/input_refs.rs new file mode 100644 index 0000000000..4b074b7403 --- /dev/null +++ b/applications/tari_validator_node/src/p2p/services/mempool/validator/input_refs.rs @@ -0,0 +1,36 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_dan_common_types::ShardId; +use tari_dan_storage::consensus_models::ExecutedTransaction; + +use crate::p2p::services::mempool::{MempoolError, Validator}; + +/// Refuse to process the transaction if any input_refs are downed +pub struct InputRefsValidator; + +impl InputRefsValidator { + pub fn new() -> Self { + Self + } +} + +impl Validator for InputRefsValidator { + type Error = MempoolError; + + async fn validate(&self, executed: &ExecutedTransaction) -> Result<(), Self::Error> { + let Some(diff) = executed.result().finalize.result.accept().cloned() else { + return Ok(()); + }; + + let is_input_refs_downed = diff + .down_iter() + .map(|(s, v)| ShardId::from_address(s, *v)) + .any(|s| executed.transaction().input_refs().contains(&s)); + + if is_input_refs_downed { + return Err(MempoolError::InputRefsDowned); + } + Ok(()) + } +} diff --git a/applications/tari_validator_node/src/p2p/services/mempool/validator/mod.rs b/applications/tari_validator_node/src/p2p/services/mempool/validator/mod.rs new file mode 100644 index 0000000000..dd1922bdff --- /dev/null +++ b/applications/tari_validator_node/src/p2p/services/mempool/validator/mod.rs @@ -0,0 +1,49 @@ +// Copyright 2022 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +pub use and_then::*; +pub use fee::*; +pub use input_refs::*; +pub use template_exists::*; + +mod and_then; +mod fee; +mod input_refs; +mod template_exists; + +use async_trait::async_trait; + +use crate::p2p::services::mempool::MempoolError; + +#[async_trait] +pub trait Validator { + type Error; + + async fn validate(&self, input: &T) -> Result<(), Self::Error>; + + fn boxed(self) -> BoxedValidator + where Self: Sized + Send + Sync + 'static { + BoxedValidator { inner: Box::new(self) } + } + + fn and_then(self, other: V) -> AndThen + where + V: Validator, + Self: Sized, + { + AndThen::new(self, other) + } +} + +pub struct BoxedValidator { + inner: Box + Send + Sync + 'static>, +} + +#[async_trait] +impl Validator for BoxedValidator { + type Error = E; + + async fn validate(&self, input: &T) -> Result<(), Self::Error> { + self.inner.validate(input).await + } +} diff --git a/applications/tari_validator_node/src/p2p/services/mempool/validator.rs b/applications/tari_validator_node/src/p2p/services/mempool/validator/template_exists.rs similarity index 55% rename from applications/tari_validator_node/src/p2p/services/mempool/validator.rs rename to applications/tari_validator_node/src/p2p/services/mempool/validator/template_exists.rs index f82ca32734..500af4d825 100644 --- a/applications/tari_validator_node/src/p2p/services/mempool/validator.rs +++ b/applications/tari_validator_node/src/p2p/services/mempool/validator/template_exists.rs @@ -1,45 +1,12 @@ -// Copyright 2022 The Tari Project -// SPDX-License-Identifier: BSD-3-Clause - use async_trait::async_trait; use tari_dan_app_utilities::template_manager::{implementation::TemplateManager, interface::TemplateManagerError}; use tari_engine_types::instruction::Instruction; use tari_transaction::Transaction; -use crate::p2p::services::mempool::{AndThen, MempoolError}; - -#[async_trait] -pub trait Validator { - type Error; - - async fn validate(&self, input: &T) -> Result<(), Self::Error>; - - fn boxed(self) -> BoxedValidator - where Self: Sized + Send + Sync + 'static { - BoxedValidator { inner: Box::new(self) } - } - - fn and_then(self, other: V) -> AndThen - where - V: Validator, - Self: Sized, - { - AndThen::new(self, other) - } -} - -pub struct BoxedValidator { - inner: Box + Send + Sync + 'static>, -} - -#[async_trait] -impl Validator for BoxedValidator { - type Error = E; +use crate::p2p::services::mempool::{MempoolError, Validator}; - async fn validate(&self, input: &T) -> Result<(), Self::Error> { - self.inner.validate(input).await - } -} +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause #[derive(Debug)] pub struct TemplateExistsValidator { @@ -81,18 +48,3 @@ impl Validator for TemplateExistsValidator { Ok(()) } } - -#[derive(Debug)] -pub struct FeeTransactionValidator; - -#[async_trait] -impl Validator for FeeTransactionValidator { - type Error = MempoolError; - - async fn validate(&self, transaction: &Transaction) -> Result<(), MempoolError> { - if transaction.fee_instructions().is_empty() { - return Err(MempoolError::NoFeeInstructions); - } - Ok(()) - } -} diff --git a/clients/base_node_client/Cargo.toml b/clients/base_node_client/Cargo.toml index ec3bff8802..906b6814d5 100644 --- a/clients/base_node_client/Cargo.toml +++ b/clients/base_node_client/Cargo.toml @@ -13,6 +13,7 @@ tari_core = { git = "https://github.com/tari-project/tari.git", tag = "v0.51.0-p tari_utilities = "0.4" tari_dan_common_types = { path = "../../dan_layer/common_types" } +tari_engine_types = { path = "../../dan_layer/engine_types" } async-trait = "0.1" log = "0.4.8" diff --git a/dan_layer/common_types/src/committee.rs b/dan_layer/common_types/src/committee.rs index f6bbc5bcc8..69bc9614f4 100644 --- a/dan_layer/common_types/src/committee.rs +++ b/dan_layer/common_types/src/committee.rs @@ -156,4 +156,13 @@ impl CommitteeShard { .into_iter() .filter(|shard_id| self.includes_shard(shard_id.borrow())) } + + /// Calculates the number of distinct buckets for a given shard set + pub fn count_distinct_buckets<'a, I: IntoIterator>(&self, shards: I) -> usize { + shards + .into_iter() + .map(|shard| shard.to_committee_bucket(self.num_committees)) + .collect::>() + .len() + } } diff --git a/dan_layer/consensus/src/hotstuff/common.rs b/dan_layer/consensus/src/hotstuff/common.rs index 5be7344f22..88975fa25c 100644 --- a/dan_layer/consensus/src/hotstuff/common.rs +++ b/dan_layer/consensus/src/hotstuff/common.rs @@ -14,6 +14,11 @@ use crate::hotstuff::error::HotStuffError; const LOG_TARGET: &str = "tari::dan::consensus::hotstuff"; +/// The value that fees are divided by to determine the amount of fees to burn. 0 means no fees are burned. +/// This is a placeholder for the fee exhaust consensus constant so that we know where it's used later. +/// TODO: exhaust > 0 +pub const EXHAUST_DIVISOR: u64 = 0; + pub fn update_high_qc(tx: &mut TTx, qc: &QuorumCertificate) -> Result<(), HotStuffError> where TTx: StateStoreWriteTransaction + DerefMut, diff --git a/dan_layer/consensus/src/hotstuff/on_propose.rs b/dan_layer/consensus/src/hotstuff/on_propose.rs index debc3ea730..1bf43903e2 100644 --- a/dan_layer/consensus/src/hotstuff/on_propose.rs +++ b/dan_layer/consensus/src/hotstuff/on_propose.rs @@ -8,7 +8,13 @@ use std::{ }; use log::*; -use tari_dan_common_types::{committee::Committee, optional::Optional, Epoch, NodeHeight, ShardId}; +use tari_dan_common_types::{ + committee::{Committee, CommitteeShard}, + optional::Optional, + Epoch, + NodeHeight, + ShardId, +}; use tari_dan_storage::{ consensus_models::{ Block, @@ -28,7 +34,7 @@ use tari_epoch_manager::EpochManagerReader; use tokio::sync::mpsc; use crate::{ - hotstuff::error::HotStuffError, + hotstuff::{common::EXHAUST_DIVISOR, error::HotStuffError}, messages::{HotstuffMessage, ProposalMessage}, traits::ConsensusSpec, }; @@ -77,6 +83,7 @@ where TConsensusSpec: ConsensusSpec } let validator = self.epoch_manager.get_our_validator_node(epoch).await?; + let local_committee_shard = self.epoch_manager.get_local_committee_shard(epoch).await?; let num_committees = self.epoch_manager.get_num_committees(epoch).await?; let local_bucket = validator.shard_key.to_committee_bucket(num_committees); // The scope here is due to a shortcoming of rust. The tx is dropped at tx.commit() but it still complains that @@ -90,7 +97,14 @@ where TConsensusSpec: ConsensusSpec let parent_block = leaf_block.get_block(&mut *tx)?; - next_block = self.build_next_block(&mut tx, epoch, &parent_block, high_qc, validator.shard_key)?; + next_block = self.build_next_block( + &mut tx, + epoch, + &parent_block, + high_qc, + validator.shard_key, + &local_committee_shard, + )?; next_block.insert(&mut tx)?; next_block.as_last_proposed().set(&mut tx)?; @@ -183,6 +197,7 @@ where TConsensusSpec: ConsensusSpec parent_block: &Block, high_qc: QuorumCertificate, proposed_by: ShardId, + local_committee_shard: &CommitteeShard, ) -> Result { // TODO: Configure const TARGET_BLOCK_SIZE: usize = 1000; @@ -199,7 +214,11 @@ where TConsensusSpec: ConsensusSpec // The transaction is LocalPrepared, meaning that we know that all foreign and local nodes have // prepared. We can now propose to Accept it. We also propose the decision change which everyone should // agree with if they received the same foreign LocalPrepare. - TransactionPoolStage::LocalPrepared => Command::Accept(t.get_transaction_atom_with_decision_change()), + TransactionPoolStage::LocalPrepared => { + let involved = local_committee_shard.count_distinct_buckets(t.transaction.evidence.shards_iter()); + let leader_fee = t.calculate_leader_fee(involved as u64, EXHAUST_DIVISOR); + Command::Accept(t.get_final_transaction_atom(leader_fee)) + }, // Not reachable as there is nothing to propose for these stages. To confirm that all local nodes agreed // with the Accept, more (possibly empty) blocks with QCs will be proposed and accepted, // otherwise the Accept block will not be committed. diff --git a/dan_layer/consensus/src/hotstuff/on_receive_proposal.rs b/dan_layer/consensus/src/hotstuff/on_receive_proposal.rs index f5a4cffe0c..222fa50974 100644 --- a/dan_layer/consensus/src/hotstuff/on_receive_proposal.rs +++ b/dan_layer/consensus/src/hotstuff/on_receive_proposal.rs @@ -29,6 +29,7 @@ use tari_dan_storage::{ TransactionPool, TransactionPoolStage, TransactionRecord, + ValidatorFee, }, StateStore, StateStoreReadTransaction, @@ -40,7 +41,7 @@ use tokio::sync::{broadcast, mpsc}; use crate::{ hotstuff::{ - common::update_high_qc, + common::{update_high_qc, EXHAUST_DIVISOR}, error::HotStuffError, event::HotstuffEvent, on_beat::OnBeat, @@ -131,7 +132,7 @@ where TConsensusSpec: ConsensusSpec ) -> Result<(), HotStuffError> { // First save the block in one db transaction self.store.with_write_tx(|tx| { - self.validate_local_proposed_block(&mut *tx, &from, &block)?; + self.validate_local_proposed_block(&mut *tx, &from, &block, &local_committee)?; // Insert the block if it doesnt already exist block.justify().save(tx)?; block.save(tx)?; @@ -200,6 +201,12 @@ where TConsensusSpec: ConsensusSpec pub async fn reprocess_block(&self, block_id: &BlockId) -> Result<(), HotStuffError> { let block = self.store.with_read_tx(|tx| Block::get(tx, block_id))?; + if !self.epoch_manager.is_epoch_active(block.epoch()).await? { + return Err(HotStuffError::EpochNotActive { + epoch: block.epoch(), + details: "Cannot reprocess block from inactive epoch".to_string(), + }); + } let local_committee = self.epoch_manager.get_local_committee(block.epoch()).await?; self.process_block(&local_committee, &block).await } @@ -370,6 +377,18 @@ where TConsensusSpec: ConsensusSpec ); return Ok(None); } + + if tx_rec.transaction.transaction_fee != t.transaction_fee { + warn!( + target: LOG_TARGET, + "❌ Accept transaction fee disagreement for block {}. Leader proposed {}, we calculated {}", + block.id(), + t.transaction_fee, + tx_rec.transaction.transaction_fee + ); + return Ok(None); + } + if tx_rec.original_decision() == t.decision { if tx_rec.original_decision().is_commit() { let transaction = ExecutedTransaction::get(tx.deref_mut(), cmd.transaction_id())?; @@ -426,6 +445,17 @@ where TConsensusSpec: ConsensusSpec return Ok(None); } + if tx_rec.transaction.transaction_fee != t.transaction_fee { + warn!( + target: LOG_TARGET, + "❌ Accept transaction fee disagreement for block {}. Leader proposed {}, we calculated {}", + block.id(), + t.transaction_fee, + tx_rec.transaction.transaction_fee + ); + return Ok(None); + } + tx_rec.transition( tx, TransactionPoolStage::LocalPrepared, @@ -465,6 +495,31 @@ where TConsensusSpec: ConsensusSpec ); return Ok(None); } + + if tx_rec.transaction.transaction_fee != t.transaction_fee { + warn!( + target: LOG_TARGET, + "❌ Accept transaction fee disagreement for block {}. Leader proposed {}, we calculated {}", + block.id(), + t.transaction_fee, + tx_rec.transaction.transaction_fee + ); + return Ok(None); + } + + let distinct_shards = + local_committee_shard.count_distinct_buckets(tx_rec.transaction.evidence.shards_iter()); + let calculated_leader_fee = tx_rec.calculate_leader_fee(distinct_shards as u64, EXHAUST_DIVISOR); + if calculated_leader_fee != t.leader_fee { + warn!( + target: LOG_TARGET, + "❌ Accept leader fee disagreement for block {}. Leader proposed {}, we calculated {}", + block.id(), + t.leader_fee, + calculated_leader_fee + ); + return Ok(None); + } // If the decision was changed to Abort, which can only happen when a foreign shard decides ABORT // and we decide COMMIT, we set SomePrepared, otherwise AllPrepared. These are // the last stages. @@ -648,6 +703,8 @@ where TConsensusSpec: ConsensusSpec block: &Block, local_committee_shard: &CommitteeShard, ) -> Result<(), HotStuffError> { + let mut total_transaction_fee = 0; + let mut total_fee_due = 0; for cmd in block.commands() { let tx_rec = self.transaction_pool.get(tx, cmd.transaction_id())?; match cmd { @@ -660,6 +717,10 @@ where TConsensusSpec: ConsensusSpec target: LOG_TARGET, "Transaction {} is finalized ({})", tx_rec.transaction.id, t.decision ); + + total_transaction_fee += tx_rec.transaction.transaction_fee; + total_fee_due += t.leader_fee; + let mut executed = t.get_transaction(tx.deref_mut())?; match t.decision { // Commit the transaction substate changes. @@ -685,6 +746,17 @@ where TConsensusSpec: ConsensusSpec } } + if total_fee_due > 0 { + ValidatorFee { + validator_public_key: *block.proposed_by(), + epoch: block.epoch(), + block_id: *block.id(), + total_fee_due, + total_transaction_fee, + } + .create(tx)?; + } + Ok(()) } @@ -693,6 +765,7 @@ where TConsensusSpec: ConsensusSpec tx: &mut ::ReadTransaction<'_>, from: &TConsensusSpec::Addr, candidate_block: &Block, + local_committee: &Committee, ) -> Result<(), ProposalValidationError> { self.validate_proposed_block(from, candidate_block)?; diff --git a/dan_layer/consensus/src/hotstuff/worker.rs b/dan_layer/consensus/src/hotstuff/worker.rs index afa541f5d3..45cbb62181 100644 --- a/dan_layer/consensus/src/hotstuff/worker.rs +++ b/dan_layer/consensus/src/hotstuff/worker.rs @@ -216,12 +216,14 @@ where id: *executed.transaction().id(), decision, evidence: executed.to_initial_evidence(), - fee: executed + transaction_fee: executed .result() .fee_receipt .as_ref() .and_then(|f| f.total_fee_payment.as_u64_checked()) .unwrap_or(0), + // We calculate the leader fee later depending on the epoch of the block + leader_fee: 0, })?; Ok::<_, HotStuffError>(()) diff --git a/dan_layer/consensus_tests/src/consensus.rs b/dan_layer/consensus_tests/src/consensus.rs index b0c14abc42..faf4d3a533 100644 --- a/dan_layer/consensus_tests/src/consensus.rs +++ b/dan_layer/consensus_tests/src/consensus.rs @@ -174,14 +174,18 @@ async fn multi_validator_propose_blocks_with_new_transactions_until_all_committe #[tokio::test(flavor = "multi_thread")] async fn multi_shard_propose_blocks_with_new_transactions_until_all_committed() { + for i in 1..=9 { + std::fs::remove_file(format!("/tmp/test{}.db", i)).ok(); + } let mut test = Test::builder() + .with_sql_url("sqlite:///tmp/test{}.db") .add_committee(0, vec!["1", "2", "3"]) .add_committee(1, vec!["4", "5", "6"]) .add_committee(2, vec!["7", "8", "9"]) .start() .await; for _ in 0..20 { - test.send_transaction_to_all(Decision::Commit, 1, 5).await; + test.send_transaction_to_all(Decision::Commit, 100, 5).await; } test.wait_all_have_at_least_n_new_transactions_in_pool(20).await; diff --git a/dan_layer/engine/src/runtime/consensus.rs b/dan_layer/engine/src/runtime/consensus.rs index 8eeba40cfc..1aa2e3ec5b 100644 --- a/dan_layer/engine/src/runtime/consensus.rs +++ b/dan_layer/engine/src/runtime/consensus.rs @@ -20,7 +20,14 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use std::collections::HashMap; + +use tari_template_lib::{crypto::RistrettoPublicKeyBytes, models::Amount}; + #[derive(Debug, Clone)] pub struct ConsensusContext { pub current_epoch: u64, + /// Epoch -> Claimable Fee Amount + pub permitted_fee_claims: HashMap, + pub signer: RistrettoPublicKeyBytes, } diff --git a/dan_layer/engine/src/runtime/error.rs b/dan_layer/engine/src/runtime/error.rs index 4104df089b..4f279e2b19 100644 --- a/dan_layer/engine/src/runtime/error.rs +++ b/dan_layer/engine/src/runtime/error.rs @@ -26,6 +26,7 @@ use anyhow::anyhow; use tari_bor::BorError; use tari_dan_common_types::optional::IsNotFoundError; use tari_engine_types::{ + fee_claim::FeeClaimAddress, resource_container::ResourceError, substate::SubstateAddress, transaction_receipt::TransactionReceiptAddress, @@ -147,6 +148,8 @@ pub enum RuntimeError { method: String, details: String, }, + #[error("Fee claim not permitted: {fee_address}")] + FeeClaimNotPermitted { fee_address: FeeClaimAddress }, } impl RuntimeError { diff --git a/dan_layer/engine/src/runtime/impl.rs b/dan_layer/engine/src/runtime/impl.rs index 959b3f5229..3fd474a0e8 100644 --- a/dan_layer/engine/src/runtime/impl.rs +++ b/dan_layer/engine/src/runtime/impl.rs @@ -27,11 +27,12 @@ use std::{ use log::warn; use tari_bor::encode; +use tari_common_types::types::PublicKey; use tari_crypto::{ range_proof::RangeProofService, ristretto::{RistrettoPublicKey, RistrettoSecretKey}, }; -use tari_dan_common_types::services::template_provider::TemplateProvider; +use tari_dan_common_types::{services::template_provider::TemplateProvider, Epoch}; use tari_engine_types::{ base_layer_hashing::ownership_proof_hasher, commit_result::FinalizeResult, @@ -923,6 +924,13 @@ impl> RuntimeInte Ok(()) } + fn claim_validator_fees(&self, epoch: Epoch, validator_public_key: &PublicKey) -> Result<(), RuntimeError> { + let resource = self.tracker.claim_fee(epoch, validator_public_key)?; + let bucket_id = self.tracker.new_bucket(resource)?; + self.tracker.set_last_instruction_output(Some(encode(&bucket_id)?)); + Ok(()) + } + fn create_free_test_coins( &self, revealed_amount: Amount, diff --git a/dan_layer/engine/src/runtime/mod.rs b/dan_layer/engine/src/runtime/mod.rs index 3cf1c53b6a..ff8f6a81a7 100644 --- a/dan_layer/engine/src/runtime/mod.rs +++ b/dan_layer/engine/src/runtime/mod.rs @@ -48,6 +48,8 @@ mod workspace; use std::{fmt::Debug, sync::Arc}; +use tari_common_types::types::PublicKey; +use tari_dan_common_types::Epoch; use tari_engine_types::{ component::ComponentHeader, confidential::{ConfidentialClaim, ConfidentialOutput}, @@ -132,6 +134,8 @@ pub trait RuntimeInterface: Send + Sync { fn claim_burn(&self, claim: ConfidentialClaim) -> Result<(), RuntimeError>; + fn claim_validator_fees(&self, epoch: Epoch, validator_public_key: &PublicKey) -> Result<(), RuntimeError>; + fn create_free_test_coins( &self, revealed_amount: Amount, diff --git a/dan_layer/engine/src/runtime/tracker.rs b/dan_layer/engine/src/runtime/tracker.rs index 284cba1167..1e4cc63b99 100644 --- a/dan_layer/engine/src/runtime/tracker.rs +++ b/dan_layer/engine/src/runtime/tracker.rs @@ -29,14 +29,22 @@ use std::{ }; use log::debug; -use tari_dan_common_types::{optional::Optional, services::template_provider::TemplateProvider}; +use tari_common_types::types::PublicKey; +use tari_dan_common_types::{ + optional::Optional, + services::template_provider::TemplateProvider, + Epoch, + NodeAddressable, +}; use tari_engine_types::{ bucket::Bucket, commit_result::{RejectReason, TransactionResult}, component::{ComponentBody, ComponentHeader}, confidential::UnclaimedConfidentialOutput, events::Event, + fee_claim::{FeeClaim, FeeClaimAddress}, fees::{FeeReceipt, FeeSource}, + hashing::{hasher, EngineHashDomainLabel}, logs::LogEntry, non_fungible::NonFungibleContainer, non_fungible_index::NonFungibleIndex, @@ -52,6 +60,7 @@ use tari_template_lib::{ args::MintArg, auth::AccessRules, constants::CONFIDENTIAL_TARI_RESOURCE_ADDRESS, + crypto::RistrettoPublicKeyBytes, models::{ Amount, BucketId, @@ -106,9 +115,10 @@ impl> StateTracke state_store: MemoryStateStore, id_provider: IdProvider, template_provider: Arc, + permitted_fee_claims: HashMap, ) -> Self { Self { - working_state: Arc::new(RwLock::new(WorkingState::new(state_store))), + working_state: Arc::new(RwLock::new(WorkingState::new(state_store, permitted_fee_claims))), fee_state: Arc::new(RwLock::new(FeeState::new())), id_provider, template_provider, @@ -386,6 +396,29 @@ impl> StateTracke }) } + pub fn claim_fee(&self, epoch: Epoch, claimant_public_key: &PublicKey) -> Result { + self.write_with(|state| { + let hash = hasher(EngineHashDomainLabel::FeeClaimAddress) + .chain(&epoch) + .chain(claimant_public_key) + .result(); + let addr = FeeClaimAddress::new(hash); + let amount = state.take_fee_claim(&addr)?; + let validator_public_key = RistrettoPublicKeyBytes::from_bytes(claimant_public_key.as_bytes()).unwrap(); + + state.new_fee_claims.insert(addr, FeeClaim { + epoch: epoch.as_u64(), + validator_public_key, + amount, + }); + Ok(ResourceContainer::confidential( + *CONFIDENTIAL_TARI_RESOURCE_ADDRESS, + None, + amount, + )) + }) + } + pub fn new_component( &self, state: Vec, @@ -710,6 +743,11 @@ impl> StateTracke up_states.insert(addr, substate.into()); } + for (address, substate) in state.new_fee_claims.drain() { + let addr = SubstateAddress::FeeClaim(address); + up_states.insert(addr, substate.into()); + } + let events = state.events.clone(); let logs = state.logs.clone(); diff --git a/dan_layer/engine/src/runtime/working_state.rs b/dan_layer/engine/src/runtime/working_state.rs index 549ab5a848..575bb672c0 100644 --- a/dan_layer/engine/src/runtime/working_state.rs +++ b/dan_layer/engine/src/runtime/working_state.rs @@ -9,6 +9,7 @@ use tari_engine_types::{ component::ComponentHeader, confidential::UnclaimedConfidentialOutput, events::Event, + fee_claim::{FeeClaim, FeeClaimAddress}, logs::LogEntry, non_fungible::NonFungibleContainer, non_fungible_index::NonFungibleIndex, @@ -17,6 +18,7 @@ use tari_engine_types::{ vault::Vault, }; use tari_template_lib::models::{ + Amount, BucketId, ComponentAddress, NonFungibleAddress, @@ -44,6 +46,8 @@ pub(super) struct WorkingState { pub new_non_fungibles: HashMap, pub new_non_fungible_indexes: HashMap, pub claimed_confidential_outputs: Vec, + pub new_fee_claims: HashMap, + pub permitted_fee_claims: HashMap, pub runtime_state: Option, pub last_instruction_output: Option>, @@ -52,7 +56,7 @@ pub(super) struct WorkingState { } impl WorkingState { - pub fn new(state_store: MemoryStateStore) -> Self { + pub fn new(state_store: MemoryStateStore, permitted_fee_claims: HashMap) -> Self { Self { events: Vec::new(), logs: Vec::new(), @@ -67,6 +71,7 @@ impl WorkingState { last_instruction_output: None, workspace: Workspace::default(), state_store, + permitted_fee_claims, } } @@ -269,6 +274,14 @@ impl WorkingState { .ok_or(RuntimeError::BucketNotFound { bucket_id }) } + pub fn take_fee_claim(&mut self, fee_claim_addr: &FeeClaimAddress) -> Result { + self.permitted_fee_claims + .remove(fee_claim_addr) + .ok_or(RuntimeError::FeeClaimNotPermitted { + fee_address: *fee_claim_addr, + }) + } + pub(super) fn validate_finalized(&self) -> Result<(), TransactionCommitError> { if !self.buckets.is_empty() { return Err(TransactionCommitError::DanglingBuckets { diff --git a/dan_layer/engine/src/transaction/processor.rs b/dan_layer/engine/src/transaction/processor.rs index 5d684103f5..76b967e841 100644 --- a/dan_layer/engine/src/transaction/processor.rs +++ b/dan_layer/engine/src/transaction/processor.rs @@ -24,7 +24,7 @@ use std::sync::Arc; use log::*; use tari_bor::encode; -use tari_dan_common_types::services::template_provider::TemplateProvider; +use tari_dan_common_types::{services::template_provider::TemplateProvider, Epoch}; use tari_engine_types::{ commit_result::{ExecuteResult, FinalizeResult, RejectReason}, indexed_value::IndexedValue, @@ -93,7 +93,12 @@ impl + 'static> T pub fn execute(self, transaction: Transaction) -> Result { let id_provider = IdProvider::new(transaction.hash(), 1000); // TODO: We can avoid this for each execution with improved design - let tracker = StateTracker::new(self.state_db.clone(), id_provider, self.template_provider.clone()); + let tracker = StateTracker::new( + self.state_db.clone(), + id_provider, + self.template_provider.clone(), + self.consensus.permitted_fee_claims.clone(), + ); let initial_proofs = self.auth_params.initial_ownership_proofs.clone(); let template_provider = self.template_provider.clone(); let runtime_interface = RuntimeInterfaceImpl::initialize( @@ -260,6 +265,15 @@ impl + 'static> T runtime.interface().claim_burn(*claim)?; Ok(InstructionResult::empty()) }, + Instruction::ClaimValidatorFees { + epoch, + validator_public_key, + } => { + runtime + .interface() + .claim_validator_fees(Epoch(epoch), &validator_public_key)?; + Ok(InstructionResult::empty()) + }, Instruction::CreateFreeTestCoins { revealed_amount: amount, output, diff --git a/dan_layer/engine_types/src/argument_parser.rs b/dan_layer/engine_types/src/argument_parser.rs index a520675aef..ebf6af131e 100644 --- a/dan_layer/engine_types/src/argument_parser.rs +++ b/dan_layer/engine_types/src/argument_parser.rs @@ -112,6 +112,7 @@ impl From> for Arg { SubstateAddress::NonFungible(v) => arg!(v), SubstateAddress::NonFungibleIndex(v) => arg!(v), SubstateAddress::TransactionReceipt(v) => arg!(v), + SubstateAddress::FeeClaim(v) => arg!(v), }, StringArg::UnsignedInteger(v) => arg!(v), StringArg::SignedInteger(v) => arg!(v), diff --git a/dan_layer/engine_types/src/fee_claim.rs b/dan_layer/engine_types/src/fee_claim.rs new file mode 100644 index 0000000000..4c88561393 --- /dev/null +++ b/dan_layer/engine_types/src/fee_claim.rs @@ -0,0 +1,45 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use std::{fmt, fmt::Display}; + +use serde::{Deserialize, Serialize}; +use tari_bor::BorTag; +use tari_template_lib::{crypto::RistrettoPublicKeyBytes, models::BinaryTag, prelude::Amount, Hash}; + +use crate::serde_with; + +const TAG: u64 = BinaryTag::FeeClaim.as_u64(); + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct FeeClaimAddress(BorTag); + +impl FeeClaimAddress { + pub const fn new(address: Hash) -> Self { + Self(BorTag::new(address)) + } + + pub fn hash(&self) -> &Hash { + self.0.inner() + } +} + +impl> From for FeeClaimAddress { + fn from(address: T) -> Self { + Self::new(address.into()) + } +} + +impl Display for FeeClaimAddress { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "feeclaim_{}", self.hash()) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct FeeClaim { + pub epoch: u64, + #[serde(with = "serde_with::hex")] + pub validator_public_key: RistrettoPublicKeyBytes, + pub amount: Amount, +} diff --git a/dan_layer/engine_types/src/hashing.rs b/dan_layer/engine_types/src/hashing.rs index f8729f1134..d917998b7f 100644 --- a/dan_layer/engine_types/src/hashing.rs +++ b/dan_layer/engine_types/src/hashing.rs @@ -112,6 +112,7 @@ pub enum EngineHashDomainLabel { ComponentAddress, RandomBytes, TransactionReceipt, + FeeClaimAddress, QuorumCertificate, } @@ -134,6 +135,7 @@ impl EngineHashDomainLabel { Self::ComponentAddress => "ComponentAddress", Self::RandomBytes => "RandomBytes", Self::TransactionReceipt => "TransactionReceipt", + Self::FeeClaimAddress => "FeeClaimAddress", Self::QuorumCertificate => "QuorumCertificate", } } diff --git a/dan_layer/engine_types/src/indexed_value.rs b/dan_layer/engine_types/src/indexed_value.rs index 10e0211137..4c68dac4db 100644 --- a/dan_layer/engine_types/src/indexed_value.rs +++ b/dan_layer/engine_types/src/indexed_value.rs @@ -11,7 +11,12 @@ use tari_template_lib::{ Hash, }; -use crate::{serde_with, substate::SubstateAddress, transaction_receipt::TransactionReceiptAddress}; +use crate::{ + fee_claim::FeeClaimAddress, + serde_with, + substate::SubstateAddress, + transaction_receipt::TransactionReceiptAddress, +}; #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] pub struct IndexedValue { @@ -55,6 +60,7 @@ impl IndexedValue { SubstateAddress::UnclaimedConfidentialOutput(_) => false, // TODO: should we index this value? SubstateAddress::NonFungibleIndex(_) => false, + SubstateAddress::FeeClaim(_) => false, } } @@ -100,6 +106,7 @@ pub enum TariValue { BucketId(BucketId), Metadata(Metadata), VaultId(VaultId), + FeeClaim(FeeClaimAddress), } impl FromTagAndValue for TariValue { @@ -137,6 +144,10 @@ impl FromTagAndValue for TariValue { let vault_id: Hash = value.deserialized().map_err(BorError::from)?; Ok(Self::VaultId(vault_id.into())) }, + BinaryTag::FeeClaim => { + let value: Hash = value.deserialized().map_err(BorError::from)?; + Ok(Self::FeeClaim(value.into())) + }, } } } @@ -192,6 +203,9 @@ impl ValueVisitor for IndexedValueVisitor { TariValue::Metadata(metadata) => { self.metadata.push(metadata); }, + TariValue::FeeClaim(_) => { + // Do nothing + }, } Ok(()) } diff --git a/dan_layer/engine_types/src/instruction.rs b/dan_layer/engine_types/src/instruction.rs index 224bc4c624..d0d39d2c45 100644 --- a/dan_layer/engine_types/src/instruction.rs +++ b/dan_layer/engine_types/src/instruction.rs @@ -4,10 +4,12 @@ use std::fmt::{Display, Formatter}; use serde::{Deserialize, Serialize}; +use tari_common_types::types::PublicKey; use tari_template_lib::{ args::{Arg, LogLevel}, models::{Amount, ComponentAddress, TemplateAddress}, }; +use tari_utilities::hex::Hex; use crate::{ confidential::{ConfidentialClaim, ConfidentialOutput}, @@ -40,6 +42,10 @@ pub enum Instruction { ClaimBurn { claim: Box, }, + ClaimValidatorFees { + epoch: u64, + validator_public_key: PublicKey, + }, #[cfg(feature = "debugging")] CreateFreeTestCoins { revealed_amount: Amount, @@ -77,8 +83,21 @@ impl Display for Instruction { Self::ClaimBurn { claim } => { write!( f, - "ClaimBurn {{ commitment_address: {}, proof_of_knowledge: {:?} }}", - claim.output_address, claim.proof_of_knowledge + "ClaimBurn {{ commitment_address: {}, proof_of_knowledge: nonce({}), u({}) v({}) }}", + claim.output_address, + claim.proof_of_knowledge.public_nonce().to_hex(), + claim.proof_of_knowledge.u().to_hex(), + claim.proof_of_knowledge.v().to_hex() + ) + }, + Self::ClaimValidatorFees { + epoch, + validator_public_key, + } => { + write!( + f, + "ClaimValidatorFees {{ epoch: {}, validator_public_key: {:.5} }}", + epoch, validator_public_key ) }, Self::CreateFreeTestCoins { .. } => { diff --git a/dan_layer/engine_types/src/lib.rs b/dan_layer/engine_types/src/lib.rs index 6c97d6c345..99846d0acd 100644 --- a/dan_layer/engine_types/src/lib.rs +++ b/dan_layer/engine_types/src/lib.rs @@ -8,6 +8,7 @@ pub mod commit_result; pub mod component; pub mod confidential; pub mod events; +pub mod fee_claim; pub mod fees; pub mod hashing; pub mod indexed_value; diff --git a/dan_layer/engine_types/src/serde_with/hex.rs b/dan_layer/engine_types/src/serde_with/hex.rs index bdf43f9a89..b0d40f5442 100644 --- a/dan_layer/engine_types/src/serde_with/hex.rs +++ b/dan_layer/engine_types/src/serde_with/hex.rs @@ -36,7 +36,7 @@ pub fn serialize>(v: &T, s: S) -> Result(d: D) -> Result where D: Deserializer<'de>, - T: TryFrom>, + T: for<'a> TryFrom<&'a [u8]>, { let bytes = if d.is_human_readable() { let hex = ::deserialize(d)?; @@ -45,7 +45,7 @@ where as Deserialize>::deserialize(d)? }; - let value = T::try_from(bytes).map_err(|_| serde::de::Error::custom("Failed to convert bytes to T"))?; + let value = T::try_from(&bytes).map_err(|_| serde::de::Error::custom("Failed to convert bytes to T"))?; Ok(value) } diff --git a/dan_layer/engine_types/src/substate.rs b/dan_layer/engine_types/src/substate.rs index 1fa9dd2eac..4a4505b3c1 100644 --- a/dan_layer/engine_types/src/substate.rs +++ b/dan_layer/engine_types/src/substate.rs @@ -43,6 +43,7 @@ use tari_template_lib::{ use crate::{ component::ComponentHeader, confidential::UnclaimedConfidentialOutput, + fee_claim::{FeeClaim, FeeClaimAddress}, hashing::{hasher, EngineHashDomainLabel}, non_fungible::NonFungibleContainer, non_fungible_index::NonFungibleIndex, @@ -97,6 +98,7 @@ pub enum SubstateAddress { NonFungible(NonFungibleAddress), NonFungibleIndex(NonFungibleIndexAddress), TransactionReceipt(TransactionReceiptAddress), + FeeClaim(FeeClaimAddress), } impl SubstateAddress { @@ -143,6 +145,7 @@ impl SubstateAddress { .chain(&address.index()) .result(), SubstateAddress::TransactionReceipt(address) => *address.hash(), + SubstateAddress::FeeClaim(address) => *address.hash(), } } @@ -244,6 +247,7 @@ impl Display for SubstateAddress { SubstateAddress::NonFungibleIndex(addr) => write!(f, "{}", addr), SubstateAddress::UnclaimedConfidentialOutput(commitment_address) => write!(f, "{}", commitment_address), SubstateAddress::TransactionReceipt(addr) => write!(f, "{}", addr), + SubstateAddress::FeeClaim(addr) => write!(f, "{}", addr), } } } @@ -345,6 +349,7 @@ pub enum SubstateValue { NonFungibleIndex(NonFungibleIndex), UnclaimedConfidentialOutput(UnclaimedConfidentialOutput), TransactionReceipt(TransactionReceipt), + FeeClaim(FeeClaim), } impl SubstateValue { diff --git a/dan_layer/state_store_sqlite/migrations/2023-06-08-091819_create_state_store/up.sql b/dan_layer/state_store_sqlite/migrations/2023-06-08-091819_create_state_store/up.sql index e95259e970..d2bea02190 100644 --- a/dan_layer/state_store_sqlite/migrations/2023-06-08-091819_create_state_store/up.sql +++ b/dan_layer/state_store_sqlite/migrations/2023-06-08-091819_create_state_store/up.sql @@ -125,7 +125,7 @@ create table transactions filled_outputs text not NULL, result text NULL, execution_time_ms bigint NULL, - final_decision text NULL, + final_decision text NULL, created_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ); @@ -139,7 +139,8 @@ create table transaction_pool original_decision text not null, pending_decision text null, evidence text not null, - fee bigint not null, + transaction_fee bigint not null, + leader_fee bigint not null, stage text not null, is_ready boolean not null, updated_at timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, @@ -172,8 +173,19 @@ CREATE TABLE block_missing_txs CREATE TABLE missing_tx ( - id integer not NULL primary key AUTOINCREMENT, - transaction_id text not NULL, - block_id text not NULL, - created_at timestamp not NULL DEFAULT CURRENT_TIMESTAMP + id integer not NULL primary key AUTOINCREMENT, + transaction_id text not NULL, + block_id text not NULL, + created_at timestamp not NULL DEFAULT CURRENT_TIMESTAMP +); + +CREATE TABLE validator_fees +( + id integer not NULL primary key AUTOINCREMENT, + validator_addr text not NULL, + epoch bigint not NULL, + block_id text not NULL, + total_transaction_fee bigint not NULL, + total_fee_due bigint not NULL, + created_at timestamp not NULL DEFAULT CURRENT_TIMESTAMP ); diff --git a/dan_layer/state_store_sqlite/src/reader.rs b/dan_layer/state_store_sqlite/src/reader.rs index 9c3dd8252b..585f838671 100644 --- a/dan_layer/state_store_sqlite/src/reader.rs +++ b/dan_layer/state_store_sqlite/src/reader.rs @@ -14,7 +14,7 @@ use diesel::{ RunQueryDsl, SqliteConnection, }; -use tari_common_types::types::FixedHash; +use tari_common_types::types::{FixedHash, PublicKey}; use tari_dan_common_types::{Epoch, ShardId}; use tari_dan_storage::{ consensus_models::{ @@ -593,6 +593,27 @@ impl StateStoreReadTransaction for SqliteStateStoreReadTransaction<'_> { substates.into_iter().map(TryInto::try_into).collect() } + + fn validator_fees_get_total_fee_for_epoch( + &mut self, + epoch: Epoch, + validator_public_key: &PublicKey, + ) -> Result { + use crate::schema::validator_fees; + + let total_fee = validator_fees::table + .filter(validator_fees::epoch.eq(epoch as i64)) + .filter(validator_fees::validator_addr.eq(serialize_hex(validator_public_key))) + .select(diesel::dsl::sum(validator_fees::total_fee_due)) + .first::>(self.connection()) + .map_err(|e| SqliteStorageError::DieselError { + operation: "validator_fees_get_total_fee_for_epoch", + source: e, + })? + .unwrap_or(0); + + Ok(total_fee as u64) + } } #[derive(QueryableByName)] diff --git a/dan_layer/state_store_sqlite/src/schema.rs b/dan_layer/state_store_sqlite/src/schema.rs index 29d7ccfb1f..4b6db0ace1 100644 --- a/dan_layer/state_store_sqlite/src/schema.rs +++ b/dan_layer/state_store_sqlite/src/schema.rs @@ -135,7 +135,8 @@ diesel::table! { original_decision -> Text, pending_decision -> Nullable, evidence -> Text, - fee -> BigInt, + transaction_fee -> BigInt, + leader_fee -> BigInt, stage -> Text, is_ready -> Bool, updated_at -> Timestamp, @@ -162,6 +163,18 @@ diesel::table! { } } +diesel::table! { + validator_fees (id) { + id -> Integer, + validator_addr -> Text, + epoch -> BigInt, + block_id -> Text, + total_transaction_fee -> BigInt, + total_fee_due -> BigInt, + created_at -> Timestamp, + } +} + diesel::table! { votes (id) { id -> Integer, @@ -190,5 +203,6 @@ diesel::allow_tables_to_appear_in_same_query!( substates, transaction_pool, transactions, + validator_fees, votes, ); diff --git a/dan_layer/state_store_sqlite/src/sql_models/mod.rs b/dan_layer/state_store_sqlite/src/sql_models/mod.rs index d0cb91933e..8bb2bf352d 100644 --- a/dan_layer/state_store_sqlite/src/sql_models/mod.rs +++ b/dan_layer/state_store_sqlite/src/sql_models/mod.rs @@ -8,6 +8,7 @@ mod quorum_certificate; mod substate; mod transaction; mod transaction_pool; +mod validator_fee; mod vote; pub use block::*; @@ -17,4 +18,5 @@ pub use quorum_certificate::*; pub use substate::*; pub use transaction::*; pub use transaction_pool::*; +pub use validator_fee::*; pub use vote::*; diff --git a/dan_layer/state_store_sqlite/src/sql_models/transaction_pool.rs b/dan_layer/state_store_sqlite/src/sql_models/transaction_pool.rs index cbb3e67f44..830068a0b4 100644 --- a/dan_layer/state_store_sqlite/src/sql_models/transaction_pool.rs +++ b/dan_layer/state_store_sqlite/src/sql_models/transaction_pool.rs @@ -15,7 +15,8 @@ pub struct TransactionPoolRecord { pub original_decision: String, pub pending_decision: Option, pub evidence: String, - pub fee: i64, + pub transaction_fee: i64, + pub leader_fee: i64, pub stage: String, pub is_ready: bool, pub updated_at: PrimitiveDateTime, @@ -31,7 +32,8 @@ impl TryFrom for consensus_models::TransactionPoolRecord id: deserialize_hex_try_from(&value.transaction_id)?, decision: parse_from_string(&value.original_decision)?, evidence: deserialize_json(&value.evidence)?, - fee: value.fee as u64, + transaction_fee: value.transaction_fee as u64, + leader_fee: value.leader_fee as u64, }, pending_decision: value.pending_decision.as_deref().map(parse_from_string).transpose()?, stage: parse_from_string(&value.stage)?, diff --git a/dan_layer/state_store_sqlite/src/sql_models/validator_fee.rs b/dan_layer/state_store_sqlite/src/sql_models/validator_fee.rs new file mode 100644 index 0000000000..0e2c4df2b0 --- /dev/null +++ b/dan_layer/state_store_sqlite/src/sql_models/validator_fee.rs @@ -0,0 +1,17 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use diesel::Queryable; +use time::PrimitiveDateTime; + +#[derive(Debug, Clone, Queryable)] +pub struct ValidatorFee { + pub id: i32, + pub validator_addr: String, + pub epoch: i64, + pub block_id: String, + pub fee_due: i64, + pub total_fee: i64, + pub total_burnt: i64, + pub created_at: PrimitiveDateTime, +} diff --git a/dan_layer/state_store_sqlite/src/writer.rs b/dan_layer/state_store_sqlite/src/writer.rs index 3c794ed224..6f2e2aa9e6 100644 --- a/dan_layer/state_store_sqlite/src/writer.rs +++ b/dan_layer/state_store_sqlite/src/writer.rs @@ -8,7 +8,7 @@ use std::{ use diesel::{AsChangeset, ExpressionMethods, OptionalExtension, QueryDsl, RunQueryDsl, SqliteConnection}; use log::*; -use tari_dan_common_types::{Epoch, ShardId}; +use tari_dan_common_types::{Epoch, NodeAddressable, ShardId}; use tari_dan_storage::{ consensus_models::{ Block, @@ -28,6 +28,7 @@ use tari_dan_storage::{ SubstateRecord, TransactionAtom, TransactionPoolStage, + ValidatorFee, Vote, }, StateStoreWriteTransaction, @@ -413,7 +414,8 @@ impl StateStoreWriteTransaction for SqliteStateStoreWriteTransaction<'_> { &transaction.evidence.shards_iter().copied().collect::>(), )?), transaction_pool::original_decision.eq(transaction.decision.to_string()), - transaction_pool::fee.eq(transaction.fee as i64), + transaction_pool::transaction_fee.eq(transaction.transaction_fee as i64), + transaction_pool::leader_fee.eq(transaction.leader_fee as i64), transaction_pool::evidence.eq(serialize_json(&transaction.evidence)?), transaction_pool::stage.eq(stage.to_string()), transaction_pool::is_ready.eq(is_ready), @@ -757,6 +759,28 @@ impl StateStoreWriteTransaction for SqliteStateStoreWriteTransaction<'_> { Ok(()) } + + fn validator_fees_insert(&mut self, validator_fee: &ValidatorFee) -> Result<(), StorageError> { + use crate::schema::validator_fees; + + let values = ( + validator_fees::epoch.eq(validator_fee.epoch.as_u64() as i64), + validator_fees::block_id.eq(serialize_hex(&validator_fee.block_id)), + validator_fees::validator_addr.eq(serialize_hex(validator_fee.validator_public_key)), + validator_fees::total_transaction_fee.eq(validator_fee.total_transaction_fee as i64), + validator_fees::total_fee_due.eq(validator_fee.total_fee_due as i64), + ); + + diesel::insert_into(validator_fees::table) + .values(values) + .execute(self.connection()) + .map_err(|e| SqliteStorageError::DieselError { + operation: "validator_fees_insert", + source: e, + })?; + + Ok(()) + } } impl<'a> Deref for SqliteStateStoreWriteTransaction<'a> { diff --git a/dan_layer/storage/src/consensus_models/block.rs b/dan_layer/storage/src/consensus_models/block.rs index 4cb17d182d..6cc91c8bfb 100644 --- a/dan_layer/storage/src/consensus_models/block.rs +++ b/dan_layer/storage/src/consensus_models/block.rs @@ -207,6 +207,14 @@ impl Block { tx.blocks_get_tip(epoch) } + // pub fn get_all_proposed_by( + // tx: &mut TTx, + // epoch: Epoch, + // proposed_by: &ShardId, + // ) -> Result, StorageError> { + // tx.blocks_get_tip(epoch) + // } + pub fn exists(&self, tx: &mut TTx) -> Result { tx.blocks_exists(self.id()) } diff --git a/dan_layer/storage/src/consensus_models/command.rs b/dan_layer/storage/src/consensus_models/command.rs index d8f0d8e728..7c37b794f2 100644 --- a/dan_layer/storage/src/consensus_models/command.rs +++ b/dan_layer/storage/src/consensus_models/command.rs @@ -88,7 +88,8 @@ pub struct TransactionAtom { pub id: TransactionId, pub decision: Decision, pub evidence: Evidence, - pub fee: u64, + pub transaction_fee: u64, + pub leader_fee: u64, } impl TransactionAtom { diff --git a/dan_layer/storage/src/consensus_models/last_executed.rs b/dan_layer/storage/src/consensus_models/last_executed.rs index 4071982c6c..ea9ba46cae 100644 --- a/dan_layer/storage/src/consensus_models/last_executed.rs +++ b/dan_layer/storage/src/consensus_models/last_executed.rs @@ -1,7 +1,8 @@ // Copyright 2023 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_dan_common_types::{Epoch, NodeHeight}; +use tari_dan_common_types::NodeHeight; +use tari_engine_types::epoch::Epoch; use crate::{consensus_models::BlockId, StateStoreReadTransaction, StateStoreWriteTransaction, StorageError}; diff --git a/dan_layer/storage/src/consensus_models/last_voted.rs b/dan_layer/storage/src/consensus_models/last_voted.rs index 4687126f2f..193188d87c 100644 --- a/dan_layer/storage/src/consensus_models/last_voted.rs +++ b/dan_layer/storage/src/consensus_models/last_voted.rs @@ -1,7 +1,8 @@ // Copyright 2023 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_dan_common_types::{Epoch, NodeHeight}; +use tari_dan_common_types::NodeHeight; +use tari_engine_types::epoch::Epoch; use crate::{consensus_models::BlockId, StateStoreReadTransaction, StateStoreWriteTransaction, StorageError}; diff --git a/dan_layer/storage/src/consensus_models/locked_block.rs b/dan_layer/storage/src/consensus_models/locked_block.rs index ebce8e2b4a..e1c5c931b5 100644 --- a/dan_layer/storage/src/consensus_models/locked_block.rs +++ b/dan_layer/storage/src/consensus_models/locked_block.rs @@ -1,7 +1,8 @@ // Copyright 2023 The Tari Project // SPDX-License-Identifier: BSD-3-Clause -use tari_dan_common_types::{Epoch, NodeHeight}; +use tari_dan_common_types::NodeHeight; +use tari_engine_types::epoch::Epoch; use crate::{ consensus_models::{Block, BlockId}, diff --git a/dan_layer/storage/src/consensus_models/mod.rs b/dan_layer/storage/src/consensus_models/mod.rs index 2ecd800177..140810b31c 100644 --- a/dan_layer/storage/src/consensus_models/mod.rs +++ b/dan_layer/storage/src/consensus_models/mod.rs @@ -16,6 +16,7 @@ mod substate; mod transaction; mod transaction_decision; mod transaction_pool; +mod validator_fee; mod vote; mod vote_signature; @@ -34,5 +35,6 @@ pub use substate::*; pub use transaction::*; pub use transaction_decision::*; pub use transaction_pool::*; +pub use validator_fee::*; pub use vote::*; pub use vote_signature::*; diff --git a/dan_layer/storage/src/consensus_models/transaction_pool.rs b/dan_layer/storage/src/consensus_models/transaction_pool.rs index 0e12072ec7..046d3c5d17 100644 --- a/dan_layer/storage/src/consensus_models/transaction_pool.rs +++ b/dan_layer/storage/src/consensus_models/transaction_pool.rs @@ -180,12 +180,36 @@ impl TransactionPoolRecord { self.stage } - pub fn get_transaction_atom_with_decision_change(&self) -> TransactionAtom { + pub fn get_final_transaction_atom(&self, leader_fee: u64) -> TransactionAtom { TransactionAtom { decision: self.final_decision(), + leader_fee, ..self.transaction.clone() } } + + pub fn calculate_leader_fee(&self, involved: u64, exhaust_divisor: u64) -> u64 { + // TODO: We essentially burn a random amount depending on the shards involved in the transaction. This means it + // is hard to tell how much is actually in circulation unless we track this in the Resource. Right + // now we'll set exhaust to 0, which is just transaction_fee / involved. + let transaction_fee = self.transaction.transaction_fee; + let due_fee = transaction_fee / involved; + // The extra amount that is burnt + let due_rem = transaction_fee % involved; + + // How much we want to burn due to exhaust per involved shard + let target_exhaust_burn = if exhaust_divisor > 0 { + let base_fee = transaction_fee.checked_div(exhaust_divisor).unwrap_or(transaction_fee); + base_fee / involved + } else { + 0 + }; + + // Adjust the amount to burn taking into account the remainder that we burn + let adjusted_burn = target_exhaust_burn.checked_sub(due_rem).unwrap_or(0); + + due_fee - adjusted_burn + } } impl TransactionPoolRecord { @@ -275,3 +299,58 @@ impl IsNotFoundError for TransactionPoolError { } } } + +#[cfg(test)] +mod tests { + use super::*; + + mod calculate_leader_fee { + use super::*; + + fn create_record_with_fee(fee: u64) -> TransactionPoolRecord { + TransactionPoolRecord { + transaction: TransactionAtom { + id: TransactionId::new([0; 32]), + decision: Decision::Commit, + evidence: Default::default(), + transaction_fee: fee, + leader_fee: 0, + }, + stage: TransactionPoolStage::New, + pending_decision: None, + is_ready: false, + } + } + + #[test] + fn it_calculates_the_correct_fee_due() { + let record = create_record_with_fee(100); + + let fee = record.calculate_leader_fee(1, 0); + assert_eq!(fee, 100); + + let fee = record.calculate_leader_fee(1, 10); + assert_eq!(fee, 90); + + let fee = record.calculate_leader_fee(2, 0); + assert_eq!(fee, 50); + + let fee = record.calculate_leader_fee(2, 10); + assert_eq!(fee, 45); + + let fee = record.calculate_leader_fee(3, 0); + assert_eq!(fee, 33); + + let fee = record.calculate_leader_fee(3, 10); + assert_eq!(fee, 31); + + let record = create_record_with_fee(98); + + let fee = record.calculate_leader_fee(3, 10); + assert_eq!(fee, 31); + + let fee = record.calculate_leader_fee(10, 10); + assert_eq!(fee, 9); + } + } +} diff --git a/dan_layer/storage/src/consensus_models/validator_fee.rs b/dan_layer/storage/src/consensus_models/validator_fee.rs new file mode 100644 index 0000000000..f5c116efec --- /dev/null +++ b/dan_layer/storage/src/consensus_models/validator_fee.rs @@ -0,0 +1,30 @@ +// Copyright 2023 The Tari Project +// SPDX-License-Identifier: BSD-3-Clause + +use tari_common_types::types::PublicKey; +use tari_dan_common_types::{Epoch, ShardId}; + +use crate::{consensus_models::BlockId, StateStoreReadTransaction, StateStoreWriteTransaction, StorageError}; + +#[derive(Debug, Clone)] +pub struct ValidatorFee { + pub validator_public_key: ShardId, + pub epoch: Epoch, + pub block_id: BlockId, + pub total_fee_due: u64, + pub total_transaction_fee: u64, +} + +impl ValidatorFee { + pub fn create(&self, tx: &mut TTx) -> Result<(), StorageError> { + tx.validator_fees_insert(self) + } + + pub fn get_validator_fee_for_epoch( + tx: &mut TTx, + epoch: Epoch, + validator_public_key: &PublicKey, + ) -> Result { + tx.validator_fees_get_total_fee_for_epoch(epoch, validator_public_key) + } +} diff --git a/dan_layer/storage/src/global/validator_node_db.rs b/dan_layer/storage/src/global/validator_node_db.rs index 7309b1d2b6..e11315dbec 100644 --- a/dan_layer/storage/src/global/validator_node_db.rs +++ b/dan_layer/storage/src/global/validator_node_db.rs @@ -26,7 +26,7 @@ use std::{ }; use tari_common_types::types::PublicKey; -use tari_dan_common_types::{committee::Committee, Epoch, ShardId}; +use tari_dan_common_types::{committee::Committee, ShardId}; use crate::global::{models::ValidatorNode, GlobalDbAdapter}; diff --git a/dan_layer/storage/src/state_store/mod.rs b/dan_layer/storage/src/state_store/mod.rs index 76e9a8d6a3..7e02928a79 100644 --- a/dan_layer/storage/src/state_store/mod.rs +++ b/dan_layer/storage/src/state_store/mod.rs @@ -6,7 +6,7 @@ use std::{ ops::{Deref, DerefMut}, }; -use tari_common_types::types::FixedHash; +use tari_common_types::types::{FixedHash, PublicKey}; use tari_dan_common_types::{Epoch, ShardId}; use tari_transaction::{Transaction, TransactionId}; @@ -32,6 +32,7 @@ use crate::{ TransactionPoolRecord, TransactionPoolStage, TransactionRecord, + ValidatorFee, Vote, }, StorageError, @@ -136,6 +137,12 @@ pub trait StateStoreReadTransaction { &mut self, tx_id: &TransactionId, ) -> Result, StorageError>; + // -------------------------------- ValidatorFess -------------------------------- // + fn validator_fees_get_total_fee_for_epoch( + &mut self, + epoch: Epoch, + validator_public_key: &PublicKey, + ) -> Result; } pub trait StateStoreWriteTransaction { @@ -210,6 +217,9 @@ pub trait StateStoreWriteTransaction { destroyed_transaction_id: &TransactionId, ) -> Result<(), StorageError>; fn substates_create(&mut self, substate: SubstateRecord) -> Result<(), StorageError>; + + // -------------------------------- Validator Fees -------------------------------- // + fn validator_fees_insert(&mut self, validator_fee: &ValidatorFee) -> Result<(), StorageError>; } pub enum Ordering { diff --git a/dan_layer/template_lib/src/crypto.rs b/dan_layer/template_lib/src/crypto.rs index 4d8c851827..35ca908a86 100644 --- a/dan_layer/template_lib/src/crypto.rs +++ b/dan_layer/template_lib/src/crypto.rs @@ -54,6 +54,12 @@ impl TryFrom<&[u8]> for RistrettoPublicKeyBytes { } } +impl AsRef<[u8]> for RistrettoPublicKeyBytes { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + #[derive(Debug, PartialEq, Eq)] pub struct InvalidByteLengthError { size: usize, diff --git a/dan_layer/template_lib/src/models/amount.rs b/dan_layer/template_lib/src/models/amount.rs index 5a373de416..c06006c3dc 100644 --- a/dan_layer/template_lib/src/models/amount.rs +++ b/dan_layer/template_lib/src/models/amount.rs @@ -33,6 +33,8 @@ use tari_template_abi::rust::{ pub struct Amount(pub i64); impl Amount { + pub const MAX: Amount = Amount(i64::MAX); + pub const fn new(amount: i64) -> Self { Amount(amount) } diff --git a/dan_layer/template_lib/src/models/binary_tag.rs b/dan_layer/template_lib/src/models/binary_tag.rs index c42b3f7e8f..556bba06ca 100644 --- a/dan_layer/template_lib/src/models/binary_tag.rs +++ b/dan_layer/template_lib/src/models/binary_tag.rs @@ -31,6 +31,7 @@ pub enum BinaryTag { VaultId = 132, BucketId = 133, TransactionReceipt = 134, + FeeClaim = 135, } impl BinaryTag { @@ -43,6 +44,7 @@ impl BinaryTag { 132 => Some(Self::VaultId), 133 => Some(Self::BucketId), 134 => Some(Self::TransactionReceipt), + 135 => Some(Self::FeeClaim), _ => None, } } diff --git a/dan_layer/transaction_manifest/src/generator.rs b/dan_layer/transaction_manifest/src/generator.rs index 935e405568..ea1c372d18 100644 --- a/dan_layer/transaction_manifest/src/generator.rs +++ b/dan_layer/transaction_manifest/src/generator.rs @@ -143,6 +143,7 @@ impl ManifestInstructionGenerator { SubstateAddress::NonFungible(addr) => Ok(arg!(addr)), SubstateAddress::UnclaimedConfidentialOutput(addr) => Ok(arg!(*addr)), SubstateAddress::NonFungibleIndex(addr) => Ok(arg!(addr)), + SubstateAddress::FeeClaim(addr) => Ok(arg!(*addr)), }, ManifestValue::Literal(lit) => lit_to_arg(lit), ManifestValue::NonFungibleId(id) => Ok(arg!(id.clone())), diff --git a/dan_layer/validator_node_rpc/src/conversions/consensus.rs b/dan_layer/validator_node_rpc/src/conversions/consensus.rs index e2d9f38a79..78dc056cbc 100644 --- a/dan_layer/validator_node_rpc/src/conversions/consensus.rs +++ b/dan_layer/validator_node_rpc/src/conversions/consensus.rs @@ -292,7 +292,7 @@ impl From for proto::consensus::TransactionAtom { id: value.id.as_bytes().to_vec(), decision: i32::from(value.decision.as_u8()), evidence: Some(value.evidence.into()), - fee: value.fee, + fee: value.transaction_fee, } } } @@ -309,7 +309,7 @@ impl TryFrom for TransactionAtom { .evidence .ok_or_else(|| anyhow!("evidence not provided"))? .try_into()?, - fee: value.fee, + transaction_fee: value.fee, }) } }