Browse Source

parser: implement Compact Block encoding from ZIP307

v2_protobufs
George Tankersley 6 years ago
parent
commit
7cc7095a81
  1. 25
      parser/block.go
  2. 16
      parser/block_header.go
  3. 56
      parser/block_test.go
  4. 38
      parser/testdata/compact_blocks.json
  5. 37
      parser/transaction.go

25
parser/block.go

@ -4,6 +4,7 @@ import (
"fmt"
"github.com/gtank/ctxd/parser/internal/bytestring"
"github.com/gtank/ctxd/proto"
"github.com/pkg/errors"
)
@ -24,6 +25,16 @@ func (b *block) GetTxCount() int {
return len(b.vtx)
}
// GetHash returns the block hash in big-endian display order.
func (b *block) GetHash() []byte {
return b.hdr.GetHash()
}
// getEncodableHash returns the block hash in little-endian wire order.
func (b *block) getEncodableHash() []byte {
return b.hdr.getEncodableHash()
}
// GetHeight() extracts the block height from the coinbase transaction. See
// BIP34. Returns block height on success, or -1 on error.
func (b *block) GetHeight() int {
@ -46,6 +57,20 @@ func (b *block) GetHeight() int {
return int(blockHeight)
}
func (b *block) ToCompact() *proto.CompactBlock {
compactBlock := &proto.CompactBlock{
BlockID: &proto.BlockFilter{
BlockHeight: uint64(b.GetHeight()),
BlockHash: b.getEncodableHash(),
},
}
compactBlock.Vtx = make([]*proto.CompactTx, len(b.vtx))
for idx, tx := range b.vtx {
compactBlock.Vtx[idx] = tx.ToCompact(idx)
}
return compactBlock
}
func (b *block) ParseFromSlice(data []byte) (rest []byte, err error) {
hdr := NewBlockHeader()
data, err = hdr.ParseFromSlice(data)

16
parser/block_header.go

@ -175,3 +175,19 @@ func (hdr *blockHeader) GetHash() []byte {
hdr.cachedHash = digest[:]
return hdr.cachedHash
}
// getEncodableHash returns the bytes of a block hash in little-endian wire order.
func (hdr *blockHeader) getEncodableHash() []byte {
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[:])
return digest[:]
}

56
parser/block_test.go

@ -3,11 +3,16 @@ package parser
import (
"bufio"
"encoding/hex"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"testing"
"github.com/pkg/errors"
protobuf "github.com/golang/protobuf/proto"
)
func TestBlockParser(t *testing.T) {
@ -40,3 +45,54 @@ func TestBlockParser(t *testing.T) {
}
}
}
func TestCompactBlocks(t *testing.T) {
type compactTest struct {
BlockHeight int `json:"block"`
BlockHash string `json:"hash"`
Full string `json:"full"`
Compact string `json:"compact"`
}
var compactTests []compactTest
blockJSON, err := ioutil.ReadFile("testdata/compact_blocks.json")
if err != nil {
log.Fatal(err)
}
err = json.Unmarshal(blockJSON, &compactTests)
if err != nil {
log.Fatal(err)
}
for _, test := range compactTests {
blockData, _ := hex.DecodeString(test.Full)
block := NewBlock()
blockData, err = block.ParseFromSlice(blockData)
if err != nil {
t.Error(errors.Wrap(err, fmt.Sprintf("parsing testnet block %d", test.BlockHeight)))
continue
}
if block.GetHeight() != test.BlockHeight {
t.Errorf("incorrect block height in testnet block %d", test.BlockHeight)
continue
}
if hex.EncodeToString(block.GetHash()) != test.BlockHash {
t.Errorf("incorrect block hash in testnet block %x", test.BlockHash)
continue
}
compact := block.ToCompact()
marshaled, err := protobuf.Marshal(compact)
if err != nil {
t.Errorf("could not marshal compact testnet block %d", test.BlockHeight)
continue
}
encodedCompact := hex.EncodeToString(marshaled)
if encodedCompact != test.Compact {
t.Errorf("wrong data for compact testnet block %d\nhave: %s\nwant: %s\n", test.BlockHeight, encodedCompact, test.Compact)
continue
}
}
}

38
parser/testdata/compact_blocks.json

File diff suppressed because one or more lines are too long

37
parser/transaction.go

@ -4,6 +4,7 @@ import (
"crypto/sha256"
"github.com/gtank/ctxd/parser/internal/bytestring"
"github.com/gtank/ctxd/proto"
"github.com/pkg/errors"
)
@ -125,6 +126,12 @@ func (p *spend) ParseFromSlice(data []byte) ([]byte, error) {
return []byte(s), nil
}
func (p *spend) ToCompact() *proto.CompactSpend {
return &proto.CompactSpend{
Nf: p.nullifier,
}
}
// output is a Sapling Output Description as described in section 7.4 of the
// Zcash protocol spec. Total size is 948.
type output struct {
@ -166,6 +173,14 @@ func (p *output) ParseFromSlice(data []byte) ([]byte, error) {
return []byte(s), nil
}
func (p *output) ToCompact() *proto.CompactOutput {
return &proto.CompactOutput{
Cmu: p.cmu,
Epk: p.ephemeralKey,
Ciphertext: p.encCiphertext[:52],
}
}
// joinSplit is a JoinSplit description as described in 7.2 of the Zcash
// protocol spec. Its exact contents differ by transaction version and network
// upgrade level.
@ -254,6 +269,7 @@ type transaction struct {
txId []byte
}
// GetHash returns the transaction hash in big-endian display order.
func (tx *transaction) GetHash() []byte {
if tx.txId != nil {
return tx.txId
@ -273,6 +289,27 @@ func (tx *transaction) GetHash() []byte {
return tx.txId
}
// getEncodableHash returns the transaction hash in little-endian wire format order.
func (tx *transaction) getEncodableHash() []byte {
digest := sha256.Sum256(tx.rawBytes)
digest = sha256.Sum256(digest[:])
return digest[:]
}
func (tx *transaction) ToCompact(index int) *proto.CompactTx {
ctx := &proto.CompactTx{
Index: uint64(index), // index is contextual
Hash: tx.getEncodableHash(),
Spends: make([]*proto.CompactSpend, len(tx.shieldedSpends)),
Outputs: make([]*proto.CompactOutput, len(tx.shieldedOutputs)),
}
for i, spend := range tx.shieldedSpends {
ctx.Spends[i] = spend.ToCompact()
}
for i, output := range tx.shieldedOutputs {
ctx.Outputs[i] = output.ToCompact()
}
return ctx
}
func (tx *transaction) ParseFromSlice(data []byte) ([]byte, error) {

Loading…
Cancel
Save