lbcwallet/chain/utils_test.go
2022-08-08 01:26:02 -07:00

235 lines
6.3 KiB
Go

package chain
import (
"fmt"
"io"
"math"
"net"
"runtime"
"sync"
"time"
"github.com/lbryio/lbcd/blockchain"
"github.com/lbryio/lbcd/chaincfg"
"github.com/lbryio/lbcd/chaincfg/chainhash"
"github.com/lbryio/lbcd/wire"
btcutil "github.com/lbryio/lbcutil"
)
var chainParams = chaincfg.RegressionNetParams
// conn mocks a network connection by implementing the net.Conn interface. It is
// used to test peer connection without actually opening a network connection.
type conn struct {
io.Reader
io.Writer
io.Closer
localAddr string
remoteAddr string
}
func (c conn) LocalAddr() net.Addr {
return &addr{"tcp", c.localAddr}
}
func (c conn) RemoteAddr() net.Addr {
return &addr{"tcp", c.remoteAddr}
}
func (c conn) SetDeadline(t time.Time) error { return nil }
func (c conn) SetReadDeadline(t time.Time) error { return nil }
func (c conn) SetWriteDeadline(t time.Time) error { return nil }
// addr mocks a network address.
type addr struct {
net, address string
}
func (m addr) Network() string { return m.net }
func (m addr) String() string { return m.address }
// pipe turns two mock connections into a full-duplex connection similar to
// net.Pipe to allow pipe's with (fake) addresses.
func pipe(c1, c2 *conn) (*conn, *conn) {
r1, w1 := io.Pipe()
r2, w2 := io.Pipe()
c1.Writer = w1
c1.Closer = w1
c2.Reader = r1
c1.Reader = r2
c2.Writer = w2
c2.Closer = w2
return c1, c2
}
// calcMerkleRoot creates a merkle tree from the slice of transactions and
// returns the root of the tree.
//
// This function was copied from:
//
// https://github.com/lbryio/lbcd/blob/36a96f6a0025b6aeaebe4106821c2d46ee4be8d4/blockchain/fullblocktests/generate.go#L303
func calcMerkleRoot(txns []*wire.MsgTx) chainhash.Hash {
if len(txns) == 0 {
return chainhash.Hash{}
}
utilTxns := make([]*btcutil.Tx, 0, len(txns))
for _, tx := range txns {
utilTxns = append(utilTxns, btcutil.NewTx(tx))
}
merkles := blockchain.BuildMerkleTreeStore(utilTxns, false)
return *merkles[len(merkles)-1]
}
// solveBlock attempts to find a nonce which makes the passed block header hash
// to a value less than the target difficulty. When a successful solution is
// found true is returned and the nonce field of the passed header is updated
// with the solution. False is returned if no solution exists.
//
// This function was copied from:
//
// https://github.com/lbryio/lbcd/blob/36a96f6a0025b6aeaebe4106821c2d46ee4be8d4/blockchain/fullblocktests/generate.go#L324
func solveBlock(header *wire.BlockHeader) bool {
// sbResult is used by the solver goroutines to send results.
type sbResult struct {
found bool
nonce uint32
}
// Make sure all spawned goroutines finish executing before returning.
var wg sync.WaitGroup
defer func() {
wg.Wait()
}()
// solver accepts a block header and a nonce range to test. It is
// intended to be run as a goroutine.
targetDifficulty := blockchain.CompactToBig(header.Bits)
quit := make(chan bool)
results := make(chan sbResult)
solver := func(hdr wire.BlockHeader, startNonce, stopNonce uint32) {
defer wg.Done()
// We need to modify the nonce field of the header, so make sure
// we work with a copy of the original header.
for i := startNonce; i >= startNonce && i <= stopNonce; i++ {
select {
case <-quit:
return
default:
hdr.Nonce = i
hash := hdr.BlockHash()
if blockchain.HashToBig(&hash).Cmp(
targetDifficulty) <= 0 {
select {
case results <- sbResult{true, i}:
case <-quit:
}
return
}
}
}
select {
case results <- sbResult{false, 0}:
case <-quit:
}
}
startNonce := uint32(1)
stopNonce := uint32(math.MaxUint32)
numCores := uint32(runtime.NumCPU())
noncesPerCore := (stopNonce - startNonce) / numCores
wg.Add(int(numCores))
for i := uint32(0); i < numCores; i++ {
rangeStart := startNonce + (noncesPerCore * i)
rangeStop := startNonce + (noncesPerCore * (i + 1)) - 1
if i == numCores-1 {
rangeStop = stopNonce
}
go solver(*header, rangeStart, rangeStop)
}
for i := uint32(0); i < numCores; i++ {
result := <-results
if result.found {
close(quit)
header.Nonce = result.nonce
return true
}
}
return false
}
// genBlockChain generates a test chain with the given number of blocks.
func genBlockChain(numBlocks uint32) ([]*chainhash.Hash, map[chainhash.Hash]*wire.MsgBlock) {
prevHash := chainParams.GenesisHash
prevHeader := &chainParams.GenesisBlock.Header
hashes := make([]*chainhash.Hash, numBlocks)
blocks := make(map[chainhash.Hash]*wire.MsgBlock, numBlocks)
// Each block contains three transactions, including the coinbase
// transaction. Each non-coinbase transaction spends outputs from
// the previous block. We also need to produce blocks that succeed
// validation through blockchain.CheckBlockSanity.
script := []byte{0x01, 0x01}
createTx := func(prevOut wire.OutPoint) *wire.MsgTx {
return &wire.MsgTx{
TxIn: []*wire.TxIn{{
PreviousOutPoint: prevOut,
SignatureScript: script,
}},
TxOut: []*wire.TxOut{{PkScript: script}},
}
}
for i := uint32(0); i < numBlocks; i++ {
txs := []*wire.MsgTx{
createTx(wire.OutPoint{Index: wire.MaxPrevOutIndex}),
createTx(wire.OutPoint{Hash: *prevHash, Index: 0}),
createTx(wire.OutPoint{Hash: *prevHash, Index: 1}),
}
header := &wire.BlockHeader{
Version: 1,
PrevBlock: *prevHash,
MerkleRoot: calcMerkleRoot(txs),
Timestamp: prevHeader.Timestamp.Add(10 * time.Minute),
Bits: chainParams.PowLimitBits,
Nonce: 0,
}
if !solveBlock(header) {
panic(fmt.Sprintf("could not solve block at idx %v", i))
}
block := &wire.MsgBlock{
Header: *header,
Transactions: txs,
}
blockHash := block.BlockHash()
hashes[i] = &blockHash
blocks[blockHash] = block
prevHash = &blockHash
prevHeader = header
}
return hashes, blocks
}
// producesInvalidBlock produces a copy of the block that duplicates the last
// transaction. When the block has an odd number of transactions, this results
// in the invalid block maintaining the same hash as the valid block.
func produceInvalidBlock(block *wire.MsgBlock) *wire.MsgBlock {
numTxs := len(block.Transactions)
lastTx := block.Transactions[numTxs-1]
blockCopy := &wire.MsgBlock{
Header: block.Header,
Transactions: make([]*wire.MsgTx, numTxs),
}
copy(blockCopy.Transactions, block.Transactions)
blockCopy.AddTransaction(lastTx)
return blockCopy
}