232 lines
6.3 KiB
Go
232 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
|
|
}
|