Remove dependency on protocol version.

This commit unfortunately changes the public API of Block which I
ordinarily don't like to do, but in this case, I felt it was necessary.

The blocks used throughout the database and elsewhere should be indepedent
of the protocol version which is used to encode the block to wire format.
Each block has its own Version field which should be the deciding factor
for the serialization and deserialization of blocks.  In practice, they
are currently the same encoding, but that may not always be the case, and
it's important the blocks are stable depending on their own version
regardless of the protocol version.

This makes use of the new Serialize and Deserialize functions on MsgBlock
which are intended for long-term storage as opposed to wire encoding.
This commit is contained in:
Dave Collins 2013-08-05 12:41:31 -05:00
parent 761970e639
commit 43095c66bc
3 changed files with 75 additions and 110 deletions

View file

@ -25,13 +25,12 @@ func (e OutOfRangeError) Error() string {
}
// Block defines a bitcoin block that provides easier and more efficient
// manipulation of raw wire protocol blocks. It also memoizes hashes for the
// block and its transactions on their first access so subsequent accesses don't
// have to repeat the relatively expensive hashing operations.
// manipulation of raw blocks. It also memoizes hashes for the block and its
// transactions on their first access so subsequent accesses don't have to
// repeat the relatively expensive hashing operations.
type Block struct {
msgBlock *btcwire.MsgBlock // Underlying MsgBlock
rawBlock []byte // Raw wire encoded bytes for the block
protocolVersion uint32 // Protocol version used to encode rawBlock
serializedBlock []byte // Serialized bytes for the block
blockSha *btcwire.ShaHash // Cached block hash
blockHeight int64 // Height in the main block chain
txShas []*btcwire.ShaHash // Cached transaction hashes
@ -44,28 +43,26 @@ func (b *Block) MsgBlock() *btcwire.MsgBlock {
return b.msgBlock
}
// Bytes returns the raw wire protocol encoded bytes for the Block and the
// protocol version used to encode it. This is equivalent to calling BtcEncode
// on the underlying btcwire.MsgBlock, however it caches the result so
// subsequent calls are more efficient.
func (b *Block) Bytes() ([]byte, uint32, error) {
// Return the cached raw block bytes and associated protocol version if
// it has already been generated.
if len(b.rawBlock) != 0 {
return b.rawBlock, b.protocolVersion, nil
// Bytes returns the serialized bytes for the Block. This is equivalent to
// calling Serialize on the underlying btcwire.MsgBlock, however it caches the
// result so subsequent calls are more efficient.
func (b *Block) Bytes() ([]byte, error) {
// Return the cached serialized bytes if it has already been generated.
if len(b.serializedBlock) != 0 {
return b.serializedBlock, nil
}
// Encode the MsgBlock into raw block bytes.
// Serialize the MsgBlock.
var w bytes.Buffer
err := b.msgBlock.BtcEncode(&w, b.protocolVersion)
err := b.msgBlock.Serialize(&w)
if err != nil {
return nil, 0, err
return nil, err
}
rawBlock := w.Bytes()
serializedBlock := w.Bytes()
// Cache the encoded bytes and return them.
b.rawBlock = rawBlock
return rawBlock, b.protocolVersion, nil
// Cache the serialized bytes and return them.
b.serializedBlock = serializedBlock
return serializedBlock, nil
}
// Sha returns the block identifier hash for the Block. This is equivalent to
@ -79,7 +76,7 @@ func (b *Block) Sha() (*btcwire.ShaHash, error) {
// Generate the block hash. Ignore the error since BlockSha can't
// currently fail.
sha, _ := b.msgBlock.BlockSha(b.protocolVersion)
sha, _ := b.msgBlock.BlockSha()
// Cache the block hash and return it.
b.blockSha = &sha
@ -112,7 +109,7 @@ func (b *Block) TxSha(txNum int) (*btcwire.ShaHash, error) {
// Generate the hash for the transaction. Ignore the error since TxSha
// can't currently fail.
sha, _ := b.msgBlock.Transactions[txNum].TxSha(b.protocolVersion)
sha, _ := b.msgBlock.Transactions[txNum].TxSha()
// Cache the transaction hash and return it.
b.txShas[txNum] = &sha
@ -140,7 +137,7 @@ func (b *Block) TxShas() ([]*btcwire.ShaHash, error) {
for i, hash := range b.txShas {
if hash == nil {
// Ignore the error since TxSha can't currently fail.
sha, _ := b.msgBlock.Transactions[i].TxSha(b.protocolVersion)
sha, _ := b.msgBlock.Transactions[i].TxSha()
b.txShas[i] = &sha
}
}
@ -149,24 +146,18 @@ func (b *Block) TxShas() ([]*btcwire.ShaHash, error) {
return b.txShas, nil
}
// ProtocolVersion returns the protocol version that was used to create the
// underlying btcwire.MsgBlock.
func (b *Block) ProtocolVersion() uint32 {
return b.protocolVersion
}
// TxLoc returns the offsets and lengths of each transaction in a raw block.
// It is used to allow fast indexing into transactions within the raw byte
// stream.
func (b *Block) TxLoc() ([]btcwire.TxLoc, error) {
rawMsg, pver, err := b.Bytes()
rawMsg, err := b.Bytes()
if err != nil {
return nil, err
}
rbuf := bytes.NewBuffer(rawMsg)
var mblock btcwire.MsgBlock
txLocs, err := mblock.BtcDecodeTxLoc(rbuf, pver)
txLocs, err := mblock.DeserializeTxLoc(rbuf)
if err != nil {
return nil, err
}
@ -185,43 +176,39 @@ func (b *Block) SetHeight(height int64) {
}
// NewBlock returns a new instance of a bitcoin block given an underlying
// btcwire.MsgBlock and protocol version. See Block.
func NewBlock(msgBlock *btcwire.MsgBlock, pver uint32) *Block {
// btcwire.MsgBlock. See Block.
func NewBlock(msgBlock *btcwire.MsgBlock) *Block {
return &Block{
msgBlock: msgBlock,
protocolVersion: pver,
blockHeight: BlockHeightUnknown,
}
}
// NewBlockFromBytes returns a new instance of a bitcoin block given the
// raw wire encoded bytes and protocol version used to encode those bytes.
// See Block.
func NewBlockFromBytes(rawBlock []byte, pver uint32) (*Block, error) {
// Decode the raw block bytes into a MsgBlock.
// serialized bytes. See Block.
func NewBlockFromBytes(serializedBlock []byte) (*Block, error) {
// Deserialize the bytes into a MsgBlock.
var msgBlock btcwire.MsgBlock
br := bytes.NewBuffer(rawBlock)
err := msgBlock.BtcDecode(br, pver)
br := bytes.NewBuffer(serializedBlock)
err := msgBlock.Deserialize(br)
if err != nil {
return nil, err
}
b := Block{
msgBlock: &msgBlock,
rawBlock: rawBlock,
protocolVersion: pver,
serializedBlock: serializedBlock,
blockHeight: BlockHeightUnknown,
}
return &b, nil
}
// NewBlockFromBlockAndBytes returns a new instance of a bitcoin block given
// an underlying btcwire.MsgBlock, protocol version and raw Block. See Block.
func NewBlockFromBlockAndBytes(msgBlock *btcwire.MsgBlock, rawBlock []byte, pver uint32) *Block {
// an underlying btcwire.MsgBlock and the serialized bytes for it. See Block.
func NewBlockFromBlockAndBytes(msgBlock *btcwire.MsgBlock, serializedBlock []byte) *Block {
return &Block{
msgBlock: msgBlock,
rawBlock: rawBlock,
protocolVersion: pver,
serializedBlock: serializedBlock,
blockHeight: BlockHeightUnknown,
}
}

View file

@ -17,14 +17,9 @@ import (
// TestBlock tests the API for Block.
func TestBlock(t *testing.T) {
pver := btcwire.ProtocolVersion
b := btcutil.NewBlock(&Block100000, pver)
b := btcutil.NewBlock(&Block100000)
// Ensure we get the same data back out.
if gotPver := b.ProtocolVersion(); gotPver != pver {
t.Errorf("ProtocolVersion: wrong protocol version - got %v, want %v",
gotPver, pver)
}
if msgBlock := b.MsgBlock(); !reflect.DeepEqual(msgBlock, &Block100000) {
t.Errorf("MsgBlock: mismatched MsgBlock - got %v, want %v",
spew.Sdump(msgBlock), spew.Sdump(&Block100000))
@ -89,7 +84,7 @@ func TestBlock(t *testing.T) {
}
// Create a new block to nuke all cached data.
b = btcutil.NewBlock(&Block100000, pver)
b = btcutil.NewBlock(&Block100000)
// Request slice of all transaction shas multiple times to test
// generation and caching.
@ -125,34 +120,28 @@ func TestBlock(t *testing.T) {
}
}
// Encode the test block to bytes.
// Serialize the test block.
var block100000Buf bytes.Buffer
err = Block100000.BtcEncode(&block100000Buf, pver)
err = Block100000.Serialize(&block100000Buf)
if err != nil {
t.Errorf("BtcEncode: %v", err)
t.Errorf("Serialize: %v", err)
}
block100000Bytes := block100000Buf.Bytes()
// Request raw bytes multiple times to test generation and caching.
// Request serialized bytes multiple times to test generation and
// caching.
for i := 0; i < 2; i++ {
rawBytes, tmpPver, err := b.Bytes()
serializedBytes, err := b.Bytes()
if err != nil {
t.Errorf("Bytes: %v", err)
continue
}
if !bytes.Equal(rawBytes, block100000Bytes) {
if !bytes.Equal(serializedBytes, block100000Bytes) {
t.Errorf("Bytes #%d wrong bytes - got %v, want %v", i,
spew.Sdump(rawBytes),
spew.Sdump(serializedBytes),
spew.Sdump(block100000Bytes))
continue
}
if tmpPver != pver {
t.Errorf("Bytes #%d wrong protocol version - "+
"got %v, want %v", i, spew.Sdump(rawBytes),
spew.Sdump(block100000Bytes))
continue
}
}
// Transaction offsets and length for the transaction in Block100000.
@ -176,39 +165,34 @@ func TestBlock(t *testing.T) {
}
}
// TestNewBlockFromBytes tests creation of a Block from raw bytes.
// TestNewBlockFromBytes tests creation of a Block from serialized bytes.
func TestNewBlockFromBytes(t *testing.T) {
// Encode the test block to bytes.
pver := btcwire.ProtocolVersion
// Serialize the test block.
var block100000Buf bytes.Buffer
err := Block100000.BtcEncode(&block100000Buf, pver)
err := Block100000.Serialize(&block100000Buf)
if err != nil {
t.Errorf("BtcEncode: %v", err)
t.Errorf("Serialize: %v", err)
}
block100000Bytes := block100000Buf.Bytes()
// Create a new block from the encoded bytes.
b, err := btcutil.NewBlockFromBytes(block100000Bytes, pver)
// Create a new block from the serialized bytes.
b, err := btcutil.NewBlockFromBytes(block100000Bytes)
if err != nil {
t.Errorf("NewBlockFromBytes: %v", err)
return
}
// Ensure we get the same data back out.
rawBytes, tmpPver, err := b.Bytes()
serializedBytes, err := b.Bytes()
if err != nil {
t.Errorf("Bytes: %v", err)
return
}
if !bytes.Equal(rawBytes, block100000Bytes) {
if !bytes.Equal(serializedBytes, block100000Bytes) {
t.Errorf("Bytes: wrong bytes - got %v, want %v",
spew.Sdump(rawBytes),
spew.Sdump(serializedBytes),
spew.Sdump(block100000Bytes))
}
if tmpPver != pver {
t.Errorf("Bytes: wrong protocol version - got %v, want %v",
tmpPver, pver)
}
// Ensure the generated MsgBlock is correct.
if msgBlock := b.MsgBlock(); !reflect.DeepEqual(msgBlock, &Block100000) {
@ -220,34 +204,28 @@ func TestNewBlockFromBytes(t *testing.T) {
// TestNewBlockFromBlockAndBytes tests creation of a Block from a MsgBlock and
// raw bytes.
func TestNewBlockFromBlockAndBytes(t *testing.T) {
// Encode the test block to bytes.
pver := btcwire.ProtocolVersion
// Serialize the test block.
var block100000Buf bytes.Buffer
err := Block100000.BtcEncode(&block100000Buf, pver)
err := Block100000.Serialize(&block100000Buf)
if err != nil {
t.Errorf("BtcEncode: %v", err)
t.Errorf("Serialize: %v", err)
}
block100000Bytes := block100000Buf.Bytes()
// Create a new block from the encoded bytes.
b := btcutil.NewBlockFromBlockAndBytes(&Block100000,
block100000Bytes, pver)
// Create a new block from the serialized bytes.
b := btcutil.NewBlockFromBlockAndBytes(&Block100000, block100000Bytes)
// Ensure we get the same data back out.
rawBytes, tmpPver, err := b.Bytes()
serializedBytes, err := b.Bytes()
if err != nil {
t.Errorf("Bytes: %v", err)
return
}
if !bytes.Equal(rawBytes, block100000Bytes) {
if !bytes.Equal(serializedBytes, block100000Bytes) {
t.Errorf("Bytes: wrong bytes - got %v, want %v",
spew.Sdump(rawBytes),
spew.Sdump(serializedBytes),
spew.Sdump(block100000Bytes))
}
if tmpPver != pver {
t.Errorf("Bytes: wrong protocol version - got %v, want %v",
tmpPver, pver)
}
if msgBlock := b.MsgBlock(); !reflect.DeepEqual(msgBlock, &Block100000) {
t.Errorf("MsgBlock: mismatched MsgBlock - got %v, want %v",
spew.Sdump(msgBlock), spew.Sdump(&Block100000))
@ -264,17 +242,16 @@ func TestBlockErrors(t *testing.T) {
testErr.Error(), wantErr)
}
// Encode the test block to bytes.
pver := btcwire.ProtocolVersion
// Serialize the test block.
var block100000Buf bytes.Buffer
err := Block100000.BtcEncode(&block100000Buf, pver)
err := Block100000.Serialize(&block100000Buf)
if err != nil {
t.Errorf("BtcEncode: %v", err)
t.Errorf("Serialize: %v", err)
}
block100000Bytes := block100000Buf.Bytes()
// Create a new block from the encoded bytes.
b, err := btcutil.NewBlockFromBytes(block100000Bytes, pver)
// Create a new block from the serialized bytes.
b, err := btcutil.NewBlockFromBytes(block100000Bytes)
if err != nil {
t.Errorf("NewBlockFromBytes: %v", err)
return
@ -282,7 +259,7 @@ func TestBlockErrors(t *testing.T) {
// Truncate the block byte buffer to force errors.
shortBytes := block100000Bytes[:80]
_, err = btcutil.NewBlockFromBytes(shortBytes, pver)
_, err = btcutil.NewBlockFromBytes(shortBytes)
if err != io.EOF {
t.Errorf("NewBlockFromBytes: did not get expected error - "+
"got %v, want %v", err, io.EOF)

View file

@ -11,8 +11,9 @@ interface. The functions are only exported while the tests are being run.
package btcutil
// SetBlockBytes sets the internal raw block byte buffer to the passed buffer.
// It is used to inject errors and only available to the test package.
// SetBlockBytes sets the internal serialized block byte buffer to the passed
// buffer. It is used to inject errors and is only available to the test
// package.
func (b *Block) SetBlockBytes(buf []byte) {
b.rawBlock = buf
b.serializedBlock = buf
}