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

View file

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