Skip to content

Commit

Permalink
feat!: covenants integration (#3681)
Browse files Browse the repository at this point in the history
## Description
- add covenants to txos
- validate covenants for transactions and blocks
 
## Motivation and Context

Based on #3656

## How Has This Been Tested?
New unit test
Manually on dibbler, mined a few blocks and a sent transaction (empty covenant)
  • Loading branch information
sdbondi authored Jan 13, 2022
1 parent d4eb11b commit 2ff6fed
Show file tree
Hide file tree
Showing 70 changed files with 1,700 additions and 941 deletions.
7 changes: 6 additions & 1 deletion applications/tari_app_grpc/proto/types.proto
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,8 @@ message TransactionInput {
bytes sender_offset_public_key = 8;
// The hash of the output this input is spending
bytes output_hash = 9;
// Covenant
bytes covenant = 10;
}

// Output for a transaction, defining the new ownership of coins that are being transferred. The commitment is a
Expand All @@ -192,7 +194,8 @@ message TransactionOutput {
// Metadata signature with the homomorphic commitment private values (amount and blinding factor) and the sender
// offset private key
ComSignature metadata_signature = 7;
// Tari script offset pubkey, K_O
// Covenant
bytes covenant = 8;
}

// Options for UTXO's
Expand Down Expand Up @@ -328,6 +331,8 @@ message UnblindedOutput {
ComSignature metadata_signature = 9;
// The minimum height the script allows this output to be spent
uint64 script_lock_height = 10;
// Covenant
bytes covenant = 11;
}

// ----------------------------- Network Types ----------------------------- //
Expand Down
20 changes: 12 additions & 8 deletions applications/tari_app_grpc/src/conversions/transaction_input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
use std::convert::{TryFrom, TryInto};

use tari_common_types::types::{Commitment, PublicKey};
use tari_core::transactions::transaction::TransactionInput;
use tari_core::{
consensus::{ConsensusDecoding, ToConsensusBytes},
covenants::Covenant,
transactions::transaction::TransactionInput,
};
use tari_crypto::{
script::{ExecutionStack, TariScript},
tari_utilities::ByteArray,
Expand Down Expand Up @@ -51,6 +55,7 @@ impl TryFrom<grpc::TransactionInput> for TransactionInput {

let sender_offset_public_key =
PublicKey::from_bytes(input.sender_offset_public_key.as_bytes()).map_err(|err| format!("{:?}", err))?;
let covenant = Covenant::consensus_decode(&mut input.covenant.as_slice()).map_err(|err| err.to_string())?;

Ok(TransactionInput::new_with_output_data(
features,
Expand All @@ -59,6 +64,7 @@ impl TryFrom<grpc::TransactionInput> for TransactionInput {
ExecutionStack::from_bytes(input.input_data.as_slice()).map_err(|err| format!("{:?}", err))?,
script_signature,
sender_offset_public_key,
covenant,
))
} else {
if input.output_hash.is_empty() {
Expand All @@ -85,14 +91,9 @@ impl TryFrom<TransactionInput> for grpc::TransactionInput {
if input.is_compact() {
let output_hash = input.output_hash();
Ok(Self {
features: None,
commitment: Vec::new(),
hash: Vec::new(),
script: Vec::new(),
input_data: Vec::new(),
script_signature,
sender_offset_public_key: Vec::new(),
output_hash,
..Default::default()
})
} else {
let features = input
Expand All @@ -104,7 +105,6 @@ impl TryFrom<TransactionInput> for grpc::TransactionInput {
commitment: input
.commitment()
.map_err(|_| "Non-compact Transaction input should contain commitment".to_string())?
.clone()
.as_bytes()
.to_vec(),
hash: input
Expand All @@ -123,6 +123,10 @@ impl TryFrom<TransactionInput> for grpc::TransactionInput {
.as_bytes()
.to_vec(),
output_hash: Vec::new(),
covenant: input
.covenant()
.map_err(|_| "Non-compact Transaction input should contain covenant".to_string())?
.to_consensus_bytes(),
})
}
}
Expand Down
10 changes: 8 additions & 2 deletions applications/tari_app_grpc/src/conversions/transaction_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
use std::convert::{TryFrom, TryInto};

use tari_common_types::types::{BulletRangeProof, Commitment, PublicKey};
use tari_core::transactions::transaction::TransactionOutput;
use tari_core::{
consensus::{ConsensusDecoding, ToConsensusBytes},
covenants::Covenant,
transactions::transaction::TransactionOutput,
};
use tari_crypto::script::TariScript;
use tari_utilities::{ByteArray, Hashable};

Expand Down Expand Up @@ -51,14 +55,15 @@ impl TryFrom<grpc::TransactionOutput> for TransactionOutput {
.ok_or_else(|| "Metadata signature not provided".to_string())?
.try_into()
.map_err(|_| "Metadata signature could not be converted".to_string())?;

let covenant = Covenant::consensus_decode(&mut output.covenant.as_slice()).map_err(|err| err.to_string())?;
Ok(Self {
features,
commitment,
proof: BulletRangeProof(output.range_proof),
script,
sender_offset_public_key,
metadata_signature,
covenant,
})
}
}
Expand All @@ -78,6 +83,7 @@ impl From<TransactionOutput> for grpc::TransactionOutput {
signature_u: Vec::from(output.metadata_signature.u().as_bytes()),
signature_v: Vec::from(output.metadata_signature.v().as_bytes()),
}),
covenant: output.covenant.to_consensus_bytes(),
}
}
}
10 changes: 9 additions & 1 deletion applications/tari_app_grpc/src/conversions/unblinded_output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
use std::convert::{TryFrom, TryInto};

use tari_common_types::types::{PrivateKey, PublicKey};
use tari_core::transactions::{tari_amount::MicroTari, transaction::UnblindedOutput};
use tari_core::{
consensus::{ConsensusDecoding, ToConsensusBytes},
covenants::Covenant,
transactions::{tari_amount::MicroTari, transaction::UnblindedOutput},
};
use tari_crypto::script::{ExecutionStack, TariScript};
use tari_utilities::ByteArray;

Expand All @@ -45,6 +49,7 @@ impl From<UnblindedOutput> for grpc::UnblindedOutput {
signature_v: Vec::from(output.metadata_signature.v().as_bytes()),
}),
script_lock_height: output.script_lock_height,
covenant: output.covenant.to_consensus_bytes(),
}
}
}
Expand Down Expand Up @@ -78,6 +83,8 @@ impl TryFrom<grpc::UnblindedOutput> for UnblindedOutput {
.try_into()
.map_err(|_| "Metadata signature could not be converted".to_string())?;

let covenant = Covenant::consensus_decode(&mut output.covenant.as_slice()).map_err(|err| err.to_string())?;

Ok(Self {
value: MicroTari::from(output.value),
spending_key,
Expand All @@ -88,6 +95,7 @@ impl TryFrom<grpc::UnblindedOutput> for UnblindedOutput {
sender_offset_public_key,
metadata_signature,
script_lock_height: output.script_lock_height,
covenant,
})
}
}
2 changes: 1 addition & 1 deletion applications/test_faucet/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
task::spawn(async move {
let result = task::spawn_blocking(move || {
let script = script!(Nop);
let (utxo, key, _) = test_helpers::create_utxo(value, &fc, feature, &script);
let (utxo, key, _) = test_helpers::create_utxo(value, &fc, feature, &script, &Default::default());
print!(".");
(utxo, key, value)
})
Expand Down
2 changes: 1 addition & 1 deletion base_layer/core/src/blocks/block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ use crate::{
},
};

#[derive(Clone, Debug, PartialEq, Error)]
#[derive(Clone, Debug, Error)]
pub enum BlockValidationError {
#[error("A transaction in the block failed to validate: `{0}`")]
TransactionError(#[from] TransactionError),
Expand Down
3 changes: 3 additions & 0 deletions base_layer/core/src/blocks/genesis_block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ use tari_crypto::{

use crate::{
blocks::{block::Block, BlockHeader, BlockHeaderAccumulatedData, ChainBlock},
covenants::Covenant,
proof_of_work::{PowAlgorithm, ProofOfWork},
transactions::{
aggregated_body::AggregateBody,
Expand Down Expand Up @@ -100,6 +101,7 @@ fn get_igor_genesis_block_raw() -> Block {
sender_offset_public_key: Default::default(),
// For genesis block: Metadata signature will never be checked
metadata_signature: Default::default(),
covenant: Covenant::default(),
}],
vec![TransactionKernel {
features: KernelFeatures::COINBASE_KERNEL,
Expand Down Expand Up @@ -231,6 +233,7 @@ fn get_dibbler_genesis_block_raw() -> Block {
sender_offset_public_key: Default::default(),
// For genesis block: Metadata signature will never be checked
metadata_signature: Default::default(),
covenant: Default::default()
}],
vec![TransactionKernel {
features: KernelFeatures::COINBASE_KERNEL,
Expand Down
1 change: 1 addition & 0 deletions base_layer/core/src/chain_storage/blockchain_database.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1413,6 +1413,7 @@ fn fetch_block<T: BlockchainBackend>(db: &T, height: u64) -> Result<HistoricalBl
output.commitment,
output.script,
output.sender_offset_public_key,
output.covenant,
);
Ok(compact_input)
},
Expand Down
1 change: 1 addition & 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 @@ -1017,6 +1017,7 @@ impl LMDBDatabase {
output.commitment,
output.script,
output.sender_offset_public_key,
output.covenant,
);
},
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,7 @@ mod add_block {
lock_height: 0,
features,
script: tari_crypto::script![Nop],
covenant: Default::default(),
input_data: None,
}]);
let commitment_hex = txns[0]
Expand Down Expand Up @@ -446,6 +447,7 @@ mod add_block {
lock_height: 0,
features: Default::default(),
script: tari_crypto::script![Nop],
covenant: Default::default(),
input_data: None,
}]);

Expand Down
55 changes: 47 additions & 8 deletions base_layer/core/src/consensus/consensus_encoding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,10 @@ mod impls {
use std::io::Read;

use tari_common_types::types::{Commitment, PrivateKey, PublicKey, Signature};
use tari_crypto::script::TariScript;
use tari_crypto::{
keys::{PublicKey as PublicKeyTrait, SecretKey as SecretKeyTrait},
script::{ExecutionStack, TariScript},
};
use tari_utilities::ByteArray;

use super::*;
Expand All @@ -125,15 +128,13 @@ mod impls {
}
}

//---------------------------------- PublicKey --------------------------------------------//

impl ConsensusEncoding for PublicKey {
impl ConsensusEncoding for ExecutionStack {
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
writer.write(self.as_bytes())
self.as_bytes().consensus_encode(writer)
}
}

impl ConsensusEncodingSized for PublicKey {
impl ConsensusEncodingSized for ExecutionStack {
fn consensus_encode_exact_size(&self) -> usize {
let mut counter = ByteCounter::new();
// TODO: consensus_encode_exact_size must be cheap to run
Expand All @@ -143,6 +144,20 @@ mod impls {
}
}

//---------------------------------- PublicKey --------------------------------------------//

impl ConsensusEncoding for PublicKey {
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
writer.write(self.as_bytes())
}
}

impl ConsensusEncodingSized for PublicKey {
fn consensus_encode_exact_size(&self) -> usize {
PublicKey::key_length()
}
}

impl ConsensusDecoding for PublicKey {
fn consensus_decode<R: Read>(reader: &mut R) -> Result<Self, io::Error> {
let mut buf = [0u8; 32];
Expand All @@ -152,6 +167,30 @@ mod impls {
}
}

//---------------------------------- PrivateKey --------------------------------------------//

impl ConsensusEncoding for PrivateKey {
fn consensus_encode<W: io::Write>(&self, writer: &mut W) -> Result<usize, io::Error> {
writer.write(self.as_bytes())
}
}

impl ConsensusEncodingSized for PrivateKey {
fn consensus_encode_exact_size(&self) -> usize {
PrivateKey::key_length()
}
}

impl ConsensusDecoding for PrivateKey {
fn consensus_decode<R: Read>(reader: &mut R) -> Result<Self, io::Error> {
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
let sk =
PrivateKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
Ok(sk)
}
}

//---------------------------------- Commitment --------------------------------------------//

impl ConsensusEncoding for Commitment {
Expand Down Expand Up @@ -195,7 +234,7 @@ mod impls {

impl ConsensusEncodingSized for Signature {
fn consensus_encode_exact_size(&self) -> usize {
96
self.get_signature().consensus_encode_exact_size() + self.get_public_nonce().consensus_encode_exact_size()
}
}

Expand All @@ -205,7 +244,7 @@ mod impls {
reader.read_exact(&mut buf)?;
let pub_nonce =
PublicKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
let mut buf = [0u8; 64];
let mut buf = [0u8; 32];
reader.read_exact(&mut buf)?;
let sig =
PrivateKey::from_bytes(&buf[..]).map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?;
Expand Down
27 changes: 27 additions & 0 deletions base_layer/core/src/covenants/covenant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,9 @@ impl FromIterator<CovenantToken> for Covenant {

#[cfg(test)]
mod test {
use super::*;
use crate::{
consensus::ToConsensusBytes,
covenant,
covenants::test::{create_input, create_outputs},
};
Expand All @@ -152,4 +154,29 @@ mod test {
let num_matching_outputs = covenant.execute(0, &input, &outputs).unwrap();
assert_eq!(num_matching_outputs, 3);
}

mod consensus_encoding {
use super::*;

#[test]
fn it_encodes_to_empty_bytes() {
let bytes = Covenant::new().to_consensus_bytes();
assert_eq!(bytes.len(), 0);
}
}

mod consensus_decoding {
use super::*;

#[test]
fn it_is_identity_if_empty_bytes() {
let empty_buf = &[] as &[u8; 0];
let covenant = Covenant::consensus_decode(&mut &empty_buf[..]).unwrap();

let outputs = create_outputs(10, Default::default());
let input = create_input();
let num_selected = covenant.execute(0, &input, &outputs).unwrap();
assert_eq!(num_selected, 10);
}
}
}
6 changes: 6 additions & 0 deletions base_layer/core/src/covenants/decoder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,12 @@ mod test {
covenants::{arguments::CovenantArg, fields::OutputField, filters::CovenantFilter},
};

#[test]
fn it_immediately_ends_iterator_given_empty_bytes() {
let buf = &[] as &[u8; 0];
assert!(CovenantTokenDecoder::new(&mut &buf[..]).next().is_none());
}

#[test]
fn it_decodes_from_well_formed_bytes() {
let hash = from_hex("53563b674ba8e5166adb57afa8355bcf2ee759941eef8f8959b802367c2558bd").unwrap();
Expand Down
Loading

0 comments on commit 2ff6fed

Please sign in to comment.