lbcwallet/chain/utils_test.go
Wilmer Paulino 178d124b76
chain: add PrunedBlockDispatcher implementation
To minimally support wallets connected to pruned nodes, we add a new
subsystem that can be integrated with chain clients to request blocks
that the server has already pruned. This is done by connecting to the
server's full node peers and querying them directly. Ideally, this is a
capability supported by the server, though this is not yet possible with
bitcoind.
2021-04-02 17:28:05 -07:00

229 lines
6.2 KiB
Go

package chain
import (
"fmt"
"io"
"math"
"net"
"runtime"
"sync"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
// 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/btcsuite/btcd/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/btcsuite/btcd/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
}