Skip to content

Commit

Permalink
feat!: add burned outputs (#4364)
Browse files Browse the repository at this point in the history
Description
---
This Pr adds in the ability to create burned outputs. 
There will be a follow-up PR addressing the kernel mutability as currently the fields are mutable and need to be signed to block mutability, see: #4365. 

For added reasoning why this is needed see RFC: tari-project/rfcs#10

Full testing of this is also blocked by: #4360

How Has This Been Tested?
---
Unit and integration tests.
  • Loading branch information
SWvheerden authored Aug 2, 2022
1 parent d012823 commit 60f3877
Show file tree
Hide file tree
Showing 39 changed files with 4,629 additions and 257 deletions.
2 changes: 2 additions & 0 deletions applications/tari_app_grpc/proto/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ message TransactionKernel {
bytes hash = 8;
// Version
uint32 version = 9;
// Optional burned commitment
bytes burn_commitment = 10;
}

// A transaction input.
Expand Down
14 changes: 14 additions & 0 deletions applications/tari_app_grpc/proto/wallet.proto
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ service Wallet {
rpc RevalidateAllTransactions (RevalidateRequest) returns (RevalidateResponse);
// This will send a XTR SHA Atomic swap transaction
rpc SendShaAtomicSwapTransaction(SendShaAtomicSwapRequest) returns (SendShaAtomicSwapResponse);
// This will create a burn transaction
rpc CreateBurnTransaction(CreateBurnTransactionRequest) returns (CreateBurnTransactionResponse);
// This will claim a XTR SHA Atomic swap transaction
rpc ClaimShaAtomicSwapTransaction(ClaimShaAtomicSwapRequest) returns (ClaimShaAtomicSwapResponse);
// This will claim a HTLC refund transaction
Expand Down Expand Up @@ -106,6 +108,12 @@ message SendShaAtomicSwapRequest {
PaymentRecipient recipient = 1;
}

message CreateBurnTransactionRequest{
uint64 amount = 1;
uint64 fee_per_gram = 2;
string message = 3;
}

message PaymentRecipient {
string address = 1;
uint64 amount = 2;
Expand All @@ -131,6 +139,12 @@ message SendShaAtomicSwapResponse {
string failure_message = 5;
}

message CreateBurnTransactionResponse{
uint64 transaction_id = 1;
bool is_success = 2;
string failure_message = 3;
}

message TransferResult {
string address = 1;
uint64 transaction_id = 2;
Expand Down
14 changes: 14 additions & 0 deletions applications/tari_app_grpc/src/conversions/transaction_kernel.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,14 @@ impl TryFrom<grpc::TransactionKernel> for TransactionKernel {
.map_err(|_| "excess_sig could not be converted".to_string())?;

let kernel_features = u8::try_from(kernel.features).map_err(|_| "kernel features must be a single byte")?;
let commitment = if kernel.burn_commitment.is_empty() {
None
} else {
Some(
Commitment::from_bytes(&kernel.burn_commitment)
.map_err(|err| format!("Burn commitment could not be converted:{}", err))?,
)
};

Ok(Self::new(
TransactionKernelVersion::try_from(
Expand All @@ -56,13 +64,18 @@ impl TryFrom<grpc::TransactionKernel> for TransactionKernel {
kernel.lock_height,
excess,
excess_sig,
commitment,
))
}
}

impl From<TransactionKernel> for grpc::TransactionKernel {
fn from(kernel: TransactionKernel) -> Self {
let hash = kernel.hash();
let commitment = match kernel.burn_commitment {
Some(c) => c.as_bytes().to_vec(),
None => vec![],
};

grpc::TransactionKernel {
features: u32::from(kernel.features.bits()),
Expand All @@ -75,6 +88,7 @@ impl From<TransactionKernel> for grpc::TransactionKernel {
}),
hash,
version: kernel.version as u32,
burn_commitment: commitment,
}
}
}
35 changes: 35 additions & 0 deletions applications/tari_console_wallet/src/grpc/wallet_grpc_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ use tari_app_grpc::{
ClaimShaAtomicSwapResponse,
CoinSplitRequest,
CoinSplitResponse,
CreateBurnTransactionRequest,
CreateBurnTransactionResponse,
CreateConstitutionDefinitionRequest,
CreateConstitutionDefinitionResponse,
CreateFollowOnAssetCheckpointRequest,
Expand Down Expand Up @@ -560,6 +562,39 @@ impl wallet_server::Wallet for WalletGrpcServer {
Ok(Response::new(TransferResponse { results }))
}

async fn create_burn_transaction(
&self,
request: Request<CreateBurnTransactionRequest>,
) -> Result<Response<CreateBurnTransactionResponse>, Status> {
let message = request.into_inner();

let mut transaction_service = self.get_transaction_service();
debug!(target: LOG_TARGET, "Trying to burn {} Tari", message.amount);
let response = match transaction_service
.burn_tari(message.amount.into(), message.fee_per_gram.into(), message.message)
.await
{
Ok(tx_id) => {
debug!(target: LOG_TARGET, "Transaction broadcast: {}", tx_id,);
CreateBurnTransactionResponse {
transaction_id: tx_id.as_u64(),
is_success: true,
failure_message: Default::default(),
}
},
Err(e) => {
warn!(target: LOG_TARGET, "Failed to burn Tarid: {}", e);
CreateBurnTransactionResponse {
transaction_id: Default::default(),
is_success: false,
failure_message: e.to_string(),
}
},
};

Ok(Response::new(response))
}

async fn get_transaction_info(
&self,
request: Request<GetTransactionInfoRequest>,
Expand Down
3 changes: 2 additions & 1 deletion applications/test_faucet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ async fn write_keys(mut rx: mpsc::Receiver<(TransactionOutput, PrivateKey, Micro
}
let (pk, sig) = test_helpers::create_random_signature_from_s_key(key_sum, 0.into(), 0);
let excess = Commitment::from_public_key(&pk);
let kernel = TransactionKernel::new_current_version(KernelFeatures::empty(), MicroTari::from(0), 0, excess, sig);
let kernel =
TransactionKernel::new_current_version(KernelFeatures::empty(), MicroTari::from(0), 0, excess, sig, None);
let kernel = serde_json::to_string(&kernel).unwrap();
let _result = utxo_file.write_all(format!("{}\n", kernel).as_bytes());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ pub enum HorizonSyncError {
JoinError(#[from] task::JoinError),
#[error("A range proof verification has produced an error: {0}")]
RangeProofError(#[from] RangeProofError),
#[error("An invalid transaction has been encountered: {0}")]
TransactionError(#[from] TransactionError),
#[error("Invalid kernel signature: {0}")]
InvalidKernelSignature(TransactionError),
#[error("MMR did not match for {mmr_tree} at height {at_height}. Expected {actual_hex} to equal {expected_hex}")]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -743,14 +743,15 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> {
));

let header = self.db().fetch_chain_header(self.horizon_sync_height).await?;
let (calc_utxo_sum, calc_kernel_sum) = self.calculate_commitment_sums(&header).await?;
let (calc_utxo_sum, calc_kernel_sum, calc_burned_sum) = self.calculate_commitment_sums(&header).await?;

self.final_state_validator
.validate(
&*self.db().inner().db_read_access()?,
header.height(),
&calc_utxo_sum,
&calc_kernel_sum,
&calc_burned_sum,
)
.map_err(HorizonSyncError::FinalStateValidationFailed)?;

Expand Down Expand Up @@ -793,9 +794,10 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> {
async fn calculate_commitment_sums(
&mut self,
header: &ChainHeader,
) -> Result<(Commitment, Commitment), HorizonSyncError> {
) -> Result<(Commitment, Commitment, Commitment), HorizonSyncError> {
let mut utxo_sum = HomomorphicCommitment::default();
let mut kernel_sum = HomomorphicCommitment::default();
let mut burned_sum = HomomorphicCommitment::default();

let mut prev_mmr = 0;
let mut prev_kernel_mmr = 0;
Expand All @@ -810,7 +812,6 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> {

for h in 0..=height {
let curr_header = db.fetch_chain_header(h)?;

trace!(
target: LOG_TARGET,
"Fetching utxos from db: height:{}, header.output_mmr:{}, prev_mmr:{}, end:{}",
Expand Down Expand Up @@ -866,6 +867,9 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> {
trace!(target: LOG_TARGET, "Number of kernels returned: {}", kernels.len());
for k in kernels {
kernel_sum = &k.excess + &kernel_sum;
if k.is_burned() {
burned_sum = k.get_burn_commitment()? + &burned_sum;
}
}
prev_kernel_mmr = curr_header.header().kernel_mmr_size;

Expand All @@ -888,7 +892,7 @@ impl<'a, B: BlockchainBackend + 'static> HorizonStateSynchronization<'a, B> {
db.write(txn)?;
}

Ok((utxo_sum, kernel_sum))
Ok((utxo_sum, kernel_sum, burned_sum))
})
.await?
}
Expand Down
10 changes: 6 additions & 4 deletions base_layer/core/src/blocks/genesis_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ fn get_igor_genesis_block_raw() -> Block {
"9474ba70976e2fa06f970bb83f7d0a4d4b45e6e29f834847b659d32102f90b51",
)
.unwrap(),
sig,
sig,None

)],
);
body.sort();
Expand Down Expand Up @@ -216,7 +217,7 @@ pub fn get_dibbler_genesis_block() -> ChainBlock {
// println!("output mr: {}", block.header.output_mr.to_hex());

// Hardcode the Merkle roots once they've been computed above
block.header.kernel_mr = from_hex("8bec1140bfac718ab3acd8a5e19c1bb28e0e4a57663c2fc7e8c7155cc355aac3").unwrap();
block.header.kernel_mr = from_hex("51acb4b74cc2e43a11be4f283b653a6fc95666dcf90f66f0c32742c5fb77e640").unwrap();
block.header.witness_mr = from_hex("1df4a4200338686763c784187f7077148986e088586cf4839147a3f56adc4af6").unwrap();
block.header.output_mr = from_hex("f9616ca84e798022f638546e6ce372d1344eee56e5cf47ba7e2bf58b5e28bf45").unwrap();

Expand Down Expand Up @@ -274,6 +275,7 @@ fn get_dibbler_genesis_block_raw() -> Block {
0,
Commitment::from_hex("0cff7e89fa0468aa68f777cf600ae6f9e46fdc6e4e33540077e7303e8929295c").unwrap(),
excess_sig,
None,
);
let mut body = AggregateBody::new(vec![], vec![coinbase], vec![kernel]);
body.sort();
Expand Down Expand Up @@ -314,7 +316,7 @@ fn get_dibbler_genesis_block_raw() -> Block {
#[cfg(test)]
mod test {
use croaring::Bitmap;
use tari_common_types::types::HashDigest;
use tari_common_types::types::{Commitment, HashDigest};
use tari_mmr::{MerkleMountainRange, MutableMmr};

use super::*;
Expand Down Expand Up @@ -385,7 +387,7 @@ mod test {

let lock = db.db_read_access().unwrap();
ChainBalanceValidator::new(ConsensusManager::builder(Network::Dibbler).build(), Default::default())
.validate(&*lock, 0, &utxo_sum, &kernel_sum)
.validate(&*lock, 0, &utxo_sum, &kernel_sum, &Commitment::default())
.unwrap();
}
}
31 changes: 31 additions & 0 deletions base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1302,12 +1302,35 @@ impl LMDBDatabase {
let leaf_count = witness_mmr.get_leaf_count()?;

// Output hashes added before inputs so that inputs can spend outputs in this transaction (0-conf and combined)
let mut burned_outputs = Vec::new();
let outputs = outputs
.into_iter()
.enumerate()
.map(|(i, output)| {
output_mmr.push(output.hash())?;
witness_mmr.push(output.witness_hash())?;
// lets check burn
if output.is_burned() {
let index = match output_mmr.find_leaf_index(&output.hash())? {
Some(index) => {
debug!(target: LOG_TARGET, "Output {} burned in current block", output);
burned_outputs.push(output.commitment.clone());
index
},
None => {
return Err(ChainStorageError::UnexpectedResult(
"Output MMR did not contain the expected output".to_string(),
))
},
};
// We need to mark this as spent as well.
if !output_mmr.delete(index) {
return Err(ChainStorageError::InvalidOperation(format!(
"Could not delete index {} from the output MMR",
index
)));
}
};
Ok((output, leaf_count + i + 1))
})
.collect::<Result<Vec<_>, ChainStorageError>>()?;
Expand Down Expand Up @@ -1366,6 +1389,14 @@ impl LMDBDatabase {
"utxo_commitment_index",
)?;
}
for commitment in burned_outputs {
lmdb_delete(
txn,
&self.utxo_commitment_index,
commitment.as_bytes(),
"utxo_commitment_index",
)?;
}
// Merge current deletions with the tip bitmap
let deleted_at_current_height = output_mmr.deleted().clone();
// Merge the new indexes with the blockchain deleted bitmap
Expand Down
2 changes: 2 additions & 0 deletions base_layer/core/src/proto/transaction.proto
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ message TransactionKernel {
Signature excess_sig = 7;
// Version
uint32 version = 8;
// Optional burned commitment
Commitment burn_commitment = 9;
}

// A transaction input.
Expand Down
7 changes: 7 additions & 0 deletions base_layer/core/src/proto/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,10 @@ impl TryFrom<proto::types::TransactionKernel> for TransactionKernel {
.ok_or_else(|| "excess_sig not provided".to_string())?
.try_into()?;
let kernel_features = u8::try_from(kernel.features).map_err(|_| "Kernel features must be a single byte")?;
let commitment = match kernel.burn_commitment {
Some(burn_commitment) => Some(Commitment::from_bytes(&burn_commitment.data).map_err(|e| e.to_string())?),
None => None,
};

Ok(TransactionKernel::new(
TransactionKernelVersion::try_from(
Expand All @@ -111,19 +115,22 @@ impl TryFrom<proto::types::TransactionKernel> for TransactionKernel {
kernel.lock_height,
excess,
excess_sig,
commitment,
))
}
}

impl From<TransactionKernel> for proto::types::TransactionKernel {
fn from(kernel: TransactionKernel) -> Self {
let commitment = kernel.burn_commitment.map(|commitment| commitment.into());
Self {
features: u32::from(kernel.features.bits()),
excess: Some(kernel.excess.into()),
excess_sig: Some(kernel.excess_sig.into()),
fee: kernel.fee.into(),
lock_height: kernel.lock_height,
version: kernel.version as u32,
burn_commitment: commitment,
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/transactions/aggregated_body.rs
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,7 @@ impl AggregateBody {
for kernel in self.kernels() {
if kernel.lock_height > height {
warn!(target: LOG_TARGET, "Kernel lock height was not reached: {}", kernel);
return Err(TransactionError::InvalidKernel);
return Err(TransactionError::InvalidKernel("Invalid lock height".to_string()));
}
}
Ok(())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ pub enum TransactionError {
RangeProofError(#[from] RangeProofError),
#[error("An error occurred while performing a commitment signature: {0}")]
SigningError(#[from] CommitmentSignatureError),
#[error("Invalid kernel in body")]
InvalidKernel,
#[error("Invalid kernel in body : {0}")]
InvalidKernel(String),
#[error("Invalid coinbase in body")]
InvalidCoinbase,
#[error("Invalid coinbase maturity in body")]
Expand Down
Loading

0 comments on commit 60f3877

Please sign in to comment.