headers.go raw
1 package btcwire
2
3 import (
4 "fmt"
5
6 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
7 )
8
9 // Message vector limits.
10 const (
11 // MaxBlockHeadersPerMsg is the largest number of block headers a headers
12 // message may carry.
13 MaxBlockHeadersPerMsg = 2000
14
15 // MaxLocatorHashesPerMsg is the largest number of locator hashes a
16 // getheaders message may carry.
17 MaxLocatorHashesPerMsg = 101
18 )
19
20 // GetHeaders requests block headers following the last hash in the locator.
21 type GetHeaders struct {
22 // ProtocolVersion is the sender's protocol version.
23 ProtocolVersion uint32
24
25 // LocatorHashes is the block locator, ordered from the tip back to the
26 // genesis block.
27 LocatorHashes []chainhash.Hash
28
29 // StopHash is the hash of the last header to return, or the zero hash to
30 // request as many headers as fit in one message.
31 StopHash chainhash.Hash
32 }
33
34 // EncodePayload returns the wire encoding of the getheaders message.
35 func (g *GetHeaders) EncodePayload() ([]byte, error) {
36 if len(g.LocatorHashes) > MaxLocatorHashesPerMsg {
37 return nil, fmt.Errorf(
38 "btcwire: getheaders carries %d locator hashes, maximum is %d: %w",
39 len(g.LocatorHashes), MaxLocatorHashesPerMsg, ErrTooManyItems,
40 )
41 }
42 var e Encoder
43 e.Uint32(g.ProtocolVersion)
44 e.CompactSize(uint64(len(g.LocatorHashes)))
45 for i := range g.LocatorHashes {
46 e.Hash(g.LocatorHashes[i])
47 }
48 e.Hash(g.StopHash)
49 return e.Bytes(), nil
50 }
51
52 // DecodePayload replaces g with the getheaders message decoded from b.
53 func (g *GetHeaders) DecodePayload(b []byte) error {
54 d := NewDecoder(b)
55 version, err := d.Uint32()
56 if err != nil {
57 return fmt.Errorf("btcwire: decoding getheaders protocol version: %w", err)
58 }
59 count, err := d.count(MaxLocatorHashesPerMsg, chainhash.HashSize, "locator hash")
60 if err != nil {
61 return err
62 }
63 hashes := make([]chainhash.Hash, count)
64 for i := range hashes {
65 if hashes[i], err = d.Hash(); err != nil {
66 return fmt.Errorf("btcwire: decoding getheaders locator hash %d: %w", i, err)
67 }
68 }
69 stop, err := d.Hash()
70 if err != nil {
71 return fmt.Errorf("btcwire: decoding getheaders stop hash: %w", err)
72 }
73 *g = GetHeaders{
74 ProtocolVersion: version,
75 LocatorHashes: hashes,
76 StopHash: stop,
77 }
78 return nil
79 }
80
81 // Headers carries a batch of block headers in response to a getheaders
82 // message.
83 type Headers struct {
84 // Headers is the batch, ordered from the requested height upwards.
85 Headers []BlockHeader
86 }
87
88 // EncodePayload returns the wire encoding of the headers message. Each header
89 // is followed by a zero transaction count, as required of header-only
90 // responses.
91 func (h *Headers) EncodePayload() ([]byte, error) {
92 if len(h.Headers) > MaxBlockHeadersPerMsg {
93 return nil, fmt.Errorf(
94 "btcwire: headers carries %d headers, maximum is %d: %w",
95 len(h.Headers), MaxBlockHeadersPerMsg, ErrTooManyItems,
96 )
97 }
98 var e Encoder
99 e.CompactSize(uint64(len(h.Headers)))
100 for i := range h.Headers {
101 raw := h.Headers[i].Serialize()
102 e.Write(raw[:])
103 e.CompactSize(0)
104 }
105 return e.Bytes(), nil
106 }
107
108 // DecodePayload replaces h with the headers message decoded from b. A
109 // non-zero transaction count after a header is rejected: those entries are not
110 // valid in a headers message.
111 func (h *Headers) DecodePayload(b []byte) error {
112 d := NewDecoder(b)
113 count, err := d.count(
114 MaxBlockHeadersPerMsg, BlockHeaderSize+1, "block header",
115 )
116 if err != nil {
117 return err
118 }
119 headers := make([]BlockHeader, count)
120 for i := range headers {
121 raw, err := d.Bytes(BlockHeaderSize)
122 if err != nil {
123 return fmt.Errorf("btcwire: decoding block header %d: %w", i, err)
124 }
125 if err := headers[i].Deserialize(raw); err != nil {
126 return fmt.Errorf("btcwire: decoding block header %d: %w", i, err)
127 }
128 txCount, err := d.CompactSize()
129 if err != nil {
130 return fmt.Errorf(
131 "btcwire: decoding transaction count of block header %d: %w", i, err,
132 )
133 }
134 if txCount != 0 {
135 return fmt.Errorf(
136 "%w: headers entry %d has %d transactions",
137 ErrInvalidPayload, i, txCount,
138 )
139 }
140 }
141 h.Headers = headers
142 return nil
143 }
144