// Copyright (c) 2013-2014 Conformal Systems LLC. // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. package btcwire import ( "bytes" "fmt" "io" ) // defaultTransactionAlloc is the default size used for the backing array // for transactions. The transaction array will dynamically grow as needed, but // this figure is intended to provide enough space for the number of // transactions in the vast majority of blocks without needing to grow the // backing array multiple times. const defaultTransactionAlloc = 2048 // MaxBlocksPerMsg is the maximum number of blocks allowed per message. const MaxBlocksPerMsg = 500 // MaxBlockPayload is the maximum bytes a block message can be in bytes. const MaxBlockPayload = 1000000 // Not actually 1MB which would be 1024 * 1024 // maxTxPerBlock is the maximum number of transactions that could // possibly fit into a block. const maxTxPerBlock = (MaxBlockPayload / minTxPayload) + 1 // TxLoc holds locator data for the offset and length of where a transaction is // located within a MsgBlock data buffer. type TxLoc struct { TxStart int TxLen int } // MsgBlock implements the Message interface and represents a bitcoin // block message. It is used to deliver block and transaction information in // response to a getdata message (MsgGetData) for a given block hash. type MsgBlock struct { Header BlockHeader Transactions []*MsgTx } // AddTransaction adds a transaction to the message. func (msg *MsgBlock) AddTransaction(tx *MsgTx) error { msg.Transactions = append(msg.Transactions, tx) return nil } // ClearTransactions removes all transactions from the message. func (msg *MsgBlock) ClearTransactions() { msg.Transactions = make([]*MsgTx, 0, defaultTransactionAlloc) } // BtcDecode decodes r using the bitcoin protocol encoding into the receiver. // This is part of the Message interface implementation. // See Deserialize for decoding blocks stored to disk, such as in a database, as // opposed to decoding blocks from the wire. func (msg *MsgBlock) BtcDecode(r io.Reader, pver uint32) error { err := readBlockHeader(r, pver, &msg.Header) if err != nil { return err } txCount, err := readVarInt(r, pver) if err != nil { return err } // Prevent more transactions than could possibly fit into a block. // It would be possible to cause memory exhaustion and panics without // a sane upper bound on this count. if txCount > maxTxPerBlock { str := fmt.Sprintf("too many transactions to fit into a block "+ "[count %d, max %d]", txCount, maxTxPerBlock) return messageError("MsgBlock.BtcDecode", str) } msg.Transactions = make([]*MsgTx, 0, txCount) for i := uint64(0); i < txCount; i++ { tx := MsgTx{} err := tx.BtcDecode(r, pver) if err != nil { return err } msg.Transactions = append(msg.Transactions, &tx) } return nil } // Deserialize decodes a block from r into the receiver using a format that is // suitable for long-term storage such as a database while respecting the // Version field in the block. This function differs from BtcDecode in that // BtcDecode decodes from the bitcoin wire protocol as it was sent across the // network. The wire encoding can technically differ depending on the protocol // version and doesn't even really need to match the format of a stored block at // all. As of the time this comment was written, the encoded block is the same // in both instances, but there is a distinct difference and separating the two // allows the API to be flexible enough to deal with changes. func (msg *MsgBlock) Deserialize(r io.Reader) error { // At the current time, there is no difference between the wire encoding // at protocol version 0 and the stable long-term storage format. As // a result, make use of BtcDecode. return msg.BtcDecode(r, 0) } // DeserializeTxLoc decodes r in the same manner Deserialize does, but it takes // a byte buffer instead of a generic reader and returns a slice containing the start and length of // each transaction within the raw data that is being deserialized. func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) { fullLen := r.Len() // At the current time, there is no difference between the wire encoding // at protocol version 0 and the stable long-term storage format. As // a result, make use of existing wire protocol functions. err := readBlockHeader(r, 0, &msg.Header) if err != nil { return nil, err } txCount, err := readVarInt(r, 0) if err != nil { return nil, err } // Prevent more transactions than could possibly fit into a block. // It would be possible to cause memory exhaustion and panics without // a sane upper bound on this count. if txCount > maxTxPerBlock { str := fmt.Sprintf("too many transactions to fit into a block "+ "[count %d, max %d]", txCount, maxTxPerBlock) return nil, messageError("MsgBlock.DeserializeTxLoc", str) } // Deserialize each transaction while keeping track of its location // within the byte stream. msg.Transactions = make([]*MsgTx, 0, txCount) txLocs := make([]TxLoc, txCount) for i := uint64(0); i < txCount; i++ { txLocs[i].TxStart = fullLen - r.Len() tx := MsgTx{} err := tx.Deserialize(r) if err != nil { return nil, err } msg.Transactions = append(msg.Transactions, &tx) txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart } return txLocs, nil } // BtcEncode encodes the receiver to w using the bitcoin protocol encoding. // This is part of the Message interface implementation. // See Serialize for encoding blocks to be stored to disk, such as in a // database, as opposed to encoding blocks for the wire. func (msg *MsgBlock) BtcEncode(w io.Writer, pver uint32) error { err := writeBlockHeader(w, pver, &msg.Header) if err != nil { return err } err = writeVarInt(w, pver, uint64(len(msg.Transactions))) if err != nil { return err } for _, tx := range msg.Transactions { err = tx.BtcEncode(w, pver) if err != nil { return err } } return nil } // Serialize encodes the block to w using a format that suitable for long-term // storage such as a database while respecting the Version field in the block. // This function differs from BtcEncode in that BtcEncode encodes the block to // the bitcoin wire protocol in order to be sent across the network. The wire // encoding can technically differ depending on the protocol version and doesn't // even really need to match the format of a stored block at all. As of the // time this comment was written, the encoded block is the same in both // instances, but there is a distinct difference and separating the two allows // the API to be flexible enough to deal with changes. func (msg *MsgBlock) Serialize(w io.Writer) error { // At the current time, there is no difference between the wire encoding // at protocol version 0 and the stable long-term storage format. As // a result, make use of BtcEncode. return msg.BtcEncode(w, 0) } // Command returns the protocol command string for the message. This is part // of the Message interface implementation. func (msg *MsgBlock) Command() string { return cmdBlock } // MaxPayloadLength returns the maximum length the payload can be for the // receiver. This is part of the Message interface implementation. func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 { // Block header at 80 bytes + transaction count + max transactions // which can vary up to the MaxBlockPayload (including the block header // and transaction count). return MaxBlockPayload } // BlockSha computes the block identifier hash for this block. func (msg *MsgBlock) BlockSha() (ShaHash, error) { return msg.Header.BlockSha() } // TxShas returns a slice of hashes of all of transactions in this block. func (msg *MsgBlock) TxShas() ([]ShaHash, error) { shaList := make([]ShaHash, 0, len(msg.Transactions)) for _, tx := range msg.Transactions { // Ignore error here since TxSha can't fail in the current // implementation except due to run-time panics. sha, _ := tx.TxSha() shaList = append(shaList, sha) } return shaList, nil } // NewMsgBlock returns a new bitcoin block message that conforms to the // Message interface. See MsgBlock for details. func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock { return &MsgBlock{ Header: *blockHeader, Transactions: make([]*MsgTx, 0, defaultTransactionAlloc), } }