Skip to content

Commit 5f05704

Browse files
committed
feat: support for decoding Handshake block data
This adds support for decoding Handshake blocks and transactions, including inputs, outputs, and witnesses. Fixes #256 Signed-off-by: Aurora Gaffney <[email protected]>
1 parent 63450e8 commit 5f05704

File tree

3 files changed

+406
-0
lines changed

3 files changed

+406
-0
lines changed

internal/handshake/block.go

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file or at
5+
// https://opensource.org/licenses/MIT.
6+
7+
package handshake
8+
9+
import (
10+
"encoding/binary"
11+
"io"
12+
)
13+
14+
type Block struct {
15+
Header BlockHeader
16+
Transactions []Transaction
17+
}
18+
19+
func (b *Block) Decode(r io.Reader) error {
20+
// Decode header
21+
if err := b.Header.Decode(r); err != nil {
22+
return err
23+
}
24+
// Transactions
25+
txCount, err := binary.ReadUvarint(r.(io.ByteReader))
26+
if err != nil {
27+
return err
28+
}
29+
for i := uint64(0); i < txCount; i++ {
30+
var tmpTx Transaction
31+
if err := tmpTx.Decode(r); err != nil {
32+
return err
33+
}
34+
b.Transactions = append(b.Transactions, tmpTx)
35+
}
36+
return nil
37+
}
38+
39+
type BlockHeader struct {
40+
Nonce uint32
41+
Time uint64
42+
PrevBlock [32]byte
43+
NameRoot [32]byte
44+
ExtraNonce [24]byte
45+
ReservedRoot [32]byte
46+
WitnessRoot [32]byte
47+
MerkleRoot [32]byte
48+
Version uint32
49+
Bits uint32
50+
Mask [32]byte
51+
}
52+
53+
func (h *BlockHeader) Decode(r io.Reader) error {
54+
if err := binary.Read(r, binary.LittleEndian, h); err != nil {
55+
return err
56+
}
57+
return nil
58+
}

internal/handshake/block_test.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2025 Blink Labs Software
2+
//
3+
// Use of this source code is governed by an MIT-style
4+
// license that can be found in the LICENSE file or at
5+
// https://opensource.org/licenses/MIT.
6+
7+
package handshake_test
8+
9+
import (
10+
"bytes"
11+
"encoding/hex"
12+
"reflect"
13+
"testing"
14+
15+
"github.com/blinklabs-io/cdnsd/internal/handshake"
16+
)
17+
18+
func decodeHex(hexData string) []byte {
19+
ret, _ := hex.DecodeString(hexData)
20+
return ret
21+
}
22+
23+
func TestDecodeHandshakeBlock(t *testing.T) {
24+
// Block 0000000000000000aaeb53f05d5d6f9ec895f3ab7858c8a6b5911e41e410ebc7 from Handshake mainnet
25+
testBlockHex := "c29fc32ba934ec67000000000000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d980b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17c907ca66957417a200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000045779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc000000007093091900000000000000000000000000000000000000000000000000000000000000000200000000010000000000000000000000000000000000000000000000000000000000000000ffffffff3b2cf8140134369b3b00000000001498c8297a67eb81ec36253828b5621a601ba2328a0000a62204000306566961425443087c524fd539e1eab808000000000000000000000000021b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d00000000ffffffff74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa05000000ffffffff02000000000000000000145ad99a3052017938562ede6e228b68ca50c14663080320c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e53404bf1e01002000000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d86837107fe90d0000000014fb7148b38057231e023ce04e52a0d1d067a9100c0000000000000241cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab012102042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c02418920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d012103b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1"
26+
expectedBlock := handshake.Block{
27+
Header: handshake.BlockHeader{
28+
Nonce: 734240706,
29+
Time: 1743533225,
30+
PrevBlock: [32]byte(decodeHex("0000000000000008fb98a534f78c6594b9c5581d6e7ca688efebca93e3567d98")),
31+
NameRoot: [32]byte(decodeHex("0b5cc7b8bb7632532df5d5adc0af9f2a830fcb72b2595cd7c4e34e6371465f17")),
32+
ExtraNonce: [24]byte(decodeHex("c907ca66957417a200000000000000000000000000000000")),
33+
WitnessRoot: [32]byte(decodeHex("45779eb2591efda24b4e502cb186d6b7b3d786bb8b247180205b8e8edc70ec6c")),
34+
MerkleRoot: [32]byte(decodeHex("7daf23875654e512d4235898dfda96202d6a11f0314945c9835f60b8d14a64cc")),
35+
Version: 0,
36+
Bits: 420057968,
37+
},
38+
Transactions: []handshake.Transaction{
39+
{
40+
Version: 0,
41+
LockTime: 271014,
42+
Inputs: []handshake.TransactionInput{
43+
{
44+
PrevOutpoint: handshake.Outpoint{
45+
Index: 0xffffffff,
46+
},
47+
Sequence: 351808571,
48+
Witness: [][]byte{
49+
decodeHex("566961425443"),
50+
decodeHex("7c524fd539e1eab8"),
51+
decodeHex("0000000000000000"),
52+
},
53+
},
54+
},
55+
Outputs: []handshake.TransactionOutput{
56+
{
57+
Value: 1000027700,
58+
Address: handshake.Address{
59+
Version: 0,
60+
Hash: decodeHex("98c8297a67eb81ec36253828b5621a601ba2328a"),
61+
},
62+
},
63+
},
64+
},
65+
{
66+
Version: 0,
67+
Inputs: []handshake.TransactionInput{
68+
{
69+
PrevOutpoint: handshake.Outpoint{
70+
Hash: [32]byte(decodeHex("1b6c08ea3b56b781a821c5e5f01e93db09409bacb2c8fdbbc50659ba135ec66d")),
71+
Index: 0,
72+
},
73+
Sequence: 0xffffffff,
74+
Witness: [][]byte{
75+
decodeHex("cd37781b3ec75e9960e191825a5540aba2555a64c8efc58002f3b1163240f01b6696f298b1823c206223427738c81c79e6de38ac47138a005422b9a816354dab01"),
76+
decodeHex("02042f296a2e27a712cf445e05c5085e3e6eb7a0d1cdb2989f9259c1307e3de30c"),
77+
},
78+
},
79+
{
80+
PrevOutpoint: handshake.Outpoint{
81+
Hash: [32]byte(decodeHex("74bcb7fae5c29b149c278e0b78afd22dcdfea1339ce28af0ff68a46a716d03fa")),
82+
Index: 5,
83+
},
84+
Sequence: 0xffffffff,
85+
Witness: [][]byte{
86+
decodeHex("8920c4adbced17aaa59ab3848789870ef0ef00d83eb608f622242d6f4347040f3de5ff8198c3716cdb66915f83936fdcc6a5d31aa00e2b8f2ac2bd290229f58d01"),
87+
decodeHex("03b5c60aea8ec43bb6a8574caf5817be3ac376ca46ca0db22d330cbd5909a1d8f1"),
88+
},
89+
},
90+
},
91+
Outputs: []handshake.TransactionOutput{
92+
{
93+
Value: 0,
94+
Address: handshake.Address{
95+
Version: 0,
96+
Hash: decodeHex("5ad99a3052017938562ede6e228b68ca50c14663"),
97+
},
98+
Covenant: handshake.Covenant{
99+
Type: 8,
100+
Items: [][]byte{
101+
decodeHex("c89c49ce327748244702f481f35097199cca2f7c2549a33ecacbdf973690e534"),
102+
decodeHex("bf1e0100"),
103+
decodeHex("00000000000000020db257ed6d1c3b47b6e2299fbfcbef58996dcd6a30e9d868"),
104+
},
105+
},
106+
},
107+
{
108+
Value: 59751993399,
109+
Address: handshake.Address{
110+
Version: 0,
111+
Hash: decodeHex("fb7148b38057231e023ce04e52a0d1d067a9100c"),
112+
},
113+
},
114+
},
115+
},
116+
},
117+
}
118+
testBlockBytes, err := hex.DecodeString(testBlockHex)
119+
if err != nil {
120+
t.Fatalf("unexpected error: %s", err)
121+
}
122+
br := bytes.NewReader(testBlockBytes)
123+
var block handshake.Block
124+
err = block.Decode(br)
125+
if err != nil {
126+
t.Fatalf("unexpected error deserializing block: %s", err)
127+
}
128+
if !reflect.DeepEqual(block.Header, expectedBlock.Header) {
129+
t.Fatalf(
130+
"did not get expected block header:\n got: %#v\n wanted: %#v",
131+
block.Header,
132+
expectedBlock.Header,
133+
)
134+
}
135+
if len(block.Transactions) != len(expectedBlock.Transactions) {
136+
t.Fatalf("did not get expected TX count: got %d, wanted %d", len(block.Transactions), len(expectedBlock.Transactions))
137+
}
138+
for idx, testTx := range block.Transactions {
139+
expectedTx := expectedBlock.Transactions[idx]
140+
// Compare inputs
141+
if !reflect.DeepEqual(testTx.Inputs, expectedTx.Inputs) {
142+
t.Fatalf(
143+
"did not get expected TX inputs:\n got: %#v\n wanted: %#v",
144+
testTx.Inputs,
145+
expectedTx.Inputs,
146+
)
147+
}
148+
// Compare outputs
149+
if !reflect.DeepEqual(testTx.Outputs, expectedTx.Outputs) {
150+
t.Fatalf(
151+
"did not get expected TX outputs:\n got: %#v\n wanted: %#v",
152+
testTx.Outputs,
153+
expectedTx.Outputs,
154+
)
155+
}
156+
// Compare lock time
157+
if testTx.LockTime != expectedTx.LockTime {
158+
t.Fatalf(
159+
"did not get expected TX lock time: got %d, wanted %d",
160+
testTx.LockTime,
161+
expectedTx.LockTime,
162+
)
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)