1
0
Fork 0
mirror of https://github.com/bitcoin/bips.git synced 2025-02-24 23:38:54 +01:00
bitcoin-bips/bip-0158/gentestvectors.go
Olaoluwa Osuntokun ac76644533
BIP-0158: regenerate test vectors for fp=19, new reg filter, no ext filter
In this commit, we simplify the code that generates the test vectors to
only generate filters for a target fp of 19, and also only for the
regular filter, as it's the only filter type currently defined.

The test vectors have also been updated to include the previous output
scripts for all input within a block as these are now required to
construct the regular filter.

Finally, the generation code has been updated to properly fetch the
previous input scripts to the generation code can verify the filter it
generates manually against the end server.
2018-07-04 23:19:16 -05:00

297 lines
6.9 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, ""},
{926485, "Duplicate pushdata 913bcc2be49cb534c20474c4dee1e9c4c317e7eb"},
{987876, "Coinbase tx has unparseable output script"},
{1263442, "Includes witness 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++
}
}
}