From 3dbade630117144b00961fed475613b7e96ed0ca Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Fri, 4 Nov 2022 15:11:00 +1100 Subject: [PATCH 1/3] init --- testutil/datagen/btc_blockchain.go | 97 +++++++++++++++++++++++++++++ testutil/datagen/btc_transaction.go | 96 +++++++++++++++++++++++++++- testutil/datagen/raw_checkpoint.go | 15 ++++- 3 files changed, 206 insertions(+), 2 deletions(-) create mode 100644 testutil/datagen/btc_blockchain.go diff --git a/testutil/datagen/btc_blockchain.go b/testutil/datagen/btc_blockchain.go new file mode 100644 index 000000000..368342960 --- /dev/null +++ b/testutil/datagen/btc_blockchain.go @@ -0,0 +1,97 @@ +package datagen + +import ( + "math/big" + "math/rand" + + "github.com/babylonchain/babylon/btctxformatter" + "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" +) + +func GenRandomBlock(numBabylonTxs int, prevHash *chainhash.Hash) (*wire.MsgBlock, *btctxformatter.RawBtcCheckpoint) { + var ( + randomTxs []*wire.MsgTx + rawCkpt *btctxformatter.RawBtcCheckpoint + ) + + if numBabylonTxs == 2 { + randomTxs, rawCkpt = GenRandomBabylonTxPair() + } else if numBabylonTxs == 1 { + randomTxs, _ = GenRandomBabylonTxPair() + randomTxs[1] = GenRandomTx() + rawCkpt = nil + } else { + randomTxs = []*wire.MsgTx{GenRandomTx(), GenRandomTx()} + rawCkpt = nil + } + coinbaseTx := createCoinbaseTx(rand.Int31(), &chaincfg.SimNetParams) + msgTxs := []*wire.MsgTx{coinbaseTx} + msgTxs = append(msgTxs, randomTxs...) + + // calculate correct Merkle root + merkleRoot := calcMerkleRoot(msgTxs) + // don't apply any difficulty + difficulty, _ := new(big.Int).SetString("7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16) + workBits := blockchain.BigToCompact(difficulty) + + header := GenRandomBtcdHeader() + header.MerkleRoot = merkleRoot + header.Bits = workBits + if prevHash == nil { + header.PrevBlock = chainhash.DoubleHashH(GenRandomByteArray(10)) + } else { + header.PrevBlock = *prevHash + } + // find a nonce that satisfies difficulty + SolveBlock(header) + + block := &wire.MsgBlock{ + Header: *header, + Transactions: msgTxs, + } + return block, rawCkpt +} + +func GenRandomBlockchainWithBabylonTx(n uint64, partialPercentage float32, fullPercentage float32) ([]*wire.MsgBlock, int, []*btctxformatter.RawBtcCheckpoint) { + blocks := []*wire.MsgBlock{} + numCkptSegs := 0 + rawCkpts := []*btctxformatter.RawBtcCheckpoint{} + // percentage should be [0, 1] + if partialPercentage < 0 || partialPercentage > 1 { + return blocks, 0, rawCkpts + } + if fullPercentage < 0 || fullPercentage > 1 { + return blocks, 0, rawCkpts + } + // n should be > 0 + if n == 0 { + return blocks, 0, rawCkpts + } + + // genesis block + genesisBlock, rawCkpt := GenRandomBlock(0, nil) + blocks = append(blocks, genesisBlock) + rawCkpts = append(rawCkpts, rawCkpt) + + // blocks after genesis + for i := uint64(1); i < n; i++ { + var msgBlock *wire.MsgBlock + prevHash := blocks[len(blocks)-1].BlockHash() + if rand.Float32() < partialPercentage { + msgBlock, rawCkpt = GenRandomBlock(1, &prevHash) + numCkptSegs += 1 + } else if rand.Float32() < partialPercentage+fullPercentage { + msgBlock, rawCkpt = GenRandomBlock(2, &prevHash) + numCkptSegs += 2 + } else { + msgBlock, rawCkpt = GenRandomBlock(0, &prevHash) + } + + blocks = append(blocks, msgBlock) + rawCkpts = append(rawCkpts, rawCkpt) + } + return blocks, numCkptSegs, rawCkpts +} diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index 9255c8ff1..54ef0819a 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -9,6 +9,7 @@ import ( "runtime" "time" + "github.com/babylonchain/babylon/btctxformatter" bbn "github.com/babylonchain/babylon/types" btcctypes "github.com/babylonchain/babylon/x/btccheckpoint/types" "github.com/btcsuite/btcd/blockchain" @@ -315,7 +316,7 @@ func CreateBlockWithTransaction( proof, err := btcctypes.SpvProofFromHeaderAndTransactions(headerBytes.MustMarshal(), txBytes, 1) if err != nil { - panic("couldnt calculate prroof") + panic("could not calculate proof") } return &BtcHeaderWithProof{ @@ -323,3 +324,96 @@ func CreateBlockWithTransaction( SpvProof: proof, } } + +func GenRandomTx() *wire.MsgTx { + // structure of the below tx is from https://github.com/btcsuite/btcd/blob/master/wire/msgtx_test.go + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: chainhash.HashH(GenRandomByteArray(10)), + Index: rand.Uint32(), + }, + SignatureScript: GenRandomByteArray(10), + Sequence: rand.Uint32(), + }, + }, + TxOut: []*wire.TxOut{ + { + Value: rand.Int63(), + PkScript: GenRandomByteArray(80), + }, + }, + LockTime: 0, + } + + return tx +} + +func GenRandomBabylonTxPair() ([]*wire.MsgTx, *btctxformatter.RawBtcCheckpoint) { + txs := []*wire.MsgTx{GenRandomTx(), GenRandomTx()} + builder := txscript.NewScriptBuilder() + + // fake a raw checkpoint + rawBTCCkpt := GetRandomRawBtcCheckpoint() + // encode raw checkpoint to two halves + firstHalf, secondHalf, err := btctxformatter.EncodeCheckpointData( + btctxformatter.TestTag(48), + btctxformatter.CurrentVersion, + rawBTCCkpt, + ) + if err != nil { + panic(err) + } + + dataScript1, err := builder.AddOp(txscript.OP_RETURN).AddData(firstHalf).Script() + if err != nil { + panic(err) + } + txs[0].TxOut[0] = wire.NewTxOut(0, dataScript1) + + // reset builder + builder = txscript.NewScriptBuilder() + + dataScript2, err := builder.AddOp(txscript.OP_RETURN).AddData(secondHalf).Script() + if err != nil { + panic(err) + } + txs[1].TxOut[0] = wire.NewTxOut(0, dataScript2) + + return txs, rawBTCCkpt +} + +func GenRandomBabylonTx() *wire.MsgTx { + tx := GenRandomTx() + builder := txscript.NewScriptBuilder() + + // fake a raw checkpoint + rawBTCCkpt := GetRandomRawBtcCheckpoint() + // encode raw checkpoint to two halves + firstHalf, secondHalf, err := btctxformatter.EncodeCheckpointData( + btctxformatter.TestTag(48), + btctxformatter.CurrentVersion, + rawBTCCkpt, + ) + if err != nil { + panic(err) + } + idx := rand.Intn(2) + if idx == 0 { + dataScript, err := builder.AddOp(txscript.OP_RETURN).AddData(firstHalf).Script() + if err != nil { + panic(err) + } + tx.TxOut[0] = wire.NewTxOut(0, dataScript) + } else { + dataScript, err := builder.AddOp(txscript.OP_RETURN).AddData(secondHalf).Script() + if err != nil { + panic(err) + } + tx.TxOut[0] = wire.NewTxOut(0, dataScript) + } + + return tx +} diff --git a/testutil/datagen/raw_checkpoint.go b/testutil/datagen/raw_checkpoint.go index 97dd93930..c99ba2633 100644 --- a/testutil/datagen/raw_checkpoint.go +++ b/testutil/datagen/raw_checkpoint.go @@ -1,12 +1,25 @@ package datagen import ( + "math/rand" + + "github.com/babylonchain/babylon/btctxformatter" "github.com/babylonchain/babylon/crypto/bls12381" "github.com/babylonchain/babylon/x/checkpointing/types" "github.com/boljen/go-bitmap" - "math/rand" ) +func GetRandomRawBtcCheckpoint() *btctxformatter.RawBtcCheckpoint { + rawCkpt := GenRandomRawCheckpoint() + return &btctxformatter.RawBtcCheckpoint{ + Epoch: rawCkpt.EpochNum, + LastCommitHash: *rawCkpt.LastCommitHash, + BitMap: rawCkpt.Bitmap, + SubmitterAddress: GenRandomByteArray(btctxformatter.AddressLength), + BlsSig: rawCkpt.BlsMultiSig.Bytes(), + } +} + func GenRandomRawCheckpointWithMeta() *types.RawCheckpointWithMeta { ckptWithMeta := &types.RawCheckpointWithMeta{ Ckpt: GenRandomRawCheckpoint(), From 8379779fbdee772829671414f169abb118b31067 Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 7 Nov 2022 10:50:51 +1100 Subject: [PATCH 2/3] resolve comments --- testutil/datagen/btc_blockchain.go | 52 +++++++++++++++++++---------- testutil/datagen/btc_transaction.go | 35 +++---------------- 2 files changed, 38 insertions(+), 49 deletions(-) diff --git a/testutil/datagen/btc_blockchain.go b/testutil/datagen/btc_blockchain.go index 368342960..31147a7bb 100644 --- a/testutil/datagen/btc_blockchain.go +++ b/testutil/datagen/btc_blockchain.go @@ -11,7 +11,13 @@ import ( "github.com/btcsuite/btcd/wire" ) -func GenRandomBlock(numBabylonTxs int, prevHash *chainhash.Hash) (*wire.MsgBlock, *btctxformatter.RawBtcCheckpoint) { +// GenRandomBtcdBlock generates a random BTC block, which can include Babylon txs. +// Specifically, +// - when numBabylonTxs == 0 or numBabylonTxs > 2, it generates a BTC block with 3 random txs. +// - when numBabylonTxs == 1, it generates a BTC block with 2 random txs and a Babylon tx. +// - when numBabylonTxs == 2, it generates a BTC block with 1 random tx and 2 Babylon txs that constitute a raw BTC checkpoint. +// When numBabylonTxs == 2, the function will return the BTC raw checkpoint as well. +func GenRandomBtcdBlock(numBabylonTxs int, prevHash *chainhash.Hash) (*wire.MsgBlock, *btctxformatter.RawBtcCheckpoint) { var ( randomTxs []*wire.MsgTx rawCkpt *btctxformatter.RawBtcCheckpoint @@ -40,9 +46,7 @@ func GenRandomBlock(numBabylonTxs int, prevHash *chainhash.Hash) (*wire.MsgBlock header := GenRandomBtcdHeader() header.MerkleRoot = merkleRoot header.Bits = workBits - if prevHash == nil { - header.PrevBlock = chainhash.DoubleHashH(GenRandomByteArray(10)) - } else { + if prevHash != nil { header.PrevBlock = *prevHash } // find a nonce that satisfies difficulty @@ -55,24 +59,27 @@ func GenRandomBlock(numBabylonTxs int, prevHash *chainhash.Hash) (*wire.MsgBlock return block, rawCkpt } -func GenRandomBlockchainWithBabylonTx(n uint64, partialPercentage float32, fullPercentage float32) ([]*wire.MsgBlock, int, []*btctxformatter.RawBtcCheckpoint) { +// GenRandomBtcdBlockchainWithBabylonTx generates a blockchain of `n` blocks, in which each block has some probability of including some Babylon txs +// Specifically, each block +// - has `oneTxThreshold` probability of including 1 Babylon tx that does not has any match +// - has `twoTxThreshold - oneTxThreshold` probability of including 2 Babylon txs that constitute a checkpoint +// - has `1 - twoTxThreshold` probability of including no Babylon tx +func GenRandomBtcdBlockchainWithBabylonTx(n uint64, oneTxThreshold float32, twoTxThreshold float32) ([]*wire.MsgBlock, int, []*btctxformatter.RawBtcCheckpoint) { blocks := []*wire.MsgBlock{} numCkptSegs := 0 rawCkpts := []*btctxformatter.RawBtcCheckpoint{} - // percentage should be [0, 1] - if partialPercentage < 0 || partialPercentage > 1 { - return blocks, 0, rawCkpts + if oneTxThreshold < 0 || oneTxThreshold > 1 { + panic("oneTxThreshold should be [0, 1]") } - if fullPercentage < 0 || fullPercentage > 1 { - return blocks, 0, rawCkpts + if twoTxThreshold < oneTxThreshold || twoTxThreshold > 1 { + panic("fullPercentage should be [oneTxThreshold, 1]") } - // n should be > 0 if n == 0 { - return blocks, 0, rawCkpts + panic("n should be > 0") } // genesis block - genesisBlock, rawCkpt := GenRandomBlock(0, nil) + genesisBlock, rawCkpt := GenRandomBtcdBlock(0, nil) blocks = append(blocks, genesisBlock) rawCkpts = append(rawCkpts, rawCkpt) @@ -80,14 +87,14 @@ func GenRandomBlockchainWithBabylonTx(n uint64, partialPercentage float32, fullP for i := uint64(1); i < n; i++ { var msgBlock *wire.MsgBlock prevHash := blocks[len(blocks)-1].BlockHash() - if rand.Float32() < partialPercentage { - msgBlock, rawCkpt = GenRandomBlock(1, &prevHash) + if rand.Float32() < oneTxThreshold { + msgBlock, rawCkpt = GenRandomBtcdBlock(1, &prevHash) numCkptSegs += 1 - } else if rand.Float32() < partialPercentage+fullPercentage { - msgBlock, rawCkpt = GenRandomBlock(2, &prevHash) + } else if rand.Float32() < twoTxThreshold { + msgBlock, rawCkpt = GenRandomBtcdBlock(2, &prevHash) numCkptSegs += 2 } else { - msgBlock, rawCkpt = GenRandomBlock(0, &prevHash) + msgBlock, rawCkpt = GenRandomBtcdBlock(0, &prevHash) } blocks = append(blocks, msgBlock) @@ -95,3 +102,12 @@ func GenRandomBlockchainWithBabylonTx(n uint64, partialPercentage float32, fullP } return blocks, numCkptSegs, rawCkpts } + +// GenRandomBtcdHash generates a random hash in type `chainhash.Hash`, without any hash operations +func GenRandomBtcdHash() chainhash.Hash { + hash, err := chainhash.NewHashFromStr(GenRandomHexStr(32)) + if err != nil { + panic(err) + } + return *hash +} diff --git a/testutil/datagen/btc_transaction.go b/testutil/datagen/btc_transaction.go index 54ef0819a..1dce03635 100644 --- a/testutil/datagen/btc_transaction.go +++ b/testutil/datagen/btc_transaction.go @@ -332,7 +332,7 @@ func GenRandomTx() *wire.MsgTx { TxIn: []*wire.TxIn{ { PreviousOutPoint: wire.OutPoint{ - Hash: chainhash.HashH(GenRandomByteArray(10)), + Hash: GenRandomBtcdHash(), Index: rand.Uint32(), }, SignatureScript: GenRandomByteArray(10), @@ -359,7 +359,7 @@ func GenRandomBabylonTxPair() ([]*wire.MsgTx, *btctxformatter.RawBtcCheckpoint) rawBTCCkpt := GetRandomRawBtcCheckpoint() // encode raw checkpoint to two halves firstHalf, secondHalf, err := btctxformatter.EncodeCheckpointData( - btctxformatter.TestTag(48), + btctxformatter.TestTag(48), // TODO: randomise the tag ID btctxformatter.CurrentVersion, rawBTCCkpt, ) @@ -386,34 +386,7 @@ func GenRandomBabylonTxPair() ([]*wire.MsgTx, *btctxformatter.RawBtcCheckpoint) } func GenRandomBabylonTx() *wire.MsgTx { - tx := GenRandomTx() - builder := txscript.NewScriptBuilder() - - // fake a raw checkpoint - rawBTCCkpt := GetRandomRawBtcCheckpoint() - // encode raw checkpoint to two halves - firstHalf, secondHalf, err := btctxformatter.EncodeCheckpointData( - btctxformatter.TestTag(48), - btctxformatter.CurrentVersion, - rawBTCCkpt, - ) - if err != nil { - panic(err) - } + txs, _ := GenRandomBabylonTxPair() idx := rand.Intn(2) - if idx == 0 { - dataScript, err := builder.AddOp(txscript.OP_RETURN).AddData(firstHalf).Script() - if err != nil { - panic(err) - } - tx.TxOut[0] = wire.NewTxOut(0, dataScript) - } else { - dataScript, err := builder.AddOp(txscript.OP_RETURN).AddData(secondHalf).Script() - if err != nil { - panic(err) - } - tx.TxOut[0] = wire.NewTxOut(0, dataScript) - } - - return tx + return txs[idx] } From f99a2010b857fc994c0dbdc242198d8af6e1221e Mon Sep 17 00:00:00 2001 From: Runchao Han Date: Mon, 7 Nov 2022 10:56:38 +1100 Subject: [PATCH 3/3] minor --- testutil/datagen/btc_blockchain.go | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/testutil/datagen/btc_blockchain.go b/testutil/datagen/btc_blockchain.go index 31147a7bb..efc3ff7bb 100644 --- a/testutil/datagen/btc_blockchain.go +++ b/testutil/datagen/btc_blockchain.go @@ -19,19 +19,16 @@ import ( // When numBabylonTxs == 2, the function will return the BTC raw checkpoint as well. func GenRandomBtcdBlock(numBabylonTxs int, prevHash *chainhash.Hash) (*wire.MsgBlock, *btctxformatter.RawBtcCheckpoint) { var ( - randomTxs []*wire.MsgTx - rawCkpt *btctxformatter.RawBtcCheckpoint + randomTxs []*wire.MsgTx = []*wire.MsgTx{GenRandomTx(), GenRandomTx()} + rawCkpt *btctxformatter.RawBtcCheckpoint = nil ) if numBabylonTxs == 2 { randomTxs, rawCkpt = GenRandomBabylonTxPair() } else if numBabylonTxs == 1 { - randomTxs, _ = GenRandomBabylonTxPair() - randomTxs[1] = GenRandomTx() - rawCkpt = nil - } else { - randomTxs = []*wire.MsgTx{GenRandomTx(), GenRandomTx()} - rawCkpt = nil + bbnTxs, _ := GenRandomBabylonTxPair() + idx := rand.Intn(2) + randomTxs[idx] = bbnTxs[idx] } coinbaseTx := createCoinbaseTx(rand.Int31(), &chaincfg.SimNetParams) msgTxs := []*wire.MsgTx{coinbaseTx}