package btcwire import ( "fmt" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) // Inventory vector types. const ( // InvVectTypeError is the reserved error type. InvVectTypeError uint32 = 0 // InvVectTypeTx identifies a transaction by txid. InvVectTypeTx uint32 = 1 // InvVectTypeBlock identifies a block by block hash. InvVectTypeBlock uint32 = 2 // InvVectTypeFilteredBlock identifies a filtered block. InvVectTypeFilteredBlock uint32 = 3 // InvVectTypeWitnessTx identifies a transaction by wtxid. InvVectTypeWitnessTx uint32 = 0x40000001 ) // MaxInvPerMsg is the largest number of inventory vectors a single inv, // getdata, or notfound message may carry. const MaxInvPerMsg = 50000 // InvVect is one inventory vector: a type and the hash of the object it // identifies. type InvVect struct { // Type is one of the InvVectType constants. Type uint32 // Hash identifies the object. Its interpretation depends on Type. Hash chainhash.Hash } // Inv announces objects a peer has available. type Inv struct { // InvList is the announced inventory. InvList []InvVect } // EncodePayload returns the wire encoding of the inv message. func (v *Inv) EncodePayload() ([]byte, error) { return encodeInvList("inv", v.InvList) } // DecodePayload replaces v with the inv message decoded from b. func (v *Inv) DecodePayload(b []byte) error { list, err := decodeInvList("inv", b) if err != nil { return err } v.InvList = list return nil } // GetData requests objects announced by a peer. type GetData struct { // InvList is the requested inventory. InvList []InvVect } // EncodePayload returns the wire encoding of the getdata message. func (g *GetData) EncodePayload() ([]byte, error) { return encodeInvList("getdata", g.InvList) } // DecodePayload replaces g with the getdata message decoded from b. func (g *GetData) DecodePayload(b []byte) error { list, err := decodeInvList("getdata", b) if err != nil { return err } g.InvList = list return nil } // NotFound reports inventory that a peer could not provide. type NotFound struct { // InvList is the unavailable inventory. InvList []InvVect } // EncodePayload returns the wire encoding of the notfound message. func (n *NotFound) EncodePayload() ([]byte, error) { return encodeInvList("notfound", n.InvList) } // DecodePayload replaces n with the notfound message decoded from b. func (n *NotFound) DecodePayload(b []byte) error { list, err := decodeInvList("notfound", b) if err != nil { return err } n.InvList = list return nil } // invVectSize is the encoded size of one inventory vector. const invVectSize = 4 + chainhash.HashSize // encodeInvList appends a compact size count followed by the inventory // vectors. func encodeInvList(name string, list []InvVect) ([]byte, error) { if len(list) > MaxInvPerMsg { return nil, fmt.Errorf( "btcwire: %s carries %d inventory vectors, maximum is %d: %w", name, len(list), MaxInvPerMsg, ErrTooManyItems, ) } var e Encoder e.CompactSize(uint64(len(list))) for i := range list { e.Uint32(list[i].Type) e.Hash(list[i].Hash) } return e.Bytes(), nil } // decodeInvList decodes a compact size count followed by that many inventory // vectors. func decodeInvList(name string, b []byte) ([]InvVect, error) { d := NewDecoder(b) count, err := d.count(MaxInvPerMsg, invVectSize, name+" inventory vector") if err != nil { return nil, err } list := make([]InvVect, count) for i := range list { if list[i].Type, err = d.Uint32(); err != nil { return nil, fmt.Errorf("btcwire: decoding %s inventory type %d: %w", name, i, err) } if list[i].Hash, err = d.Hash(); err != nil { return nil, fmt.Errorf("btcwire: decoding %s inventory hash %d: %w", name, i, err) } } return list, nil }