From 4767cabd9281960422d2afab3ac4e9cd60d69a69 Mon Sep 17 00:00:00 2001 From: Thomas Marchand Date: Fri, 25 Oct 2024 13:55:32 +0100 Subject: [PATCH] fix: height_proof in set_canonical_chain tests --- src/bitcoin/transactions/coinbase.cairo | 53 +++++++++--- src/tests/fork_resolutions.cairo | 105 ++++++++++++++++++++++-- src/utu_relay.cairo | 39 ++++++--- 3 files changed, 169 insertions(+), 28 deletions(-) diff --git a/src/bitcoin/transactions/coinbase.cairo b/src/bitcoin/transactions/coinbase.cairo index bf8ea38..9d2e1b7 100644 --- a/src/bitcoin/transactions/coinbase.cairo +++ b/src/bitcoin/transactions/coinbase.cairo @@ -1,5 +1,6 @@ use crate::utils::{hash::Digest, double_sha256::double_sha256_byte_array}; +#[derive(Drop)] pub struct CoinbaseData { pub tx_id: Digest, pub height: u64 @@ -23,9 +24,14 @@ pub struct CoinbaseData { pub fn get_coinbase_data(raw_tx: @ByteArray) -> CoinbaseData { let tx_id: Digest = double_sha256_byte_array(raw_tx); + + let mut shift = 0; // would be zero for segwit transaction - let input_count = raw_tx[4]; - if input_count != 1 { + if raw_tx[4] == 0 { + // we skip marker and flag + shift += 2; + } + if raw_tx[4 + shift] != 1 { panic!("A coinbase transaction input count must be 1."); }; // we then check this single input is transaction hash 0x0 @@ -34,24 +40,25 @@ pub fn get_coinbase_data(raw_tx: @ByteArray) -> CoinbaseData { if prev_tx_i == 37 { break; } - if raw_tx[prev_tx_i] != 0 { + if raw_tx[prev_tx_i + shift] != 0 { panic!("The single coinbase input tx hash input must be 0x0.") }; prev_tx_i += 1; }; // Read the compactSize value starting at index 41, almost certainly in first branch - let first_byte = raw_tx[41]; + let first_byte = raw_tx[41 + shift]; // the first byte of coinbase data is a marker, then the next 3 bytes hold the block height - let coinbase_height_start_index = if first_byte == 0xfd { - 44 // 2-byte varint - } else if first_byte == 0xfe { - 46 // 4-byte varint - } else if first_byte == 0xff { - 50 // 8-byte varint - } else { - 42 // Single byte varint (default case) - }; + let coinbase_height_start_index = shift + + if first_byte == 0xfd { + 44 // 2-byte varint + } else if first_byte == 0xfe { + 46 // 4-byte varint + } else if first_byte == 0xff { + 50 // 8-byte varint + } else { + 42 // Single byte varint (default case) + }; if raw_tx[coinbase_height_start_index] != 0x3 { panic!( @@ -90,4 +97,24 @@ mod tests { assert(coinbase_data.height == 227_836, 'Unexpected block height'); } + + + #[test] + fn test_get_coinbase_data_2() { + // hex raw transaction of 7b20c53b4b4e962d688d4bb49fb43a53eeb117d4dfb9bcf349a8c7686e74d9e6 + // which is the coinbase tx from block 865_698, the first one to include block height + let coinbase_tx_raw_data = from_hex( + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5603a2350d194d696e656420627920416e74506f6f6c20e2002a00b4747806fabe6d6d710d04d1ea50a50e6329e2fa1ab865fa16083c12ae90926ee2d687d78e64243210000000000000000000e2701102000000000000ffffffff05220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade787aca64c130000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed70918c35041817b2ce38010673f3420b25a34c0480a9e53a280f7eb1b0ccffdc00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d54e3ecda72cb7961caa4b541b1e322bcfe0b5a03000000000000000002b6a2952534b424c4f434b3a900671964dfd53207639c814270d32fbb670620e4ddd8821dd3d5c100067a1e300000000" + ); + let coinbase_data = get_coinbase_data(@coinbase_tx_raw_data); + assert( + coinbase_data + .tx_id == hex_to_hash_rev( + "7b20c53b4b4e962d688d4bb49fb43a53eeb117d4dfb9bcf349a8c7686e74d9e6" + ), + 'Unexpected coinbase tx id' + ); + + assert(coinbase_data.height == 865_698, 'Unexpected block height'); + } } diff --git a/src/tests/fork_resolutions.cairo b/src/tests/fork_resolutions.cairo index 1d4a82b..8979c35 100644 --- a/src/tests/fork_resolutions.cairo +++ b/src/tests/fork_resolutions.cairo @@ -1,6 +1,6 @@ use super::super::interfaces::IUtuRelayDispatcherTrait; use crate::{ - interfaces::BlockStatus, utils::{hex::hex_to_hash_rev, hash::Digest}, + interfaces::BlockStatus, utils::{hex::{from_hex, hex_to_hash_rev}, hash::Digest}, bitcoin::block::{BlockHeader, BlockHeaderTrait}, tests::utils::{deploy_utu, BlockStatusIntoSpan, DigestIntoSpan}, }; @@ -70,11 +70,31 @@ fn test_replacing_by_longer_chain() { ]; utu.register_blocks(block_headers.span()); + let coinbase_tx_raw_data = from_hex( + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5603a2350d194d696e656420627920416e74506f6f6c20e2002a00b4747806fabe6d6d710d04d1ea50a50e6329e2fa1ab865fa16083c12ae90926ee2d687d78e64243210000000000000000000e2701102000000000000ffffffff05220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade787aca64c130000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed70918c35041817b2ce38010673f3420b25a34c0480a9e53a280f7eb1b0ccffdc00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d54e3ecda72cb7961caa4b541b1e322bcfe0b5a03000000000000000002b6a2952534b424c4f434b3a900671964dfd53207639c814270d32fbb670620e4ddd8821dd3d5c100067a1e300000000" + ); + + let merkle_branch = [ + hex_to_hash_rev("1a523b9d1db74158daea077276b1cd3f3eac87cd685d136133d414d04d7f1aed"), + hex_to_hash_rev("f3b0b4fced6987a1c9a5f2f70eb245585d02bf8579c4ec32d08faa88285738b7"), + hex_to_hash_rev("ea38f07950ea6fd120699c1b77578a8e8f922d4b6640345f5ac264d11b4598da"), + hex_to_hash_rev("4385664ebdd2988d5293167646a791d4ec3e165be0012424c70a842270bba968"), + hex_to_hash_rev("fa19b3bda922723a31ea7d1cfe474cd9f52a8d70b28c481c6567f92581b4d0ab"), + hex_to_hash_rev("47f2f41cdede72662413e02b0a646b3c5f5b4f2629a81f83ad903db78302ef19"), + hex_to_hash_rev("03c644a689e264ed605517e22929b7c6e972d2ad2e942bfeaea50cae8f3a598d"), + hex_to_hash_rev("5041fe4b358c487b01cec6b2ba8780d831465bcae3b748f7e2377915a58ee0f4"), + hex_to_hash_rev("67a27f730a4d4ac69937afb8be7464564710fa2ddcbffc1cdc1cccd4543c6d36"), + hex_to_hash_rev("7602d7c94c5f25b91937fc5ac1c4a3f2cffca3426ca1df7fa3fabd690755f46d"), + hex_to_hash_rev("29c3f6ea6e559e9f03625061f89f405ab7341b32e1e3d3507de104c506e92af4"), + hex_to_hash_rev("d9d0c63e9fdd9a4b278cb41c669405073988f75baaa7719fcda0650badc4b2a2"), + hex_to_hash_rev("372d13ce7f696f978dbf135af7f3658df37126abf93c0996f92ff8f709081ef0") + ]; + let height_proof = (block_865_698, coinbase_tx_raw_data, merkle_branch.span()); + // this should set the chain to an orphan block - utu.update_canonical_chain(865_698, 865_699, block_865_699_hash_1, Option::None); + utu.update_canonical_chain(865_698, 865_699, block_865_699_hash_1, Option::Some(height_proof)); let orphan_digest = utu.get_block(865_699); assert(orphan_digest == block_865_699_hash_1, 'wrong orphan digest'); - // then this should correct it because the canonical chain is stronger utu.update_canonical_chain(865_698, 865_700, block_865_700_hash, Option::None); let updated_block_digest = utu.get_block(865_699); @@ -84,6 +104,36 @@ fn test_replacing_by_longer_chain() { assert(utu.get_block(865_700) == block_865_700_hash, 'wrong last block'); } +#[test] +#[should_panic( + expected: "You must provide a height proof if you don't continue the canonical chain." +)] +fn test_missing_height_proof() { + let utu = deploy_utu(); + + start_cheat_block_timestamp(utu.contract_address, 1_728_969_360); + + // Block 865_698 + let block_865_698_hash = hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ); + let block_865_698: BlockHeader = BlockHeaderTrait::new( + 582_238_208_u32, + hex_to_hash_rev("0000000000000000000135e8b5214c6de06ad988280816ce0daa1d92317c4904"), + hex_to_hash_rev("219394ee994ef9dda390b34d6ef8d7fb3e24a05b2c29f02c1d7839aa6c154787"), + 1_728_969_360_u32, + 0x17030ecd_u32, + 3_876_725_546, + ); + + // Register all blocks + let block_headers: Array = array![block_865_698]; + utu.register_blocks(block_headers.span()); + + // this should panic + utu.update_canonical_chain(865_698, 865_698, block_865_698_hash, Option::None); +} + #[test] #[should_panic(expected: "Canonical chain has a stronger cumulated pow than your proposed fork.")] @@ -144,8 +194,28 @@ fn test_replacing_by_shorter_chain() { ]; utu.register_blocks(block_headers.span()); + let coinbase_tx_raw_data = from_hex( + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5603a2350d194d696e656420627920416e74506f6f6c20e2002a00b4747806fabe6d6d710d04d1ea50a50e6329e2fa1ab865fa16083c12ae90926ee2d687d78e64243210000000000000000000e2701102000000000000ffffffff05220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade787aca64c130000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed70918c35041817b2ce38010673f3420b25a34c0480a9e53a280f7eb1b0ccffdc00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d54e3ecda72cb7961caa4b541b1e322bcfe0b5a03000000000000000002b6a2952534b424c4f434b3a900671964dfd53207639c814270d32fbb670620e4ddd8821dd3d5c100067a1e300000000" + ); + let merkle_branch = [ + hex_to_hash_rev("1a523b9d1db74158daea077276b1cd3f3eac87cd685d136133d414d04d7f1aed"), + hex_to_hash_rev("f3b0b4fced6987a1c9a5f2f70eb245585d02bf8579c4ec32d08faa88285738b7"), + hex_to_hash_rev("ea38f07950ea6fd120699c1b77578a8e8f922d4b6640345f5ac264d11b4598da"), + hex_to_hash_rev("4385664ebdd2988d5293167646a791d4ec3e165be0012424c70a842270bba968"), + hex_to_hash_rev("fa19b3bda922723a31ea7d1cfe474cd9f52a8d70b28c481c6567f92581b4d0ab"), + hex_to_hash_rev("47f2f41cdede72662413e02b0a646b3c5f5b4f2629a81f83ad903db78302ef19"), + hex_to_hash_rev("03c644a689e264ed605517e22929b7c6e972d2ad2e942bfeaea50cae8f3a598d"), + hex_to_hash_rev("5041fe4b358c487b01cec6b2ba8780d831465bcae3b748f7e2377915a58ee0f4"), + hex_to_hash_rev("67a27f730a4d4ac69937afb8be7464564710fa2ddcbffc1cdc1cccd4543c6d36"), + hex_to_hash_rev("7602d7c94c5f25b91937fc5ac1c4a3f2cffca3426ca1df7fa3fabd690755f46d"), + hex_to_hash_rev("29c3f6ea6e559e9f03625061f89f405ab7341b32e1e3d3507de104c506e92af4"), + hex_to_hash_rev("d9d0c63e9fdd9a4b278cb41c669405073988f75baaa7719fcda0650badc4b2a2"), + hex_to_hash_rev("372d13ce7f696f978dbf135af7f3658df37126abf93c0996f92ff8f709081ef0") + ]; + let height_proof = (block_865_698, coinbase_tx_raw_data, merkle_branch.span()); + // we set the canonical chain to the stronger canonical chain - utu.update_canonical_chain(865_698, 865_700, block_865_700_hash, Option::None); + utu.update_canonical_chain(865_698, 865_700, block_865_700_hash, Option::Some(height_proof)); // then we try to update to an orphan block utu.update_canonical_chain(865_698, 865_699, block_865_699_hash_1, Option::None); @@ -201,8 +271,28 @@ fn test_replacing_by_equal_chain() { let block_headers: Array = array![block_865_698, block_865_699_1, block_865_699_2]; utu.register_blocks(block_headers.span()); + let coinbase_tx_raw_data = from_hex( + "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff5603a2350d194d696e656420627920416e74506f6f6c20e2002a00b4747806fabe6d6d710d04d1ea50a50e6329e2fa1ab865fa16083c12ae90926ee2d687d78e64243210000000000000000000e2701102000000000000ffffffff05220200000000000017a91442402a28dd61f2718a4b27ae72a4791d5bbdade787aca64c130000000017a9145249bdf2c131d43995cff42e8feee293f79297a8870000000000000000266a24aa21a9ed70918c35041817b2ce38010673f3420b25a34c0480a9e53a280f7eb1b0ccffdc00000000000000002f6a2d434f52450164db24a662e20bbdf72d1cc6e973dbb2d12897d54e3ecda72cb7961caa4b541b1e322bcfe0b5a03000000000000000002b6a2952534b424c4f434b3a900671964dfd53207639c814270d32fbb670620e4ddd8821dd3d5c100067a1e300000000" + ); + let merkle_branch = [ + hex_to_hash_rev("1a523b9d1db74158daea077276b1cd3f3eac87cd685d136133d414d04d7f1aed"), + hex_to_hash_rev("f3b0b4fced6987a1c9a5f2f70eb245585d02bf8579c4ec32d08faa88285738b7"), + hex_to_hash_rev("ea38f07950ea6fd120699c1b77578a8e8f922d4b6640345f5ac264d11b4598da"), + hex_to_hash_rev("4385664ebdd2988d5293167646a791d4ec3e165be0012424c70a842270bba968"), + hex_to_hash_rev("fa19b3bda922723a31ea7d1cfe474cd9f52a8d70b28c481c6567f92581b4d0ab"), + hex_to_hash_rev("47f2f41cdede72662413e02b0a646b3c5f5b4f2629a81f83ad903db78302ef19"), + hex_to_hash_rev("03c644a689e264ed605517e22929b7c6e972d2ad2e942bfeaea50cae8f3a598d"), + hex_to_hash_rev("5041fe4b358c487b01cec6b2ba8780d831465bcae3b748f7e2377915a58ee0f4"), + hex_to_hash_rev("67a27f730a4d4ac69937afb8be7464564710fa2ddcbffc1cdc1cccd4543c6d36"), + hex_to_hash_rev("7602d7c94c5f25b91937fc5ac1c4a3f2cffca3426ca1df7fa3fabd690755f46d"), + hex_to_hash_rev("29c3f6ea6e559e9f03625061f89f405ab7341b32e1e3d3507de104c506e92af4"), + hex_to_hash_rev("d9d0c63e9fdd9a4b278cb41c669405073988f75baaa7719fcda0650badc4b2a2"), + hex_to_hash_rev("372d13ce7f696f978dbf135af7f3658df37126abf93c0996f92ff8f709081ef0") + ]; + let height_proof = (block_865_698, coinbase_tx_raw_data, merkle_branch.span()); + // we set the canonical chain to the canonical chain - utu.update_canonical_chain(865_698, 865_700, block_865_699_hash_2, Option::None); + utu.update_canonical_chain(865_698, 865_699, block_865_699_hash_2, Option::Some(height_proof)); // then we try to update to an orphan block (should be refused so that you can't update back and // forth) @@ -275,6 +365,11 @@ fn test_replacing_by_longer_but_weaker_chain() { // b) [ 0x1, 0x2b, 0x3b, 0x4], 1000, 500, 500, 500 // where a[1:] cpow equals 1999 > 1500 for b[1:] even though b is longer + // so we skip height verification + let chain_block_1_addr = + 3057458122501230473334132957886155656455173919071911735801003915624585018607; + store(utu.contract_address, chain_block_1_addr, block1_digest.into()); + utu.update_canonical_chain(1, 3, block3a_digest, Option::None); utu.update_canonical_chain(1, 4, block4_digest, Option::None); } diff --git a/src/utu_relay.cairo b/src/utu_relay.cairo index e382ab6..10bc206 100644 --- a/src/utu_relay.cairo +++ b/src/utu_relay.cairo @@ -62,13 +62,16 @@ pub mod UtuRelay { end_block_hash: Digest, height_proof: Option<(BlockHeader, ByteArray, Span)> ) { + let mut requires_height_proof = true; // This helper will write the ancestry of end_block_hash over [begin_height, end_height] // with chain[end_height] holding end_block_hash. If it overwrote some blocks, it // returns the cumulated pow of the overwritten blocks (current) and the fork (new). let (mut current_cpow, new_cpow) = self - .update_canonical_chain_helper(end_block_hash, end_height, begin_height - 1); + .update_canonical_chain_helper( + ref requires_height_proof, end_block_hash, end_height, begin_height - 1 + ); - if self.chain.read(begin_height - 1).is_zero() { + if requires_height_proof { match height_proof { Option::None => { panic!( @@ -79,6 +82,10 @@ pub mod UtuRelay { header, coinbase_raw_data, merkle_proof )) => { if self.chain.read(begin_height) != header.hash() { + println!("begin_height: {}", begin_height); + println!( + "hashes: {}, {}", self.chain.read(begin_height), header.hash() + ); panic!( "Your provided proof doesn't correspond to the begin block height." ); @@ -145,7 +152,11 @@ pub mod UtuRelay { #[generate_trait] pub impl InternalImpl of InternalTrait { fn update_canonical_chain_helper( - ref self: ContractState, new_block_digest: Digest, block_index: u64, stop_index: u64, + ref self: ContractState, + ref requires_height_proof: bool, + new_block_digest: Digest, + block_index: u64, + stop_index: u64, ) -> (u128, u128) { // fetch the block stored in the chain let block_digest_entry = self.chain.entry(block_index); @@ -158,12 +169,15 @@ pub mod UtuRelay { // For honest users, simply provide the correct replacement blocks if block_index == stop_index { // new_block_digest is previous_block_digest of his son we just processed - if current_block_digest != Zero::zero() - && current_block_digest != new_block_digest { - panic!( - "Canonical chain block preceding your proposed fork is inconsistent. Please provide a stronger replacement." - ); - // if there is no block, we need a height_proof + if current_block_digest != Zero::zero() { + if current_block_digest != new_block_digest { + panic!( + "Canonical chain block preceding your proposed fork is inconsistent. Please provide a stronger replacement." + ); + } else { + // if there is a valid connecting block, we don't need a height_proof + requires_height_proof = false; + }; } return (0, 0); @@ -178,11 +192,16 @@ pub mod UtuRelay { let (cpow, new_cpow) = self .update_canonical_chain_helper( - new_block.prev_block_digest, block_index - 1, stop_index + ref requires_height_proof, + new_block.prev_block_digest, + block_index - 1, + stop_index ); // if there was no conflict before if current_block_digest == new_block_digest { + // this block was already present, no need for a height proof + requires_height_proof = false; return (0, 0); // if there was a conflict (may be), we measure cpow & new_cpow } else {