From b5e0442443aac91fe421d61d85be0f20c5ed0a3b Mon Sep 17 00:00:00 2001 From: Aurora Gaffney Date: Sun, 23 Nov 2025 22:41:14 -0500 Subject: [PATCH] fix: use correct varint decoding for Handshake * move varint decode/encode from messages to common place * add additional block tests * use custom varint decode in block/transaction decode Fixes #438 Signed-off-by: Aurora Gaffney --- internal/handshake/block.go | 2 +- internal/handshake/block_test.go | 359 +++++++++++++----------- internal/handshake/protocol/messages.go | 68 +---- internal/handshake/transaction.go | 8 +- internal/handshake/varint.go | 91 ++++++ 5 files changed, 293 insertions(+), 235 deletions(-) create mode 100644 internal/handshake/varint.go diff --git a/internal/handshake/block.go b/internal/handshake/block.go index c02b907..5ac4ace 100644 --- a/internal/handshake/block.go +++ b/internal/handshake/block.go @@ -45,7 +45,7 @@ func (b *Block) Decode(r *bytes.Buffer) error { return err } // Transactions - txCount, err := binary.ReadUvarint(r) + txCount, err := ReadUvarintReader(r) if err != nil { return err } diff --git a/internal/handshake/block_test.go b/internal/handshake/block_test.go index 6a2a4d9..e6ba540 100644 --- a/internal/handshake/block_test.go +++ b/internal/handshake/block_test.go @@ -20,212 +20,231 @@ func decodeHex(hexData string) []byte { return ret } -func TestDecodeHandshakeBlock(t *testing.T) { - // Block 0000000000000000aaeb53f05d5d6f9ec895f3ab7858c8a6b5911e41e410ebc7 from Handshake mainnet - testBlockHex := "c29fc32ba934ec67000000000000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d980b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17c907ca66957417a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc000000007093091900000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3b2cf8140134369b3b00000000001498c8297a67eb81ec36253828b5621a601ba2328a0000a62204000306566961425443087c524fd539e1eab808000000000000000000000000021b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d00000000ffffffff74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa05000000ffffffff02000000000000000000145ad99a3052017938562ede6e228b68ca50c14663080320c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e53404bf1e01002000000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d86837107fe90d0000000014fb7148b38057231e023ce04e52a0d1d067a9100c0000000000000241cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab012102042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c02418920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d012103b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1" - expectedBlock := handshake.Block{ - Header: handshake.BlockHeader{ - Nonce: 734240706, - Time: 1743533225, - PrevBlock: [32]byte( - decodeHex( - "0000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d98", +var blockTestDefs = []struct { + blockHex string + expectedBlock *handshake.Block + expectedHash string +}{ + // TODO + { + blockHex: "c29fc32ba934ec67000000000000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d980b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17c907ca66957417a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc000000007093091900000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3b2cf8140134369b3b00000000001498c8297a67eb81ec36253828b5621a601ba2328a0000a62204000306566961425443087c524fd539e1eab808000000000000000000000000021b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d00000000ffffffff74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa05000000ffffffff02000000000000000000145ad99a3052017938562ede6e228b68ca50c14663080320c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e53404bf1e01002000000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d86837107fe90d0000000014fb7148b38057231e023ce04e52a0d1d067a9100c0000000000000241cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab012102042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c02418920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d012103b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1", + expectedHash: "0000000000000000aaeb53f05d5d6f9ec895f3ab7858c8a6b5911e41e410ebc7", + expectedBlock: &handshake.Block{ + Header: handshake.BlockHeader{ + Nonce: 734240706, + Time: 1743533225, + PrevBlock: [32]byte( + decodeHex( + "0000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d98", + ), ), - ), - NameRoot: [32]byte( - decodeHex( - "0b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17", + NameRoot: [32]byte( + decodeHex( + "0b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17", + ), ), - ), - ExtraNonce: [24]byte( - decodeHex("c907ca66957417a200000000000000000000000000000000"), - ), - WitnessRoot: [32]byte( - decodeHex( - "45779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c", + ExtraNonce: [24]byte( + decodeHex("c907ca66957417a200000000000000000000000000000000"), ), - ), - MerkleRoot: [32]byte( - decodeHex( - "7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc", + WitnessRoot: [32]byte( + decodeHex( + "45779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c", + ), ), - ), - Version: 0, - Bits: 420057968, - }, - Transactions: []handshake.Transaction{ - { - Version: 0, - LockTime: 271014, - Inputs: []handshake.TransactionInput{ - { - PrevOutpoint: handshake.Outpoint{ - Index: 0xffffffff, - }, - Sequence: 351808571, - Witness: [][]byte{ - decodeHex("566961425443"), - decodeHex("7c524fd539e1eab8"), - decodeHex("0000000000000000"), + MerkleRoot: [32]byte( + decodeHex( + "7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc", + ), + ), + Version: 0, + Bits: 420057968, + }, + Transactions: []handshake.Transaction{ + { + Version: 0, + LockTime: 271014, + Inputs: []handshake.TransactionInput{ + { + PrevOutpoint: handshake.Outpoint{ + Index: 0xffffffff, + }, + Sequence: 351808571, + Witness: [][]byte{ + decodeHex("566961425443"), + decodeHex("7c524fd539e1eab8"), + decodeHex("0000000000000000"), + }, }, }, - }, - Outputs: []handshake.TransactionOutput{ - { - Value: 1000027700, - Address: handshake.Address{ - Version: 0, - Hash: decodeHex( - "98c8297a67eb81ec36253828b5621a601ba2328a", - ), + Outputs: []handshake.TransactionOutput{ + { + Value: 1000027700, + Address: handshake.Address{ + Version: 0, + Hash: decodeHex( + "98c8297a67eb81ec36253828b5621a601ba2328a", + ), + }, }, }, }, - }, - { - Version: 0, - Inputs: []handshake.TransactionInput{ - { - PrevOutpoint: handshake.Outpoint{ - Hash: [32]byte( + { + Version: 0, + Inputs: []handshake.TransactionInput{ + { + PrevOutpoint: handshake.Outpoint{ + Hash: [32]byte( + decodeHex( + "1b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d", + ), + ), + Index: 0, + }, + Sequence: 0xffffffff, + Witness: [][]byte{ decodeHex( - "1b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d", + "cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab01", ), - ), - Index: 0, - }, - Sequence: 0xffffffff, - Witness: [][]byte{ - decodeHex( - "cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab01", - ), - decodeHex( - "02042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c", - ), - }, - }, - { - PrevOutpoint: handshake.Outpoint{ - Hash: [32]byte( decodeHex( - "74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa", + "02042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c", ), - ), - Index: 5, - }, - Sequence: 0xffffffff, - Witness: [][]byte{ - decodeHex( - "8920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d01", - ), - decodeHex( - "03b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1", - ), - }, - }, - }, - Outputs: []handshake.TransactionOutput{ - { - Value: 0, - Address: handshake.Address{ - Version: 0, - Hash: decodeHex( - "5ad99a3052017938562ede6e228b68ca50c14663", - ), + }, }, - Covenant: handshake.GenericCovenant{ - Type: 8, - Items: [][]byte{ + { + PrevOutpoint: handshake.Outpoint{ + Hash: [32]byte( + decodeHex( + "74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa", + ), + ), + Index: 5, + }, + Sequence: 0xffffffff, + Witness: [][]byte{ decodeHex( - "c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e534", + "8920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d01", ), - decodeHex("bf1e0100"), decodeHex( - "00000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d868", + "03b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1", ), }, }, }, - { - Value: 59751993399, - Address: handshake.Address{ - Version: 0, - Hash: decodeHex( - "fb7148b38057231e023ce04e52a0d1d067a9100c", - ), + Outputs: []handshake.TransactionOutput{ + { + Value: 0, + Address: handshake.Address{ + Version: 0, + Hash: decodeHex( + "5ad99a3052017938562ede6e228b68ca50c14663", + ), + }, + Covenant: handshake.GenericCovenant{ + Type: 8, + Items: [][]byte{ + decodeHex( + "c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e534", + ), + decodeHex("bf1e0100"), + decodeHex( + "00000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d868", + ), + }, + }, + }, + { + Value: 59751993399, + Address: handshake.Address{ + Version: 0, + Hash: decodeHex( + "fb7148b38057231e023ce04e52a0d1d067a9100c", + ), + }, }, }, }, }, }, - } - testBlockBytes, err := hex.DecodeString(testBlockHex) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - br := bytes.NewReader(testBlockBytes) - block, err := handshake.NewBlockFromReader(br) - if err != nil { - t.Fatalf("unexpected error deserializing block: %s", err) - } - if !reflect.DeepEqual(block.Header, expectedBlock.Header) { - t.Fatalf( - "did not get expected block header:\n got: %#v\n wanted: %#v", - block.Header, - expectedBlock.Header, - ) - } - if len(block.Transactions) != len(expectedBlock.Transactions) { - t.Fatalf( - "did not get expected TX count: got %d, wanted %d", - len(block.Transactions), - len(expectedBlock.Transactions), - ) - } - for idx, testTx := range block.Transactions { - expectedTx := expectedBlock.Transactions[idx] - // Compare inputs - if !reflect.DeepEqual(testTx.Inputs, expectedTx.Inputs) { - t.Fatalf( - "did not get expected TX inputs:\n got: %#v\n wanted: %#v", - testTx.Inputs, - expectedTx.Inputs, - ) + }, + { + blockHex: "e10afe1d0575465e000000000000000000000660013cac2e01c211a6c1035f31395126c4ed9d6d0f9c8b01b9000000000000000000000000000000000000000000000000000000000000000000001cb89fa82d79000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000f154361b707effe251bea1832f6406808b6cbfe0e66545ac8bd24d34d48df318c55e70b65ad508c302b8de3b1801978d6c2c6a977a1fe092931f1233303d37c6010000009a80081a000000000000000000000000000000000000000000000000000000000000000001000000000b0000000000000000000000000000000000000000000000000000000000000000ffffffff0e67bca30000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000ffffffffffffffff0b00863ba1010000000014ff1538413692fc752d0a5c26373f1c8141a764fe000000f0ab75a40d000000143e80585bb592b510c71b4130048cc89cae36414a00000088526a7400000000140df60b10ba95c995c534c5fcd780e0a90c223dcf00000010a5d4e80000000014e48023536826f4b90aeba5c17d86b29c624bd7be00000010a5d4e80000000014e6b96ee9010fccdba47c39b65a40e080104ccda5000000a89c134602000000144110f9669dcb86d4f335fcda7ae04a4cadf5cd1400000010a5d4e8000000001485775a2244453b7e65b00d9628bbc465d380f36f00000098f73e5d0100000014d1076f706774125a9d56caca5fe8f8615f43b99b00000098f73e5d01000000147e58eda1cf19ff672b4a10d364ffe80591b164d800000088526a740000000014284beb0688f0d154640082e5196cdb697775efb3000000e8764817000000001498b59906bd3cf4fe1274027c250e7c6f53e69c6f0000e0070000030f6d696e65642062792036626c6f636b0827b3d70119f2af3108000000000000000001fda4011e0100000bd1573aed4e516931f04bf5b7b0e83409a4a688b13a2e4bfe2e70efc01a0d5fbab892fe888192e83f11ff98e91fc13b17db233d7aa5fd62049b51a72807897004da8fa7ab55852f8a8cbd2403a1a365d079a9d621e40edf1b13a82f3b3e83be47c77f140b4c6cbe7f4b3fedac981aec90f746ff442c5775790909b7bb4566c395b30f142db9f3328bf4c557f61bef54e2184ed7831eebf63651fb62b932c3cdff0b5552b7887ddc178e96922af2be3facfc41efaa0554b86a50470b387214cfb2cdadd49affcaf6774a9b03b77262d49de50f88ac12e5cb837c136513c1e21cf08e71041eecb1610319bdd541fae81ee96ff83d0e208363932df6b439db2987ac0ac39775f0fee6f0e9f1d836f3c36c65a7052299b2e8497fae2e7c137d65897914daacaf73203e57e138c2a90f138092847a81680cc06d2c5d7621e716f01f005b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed870000200400143e80585bb592b510c71b4130048cc89cae36414a00557993a40d00000100143e80585bb592b510c71b4130048cc89cae36414afe0065cd1d0001fda401390100000b6f7ea52d0e03ed2ace6b8213c782e31d9e36a7e1a603203acc75559a06978f98cbf5536c440b704ac1efa5eedfd52b1a6454c3d8567fab9cc7ea13279e86aeca4637c235c05599a637147ed50f346f33b8c3f7e6cbc5d02f05ab04ae6ef09f1d4368df5aed3ff761401bb91274ae58946015cf0f85046400a9698bd2e483b2973cdb9d972b6017f285ce1082a37cc0cddfbaf90d3aebe2b99b0025891cde17678315591505f4a9083ced2b950c56d44f61a270b9664b0c44ddbe1693610ab617cdadd49affcaf6774a9b03b77262d49de50f88ac12e5cb837c136513c1e21cf08e71041eecb1610319bdd541fae81ee96ff83d0e208363932df6b439db2987ac0ac39775f0fee6f0e9f1d836f3c36c65a7052299b2e8497fae2e7c137d65897914daacaf73203e57e138c2a90f138092847a81680cc06d2c5d7621e716f01f005b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed870000200400140df60b10ba95c995c534c5fcd780e0a90c223dcf00ed1f88740000000100140df60b10ba95c995c534c5fcd780e0a90c223dcffe0065cd1d0001fda4013b0300000be8078dc47e46761a074ef9195ec3e12687862676a03519b55c6b7bf664956e18acd0cae14e715a9de42f4e42fed14fd0074a0c16732ab45c1d43749ca7558daa9200086512edd60084ab51cdb194603711d2846ca4bd0fd039b8179ba853d4d9c23abbe62d58e3cf216fd4b521db688f7c154a04af8fca7355d9e78ff3b821bf9c1e7e4df556caee5b1b22862849c2e261470b661f176ed18d73c6fa9fc1f4b9bd05fcd9c85f86c3a8fad0cee0dd1c04b92236287cb8667017adb1dbc917e8bff180df91d8651982f1cfa41000e3448d63826a9356fd32def245b31ee4387a59bdf727521dcc8d0b78608ed752efec9f8793316afa83f7ada2532d1cc7c03fbeaeb076a558a13b2318d4c8e8faaf6c4529d6c7dfe295ed412b573008cb2d843e05c741e30fecf2f0c3a1a49d9b90d7e9b14677f77f0de53b87ced2cdfe1b08995b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed87000020040014e48023536826f4b90aeba5c17d86b29c624bd7be007572f2e8000000010014e48023536826f4b90aeba5c17d86b29c624bd7befe0065cd1d0001fda401560000000b8e7c489e0183a073a34c492ca92fc5f899778fd58d3d58f5a28439da423ec2aab542ccca13c7fcd7758fe58d71c7b85d7f887031239f88a59e4a925e3f9c567f76e59a0e1d708fbd8ae8fca9c508edf7aab43b5d219bbe9a6b3a6f163d15e855b3ceb16cae30e13e05812018b56bdd41934d00af9db823e44290b4843e7dce58aa242c88b7409c238c1438c5db770996b0ec3dd87b9e0b6cec475f7231b9195a2a72451a3a37e6883c36a071725e26b96c1ccc9c9cb1b478691cd35e3623ceb12c0e142e3b0316ba174c710de0d8767b12629db8daffc5fe278a42372b531538d38d2b4e6701581054ea0ba4b7b5f79b624adba2f7467dc50365df4b8f126ddb76e3fd0fca91b81256c0d835e93afe786abdfc8ce916f01ee2debf59bd3ac2dc14daacaf73203e57e138c2a90f138092847a81680cc06d2c5d7621e716f01f005b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed87000020040014e6b96ee9010fccdba47c39b65a40e080104ccda5007572f2e8000000010014e6b96ee9010fccdba47c39b65a40e080104ccda5fe0065cd1d0001fda401a30400000b55a38e42d142866061b1cc74e7047504803c9fd2ca68e93d7beee6612b506dd097d2d6871c8fcb3a86f6b65cb4f8d0e6507c63f4230f1fb83ecf0ae9260026e37713a197fc025227785c9c7942b00d800f93fb9a36b08d94df0299510ca5603f2db6149f7d6ada8f8cfc5273117f62037fc6ae1ef975bed0a95d75d9b58175f48e52a6a9eb316870991f587f22cf5d0914d4a164e7c3c6f556cb6ed7e270cb3ca90ec507db2ec44652b80564133913f1a31c906aa9d98b2f0f17f5c3e07d519422f976ed813484134f7d090ff38661ba7dad7d6eba34dd9796546ac304ed6719e358a0e69f67e502301a4ea5c5955f25f0cf138c02f000917e69264628e7b3f6b5b88b27bf5097f0ce1d314175858fc812eb1d4127d129a349be00792490e5cd0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a80af81f288c4e9fda063a66eec848081931cae872dd7a8eb94f6a5d9a7b8fdcac0000200400144110f9669dcb86d4f335fcda7ae04a4cadf5cd14000d6a31460200000100144110f9669dcb86d4f335fcda7ae04a4cadf5cd14fe0065cd1d0001fda4011d0200000bff0eec3a7d260b1e55bbb30c08918f8ee8f7556bcaf0ddcd4fd5b00b5af954a4095cb6d7d3c9a59b11dca1e3fa419e86206d41ee0da6dc429c2b25ea967aa65a2188c1e56c1d44e6cbe478f7b7c8f2d384aa6d2750ed0664d601685aa384200afe9ca1fc45ea959aba88c950adbe4d2a81fd40e4971f3fe6d1fc2624cbb6df8696b93b2fec11f632191aa24f88805d6611eae89cf7feb9ea96cb8bc63101d0acba0760839abcbbc81098d0b4ba8686fedc5768739cc8dfc7902fe48b7459c90e0b9a349532721c9205b2168b248eefec4f0c0620f4fe8321be3e35290a2e9cd7dec54c829b5ffd5b7a3f949906fbce707e63cca80b1919c52d8f0a6e6c8ebcaaeb7e2f8296ac2a5fc057da5bb7d7c2295de9f77c5e070ee72662b2773a2cb5d605c741e30fecf2f0c3a1a49d9b90d7e9b14677f77f0de53b87ced2cdfe1b08995b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed8700002004001485775a2244453b7e65b00d9628bbc465d380f36f007572f2e800000001001485775a2244453b7e65b00d9628bbc465d380f36ffe0065cd1d0001fda401f70400000b11382cd89148c9135fd6ab784c2c26d5d4911bf0da72fec8d26d57096011fc9e9e43e9f4913db60034c973ceb6d79c09b2cb333ba89026149331f2d43c44820764ad55e3e857b021b24f46dae7f4a4ffc4bbdf2c85984caf4abb0fb7b3f70bf3c2fd827811edc3b3f23cb289ebf544762734dddfcaac674aafd2ed5581f250614d7cb24fda2f3b8190028ed0a03de8a39d382e32ab1a649d840fb3d3848da356b2d3f13f5e4682266b48f50ac7f2ae11ca119cf830b2be42b5cc7c48f48f848af70febbd19a8a2371abbee63204bc4ea2ae99ffffd758f80d0af13098fe46111e358a0e69f67e502301a4ea5c5955f25f0cf138c02f000917e69264628e7b3f6b5b88b27bf5097f0ce1d314175858fc812eb1d4127d129a349be00792490e5cd0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a80af81f288c4e9fda063a66eec848081931cae872dd7a8eb94f6a5d9a7b8fdcac000020040014d1076f706774125a9d56caca5fe8f8615f43b99b00fdc45c5d010000010014d1076f706774125a9d56caca5fe8f8615f43b99bfe0065cd1d0001fda4011b0000000b7458edac1adabd5829dd509a157256d3710b4d8076cb9e6a51e53050ab65fc0ce18beb253f513c79ea81b49bb8bf5a3643277c1679fcd2abcbecde20e9f491e51ac669331ed9d1fd98af7fe10047daef4b88c01d3f51415267c3608c8030ca70fd372af2f90283c9b113d094cd70fc805846cb693a20ce7d2eb4ba91922e427278e95738f511a9be933a5a20638764ec564414f8853011640df073fbbf3ec479f273ce699a34943426731003ea978812031f9c125d394de369e4e2c48b076e4f9733bd5c8225b2e670e6c7edfb91beed9669e94025124ae9173de542e3e76274d38d2b4e6701581054ea0ba4b7b5f79b624adba2f7467dc50365df4b8f126ddb76e3fd0fca91b81256c0d835e93afe786abdfc8ce916f01ee2debf59bd3ac2dc14daacaf73203e57e138c2a90f138092847a81680cc06d2c5d7621e716f01f005b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed870000200400147e58eda1cf19ff672b4a10d364ffe80591b164d800fdc45c5d0100000100147e58eda1cf19ff672b4a10d364ffe80591b164d8fe0065cd1d0001fda401f40400000bcf8213b0cc54fc18e010a6bb9ddc3bc01b55e0a3aab5a2d985c1d2817871d058446da5fde6be4ca76584b1ed651eaa59d38fdd77db5c90b52acaa8decfc1a74b64ad55e3e857b021b24f46dae7f4a4ffc4bbdf2c85984caf4abb0fb7b3f70bf3c2fd827811edc3b3f23cb289ebf544762734dddfcaac674aafd2ed5581f250614d7cb24fda2f3b8190028ed0a03de8a39d382e32ab1a649d840fb3d3848da356b2d3f13f5e4682266b48f50ac7f2ae11ca119cf830b2be42b5cc7c48f48f848af70febbd19a8a2371abbee63204bc4ea2ae99ffffd758f80d0af13098fe46111e358a0e69f67e502301a4ea5c5955f25f0cf138c02f000917e69264628e7b3f6b5b88b27bf5097f0ce1d314175858fc812eb1d4127d129a349be00792490e5cd0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a80af81f288c4e9fda063a66eec848081931cae872dd7a8eb94f6a5d9a7b8fdcac000020040014284beb0688f0d154640082e5196cdb697775efb300ed1f8874000000010014284beb0688f0d154640082e5196cdb697775efb3fe0065cd1d0001fda401820100000b4e7d87026aba05711d51d3b2275b38c214dac09e13b7307c51bc4aaa828ddf7309b1c140bfe88564de108b1ae924764d6a9f30d490516ffde94b5f399a9249216659dc4f48e60eee37da5bf6a3717060502e0c8292397948715be98a6b4799d724ce560c74aa8af81232c6139f17809d1bfcda21f4e87404db2f6b604dd4d40b9692c8820df9485cfa300de9ce88659004beba584840a348e42b7ab972bc9a38949f39433f99e0f4e6c8bf549f623e3ec2af631833aa47d3892c1917545b1a0f053c86c4237519f56a1915e5a2c843654cd571fe944ac49135ebc18af84dc5868f57f108e8994f5b94b9aa280f0fb9b234e6317e2ef63b0f1380d3897be7682a0ac39775f0fee6f0e9f1d836f3c36c65a7052299b2e8497fae2e7c137d65897914daacaf73203e57e138c2a90f138092847a81680cc06d2c5d7621e716f01f005b0a3e491dae4390049bb15f46655e31e3da3253b29cf0104f59d0c630baed8700002004001498b59906bd3cf4fe1274027c250e7c6f53e69c6f004d44661700000001001498b59906bd3cf4fe1274027c250e7c6f53e69c6ffe0065cd1d00", + expectedHash: "0000000000000424ee6c2a5d6e0da5edfc47a4a10328c1792056ee48303c3e40", + }, +} + +func TestDecodeHandshakeBlock(t *testing.T) { + for _, testDef := range blockTestDefs { + if testDef.expectedBlock == nil { + continue + } + testBlockBytes, err := hex.DecodeString(testDef.blockHex) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + br := bytes.NewReader(testBlockBytes) + block, err := handshake.NewBlockFromReader(br) + if err != nil { + t.Fatalf("unexpected error deserializing block: %s", err) } - // Compare outputs - if !reflect.DeepEqual(testTx.Outputs, expectedTx.Outputs) { + if !reflect.DeepEqual(block.Header, testDef.expectedBlock.Header) { t.Fatalf( - "did not get expected TX outputs:\n got: %#v\n wanted: %#v", - testTx.Outputs, - expectedTx.Outputs, + "did not get expected block header:\n got: %#v\n wanted: %#v", + block.Header, + testDef.expectedBlock.Header, ) } - // Compare lock time - if testTx.LockTime != expectedTx.LockTime { + if len(block.Transactions) != len(testDef.expectedBlock.Transactions) { t.Fatalf( - "did not get expected TX lock time: got %d, wanted %d", - testTx.LockTime, - expectedTx.LockTime, + "did not get expected TX count: got %d, wanted %d", + len(block.Transactions), + len(testDef.expectedBlock.Transactions), ) } + for idx, testTx := range block.Transactions { + expectedTx := testDef.expectedBlock.Transactions[idx] + // Compare inputs + if !reflect.DeepEqual(testTx.Inputs, expectedTx.Inputs) { + t.Fatalf( + "did not get expected TX inputs:\n got: %#v\n wanted: %#v", + testTx.Inputs, + expectedTx.Inputs, + ) + } + // Compare outputs + if !reflect.DeepEqual(testTx.Outputs, expectedTx.Outputs) { + t.Fatalf( + "did not get expected TX outputs:\n got: %#v\n wanted: %#v", + testTx.Outputs, + expectedTx.Outputs, + ) + } + // Compare lock time + if testTx.LockTime != expectedTx.LockTime { + t.Fatalf( + "did not get expected TX lock time: got %d, wanted %d", + testTx.LockTime, + expectedTx.LockTime, + ) + } + } } } func TestHandshakeBlockHash(t *testing.T) { - expectedHash := "0000000000000000aaeb53f05d5d6f9ec895f3ab7858c8a6b5911e41e410ebc7" - testBlockHex := "c29fc32ba934ec67000000000000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d980b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17c907ca66957417a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc000000007093091900000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3b2cf8140134369b3b00000000001498c8297a67eb81ec36253828b5621a601ba2328a0000a62204000306566961425443087c524fd539e1eab808000000000000000000000000021b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d00000000ffffffff74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa05000000ffffffff02000000000000000000145ad99a3052017938562ede6e228b68ca50c14663080320c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e53404bf1e01002000000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d86837107fe90d0000000014fb7148b38057231e023ce04e52a0d1d067a9100c0000000000000241cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab012102042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c02418920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d012103b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1" - testBlockBytes, err := hex.DecodeString(testBlockHex) - if err != nil { - t.Fatalf("unexpected error: %s", err) - } - br := bytes.NewReader(testBlockBytes) - block, err := handshake.NewBlockFromReader(br) - if err != nil { - t.Fatalf("unexpected error deserializing block: %s", err) - } - blockHash := block.Hash() - blockHashHex := hex.EncodeToString(blockHash[:]) - if blockHashHex != expectedHash { - t.Fatalf("did not get expected block hash, got %s, wanted %s", blockHashHex, expectedHash) + for _, testDef := range blockTestDefs { + testBlockBytes, err := hex.DecodeString(testDef.blockHex) + if err != nil { + t.Fatalf("unexpected error: %s", err) + } + br := bytes.NewReader(testBlockBytes) + block, err := handshake.NewBlockFromReader(br) + if err != nil { + t.Fatalf("unexpected error deserializing block: %s", err) + } + blockHash := block.Hash() + blockHashHex := hex.EncodeToString(blockHash[:]) + if blockHashHex != testDef.expectedHash { + t.Fatalf("did not get expected block hash, got %s, wanted %s", blockHashHex, testDef.expectedHash) + } } } diff --git a/internal/handshake/protocol/messages.go b/internal/handshake/protocol/messages.go index 386cfed..0b6e840 100644 --- a/internal/handshake/protocol/messages.go +++ b/internal/handshake/protocol/messages.go @@ -11,7 +11,6 @@ import ( "encoding/binary" "errors" "fmt" - "math" "net" "github.com/blinklabs-io/cdnsd/internal/handshake" @@ -106,57 +105,6 @@ func decodeMessage(header *msgHeader, payload []byte) (Message, error) { return ret, nil } -func readUvarint(data []byte) (uint64, int, error) { - if len(data) == 0 { - return 0, 0, errors.New("data is empty") - } - var ret uint64 - prefix := data[0] - switch prefix { - case 0xff: - if len(data) < 9 { - return 0, 0, errors.New("invalid length for uint64") - } - ret = uint64(binary.LittleEndian.Uint64(data[1:9])) - return ret, 9, nil - case 0xfe: - if len(data) < 5 { - return 0, 0, errors.New("invalid length for uint32") - } - ret = uint64(binary.LittleEndian.Uint32(data[1:5])) - return ret, 5, nil - case 0xfd: - if len(data) < 3 { - return 0, 0, errors.New("invalid length for uint16") - } - ret = uint64(binary.LittleEndian.Uint16(data[1:3])) - return ret, 3, nil - default: - return uint64(prefix), 1, nil - } -} - -func writeUvarint(val uint64) []byte { - var ret []byte - switch { - case val < 0xfd: - ret = []byte{uint8(val)} - case val <= math.MaxUint16: - ret = make([]byte, 3) - ret[0] = 0xfd // nolint:gosec // false positive for slice index out of bounds - binary.LittleEndian.PutUint16(ret[1:], uint16(val)) - case val <= math.MaxUint32: - ret = make([]byte, 5) - ret[0] = 0xfe // nolint:gosec // false positive for slice index out of bounds - binary.LittleEndian.PutUint32(ret[1:], uint32(val)) - default: - ret = make([]byte, 9) - ret[0] = 0xff // nolint:gosec // false positive for slice index out of bounds - binary.LittleEndian.PutUint64(ret[1:], val) - } - return ret -} - type msgHeader struct { NetworkMagic uint32 MessageType uint8 @@ -358,7 +306,7 @@ type MsgAddr struct { func (m *MsgAddr) Encode() []byte { buf := new(bytes.Buffer) - uvarCount := writeUvarint(uint64(len(m.Peers))) + uvarCount := handshake.WriteUvarint(uint64(len(m.Peers))) _, _ = buf.Write(uvarCount) for _, peer := range m.Peers { peerBytes := peer.Encode() @@ -368,7 +316,7 @@ func (m *MsgAddr) Encode() []byte { } func (m *MsgAddr) Decode(data []byte) error { - count, bytesRead, err := readUvarint(data) + count, bytesRead, err := handshake.ReadUvarint(data) if err != nil { return err } @@ -392,7 +340,7 @@ type MsgGetData struct { func (m *MsgGetData) Encode() []byte { buf := new(bytes.Buffer) - uvarCount := writeUvarint(uint64(len(m.Inventory))) + uvarCount := handshake.WriteUvarint(uint64(len(m.Inventory))) _, _ = buf.Write(uvarCount) for _, inv := range m.Inventory { invBytes := inv.Encode() @@ -402,7 +350,7 @@ func (m *MsgGetData) Encode() []byte { } func (m *MsgGetData) Decode(data []byte) error { - count, bytesRead, err := readUvarint(data) + count, bytesRead, err := handshake.ReadUvarint(data) if err != nil { return err } @@ -427,7 +375,7 @@ type MsgGetHeaders struct { func (m *MsgGetHeaders) Encode() []byte { buf := new(bytes.Buffer) - locatorCount := writeUvarint(uint64(len(m.Locator))) + locatorCount := handshake.WriteUvarint(uint64(len(m.Locator))) _, _ = buf.Write(locatorCount) for _, loc := range m.Locator { _, _ = buf.Write(loc[:]) @@ -437,7 +385,7 @@ func (m *MsgGetHeaders) Encode() []byte { } func (m *MsgGetHeaders) Decode(data []byte) error { - count, bytesRead, err := readUvarint(data) + count, bytesRead, err := handshake.ReadUvarint(data) if err != nil { return err } @@ -461,7 +409,7 @@ type MsgHeaders struct { func (m *MsgHeaders) Encode() []byte { buf := new(bytes.Buffer) - headerCount := writeUvarint(uint64(len(m.Headers))) + headerCount := handshake.WriteUvarint(uint64(len(m.Headers))) _, _ = buf.Write(headerCount) for _, header := range m.Headers { _, _ = buf.Write(header.Encode()) @@ -470,7 +418,7 @@ func (m *MsgHeaders) Encode() []byte { } func (m *MsgHeaders) Decode(data []byte) error { - count, bytesRead, err := readUvarint(data) + count, bytesRead, err := handshake.ReadUvarint(data) if err != nil { return err } diff --git a/internal/handshake/transaction.go b/internal/handshake/transaction.go index 176c8e3..42e9af3 100644 --- a/internal/handshake/transaction.go +++ b/internal/handshake/transaction.go @@ -49,7 +49,7 @@ func (t *Transaction) Decode(r *bytes.Buffer) error { return err } // Inputs - inCount, err := binary.ReadUvarint(r) + inCount, err := ReadUvarintReader(r) if err != nil { return err } @@ -61,7 +61,7 @@ func (t *Transaction) Decode(r *bytes.Buffer) error { t.Inputs = append(t.Inputs, tmpInput) } // Outputs - outCount, err := binary.ReadUvarint(r) + outCount, err := ReadUvarintReader(r) if err != nil { return err } @@ -136,13 +136,13 @@ func (i *TransactionInput) Decode(r *bytes.Buffer) error { } func (i *TransactionInput) DecodeWitness(r io.Reader) error { - witnessCount, err := binary.ReadUvarint(r.(io.ByteReader)) + witnessCount, err := ReadUvarintReader(r) if err != nil { return err } i.Witness = make([][]byte, witnessCount) for j := range witnessCount { - itemLength, err := binary.ReadUvarint(r.(io.ByteReader)) + itemLength, err := ReadUvarintReader(r) if err != nil { return err } diff --git a/internal/handshake/varint.go b/internal/handshake/varint.go new file mode 100644 index 0000000..3a4bf8e --- /dev/null +++ b/internal/handshake/varint.go @@ -0,0 +1,91 @@ +// Copyright 2025 Blink Labs Software +// +// Use of this source code is governed by an MIT-style +// license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +package handshake + +import ( + "bytes" + "encoding/binary" + "errors" + "io" + "math" +) + +func ReadUvarint(data []byte) (uint64, int, error) { + if len(data) == 0 { + return 0, 0, errors.New("data is empty") + } + r := bytes.NewReader(data) + val, err := ReadUvarintReader(r) + if err != nil { + return 0, 0, err + } + return val, len(data) - r.Len(), nil +} + +func ReadUvarintReader(r io.Reader) (uint64, error) { + var ret uint64 + prefix := make([]byte, 1) + if _, err := io.ReadFull(r, prefix); err != nil { + return 0, err + } + switch prefix[0] { + case 0xff: + data := make([]byte, 8) + if _, err := io.ReadFull(r, data); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return 0, errors.New("invalid length for uint64") + } + return 0, err + } + ret = uint64(binary.LittleEndian.Uint64(data)) + return ret, nil + case 0xfe: + data := make([]byte, 4) + if _, err := io.ReadFull(r, data); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return 0, errors.New("invalid length for uint32") + } + return 0, err + } + ret = uint64(binary.LittleEndian.Uint32(data)) + return ret, nil + case 0xfd: + data := make([]byte, 2) + if _, err := io.ReadFull(r, data); err != nil { + if errors.Is(err, io.ErrUnexpectedEOF) { + return 0, errors.New("invalid length for uint16") + } + return 0, err + } + ret = uint64(binary.LittleEndian.Uint16(data)) + return ret, nil + default: + // nolint:gosec // This isn't actually an issue, but the latest gosec is giving false positives + return uint64(prefix[0]), nil + } +} + +func WriteUvarint(val uint64) []byte { + var ret []byte + switch { + case val < 0xfd: + ret = []byte{uint8(val)} + case val <= math.MaxUint16: + ret = make([]byte, 3) + ret[0] = 0xfd // nolint:gosec // false positive for slice index out of bounds + binary.LittleEndian.PutUint16(ret[1:], uint16(val)) + case val <= math.MaxUint32: + ret = make([]byte, 5) + ret[0] = 0xfe // nolint:gosec // false positive for slice index out of bounds + binary.LittleEndian.PutUint32(ret[1:], uint32(val)) + default: + ret = make([]byte, 9) + ret[0] = 0xff // nolint:gosec // false positive for slice index out of bounds + binary.LittleEndian.PutUint64(ret[1:], val) + } + return ret +}