diff --git a/src/interfaces.cairo b/src/interfaces.cairo index e54dabc..900cb94 100644 --- a/src/interfaces.cairo +++ b/src/interfaces.cairo @@ -7,7 +7,6 @@ pub struct BlockStatus { // memory pub registration_timestamp: u64, pub prev_block_digest: Digest, - pub challenged_cpow: u128, pub pow: u128, } @@ -19,12 +18,7 @@ pub impl BlockStatusImpl of BlockStatusTrait { } fn new(prev_block_digest: Digest, pow: u128) -> BlockStatus { - BlockStatus { - prev_block_digest, - registration_timestamp: get_block_timestamp(), - challenged_cpow: 0, - pow, - } + BlockStatus { prev_block_digest, registration_timestamp: get_block_timestamp(), pow, } } } @@ -52,4 +46,6 @@ pub trait IUtuRelay { ) -> bool; fn get_status(self: @TContractState, block_hash: Digest) -> BlockStatus; + + fn get_block(self: @TContractState, height: u64) -> Digest; } diff --git a/src/tests/blocks_registration.cairo b/src/tests/blocks_registration.cairo index 85f49d9..3a76a6d 100644 --- a/src/tests/blocks_registration.cairo +++ b/src/tests/blocks_registration.cairo @@ -43,7 +43,6 @@ fn test_single_block_registration() { prev_block_digest: hex_to_hash_rev( "000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55" ), - challenged_cpow: 0, pow: 4295032833, }, 'unexpected final status' @@ -102,7 +101,6 @@ fn test_two_blocks_registration() { prev_block_digest: hex_to_hash_rev( "000000002a22cfee1f2c846adbd12b3e183d4f97683f85dad08a79780a84bd55" ), - challenged_cpow: 0, pow: 4295032833, }, 'unexpected status for block 170' @@ -112,10 +110,7 @@ fn test_two_blocks_registration() { let status_171 = utu.get_status(block_171_hash); assert( status_171 == BlockStatus { - registration_timestamp: 1234567890, - prev_block_digest: block_170_hash, - challenged_cpow: 0, - pow: 4295032833, + registration_timestamp: 1234567890, prev_block_digest: block_170_hash, pow: 4295032833, }, 'unexpected status for block 171' ); @@ -175,7 +170,6 @@ fn test_same_height_registration() { prev_block_digest: hex_to_hash_rev( "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" ), - challenged_cpow: 0, pow: 395356030850084270690399, }, 'unexpected status for block 1' @@ -189,7 +183,6 @@ fn test_same_height_registration() { prev_block_digest: hex_to_hash_rev( "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" ), - challenged_cpow: 0, pow: 395356030850084270690399, }, 'unexpected status for block 2' diff --git a/src/tests/fork_resolutions.cairo b/src/tests/fork_resolutions.cairo index e69de29..4fdc71f 100644 --- a/src/tests/fork_resolutions.cairo +++ b/src/tests/fork_resolutions.cairo @@ -0,0 +1,355 @@ +use super::super::interfaces::IUtuRelayDispatcherTrait; +use crate::{ + interfaces::BlockStatus, utils::{hex::hex_to_hash_rev, hash::Digest}, + bitcoin::block::{BlockHeader, HumanReadableBlockHeader}, + tests::utils::{deploy_utu, BlockStatusIntoSpan, DigestIntoSpan}, +}; +use snforge_std::{start_cheat_block_timestamp, store, load, map_entry_address}; + +#[test] +fn test_replacing_by_longer_chain() { + 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 = HumanReadableBlockHeader { + version: 582_238_208_u32, + prev_block_hash: hex_to_hash_rev( + "0000000000000000000135e8b5214c6de06ad988280816ce0daa1d92317c4904" + ), + merkle_root_hash: hex_to_hash_rev( + "219394ee994ef9dda390b34d6ef8d7fb3e24a05b2c29f02c1d7839aa6c154787" + ), + time: 1_728_969_360_u32, + bits: 0x17030ecd_u32, + nonce: 3_876_725_546, + } + .into(); + + // Block 865_699 (first version) + let block_865_699_hash_1 = hex_to_hash_rev( + "00000000000000000002648ba35429c4e46e38d5261331bbddd7244baf94d515" + ); + let block_865_699_1: BlockHeader = HumanReadableBlockHeader { + version: 632_832_000_u32, + prev_block_hash: hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ), + merkle_root_hash: hex_to_hash_rev( + "c491a795a7e3e7286426c15a623e03a80f31cf75fca08b31eb6a325cfe17b5e2" + ), + time: 1_728_969_824_u32, + bits: 0x17030ecd_u32, + nonce: 2_662_381_191, + } + .into(); + + // Block 865_699 (second version, canonical chain) + let block_865_699_hash_2 = hex_to_hash_rev( + "00000000000000000000e7a78ccc708a62c6e04e8a4b5ef3bf4abd7a4c1b5b10" + ); + let block_865_699_2: BlockHeader = HumanReadableBlockHeader { + version: 905_969_664_u32, + prev_block_hash: hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ), + merkle_root_hash: hex_to_hash_rev( + "92888eb107c908635e5b5061ed2ac76744ba875661cfb1b77fe39f4c8dc60b11" + ), + time: 1_728_969_853_u32, + bits: 0x17030ecd_u32, + nonce: 3_760_750_539, + } + .into(); + + // Block 865_700 (canonical chain) + let block_865_700_hash = hex_to_hash_rev( + "00000000000000000002a82a6dee77c45ecd5a072a6ad9fe31d818ff62f0d16b" + ); + let block_865_700: BlockHeader = HumanReadableBlockHeader { + version: 873_521_152_u32, + prev_block_hash: hex_to_hash_rev( + "00000000000000000000e7a78ccc708a62c6e04e8a4b5ef3bf4abd7a4c1b5b10" + ), + merkle_root_hash: hex_to_hash_rev( + "2c5b149489af4585aefd2e8954de51ea461e2ebfb0b15ecf88aa1e94d276c997" + ), + time: 1_728_970_451_u32, + bits: 0x17030ecd_u32, + nonce: 3_606_011_432, + } + .into(); + + // Register all blocks + let block_headers: Array = array![ + block_865_698, block_865_699_1, block_865_699_2, block_865_700 + ]; + utu.register_blocks(block_headers.span()); + + // this should set the chain to an orphan block + utu.set_main_chain(865_698, 865_699, block_865_699_hash_1); + 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.set_main_chain(865_698, 865_700, block_865_700_hash); + let updated_block_digest = utu.get_block(865_699); + assert(updated_block_digest == block_865_699_hash_2, 'wrong replaced digest'); + + assert(utu.get_block(865_698) == block_865_698_hash, 'wrong first block'); + assert(utu.get_block(865_700) == block_865_700_hash, 'wrong last block'); +} + + +#[test] +#[should_panic(expected: "Main chain has a stronger cpow than your proposed fork last block pow.")] +fn test_replacing_by_shorter_chain() { + let utu = deploy_utu(); + + start_cheat_block_timestamp(utu.contract_address, 1_728_969_360); + + // Block 865_698 + let block_865_698: BlockHeader = HumanReadableBlockHeader { + version: 582_238_208_u32, + prev_block_hash: hex_to_hash_rev( + "0000000000000000000135e8b5214c6de06ad988280816ce0daa1d92317c4904" + ), + merkle_root_hash: hex_to_hash_rev( + "219394ee994ef9dda390b34d6ef8d7fb3e24a05b2c29f02c1d7839aa6c154787" + ), + time: 1_728_969_360_u32, + bits: 0x17030ecd_u32, + nonce: 3_876_725_546, + } + .into(); + + // Block 865_699 (first version) + let block_865_699_hash_1 = hex_to_hash_rev( + "00000000000000000002648ba35429c4e46e38d5261331bbddd7244baf94d515" + ); + let block_865_699_1: BlockHeader = HumanReadableBlockHeader { + version: 632_832_000_u32, + prev_block_hash: hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ), + merkle_root_hash: hex_to_hash_rev( + "c491a795a7e3e7286426c15a623e03a80f31cf75fca08b31eb6a325cfe17b5e2" + ), + time: 1_728_969_824_u32, + bits: 0x17030ecd_u32, + nonce: 2_662_381_191, + } + .into(); + + // Block 865_699 (second version, canonical chain) + let block_865_699_2: BlockHeader = HumanReadableBlockHeader { + version: 905_969_664_u32, + prev_block_hash: hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ), + merkle_root_hash: hex_to_hash_rev( + "92888eb107c908635e5b5061ed2ac76744ba875661cfb1b77fe39f4c8dc60b11" + ), + time: 1_728_969_853_u32, + bits: 0x17030ecd_u32, + nonce: 3_760_750_539, + } + .into(); + + // Block 865_700 (canonical chain) + let block_865_700_hash = hex_to_hash_rev( + "00000000000000000002a82a6dee77c45ecd5a072a6ad9fe31d818ff62f0d16b" + ); + let block_865_700: BlockHeader = HumanReadableBlockHeader { + version: 873_521_152_u32, + prev_block_hash: hex_to_hash_rev( + "00000000000000000000e7a78ccc708a62c6e04e8a4b5ef3bf4abd7a4c1b5b10" + ), + merkle_root_hash: hex_to_hash_rev( + "2c5b149489af4585aefd2e8954de51ea461e2ebfb0b15ecf88aa1e94d276c997" + ), + time: 1_728_970_451_u32, + bits: 0x17030ecd_u32, + nonce: 3_606_011_432, + } + .into(); + + // Register all blocks + let block_headers: Array = array![ + block_865_698, block_865_699_1, block_865_699_2, block_865_700 + ]; + utu.register_blocks(block_headers.span()); + + // we set the main chain to the stronger canonical chain + utu.set_main_chain(865_698, 865_700, block_865_700_hash); + + // then we try to update to an orphan block + utu.set_main_chain(865_698, 865_699, block_865_699_hash_1); + let orphan_digest = utu.get_block(865_699); + assert(orphan_digest == block_865_699_hash_1, 'wrong orphan digest'); +} + + +#[test] +#[should_panic(expected: "Main chain has a stronger cpow than your proposed fork last block pow.")] +fn test_replacing_by_equal_chain() { + let utu = deploy_utu(); + + start_cheat_block_timestamp(utu.contract_address, 1_728_969_360); + + // Block 865_698 + let block_865_698: BlockHeader = HumanReadableBlockHeader { + version: 582_238_208_u32, + prev_block_hash: hex_to_hash_rev( + "0000000000000000000135e8b5214c6de06ad988280816ce0daa1d92317c4904" + ), + merkle_root_hash: hex_to_hash_rev( + "219394ee994ef9dda390b34d6ef8d7fb3e24a05b2c29f02c1d7839aa6c154787" + ), + time: 1_728_969_360_u32, + bits: 0x17030ecd_u32, + nonce: 3_876_725_546, + } + .into(); + + // Block 865_699 (first version) + let block_865_699_hash_1 = hex_to_hash_rev( + "00000000000000000002648ba35429c4e46e38d5261331bbddd7244baf94d515" + ); + let block_865_699_1: BlockHeader = HumanReadableBlockHeader { + version: 632_832_000_u32, + prev_block_hash: hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ), + merkle_root_hash: hex_to_hash_rev( + "c491a795a7e3e7286426c15a623e03a80f31cf75fca08b31eb6a325cfe17b5e2" + ), + time: 1_728_969_824_u32, + bits: 0x17030ecd_u32, + nonce: 2_662_381_191, + } + .into(); + + // Block 865_699 (second version, canonical chain) + let block_865_699_hash_2 = hex_to_hash_rev( + "00000000000000000000e7a78ccc708a62c6e04e8a4b5ef3bf4abd7a4c1b5b10" + ); + let block_865_699_2: BlockHeader = HumanReadableBlockHeader { + version: 905_969_664_u32, + prev_block_hash: hex_to_hash_rev( + "000000000000000000012bc4e973e18e17b9980ba5b6fe545a5f05e0e222828c" + ), + merkle_root_hash: hex_to_hash_rev( + "92888eb107c908635e5b5061ed2ac76744ba875661cfb1b77fe39f4c8dc60b11" + ), + time: 1_728_969_853_u32, + bits: 0x17030ecd_u32, + nonce: 3_760_750_539, + } + .into(); + + // Register all blocks + let block_headers: Array = array![block_865_698, block_865_699_1, block_865_699_2]; + utu.register_blocks(block_headers.span()); + + // we set the main chain to the canonical chain + utu.set_main_chain(865_698, 865_700, block_865_699_hash_2); + + // then we try to update to an orphan block (should be refused so that you can't update back and + // forth) + utu.set_main_chain(865_698, 865_699, block_865_699_hash_1); + let orphan_digest = utu.get_block(865_699); + assert(orphan_digest == block_865_699_hash_1, 'wrong orphan digest'); +} + +#[test] +//#[should_panic(expected: "Main chain has a single block stronger than your proposed fork.")] +fn test_replacing_by_longer_but_weaker_chain() { + let utu = deploy_utu(); + // a random timestamp + let registration_timestamp = 1_728_969_360; + // we store a BlockStatus with hash 0x1 and pow 999_999 + + let block1 = BlockStatus { + registration_timestamp, prev_block_digest: 0x0_u256.into(), pow: 1_000, + }; + let block1_digest: Digest = 0x1_u256.into(); + + let block2a = BlockStatus { + registration_timestamp, prev_block_digest: block1_digest, pow: 1_000, + }; + let block2a_digest: Digest = 0x2a_u256.into(); + + let block2b = BlockStatus { + registration_timestamp, prev_block_digest: block1_digest, pow: 500, + }; + let block2b_digest: Digest = 0x2b_u256.into(); + + let block3a = BlockStatus { + registration_timestamp, prev_block_digest: block2a_digest, pow: 999, + }; + let block3a_digest: Digest = 0x3a_u256.into(); + + let block3b = BlockStatus { + registration_timestamp, prev_block_digest: block2b_digest, pow: 500, + }; + let block3b_digest: Digest = 0x3b_u256.into(); + + let block4 = BlockStatus { + registration_timestamp, prev_block_digest: block3b_digest, pow: 500, + }; + let block4_digest: Digest = 0x4_u256.into(); + + store( + utu.contract_address, + map_entry_address(selector!("blocks"), block1_digest.into()), + block1.into() + ); + + store( + utu.contract_address, + map_entry_address(selector!("blocks"), block2a_digest.into()), + block2a.into() + ); + + store( + utu.contract_address, + map_entry_address(selector!("blocks"), block2b_digest.into()), + block2b.into() + ); + + store( + utu.contract_address, + map_entry_address(selector!("blocks"), block3a_digest.into()), + block3a.into() + ); + + store( + utu.contract_address, + map_entry_address(selector!("blocks"), block3b_digest.into()), + block3b.into() + ); + + store( + utu.contract_address, + map_entry_address(selector!("blocks"), block4_digest.into()), + block4.into() + ); + // we now have 2 chains: + // a) [ 0x1, 0x2a, 0x3a ] + // b) [ 0x1, 0x2b, 0x3b, 0x4] + // where a[1:] cpow equals 1999 > 1500 for b[1:] even though b is longer + + let block1_status = utu.get_status(block1_digest); + let loaded_status = load( + utu.contract_address, map_entry_address(selector!("blocks"), block1_digest.into()), 10 + ); + // println!("block1_status: {:?}", block1_status); +// println!("loaded_status: {:?}", loaded_status); +//utu.set_main_chain(1, 3, block2a_digest); +//utu.set_main_chain(1, 4, block2b_digest); +} diff --git a/src/tests/utils.cairo b/src/tests/utils.cairo index 4c0cd23..61fe4a3 100644 --- a/src/tests/utils.cairo +++ b/src/tests/utils.cairo @@ -1,7 +1,23 @@ -use crate::interfaces::IUtuRelayDispatcher; +use crate::{interfaces::{BlockStatus, IUtuRelayDispatcher}, utils::hash::Digest}; use starknet::{ContractAddress, contract_address_const}; use snforge_std::{declare, ContractClassTrait, DeclareResultTrait}; +pub impl BlockStatusIntoSpan of Into> { + fn into(self: BlockStatus) -> Span { + let mut serialized_struct: Array = array![]; + self.serialize(ref serialized_struct); + serialized_struct.span() + } +} + +pub impl DigestIntoSpan of Into> { + fn into(self: Digest) -> Span { + let mut serialized_struct: Array = array![]; + self.serialize(ref serialized_struct); + serialized_struct.span() + } +} + pub fn deploy_utu() -> IUtuRelayDispatcher { let contract = declare("UtuRelay").unwrap().contract_class(); let _owner: ContractAddress = contract_address_const::<'owner'>(); @@ -11,5 +27,5 @@ pub fn deploy_utu() -> IUtuRelayDispatcher { .deploy(@constructor_calldata) .unwrap(); - IUtuRelayDispatcher { contract_address } + IUtuRelayDispatcher { contract_address } } diff --git a/src/utu_relay.cairo b/src/utu_relay.cairo index f4a3a99..c4df986 100644 --- a/src/utu_relay.cairo +++ b/src/utu_relay.cairo @@ -75,12 +75,12 @@ pub mod UtuRelay { // cancel if these existing blocks have a stronger cpow let new_block = self.blocks.read(end_block_hash); let mut new_cpow = new_block.pow; + if new_cpow <= current_cpow { panic!("Main chain has a stronger cpow than your proposed fork last block pow."); }; self.chain.write(end_height, end_block_hash); let mut block_hash = new_block.prev_block_digest; - // write blocks loop { if begin_height == end_height { @@ -113,5 +113,9 @@ pub mod UtuRelay { fn get_status(self: @ContractState, block_hash: Digest) -> BlockStatus { self.blocks.read(block_hash) } + + fn get_block(self: @ContractState, height: u64) -> Digest { + self.chain.read(height) + } } }