diff --git a/packages/client/src/main.cairo b/packages/client/src/main.cairo index 5977160d..82dcd3e4 100644 --- a/packages/client/src/main.cairo +++ b/packages/client/src/main.cairo @@ -1,7 +1,8 @@ use consensus::types::block::Block; use consensus::types::state::State; use consensus::types::chain_state::BlockValidator; -use consensus::types::utxo_set::UtxoSet; +use consensus::types::utreexo::UtreexoStateTrait; +use consensus::types::utxo_set::{UtxoSet, UtxoSetTrait}; /// Raito program arguments. #[derive(Serde)] @@ -21,20 +22,22 @@ fn main(mut arguments: Span) -> State { let Args { mut state, blocks, } = Serde::deserialize(ref arguments) .expect('Failed to deserialize'); - let mut utxo_set = UtxoSet { - utreexo_state: state.utreexo_state, - leaves_to_add: Default::default(), - cache: Default::default(), - }; + let mut utxo_set: UtxoSet = Default::default(); + // Validate and apply block, accumulating UTXO updates in utxo_set for block in blocks { state .chain_state = state .chain_state .validate_and_apply(block, ref utxo_set) - .expect('Validation failed'); + .expect('Block validation failed'); }; - state.utreexo_state = utxo_set.utreexo_state; + // Validate and apply UTXO updates + state.utreexo_state.validate_and_apply(ref utxo_set).expect('Utreexo validation failed'); + + // Ensure all UTXOs have been processed + utxo_set.finalize().expect('UtxoSet finalization failed'); + state } diff --git a/packages/client/src/test.cairo b/packages/client/src/test.cairo index 61bc4a64..414c40a4 100644 --- a/packages/client/src/test.cairo +++ b/packages/client/src/test.cairo @@ -1,7 +1,7 @@ use consensus::types::block::Block; use consensus::types::chain_state::{ChainState, BlockValidatorImpl}; use consensus::types::state::{State}; -use consensus::types::utxo_set::UtxoSet; +use consensus::types::utxo_set::{UtxoSet, UtxoSetTrait}; use core::testing::get_available_gas; /// Integration testing program arguments. @@ -27,11 +27,7 @@ fn test(mut arguments: Span) { // Temporary solution while script doesn't handle utreexo. // Allows to test one isolated block, or a batch of blocks starting from genesis. let mut state: State = State { chain_state: chain_state, utreexo_state: Default::default(), }; - let mut utxo_set: UtxoSet = UtxoSet { - utreexo_state: state.utreexo_state, - leaves_to_add: Default::default(), - cache: Default::default() - }; + let mut utxo_set: UtxoSet = Default::default(); let mut gas_before = get_available_gas(); @@ -56,11 +52,41 @@ fn test(mut arguments: Span) { if state.chain_state != expected_chain_state { println!( - "FAIL: block={} error='expected state {:?}, actual {:?}'", + "FAIL: block={} error='expected chain state {:?}, actual {:?}'", state.chain_state.block_height, expected_chain_state, state.chain_state ); panic!(); } + + // TODO: provide the expected utreexo state via args and compare it with the actual one + // + // gas_before = get_available_gas(); + // + // match state.utreexo_state.validate_and_apply(ref utxo_set) { + // Result::Ok(()) => { + // let gas_after = get_available_gas(); + // println!("OK: gas_spent={}", gas_before - gas_after); + // }, + // Result::Err(err) => { + // let gas_after = get_available_gas(); + // println!("FAIL: gas_spent={} error='{:?}'", gas_before - gas_after, err); + // panic!(); + // } + // } + // + // if state.utreexo_state != expected_utreexo_state { + // println!( + // "FAIL: error='expected utreexo state {:?}, actual {:?}'", + // expected_utreexo_state, + // state.utreexo_state + // ); + // panic!(); + // } + + if let Result::Err(err) = utxo_set.finalize() { + println!("FAIL: error='{}'", err); + panic!(); + } } diff --git a/packages/client/tests/data/full_169.json b/packages/client/tests/data/full_169.json index 72a747d5..2d1f13a0 100644 --- a/packages/client/tests/data/full_169.json +++ b/packages/client/tests/data/full_169.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee", "version": 1, "time": 1231731025, "bits": 486604799, @@ -45,6 +46,7 @@ "pk_script": "0x", "cached": false }, + "block_hash": "0000000000000000000000000000000000000000000000000000000000000000", "block_height": 0, "block_time": 0, "is_coinbase": false @@ -78,6 +80,7 @@ "pk_script": "0x410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac", "cached": false }, + "block_hash": "000000008d9dc510f23c2657fc4f67bea30078cc05a90eb89e84cc475c080805", "block_height": 9, "block_time": 1231473279, "is_coinbase": true diff --git a/packages/client/tests/data/full_757738.json b/packages/client/tests/data/full_757738.json index a31eaab9..d0121ed9 100644 --- a/packages/client/tests/data/full_757738.json +++ b/packages/client/tests/data/full_757738.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000000000000002601c74946371bd1bf00ad3154f011c20abad1cabd0ea", "version": 607625220, "time": 1665249955, "bits": 386464174, @@ -45,6 +46,7 @@ "pk_script": "0x", "cached": false }, + "block_hash": "0000000000000000000000000000000000000000000000000000000000000000", "block_height": 0, "block_time": 0, "is_coinbase": false @@ -93,6 +95,7 @@ "pk_script": "0x002044709e063fb66d783831a1237a60471d7b2a3de03a41000f4ed9e1d1afa15904", "cached": false }, + "block_hash": "0000000000000000000528fd30501f51da1b8c376f4251f231295bf61c67d8fa", "block_height": 757660, "block_time": 1665213186, "is_coinbase": false @@ -134,6 +137,7 @@ "pk_script": "0x0020e8c919a22e5fc0c47c068b1b607dcf6fa4550198e85a2cd87041d050c6eccd39", "cached": false }, + "block_hash": "00000000000000000006868fcae3934b5188a5ccb16ae3955e869dbd69c5b4e4", "block_height": 757699, "block_time": 1665235449, "is_coinbase": false @@ -180,6 +184,7 @@ "pk_script": "0x76a914a8da15e1b8a2a3fe71ada229143fd5f614656a4e88ac", "cached": false }, + "block_hash": "000000000000000000086a69ae02517624f5fafd82f27e815811c95ee959a708", "block_height": 756675, "block_time": 1664696271, "is_coinbase": false @@ -216,6 +221,7 @@ "pk_script": "0x76a91439398b5572a34b16e5c1051b930cdedc59cd4b4488ac", "cached": false }, + "block_hash": "00000000000000000001a0158522c8e1ca002f1bb8e331c4ebaeed485e4acaa5", "block_height": 757589, "block_time": 1665179059, "is_coinbase": false @@ -252,6 +258,7 @@ "pk_script": "0x76a9144fc520fd6541feb49bf8764197015fd104130c4488ac", "cached": false }, + "block_hash": "00000000000000000000fa91f6d269f011d83d5d4b26645bd69a70fa7cd6769f", "block_height": 757688, "block_time": 1665230030, "is_coinbase": false @@ -288,6 +295,7 @@ "pk_script": "0xa914ad61e432763c7b97befb092143cfefdee732745987", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -339,6 +347,7 @@ "pk_script": "0x001415d1e061eaa8f8d5e11c5f336a16be272bc356c0", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -359,6 +368,7 @@ "pk_script": "0x0014ad284c3f64b4c67fb0c92f659a6327d61a43c4a4", "cached": false }, + "block_hash": "00000000000000000003f64a7b492c3202c4ec2374388ab3e0d12f755e661db8", "block_height": 757701, "block_time": 1665236549, "is_coinbase": false @@ -398,6 +408,7 @@ "pk_script": "0x00144a93ec2dffcca4a6dd45e29184a8c05c9005594d", "cached": false }, + "block_hash": "0000000000000000000477ca3103e630a4803ce8629d58efe5a4542de74b09f7", "block_height": 757445, "block_time": 1665105449, "is_coinbase": false @@ -437,6 +448,7 @@ "pk_script": "0x0020ff020da2d6e6b8dafc303f8ad16c52cd8a270d35c84be7d15413fbe357489616", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -479,6 +491,7 @@ "pk_script": "0xa914cf9fd38d1b6835e896ceac457f8bb03779873a5e87", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -518,6 +531,7 @@ "pk_script": "0xa91496ac6867c3c27ae9ffcffa480dc473bf15d6b6f787", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -557,6 +571,7 @@ "pk_script": "0x00207250d91085a77a4568fa4cfd5bebb59f0b9cb3530f8154cd4fab6d28abd548fe", "cached": false }, + "block_hash": "000000000000000000018d52b8e759933181eda56c7e054156b3141f45d92e1c", "block_height": 757395, "block_time": 1665080032, "is_coinbase": false @@ -603,6 +618,7 @@ "pk_script": "0x0014b15300c4943ed0cacc8d1ace259c1bd1647c5a7e", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -642,6 +658,7 @@ "pk_script": "0x00140c53e64f7c954323052cb313e20a7dc16e51d3a3", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -741,6 +758,7 @@ "pk_script": "0x00141bafc04db6875f0372680088646832c8f8cc4c21", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -775,6 +793,7 @@ "pk_script": "0x00204bb6c5b9fd253342cfcba46a1920c71489d21734c8c5ebceaee2c983a3359c64", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -826,6 +845,7 @@ "pk_script": "0xa9140bdc3cf8a413c4823c3a41995108116f36b9085a87", "cached": false }, + "block_hash": "000000000000000000016d82a353995e3e713c53319f71361cd02b53cc0fb49e", "block_height": 757410, "block_time": 1665085349, "is_coinbase": false @@ -848,6 +868,7 @@ "pk_script": "0xa9140bdc3cf8a413c4823c3a41995108116f36b9085a87", "cached": false }, + "block_hash": "000000000000000000055d74536da85fd858703f61f0f738d335bfdaa7181619", "block_height": 757598, "block_time": 1665183111, "is_coinbase": false @@ -870,6 +891,7 @@ "pk_script": "0xa9140bdc3cf8a413c4823c3a41995108116f36b9085a87", "cached": false }, + "block_hash": "000000000000000000055d74536da85fd858703f61f0f738d335bfdaa7181619", "block_height": 757598, "block_time": 1665183111, "is_coinbase": false @@ -892,6 +914,7 @@ "pk_script": "0xa9140bdc3cf8a413c4823c3a41995108116f36b9085a87", "cached": false }, + "block_hash": "000000000000000000055d74536da85fd858703f61f0f738d335bfdaa7181619", "block_height": 757598, "block_time": 1665183111, "is_coinbase": false @@ -933,6 +956,7 @@ "pk_script": "0x001447bfd4c95137738c1b9e131b6557b1dcae1a741d", "cached": false }, + "block_hash": "0000000000000000000464486d1e0a909ea78f4a6023f202682137c346dda6ad", "block_height": 756818, "block_time": 1664773599, "is_coinbase": false @@ -953,6 +977,7 @@ "pk_script": "0x001447bfd4c95137738c1b9e131b6557b1dcae1a741d", "cached": false }, + "block_hash": "0000000000000000000342923b4df87bba3ccb34b100dc176d4799707331407f", "block_height": 757690, "block_time": 1665231092, "is_coinbase": false @@ -992,6 +1017,7 @@ "pk_script": "0x00140b7389afc5745dccb1ae670e1af85166a58d490e", "cached": false }, + "block_hash": "00000000000000000000fa91f6d269f011d83d5d4b26645bd69a70fa7cd6769f", "block_height": 757688, "block_time": 1665230030, "is_coinbase": false @@ -1012,6 +1038,7 @@ "pk_script": "0x00140b7389afc5745dccb1ae670e1af85166a58d490e", "cached": false }, + "block_hash": "00000000000000000002f0aca4ba708fbadb17781ce3194a2928dd8cb9748222", "block_height": 757736, "block_time": 1665249581, "is_coinbase": false @@ -1046,6 +1073,7 @@ "pk_script": "0x001412df6ffbe342f41bdb6304aca93584a0cd411779", "cached": false }, + "block_hash": "000000000000000000083cc4888114cd9295e66fc9efb289e579706fa7b40bae", "block_height": 757713, "block_time": 1665241142, "is_coinbase": false @@ -1085,6 +1113,7 @@ "pk_script": "0x00140b0fa4545711fe6761bd017a337416d7f793959a", "cached": false }, + "block_hash": "000000000000000000079739304ca072bb310e5b3e86a321e5a8d9c6e167813f", "block_height": 757731, "block_time": 1665247316, "is_coinbase": false @@ -1124,6 +1153,7 @@ "pk_script": "0x0014c0da5d5d736d0b6a2731db0e2ac8754fd1d2dd0e", "cached": false }, + "block_hash": "00000000000000000001c4ffa847267d4f7a6842421cba3f1ec195062ea1057e", "block_height": 757738, "block_time": 1665249935, "is_coinbase": false @@ -1163,6 +1193,7 @@ "pk_script": "0x0020c10438f69200f8b41bf1efb719613fdd2cb858cd29bd87acef9413886259c332", "cached": false }, + "block_hash": "00000000000000000001c4ffa847267d4f7a6842421cba3f1ec195062ea1057e", "block_height": 757738, "block_time": 1665249935, "is_coinbase": false @@ -1199,6 +1230,7 @@ "pk_script": "0x0014a6ad7b283bdfd934b3b22800d761085ea76e5a94", "cached": false }, + "block_hash": "00000000000000000007298e56f2681be6533136531341b0bd056df36e2265b0", "block_height": 757730, "block_time": 1665245937, "is_coinbase": false @@ -1238,6 +1270,7 @@ "pk_script": "0x0014873b00eafc8bd6ec1b0c5a7c382b5c9887b28807", "cached": false }, + "block_hash": "00000000000000000002007c2afd22210158c44986e0e791860a2946b1823703", "block_height": 757714, "block_time": 1665241693, "is_coinbase": false @@ -1272,6 +1305,7 @@ "pk_script": "0x76a9141bf21ed0f5ac8282bfc844ccb5f3187884524fb088ac", "cached": false }, + "block_hash": "00000000000000000001c4ffa847267d4f7a6842421cba3f1ec195062ea1057e", "block_height": 757738, "block_time": 1665249935, "is_coinbase": false @@ -1289,6 +1323,7 @@ "pk_script": "0x76a914a7b8c3f2c55a729832c057750d2f1568063552a088ac", "cached": false }, + "block_hash": "00000000000000000008e4d4fc36e83803f9602a067f68bd53cf11b73d0c52ab", "block_height": 757567, "block_time": 1665166616, "is_coinbase": false @@ -1320,6 +1355,7 @@ "pk_script": "0x001473540c3bd326dbf6cd66d9fa7bd2f11197382cd8", "cached": false }, + "block_hash": "000000000000000000079739304ca072bb310e5b3e86a321e5a8d9c6e167813f", "block_height": 757731, "block_time": 1665247316, "is_coinbase": false @@ -1340,6 +1376,7 @@ "pk_script": "0x001473540c3bd326dbf6cd66d9fa7bd2f11197382cd8", "cached": false }, + "block_hash": "000000000000000000080f121c246c1261b8067d1714cc0696728561c7bd862f", "block_height": 757734, "block_time": 1665248868, "is_coinbase": false @@ -1379,6 +1416,7 @@ "pk_script": "0x00146617c9f222186efdfa2ca6326426b72569c093fb", "cached": false }, + "block_hash": "00000000000000000006cc144a3bba64189bbad3b2768c718d7a203447ac0df7", "block_height": 757663, "block_time": 1665215093, "is_coinbase": false @@ -1418,6 +1456,7 @@ "pk_script": "0x76a914ce35e804ab2f8db20e42a875bd6f56f83287dbbc88ac", "cached": false }, + "block_hash": "000000000000000000080f121c246c1261b8067d1714cc0696728561c7bd862f", "block_height": 757734, "block_time": 1665248868, "is_coinbase": false @@ -1454,6 +1493,7 @@ "pk_script": "0x0014dc1691cacb0a7365a1ab492347bd25c939cc9b60", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -1474,6 +1514,7 @@ "pk_script": "0x0014dc1691cacb0a7365a1ab492347bd25c939cc9b60", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -1494,6 +1535,7 @@ "pk_script": "0x0014dc1691cacb0a7365a1ab492347bd25c939cc9b60", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -1528,6 +1570,7 @@ "pk_script": "0x0014ca5ebb6ecf79994b90d4984c8ab273957e844c27", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -1567,6 +1610,7 @@ "pk_script": "0x001436fc88349cf8132c739f97e8ed440e035a14ad45", "cached": false }, + "block_hash": "00000000000000000005cb5975cb159837471adbe3f3f789e1b632580c40bca3", "block_height": 757407, "block_time": 1665083853, "is_coinbase": false @@ -1587,6 +1631,7 @@ "pk_script": "0x001432b24316ee23f7388bdb07297eb50cc9ae37c200", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -1626,6 +1671,7 @@ "pk_script": "0x00146a15d006a9ce0b19f506a4f35b7a9d931e454ed0", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -1660,6 +1706,7 @@ "pk_script": "0x0014c5d2786175a67a428f9f161b9bc66971310598a0", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -1699,6 +1746,7 @@ "pk_script": "0x0014ec5cc0512b2d7067ae2d314cd192fe2e9815f64f", "cached": false }, + "block_hash": "0000000000000000000229123c4910d222a1471e8998cb21ad5e2a64711a1a21", "block_height": 756916, "block_time": 1664829357, "is_coinbase": false @@ -1733,6 +1781,7 @@ "pk_script": "0x0014df419d00a2f54e4a49167a30653467002a3c355c", "cached": false }, + "block_hash": "00000000000000000004572de32677b2ff0e555ec33fe0cd846c17b8972d7673", "block_height": 748467, "block_time": 1659917583, "is_coinbase": false @@ -1772,6 +1821,7 @@ "pk_script": "0xa9147d5507948d35fddeb228e03a10e95e8e2cf0153887", "cached": false }, + "block_hash": "000000000000000000039355d62776206defd9fdbdc3771a032952532c35dd8d", "block_height": 755815, "block_time": 1664213995, "is_coinbase": false @@ -1816,6 +1866,7 @@ "pk_script": "0x001425c8eb18f8308afc61846578be9276a5cb2df3a6", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -1836,6 +1887,7 @@ "pk_script": "0x001425c8eb18f8308afc61846578be9276a5cb2df3a6", "cached": false }, + "block_hash": "00000000000000000006e3485372476ee3bda1de796b001b56782fa1e17be39c", "block_height": 757717, "block_time": 1665243090, "is_coinbase": false @@ -1870,6 +1922,7 @@ "pk_script": "0x001492f38bab1075e5bb6e7d47238b3089861be7709c", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -1904,6 +1957,7 @@ "pk_script": "0x001472a1a5f786ea4a6d7623e36dcff6109fc2eb87c2", "cached": false }, + "block_hash": "000000000000000000056f479da4fcbf0a4dcdeeaa309f4d965ec70b8e742616", "block_height": 757712, "block_time": 1665241008, "is_coinbase": false @@ -1943,6 +1997,7 @@ "pk_script": "0x0014ce5709ae4c7aef429a8ca3350f2aa0ce85ba7ff7", "cached": false }, + "block_hash": "00000000000000000002f0aca4ba708fbadb17781ce3194a2928dd8cb9748222", "block_height": 757736, "block_time": 1665249581, "is_coinbase": false @@ -1982,6 +2037,7 @@ "pk_script": "0x0014b39816a0ff03dbc4bb3614306fb341882e8f5dc4", "cached": false }, + "block_hash": "00000000000000000007a77a5e6c91466273b3fd2c2e1d85b0f377cff14f4294", "block_height": 757692, "block_time": 1665233385, "is_coinbase": false @@ -2021,6 +2077,7 @@ "pk_script": "0x76a91425ceb7d5f6b1d9d15375037a8f44011b2e62b5dd88ac", "cached": false }, + "block_hash": "00000000000000000003b3bdd08b2bc8a4dac190685d4a562b15cf14e3f2fed0", "block_height": 757544, "block_time": 1665156060, "is_coinbase": false @@ -2038,6 +2095,7 @@ "pk_script": "0x76a914fdb57509e8e4c78421f117981c74fac5f7a358ee88ac", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -2069,6 +2127,7 @@ "pk_script": "0x00147b09664a87ccf2806a9b48f003384081d6aaaba5", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -2089,6 +2148,7 @@ "pk_script": "0x00141636ab581f574bfcce6cd98223be82c17237a644", "cached": false }, + "block_hash": "00000000000000000006098f2ffdd34e0b4d1efaab83cff691c8c882cec09dfc", "block_height": 757703, "block_time": 1665237620, "is_coinbase": false @@ -2109,6 +2169,7 @@ "pk_script": "0x0014607aa0536a89b6dc823e31d1d7c64b094caccc3a", "cached": false }, + "block_hash": "00000000000000000007298e56f2681be6533136531341b0bd056df36e2265b0", "block_height": 757730, "block_time": 1665245937, "is_coinbase": false @@ -2129,6 +2190,7 @@ "pk_script": "0x00140b759b59baa8cc56c135614de4127798fdd57dab", "cached": false }, + "block_hash": "00000000000000000007298e56f2681be6533136531341b0bd056df36e2265b0", "block_height": 757730, "block_time": 1665245937, "is_coinbase": false @@ -2149,6 +2211,7 @@ "pk_script": "0x00141f5604584c1a6243cbc2f1ab7c82697a1dddb72a", "cached": false }, + "block_hash": "000000000000000000079739304ca072bb310e5b3e86a321e5a8d9c6e167813f", "block_height": 757731, "block_time": 1665247316, "is_coinbase": false @@ -2169,6 +2232,7 @@ "pk_script": "0xa9143b22623fcfac967186714cb50a862f7383de8cf387", "cached": false }, + "block_hash": "000000000000000000079739304ca072bb310e5b3e86a321e5a8d9c6e167813f", "block_height": 757731, "block_time": 1665247316, "is_coinbase": false @@ -2243,6 +2307,7 @@ "pk_script": "0x001434717e7661168643c31b031462aac7c615741747", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -2282,6 +2347,7 @@ "pk_script": "0x0014742ce48f673c79b7806ac1eec2de2fc92f0b84f8", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -2321,6 +2387,7 @@ "pk_script": "0x0014da586708cd6ea6a31ecf58aaa2d4a3a61df979c3", "cached": false }, + "block_hash": "00000000000000000006c11951289d59d2094d8ccb02510c26e2aa441d55b307", "block_height": 757607, "block_time": 1665186534, "is_coinbase": false @@ -2355,6 +2422,7 @@ "pk_script": "0xa914bf1b8aa79d105a49d3de886210ac1c80357831df87", "cached": false }, + "block_hash": "000000000000000000056aba36b5f6aedf5581c437d99ea32172d1d9e3786afa", "block_height": 757154, "block_time": 1664944982, "is_coinbase": false @@ -2378,6 +2446,7 @@ "pk_script": "0xa914dc227094e287f6cec51c31f6cc610934503f28b987", "cached": false }, + "block_hash": "000000000000000000015b92ded494a7dd61bbd81bd3dac4f805298671f0a7db", "block_height": 757519, "block_time": 1665146839, "is_coinbase": false @@ -2420,6 +2489,7 @@ "pk_script": "0x76a9148b6f101c51e5a5d332985329a70da3071df2899888ac", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -2437,6 +2507,7 @@ "pk_script": "0x76a914b783ebaf1e53aa8dd6f8bbd3c8622b0e08bcde3288ac", "cached": false }, + "block_hash": "00000000000000000008130318bfea137d2e4dabedcdf4d99eeea52a827ca9e5", "block_height": 755835, "block_time": 1664226763, "is_coinbase": false @@ -2473,6 +2544,7 @@ "pk_script": "0x00140427b2dd010924a7d3747a79444f656978ec82c2", "cached": false }, + "block_hash": "00000000000000000006098f2ffdd34e0b4d1efaab83cff691c8c882cec09dfc", "block_height": 757703, "block_time": 1665237620, "is_coinbase": false @@ -2512,6 +2584,7 @@ "pk_script": "0x0014f80fa4ca2ad41d2a338e228789042769797970ef", "cached": false }, + "block_hash": "00000000000000000003d75198690a562ec6fc68fe59d43a15d9543c021f00e9", "block_height": 757659, "block_time": 1665211847, "is_coinbase": false @@ -2532,6 +2605,7 @@ "pk_script": "0x00145fa5093c5cc6f6d6cd6eb679995f51aead1ed5a0", "cached": false }, + "block_hash": "00000000000000000007e076d996e687b4a93dae8b81d3a6d8bd6f249895a95e", "block_height": 757333, "block_time": 1665042744, "is_coinbase": false @@ -2571,6 +2645,7 @@ "pk_script": "0x0014317cb16153e35648d38123516c5bf076d2966b0d", "cached": false }, + "block_hash": "000000000000000000085fa275c85243f63bc85f380e1cca6b1c122b90587998", "block_height": 756293, "block_time": 1664503868, "is_coinbase": false @@ -2605,6 +2680,7 @@ "pk_script": "0x0014fba271901401d16d05e34a5cfdfa81ece6c7bf44", "cached": false }, + "block_hash": "000000000000000000079f8f03f9e8f807cd71f6e74a818925c253d664c7cdf3", "block_height": 757548, "block_time": 1665157906, "is_coinbase": false @@ -2625,6 +2701,7 @@ "pk_script": "0x00147ce283713da856414dca53a6143ac7239970a41a", "cached": false }, + "block_hash": "0000000000000000000322d4b7b0d5d51bb80e5fa80b48db9587460ce294c1a7", "block_height": 757698, "block_time": 1665234821, "is_coinbase": false @@ -2645,6 +2722,7 @@ "pk_script": "0x001480514ba9270fbbeac3097cf439075811222b78ce", "cached": false }, + "block_hash": "000000000000000000086dc8b8deefe419527d15af8de65f819ec70c7b81fa74", "block_height": 755893, "block_time": 1664266866, "is_coinbase": false @@ -2665,6 +2743,7 @@ "pk_script": "0x00143803b290cfbae268f1ae29408d548a92b486c363", "cached": false }, + "block_hash": "00000000000000000001c40c09b3f348bce6ea73990ecc15c2d915c0806e24b7", "block_height": 757599, "block_time": 1665183304, "is_coinbase": false @@ -2704,6 +2783,7 @@ "pk_script": "0x002037fda9847471fa78ea3d67179ee99d5da51c66c8c69252c0954f2e134ec7340d", "cached": false }, + "block_hash": "0000000000000000000484523bb3b96261b85533561290f2d2cc11b7fbfc7bdc", "block_height": 757728, "block_time": 1665245428, "is_coinbase": false @@ -2745,6 +2825,7 @@ "pk_script": "0x001489eff04c294fb6d3a46270134ec6013fb889ef16", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -2784,6 +2865,7 @@ "pk_script": "0x76a9147b904242a4c2ad57cddd3b9e2b7d2aed67244dad88ac", "cached": false }, + "block_hash": "00000000000000000001c4ffa847267d4f7a6842421cba3f1ec195062ea1057e", "block_height": 757738, "block_time": 1665249935, "is_coinbase": false @@ -2820,6 +2902,7 @@ "pk_script": "0x76a9147711eab6ffcc99e4376ac39f4022de413e1ea2a988ac", "cached": false }, + "block_hash": "0000000000000000000060b46729c10e86ec9eea673852c9011e158103ce4294", "block_height": 757735, "block_time": 1665249416, "is_coinbase": false @@ -2856,6 +2939,7 @@ "pk_script": "0x76a9142e70364cfed8761914d5f91a923dc5c2b26dfce988ac", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -2873,6 +2957,7 @@ "pk_script": "0x76a914b4a789ce3b27f32fe0c5f443d01bf81ef5cf743988ac", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -2909,6 +2994,7 @@ "pk_script": "0x0020ddc9efe2471c1ed3a002b02dd093d3da115d7ee48591461ae417dcb9c43d92d4", "cached": false }, + "block_hash": "000000000000000000079739304ca072bb310e5b3e86a321e5a8d9c6e167813f", "block_height": 757731, "block_time": 1665247316, "is_coinbase": false @@ -2931,6 +3017,7 @@ "pk_script": "0x0020ddc9efe2471c1ed3a002b02dd093d3da115d7ee48591461ae417dcb9c43d92d4", "cached": false }, + "block_hash": "0000000000000000000249c6e769fc65af62d18331e5803f2f7f5e1a482d1013", "block_height": 757732, "block_time": 1665248544, "is_coinbase": false @@ -2967,6 +3054,7 @@ "pk_script": "0x00208ee5855e25e952df928d06a69aa4846ec2995ffa6632794208e4f8b92f21bd62", "cached": false }, + "block_hash": "00000000000000000004752b31b61b3d138669da5ad8e6bbdea4bcad5fe55fa5", "block_height": 725621, "block_time": 1646245273, "is_coinbase": false @@ -3008,6 +3096,7 @@ "pk_script": "0xa914f500fe6addcb5f128592b27cc855b8d0614900d687", "cached": false }, + "block_hash": "000000000000000000034ce911a0cfd12d7f100085f071611ae62f5438ff6779", "block_height": 756789, "block_time": 1664758496, "is_coinbase": false @@ -3025,6 +3114,7 @@ "pk_script": "0xa914f500fe6addcb5f128592b27cc855b8d0614900d687", "cached": false }, + "block_hash": "0000000000000000000136d2e367c81f36bd4c1e320231ed5d501b4e654151a1", "block_height": 757435, "block_time": 1665099208, "is_coinbase": false @@ -3042,6 +3132,7 @@ "pk_script": "0xa914f500fe6addcb5f128592b27cc855b8d0614900d687", "cached": false }, + "block_hash": "000000000000000000022f1b6d09bc57d460d3fa5b02c3e22905c36402b25cc7", "block_height": 756797, "block_time": 1664763456, "is_coinbase": false @@ -3059,6 +3150,7 @@ "pk_script": "0xa914f500fe6addcb5f128592b27cc855b8d0614900d687", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false @@ -3090,6 +3182,7 @@ "pk_script": "0xa914418adb947b13edb7e7bdb7f0dbeeabcde60c3e3087", "cached": false }, + "block_hash": "0000000000000000000406f9e745e597fdf953ef0a0c123b15da34748f80446e", "block_height": 757737, "block_time": 1665249850, "is_coinbase": false diff --git a/packages/client/tests/data/light_116927.json b/packages/client/tests/data/light_116927.json index 568d3f73..5a660749 100644 --- a/packages/client/tests/data/light_116927.json +++ b/packages/client/tests/data/light_116927.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000494457221111a6a99da34a993c69c2761582bec31f90ba5d560d", "version": 1, "time": 1302034197, "bits": 453036989, diff --git a/packages/client/tests/data/light_150012.json b/packages/client/tests/data/light_150012.json index 784b2ff8..08da27bc 100644 --- a/packages/client/tests/data/light_150012.json +++ b/packages/client/tests/data/light_150012.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000011f9791dcfdfe0e402b79a165a3b781bafcc918b6f2166d577c", "version": 1, "time": 1319128988, "bits": 436956491, diff --git a/packages/client/tests/data/light_169.json b/packages/client/tests/data/light_169.json index 01fc67f1..e476204e 100644 --- a/packages/client/tests/data/light_169.json +++ b/packages/client/tests/data/light_169.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee", "version": 1, "time": 1231731025, "bits": 486604799, diff --git a/packages/client/tests/data/light_2015.json b/packages/client/tests/data/light_2015.json index fef8113c..b074c354 100644 --- a/packages/client/tests/data/light_2015.json +++ b/packages/client/tests/data/light_2015.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000a141216a896c54f211301c436e557a8d55900637bbdce14c6c7bddef", "version": 1, "time": 1233063531, "bits": 486604799, diff --git a/packages/client/tests/data/light_209999.json b/packages/client/tests/data/light_209999.json index 27b819dc..655df88e 100644 --- a/packages/client/tests/data/light_209999.json +++ b/packages/client/tests/data/light_209999.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e", "version": 2, "time": 1354116278, "bits": 436527338, diff --git a/packages/client/tests/data/light_24834.json b/packages/client/tests/data/light_24834.json index 1d841132..06fb787b 100644 --- a/packages/client/tests/data/light_24834.json +++ b/packages/client/tests/data/light_24834.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000132fbe8314fc571c0be60b31ccd461c9ee85f42bde8c6d160a9dacc0", "version": 1, "time": 1255321278, "bits": 486604799, diff --git a/packages/client/tests/data/light_32255.json b/packages/client/tests/data/light_32255.json index 49e13238..8eff8ef5 100644 --- a/packages/client/tests/data/light_32255.json +++ b/packages/client/tests/data/light_32255.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000004f2886a170adb7204cb0c7a824217dd24d11a74423d564c4e0904967", "version": 1, "time": 1262153464, "bits": 486594666, diff --git a/packages/client/tests/data/light_403199.json b/packages/client/tests/data/light_403199.json index bc120db5..e22ce342 100644 --- a/packages/client/tests/data/light_403199.json +++ b/packages/client/tests/data/light_403199.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000000000c4272a5c68b4f55e5af734e88ceab09abf73e9ac3b6d01", "version": 4, "time": 1458292068, "bits": 403088579, diff --git a/packages/client/tests/data/light_478557.json b/packages/client/tests/data/light_478557.json index 317a3518..14d78b84 100644 --- a/packages/client/tests/data/light_478557.json +++ b/packages/client/tests/data/light_478557.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "0000000000000000011865af4122fe3b144e2cbeea86142e8ff2fb4107352d43", "version": 536870914, "time": 1501593374, "bits": 402736949, diff --git a/packages/client/tests/data/light_481823.json b/packages/client/tests/data/light_481823.json index 3e0432ef..730bd055 100644 --- a/packages/client/tests/data/light_481823.json +++ b/packages/client/tests/data/light_481823.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "0000000000000000001c8018d9cb3b742ef25114f27563e3fc4a1902167f9893", "version": 536870914, "time": 1503539857, "bits": 402734313, diff --git a/packages/client/tests/data/light_489888.json b/packages/client/tests/data/light_489888.json index 079da1f2..6b116138 100644 --- a/packages/client/tests/data/light_489888.json +++ b/packages/client/tests/data/light_489888.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000000000a0930da60f6dfe83aace79726f40b4c3fc0c4b0da95ffd", "version": 536870912, "time": 1508040630, "bits": 402713392, diff --git a/packages/client/tests/data/light_491406.json b/packages/client/tests/data/light_491406.json index 810d92bf..23952621 100644 --- a/packages/client/tests/data/light_491406.json +++ b/packages/client/tests/data/light_491406.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000000000e5438564434edaf41e63829a637521a96235adf4653e1b", "version": 536870912, "time": 1508808039, "bits": 402713392, diff --git a/packages/client/tests/data/light_57042.json b/packages/client/tests/data/light_57042.json index ed7710ac..247cf75f 100644 --- a/packages/client/tests/data/light_57042.json +++ b/packages/client/tests/data/light_57042.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000152340ca42227603908689183edc47355204e7aca59383b0aaac1fd8", "version": 1, "time": 1274552191, "bits": 471178276, diff --git a/packages/client/tests/data/light_629999.json b/packages/client/tests/data/light_629999.json index 2732e284..45071fac 100644 --- a/packages/client/tests/data/light_629999.json +++ b/packages/client/tests/data/light_629999.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000000000024bead8df69990852c202db0e0097c1a12ea637d7e96d", "version": 536870912, "time": 1589225023, "bits": 387021369, diff --git a/packages/client/tests/data/light_709631.json b/packages/client/tests/data/light_709631.json index 0b0852b4..504f37aa 100644 --- a/packages/client/tests/data/light_709631.json +++ b/packages/client/tests/data/light_709631.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "0000000000000000000687bca986194dc2c1f949318629b44bb54ec0a94d8244", "version": 538968068, "time": 1636866927, "bits": 386689514, diff --git a/packages/client/tests/data/light_72575.json b/packages/client/tests/data/light_72575.json index f89f73bb..010fd785 100644 --- a/packages/client/tests/data/light_72575.json +++ b/packages/client/tests/data/light_72575.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000002897d721b6b31ad519e3e54a90741a24a019f23e41911a8c8b0d25", "version": 1, "time": 1281037595, "bits": 469809688, diff --git a/packages/client/tests/data/light_757738.json b/packages/client/tests/data/light_757738.json index 713fdd08..9186db14 100644 --- a/packages/client/tests/data/light_757738.json +++ b/packages/client/tests/data/light_757738.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "00000000000000000002601c74946371bd1bf00ad3154f011c20abad1cabd0ea", "version": 607625220, "time": 1665249955, "bits": 386464174, diff --git a/packages/client/tests/data/light_757752.json b/packages/client/tests/data/light_757752.json index 9466aeb4..2305dd7e 100644 --- a/packages/client/tests/data/light_757752.json +++ b/packages/client/tests/data/light_757752.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "000000000000000000032c781dbe11459fba50312acfca3cd96fa2bc4367d5b1", "version": 549453828, "time": 1665258359, "bits": 386464174, diff --git a/packages/client/tests/data/light_774627.json b/packages/client/tests/data/light_774627.json index 0c48d921..02bbe144 100644 --- a/packages/client/tests/data/light_774627.json +++ b/packages/client/tests/data/light_774627.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "0000000000000000000515e202c8ae73c8155fc472422d7593af87aa74f2cf3d", "version": 680919040, "time": 1675283913, "bits": 386344736, diff --git a/packages/client/tests/data/light_839999.json b/packages/client/tests/data/light_839999.json index 99b73bda..8be104bd 100644 --- a/packages/client/tests/data/light_839999.json +++ b/packages/client/tests/data/light_839999.json @@ -22,6 +22,7 @@ "blocks": [ { "header": { + "hash": "0000000000000000000320283a032748cef8227873ff4872689bf23f1cda83a5", "version": 710926336, "time": 1713571767, "bits": 386089497, diff --git a/packages/consensus/src/codec.cairo b/packages/consensus/src/codec.cairo index 6602bcc7..bb96cda8 100644 --- a/packages/consensus/src/codec.cairo +++ b/packages/consensus/src/codec.cairo @@ -208,6 +208,7 @@ mod tests { ), vout: 4294967295_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -229,6 +230,7 @@ mod tests { ), vout: 0_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -253,6 +255,7 @@ mod tests { ), vout: 4294967295, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -282,6 +285,7 @@ mod tests { ), vout: 1_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -314,6 +318,7 @@ mod tests { ), vout: 0_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -331,6 +336,7 @@ mod tests { ), vout: 1_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -348,6 +354,7 @@ mod tests { ), vout: 1_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -407,6 +414,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "000000000000000931112ca80c5badf6047373b1bb53587fc23344871734bbff" + ), block_height: 149994_u32, block_time: 1319114701_u32, is_coinbase: true, @@ -430,6 +440,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "0000000000000779e6cfb6bdd06458b8e8adffea65c719edb3450d8b05f9cc57" + ), block_height: 150006_u32, block_time: 1319124571_u32, is_coinbase: true, @@ -453,6 +466,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "0000000000000a43df068d144a5854b92d5a866d1c25f324f80077b86acb74e1" + ), block_height: 150005_u32, block_time: 1319122014_u32, is_coinbase: true, @@ -476,6 +492,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "000000000000010d9f3d69f259027feaa1fe8637a01300db0536b33fc552351d" + ), block_height: 149935_u32, block_time: 1319066844_u32, is_coinbase: true, @@ -499,6 +518,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "00000000000001283f1ade495834dab6b796ba8d94f8db0e5625b2eaf3bd1490" + ), block_height: 149940_u32, block_time: 1319069371_u32, is_coinbase: true, @@ -522,6 +544,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "00000000000009b7da59908141bc7e4716497200cb2d7bdaa5c93d0c9c642eb1" + ), block_height: 149895_u32, block_time: 1319035283_u32, is_coinbase: true, @@ -545,6 +570,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "00000000000006c23e77cedfd97ea2d6434371236e0a373d22e77e2a4a8a52b5" + ), block_height: 149814_u32, block_time: 1318990721_u32, is_coinbase: true, @@ -568,6 +596,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "0000000000000b4ece814065c9a591382cc90447efdd302ec5d618ffaa04e023" + ), block_height: 149984_u32, block_time: 1319104909_u32, is_coinbase: true, @@ -627,6 +658,9 @@ mod tests { ), cached: false, }, + block_hash: hex_to_hash_rev( + "00000000000002db188274c80ae6a97f67a7d5f355815cca6d30a48e0bb01153" + ), block_height: 206120_u32, block_time: 1351856022_u32, is_coinbase: true, @@ -725,6 +759,7 @@ mod tests { ), vout: 0x0, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -790,6 +825,7 @@ mod tests { ), vout: 0_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -813,6 +849,7 @@ mod tests { ), vout: 1_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -876,6 +913,7 @@ mod tests { ), vout: 1, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, diff --git a/packages/consensus/src/types/block.cairo b/packages/consensus/src/types/block.cairo index fe6294e2..4ab5cdb6 100644 --- a/packages/consensus/src/types/block.cairo +++ b/packages/consensus/src/types/block.cairo @@ -40,6 +40,8 @@ pub enum TransactionData { /// like previous_block_hash (in the previous chain state). #[derive(Drop, Copy, Debug, PartialEq, Default, Serde)] pub struct Header { + /// Hash of the block. + pub hash: Digest, /// The version of the block. pub version: u32, /// The timestamp of the block. @@ -54,8 +56,10 @@ pub struct Header { #[generate_trait] pub impl BlockHashImpl of BlockHash { - /// Compute hash of the block header given the missing fields. - fn hash(self: @Header, prev_block_hash: Digest, merkle_root: Digest) -> Digest { + /// Checks if the block hash is valid by re-computing it given the missing fields. + fn validate_hash( + self: @Header, prev_block_hash: Digest, merkle_root: Digest + ) -> Result<(), ByteArray> { let mut header_data_u32: Array = array![]; header_data_u32.append(u32_byte_reverse(*self.version)); @@ -66,7 +70,13 @@ pub impl BlockHashImpl of BlockHash { header_data_u32.append(u32_byte_reverse(*self.bits)); header_data_u32.append(u32_byte_reverse(*self.nonce)); - double_sha256_u32_array(header_data_u32) + let hash = double_sha256_u32_array(header_data_u32); + + if *self.hash == hash { + Result::Ok(()) + } else { + Result::Err("Invalid block hash") + } } } @@ -92,7 +102,8 @@ impl BlockDisplay of Display { impl HeaderDisplay of Display
{ fn fmt(self: @Header, ref f: Formatter) -> Result<(), Error> { let str: ByteArray = format!( - "Header {{ version: {}, time: {}, bits: {}, nonce: {}}}", + "Header {{ hash: {}, version: {}, time: {}, bits: {}, nonce: {}}}", + *self.hash, *self.version, *self.time, *self.bits, @@ -120,6 +131,7 @@ mod tests { use super::{Header, BlockHash}; use crate::types::chain_state::ChainState; use utils::hash::Digest; + use utils::hex::hex_to_hash_rev; #[test] fn test_block_hash() { @@ -130,20 +142,20 @@ mod tests { .into(); // block 170 let header = Header { - version: 1_u32, time: 1231731025_u32, bits: 0x1d00ffff_u32, nonce: 1889418792_u32 + hash: hex_to_hash_rev( + "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee" + ), + version: 1_u32, + time: 1231731025_u32, + bits: 0x1d00ffff_u32, + nonce: 1889418792_u32 }; let merkle_root: Digest = 0x7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff_u256 .into(); - let block_hash_result: Digest = header.hash(chain_state.best_block_hash, merkle_root); - - //0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee - let expected_block_hash: Digest = - 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee_u256 - .into(); - - assert_eq!(expected_block_hash, block_hash_result); + let result = header.validate_hash(chain_state.best_block_hash, merkle_root); + assert!(result.is_ok()); } #[test] @@ -155,19 +167,20 @@ mod tests { .into(); // block 170 let header = Header { - version: 1_u32, time: 1231731025_u32, bits: 0x1d00ffff_u32, nonce: 1889418792_u32 + hash: hex_to_hash_rev( + "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee" + ), + version: 1_u32, + time: 1231731025_u32, + bits: 0x1d00ffff_u32, + nonce: 1889418792_u32 }; let merkle_root: Digest = 0x6dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff_u256 .into(); - let block_hash_result: Digest = header.hash(chain_state.best_block_hash, merkle_root); - - let expected_block_hash: Digest = - 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee_u256 - .into(); - - assert_ne!(expected_block_hash, block_hash_result); + let result = header.validate_hash(chain_state.best_block_hash, merkle_root); + assert!(result.is_err()); } #[test] @@ -179,18 +192,19 @@ mod tests { .into(); // block 170 let header = Header { - version: 1_u32, time: 1231731025_u32, bits: 0x1d00ffff_u32, nonce: 1889418792_u32 + hash: hex_to_hash_rev( + "00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee" + ), + version: 1_u32, + time: 1231731025_u32, + bits: 0x1d00ffff_u32, + nonce: 1889418792_u32 }; let merkle_root: Digest = 0x7dac2c5666815c17a3b36427de37bb9d2e2c5ccec3f8633eb91a4205cb4c10ff_u256 .into(); - let block_hash_result: Digest = header.hash(chain_state.best_block_hash, merkle_root); - - let expected_block_hash: Digest = - 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee_u256 - .into(); - - assert_ne!(expected_block_hash, block_hash_result); + let result = header.validate_hash(chain_state.best_block_hash, merkle_root); + assert!(result.is_err()); } } diff --git a/packages/consensus/src/types/chain_state.cairo b/packages/consensus/src/types/chain_state.cairo index ea7ecf2d..529825d0 100644 --- a/packages/consensus/src/types/chain_state.cairo +++ b/packages/consensus/src/types/chain_state.cairo @@ -69,13 +69,15 @@ pub impl BlockValidatorImpl of BlockValidator { TransactionData::MerkleRoot(root) => root, TransactionData::Transactions(txs) => { let (total_fees, txid_root, wtxid_root) = compute_and_validate_tx_data( - txs, block_height, block.header.time, ref utxo_set + txs, block.header.hash, block_height, block.header.time, ref utxo_set )?; validate_coinbase(txs[0], total_fees, block_height, wtxid_root)?; txid_root } }; + block.header.validate_hash(self.best_block_hash, txid_root)?; + let (current_target, epoch_start_time) = adjust_difficulty( self.current_target, self.epoch_start_time, @@ -84,7 +86,7 @@ pub impl BlockValidatorImpl of BlockValidator { block.header.time ); let total_work = compute_total_work(self.total_work, current_target); - let best_block_hash = block.header.hash(self.best_block_hash, txid_root); + let best_block_hash = block.header.hash; validate_proof_of_work(current_target, best_block_hash)?; validate_bits(current_target, block.header.bits)?; diff --git a/packages/consensus/src/types/transaction.cairo b/packages/consensus/src/types/transaction.cairo index e5ae7d11..c0735766 100644 --- a/packages/consensus/src/types/transaction.cairo +++ b/packages/consensus/src/types/transaction.cairo @@ -5,6 +5,9 @@ use utils::{hash::Digest, bytearray::{ByteArraySnapHash, ByteArraySnapSerde}}; use core::fmt::{Display, Formatter, Error}; +use core::hash::HashStateTrait; +use core::hash::HashStateExTrait; +use core::poseidon::PoseidonTrait; /// Represents a transaction. /// https://learnmeabitcoin.com/technical/transaction/ @@ -86,6 +89,9 @@ pub struct OutPoint { /// Referenced output data (meta field). /// Must be set to default for coinbase inputs. pub data: TxOut, + /// The hash of the block that contains this output (meta field). + /// Used for hardening against collision attacks (https://eprint.iacr.org/2019/611.pdf). + pub block_hash: Digest, /// The height of the block that contains this output (meta field). /// Used to validate coinbase tx spending (not sooner than 100 blocks) and relative timelocks /// (it has been more than X block since the transaction containing this output was mined). @@ -123,6 +129,13 @@ pub struct TxOut { pub cached: bool, } +#[generate_trait] +pub impl OutPointImpl of OutPointTrait { + fn hash(self: @OutPoint) -> felt252 { + PoseidonTrait::new().update_with(*self).finalize() + } +} + impl TxOutDefault of Default { fn default() -> TxOut { TxOut { value: 0, pk_script: @"", cached: false, } @@ -165,6 +178,7 @@ impl OutPointDisplay of Display { txid: {}, vout: {}, data: {}, + block_hash: {}, block_height: {}, block_time: {}, is_coinbase: {}, @@ -172,6 +186,7 @@ impl OutPointDisplay of Display { *self.txid, *self.vout, *self.data, + *self.block_hash, *self.block_height, *self.block_time, *self.is_coinbase @@ -196,15 +211,12 @@ impl TxOutDisplay of Display { #[cfg(test)] mod tests { - use core::hash::HashStateTrait; - use core::hash::HashStateExTrait; - use core::poseidon::PoseidonTrait; - use super::{OutPoint, TxOut}; + use super::{OutPoint, OutPointTrait, TxOut}; use utils::hash::{DigestTrait}; #[test] pub fn test_outpoint_poseidon_hash() { - let test_outpoint = OutPoint { + let mut test_outpoint = OutPoint { txid: DigestTrait::new([1, 2, 3, 4, 5, 6, 7, 8]), vout: 2, // https://learnmeabitcoin.com/explorer/tx/0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9#output-0 @@ -213,13 +225,24 @@ mod tests { pk_script: @"410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac", cached: false, }, + block_hash: 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ee_u256 + .into(), block_height: 9, block_time: 1650000000, is_coinbase: false, }; - let hash = PoseidonTrait::new().update_with(test_outpoint).finalize(); assert_eq!( - hash, 2066345132208626685244560166700396327660679738257525753112003865133004328681 + test_outpoint.hash(), + 1078799518591159253686478630433512427930158685501072491129204005222453242688 + ); + + // Changing block_hash must lead to different outpoint hash + test_outpoint + .block_hash = 0x00000000d1145790a8694403d4063f323d499e655c83426834d4ce2f8dd4a2ef_u256 + .into(); + assert_ne!( + test_outpoint.hash(), + 1078799518591159253686478630433512427930158685501072491129204005222453242688 ); } } diff --git a/packages/consensus/src/types/utreexo.cairo b/packages/consensus/src/types/utreexo.cairo index 9bc28907..006880cc 100644 --- a/packages/consensus/src/types/utreexo.cairo +++ b/packages/consensus/src/types/utreexo.cairo @@ -28,6 +28,7 @@ //! Read more about utreexo: https://eprint.iacr.org/2019/611.pdf use super::transaction::OutPoint; +use super::utxo_set::UtxoSet; use utils::hash::{DigestImpl, DigestIntoU256}; use core::poseidon::PoseidonTrait; use core::hash::{HashStateTrait, HashStateExTrait}; @@ -46,6 +47,19 @@ pub struct UtreexoState { pub num_leaves: u64, } +#[generate_trait] +pub impl UtreexoStateImpl of UtreexoStateTrait { + fn validate_and_apply(ref self: UtreexoState, ref utxo_set: UtxoSet) -> Result<(), ByteArray> { + while let Option::Some(leave) = utxo_set.leaves_to_add.pop_front() { + self.add(leave); + }; + // TODO: delete leaves from utreexo + // while let Option::Some(leave) = utxo_set.leaves_to_delete.pop_front() { + // }; + Result::Ok(()) + } +} + /// Accumulator interface pub trait UtreexoAccumulator { /// Adds single output to the accumulator. @@ -243,7 +257,6 @@ impl UtreexoBatchProofDisplay of Display { #[cfg(test)] mod tests { use super::{UtreexoState, UtreexoAccumulator, UtreexoProof}; - use consensus::types::utxo_set::{UtxoSet, UtxoSetTrait}; /// Test the basic functionality of the Utreexo accumulator /// @@ -388,24 +401,22 @@ mod tests { fn test_utreexo_add() { // Test data is generated using scripts/data/utreexo.py - let mut utxo_set: UtxoSet = UtxoSetTrait::new(Default::default()); + let mut utreexo_state: UtreexoState = Default::default(); let outpoint: felt252 = 0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C; // add first leave to empty utreexo - utxo_set.leaves_to_add = array![outpoint]; - utxo_set.utreexo_add(); + utreexo_state.add(outpoint); let expected: Span> = array![ Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), Option::None ] .span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add first leave"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 1); + assert_eq!(utreexo_state.roots, expected, "cannot add first leave"); + assert_eq!(utreexo_state.num_leaves, 1); // add second leave - utxo_set.leaves_to_add = array![outpoint]; - utxo_set.utreexo_add(); + utreexo_state.add(outpoint); let expected: Span> = array![ Option::None, @@ -413,12 +424,11 @@ mod tests { Option::None ] .span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add second leave"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 2); + assert_eq!(utreexo_state.roots, expected, "cannot add second leave"); + assert_eq!(utreexo_state.num_leaves, 2); // add thirdth leave - utxo_set.leaves_to_add = array![outpoint]; - utxo_set.utreexo_add(); + utreexo_state.add(outpoint); let expected: Span> = array![ Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), @@ -426,12 +436,11 @@ mod tests { Option::None ] .span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add thirdth leave"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 3); + assert_eq!(utreexo_state.roots, expected, "cannot add thirdth leave"); + assert_eq!(utreexo_state.num_leaves, 3); // add fourth leave - utxo_set.leaves_to_add = array![outpoint]; - utxo_set.utreexo_add(); + utreexo_state.add(outpoint); let expected: Span> = array![ Option::None, @@ -440,12 +449,11 @@ mod tests { Option::None ] .span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add fourth leave"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 4); + assert_eq!(utreexo_state.roots, expected, "cannot add fourth leave"); + assert_eq!(utreexo_state.num_leaves, 4); // add fifth leave - utxo_set.leaves_to_add = array![outpoint]; - utxo_set.utreexo_add(); + utreexo_state.add(outpoint); let expected: Span> = array![ Option::Some(0x291F8F5FC449D42C715B529E542F24A80136D18F4A85DE28829CD3DCAAC1B9C), @@ -454,12 +462,13 @@ mod tests { Option::None ] .span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add fifth leave"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 5); + assert_eq!(utreexo_state.roots, expected, "cannot add fifth leave"); + assert_eq!(utreexo_state.num_leaves, 5); // add 3 leaves - utxo_set.leaves_to_add = array![outpoint, outpoint, outpoint]; - utxo_set.utreexo_add(); + for _ in 1..4_u8 { + utreexo_state.add(outpoint); + }; let expected: Span> = array![ Option::None, @@ -469,37 +478,13 @@ mod tests { Option::None ] .span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add 3 leaves"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 8); + assert_eq!(utreexo_state.roots, expected, "cannot add 3 leaves"); + assert_eq!(utreexo_state.num_leaves, 8); // add 22 leaves - utxo_set - .leaves_to_add = - array![ - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint, - outpoint - ]; - utxo_set.utreexo_add(); + for _ in 1..23_u8 { + utreexo_state.add(outpoint); + }; let expected: Span> = [ Option::None(()), @@ -509,7 +494,7 @@ mod tests { Option::Some(0x58D6BEF6CFC28638FB4C8271355961F50922BCC1577DD2B6D04E11B7A911702), Option::None(()) ].span(); - assert_eq!(utxo_set.utreexo_state.roots, expected, "cannot add 22 leaves"); - assert_eq!(utxo_set.utreexo_state.num_leaves, 30); + assert_eq!(utreexo_state.roots, expected, "cannot add 22 leaves"); + assert_eq!(utreexo_state.num_leaves, 30); } } diff --git a/packages/consensus/src/types/utxo_set.cairo b/packages/consensus/src/types/utxo_set.cairo index 7ff54bb2..e005e5ec 100644 --- a/packages/consensus/src/types/utxo_set.cairo +++ b/packages/consensus/src/types/utxo_set.cairo @@ -10,10 +10,7 @@ //! Utreexo accumulator or local cache. use core::dict::Felt252Dict; -use core::hash::{HashStateTrait, HashStateExTrait}; -use core::poseidon::PoseidonTrait; -use super::transaction::OutPoint; -use super::utreexo::{UtreexoState, UtreexoAccumulator}; +use super::transaction::{OutPoint, OutPointTrait}; pub const TX_OUTPUT_STATUS_NONE: u8 = 0; pub const TX_OUTPUT_STATUS_UNSPENT: u8 = 1; @@ -21,52 +18,142 @@ pub const TX_OUTPUT_STATUS_SPENT: u8 = 2; #[derive(Default, Destruct)] pub struct UtxoSet { - /// Utreexo state. - pub utreexo_state: UtreexoState, - /// The leaves represent the poseidon hashes of a block's outpoints, i.e. utxos + /// Hashes of created UTXOs. pub leaves_to_add: Array, - /// Hashes of UTXOs created within the current block(s). - /// Note that to preserve the ordering, cache has to be updated right after a + /// Hashes of spent (i.e. deleted) UTXOs. + pub leaves_to_delete: Array, + /// Number of pending cached UTXOs that must be spent within the current block(s). + pub num_cached: i32, + /// Statuses of UTXOs created or spent within the current block(s). + /// Note that to preserve the ordering, statuses has to be updated right after a /// particular output is created or spent. pub cache: Felt252Dict, } #[generate_trait] pub impl UtxoSetImpl of UtxoSetTrait { - fn new(utreexo_state: UtreexoState) -> UtxoSet { - UtxoSet { utreexo_state, ..Default::default() } - } - - fn add(ref self: UtxoSet, output: OutPoint) -> Result<(), ByteArray> { - let hash = PoseidonTrait::new().update_with(output).finalize(); - if output.data.cached { - if self.cache.get(hash) != TX_OUTPUT_STATUS_NONE { - return Result::Err("The output has already been added"); + fn add(ref self: UtxoSet, outpoint: OutPoint) -> Result<(), ByteArray> { + let hash = outpoint.hash(); + if self.cache.get(hash) == TX_OUTPUT_STATUS_NONE { + if outpoint.data.cached { + self.num_cached += 1; + } else { + self.leaves_to_add.append(hash); } self.cache.insert(hash, TX_OUTPUT_STATUS_UNSPENT); + Result::Ok(()) } else { - self.leaves_to_add.append(hash); - } - Result::Ok(()) - } - - fn utreexo_add(ref self: UtxoSet) { - for leave in self.leaves_to_add.clone() { - self.utreexo_state.add(leave); + Result::Err("The output has already been added") } } - fn spend(ref self: UtxoSet, output: @OutPoint) -> Result<(), ByteArray> { - let hash = PoseidonTrait::new().update_with(*output).finalize(); + fn spend(ref self: UtxoSet, outpoint: @OutPoint) -> Result<(), ByteArray> { + let hash = outpoint.hash(); let status = self.cache.get(hash); if status == TX_OUTPUT_STATUS_NONE { // Extra check that can be removed later. - assert(!*output.data.cached, 'output is not cached'); - // TODO: verify inclusion and update utreexo roots - } else if status == TX_OUTPUT_STATUS_SPENT { - return Result::Err("The output has already been spent"); + assert(!*outpoint.data.cached, 'cached output was not cached'); + + self.cache.insert(hash, TX_OUTPUT_STATUS_SPENT); + self.leaves_to_delete.append(hash); + Result::Ok(()) + } else if status == TX_OUTPUT_STATUS_UNSPENT { + // Extra check that can be removed later. + assert(*outpoint.data.cached, 'non-cached output was cached'); + + self.cache.insert(hash, TX_OUTPUT_STATUS_SPENT); + self.num_cached -= 1; + Result::Ok(()) + } else { + Result::Err("The output has already been spent") + } + } + + fn finalize(ref self: UtxoSet) -> Result<(), ByteArray> { + if self.num_cached != 0 { + Result::Err("There are unprocessed cached outputs") + } // TODO: uncomment when utreexo is enabled + // else if self.leaves_to_add.len() != 0 { + // Result::Err("There are unprocessed leaves to add") + // } else if self.leaves_to_delete.len() != 0 { + // Result::Err("There are unprocessed leaves to delete") + // } + else { + Result::Ok(()) + } + } +} + +#[cfg(test)] +mod tests { + use crate::types::transaction::{TxOut, OutPoint}; + use crate::types::utxo_set::{UtxoSet, UtxoSetTrait}; + use utils::hex::{from_hex, hex_to_hash_rev}; + + #[test] + fn test_utxo_set_add() { + let mut utxo_set: UtxoSet = Default::default(); + + // Test normal flow + utxo_set.add(dummy_outpoint(0, false)).unwrap(); + utxo_set.add(dummy_outpoint(1, true)).unwrap(); + utxo_set.add(dummy_outpoint(2, false)).unwrap(); + + assert_eq!(utxo_set.leaves_to_add.len(), 2); + assert_eq!(utxo_set.leaves_to_delete.len(), 0); + assert_eq!(utxo_set.num_cached, 1); + + // Test cached duplicates + let result = utxo_set.add(dummy_outpoint(1, true)); + assert_eq!(result.unwrap_err(), "The output has already been added"); + + // Test non-cached duplicates + let result = utxo_set.add(dummy_outpoint(0, false)); + assert_eq!(result.unwrap_err(), "The output has already been added"); + } + + #[test] + fn test_utxo_set_spend() { + let mut utxo_set: UtxoSet = Default::default(); + + // Test normal flow + utxo_set.add(dummy_outpoint(0, true)).unwrap(); + utxo_set.add(dummy_outpoint(1, false)).unwrap(); + utxo_set.add(dummy_outpoint(2, true)).unwrap(); + + utxo_set.spend(@dummy_outpoint(0, true)).unwrap(); + utxo_set.spend(@dummy_outpoint(3, false)).unwrap(); + utxo_set.spend(@dummy_outpoint(2, true)).unwrap(); + utxo_set.spend(@dummy_outpoint(4, false)).unwrap(); + + assert_eq!(utxo_set.leaves_to_add.len(), 1); + assert_eq!(utxo_set.leaves_to_delete.len(), 2); + assert_eq!(utxo_set.num_cached, 0); + + // Test cached double spending + let result = utxo_set.spend(@dummy_outpoint(0, true)); + assert_eq!(result.unwrap_err(), "The output has already been spent"); + + // Test non-cached double spending + let result = utxo_set.spend(@dummy_outpoint(3, false)); + assert_eq!(result.unwrap_err(), "The output has already been spent"); + } + + fn dummy_outpoint(vout: u32, cached: bool) -> OutPoint { + OutPoint { + txid: hex_to_hash_rev( + "0000000000000000000000000000000000000000000000000000000000000000" + ), + vout, + data: TxOut { + value: 50, + pk_script: @from_hex("76a914000000000000000000000000000000000000000088ac"), + cached, + }, + block_hash: Default::default(), + block_height: Default::default(), + block_time: Default::default(), + is_coinbase: false, } - self.cache.insert(hash, TX_OUTPUT_STATUS_SPENT); - Result::Ok(()) } } diff --git a/packages/consensus/src/validation/block.cairo b/packages/consensus/src/validation/block.cairo index aad2d1cd..6a5f0e9b 100644 --- a/packages/consensus/src/validation/block.cairo +++ b/packages/consensus/src/validation/block.cairo @@ -29,7 +29,11 @@ pub fn validate_block_weight(weight: usize) -> Result<(), ByteArray> { /// - wTXID commitment (only for blocks after Segwit upgrade, otherwise return zero hash) /// - Block weight pub fn compute_and_validate_tx_data( - txs: Span, block_height: u32, block_time: u32, ref utxo_set: UtxoSet + txs: Span, + block_hash: Digest, + block_height: u32, + block_time: u32, + ref utxo_set: UtxoSet ) -> Result<(u64, Digest, Digest), ByteArray> { let mut txids: Array = array![]; let mut wtxids: Array = array![]; @@ -71,7 +75,13 @@ pub fn compute_and_validate_tx_data( for output in *tx .outputs { let outpoint = OutPoint { - txid, vout, data: *output, block_height, block_time, is_coinbase: true, + txid, + vout, + data: *output, + block_hash, + block_height, + block_time, + is_coinbase: true, }; inner_result = utxo_set.add(outpoint); if inner_result.is_err() { @@ -81,7 +91,10 @@ pub fn compute_and_validate_tx_data( }; is_coinbase = false; } else { - let fee = match validate_transaction(tx, block_height, block_time, txid, ref utxo_set) { + let fee = + match validate_transaction( + tx, block_hash, block_height, block_time, txid, ref utxo_set + ) { Result::Ok(fee) => fee, Result::Err(err) => { inner_result = Result::Err(err); diff --git a/packages/consensus/src/validation/coinbase.cairo b/packages/consensus/src/validation/coinbase.cairo index 416cc7f7..783c69da 100644 --- a/packages/consensus/src/validation/coinbase.cairo +++ b/packages/consensus/src/validation/coinbase.cairo @@ -240,6 +240,7 @@ mod tests { txid: 0_u256.into(), vout: 0xffffffff_u32, data: TxOut { value: 0_64, ..Default::default(), }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -253,6 +254,7 @@ mod tests { txid: 0_u256.into(), vout: 0xffffffff_u32, data: TxOut { value: 0_64, ..Default::default(), }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -282,6 +284,7 @@ mod tests { txid: 0_u256.into(), vout: 0x1_u32, data: TxOut { value: 0_64, ..Default::default(), }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -300,6 +303,7 @@ mod tests { txid: 0x2_u256.into(), vout: 0xFFFFFFFF_u32, data: TxOut { value: 0_64, ..Default::default(), }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -322,6 +326,7 @@ mod tests { txid: 0_u256.into(), vout: 0xffffffff_u32, data: TxOut { value: 0_64, ..Default::default(), }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -358,6 +363,7 @@ mod tests { txid: 0_u256.into(), vout: 0xffffffff_u32, data: TxOut { value: 0_64, ..Default::default(), }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -395,6 +401,7 @@ mod tests { txid: 0x0_u256.into(), vout: 0xffffffff_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -600,6 +607,7 @@ mod tests { txid: 0x0_u256.into(), vout: 0xffffffff_u32, data: Default::default(), + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, diff --git a/packages/consensus/src/validation/locktime.cairo b/packages/consensus/src/validation/locktime.cairo index 5178621b..99c4844b 100644 --- a/packages/consensus/src/validation/locktime.cairo +++ b/packages/consensus/src/validation/locktime.cairo @@ -132,6 +132,8 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: 0x000000007bc154e0fa7ea32218a72fe2c1bb9f86cf8c9ebf9a715ed27fdb229a_u256 + .into(), block_height: 100, block_time: 1600000000, is_coinbase: false, @@ -155,6 +157,8 @@ mod tests { ), vout: 0, data: TxOut { value: 188442, ..Default::default() }, + block_hash: 0x00000000000000000006440de711734db5ed23115a2689539f99376c0385f8a6_u256 + .into(), block_height: 603018, block_time: 1573324462, is_coinbase: false, @@ -178,6 +182,8 @@ mod tests { ), vout: 0, data: TxOut { value: 13671, ..Default::default() }, + block_hash: 0x0000000000000000000e0c3650a889c4831a957f2fefc3d5f74f4faba7db7565_u256 + .into(), block_height: 603434, block_time: 1573549241, is_coinbase: false, @@ -198,6 +204,8 @@ mod tests { ), vout: 0, data: TxOut { value: 188442, ..Default::default() }, + block_hash: 0x00000000000000000006440de711734db5ed23115a2689539f99376c0385f8a6_u256 + .into(), block_height: 603018, // Initial block height block_time: 1573324462, is_coinbase: false, @@ -226,6 +234,8 @@ mod tests { ), vout: 0, data: TxOut { value: 13671, ..Default::default() }, + block_hash: 0x0000000000000000000e0c3650a889c4831a957f2fefc3d5f74f4faba7db7565_u256 + .into(), block_height: 603434, block_time: 1573549241, // Initial block time is_coinbase: false, @@ -253,6 +263,8 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: 0x000000007bc154e0fa7ea32218a72fe2c1bb9f86cf8c9ebf9a715ed27fdb229a_u256 + .into(), block_height: 100, // Previous block height block_time: 1600000000, // Previous block time is_coinbase: false, diff --git a/packages/consensus/src/validation/transaction.cairo b/packages/consensus/src/validation/transaction.cairo index a3b34ba3..3883ae42 100644 --- a/packages/consensus/src/validation/transaction.cairo +++ b/packages/consensus/src/validation/transaction.cairo @@ -11,7 +11,12 @@ use utils::hash::Digest; /// /// This does not include script checks and outpoint inclusion verification. pub fn validate_transaction( - tx: @Transaction, block_height: u32, block_time: u32, txid: Digest, ref utxo_set: UtxoSet + tx: @Transaction, + block_hash: Digest, + block_height: u32, + block_time: u32, + txid: Digest, + ref utxo_set: UtxoSet ) -> Result { if (*tx.inputs).is_empty() { return Result::Err("transaction inputs are empty"); @@ -74,7 +79,7 @@ pub fn validate_transaction( // Adds outpoint to the cache if the corresponding transaction output will be used // as a transaction input in the same block(s), or adds it to the utreexo otherwise. let outpoint = OutPoint { - txid, vout, data: *output, block_height, block_time, is_coinbase: false, + txid, vout, data: *output, block_hash, block_height, block_time, is_coinbase: false, }; inner_result = utxo_set.add(outpoint); @@ -120,12 +125,12 @@ fn validate_coinbase_maturity(output_height: u32, block_height: u32) -> Result<( #[cfg(test)] mod tests { use core::dict::Felt252Dict; - use core::hash::{HashStateTrait, HashStateExTrait}; - use core::poseidon::PoseidonTrait; use crate::codec::Encode; - use crate::types::transaction::{Transaction, TxIn, TxOut, OutPoint}; + use crate::types::transaction::{Transaction, TxIn, TxOut, OutPoint, OutPointTrait}; use crate::types::utxo_set::{UtxoSet, TX_OUTPUT_STATUS_UNSPENT}; - use utils::{hex::{from_hex, hex_to_hash_rev}, double_sha256::double_sha256_byte_array}; + use utils::{ + hash::Digest, hex::{from_hex, hex_to_hash_rev}, double_sha256::double_sha256_byte_array + }; use super::validate_transaction; // TODO: tests for coinbase maturity @@ -147,6 +152,7 @@ mod tests { ), vout: 0x00000000, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: true, @@ -172,11 +178,12 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - assert!(validate_transaction(@tx, 0, 0, txid, ref utxo_set).is_err()); + assert!(validate_transaction(@tx, Default::default(), 0, 0, txid, ref utxo_set).is_err()); utxo_set = Default::default(); - let fee = validate_transaction(@tx, 101, 0, txid, ref utxo_set).unwrap(); + let fee = validate_transaction(@tx, Default::default(), 101, 0, txid, ref utxo_set) + .unwrap(); assert_eq!(fee, 10); } @@ -201,7 +208,7 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - let result = validate_transaction(@tx, 0, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, Default::default(), 0, 0, txid, ref utxo_set); assert!(result.is_err()); // assert_eq!(result.unwrap_err(), "transaction inputs are empty"); } @@ -221,6 +228,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -237,7 +245,7 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - let result = validate_transaction(@tx, 0, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, Default::default(), 0, 0, txid, ref utxo_set); assert!(result.is_err()); // assert_eq!(result.unwrap_err(), "transaction outputs are empty"); } @@ -257,6 +265,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -281,7 +290,7 @@ mod tests { let mut utxo_set: UtxoSet = Default::default(); // Transaction should be invalid when current block height is less than locktime - let result = validate_transaction(@tx, 500000, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, Default::default(), 500000, 0, txid, ref utxo_set); assert!(result.is_err()); assert_eq!( result.unwrap_err().into(), @@ -292,7 +301,7 @@ mod tests { // Transaction should be valid when current block height is equal to or greater than // locktime - let result = validate_transaction(@tx, 500001, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, Default::default(), 500001, 0, txid, ref utxo_set); assert!(result.is_ok()); } @@ -311,6 +320,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -335,7 +345,9 @@ mod tests { let mut utxo_set: UtxoSet = Default::default(); // Transaction should be invalid when current block time is not greater than locktime - let result = validate_transaction(@tx, 0, 1600000000, txid, ref utxo_set); + let result = validate_transaction( + @tx, Default::default(), 0, 1600000000, txid, ref utxo_set + ); assert!(result.is_err()); assert_eq!( result.unwrap_err().into(), @@ -345,7 +357,9 @@ mod tests { utxo_set = Default::default(); // Transaction should be valid when current block time is equal to or greater than locktime - let result = validate_transaction(@tx, 0, 1600000001, txid, ref utxo_set); + let result = validate_transaction( + @tx, Default::default(), 0, 1600000001, txid, ref utxo_set + ); assert!(result.is_ok()); } @@ -364,6 +378,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -388,13 +403,17 @@ mod tests { let mut utxo_set: UtxoSet = Default::default(); // Transaction should still valid when current block time is not greater than locktime - let result = validate_transaction(@tx, 0, 1600000000, txid, ref utxo_set); + let result = validate_transaction( + @tx, Default::default(), 0, 1600000000, txid, ref utxo_set + ); assert!(result.is_ok()); utxo_set = Default::default(); // Transaction should be valid when current block time is greater than locktime - let result = validate_transaction(@tx, 0, 1600000001, txid, ref utxo_set); + let result = validate_transaction( + @tx, Default::default(), 0, 1600000001, txid, ref utxo_set + ); assert!(result.is_ok()); } @@ -413,6 +432,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -437,13 +457,13 @@ mod tests { let mut utxo_set: UtxoSet = Default::default(); // Transaction should still valid when current block time is not greater than locktime - let result = validate_transaction(@tx, 500000, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, Default::default(), 500000, 0, txid, ref utxo_set); assert!(result.is_ok()); utxo_set = Default::default(); // Transaction should be valid when current block time is greater than locktime - let result = validate_transaction(@tx, 500001, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, Default::default(), 500001, 0, txid, ref utxo_set); assert!(result.is_ok()); } @@ -464,6 +484,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: true, @@ -487,7 +508,8 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - validate_transaction(@tx, block_height, 0, txid, ref utxo_set).unwrap_err(); + validate_transaction(@tx, Default::default(), block_height, 0, txid, ref utxo_set) + .unwrap_err(); } #[test] @@ -507,6 +529,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, ..Default::default() }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: true, @@ -530,12 +553,12 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - validate_transaction(@tx, block_height, 0, txid, ref utxo_set).unwrap(); + validate_transaction(@tx, Default::default(), block_height, 0, txid, ref utxo_set).unwrap(); } #[test] - #[should_panic(expected: 'output is not cached')] - fn test_uncached_utxo_spending_attempt() { + #[should_panic(expected: 'cached output was not cached')] + fn test_missed_cached_utxo() { let block_height = 150; let tx = Transaction { @@ -551,6 +574,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: true }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -574,7 +598,56 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - validate_transaction(@tx, block_height, 0, txid, ref utxo_set).unwrap(); + validate_transaction(@tx, Default::default(), block_height, 0, txid, ref utxo_set).unwrap(); + } + + #[test] + #[should_panic(expected: 'non-cached output was cached')] + fn test_wrongly_cached_utxo() { + let block_height = 150; + + let tx = Transaction { + version: 1, + is_segwit: false, + inputs: array![ + TxIn { + script: @from_hex(""), + sequence: 0xfffffffe, + previous_output: OutPoint { + txid: hex_to_hash_rev( + "0000000000000000000000000000000000000000000000000000000000000000" + ), + vout: 0, + data: TxOut { value: 100, pk_script: @from_hex(""), cached: false }, + block_hash: Default::default(), + block_height: Default::default(), + block_time: Default::default(), + is_coinbase: false, + }, + witness: array![].span(), + } + ] + .span(), + outputs: array![ + TxOut { + value: 50, + pk_script: @from_hex("76a914000000000000000000000000000000000000000088ac"), + cached: false, + } + ] + .span(), + lock_time: 0 + }; + + let tx_bytes_legacy = @tx.encode(); + let txid = double_sha256_byte_array(tx_bytes_legacy); + + let mut cache: Felt252Dict = Default::default(); + let outpoint_hash = (*tx.inputs[0]).previous_output.hash(); + cache.insert(outpoint_hash, TX_OUTPUT_STATUS_UNSPENT); + let mut utxo_set = UtxoSet { cache, ..Default::default() }; + + validate_transaction(@tx, Default::default(), block_height, 0, txid, ref utxo_set).unwrap(); } #[test] @@ -594,6 +667,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: true }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -617,20 +691,19 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut cache: Felt252Dict = Default::default(); - let outpoint_hash = PoseidonTrait::new() - .update_with((*tx.inputs[0]).previous_output) - .finalize(); + let outpoint_hash = (*tx.inputs[0]).previous_output.hash(); cache.insert(outpoint_hash, TX_OUTPUT_STATUS_UNSPENT); - let mut utxo_set: UtxoSet = UtxoSet { - utreexo_state: Default::default(), leaves_to_add: Default::default(), cache: cache, - }; + let mut utxo_set = UtxoSet { cache, ..Default::default() }; - validate_transaction(@tx, block_height, 0, txid, ref utxo_set).unwrap(); + validate_transaction(@tx, Default::default(), block_height, 0, txid, ref utxo_set).unwrap(); } #[test] fn test_cached_utxo_duplicates() { let block_height = 150; + let block_hash: Digest = + 0x000000009ca75733b4cf527fe193b919201a2ed38c9e147a5665fdfade551f4d_u256 + .into(); let tx = Transaction { version: 1, @@ -645,6 +718,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: false }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -668,24 +742,20 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut cache: Felt252Dict = Default::default(); - let outpoint_hash = PoseidonTrait::new() - .update_with( - OutPoint { - txid, - vout: 0, - data: *tx.outputs[0], - block_height, - block_time: Default::default(), - is_coinbase: false, - } - ) - .finalize(); - cache.insert(outpoint_hash, TX_OUTPUT_STATUS_UNSPENT); - let mut utxo_set: UtxoSet = UtxoSet { - utreexo_state: Default::default(), leaves_to_add: Default::default(), cache, + let outpoint = OutPoint { + txid, + vout: 0, + data: *tx.outputs[0], + block_hash, + block_height, + block_time: Default::default(), + is_coinbase: false, }; + let outpoint_hash = outpoint.hash(); + cache.insert(outpoint_hash, TX_OUTPUT_STATUS_UNSPENT); + let mut utxo_set = UtxoSet { cache, ..Default::default() }; - let result = validate_transaction(@tx, block_height, 0, txid, ref utxo_set); + let result = validate_transaction(@tx, block_hash, block_height, 0, txid, ref utxo_set); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "The output has already been added"); } @@ -707,6 +777,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: true }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -722,6 +793,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: true }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -745,15 +817,13 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut cache: Felt252Dict = Default::default(); - let outpoint_hash = PoseidonTrait::new() - .update_with((*tx.inputs[0]).previous_output) - .finalize(); + let outpoint_hash = (*tx.inputs[0]).previous_output.hash(); cache.insert(outpoint_hash, TX_OUTPUT_STATUS_UNSPENT); - let mut utxo_set: UtxoSet = UtxoSet { - utreexo_state: Default::default(), leaves_to_add: Default::default(), cache, - }; + let mut utxo_set = UtxoSet { cache, ..Default::default() }; - let result = validate_transaction(@tx, block_height, 0, txid, ref utxo_set); + let result = validate_transaction( + @tx, Default::default(), block_height, 0, txid, ref utxo_set + ); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "The output has already been spent"); } @@ -775,6 +845,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: false }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -790,6 +861,7 @@ mod tests { ), vout: 0, data: TxOut { value: 100, pk_script: @from_hex(""), cached: false }, + block_hash: Default::default(), block_height: Default::default(), block_time: Default::default(), is_coinbase: false, @@ -813,7 +885,9 @@ mod tests { let txid = double_sha256_byte_array(tx_bytes_legacy); let mut utxo_set: UtxoSet = Default::default(); - let result = validate_transaction(@tx, block_height, 0, txid, ref utxo_set); + let result = validate_transaction( + @tx, Default::default(), block_height, 0, txid, ref utxo_set + ); assert!(result.is_err()); assert_eq!(result.unwrap_err(), "The output has already been spent"); } diff --git a/scripts/data/generate_data.py b/scripts/data/generate_data.py index 2c253614..780edf9b 100755 --- a/scripts/data/generate_data.py +++ b/scripts/data/generate_data.py @@ -167,6 +167,7 @@ def resolve_outpoint(input: dict): "txid": input["txid"], "vout": input["vout"], "data": format_output(tx["vout"][input["vout"]]), + "block_hash": block["hash"], "block_height": block["height"], "block_time": block["time"], "is_coinbase": tx["vin"][0].get("coinbase") is not None, @@ -186,6 +187,7 @@ def format_coinbase_input(input: dict): "pk_script": "0x", "cached": False, }, + "block_hash": "0" * 64, "block_height": 0, "block_time": 0, "is_coinbase": False, @@ -241,6 +243,7 @@ def format_header(header: dict): :param header: block header obtained from RPC """ return { + "hash": header["hash"], "version": header["version"], "time": header["time"], "bits": int.from_bytes(bytes.fromhex(header["bits"]), "big"),