diff --git a/testutil/datagen/btc_blockchain.go b/testutil/datagen/btc_blockchain.go new file mode 100644 index 000000000..efc3ff7bb --- /dev/null +++ b/testutil/datagen/btc_blockchain.go @@ -0,0 +1,110 @@ +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" +) + +// 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 = []*wire.MsgTx{GenRandomTx(), GenRandomTx()} + rawCkpt *btctxformatter.RawBtcCheckpoint = nil + ) + + if numBabylonTxs == 2 { + randomTxs, rawCkpt = GenRandomBabylonTxPair() + } else if numBabylonTxs == 1 { + bbnTxs, _ := GenRandomBabylonTxPair() + idx := rand.Intn(2) + randomTxs[idx] = bbnTxs[idx] + } + 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 = *prevHash + } + // find a nonce that satisfies difficulty + SolveBlock(header) + + block := &wire.MsgBlock{ + Header: *header, + Transactions: msgTxs, + } + return block, rawCkpt +} + +// 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{} + if oneTxThreshold < 0 || oneTxThreshold > 1 { + panic("oneTxThreshold should be [0, 1]") + } + if twoTxThreshold < oneTxThreshold || twoTxThreshold > 1 { + panic("fullPercentage should be [oneTxThreshold, 1]") + } + if n == 0 { + panic("n should be > 0") + } + + // genesis block + genesisBlock, rawCkpt := GenRandomBtcdBlock(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() < oneTxThreshold { + msgBlock, rawCkpt = GenRandomBtcdBlock(1, &prevHash) + numCkptSegs += 1 + } else if rand.Float32() < twoTxThreshold { + msgBlock, rawCkpt = GenRandomBtcdBlock(2, &prevHash) + numCkptSegs += 2 + } else { + msgBlock, rawCkpt = GenRandomBtcdBlock(0, &prevHash) + } + + blocks = append(blocks, msgBlock) + rawCkpts = append(rawCkpts, rawCkpt) + } + 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 9255c8ff1..1dce03635 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,69 @@ 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: GenRandomBtcdHash(), + 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), // TODO: randomise the tag ID + 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 { + txs, _ := GenRandomBabylonTxPair() + idx := rand.Intn(2) + return txs[idx] +} 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(),