mirror of
https://github.com/bitcoin/bips.git
synced 2024-11-19 09:50:06 +01:00
dd3948b474
In this commit, we add a new test case for a filter built from a block that has a transaction with an OP_RETURN which isn't followed by only push data items. The prior implementation for btcd (which was used to generated these test vectors), had a stricter check which caused it to add extra items to the filter. We also add a case of a block that has a single coinbase transaction, with that transaction having only an OP_RETURN output. As a result, that filter will be "empty", and is signalled by by a single zero (0x00) byte. In order to make building the code that makes the test vectors reproducible, we've added go.mod and go.sum files as well.
302 lines
7.1 KiB
Go
302 lines
7.1 KiB
Go
// This program connects to your local btcd and generates test vectors for
|
|
// 5 blocks and collision space sizes of 1-32 bits. Change the RPC cert path
|
|
// and credentials to run on your system. The program assumes you're running
|
|
// a btcd with cfilter support, which mainline btcd doesn't have; in order to
|
|
// circumvent this assumption, comment out the if block that checks for
|
|
// filter size of DefaultP.
|
|
|
|
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/hex"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/btcsuite/btcd/blockchain"
|
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
"github.com/btcsuite/btcd/rpcclient"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/gcs/builder"
|
|
"github.com/davecgh/go-spew/spew"
|
|
)
|
|
|
|
var (
|
|
// testBlockHeights are the heights of the blocks to include in the test
|
|
// vectors. Any new entries must be added in sorted order.
|
|
testBlockHeights = []testBlockCase{
|
|
{0, "Genesis block"},
|
|
{2, ""},
|
|
{3, ""},
|
|
{15007, "Tx has non-standard OP_RETURN output followed by opcodes"},
|
|
{49291, "Tx pays to empty output script"},
|
|
{180480, "Tx spends from empty output script"},
|
|
{926485, "Duplicate pushdata 913bcc2be49cb534c20474c4dee1e9c4c317e7eb"},
|
|
{987876, "Coinbase tx has unparseable output script"},
|
|
{1263442, "Includes witness data"},
|
|
{1414221, "Empty data"},
|
|
}
|
|
|
|
defaultBtcdDir = btcutil.AppDataDir("btcd", false)
|
|
defaultBtcdRPCCertFile = filepath.Join(defaultBtcdDir, "rpc.cert")
|
|
)
|
|
|
|
const (
|
|
fp = 19
|
|
)
|
|
|
|
type testBlockCase struct {
|
|
height uint32
|
|
comment string
|
|
}
|
|
|
|
type JSONTestWriter struct {
|
|
writer io.Writer
|
|
firstRowWritten bool
|
|
}
|
|
|
|
func NewJSONTestWriter(writer io.Writer) *JSONTestWriter {
|
|
return &JSONTestWriter{writer: writer}
|
|
}
|
|
|
|
func (w *JSONTestWriter) WriteComment(comment string) error {
|
|
return w.WriteTestCase([]interface{}{comment})
|
|
}
|
|
|
|
func (w *JSONTestWriter) WriteTestCase(row []interface{}) error {
|
|
var err error
|
|
if w.firstRowWritten {
|
|
_, err = io.WriteString(w.writer, ",\n")
|
|
} else {
|
|
_, err = io.WriteString(w.writer, "[\n")
|
|
w.firstRowWritten = true
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
rowBytes, err := json.Marshal(row)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = w.writer.Write(rowBytes)
|
|
return err
|
|
}
|
|
|
|
func (w *JSONTestWriter) Close() error {
|
|
if !w.firstRowWritten {
|
|
return nil
|
|
}
|
|
|
|
_, err := io.WriteString(w.writer, "\n]\n")
|
|
return err
|
|
}
|
|
|
|
func fetchPrevOutputScripts(client *rpcclient.Client, block *wire.MsgBlock) ([][]byte, error) {
|
|
var prevScripts [][]byte
|
|
|
|
txCache := make(map[chainhash.Hash]*wire.MsgTx)
|
|
for _, tx := range block.Transactions {
|
|
if blockchain.IsCoinBaseTx(tx) {
|
|
continue
|
|
}
|
|
|
|
for _, txIn := range tx.TxIn {
|
|
prevOp := txIn.PreviousOutPoint
|
|
|
|
tx, ok := txCache[prevOp.Hash]
|
|
if !ok {
|
|
originTx, err := client.GetRawTransaction(
|
|
&prevOp.Hash,
|
|
)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("unable to get "+
|
|
"txid=%v: %v", prevOp.Hash, err)
|
|
}
|
|
|
|
txCache[prevOp.Hash] = originTx.MsgTx()
|
|
|
|
tx = originTx.MsgTx()
|
|
}
|
|
|
|
index := prevOp.Index
|
|
|
|
prevScripts = append(
|
|
prevScripts, tx.TxOut[index].PkScript,
|
|
)
|
|
}
|
|
}
|
|
|
|
return prevScripts, nil
|
|
}
|
|
|
|
func main() {
|
|
var (
|
|
writerFile *JSONTestWriter
|
|
prevBasicHeader chainhash.Hash
|
|
)
|
|
fName := fmt.Sprintf("testnet-%02d.json", fp)
|
|
file, err := os.Create(fName)
|
|
if err != nil {
|
|
fmt.Println("Error creating output file: ", err.Error())
|
|
return
|
|
}
|
|
defer file.Close()
|
|
|
|
writer := &JSONTestWriter{
|
|
writer: file,
|
|
}
|
|
defer writer.Close()
|
|
|
|
err = writer.WriteComment("Block Height,Block Hash,Block," +
|
|
"[Prev Output Scripts for Block],Previous Basic Header," +
|
|
"Basic Filter,Basic Header,Notes")
|
|
if err != nil {
|
|
fmt.Println("Error writing to output file: ", err.Error())
|
|
return
|
|
}
|
|
|
|
writerFile = writer
|
|
|
|
cert, err := ioutil.ReadFile(defaultBtcdRPCCertFile)
|
|
if err != nil {
|
|
fmt.Println("Couldn't read RPC cert: ", err.Error())
|
|
return
|
|
}
|
|
|
|
conf := rpcclient.ConnConfig{
|
|
Host: "127.0.0.1:18334",
|
|
Endpoint: "ws",
|
|
User: "kek",
|
|
Pass: "kek",
|
|
Certificates: cert,
|
|
}
|
|
client, err := rpcclient.New(&conf, nil)
|
|
if err != nil {
|
|
fmt.Println("Couldn't create a new client: ", err.Error())
|
|
return
|
|
}
|
|
|
|
var testBlockIndex int
|
|
for height := 0; testBlockIndex < len(testBlockHeights); height++ {
|
|
blockHash, err := client.GetBlockHash(int64(height))
|
|
if err != nil {
|
|
fmt.Println("Couldn't get block hash: ", err.Error())
|
|
return
|
|
}
|
|
|
|
block, err := client.GetBlock(blockHash)
|
|
if err != nil {
|
|
fmt.Println("Couldn't get block hash: ", err.Error())
|
|
return
|
|
}
|
|
|
|
var blockBuf bytes.Buffer
|
|
err = block.Serialize(&blockBuf)
|
|
if err != nil {
|
|
fmt.Println("Error serializing block to buffer: ", err.Error())
|
|
return
|
|
}
|
|
blockBytes := blockBuf.Bytes()
|
|
|
|
prevOutputScripts, err := fetchPrevOutputScripts(client, block)
|
|
if err != nil {
|
|
fmt.Println("Couldn't fetch prev output scipts: ", err)
|
|
return
|
|
}
|
|
|
|
basicFilter, err := builder.BuildBasicFilter(block, prevOutputScripts)
|
|
if err != nil {
|
|
fmt.Println("Error generating basic filter: ", err.Error())
|
|
return
|
|
}
|
|
basicHeader, err := builder.MakeHeaderForFilter(basicFilter, prevBasicHeader)
|
|
if err != nil {
|
|
fmt.Println("Error generating header for filter: ", err.Error())
|
|
return
|
|
}
|
|
|
|
// We'll now ensure that we've constructed the same filter as
|
|
// the chain server we're fetching blocks form.
|
|
filter, err := client.GetCFilter(
|
|
blockHash, wire.GCSFilterRegular,
|
|
)
|
|
if err != nil {
|
|
fmt.Println("Error getting basic filter: ",
|
|
err.Error())
|
|
return
|
|
}
|
|
|
|
nBytes, err := basicFilter.NBytes()
|
|
if err != nil {
|
|
fmt.Println("Couldn't get NBytes(): ", err)
|
|
return
|
|
}
|
|
if !bytes.Equal(filter.Data, nBytes) {
|
|
// Don't error on empty filters
|
|
fmt.Printf("basic filter doesn't match: generated "+
|
|
"%x, rpc returns %x, block %v", nBytes,
|
|
filter.Data, spew.Sdump(block))
|
|
return
|
|
}
|
|
|
|
header, err := client.GetCFilterHeader(
|
|
blockHash, wire.GCSFilterRegular,
|
|
)
|
|
if err != nil {
|
|
fmt.Println("Error getting basic header: ", err.Error())
|
|
return
|
|
}
|
|
if !bytes.Equal(header.PrevFilterHeader[:], basicHeader[:]) {
|
|
fmt.Println("Basic header doesn't match!")
|
|
return
|
|
}
|
|
|
|
if height%1000 == 0 {
|
|
fmt.Printf("Verified height %v against server\n", height)
|
|
}
|
|
|
|
if uint32(height) == testBlockHeights[testBlockIndex].height {
|
|
var bfBytes []byte
|
|
bfBytes, err = basicFilter.NBytes()
|
|
if err != nil {
|
|
fmt.Println("Couldn't get NBytes(): ", err)
|
|
return
|
|
}
|
|
|
|
prevScriptStrings := make([]string, len(prevOutputScripts))
|
|
for i, prevScript := range prevOutputScripts {
|
|
prevScriptStrings[i] = hex.EncodeToString(prevScript)
|
|
}
|
|
|
|
row := []interface{}{
|
|
height,
|
|
blockHash.String(),
|
|
hex.EncodeToString(blockBytes),
|
|
prevScriptStrings,
|
|
prevBasicHeader.String(),
|
|
hex.EncodeToString(bfBytes),
|
|
basicHeader.String(),
|
|
testBlockHeights[testBlockIndex].comment,
|
|
}
|
|
err = writerFile.WriteTestCase(row)
|
|
if err != nil {
|
|
fmt.Println("Error writing test case to output: ", err.Error())
|
|
return
|
|
}
|
|
}
|
|
|
|
prevBasicHeader = basicHeader
|
|
|
|
if uint32(height) == testBlockHeights[testBlockIndex].height {
|
|
testBlockIndex++
|
|
}
|
|
}
|
|
}
|