Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Implement validate_coinbase function #14 #57

Merged
merged 12 commits into from
Aug 13, 2024
2 changes: 1 addition & 1 deletion src/state.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ pub struct Transaction {
#[derive(Drop, Copy)]
pub struct TxOut {
/// The value of the output in satoshis.
pub value: i64,
pub value: u64,
/// The spending script (aka locking code) for this output.
pub pk_script: @ByteArray,
}
Expand Down
24 changes: 24 additions & 0 deletions src/utils.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,27 @@ pub fn double_sha256(a: u256, b: u256) -> u256 {
low: x4.into() * TWO_POW_96 + x5.into() * TWO_POW_64 + x6.into() * TWO_POW_32 + x7.into(),
}
}

fn hex_to_byte(h: u8) -> u8 {
if h >= 48 && h <= 57 {
return h - 48;
} else if h >= 65 && h <= 70 {
return h - 55;
} else if h >= 97 && h <= 102 {
return h - 87;
}
panic!("Wrong hex character: {h}");
0
}

pub fn from_base16(hexs: @ByteArray) -> ByteArray {
let mut result: ByteArray = Default::default();
let mut i = 0;
let len = hexs.len();
while i < len {
result.append_word(hex_to_byte(hexs.at(i).unwrap()).into(), 4);
i += 1;
};

result
}
226 changes: 220 additions & 6 deletions src/validation.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
use core::traits::Into;
use core::traits::TryInto;

use super::merkle_tree::merkle_root;
use super::utils::{shl, shr};
use super::state::{Block, ChainState, Transaction, UtreexoState};
use super::state::{Block, ChainState, Transaction, UtreexoState, OutPoint};

const MAX_TARGET: u256 = 0x00000000FFFF0000000000000000000000000000000000000000000000000000;

Expand All @@ -11,7 +14,7 @@ impl BlockValidatorImpl of BlockValidator {

let (total_fees, merkle_root) = fee_and_merkle_root(@block)?;

validate_coinbase(@block, total_fees)?;
validate_coinbase(@block, total_fees, self.block_height)?;

let prev_timestamps = next_prev_timestamps(@self, @block);
let (current_target, epoch_start_time) = adjust_difficulty(@self, @block);
Expand Down Expand Up @@ -193,8 +196,40 @@ fn fee_and_merkle_root(block: @Block) -> Result<(u256, u256), ByteArray> {
Result::Ok((total_fee, merkle_root(ref txids)))
}

fn validate_coinbase(block: @Block, total_fees: u256) -> Result<(), ByteArray> {
//TODO implement
fn validate_coinbase(block: @Block, total_fees: u256, block_height: u32) -> Result<(), ByteArray> {
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
let tx = block.txs[0];

// Validate the coinbase input
// Ensure there is exactly one coinbase input
assert((*tx.inputs).len() == 1, 'Input count should be 1');

// Ensure the input's vout is 0xFFFFFFFF
assert(*tx.inputs[0].previous_output.vout == 0xFFFFFFFF, 'vout should be 0xFFFFFFFF');

// Ensure the input's TXID is zero
assert(*tx.inputs[0].previous_output.txid == 0, 'txid should be 0');

// Validate the outputs' amounts
// Sum up the total amount of all outputs
// and also add the outputs to the UtreexoSet.
let mut total_output_amount = 0;

for txs in *block
.txs {
for output in *txs.outputs {
total_output_amount + (*output.value);
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
//TODO add outputs to UtreexoSet
maciejka marked this conversation as resolved.
Show resolved Hide resolved

};
};

// Ensure the total output amount is at most the block reward + TX fees
let block_reward = compute_block_reward(block_height);
assert(
total_output_amount.into() <= total_fees + block_reward.into(),
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
'total output > block rwd + fees'
);

Result::Ok(())
}

Expand All @@ -206,11 +241,12 @@ fn compute_block_reward(block_height: u32) -> u64 {

#[cfg(test)]
mod tests {
use raito::state::{Header, Transaction, TxIn, TxOut};
use raito::state::{Header, Transaction, TxIn, TxOut, OutPoint};
use super::{
validate_timestamp, validate_proof_of_work, compute_block_reward, compute_total_work,
compute_work_from_target, shr, shl, Block, ChainState, UtreexoState,
compute_work_from_target, shr, shl, Block, ChainState, UtreexoState, validate_coinbase
};
use raito::utils::from_base16;


#[test]
Expand Down Expand Up @@ -350,4 +386,182 @@ mod tests {
let last_reward = compute_block_reward(max_halvings * block_height);
assert_eq!(last_reward, 0);
}

#[test]
#[should_panic(expected: ('Input count should be 1',))]
fn test_validate_coinbase_with_multiple_input() {
let block = Block {
header: Header { version: 1_u32, time: 1231006505_u32, bits: 1, nonce: 2083236893_u32 },
txs: array![
Transaction {
version: 1,
is_segwit: false,
inputs: array![
TxIn {
script: @from_base16(
@"01091d8d76a82122082246acbb6cc51c839d9012ddaca46048de07ca8eec221518200241cdb85fab4815c6c624d6e932774f3fdf5fa2a1d3a1614951afb83269e1454e2002443047"
),
sequence: 4294967295,
previous_output: OutPoint {
txid: 0_u256, vout: 0xffffffff_u32, txo_index: 0,
},
witness: array![from_base16(@"")].span()
},
TxIn {
script: @from_base16(
@"01091d8d76a82122082246acbb6cc51c839d9012ddaca46048de07ca8eec221518200241cdb85fab4815c6c624d6e932774f3fdf5fa2a1d3a1614951afb83269e1454e2002443047"
),
sequence: 4294967295,
previous_output: OutPoint {
txid: 0_u256, vout: 0xffffffff_u32, txo_index: 0,
},
witness: array![from_base16(@"")].span()
}
]
.span(),
outputs: array![
TxOut {
value: 5000000000_u64,
pk_script: @from_base16(
@"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"
),
}
]
.span(),
lock_time: 0
}
]
.span()
};
let total_fees = 5000000000_u64;
let block_height = 1;

validate_coinbase(@block, total_fees.into(), block_height);
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
#[should_panic(expected: ('vout should be 0xFFFFFFFF',))]
fn test_validate_coinbase_with_wrong_vout() {
let block = Block {
header: Header { version: 1_u32, time: 1231006505_u32, bits: 1, nonce: 2083236893_u32 },
txs: array![
Transaction {
version: 1,
is_segwit: false,
inputs: array![
TxIn {
script: @from_base16(
@"01091d8d76a82122082246acbb6cc51c839d9012ddaca46048de07ca8eec221518200241cdb85fab4815c6c624d6e932774f3fdf5fa2a1d3a1614951afb83269e1454e2002443047"
),
sequence: 4294967295,
previous_output: OutPoint {
txid: 0_u256, vout: 0x1_u32, txo_index: 0,
},
witness: array![from_base16(@"")].span()
}
]
.span(),
outputs: array![
TxOut {
value: 5000000000_u64,
pk_script: @from_base16(
@"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"
),
}
]
.span(),
lock_time: 0
}
]
.span()
};
let total_fees = 5000000000_u64;
let block_height = 1;

validate_coinbase(@block, total_fees.into(), block_height);
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
}

#[test]
#[should_panic(expected: ('txid should be 0',))]
fn test_validate_coinbase_with_txid_not_zero() {
let block = Block {
header: Header { version: 1_u32, time: 1231006505_u32, bits: 1, nonce: 2083236893_u32 },
txs: array![
Transaction {
version: 1,
is_segwit: false,
inputs: array![
TxIn {
script: @from_base16(
@"01091d8d76a82122082246acbb6cc51c839d9012ddaca46048de07ca8eec221518200241cdb85fab4815c6c624d6e932774f3fdf5fa2a1d3a1614951afb83269e1454e2002443047"
),
sequence: 4294967295,
previous_output: OutPoint {
txid: 0x2_u256, vout: 0xFFFFFFFF_u32, txo_index: 0,
},
witness: array![from_base16(@"")].span()
}
]
.span(),
outputs: array![
TxOut {
value: 5000000000_u64,
pk_script: @from_base16(
@"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"
),
}
]
.span(),
lock_time: 0
}
]
.span()
};
let total_fees = 5000000000_u64;
let block_height = 1;

validate_coinbase(@block, total_fees.into(), block_height);
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
}
// #[test]
// #[should_panic(expected: ('total output > block rwd + fees',))]
// fn test_validate_coinbase_outputs_amount() {
manlikeHB marked this conversation as resolved.
Show resolved Hide resolved
// let block = Block {
// header: Header { version: 1_u32, time: 1231006505_u32, bits: 1, nonce: 2083236893_u32
// }, txs: array![
// Transaction {
// version: 1,
// is_segwit: false,
// inputs: array![
// TxIn {
// script: @from_base16(
// @"01091d8d76a82122082246acbb6cc51c839d9012ddaca46048de07ca8eec221518200241cdb85fab4815c6c624d6e932774f3fdf5fa2a1d3a1614951afb83269e1454e2002443047"
// ),
// sequence: 4294967295,
// previous_output: OutPoint {
// txid: 0_u256, vout: 0xFFFFFFFF_u32, txo_index: 0,
// },
// witness: array![from_base16(@"")].span()
// }
// ]
// .span(),
// outputs: array![
// TxOut {
// value: 5000000000_u64,
// pk_script: @from_base16(
// @"4104678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5fac"
// ),
// }
// ]
// .span(),
// lock_time: 0
// }
// ]
// .span()
// };

// let total_fees = 5000000000_u64;
// let block_height = 856_563;

// validate_coinbase(@block, total_fees.into(), block_height);
// }
}
Loading