[DEV-311] Enforce gas limit in blocks (#172)

* [DEV-311] Moved subnetwork storage from directly in DAG to subnetworkStore

* Added gas validation in CheckBlockSanity

* [DEV-311] Add SubnetworkStore to last remaining call for CheckBlockSanity

* [DEV-311] Added subnetworkID to config in TestcheckBlockSanity

* [DEV-311] Moved CheckBlockSanity to be method of BlockDAG, and removed subnetworkStore argument

* [DEV-311] Removed SubnetworkID as parameter to CheckBlockSanity

* [DEV-311] Update gas usage before

* [DEV-311] some chain=>DAG updates in comments

* [DEV-311] Removed remaining dag-specific parameters from checkBlockSanity
This commit is contained in:
Svarog 2019-01-24 17:25:11 +02:00 committed by Ori Newman
parent 6960e2469a
commit 9f93a1c50b
9 changed files with 83 additions and 31 deletions

View File

@ -147,6 +147,8 @@ type BlockDAG struct {
notifications []NotificationCallback
lastFinalityPoint *blockNode
SubnetworkStore *subnetworkStore
}
// HaveBlock returns whether or not the DAG instance has the block represented
@ -1482,6 +1484,7 @@ func New(config *Config) (*BlockDAG, error) {
warningCaches: newThresholdCaches(vbNumBits),
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
blockCount: 1,
SubnetworkStore: newSubnetworkStore(config.DB),
subnetworkID: config.SubnetworkID,
}

View File

@ -164,7 +164,7 @@ func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (bool,
}
// Perform preliminary sanity checks on the block and its transactions.
err = checkBlockSanity(block, dag.dagParams.PowLimit, dag.timeSource, dag.subnetworkID, flags)
err = dag.checkBlockSanity(block, flags)
if err != nil {
return false, err
}

View File

@ -4,6 +4,7 @@ import (
"bytes"
"encoding/binary"
"fmt"
"github.com/daglabs/btcd/util"
"github.com/daglabs/btcd/database"
@ -11,6 +12,16 @@ import (
"github.com/daglabs/btcd/wire"
)
type subnetworkStore struct {
db database.DB
}
func newSubnetworkStore(db database.DB) *subnetworkStore {
return &subnetworkStore{
db: db,
}
}
// registerSubnetworks scans a list of accepted transactions, singles out
// subnetwork registry transactions, validates them, and registers a new
// subnetwork based on it.
@ -77,10 +88,10 @@ func txToSubnetworkID(tx *wire.MsgTx) (*subnetworkid.SubnetworkID, error) {
// subnetwork returns a registered subnetwork. If the subnetwork does not exist
// this method returns an error.
func (dag *BlockDAG) subnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subnetwork, error) {
func (s *subnetworkStore) subnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subnetwork, error) {
var sNet *subnetwork
var err error
dbErr := dag.db.View(func(dbTx database.Tx) error {
dbErr := s.db.View(func(dbTx database.Tx) error {
sNet, err = dbGetSubnetwork(dbTx, subnetworkID)
return nil
})
@ -96,8 +107,8 @@ func (dag *BlockDAG) subnetwork(subnetworkID *subnetworkid.SubnetworkID) (*subne
// GasLimit returns the gas limit of a registered subnetwork. If the subnetwork does not
// exist this method returns an error.
func (dag *BlockDAG) GasLimit(subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
sNet, err := dag.subnetwork(subnetworkID)
func (s *subnetworkStore) GasLimit(subnetworkID *subnetworkid.SubnetworkID) (uint64, error) {
sNet, err := s.subnetwork(subnetworkID)
if err != nil {
return 0, err
}

View File

@ -26,7 +26,7 @@ func TestSubnetworkRegistry(t *testing.T) {
if err != nil {
t.Fatalf("could not register network: %s", err)
}
limit, err := dag.GasLimit(subnetworkID)
limit, err := dag.SubnetworkStore.GasLimit(subnetworkID)
if err != nil {
t.Fatalf("could not retrieve gas limit: %s", err)
}

View File

@ -283,7 +283,7 @@ func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID
// The flags modify the behavior of this function as follows:
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
// difficulty is not performed.
func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error {
func (dag *BlockDAG) checkProofOfWork(header *wire.BlockHeader, flags BehaviorFlags) error {
// The target difficulty must be larger than zero.
target := CompactToBig(header.Bits)
if target.Sign() <= 0 {
@ -293,9 +293,9 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio
}
// The target difficulty must be less than the maximum allowed.
if target.Cmp(powLimit) > 0 {
if target.Cmp(dag.dagParams.PowLimit) > 0 {
str := fmt.Sprintf("block target difficulty of %064x is "+
"higher than max of %064x", target, powLimit)
"higher than max of %064x", target, dag.dagParams.PowLimit)
return ruleError(ErrUnexpectedDifficulty, str)
}
@ -399,11 +399,11 @@ func CountP2SHSigOps(tx *util.Tx, isCoinBaseTx bool, utxoSet UTXOSet) (int, erro
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkProofOfWork.
func checkBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int, timeSource MedianTimeSource, flags BehaviorFlags) error {
func (dag *BlockDAG) checkBlockHeaderSanity(header *wire.BlockHeader, flags BehaviorFlags) error {
// Ensure the proof of work bits in the block header is in min/max range
// and the block hash is less than the target value described by the
// bits.
err := checkProofOfWork(header, powLimit, flags)
err := dag.checkProofOfWork(header, flags)
if err != nil {
return err
}
@ -425,7 +425,7 @@ func checkBlockHeaderSanity(header *wire.BlockHeader, powLimit *big.Int, timeSou
}
// Ensure the block time is not too far in the future.
maxTimestamp := timeSource.AdjustedTime().Add(time.Second *
maxTimestamp := dag.timeSource.AdjustedTime().Add(time.Second *
MaxTimeOffsetSeconds)
if header.Timestamp.After(maxTimestamp) {
str := fmt.Sprintf("block timestamp of %v is too far in the "+
@ -456,11 +456,11 @@ func checkBlockParentsOrder(header *wire.BlockHeader) error {
//
// The flags do not modify the behavior of this function directly, however they
// are needed to pass along to checkBlockHeaderSanity.
func checkBlockSanity(block *util.Block, powLimit *big.Int, timeSource MedianTimeSource,
subnetworkID *subnetworkid.SubnetworkID, flags BehaviorFlags) error {
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) error {
msgBlock := block.MsgBlock()
header := &msgBlock.Header
err := checkBlockHeaderSanity(header, powLimit, timeSource, flags)
err := dag.checkBlockHeaderSanity(header, flags)
if err != nil {
return err
}
@ -512,7 +512,7 @@ func checkBlockSanity(block *util.Block, powLimit *big.Int, timeSource MedianTim
// Do some preliminary checks on each transaction to ensure they are
// sane before continuing.
for _, tx := range transactions {
err := CheckTransactionSanity(tx, subnetworkID)
err := CheckTransactionSanity(tx, dag.subnetworkID)
if err != nil {
return err
}
@ -572,13 +572,41 @@ func checkBlockSanity(block *util.Block, powLimit *big.Int, timeSource MedianTim
}
}
// Amount of gas consumed per sub-network shouldn't be more than the subnetwork's limit
gasUsageInAllSubnetworks := map[subnetworkid.SubnetworkID]uint64{}
for _, tx := range transactions {
msgTx := tx.MsgTx()
// In DAGCoin and Registry sub-networks all txs must have Gas = 0, and that is validated in checkTransactionSanity
// Therefore - no need to check them here.
if msgTx.SubnetworkID != wire.SubnetworkIDNative && msgTx.SubnetworkID != wire.SubnetworkIDRegistry {
gasUsageInSubnetwork := gasUsageInAllSubnetworks[msgTx.SubnetworkID]
gasUsageInSubnetwork += msgTx.Gas
if gasUsageInSubnetwork < gasUsageInAllSubnetworks[msgTx.SubnetworkID] { // protect from overflows
str := fmt.Sprintf("Block gas usage in subnetwork with ID %s has overflown", msgTx.SubnetworkID)
return ruleError(ErrInvalidGas, str)
}
gasUsageInAllSubnetworks[msgTx.SubnetworkID] = gasUsageInSubnetwork
gasLimit, err := dag.SubnetworkStore.GasLimit(&msgTx.SubnetworkID)
if err != nil {
return err
}
if gasUsageInSubnetwork > gasLimit {
str := fmt.Sprintf("Block wastes too much gas in subnetwork with ID %s", msgTx.SubnetworkID)
return ruleError(ErrInvalidGas, str)
}
}
}
return nil
}
// CheckBlockSanity performs some preliminary checks on a block to ensure it is
// sane before continuing with block processing. These checks are context free.
func CheckBlockSanity(block *util.Block, powLimit *big.Int, timeSource MedianTimeSource, subnetworkID *subnetworkid.SubnetworkID) error {
return checkBlockSanity(block, powLimit, timeSource, subnetworkID, BFNone)
func (dag *BlockDAG) CheckBlockSanity(block *util.Block, powLimit *big.Int,
timeSource MedianTimeSource) error {
return dag.checkBlockSanity(block, BFNone)
}
// ExtractCoinbaseHeight attempts to extract the height of the block from the
@ -1106,7 +1134,7 @@ func (dag *BlockDAG) CheckConnectBlockTemplate(block *util.Block) error {
return ruleError(ErrParentBlockNotCurrentTips, str)
}
err := checkBlockSanity(block, dag.dagParams.PowLimit, dag.timeSource, dag.subnetworkID, flags)
err := dag.checkBlockSanity(block, flags)
if err != nil {
return err
}

View File

@ -73,7 +73,7 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
SubnetworkID: &wire.SubnetworkIDSupportsAll,
})
if err != nil {
t.Errorf("Failed to setup chain instance: %v", err)
t.Errorf("Failed to setup dag instance: %v", err)
return
}
defer teardownFunc()
@ -152,19 +152,30 @@ func TestCheckConnectBlockTemplate(t *testing.T) {
// TestCheckBlockSanity tests the CheckBlockSanity function to ensure it works
// as expected.
func TestCheckBlockSanity(t *testing.T) {
// Create a new database and dag instance to run tests against.
dag, teardownFunc, err := DAGSetup("TestCheckBlockSanity", Config{
DAGParams: &dagconfig.SimNetParams,
SubnetworkID: &wire.SubnetworkIDSupportsAll,
})
if err != nil {
t.Errorf("Failed to setup dag instance: %v", err)
return
}
defer teardownFunc()
powLimit := dagconfig.MainNetParams.PowLimit
block := util.NewBlock(&Block100000)
timeSource := NewMedianTime()
if len(block.Transactions()) < 3 {
t.Fatalf("Too few transactions in block, expect at least 3, got %v", len(block.Transactions()))
}
err := CheckBlockSanity(block, powLimit, timeSource, &wire.SubnetworkIDNative)
err = dag.CheckBlockSanity(block, powLimit, timeSource)
if err != nil {
t.Errorf("CheckBlockSanity: %v", err)
}
// Test with block with wrong transactions sorting order
blockWithWrongTxOrder := util.NewBlock(&BlockWithWrongTxOrder)
err = CheckBlockSanity(blockWithWrongTxOrder, powLimit, timeSource, &wire.SubnetworkIDNative)
err = dag.CheckBlockSanity(blockWithWrongTxOrder, powLimit, timeSource)
if err == nil {
t.Errorf("CheckBlockSanity: transactions disorder is not detected")
}
@ -179,7 +190,7 @@ func TestCheckBlockSanity(t *testing.T) {
// second fails.
timestamp := block.MsgBlock().Header.Timestamp
block.MsgBlock().Header.Timestamp = timestamp.Add(time.Nanosecond)
err = CheckBlockSanity(block, powLimit, timeSource, &wire.SubnetworkIDNative)
err = dag.CheckBlockSanity(block, powLimit, timeSource)
if err == nil {
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
}
@ -445,7 +456,7 @@ func TestCheckBlockSanity(t *testing.T) {
}
btcutilInvalidBlock := util.NewBlock(&invalidParentsOrderBlock)
err = CheckBlockSanity(btcutilInvalidBlock, powLimit, timeSource, &wire.SubnetworkIDNative)
err = dag.CheckBlockSanity(btcutilInvalidBlock, powLimit, timeSource)
if err == nil {
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
}

View File

@ -754,7 +754,7 @@ func (mp *TxPool) maybeAcceptTransaction(tx *util.Tx, isNew, rateLimit, rejectDu
if msgTx.SubnetworkID == wire.SubnetworkIDSupportsAll {
return nil, nil, txRuleError(wire.RejectInvalid, "SubnetworkIDSupportsAll is not permited in transaction")
} else if msgTx.SubnetworkID != wire.SubnetworkIDNative {
gasLimit, err := mp.cfg.DAG.GasLimit(&msgTx.SubnetworkID)
gasLimit, err := mp.cfg.DAG.SubnetworkStore.GasLimit(&msgTx.SubnetworkID)
if err != nil {
return nil, nil, err
}

View File

@ -494,7 +494,7 @@ func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress util.Address) (*BlockTe
if !ok {
gasUsage = 0
}
gasLimit, err := g.dag.GasLimit(&subnetworkID)
gasLimit, err := g.dag.SubnetworkStore.GasLimit(&subnetworkID)
if err != nil {
log.Errorf("Cannot get GAS limit for subnetwork %v", subnetworkID)
continue

View File

@ -3398,7 +3398,7 @@ func verifyDAG(s *Server, level, depth int32) error {
if finishHeight < 0 {
finishHeight = 0
}
log.Infof("Verifying chain for %d blocks at level %d",
log.Infof("Verifying DAG for %d blocks at level %d",
s.cfg.DAG.Height()-finishHeight, level) //TODO: (Ori) This is probably wrong. Done only for compilation
currentHash := s.cfg.DAG.HighestTipHash()
@ -3411,10 +3411,9 @@ func verifyDAG(s *Server, level, depth int32) error {
return err
}
// Level 1 does basic chain sanity checks.
// Level 1 does basic DAG sanity checks.
if level > 0 {
err := blockdag.CheckBlockSanity(block, s.cfg.DAGParams.PowLimit,
s.cfg.TimeSource, config.MainConfig().SubnetworkID)
err := s.cfg.DAG.CheckBlockSanity(block, s.cfg.DAGParams.PowLimit, s.cfg.TimeSource)
if err != nil {
log.Errorf("Verify is unable to validate "+
"block at hash %v height %d: %v",
@ -3425,7 +3424,7 @@ func verifyDAG(s *Server, level, depth int32) error {
currentHash = *block.MsgBlock().Header.SelectedParentHash()
}
log.Infof("Chain verify completed successfully")
log.Infof("DAG verify completed successfully")
return nil
}