summaryrefslogtreecommitdiff
path: root/bip-0158/gentestvectors.go
diff options
context:
space:
mode:
Diffstat (limited to 'bip-0158/gentestvectors.go')
-rw-r--r--bip-0158/gentestvectors.go301
1 files changed, 301 insertions, 0 deletions
diff --git a/bip-0158/gentestvectors.go b/bip-0158/gentestvectors.go
new file mode 100644
index 0000000..3435eb3
--- /dev/null
+++ b/bip-0158/gentestvectors.go
@@ -0,0 +1,301 @@
+// 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++
+ }
+ }
+}