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