Skip to content

Commit

Permalink
Merge pull request #5 from libsv/enhancement/header_client
Browse files Browse the repository at this point in the history
  • Loading branch information
jadwahab authored Jun 1, 2021
2 parents 4f8aae5 + ee78ddc commit 1b7e111
Show file tree
Hide file tree
Showing 7 changed files with 98 additions and 78 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/greetings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,4 @@ jobs:
- uses: actions/first-interaction@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
pr-message: 'Congrats, you just opened your first pull request on libsv/libsv! Thank you for contributing!'
pr-message: 'Congrats, you just opened your first pull request on libsv/go-bc! Thank you for contributing!'
96 changes: 47 additions & 49 deletions blockheader.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,93 +31,91 @@ type BlockHeader struct {

// TODO: make fields private and make getters and setters

// ToString returns the Block Header encoded as hex string.
func (bh *BlockHeader) ToString() string {
return hex.EncodeToString(bh.ToBytes())
}

// ToBytes returns the Block Header encoded as bytes.
func (bh *BlockHeader) ToBytes() []byte {
// TODO:
return []byte{}
}

// EncodeBlockHeaderStr will encode a block header byte slice
// into the bitcoin block header structure.
// See https://en.bitcoin.it/wiki/Block_hashing_algorithm
func EncodeBlockHeaderStr(headerStr string) (*BlockHeader, error) {
if len(headerStr) != 160 {
return nil, errors.New("block header should be 80 bytes long")
}

headerBytes, err := hex.DecodeString(headerStr)
// String returns the Block Header encoded as hex string.
func (bh *BlockHeader) String() (string, error) {
bb, err := bh.Bytes()
if err != nil {
return nil, err
}

return EncodeBlockHeader(headerBytes)
}

// EncodeBlockHeader will encode a block header byte slice
// into the bitcoin block header structure.
// See https://en.bitcoin.it/wiki/Block_hashing_algorithm
func EncodeBlockHeader(headerBytes []byte) (*BlockHeader, error) {
if len(headerBytes) != 80 {
return nil, errors.New("block header should be 80 bytes long")
return "", err
}

return &BlockHeader{
Version: binary.LittleEndian.Uint32(headerBytes[:4]),
HashPrevBlock: hex.EncodeToString(bt.ReverseBytes(headerBytes[4:36])),
HashMerkleRoot: hex.EncodeToString(bt.ReverseBytes(headerBytes[36:68])),
Time: binary.LittleEndian.Uint32(headerBytes[68:72]),
Bits: hex.EncodeToString(bt.ReverseBytes(headerBytes[72:76])),
Nonce: binary.LittleEndian.Uint32(headerBytes[76:]),
}, nil
return hex.EncodeToString(bb), nil
}

// DecodeBlockHeader will decode a bitcoin block header struct
// Bytes will decode a bitcoin block header struct
// into a byte slice.
// See https://en.bitcoin.it/wiki/Block_hashing_algorithm
func DecodeBlockHeader(header *BlockHeader) ([]byte, error) {
func (bh *BlockHeader) Bytes() ([]byte, error) {
bytes := []byte{}

v := make([]byte, 4)
binary.LittleEndian.PutUint32(v, header.Version)
binary.LittleEndian.PutUint32(v, bh.Version)
bytes = append(bytes, v...)

p, err := hex.DecodeString(header.HashPrevBlock)
p, err := hex.DecodeString(bh.HashPrevBlock)
if err != nil {
return nil, err
}
p = bt.ReverseBytes(p)
bytes = append(bytes, p...)

m, err := hex.DecodeString(header.HashMerkleRoot)
m, err := hex.DecodeString(bh.HashMerkleRoot)
if err != nil {
return nil, err
}
m = bt.ReverseBytes(m)
bytes = append(bytes, m...)

t := make([]byte, 4)
binary.LittleEndian.PutUint32(t, header.Time)
binary.LittleEndian.PutUint32(t, bh.Time)
bytes = append(bytes, t...)

b, err := hex.DecodeString(header.Bits)
b, err := hex.DecodeString(bh.Bits)
if err != nil {
return nil, err
}
b = bt.ReverseBytes(b)
bytes = append(bytes, b...)

n := make([]byte, 4)
binary.LittleEndian.PutUint32(t, header.Nonce)
binary.LittleEndian.PutUint32(t, bh.Nonce)
bytes = append(bytes, n...)

return bytes, nil
}

// EncodeBlockHeaderStr will encode a block header hash
// into the bitcoin block header structure.
// See https://en.bitcoin.it/wiki/Block_hashing_algorithm
func EncodeBlockHeaderStr(headerStr string) (*BlockHeader, error) {
if len(headerStr) != 160 {
return nil, errors.New("block header should be 80 bytes long")
}

headerBytes, err := hex.DecodeString(headerStr)
if err != nil {
return nil, err
}

return EncodeBlockHeader(headerBytes)
}

// EncodeBlockHeader will encode a block header byte slice
// into the bitcoin block header structure.
// See https://en.bitcoin.it/wiki/Block_hashing_algorithm
func EncodeBlockHeader(headerBytes []byte) (*BlockHeader, error) {
if len(headerBytes) != 80 {
return nil, errors.New("block header should be 80 bytes long")
}

return &BlockHeader{
Version: binary.LittleEndian.Uint32(headerBytes[:4]),
HashPrevBlock: hex.EncodeToString(bt.ReverseBytes(headerBytes[4:36])),
HashMerkleRoot: hex.EncodeToString(bt.ReverseBytes(headerBytes[36:68])),
Time: binary.LittleEndian.Uint32(headerBytes[68:72]),
Bits: hex.EncodeToString(bt.ReverseBytes(headerBytes[72:76])),
Nonce: binary.LittleEndian.Uint32(headerBytes[76:]),
}, nil
}

// ExtractMerkleRootFromBlockHeader will take an 80 byte Bitcoin block
// header hex string and return the Merkle Root from it.
func ExtractMerkleRootFromBlockHeader(header string) (string, error) {
Expand Down
2 changes: 1 addition & 1 deletion blockheader_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ func TestDecodeBlockHeader(t *testing.T) {
}
expectedHeader := "00000020fb9eacea87c1cc294a4f1633a45b9bfb21cf9878b439c6138d96b8ca3a856e3a37307cd123724eaa4ade23d29feea1358458d5c110275b6cca4e2b79cd14d98e39573460ffff7f2000000000"

headerBytes, err := bc.DecodeBlockHeader(bh)
headerBytes, err := bh.Bytes()
header := hex.EncodeToString(headerBytes)

assert.NoError(t, err)
Expand Down
19 changes: 16 additions & 3 deletions blockheaderchain.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
package bc

import "context"
import (
"context"
"errors"
)

var (
// ErrHeaderNotFound can be returned if the blockHash isn't found on the network.
ErrHeaderNotFound = errors.New("header with not found")
// ErrNotOnLongestChain indicates the blockhash is present but isn't on the longest current chain.
ErrNotOnLongestChain = errors.New("header exists but is not on the longest chain")
)

// A BlockHeaderChain is a generic interface used to map things in the block header chain
// (chain of block headers). For example, it is used to get a block Header from a bitcoin
// block hash if it exists in the longest block header chain.
//
// Errors can be returned if the header isn't found or is on a stale chain, you may also use the
// ErrHeaderNotFound & ErrNotOnLongestChain sentinel errors when implementing the interface.
type BlockHeaderChain interface {
BlockHeader(ctx context.Context, blockHash string) (blockHeader string, err error)
}
BlockHeader(ctx context.Context, blockHash string) (*BlockHeader, error)
}
35 changes: 27 additions & 8 deletions spvclient.go
Original file line number Diff line number Diff line change
@@ -1,20 +1,39 @@
package bc

import (
"errors"
)

// An SPVClient is a struct used to specify interfaces
// used to complete Simple Payment Verification (SPV)
// in conjunction with a Merkle Proof.
//
// The implementation of BlockHeaderChain which is supplied will depend on the client
// you are using, some may return a HeaderJSON response others may return the blockhash.
type SPVClient struct {
// BlockHeaderChain will be set when an implementation returning a bc.BlockHeader type is provided.
bhc BlockHeaderChain
}

// NewSPVClient creates a new SPVClient based on params
// passed or will use defaults if nil is passed.
func NewSPVClient(bhc BlockHeaderChain) *SPVClient {
if bhc == nil {
return &SPVClient{}
}
// SPVOpts can be implemented to provided functional options for an SPVClient.
type SPVOpts func(*SPVClient)

return &SPVClient{
bhc: bhc,
// WithBlockHeaderChain will inject the provided BlockHeaderChain into the SPVClient.
func WithBlockHeaderChain(bhc BlockHeaderChain) SPVOpts {
return func(s *SPVClient) {
s.bhc = bhc
}
}

// NewSPVClient creates a new SPVClient based on the options provided.
// If no BlockHeaderChain implementation is provided, the setup will return an error.
func NewSPVClient(opts ...SPVOpts) (*SPVClient, error) {
cli := &SPVClient{}
for _, opt := range opts {
opt(cli)
}
if cli.bhc == nil {
return nil, errors.New("at least one blockchain header implementation should be returned")
}
return cli, nil
}
12 changes: 2 additions & 10 deletions verifymerkleproof.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,12 @@ func (spvc *SPVClient) VerifyMerkleProof(ctx context.Context, proof []byte) (val
// if bits 1 and 2 of flags are NOT set, target should contain a block hash (32 bytes)
case 0:
// The `target` field contains a block hash

blockHeader, err := spvc.bhc.BlockHeader(ctx, mpb.target)
if err != nil {
return false, false, err
}

merkleRoot, err = ExtractMerkleRootFromBlockHeader(blockHeader)
if err != nil {
return false, false, err
}
merkleRoot = blockHeader.HashMerkleRoot

// if bit 2 of flags is set, target should contain a merkle root (32 bytes)
case 4:
Expand Down Expand Up @@ -110,11 +106,7 @@ func (spvc *SPVClient) VerifyMerkleProofJSON(ctx context.Context, proof *MerkleP
if err != nil {
return false, false, err
}

merkleRoot, err = ExtractMerkleRootFromBlockHeader(blockHeader)
if err != nil {
return false, false, err
}
merkleRoot = blockHeader.HashMerkleRoot

} else if proof.TargetType == "header" && len(proof.Target) == 160 {
// The `target` field contains a block header
Expand Down
10 changes: 4 additions & 6 deletions verifymerkleproof_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,8 @@ import (

type mockBlockHeaderChain struct{}

func (bhc *mockBlockHeaderChain) BlockHeader(ctx context.Context, blockHash string) (blockHeader string, err error) {
return map[string]string{
"75edb0a69eb195cdd81e310553aa4d25e18450e08f168532a2c2e9cf447bf169": "000000208e33a53195acad0ab42ddbdbe3e4d9ca081332e5b01a62e340dbd8167d1a787b702f61bb913ac2063e0f2aed6d933d3386234da5c8eb9e30e498efd25fb7cb96fff12c60ffff7f2001000000",
}[blockHash], nil
func (bhc *mockBlockHeaderChain) BlockHeader(ctx context.Context, blockHash string) (blockHeader *bc.BlockHeader, err error) {
return bc.EncodeBlockHeaderStr("000000208e33a53195acad0ab42ddbdbe3e4d9ca081332e5b01a62e340dbd8167d1a787b702f61bb913ac2063e0f2aed6d933d3386234da5c8eb9e30e498efd25fb7cb96fff12c60ffff7f2001000000")
}

func TestVerifyMerkleProof(t *testing.T) {
Expand All @@ -31,9 +29,9 @@ func TestVerifyMerkleProof(t *testing.T) {
"391e62b3419d8a943f7dbc7bddc90e30ec724c033000dc0c8872253c27b03a42",
},
}
hcm := mockBlockHeaderChain{}
hcm := &mockBlockHeaderChain{}

spvc := bc.NewSPVClient(&hcm)
spvc, _ := bc.NewSPVClient(bc.WithBlockHeaderChain(hcm))

t.Run("JSON", func(t *testing.T) {
valid, isLastInTree, err := spvc.VerifyMerkleProofJSON(context.Background(), proofJSON)
Expand Down

0 comments on commit 1b7e111

Please sign in to comment.