node.go raw
1 // Package testnode is a deterministic Bitcoin P2P peer that serves a short
2 // regtest chain with BIP157 compact filters. It exists so the tunnel, bridge
3 // and light-client code can be exercised end to end without a full node.
4 package testnode
5
6 import (
7 "encoding/binary"
8 "errors"
9 "fmt"
10 "io"
11 "net"
12 "sync"
13
14 "git.smesh.lol/nostrino/pkg/btcwire"
15 "git.smesh.lol/nostrino/pkg/chain"
16 "git.smesh.lol/nostrino/pkg/crypto/chainhash"
17 "git.smesh.lol/nostrino/pkg/filter"
18 )
19
20 // Config builds a fake chain.
21 type Config struct {
22 // Blocks is the number of blocks after genesis. Minimum 1.
23 Blocks int
24 // Bits is the compact target. Zero means the regtest target 0x207fffff.
25 Bits uint32
26 }
27
28 // Node is a fake peer with a generated chain.
29 type Node struct {
30 profile *chain.Profile
31 headers []btcwire.BlockHeader
32 filters [][]byte
33 filterHashes []chainhash.Hash
34 filterHeaders []chainhash.Hash
35 scripts [][]byte
36
37 mu sync.Mutex
38 ln net.Listener
39 closed bool
40 conns map[net.Conn]struct{}
41 wg sync.WaitGroup
42 }
43
44 // New generates a regtest chain of cfg.Blocks headers. The genesis header is
45 // the profile's.
46 func New(profile *chain.Profile, cfg Config) (*Node, error) {
47 if profile == nil {
48 return nil, errors.New("testnode: nil profile")
49 }
50 if profile.Name != "regtest" {
51 return nil, fmt.Errorf("testnode: profile %q is not regtest", profile.Name)
52 }
53 if cfg.Blocks < 1 {
54 cfg.Blocks = 1
55 }
56 if cfg.Bits == 0 {
57 cfg.Bits = 0x207fffff
58 }
59 n := &Node{
60 profile: profile,
61 conns: make(map[net.Conn]struct{}),
62 }
63 n.headers = append(n.headers, profile.GenesisHeader)
64 n.filters = append(n.filters, filter.BuildFromScripts([][]byte{[]byte("block-0")}, [32]byte(profile.GenesisHash)))
65 n.scripts = append(n.scripts, []byte("block-0"))
66 for i := 1; i <= cfg.Blocks; i++ {
67 prev := n.headers[i-1]
68 prevHash := prev.BlockHash()
69 merkle := chainhash.DoubleHashH([]byte(fmt.Sprintf("merkle-%d", i)))
70 h := btcwire.BlockHeader{
71 Version: 1,
72 PrevBlock: prevHash,
73 MerkleRoot: merkle,
74 Timestamp: uint32(1296688602 + i*600),
75 Bits: cfg.Bits,
76 }
77 for nonce := uint32(0); nonce < 1_000_000; nonce++ {
78 h.Nonce = nonce
79 if h.CheckProofOfWork() {
80 break
81 }
82 }
83 if !h.CheckProofOfWork() {
84 return nil, fmt.Errorf("testnode: could not mine block %d", i)
85 }
86 n.headers = append(n.headers, h)
87 script := []byte(fmt.Sprintf("block-%d", i))
88 n.scripts = append(n.scripts, script)
89 f := filter.BuildFromScripts([][]byte{script}, [32]byte(h.BlockHash()))
90 n.filters = append(n.filters, f)
91 }
92 for i, f := range n.filters {
93 fh := chainhash.DoubleHashH(f)
94 n.filterHashes = append(n.filterHashes, fh)
95 var prev chainhash.Hash
96 if i > 0 {
97 prev = n.filterHeaders[i-1]
98 }
99 n.filterHeaders = append(n.filterHeaders, btcwire.FilterHeader(fh, prev))
100 }
101 return n, nil
102 }
103
104 // Height is the chain tip height.
105 func (n *Node) Height() uint32 { return uint32(len(n.headers) - 1) }
106
107 // Header returns the header at height.
108 func (n *Node) Header(height uint32) (btcwire.BlockHeader, bool) {
109 if int(height) >= len(n.headers) {
110 return btcwire.BlockHeader{}, false
111 }
112 return n.headers[height], true
113 }
114
115 // Filter returns the compact filter at height.
116 func (n *Node) Filter(height uint32) ([]byte, bool) {
117 if int(height) >= len(n.filters) {
118 return nil, false
119 }
120 return n.filters[height], true
121 }
122
123 // Script returns the script embedded in the filter at height.
124 func (n *Node) Script(height uint32) []byte {
125 if int(height) >= len(n.scripts) {
126 return nil
127 }
128 return n.scripts[height]
129 }
130
131 // Listen starts the node on addr (127.0.0.1:0 for any port).
132 func (n *Node) Listen(addr string) (string, error) {
133 ln, err := net.Listen("tcp", addr)
134 if err != nil {
135 return "", err
136 }
137 n.mu.Lock()
138 n.ln = ln
139 n.mu.Unlock()
140 n.wg.Add(1)
141 go n.acceptLoop(ln)
142 return ln.Addr().String(), nil
143 }
144
145 func (n *Node) acceptLoop(ln net.Listener) {
146 defer n.wg.Done()
147 for {
148 conn, err := ln.Accept()
149 if err != nil {
150 return
151 }
152 n.mu.Lock()
153 if n.closed {
154 n.mu.Unlock()
155 conn.Close()
156 return
157 }
158 n.conns[conn] = struct{}{}
159 n.mu.Unlock()
160 n.wg.Add(1)
161 go func() {
162 defer n.wg.Done()
163 defer func() {
164 n.mu.Lock()
165 delete(n.conns, conn)
166 n.mu.Unlock()
167 conn.Close()
168 }()
169 n.serve(conn)
170 }()
171 }
172 }
173
174 // Close stops the listener and all connections.
175 func (n *Node) Close() error {
176 n.mu.Lock()
177 if n.closed {
178 n.mu.Unlock()
179 return nil
180 }
181 n.closed = true
182 ln := n.ln
183 conns := make([]net.Conn, 0, len(n.conns))
184 for c := range n.conns {
185 conns = append(conns, c)
186 }
187 n.mu.Unlock()
188 if ln != nil {
189 ln.Close()
190 }
191 for _, c := range conns {
192 c.Close()
193 }
194 n.wg.Wait()
195 return nil
196 }
197
198 func (n *Node) serve(conn net.Conn) {
199 reader := btcwire.NewReader(conn, n.profile.Magic)
200 write := func(cmd string, payload btcwire.Payload) error {
201 var raw []byte
202 if payload != nil {
203 b, err := payload.EncodePayload()
204 if err != nil {
205 return err
206 }
207 raw = b
208 }
209 frame, err := btcwire.EncodeMessage(n.profile.Magic, cmd, raw)
210 if err != nil {
211 return err
212 }
213 _, err = conn.Write(frame)
214 return err
215 }
216 for {
217 msg, err := reader.Next()
218 if err != nil {
219 if errors.Is(err, io.EOF) {
220 return
221 }
222 return
223 }
224 switch msg.Command {
225 case btcwire.CmdVersion:
226 v := &btcwire.Version{
227 ProtocolVersion: btcwire.ProtocolVersion,
228 Services: btcwire.ServiceNodeNetwork | btcwire.ServiceNodeWitness | btcwire.ServiceNodeCompactFilters,
229 Timestamp: 1700000000,
230 Nonce: 42,
231 UserAgent: "/nostrino-testnode:0.1.0/",
232 LastBlock: int32(n.Height()),
233 Relay: false,
234 }
235 if err := write(btcwire.CmdVersion, v); err != nil {
236 return
237 }
238 if err := write(btcwire.CmdVerAck, nil); err != nil {
239 return
240 }
241 case btcwire.CmdVerAck:
242 // handshake complete
243 case btcwire.CmdPing:
244 var p btcwire.Ping
245 if p.DecodePayload(msg.Payload) == nil {
246 if write(btcwire.CmdPong, &btcwire.Pong{Nonce: p.Nonce}) != nil {
247 return
248 }
249 }
250 case btcwire.CmdGetHeaders:
251 var req btcwire.GetHeaders
252 if err := req.DecodePayload(msg.Payload); err != nil {
253 return
254 }
255 if err := n.sendHeaders(write, &req); err != nil {
256 return
257 }
258 case btcwire.CmdGetCFHeaders:
259 var req btcwire.GetCFHeaders
260 if err := req.DecodePayload(msg.Payload); err != nil {
261 return
262 }
263 if err := n.sendCFHeaders(write, &req); err != nil {
264 return
265 }
266 case btcwire.CmdGetCFilters:
267 var req btcwire.GetCFilters
268 if err := req.DecodePayload(msg.Payload); err != nil {
269 return
270 }
271 if err := n.sendCFilters(write, &req); err != nil {
272 return
273 }
274 case btcwire.CmdGetCFCheckpt:
275 var req btcwire.GetCFCheckpt
276 if err := req.DecodePayload(msg.Payload); err != nil {
277 return
278 }
279 if err := n.sendCFCheckpt(write, &req); err != nil {
280 return
281 }
282 case btcwire.CmdSendHeaders, btcwire.CmdWtxidRelay, btcwire.CmdSendAddrV2, btcwire.CmdFeeFilter:
283 // accepted, no response
284 default:
285 // ignore unknown commands
286 }
287 }
288 }
289
290 func (n *Node) heightOf(h chainhash.Hash) (uint32, bool) {
291 for i := range n.headers {
292 if n.headers[i].BlockHash() == h {
293 return uint32(i), true
294 }
295 }
296 return 0, false
297 }
298
299 func (n *Node) startAfterLocator(locator []chainhash.Hash) uint32 {
300 for _, h := range locator {
301 if height, ok := n.heightOf(h); ok {
302 return height + 1
303 }
304 }
305 return 0
306 }
307
308 func (n *Node) sendHeaders(write func(string, btcwire.Payload) error, req *btcwire.GetHeaders) error {
309 start := n.startAfterLocator(req.LocatorHashes)
310 end := uint32(len(n.headers))
311 if req.StopHash != (chainhash.Hash{}) {
312 if h, ok := n.heightOf(req.StopHash); ok {
313 end = h + 1
314 }
315 }
316 if start >= uint32(len(n.headers)) || start >= end {
317 return write(btcwire.CmdHeaders, &btcwire.Headers{})
318 }
319 if end > start+btcwire.MaxBlockHeadersPerMsg {
320 end = start + btcwire.MaxBlockHeadersPerMsg
321 }
322 return write(btcwire.CmdHeaders, &btcwire.Headers{Headers: n.headers[start:end]})
323 }
324
325 func (n *Node) sendCFHeaders(write func(string, btcwire.Payload) error, req *btcwire.GetCFHeaders) error {
326 if req.FilterType != btcwire.FilterTypeBasic {
327 return write(btcwire.CmdCFHeaders, &btcwire.CFHeaders{FilterType: req.FilterType, StopHash: req.StopHash})
328 }
329 start := req.StartHeight
330 if start > n.Height() {
331 return write(btcwire.CmdCFHeaders, &btcwire.CFHeaders{FilterType: req.FilterType, StopHash: req.StopHash})
332 }
333 end := uint32(len(n.headers) - 1)
334 if req.StopHash != (chainhash.Hash{}) {
335 if h, ok := n.heightOf(req.StopHash); ok {
336 end = h
337 }
338 }
339 if end > start+btcwire.MaxFilterHashesPerMsg-1 {
340 end = start + btcwire.MaxFilterHashesPerMsg - 1
341 }
342 var prev chainhash.Hash
343 if start > 0 {
344 prev = n.filterHeaders[start-1]
345 }
346 hashes := append([]chainhash.Hash(nil), n.filterHashes[start:end+1]...)
347 stop, ok := n.Header(end)
348 if !ok {
349 return errors.New("testnode: no stop header")
350 }
351 return write(btcwire.CmdCFHeaders, &btcwire.CFHeaders{
352 FilterType: btcwire.FilterTypeBasic,
353 StopHash: stop.BlockHash(),
354 PrevFilterHeader: prev,
355 FilterHashes: hashes,
356 })
357 }
358
359 func (n *Node) sendCFilters(write func(string, btcwire.Payload) error, req *btcwire.GetCFilters) error {
360 if req.FilterType != btcwire.FilterTypeBasic {
361 return errors.New("testnode: unsupported filter type")
362 }
363 start := req.StartHeight
364 end := uint32(len(n.headers) - 1)
365 if req.StopHash != (chainhash.Hash{}) {
366 h, ok := n.heightOf(req.StopHash)
367 if !ok {
368 return errors.New("testnode: unknown stop hash")
369 }
370 end = h
371 }
372 if start > end {
373 return nil
374 }
375 for h := start; h <= end; h++ {
376 hdr := n.headers[h]
377 if err := write(btcwire.CmdCFilter, &btcwire.CFilter{
378 FilterType: btcwire.FilterTypeBasic,
379 BlockHash: hdr.BlockHash(),
380 Filter: n.filters[h],
381 }); err != nil {
382 return err
383 }
384 }
385 return nil
386 }
387
388 func (n *Node) sendCFCheckpt(write func(string, btcwire.Payload) error, req *btcwire.GetCFCheckpt) error {
389 end := uint32(len(n.headers) - 1)
390 if req.StopHash != (chainhash.Hash{}) {
391 h, ok := n.heightOf(req.StopHash)
392 if !ok {
393 h = end
394 }
395 end = h
396 }
397 // One checkpoint per 1000 filters, as BIP157 recommends for serving.
398 step := uint32(1000)
399 var pts []chainhash.Hash
400 for h := step - 1; h <= end; h += step {
401 pts = append(pts, n.filterHeaders[h])
402 }
403 stop, _ := n.Header(end)
404 return write(btcwire.CmdCFCheckpt, &btcwire.CFCheckpt{
405 FilterType: btcwire.FilterTypeBasic,
406 StopHash: stop.BlockHash(),
407 FilterHeaders: pts,
408 })
409 }
410
411 // MerkleOf is a helper for callers that build their own headers.
412 func MerkleOf(height uint32) chainhash.Hash {
413 var b [4]byte
414 binary.LittleEndian.PutUint32(b[:], height)
415 return chainhash.DoubleHashH(b[:])
416 }
417
418 // FilterHeader returns the rolling filter header at height.
419 func (n *Node) FilterHeader(height uint32) (chainhash.Hash, bool) {
420 if int(height) >= len(n.filterHeaders) {
421 return chainhash.Hash{}, false
422 }
423 return n.filterHeaders[height], true
424 }
425
426 // FilterHash returns the hash of the compact filter at height.
427 func (n *Node) FilterHash(height uint32) (chainhash.Hash, bool) {
428 if int(height) >= len(n.filterHashes) {
429 return chainhash.Hash{}, false
430 }
431 return n.filterHashes[height], true
432 }
433
434 // GenesisHash returns the profile's genesis hash.
435 func (n *Node) GenesisHash() chainhash.Hash { return n.profile.GenesisHash }
436
437 // ForkAt replaces every block above height with blocks new ones built on the
438 // common ancestor, producing a chain the same length but a different branch.
439 // It is used to test reorg handling.
440 func (n *Node) ForkAt(height uint32, blocks int, tag string) error {
441 if int(height) >= len(n.headers) {
442 return fmt.Errorf("testnode: fork height %d above tip %d", height, len(n.headers)-1)
443 }
444 n.headers = n.headers[:height+1]
445 n.filters = n.filters[:height+1]
446 n.filterHashes = n.filterHashes[:height+1]
447 n.filterHeaders = n.filterHeaders[:height+1]
448 n.scripts = n.scripts[:height+1]
449 for i := 1; i <= blocks; i++ {
450 h := height + uint32(i)
451 prev := n.headers[h-1]
452 prevHash := prev.BlockHash()
453 merkle := chainhash.DoubleHashH([]byte(fmt.Sprintf("merkle-%d-%s", h, tag)))
454 hdr := btcwire.BlockHeader{
455 Version: 1,
456 PrevBlock: prevHash,
457 MerkleRoot: merkle,
458 Timestamp: prev.Timestamp + 600,
459 Bits: prev.Bits,
460 }
461 for nonce := uint32(0); nonce < 1_000_000; nonce++ {
462 hdr.Nonce = nonce
463 if hdr.CheckProofOfWork() {
464 break
465 }
466 }
467 if !hdr.CheckProofOfWork() {
468 return fmt.Errorf("testnode: could not mine fork block %d", h)
469 }
470 n.headers = append(n.headers, hdr)
471 script := []byte(fmt.Sprintf("block-%d-%s", h, tag))
472 n.scripts = append(n.scripts, script)
473 f := filter.BuildFromScripts([][]byte{script}, [32]byte(hdr.BlockHash()))
474 n.filters = append(n.filters, f)
475 fh := chainhash.DoubleHashH(f)
476 n.filterHashes = append(n.filterHashes, fh)
477 n.filterHeaders = append(n.filterHeaders, btcwire.FilterHeader(fh, n.filterHeaders[h-1]))
478 }
479 return nil
480 }
481