From 5d3e0308a8d3d4d4693f3381afda762407f25423 Mon Sep 17 00:00:00 2001 From: George Tankersley Date: Wed, 12 Sep 2018 00:00:00 +0000 Subject: [PATCH] parser: sufficiently test block header serialization --- parser/block.go | 43 +++++++++++++++++++++++++++++++++++++++---- parser/block_test.go | 44 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 80 insertions(+), 7 deletions(-) diff --git a/parser/block.go b/parser/block.go index 3d1d545..7b836a1 100644 --- a/parser/block.go +++ b/parser/block.go @@ -1,14 +1,18 @@ package parser import ( + "bytes" + "crypto/sha256" "encoding/binary" "io" + "log" "github.com/pkg/errors" ) const ( - EQUIHASH_SIZE = 1344 // size of an Equihash solution in bytes + EQUIHASH_SIZE = 1344 // size of an Equihash solution in bytes + SER_BLOCK_HEADER_SIZE = 1487 // size of a serialized block header ) // A block header as defined in version 2018.0-beta-29 of the Zcash Protocol Spec. @@ -55,11 +59,11 @@ type RawBlockHeader struct { // EquihashSize is a concrete instance of Bitcoin's CompactSize encoding. type EquihashSize struct { - _ byte // always the byte value 253 - Size uint16 // always 1344 + SizeTag byte // always the byte value 253 + Size uint16 // always 1344 } -func ReadBlockHeader(r io.Reader) (*RawBlockHeader, error) { +func readRawBlockHeader(r io.Reader) (*RawBlockHeader, error) { var blockHeader RawBlockHeader err := binary.Read(r, binary.LittleEndian, &blockHeader) if err != nil { @@ -67,3 +71,34 @@ func ReadBlockHeader(r io.Reader) (*RawBlockHeader, error) { } return &blockHeader, nil } + +func (hdr *RawBlockHeader) MarshalBinary() ([]byte, error) { + serBytes := make([]byte, 0, SER_BLOCK_HEADER_SIZE) + serBuf := bytes.NewBuffer(serBytes) + err := binary.Write(serBuf, binary.LittleEndian, hdr) + return serBytes[:SER_BLOCK_HEADER_SIZE], err +} + +type BlockHeader struct { + *RawBlockHeader + cachedBlockHash []byte +} + +func (hdr *BlockHeader) GetBlockHash() []byte { + if hdr.cachedBlockHash != nil { + return hdr.cachedBlockHash + } + + serializedHeader, err := hdr.MarshalBinary() + if err != nil { + log.Fatalf("error marshaling block header: %v", err) + return nil + } + + // SHA256d + digest := sha256.Sum256(serializedHeader) + digest = sha256.Sum256(digest[:]) + + hdr.cachedBlockHash = digest[:] + return hdr.cachedBlockHash +} diff --git a/parser/block_test.go b/parser/block_test.go index cf17922..c24af5d 100644 --- a/parser/block_test.go +++ b/parser/block_test.go @@ -8,7 +8,7 @@ import ( "testing" ) -func TestReadBlockHeader(t *testing.T) { +func TestBlockHeader(t *testing.T) { testBlocks, err := os.Open("testdata/blocks") if err != nil { t.Fatal(err) @@ -25,13 +25,16 @@ func TestReadBlockHeader(t *testing.T) { t.Error(err) continue } + + // Try to read the header reader := bytes.NewReader(decodedBlockData) - rawHeader, err := ReadBlockHeader(reader) + rawHeader, err := readRawBlockHeader(reader) if err != nil { t.Error(err) - break + continue } + // Some basic sanity checks if rawHeader.Version != 4 { t.Error("Read wrong version in a test block.") break @@ -47,5 +50,40 @@ func TestReadBlockHeader(t *testing.T) { t.Error("Got wrong Equihash solution size.") break } + + // Re-serialize and check for consistency + serializedHeader, err := rawHeader.MarshalBinary() + if err != nil { + t.Errorf("Error serializing header: %v", err) + break + } + + if !bytes.Equal(serializedHeader, decodedBlockData[:SER_BLOCK_HEADER_SIZE]) { + offset := 0 + length := 0 + for i := 0; i < SER_BLOCK_HEADER_SIZE; i++ { + if serializedHeader[i] != decodedBlockData[i] { + if offset == 0 { + offset = i + } + length++ + } + } + t.Errorf("Block header failed round-trip serialization:\nwant\n%x\ngot\n%x\nat %d", serializedHeader[offset:offset+length], decodedBlockData[offset:offset+length], offset) + break + } + + blockHeader := &BlockHeader{ + rawHeader, + nil, + } + hash := blockHeader.GetBlockHash() + + // This is not necessarily true for anything but our current test cases. + for _, b := range hash[28:] { + if b != 0 { + t.Errorf("Hash lacked trailing zeros") + } + } } }