parser: implement Compact Block encoding from ZIP307
This commit is contained in:
parent
7736b2464b
commit
7cc7095a81
|
@ -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)
|
||||
|
|
|
@ -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[:]
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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…
Reference in New Issue