payload_test.go raw

   1  package btcwire
   2  
   3  import (
   4  	"bytes"
   5  	"testing"
   6  
   7  	"git.smesh.lol/nostrino/pkg/crypto/chainhash"
   8  )
   9  
  10  // Compile-time assertions that every payload type implements Payload.
  11  var (
  12  	_ Payload = (*Version)(nil)
  13  	_ Payload = (*VerAck)(nil)
  14  	_ Payload = (*SendHeaders)(nil)
  15  	_ Payload = (*WtxidRelay)(nil)
  16  	_ Payload = (*SendAddrV2)(nil)
  17  	_ Payload = (*Ping)(nil)
  18  	_ Payload = (*Pong)(nil)
  19  	_ Payload = (*FeeFilter)(nil)
  20  	_ Payload = (*GetHeaders)(nil)
  21  	_ Payload = (*Headers)(nil)
  22  	_ Payload = (*GetCFHeaders)(nil)
  23  	_ Payload = (*CFHeaders)(nil)
  24  	_ Payload = (*GetCFilters)(nil)
  25  	_ Payload = (*CFilter)(nil)
  26  	_ Payload = (*GetCFCheckpt)(nil)
  27  	_ Payload = (*CFCheckpt)(nil)
  28  	_ Payload = (*Inv)(nil)
  29  	_ Payload = (*GetData)(nil)
  30  	_ Payload = (*NotFound)(nil)
  31  	_ Payload = (*Tx)(nil)
  32  )
  33  
  34  // testHash returns a deterministic, non-zero hash derived from b.
  35  func testHash(b byte) chainhash.Hash {
  36  	var h chainhash.Hash
  37  	for i := range h {
  38  		h[i] = b + byte(i)
  39  	}
  40  	return h
  41  }
  42  
  43  // allCommands lists every command constant this package defines.
  44  var allCommands = []string{
  45  	CmdVersion, CmdVerAck, CmdPing, CmdPong,
  46  	CmdGetHeaders, CmdHeaders,
  47  	CmdGetCFilters, CmdCFilter,
  48  	CmdGetCFHeaders, CmdCFHeaders,
  49  	CmdGetCFCheckpt, CmdCFCheckpt,
  50  	CmdGetData, CmdTx, CmdInv, CmdNotFound,
  51  	CmdSendHeaders, CmdWtxidRelay, CmdSendAddrV2,
  52  	CmdFeeFilter,
  53  }
  54  
  55  // TestPayloadFrames round trips every payload type through a complete frame,
  56  // checking the command and that re-encoding the decoded payload reproduces
  57  // the original bytes.
  58  func TestPayloadFrames(t *testing.T) {
  59  	tests := []struct {
  60  		command string
  61  		value   func() Payload
  62  		zero    func() Payload
  63  	}{
  64  		{CmdVersion, func() Payload {
  65  			v := &Version{
  66  				ProtocolVersion: ProtocolVersion,
  67  				Services:        ServiceNodeNetwork | ServiceNodeCompactFilters,
  68  				Timestamp:       1700000000,
  69  				Nonce:           0x0102030405060708,
  70  				UserAgent:       "nostrino:0.1.0",
  71  				LastBlock:       800000,
  72  				Relay:           true,
  73  			}
  74  			v.AddrRecv = mustAddress(t, "127.0.0.1", 8333)
  75  			v.AddrFrom = mustAddress(t, "2001:db8::1", 18333)
  76  			return v
  77  		}, func() Payload { return &Version{} }},
  78  		{CmdVerAck, func() Payload { return &VerAck{} }, func() Payload { return &VerAck{} }},
  79  		{CmdSendHeaders, func() Payload { return &SendHeaders{} }, func() Payload { return &SendHeaders{} }},
  80  		{CmdWtxidRelay, func() Payload { return &WtxidRelay{} }, func() Payload { return &WtxidRelay{} }},
  81  		{CmdSendAddrV2, func() Payload { return &SendAddrV2{} }, func() Payload { return &SendAddrV2{} }},
  82  		{CmdPing, func() Payload { return &Ping{Nonce: 42} }, func() Payload { return &Ping{} }},
  83  		{CmdPong, func() Payload { return &Pong{Nonce: 43} }, func() Payload { return &Pong{} }},
  84  		{CmdFeeFilter, func() Payload { return &FeeFilter{FeeRate: 1000} }, func() Payload { return &FeeFilter{} }},
  85  		{CmdGetHeaders, func() Payload {
  86  			return &GetHeaders{
  87  				ProtocolVersion: ProtocolVersion,
  88  				LocatorHashes:   []chainhash.Hash{testHash(1), testHash(2)},
  89  				StopHash:        testHash(3),
  90  			}
  91  		}, func() Payload { return &GetHeaders{} }},
  92  		{CmdHeaders, func() Payload {
  93  			return &Headers{Headers: []BlockHeader{
  94  				{Version: 1, PrevBlock: testHash(4), MerkleRoot: testHash(5), Timestamp: 100, Bits: 0x1d00ffff, Nonce: 7},
  95  				{Version: 2, PrevBlock: testHash(6), MerkleRoot: testHash(7), Timestamp: 200, Bits: 0x1b0404cb, Nonce: 8},
  96  			}}
  97  		}, func() Payload { return &Headers{} }},
  98  		{CmdGetCFHeaders, func() Payload {
  99  			return &GetCFHeaders{FilterType: FilterTypeBasic, StartHeight: 700000, StopHash: testHash(8)}
 100  		}, func() Payload { return &GetCFHeaders{} }},
 101  		{CmdCFHeaders, func() Payload {
 102  			return &CFHeaders{
 103  				FilterType:       FilterTypeBasic,
 104  				StopHash:         testHash(9),
 105  				PrevFilterHeader: testHash(10),
 106  				FilterHashes:     []chainhash.Hash{testHash(11), testHash(12)},
 107  			}
 108  		}, func() Payload { return &CFHeaders{} }},
 109  		{CmdGetCFilters, func() Payload {
 110  			return &GetCFilters{FilterType: FilterTypeBasic, StartHeight: 1, StopHash: testHash(13)}
 111  		}, func() Payload { return &GetCFilters{} }},
 112  		{CmdCFilter, func() Payload {
 113  			return &CFilter{FilterType: FilterTypeBasic, BlockHash: testHash(14), Filter: []byte{0x00, 0x01, 0x02}}
 114  		}, func() Payload { return &CFilter{} }},
 115  		{CmdGetCFCheckpt, func() Payload {
 116  			return &GetCFCheckpt{FilterType: FilterTypeBasic, StopHash: testHash(15)}
 117  		}, func() Payload { return &GetCFCheckpt{} }},
 118  		{CmdCFCheckpt, func() Payload {
 119  			return &CFCheckpt{
 120  				FilterType:    FilterTypeBasic,
 121  				StopHash:      testHash(16),
 122  				FilterHeaders: []chainhash.Hash{testHash(17), testHash(18)},
 123  			}
 124  		}, func() Payload { return &CFCheckpt{} }},
 125  		{CmdInv, func() Payload {
 126  			return &Inv{InvList: []InvVect{
 127  				{Type: InvVectTypeTx, Hash: testHash(19)},
 128  				{Type: InvVectTypeWitnessTx, Hash: testHash(20)},
 129  			}}
 130  		}, func() Payload { return &Inv{} }},
 131  		{CmdGetData, func() Payload {
 132  			return &GetData{InvList: []InvVect{{Type: InvVectTypeFilteredBlock, Hash: testHash(21)}}}
 133  		}, func() Payload { return &GetData{} }},
 134  		{CmdNotFound, func() Payload {
 135  			return &NotFound{InvList: []InvVect{{Type: InvVectTypeBlock, Hash: testHash(22)}}}
 136  		}, func() Payload { return &NotFound{} }},
 137  		{CmdTx, func() Payload {
 138  			return &Tx{Raw: []byte{0x01, 0x00, 0x00, 0x00, 0x00}}
 139  		}, func() Payload { return &Tx{} }},
 140  	}
 141  	for _, tc := range tests {
 142  		t.Run(tc.command, func(t *testing.T) {
 143  			want, err := tc.value().EncodePayload()
 144  			if err != nil {
 145  				t.Fatalf("EncodePayload: %v", err)
 146  			}
 147  			frame, err := EncodeMessagePayload(MainNet, tc.command, tc.value())
 148  			if err != nil {
 149  				t.Fatalf("EncodeMessagePayload: %v", err)
 150  			}
 151  			msg, err := DecodeMessage(MainNet, bytes.NewReader(frame))
 152  			if err != nil {
 153  				t.Fatalf("DecodeMessage: %v", err)
 154  			}
 155  			if msg.Command != tc.command {
 156  				t.Fatalf("command = %q, want %q", msg.Command, tc.command)
 157  			}
 158  			if !bytes.Equal(msg.Payload, want) {
 159  				t.Fatalf("frame payload = %x, want %x", msg.Payload, want)
 160  			}
 161  			decoded := tc.zero()
 162  			if err := msg.Decode(decoded); err != nil {
 163  				t.Fatalf("Message.Decode: %v", err)
 164  			}
 165  			got, err := decoded.EncodePayload()
 166  			if err != nil {
 167  				t.Fatalf("re-EncodePayload: %v", err)
 168  			}
 169  			if !bytes.Equal(got, want) {
 170  				t.Fatalf("re-encoded payload = %x, want %x", got, want)
 171  			}
 172  		})
 173  	}
 174  }
 175  
 176  // mustAddress parses host into a NetAddress or fails the test.
 177  func mustAddress(t *testing.T, host string, port uint16) NetAddress {
 178  	t.Helper()
 179  	addr, err := NewNetAddress(host, port)
 180  	if err != nil {
 181  		t.Fatalf("NewNetAddress(%q, %d): %v", host, port, err)
 182  	}
 183  	return *addr
 184  }
 185