kaspad/blockdag/test_utils.go
Evgeny Khirin e76ce06acd [DEV-337] enusre transaction not overuse gas (#152)
* [DEV-312] Take in account subnetwork's GAS limit, when adding
transactions to block. Try to do that optimally.

* [DEV-312] Fixed GAS overusage calculation

* [DEV-337] Make sure that a transaction that uses more gas than the total allowed for sub-network

* [DEV-337] Moved transaction GAS check to mempool

* [DEV-337] Added Unit test for gas usage in transaction

* [DEV-337] Fixed build

* [DEV-337] Fixed tests stuff

* [DEV-337] Removed TODO comment
2019-01-09 15:54:48 +02:00

233 lines
6.9 KiB
Go

package blockdag
import (
"encoding/binary"
"fmt"
"os"
"path/filepath"
"time"
"github.com/daglabs/btcd/dagconfig"
"github.com/daglabs/btcd/dagconfig/daghash"
"github.com/daglabs/btcd/database"
_ "github.com/daglabs/btcd/database/ffldb" // blank import ffldb so that its init() function runs before tests
"github.com/daglabs/btcd/txscript"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/wire"
)
const (
// testDbType is the database backend type to use for the tests.
testDbType = "ffldb"
// testDbRoot is the root directory used to create all test databases.
testDbRoot = "testdbs"
// blockDataNet is the expected network in the test block data.
blockDataNet = wire.MainNet
)
// isSupportedDbType returns whether or not the passed database type is
// currently supported.
func isSupportedDbType(dbType string) bool {
supportedDrivers := database.SupportedDrivers()
for _, driver := range supportedDrivers {
if dbType == driver {
return true
}
}
return false
}
// filesExists returns whether or not the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// DAGSetup is used to create a new db and chain instance with the genesis
// block already inserted. In addition to the new chain instance, it returns
// a teardown function the caller should invoke when done testing to clean up.
func DAGSetup(dbName string, config Config) (*BlockDAG, func(), error) {
if !isSupportedDbType(testDbType) {
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
}
var teardown func()
if config.DB == nil {
// Create the root directory for test databases.
if !fileExists(testDbRoot) {
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
err := fmt.Errorf("unable to create test db "+
"root: %v", err)
return nil, nil, err
}
}
dbPath := filepath.Join(testDbRoot, dbName)
_ = os.RemoveAll(dbPath)
var err error
config.DB, err = database.Create(testDbType, dbPath, blockDataNet)
if err != nil {
return nil, nil, fmt.Errorf("error creating db: %v", err)
}
// Setup a teardown function for cleaning up. This function is
// returned to the caller to be invoked when it is done testing.
teardown = func() {
config.DB.Close()
os.RemoveAll(dbPath)
os.RemoveAll(testDbRoot)
}
}
config.TimeSource = NewMedianTime()
config.SigCache = txscript.NewSigCache(1000)
// Create the DAG instance.
dag, err := New(&config)
if err != nil {
teardown()
err := fmt.Errorf("failed to create dag instance: %v", err)
return nil, nil, err
}
return dag, teardown, nil
}
// OpTrueScript is script returning TRUE
var OpTrueScript = []byte{txscript.OpTrue}
// createCoinbaseTxForTest returns a coinbase transaction with the requested number of
// outputs paying an appropriate subsidy based on the passed block height to the
// address associated with the harness. It automatically uses a standard
// signature script that starts with the block height
func createCoinbaseTxForTest(blockHeight int32, numOutputs uint32, extraNonce int64, params *dagconfig.Params) (*wire.MsgTx, error) {
// Create standard coinbase script.
coinbaseScript, err := txscript.NewScriptBuilder().
AddInt64(int64(blockHeight)).AddInt64(extraNonce).Script()
if err != nil {
return nil, err
}
tx := wire.NewMsgTx(wire.TxVersion)
tx.AddTxIn(&wire.TxIn{
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
PreviousOutPoint: *wire.NewOutPoint(&daghash.Hash{},
wire.MaxPrevOutIndex),
SignatureScript: coinbaseScript,
Sequence: wire.MaxTxInSequenceNum,
})
totalInput := CalcBlockSubsidy(blockHeight, params)
amountPerOutput := totalInput / uint64(numOutputs)
remainder := totalInput - amountPerOutput*uint64(numOutputs)
for i := uint32(0); i < numOutputs; i++ {
// Ensure the final output accounts for any remainder that might
// be left from splitting the input amount.
amount := amountPerOutput
if i == numOutputs-1 {
amount = amountPerOutput + remainder
}
tx.AddTxOut(&wire.TxOut{
PkScript: OpTrueScript,
Value: amount,
})
}
return tx, nil
}
// RegisterSubnetworkForTest is used to register network on DAG with specified GAS limit.
func RegisterSubnetworkForTest(dag *BlockDAG, gasLimit uint64) (subNetworkID uint64, err error) {
blockTime := time.Unix(dag.genesis.timestamp, 0)
extraNonce := int64(0)
buildNextBlock := func(parents blockSet, txs []*wire.MsgTx) (*util.Block, error) {
// We need to change the blockTime to keep all block hashes unique
blockTime = blockTime.Add(time.Second)
// We need to change the extraNonce to keep coinbase hashes unique
extraNonce++
bh := &wire.BlockHeader{
Version: 1,
Bits: dag.genesis.bits,
ParentHashes: parents.hashes(),
Timestamp: blockTime,
}
msgBlock := wire.NewMsgBlock(bh)
blockHeight := parents.maxHeight() + 1
coinbaseTx, err := createCoinbaseTxForTest(blockHeight, 1, extraNonce, dag.dagParams)
if err != nil {
return nil, err
}
_ = msgBlock.AddTransaction(coinbaseTx)
for _, tx := range txs {
_ = msgBlock.AddTransaction(tx)
}
return util.NewBlock(msgBlock), nil
}
addBlockToDAG := func(block *util.Block) (*blockNode, error) {
dag.dagLock.Lock()
defer dag.dagLock.Unlock()
err = dag.maybeAcceptBlock(block, BFNone)
if err != nil {
return nil, err
}
return dag.index.LookupNode(block.Hash()), nil
}
currentNode := dag.genesis
// Create a block with a valid sub-network registry transaction
registryTx := wire.NewMsgTx(wire.TxVersion)
registryTx.SubNetworkID = wire.SubNetworkRegistry
registryTx.Payload = make([]byte, 8)
binary.LittleEndian.PutUint64(registryTx.Payload, gasLimit)
// Add it to the DAG
registryBlock, err := buildNextBlock(setFromSlice(currentNode), []*wire.MsgTx{registryTx})
if err != nil {
return 0, fmt.Errorf("could not build registry block: %s", err)
}
currentNode, err = addBlockToDAG(registryBlock)
if err != nil {
return 0, fmt.Errorf("could not add registry block to DAG: %s", err)
}
// Add 2*finalityInterval to ensure the registry transaction is finalized
for currentNode.blueScore < 2*finalityInterval {
nextBlock, err := buildNextBlock(setFromSlice(currentNode), []*wire.MsgTx{})
if err != nil {
return 0, fmt.Errorf("could not create block: %s", err)
}
currentNode, err = addBlockToDAG(nextBlock)
if err != nil {
return 0, fmt.Errorf("could not add block to DAG: %s", err)
}
}
// Make sure that the sub-network had been successfully registered by trying
// to retrieve its gas limit.
mostRecentlyRegisteredSubNetworkID := dag.lastSubNetworkID - 1
limit, err := dag.GasLimit(mostRecentlyRegisteredSubNetworkID)
if err != nil {
return 0, fmt.Errorf("could not retrieve gas limit: %s", err)
}
if limit != gasLimit {
return 0, fmt.Errorf("unexpected gas limit. want: %d, got: %d", gasLimit, limit)
}
return mostRecentlyRegisteredSubNetworkID, nil
}