package btcwire import ( "fmt" "git.smesh.lol/nostrino/pkg/crypto/chainhash" ) // Message vector limits. const ( // MaxBlockHeadersPerMsg is the largest number of block headers a headers // message may carry. MaxBlockHeadersPerMsg = 2000 // MaxLocatorHashesPerMsg is the largest number of locator hashes a // getheaders message may carry. MaxLocatorHashesPerMsg = 101 ) // GetHeaders requests block headers following the last hash in the locator. type GetHeaders struct { // ProtocolVersion is the sender's protocol version. ProtocolVersion uint32 // LocatorHashes is the block locator, ordered from the tip back to the // genesis block. LocatorHashes []chainhash.Hash // StopHash is the hash of the last header to return, or the zero hash to // request as many headers as fit in one message. StopHash chainhash.Hash } // EncodePayload returns the wire encoding of the getheaders message. func (g *GetHeaders) EncodePayload() ([]byte, error) { if len(g.LocatorHashes) > MaxLocatorHashesPerMsg { return nil, fmt.Errorf( "btcwire: getheaders carries %d locator hashes, maximum is %d: %w", len(g.LocatorHashes), MaxLocatorHashesPerMsg, ErrTooManyItems, ) } var e Encoder e.Uint32(g.ProtocolVersion) e.CompactSize(uint64(len(g.LocatorHashes))) for i := range g.LocatorHashes { e.Hash(g.LocatorHashes[i]) } e.Hash(g.StopHash) return e.Bytes(), nil } // DecodePayload replaces g with the getheaders message decoded from b. func (g *GetHeaders) DecodePayload(b []byte) error { d := NewDecoder(b) version, err := d.Uint32() if err != nil { return fmt.Errorf("btcwire: decoding getheaders protocol version: %w", err) } count, err := d.count(MaxLocatorHashesPerMsg, chainhash.HashSize, "locator hash") if err != nil { return err } hashes := make([]chainhash.Hash, count) for i := range hashes { if hashes[i], err = d.Hash(); err != nil { return fmt.Errorf("btcwire: decoding getheaders locator hash %d: %w", i, err) } } stop, err := d.Hash() if err != nil { return fmt.Errorf("btcwire: decoding getheaders stop hash: %w", err) } *g = GetHeaders{ ProtocolVersion: version, LocatorHashes: hashes, StopHash: stop, } return nil } // Headers carries a batch of block headers in response to a getheaders // message. type Headers struct { // Headers is the batch, ordered from the requested height upwards. Headers []BlockHeader } // EncodePayload returns the wire encoding of the headers message. Each header // is followed by a zero transaction count, as required of header-only // responses. func (h *Headers) EncodePayload() ([]byte, error) { if len(h.Headers) > MaxBlockHeadersPerMsg { return nil, fmt.Errorf( "btcwire: headers carries %d headers, maximum is %d: %w", len(h.Headers), MaxBlockHeadersPerMsg, ErrTooManyItems, ) } var e Encoder e.CompactSize(uint64(len(h.Headers))) for i := range h.Headers { raw := h.Headers[i].Serialize() e.Write(raw[:]) e.CompactSize(0) } return e.Bytes(), nil } // DecodePayload replaces h with the headers message decoded from b. A // non-zero transaction count after a header is rejected: those entries are not // valid in a headers message. func (h *Headers) DecodePayload(b []byte) error { d := NewDecoder(b) count, err := d.count( MaxBlockHeadersPerMsg, BlockHeaderSize+1, "block header", ) if err != nil { return err } headers := make([]BlockHeader, count) for i := range headers { raw, err := d.Bytes(BlockHeaderSize) if err != nil { return fmt.Errorf("btcwire: decoding block header %d: %w", i, err) } if err := headers[i].Deserialize(raw); err != nil { return fmt.Errorf("btcwire: decoding block header %d: %w", i, err) } txCount, err := d.CompactSize() if err != nil { return fmt.Errorf( "btcwire: decoding transaction count of block header %d: %w", i, err, ) } if txCount != 0 { return fmt.Errorf( "%w: headers entry %d has %d transactions", ErrInvalidPayload, i, txCount, ) } } h.Headers = headers return nil }