diff --git a/blockdag/dag.go b/blockdag/dag.go index 6afe9d5d4..55b8bc3fd 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -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, } diff --git a/blockdag/process.go b/blockdag/process.go index ea1043e1c..5fb9acbb2 100644 --- a/blockdag/process.go +++ b/blockdag/process.go @@ -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 } diff --git a/blockdag/subnetworks.go b/blockdag/subnetworks.go index b9ae1eb6d..26f4438d7 100644 --- a/blockdag/subnetworks.go +++ b/blockdag/subnetworks.go @@ -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 } diff --git a/blockdag/subnetworks_test.go b/blockdag/subnetworks_test.go index 890a66e92..a46b244cd 100644 --- a/blockdag/subnetworks_test.go +++ b/blockdag/subnetworks_test.go @@ -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) } diff --git a/blockdag/validate.go b/blockdag/validate.go index 2fefa3d59..a4a49458b 100644 --- a/blockdag/validate.go +++ b/blockdag/validate.go @@ -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 } diff --git a/blockdag/validate_test.go b/blockdag/validate_test.go index 7216e5c88..9d777e3e1 100644 --- a/blockdag/validate_test.go +++ b/blockdag/validate_test.go @@ -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") } diff --git a/mempool/mempool.go b/mempool/mempool.go index cc3a60225..8c492d8c7 100644 --- a/mempool/mempool.go +++ b/mempool/mempool.go @@ -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 } diff --git a/mining/mining.go b/mining/mining.go index 68af8969f..c4089e5de 100644 --- a/mining/mining.go +++ b/mining/mining.go @@ -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 diff --git a/server/rpc/rpcserver.go b/server/rpc/rpcserver.go index d92932948..ea3c936a3 100644 --- a/server/rpc/rpcserver.go +++ b/server/rpc/rpcserver.go @@ -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 }