mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-23 03:48:20 +00:00
Compare commits
1 Commits
v0.6.3-dev
...
v0.6.1-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5f3fb0bf9f |
@@ -9,9 +9,8 @@ import (
|
||||
crand "crypto/rand" // for seeding
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
@@ -24,6 +23,7 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -6,19 +6,20 @@ package addressmanager
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
// naTest is used to describe a test to be performed against the NetAddressKey
|
||||
@@ -5,7 +5,7 @@
|
||||
package addressmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
@@ -5,11 +5,12 @@
|
||||
package addressmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
// KnownAddress tracks information about a known network address that is used
|
||||
@@ -5,13 +5,13 @@
|
||||
package addressmanager_test
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/addressmanager"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
func TestChance(t *testing.T) {
|
||||
@@ -5,7 +5,7 @@
|
||||
package addressmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
package addressmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"net"
|
||||
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -5,9 +5,10 @@
|
||||
package addressmanager
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
// TestIPTypes ensures the various functions which determine the type of an IP
|
||||
56
app/app.go
56
app/app.go
@@ -4,24 +4,24 @@ import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/netadapter/id"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/domain/mining"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/infrastructure/signal"
|
||||
"github.com/kaspanet/kaspad/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/network/dnsseed"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/network/protocol"
|
||||
"github.com/kaspanet/kaspad/network/rpc"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/connmanager"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dnsseed"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/mempool"
|
||||
"github.com/kaspanet/kaspad/mining"
|
||||
"github.com/kaspanet/kaspad/netadapter"
|
||||
"github.com/kaspanet/kaspad/protocol"
|
||||
"github.com/kaspanet/kaspad/rpc"
|
||||
"github.com/kaspanet/kaspad/signal"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
@@ -62,11 +62,11 @@ func (a *App) Start() {
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down all the kaspad services.
|
||||
func (a *App) Stop() {
|
||||
func (a *App) Stop() error {
|
||||
// Make sure this only happens once.
|
||||
if atomic.AddInt32(&a.shutdown, 1) != 1 {
|
||||
log.Infof("Kaspad is already in the process of shutting down")
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Warnf("Kaspad shutting down")
|
||||
@@ -86,12 +86,7 @@ func (a *App) Stop() {
|
||||
}
|
||||
}
|
||||
|
||||
err = a.addressManager.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping address manager: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
// New returns a new App instance configured to listen on addr for the
|
||||
@@ -195,10 +190,11 @@ func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript
|
||||
MaxTxVersion: 1,
|
||||
},
|
||||
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
||||
return dag.CalcSequenceLockNoLock(tx, utxoSet)
|
||||
return dag.CalcSequenceLockNoLock(tx, utxoSet, true)
|
||||
},
|
||||
SigCache: sigCache,
|
||||
DAG: dag,
|
||||
IsDeploymentActive: dag.IsDeploymentActive,
|
||||
SigCache: sigCache,
|
||||
DAG: dag,
|
||||
}
|
||||
|
||||
return mempool.New(&mempoolConfig)
|
||||
@@ -245,3 +241,9 @@ func (a *App) P2PNodeID() *id.ID {
|
||||
func (a *App) AddressManager() *addressmanager.AddressManager {
|
||||
return a.addressManager
|
||||
}
|
||||
|
||||
// WaitForShutdown blocks until the main listener and peer handlers are stopped.
|
||||
func (a *App) WaitForShutdown() {
|
||||
// TODO(libp2p)
|
||||
// a.p2pServer.WaitForShutdown()
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
|
||||
154
blockdag/accept.go
Normal file
154
blockdag/accept.go
Normal file
@@ -0,0 +1,154 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
|
||||
blockHeader := &block.MsgBlock().Header
|
||||
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
|
||||
newNode.status = statusInvalidAncestor
|
||||
dag.index.AddNode(newNode)
|
||||
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
err = dag.index.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dbTx.Commit()
|
||||
}
|
||||
|
||||
// maybeAcceptBlock potentially accepts a block into the block DAG. It
|
||||
// performs several validation checks which depend on its position within
|
||||
// the block DAG before adding it. The block is expected to have already
|
||||
// gone through ProcessBlock before calling this function with it.
|
||||
//
|
||||
// The flags are also passed to checkBlockContext and connectToDAG. See
|
||||
// their documentation for how the flags modify their behavior.
|
||||
//
|
||||
// This function MUST be called with the dagLock held (for writes).
|
||||
func (dag *BlockDAG) maybeAcceptBlock(block *util.Block, flags BehaviorFlags) error {
|
||||
parents, err := lookupParentNodes(block, dag)
|
||||
if err != nil {
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
|
||||
err := dag.addNodeToIndexWithInvalidAncestor(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// The block must pass all of the validation rules which depend on the
|
||||
// position of the block within the block DAG.
|
||||
err = dag.checkBlockContext(block, parents, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a new block node for the block and add it to the node index.
|
||||
newNode, selectedParentAnticone := dag.newBlockNode(&block.MsgBlock().Header, parents)
|
||||
newNode.status = statusDataStored
|
||||
dag.index.AddNode(newNode)
|
||||
|
||||
// Insert the block into the database if it's not already there. Even
|
||||
// though it is possible the block will ultimately fail to connect, it
|
||||
// has already passed all proof-of-work and validity tests which means
|
||||
// it would be prohibitively expensive for an attacker to fill up the
|
||||
// disk with a bunch of blocks that fail to connect. This is necessary
|
||||
// since it allows block download to be decoupled from the much more
|
||||
// expensive connection logic. It also has some other nice properties
|
||||
// such as making blocks that never become part of the DAG or
|
||||
// blocks that fail to connect available for further analysis.
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
blockExists, err := dbaccess.HasBlock(dbTx, block.Hash())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockExists {
|
||||
err := storeBlock(dbTx, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = dag.index.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Make sure that all the block's transactions are finalized
|
||||
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||
bluestParent := parents.bluest()
|
||||
if !fastAdd {
|
||||
if err := dag.validateAllTxsFinalized(block, newNode, bluestParent); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Connect the passed block to the DAG. This also handles validation of the
|
||||
// transaction scripts.
|
||||
chainUpdates, err := dag.addBlock(newNode, block, selectedParentAnticone, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Notify the caller that the new block was accepted into the block
|
||||
// DAG. The caller would typically want to react by relaying the
|
||||
// inventory to other peers.
|
||||
dag.dagLock.Unlock()
|
||||
dag.sendNotification(NTBlockAdded, &BlockAddedNotificationData{
|
||||
Block: block,
|
||||
WasUnorphaned: flags&BFWasUnorphaned != 0,
|
||||
})
|
||||
if len(chainUpdates.addedChainBlockHashes) > 0 {
|
||||
dag.sendNotification(NTChainChanged, &ChainChangedNotificationData{
|
||||
RemovedChainBlockHashes: chainUpdates.removedChainBlockHashes,
|
||||
AddedChainBlockHashes: chainUpdates.addedChainBlockHashes,
|
||||
})
|
||||
}
|
||||
dag.dagLock.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
|
||||
header := block.MsgBlock().Header
|
||||
parentHashes := header.ParentHashes
|
||||
|
||||
nodes := newBlockSet()
|
||||
for _, parentHash := range parentHashes {
|
||||
node, ok := dag.index.LookupNode(parentHash)
|
||||
if !ok {
|
||||
str := fmt.Sprintf("parent block %s is unknown", parentHash)
|
||||
return nil, ruleError(ErrParentBlockUnknown, str)
|
||||
} else if dag.index.NodeStatus(node).KnownInvalid() {
|
||||
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
|
||||
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
||||
}
|
||||
|
||||
nodes.add(node)
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
107
blockdag/accept_test.go
Normal file
107
blockdag/accept_test.go
Normal file
@@ -0,0 +1,107 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
)
|
||||
|
||||
func TestMaybeAcceptBlockErrors(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
dag.TestSetCoinbaseMaturity(0)
|
||||
|
||||
// Test rejecting the block if its parents are missing
|
||||
orphanBlockFile := "blk_3B.dat"
|
||||
loadedBlocks, err := LoadBlocks(filepath.Join("testdata/", orphanBlockFile))
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: "+
|
||||
"Error loading file '%s': %s\n", orphanBlockFile, err)
|
||||
}
|
||||
block := loadedBlocks[0]
|
||||
|
||||
err = dag.maybeAcceptBlock(block, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
|
||||
}
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); !ok {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Expected RuleError but got %s", err)
|
||||
} else if ruleErr.ErrorCode != ErrParentBlockUnknown {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Unexpected error code. Want: %s, got: %s", ErrParentBlockUnknown, ruleErr.ErrorCode)
|
||||
}
|
||||
|
||||
// Test rejecting the block if its parents are invalid
|
||||
blocksFile := "blk_0_to_4.dat"
|
||||
blocks, err := LoadBlocks(filepath.Join("testdata/", blocksFile))
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: "+
|
||||
"Error loading file '%s': %s\n", blocksFile, err)
|
||||
}
|
||||
|
||||
// Add a valid block and mark it as invalid
|
||||
block1 := blocks[1]
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(block1, BFNone)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: block 1 is too far in the future")
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: incorrectly returned block 1 is an orphan")
|
||||
}
|
||||
blockNode1, ok := dag.index.LookupNode(block1.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", block1.Hash())
|
||||
}
|
||||
dag.index.SetStatusFlags(blockNode1, statusValidateFailed)
|
||||
|
||||
block2 := blocks[2]
|
||||
err = dag.maybeAcceptBlock(block2, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
|
||||
}
|
||||
if ok := errors.As(err, &ruleErr); !ok {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Expected RuleError but got %s", err)
|
||||
} else if ruleErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode)
|
||||
}
|
||||
|
||||
// Set block1's status back to valid for next tests
|
||||
dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed)
|
||||
|
||||
// Test rejecting the block due to bad context
|
||||
originalBits := block2.MsgBlock().Header.Bits
|
||||
block2.MsgBlock().Header.Bits = 0
|
||||
err = dag.maybeAcceptBlock(block2, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
|
||||
}
|
||||
if ok := errors.As(err, &ruleErr); !ok {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Expected RuleError but got %s", err)
|
||||
} else if ruleErr.ErrorCode != ErrUnexpectedDifficulty {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode)
|
||||
}
|
||||
|
||||
// Set block2's bits back to valid for next tests
|
||||
block2.MsgBlock().Header.Bits = originalBits
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -5,12 +5,10 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -136,42 +134,3 @@ func (bi *blockIndex) flushToDB(dbContext *dbaccess.TxContext) error {
|
||||
func (bi *blockIndex) clearDirtyEntries() {
|
||||
bi.dirty = make(map[*blockNode]struct{})
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) addNodeToIndexWithInvalidAncestor(block *util.Block) error {
|
||||
blockHeader := &block.MsgBlock().Header
|
||||
newNode, _ := dag.newBlockNode(blockHeader, newBlockSet())
|
||||
newNode.status = statusInvalidAncestor
|
||||
dag.index.AddNode(newNode)
|
||||
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
err = dag.index.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return dbTx.Commit()
|
||||
}
|
||||
|
||||
func lookupParentNodes(block *util.Block, dag *BlockDAG) (blockSet, error) {
|
||||
header := block.MsgBlock().Header
|
||||
parentHashes := header.ParentHashes
|
||||
|
||||
nodes := newBlockSet()
|
||||
for _, parentHash := range parentHashes {
|
||||
node, ok := dag.index.LookupNode(parentHash)
|
||||
if !ok {
|
||||
str := fmt.Sprintf("parent block %s is unknown", parentHash)
|
||||
return nil, ruleError(ErrParentBlockUnknown, str)
|
||||
} else if dag.index.NodeStatus(node).KnownInvalid() {
|
||||
str := fmt.Sprintf("parent block %s is known to be invalid", parentHash)
|
||||
return nil, ruleError(ErrInvalidAncestorBlock, str)
|
||||
}
|
||||
|
||||
nodes.add(node)
|
||||
}
|
||||
|
||||
return nodes, nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"testing"
|
||||
)
|
||||
109
blockdag/blocklocator.go
Normal file
109
blockdag/blocklocator.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BlockLocator is used to help locate a specific block. The algorithm for
|
||||
// building the block locator is to add block hashes in reverse order on the
|
||||
// block's selected parent chain until the desired stop block is reached.
|
||||
// In order to keep the list of locator hashes to a reasonable number of entries,
|
||||
// the step between each entry is doubled each loop iteration to exponentially
|
||||
// decrease the number of hashes as a function of the distance from the block
|
||||
// being located.
|
||||
//
|
||||
// For example, assume a selected parent chain with IDs as depicted below, and the
|
||||
// stop block is genesis:
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
//
|
||||
// The block locator for block 17 would be the hashes of blocks:
|
||||
// [17 16 14 11 7 2 genesis]
|
||||
type BlockLocator []*daghash.Hash
|
||||
|
||||
// BlockLocatorFromHashes returns a block locator from high and low hash.
|
||||
// See BlockLocator for details on the algorithm used to create a block locator.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) BlockLocatorFromHashes(highHash, lowHash *daghash.Hash) (BlockLocator, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
highNode, ok := dag.index.LookupNode(highHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("block %s is unknown", highHash)
|
||||
}
|
||||
|
||||
lowNode, ok := dag.index.LookupNode(lowHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("block %s is unknown", lowHash)
|
||||
}
|
||||
|
||||
return dag.blockLocator(highNode, lowNode)
|
||||
}
|
||||
|
||||
// blockLocator returns a block locator for the passed high and low nodes.
|
||||
// See the BlockLocator type comments for more details.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) blockLocator(highNode, lowNode *blockNode) (BlockLocator, error) {
|
||||
// We use the selected parent of the high node, so the
|
||||
// block locator won't contain the high node.
|
||||
highNode = highNode.selectedParent
|
||||
|
||||
node := highNode
|
||||
step := uint64(1)
|
||||
locator := make(BlockLocator, 0)
|
||||
for node != nil {
|
||||
locator = append(locator, node.hash)
|
||||
|
||||
// Nothing more to add once the low node has been added.
|
||||
if node.blueScore <= lowNode.blueScore {
|
||||
if node != lowNode {
|
||||
return nil, errors.Errorf("highNode and lowNode are " +
|
||||
"not in the same selected parent chain.")
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
// Calculate blueScore of previous node to include ensuring the
|
||||
// final node is lowNode.
|
||||
nextBlueScore := node.blueScore - step
|
||||
if nextBlueScore < lowNode.blueScore {
|
||||
nextBlueScore = lowNode.blueScore
|
||||
}
|
||||
|
||||
// walk backwards through the nodes to the correct ancestor.
|
||||
node = node.SelectedAncestor(nextBlueScore)
|
||||
|
||||
// Double the distance between included hashes.
|
||||
step *= 2
|
||||
}
|
||||
|
||||
return locator, nil
|
||||
}
|
||||
|
||||
// FindNextLocatorBoundaries returns the lowest unknown block locator, hash
|
||||
// and the highest known block locator hash. This is used to create the
|
||||
// next block locator to find the highest shared known chain block with the
|
||||
// sync peer.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) FindNextLocatorBoundaries(locator BlockLocator) (highHash, lowHash *daghash.Hash) {
|
||||
// Find the most recent locator block hash in the DAG. In the case none of
|
||||
// the hashes in the locator are in the DAG, fall back to the genesis block.
|
||||
lowNode := dag.genesis
|
||||
nextBlockLocatorIndex := int64(len(locator) - 1)
|
||||
for i, hash := range locator {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if ok {
|
||||
lowNode = node
|
||||
nextBlockLocatorIndex = int64(i) - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextBlockLocatorIndex < 0 {
|
||||
return nil, lowNode.hash
|
||||
}
|
||||
return locator[nextBlockLocatorIndex], lowNode.hash
|
||||
}
|
||||
@@ -8,11 +8,11 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"testing"
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"reflect"
|
||||
@@ -51,14 +51,14 @@ func TestBlueBlockWindow(t *testing.T) {
|
||||
expectedWindowWithGenesisPadding: []string{"B", "A", "A", "A", "A", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
parents: []string{"D", "C"},
|
||||
id: "E",
|
||||
expectedWindowWithGenesisPadding: []string{"C", "D", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
parents: []string{"D", "C"},
|
||||
id: "F",
|
||||
expectedWindowWithGenesisPadding: []string{"C", "D", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"A"},
|
||||
@@ -73,37 +73,37 @@ func TestBlueBlockWindow(t *testing.T) {
|
||||
{
|
||||
parents: []string{"H", "F"},
|
||||
id: "I",
|
||||
expectedWindowWithGenesisPadding: []string{"F", "C", "D", "B", "A", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"F", "D", "C", "B", "A", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"I"},
|
||||
id: "J",
|
||||
expectedWindowWithGenesisPadding: []string{"I", "F", "C", "D", "B", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"I", "F", "D", "C", "B", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"J"},
|
||||
id: "K",
|
||||
expectedWindowWithGenesisPadding: []string{"J", "I", "F", "C", "D", "B", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"J", "I", "F", "D", "C", "B", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"K"},
|
||||
id: "L",
|
||||
expectedWindowWithGenesisPadding: []string{"K", "J", "I", "F", "C", "D", "B", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"K", "J", "I", "F", "D", "C", "B", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"L"},
|
||||
id: "M",
|
||||
expectedWindowWithGenesisPadding: []string{"L", "K", "J", "I", "F", "C", "D", "B", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"L", "K", "J", "I", "F", "D", "C", "B", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "N",
|
||||
expectedWindowWithGenesisPadding: []string{"M", "L", "K", "J", "I", "F", "C", "D", "B", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"M", "L", "K", "J", "I", "F", "D", "C", "B", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"N"},
|
||||
id: "O",
|
||||
expectedWindowWithGenesisPadding: []string{"N", "M", "L", "K", "J", "I", "F", "C", "D", "B"},
|
||||
expectedWindowWithGenesisPadding: []string{"N", "M", "L", "K", "J", "I", "F", "D", "C", "B"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ import (
|
||||
"encoding/binary"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/coinbasepayload"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
@@ -197,24 +197,3 @@ func coinbaseOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
|
||||
|
||||
return txOut, nil
|
||||
}
|
||||
|
||||
// NextBlockCoinbaseTransaction prepares the coinbase transaction for the next mined block
|
||||
//
|
||||
// This function CAN'T be called with the DAG lock held.
|
||||
func (dag *BlockDAG) NextBlockCoinbaseTransaction(scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
return dag.NextBlockCoinbaseTransactionNoLock(scriptPubKey, extraData)
|
||||
}
|
||||
|
||||
// NextBlockCoinbaseTransactionNoLock prepares the coinbase transaction for the next mined block
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
func (dag *BlockDAG) NextBlockCoinbaseTransactionNoLock(scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
|
||||
txsAcceptanceData, err := dag.TxsAcceptedByVirtual()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dag.virtual.blockNode.expectedCoinbaseTransaction(dag, txsAcceptanceData, scriptPubKey, extraData)
|
||||
}
|
||||
@@ -17,8 +17,8 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
@@ -103,6 +103,8 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
|
||||
TimestampDeviationTolerance: params.TimestampDeviationTolerance,
|
||||
powMaxBits: util.BigToCompact(params.PowMax),
|
||||
index: index,
|
||||
warningCaches: newThresholdCaches(vbNumBits),
|
||||
deploymentCaches: newThresholdCaches(dagconfig.DefinedDeployments),
|
||||
}
|
||||
|
||||
// Create a genesis block node and block index index populated with it
|
||||
2143
blockdag/dag.go
Normal file
2143
blockdag/dag.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -14,12 +14,12 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
@@ -305,6 +305,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
name string
|
||||
tx *domainmessage.MsgTx
|
||||
utxoSet UTXOSet
|
||||
mempool bool
|
||||
want *SequenceLock
|
||||
}{
|
||||
// A transaction with a single input with max sequence number.
|
||||
@@ -461,6 +462,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
name: "single input, unconfirmed, lock-time in blocks",
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
mempool: true,
|
||||
want: &SequenceLock{
|
||||
Milliseconds: -1,
|
||||
BlockBlueScore: int64(nextBlockBlueScore) + 1,
|
||||
@@ -473,6 +475,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
name: "single input, unconfirmed, lock-time in milliseoncds",
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
mempool: true,
|
||||
want: &SequenceLock{
|
||||
Milliseconds: nextMedianTime + 1048575,
|
||||
BlockBlueScore: -1,
|
||||
@@ -483,7 +486,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
t.Logf("Running %v SequenceLock tests", len(tests))
|
||||
for _, test := range tests {
|
||||
utilTx := util.NewTx(test.tx)
|
||||
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet)
|
||||
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet, test.mempool)
|
||||
if err != nil {
|
||||
t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err)
|
||||
}
|
||||
@@ -1121,6 +1124,21 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsDAGCurrentMaxDiff(t *testing.T) {
|
||||
netParams := []*dagconfig.Params{
|
||||
&dagconfig.MainnetParams,
|
||||
&dagconfig.TestnetParams,
|
||||
&dagconfig.DevnetParams,
|
||||
&dagconfig.RegressionNetParams,
|
||||
&dagconfig.SimnetParams,
|
||||
}
|
||||
for _, params := range netParams {
|
||||
if params.FinalityDuration < isDAGCurrentMaxDiff*params.TargetTimePerBlock {
|
||||
t.Errorf("in %s, a DAG can be considered current even if it's below the finality point", params.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock, expectedRuleErr error) {
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck)
|
||||
|
||||
@@ -11,11 +11,11 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
@@ -662,16 +662,3 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
|
||||
|
||||
return blockHashes, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) fetchBlueBlocks(node *blockNode) ([]*util.Block, error) {
|
||||
blueBlocks := make([]*util.Block, len(node.blues))
|
||||
for i, blueBlockNode := range node.blues {
|
||||
blueBlock, err := dag.fetchBlockByHash(blueBlockNode.hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blueBlocks[i] = blueBlock
|
||||
}
|
||||
return blueBlocks, nil
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
// block given its bluest parent.
|
||||
func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime mstime.Time) uint32 {
|
||||
// Genesis block.
|
||||
if dag.Params.DisableDifficultyAdjustment || bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
|
||||
if bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
|
||||
return dag.powMaxBits
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"math/big"
|
||||
"testing"
|
||||
@@ -80,7 +80,7 @@ func TestCalcWork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDifficulty(t *testing.T) {
|
||||
params := dagconfig.MainnetParams
|
||||
params := dagconfig.SimnetParams
|
||||
params.K = 1
|
||||
params.DifficultyAdjustmentWindowSize = 264
|
||||
dag, teardownFunc, err := DAGSetup("TestDifficulty", true, Config{
|
||||
@@ -13,11 +13,11 @@ import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/testtools"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/mining"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/mining"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
)
|
||||
@@ -7,8 +7,8 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
@@ -34,7 +34,7 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
k: 3,
|
||||
expectedReds: []string{"F", "G", "H", "I", "O", "P"},
|
||||
expectedReds: []string{"F", "G", "H", "I", "N", "P"},
|
||||
dagData: []*testBlockData{
|
||||
{
|
||||
parents: []string{"A"},
|
||||
@@ -167,7 +167,7 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
id: "T",
|
||||
expectedScore: 13,
|
||||
expectedSelectedParent: "S",
|
||||
expectedBlues: []string{"S", "Q", "N"},
|
||||
expectedBlues: []string{"S", "O", "Q"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
@@ -9,10 +9,10 @@ import (
|
||||
"syscall"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
@@ -8,8 +8,8 @@ Package indexers implements optional block DAG indexes.
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.INDX)
|
||||
@@ -5,8 +5,8 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
@@ -134,49 +132,3 @@ func buildMerkleTreeStore(hashes []*daghash.Hash) MerkleTree {
|
||||
|
||||
return merkles
|
||||
}
|
||||
|
||||
func calculateAcceptedIDMerkleRoot(multiBlockTxsAcceptanceData MultiBlockTxsAcceptanceData) *daghash.Hash {
|
||||
var acceptedTxs []*util.Tx
|
||||
for _, blockTxsAcceptanceData := range multiBlockTxsAcceptanceData {
|
||||
for _, txAcceptance := range blockTxsAcceptanceData.TxAcceptanceData {
|
||||
if !txAcceptance.IsAccepted {
|
||||
continue
|
||||
}
|
||||
acceptedTxs = append(acceptedTxs, txAcceptance.Tx)
|
||||
}
|
||||
}
|
||||
sort.Slice(acceptedTxs, func(i, j int) bool {
|
||||
return daghash.LessTxID(acceptedTxs[i].ID(), acceptedTxs[j].ID())
|
||||
})
|
||||
|
||||
acceptedIDMerkleTree := BuildIDMerkleTreeStore(acceptedTxs)
|
||||
return acceptedIDMerkleTree.Root()
|
||||
}
|
||||
|
||||
func (node *blockNode) validateAcceptedIDMerkleRoot(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData) error {
|
||||
if node.isGenesis() {
|
||||
return nil
|
||||
}
|
||||
|
||||
calculatedAccepetedIDMerkleRoot := calculateAcceptedIDMerkleRoot(txsAcceptanceData)
|
||||
header := node.Header()
|
||||
if !header.AcceptedIDMerkleRoot.IsEqual(calculatedAccepetedIDMerkleRoot) {
|
||||
str := fmt.Sprintf("block accepted ID merkle root is invalid - block "+
|
||||
"header indicates %s, but calculated value is %s",
|
||||
header.AcceptedIDMerkleRoot, calculatedAccepetedIDMerkleRoot)
|
||||
return ruleError(ErrBadMerkleRoot, str)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NextAcceptedIDMerkleRootNoLock prepares the acceptedIDMerkleRoot for the next mined block
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
func (dag *BlockDAG) NextAcceptedIDMerkleRootNoLock() (*daghash.Hash, error) {
|
||||
txsAcceptanceData, err := dag.TxsAcceptedByVirtual()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return calculateAcceptedIDMerkleRoot(txsAcceptanceData), nil
|
||||
}
|
||||
@@ -3,20 +3,16 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// The current block version
|
||||
const blockVersion = 0x10000000
|
||||
|
||||
// BlockForMining returns a block with the given transactions
|
||||
// that points to the current DAG tips, that is valid from
|
||||
// all aspects except proof of work.
|
||||
@@ -26,6 +22,13 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.Msg
|
||||
blockTimestamp := dag.NextBlockTime()
|
||||
requiredDifficulty := dag.NextRequiredDifficulty(blockTimestamp)
|
||||
|
||||
// Calculate the next expected block version based on the state of the
|
||||
// rule change deployments.
|
||||
nextBlockVersion, err := dag.CalcNextBlockVersion()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a new block ready to be solved.
|
||||
hashMerkleTree := BuildHashMerkleTreeStore(transactions)
|
||||
acceptedIDMerkleRoot, err := dag.NextAcceptedIDMerkleRootNoLock()
|
||||
@@ -43,7 +46,7 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.Msg
|
||||
}
|
||||
|
||||
msgBlock.Header = domainmessage.BlockHeader{
|
||||
Version: blockVersion,
|
||||
Version: nextBlockVersion,
|
||||
ParentHashes: dag.TipHashes(),
|
||||
HashMerkleRoot: hashMerkleTree.Root(),
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
@@ -124,15 +127,3 @@ func (dag *BlockDAG) NextBlockTime() mstime.Time {
|
||||
|
||||
return newTimestamp
|
||||
}
|
||||
|
||||
// CurrentBits returns the bits of the tip with the lowest bits, which also means it has highest difficulty.
|
||||
func (dag *BlockDAG) CurrentBits() uint32 {
|
||||
tips := dag.virtual.tips()
|
||||
minBits := uint32(math.MaxUint32)
|
||||
for tip := range tips {
|
||||
if minBits > tip.Header().Bits {
|
||||
minBits = tip.Header().Bits
|
||||
}
|
||||
}
|
||||
return minBits
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/locks"
|
||||
"github.com/pkg/errors"
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
)
|
||||
|
||||
// TestNotifications ensures that notification callbacks are fired on events.
|
||||
287
blockdag/process.go
Normal file
287
blockdag/process.go
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// BehaviorFlags is a bitmask defining tweaks to the normal behavior when
|
||||
// performing DAG processing and consensus rules checks.
|
||||
type BehaviorFlags uint32
|
||||
|
||||
const (
|
||||
// BFFastAdd may be set to indicate that several checks can be avoided
|
||||
// for the block since it is already known to fit into the DAG due to
|
||||
// already proving it correct links into the DAG.
|
||||
BFFastAdd BehaviorFlags = 1 << iota
|
||||
|
||||
// BFNoPoWCheck may be set to indicate the proof of work check which
|
||||
// ensures a block hashes to a value less than the required target will
|
||||
// not be performed.
|
||||
BFNoPoWCheck
|
||||
|
||||
// BFWasUnorphaned may be set to indicate that a block was just now
|
||||
// unorphaned
|
||||
BFWasUnorphaned
|
||||
|
||||
// BFAfterDelay may be set to indicate that a block had timestamp too far
|
||||
// in the future, just finished the delay
|
||||
BFAfterDelay
|
||||
|
||||
// BFIsSync may be set to indicate that the block was sent as part of the
|
||||
// netsync process
|
||||
BFIsSync
|
||||
|
||||
// BFWasStored is set to indicate that the block was previously stored
|
||||
// in the block index but was never fully processed
|
||||
BFWasStored
|
||||
|
||||
// BFDisallowDelay is set to indicate that a delayed block should be rejected.
|
||||
// This is used for the case where a block is submitted through RPC.
|
||||
BFDisallowDelay
|
||||
|
||||
// BFDisallowOrphans is set to indicate that an orphan block should be rejected.
|
||||
// This is used for the case where a block is submitted through RPC.
|
||||
BFDisallowOrphans
|
||||
|
||||
// BFNone is a convenience value to specifically indicate no flags.
|
||||
BFNone BehaviorFlags = 0
|
||||
)
|
||||
|
||||
// IsInDAG determines whether a block with the given hash exists in
|
||||
// the DAG.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) IsInDAG(hash *daghash.Hash) bool {
|
||||
return dag.index.HaveBlock(hash)
|
||||
}
|
||||
|
||||
// processOrphans determines if there are any orphans which depend on the passed
|
||||
// block hash (they are no longer orphans if true) and potentially accepts them.
|
||||
// It repeats the process for the newly accepted blocks (to detect further
|
||||
// orphans which may no longer be orphans) until there are no more.
|
||||
//
|
||||
// The flags do not modify the behavior of this function directly, however they
|
||||
// are needed to pass along to maybeAcceptBlock.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) processOrphans(hash *daghash.Hash, flags BehaviorFlags) error {
|
||||
// Start with processing at least the passed hash. Leave a little room
|
||||
// for additional orphan blocks that need to be processed without
|
||||
// needing to grow the array in the common case.
|
||||
processHashes := make([]*daghash.Hash, 0, 10)
|
||||
processHashes = append(processHashes, hash)
|
||||
for len(processHashes) > 0 {
|
||||
// Pop the first hash to process from the slice.
|
||||
processHash := processHashes[0]
|
||||
processHashes[0] = nil // Prevent GC leak.
|
||||
processHashes = processHashes[1:]
|
||||
|
||||
// Look up all orphans that are parented by the block we just
|
||||
// accepted. An indexing for loop is
|
||||
// intentionally used over a range here as range does not
|
||||
// reevaluate the slice on each iteration nor does it adjust the
|
||||
// index for the modified slice.
|
||||
for i := 0; i < len(dag.prevOrphans[*processHash]); i++ {
|
||||
orphan := dag.prevOrphans[*processHash][i]
|
||||
if orphan == nil {
|
||||
log.Warnf("Found a nil entry at index %d in the "+
|
||||
"orphan dependency list for block %s", i,
|
||||
processHash)
|
||||
continue
|
||||
}
|
||||
|
||||
// Skip this orphan if one or more of its parents are
|
||||
// still missing.
|
||||
_, err := lookupParentNodes(orphan.block, dag)
|
||||
if err != nil {
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrParentBlockUnknown {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove the orphan from the orphan pool.
|
||||
orphanHash := orphan.block.Hash()
|
||||
dag.removeOrphanBlock(orphan)
|
||||
i--
|
||||
|
||||
// Potentially accept the block into the block DAG.
|
||||
err = dag.maybeAcceptBlock(orphan.block, flags|BFWasUnorphaned)
|
||||
if err != nil {
|
||||
// Since we don't want to reject the original block because of
|
||||
// a bad unorphaned child, only return an error if it's not a RuleError.
|
||||
if !errors.As(err, &RuleError{}) {
|
||||
return err
|
||||
}
|
||||
log.Warnf("Verification failed for orphan block %s: %s", orphanHash, err)
|
||||
}
|
||||
|
||||
// Add this block to the list of blocks to process so
|
||||
// any orphan blocks that depend on this block are
|
||||
// handled too.
|
||||
processHashes = append(processHashes, orphanHash)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ProcessBlock is the main workhorse for handling insertion of new blocks into
|
||||
// the block DAG. It includes functionality such as rejecting duplicate
|
||||
// blocks, ensuring blocks follow all rules, orphan handling, and insertion into
|
||||
// the block DAG.
|
||||
//
|
||||
// When no errors occurred during processing, the first return value indicates
|
||||
// whether or not the block is an orphan.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) ProcessBlock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
|
||||
dag.dagLock.Lock()
|
||||
defer dag.dagLock.Unlock()
|
||||
return dag.processBlockNoLock(block, flags)
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) processBlockNoLock(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
|
||||
isAfterDelay := flags&BFAfterDelay == BFAfterDelay
|
||||
wasBlockStored := flags&BFWasStored == BFWasStored
|
||||
disallowDelay := flags&BFDisallowDelay == BFDisallowDelay
|
||||
disallowOrphans := flags&BFDisallowOrphans == BFDisallowOrphans
|
||||
|
||||
blockHash := block.Hash()
|
||||
log.Tracef("Processing block %s", blockHash)
|
||||
|
||||
// The block must not already exist in the DAG.
|
||||
if dag.IsInDAG(blockHash) && !wasBlockStored {
|
||||
str := fmt.Sprintf("already have block %s", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
// The block must not already exist as an orphan.
|
||||
if _, exists := dag.orphans[*blockHash]; exists {
|
||||
str := fmt.Sprintf("already have block (orphan) %s", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
if dag.isKnownDelayedBlock(blockHash) {
|
||||
str := fmt.Sprintf("already have block (delayed) %s", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
if !isAfterDelay {
|
||||
// Perform preliminary sanity checks on the block and its transactions.
|
||||
delay, err := dag.checkBlockSanity(block, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if delay != 0 && disallowDelay {
|
||||
str := fmt.Sprintf("Cannot process blocks beyond the allowed time offset while the BFDisallowDelay flag is raised %s", blockHash)
|
||||
return false, true, ruleError(ErrDelayedBlockIsNotAllowed, str)
|
||||
}
|
||||
|
||||
if delay != 0 {
|
||||
err = dag.addDelayedBlock(block, delay)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
return false, true, nil
|
||||
}
|
||||
}
|
||||
|
||||
var missingParents []*daghash.Hash
|
||||
for _, parentHash := range block.MsgBlock().Header.ParentHashes {
|
||||
if !dag.IsInDAG(parentHash) {
|
||||
missingParents = append(missingParents, parentHash)
|
||||
}
|
||||
}
|
||||
if len(missingParents) > 0 && disallowOrphans {
|
||||
str := fmt.Sprintf("Cannot process orphan blocks while the BFDisallowOrphans flag is raised %s", blockHash)
|
||||
return false, false, ruleError(ErrOrphanBlockIsNotAllowed, str)
|
||||
}
|
||||
|
||||
// Handle the case of a block with a valid timestamp(non-delayed) which points to a delayed block.
|
||||
delay, isParentDelayed := dag.maxDelayOfParents(missingParents)
|
||||
if isParentDelayed {
|
||||
// Add Millisecond to ensure that parent process time will be after its child.
|
||||
delay += time.Millisecond
|
||||
err := dag.addDelayedBlock(block, delay)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
return false, true, err
|
||||
}
|
||||
|
||||
// Handle orphan blocks.
|
||||
if len(missingParents) > 0 {
|
||||
// Some orphans during netsync are a normal part of the process, since the anticone
|
||||
// of the chain-split is never explicitly requested.
|
||||
// Therefore, if we are during netsync - don't report orphans to default logs.
|
||||
//
|
||||
// The number K*2 was chosen since in peace times anticone is limited to K blocks,
|
||||
// while some red block can make it a bit bigger, but much more than that indicates
|
||||
// there might be some problem with the netsync process.
|
||||
if flags&BFIsSync == BFIsSync && dagconfig.KType(len(dag.orphans)) < dag.Params.K*2 {
|
||||
log.Debugf("Adding orphan block %s. This is normal part of netsync process", blockHash)
|
||||
} else {
|
||||
log.Infof("Adding orphan block %s", blockHash)
|
||||
}
|
||||
dag.addOrphanBlock(block)
|
||||
|
||||
return true, false, nil
|
||||
}
|
||||
|
||||
// The block has passed all context independent checks and appears sane
|
||||
// enough to potentially accept it into the block DAG.
|
||||
err = dag.maybeAcceptBlock(block, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
// Accept any orphan blocks that depend on this block (they are
|
||||
// no longer orphans) and repeat for those accepted blocks until
|
||||
// there are no more.
|
||||
err = dag.processOrphans(blockHash, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
if !isAfterDelay {
|
||||
err = dag.processDelayedBlocks()
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
}
|
||||
|
||||
dag.addBlockProcessingTimestamp()
|
||||
|
||||
log.Debugf("Accepted block %s", blockHash)
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
// maxDelayOfParents returns the maximum delay of the given block hashes.
|
||||
// Note that delay could be 0, but isDelayed will return true. This is the case where the parent process time is due.
|
||||
func (dag *BlockDAG) maxDelayOfParents(parentHashes []*daghash.Hash) (delay time.Duration, isDelayed bool) {
|
||||
for _, parentHash := range parentHashes {
|
||||
if delayedParent, exists := dag.delayedBlocks[*parentHash]; exists {
|
||||
isDelayed = true
|
||||
parentDelay := delayedParent.processTime.Sub(dag.Now())
|
||||
if parentDelay > delay {
|
||||
delay = parentDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return delay, isDelayed
|
||||
}
|
||||
@@ -1,14 +1,13 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -105,9 +104,6 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
blockDelay := time.Duration(dag1.Params.TimestampDeviationTolerance)*dag1.Params.TargetTimePerBlock + 5*time.Second
|
||||
delayedBlock.Header.Timestamp = initialTime.Add(blockDelay)
|
||||
|
||||
// We change the nonce here because processDelayedBlocks always runs without BFNoPoWCheck.
|
||||
delayedBlock.Header.Nonce = 2
|
||||
|
||||
isOrphan, isDelayed, err := dag1.ProcessBlock(util.NewBlock(delayedBlock), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Fatalf("ProcessBlock returned unexpected error: %s\n", err)
|
||||
@@ -240,101 +236,3 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
t.Errorf("delayedBlockChild shouldn't be added to the DAG because its parent has been added to the DAG")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaybeAcceptBlockErrors(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestMaybeAcceptBlockErrors", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
dag.TestSetCoinbaseMaturity(0)
|
||||
|
||||
// Test rejecting the block if its parents are missing
|
||||
orphanBlockFile := "blk_3B.dat"
|
||||
loadedBlocks, err := LoadBlocks(filepath.Join("testdata/", orphanBlockFile))
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: "+
|
||||
"Error loading file '%s': %s\n", orphanBlockFile, err)
|
||||
}
|
||||
block := loadedBlocks[0]
|
||||
|
||||
err = dag.maybeAcceptBlock(block, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Expected: %s, got: <nil>", ErrParentBlockUnknown)
|
||||
}
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); !ok {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Expected RuleError but got %s", err)
|
||||
} else if ruleErr.ErrorCode != ErrParentBlockUnknown {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are missing: "+
|
||||
"Unexpected error code. Want: %s, got: %s", ErrParentBlockUnknown, ruleErr.ErrorCode)
|
||||
}
|
||||
|
||||
// Test rejecting the block if its parents are invalid
|
||||
blocksFile := "blk_0_to_4.dat"
|
||||
blocks, err := LoadBlocks(filepath.Join("testdata/", blocksFile))
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: "+
|
||||
"Error loading file '%s': %s\n", blocksFile, err)
|
||||
}
|
||||
|
||||
// Add a valid block and mark it as invalid
|
||||
block1 := blocks[1]
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(block1, BFNone)
|
||||
if err != nil {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: Valid block unexpectedly returned an error: %s", err)
|
||||
}
|
||||
if isDelayed {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: block 1 is too far in the future")
|
||||
}
|
||||
if isOrphan {
|
||||
t.Fatalf("TestMaybeAcceptBlockErrors: incorrectly returned block 1 is an orphan")
|
||||
}
|
||||
blockNode1, ok := dag.index.LookupNode(block1.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", block1.Hash())
|
||||
}
|
||||
dag.index.SetStatusFlags(blockNode1, statusValidateFailed)
|
||||
|
||||
block2 := blocks[2]
|
||||
err = dag.maybeAcceptBlock(block2, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Expected: %s, got: <nil>", ErrInvalidAncestorBlock)
|
||||
}
|
||||
if ok := errors.As(err, &ruleErr); !ok {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Expected RuleError but got %s", err)
|
||||
} else if ruleErr.ErrorCode != ErrInvalidAncestorBlock {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block if its parents are invalid: "+
|
||||
"Unexpected error. Want: %s, got: %s", ErrInvalidAncestorBlock, ruleErr.ErrorCode)
|
||||
}
|
||||
|
||||
// Set block1's status back to valid for next tests
|
||||
dag.index.UnsetStatusFlags(blockNode1, statusValidateFailed)
|
||||
|
||||
// Test rejecting the block due to bad context
|
||||
originalBits := block2.MsgBlock().Header.Bits
|
||||
block2.MsgBlock().Header.Bits = 0
|
||||
err = dag.maybeAcceptBlock(block2, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Expected: %s, got: <nil>", ErrUnexpectedDifficulty)
|
||||
}
|
||||
if ok := errors.As(err, &ruleErr); !ok {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Expected RuleError but got %s", err)
|
||||
} else if ruleErr.ErrorCode != ErrUnexpectedDifficulty {
|
||||
t.Errorf("TestMaybeAcceptBlockErrors: rejecting the block due to bad context: "+
|
||||
"Unexpected error. Want: %s, got: %s", ErrUnexpectedDifficulty, ruleErr.ErrorCode)
|
||||
}
|
||||
|
||||
// Set block2's bits back to valid for next tests
|
||||
block2.MsgBlock().Header.Bits = originalBits
|
||||
}
|
||||
@@ -2,7 +2,7 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"strings"
|
||||
@@ -268,12 +268,6 @@ func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode, reindexRo
|
||||
rtn.children = append(rtn.children, child)
|
||||
child.parent = rtn
|
||||
|
||||
// Temporarily set the child's interval to be empty, at
|
||||
// the start of rtn's remaining interval. This is done
|
||||
// so that child-of-rtn checks (e.g.
|
||||
// findAncestorAmongChildren) will not fail for rtn.
|
||||
child.interval = newReachabilityInterval(remaining.start, remaining.start-1)
|
||||
|
||||
// Handle rtn not being a descendant of the reindex root.
|
||||
// Note that we check rtn here instead of child because
|
||||
// at this point we don't yet know child's interval.
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"reflect"
|
||||
"strings"
|
||||
@@ -1047,37 +1047,3 @@ func TestReindexIntervalsEarlierThanReindexRoot(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTipsAfterReindexIntervalsEarlierThanReindexRoot(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestTipsAfterReindexIntervalsEarlierThanReindexRoot", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// Set the reindex window to a low number to make this test run fast
|
||||
originalReachabilityReindexWindow := reachabilityReindexWindow
|
||||
reachabilityReindexWindow = 10
|
||||
defer func() {
|
||||
reachabilityReindexWindow = originalReachabilityReindexWindow
|
||||
}()
|
||||
|
||||
// Add a chain of reachabilityReindexWindow + 1 blocks above the genesis.
|
||||
// This will set the reindex root to the child of genesis
|
||||
chainTipHash := dag.Params.GenesisHash
|
||||
for i := uint64(0); i < reachabilityReindexWindow+1; i++ {
|
||||
block := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{chainTipHash}, nil)
|
||||
chainTipHash = block.BlockHash()
|
||||
}
|
||||
|
||||
// Add another block above the genesis block. This will trigger an
|
||||
// earlier-than-reindex-root reindex
|
||||
sideBlock := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{dag.Params.GenesisHash}, nil)
|
||||
|
||||
// Add a block whose parents are the chain tip and the side block.
|
||||
// We expect this not to fail
|
||||
PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{chainTipHash, sideBlock.BlockHash()}, nil)
|
||||
}
|
||||
@@ -2,9 +2,9 @@ package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/infrastructure/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
@@ -9,8 +9,8 @@ import (
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"runtime"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
)
|
||||
|
||||
// TestCheckBlockScripts ensures that validating the all of the scripts in a
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
@@ -155,50 +155,3 @@ func deserializeSubnetwork(serializedSNetBytes []byte) (*subnetwork, error) {
|
||||
gasLimit: gasLimit,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) validateGasLimit(block *util.Block) error {
|
||||
var currentSubnetworkID *subnetworkid.SubnetworkID
|
||||
var currentSubnetworkGasLimit uint64
|
||||
var currentGasUsage uint64
|
||||
var err error
|
||||
|
||||
// We assume here that transactions are ordered by subnetworkID,
|
||||
// since it was already validated in checkTransactionSanity
|
||||
for _, tx := range block.Transactions() {
|
||||
msgTx := tx.MsgTx()
|
||||
|
||||
// In native and Built-In subnetworks all txs must have Gas = 0, and that was already validated in checkTransactionSanity
|
||||
// Therefore - no need to check them here.
|
||||
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) || msgTx.SubnetworkID.IsBuiltIn() {
|
||||
continue
|
||||
}
|
||||
|
||||
if !msgTx.SubnetworkID.IsEqual(currentSubnetworkID) {
|
||||
currentSubnetworkID = &msgTx.SubnetworkID
|
||||
currentGasUsage = 0
|
||||
currentSubnetworkGasLimit, err = dag.GasLimit(currentSubnetworkID)
|
||||
if err != nil {
|
||||
return errors.Errorf("Error getting gas limit for subnetworkID '%s': %s", currentSubnetworkID, err)
|
||||
}
|
||||
}
|
||||
|
||||
newGasUsage := currentGasUsage + msgTx.Gas
|
||||
if newGasUsage < currentGasUsage { // check for overflow
|
||||
str := fmt.Sprintf("Block gas usage in subnetwork with ID %s has overflown", currentSubnetworkID)
|
||||
return ruleError(ErrInvalidGas, str)
|
||||
}
|
||||
if newGasUsage > currentSubnetworkGasLimit {
|
||||
str := fmt.Sprintf("Block wastes too much gas in subnetwork with ID %s", currentSubnetworkID)
|
||||
return ruleError(ErrInvalidGas, str)
|
||||
}
|
||||
|
||||
currentGasUsage = newGasUsage
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SubnetworkID returns the node's subnetwork ID
|
||||
func (dag *BlockDAG) SubnetworkID() *subnetworkid.SubnetworkID {
|
||||
return dag.subnetworkID
|
||||
}
|
||||
@@ -5,13 +5,7 @@ import (
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
syncRateWindowDuration = 15 * time.Minute
|
||||
|
||||
// isDAGCurrentMaxDiff is the number of blocks from the network tips (estimated by timestamps) for the current
|
||||
// to be considered not synced
|
||||
isDAGCurrentMaxDiff = 40_000
|
||||
)
|
||||
const syncRateWindowDuration = 15 * time.Minute
|
||||
|
||||
// addBlockProcessingTimestamp adds the last block processing timestamp in order to measure the recent sync rate.
|
||||
//
|
||||
@@ -64,40 +58,3 @@ func (dag *BlockDAG) IsSyncRateBelowThreshold(maxDeviation float64) bool {
|
||||
func (dag *BlockDAG) uptime() time.Duration {
|
||||
return mstime.Now().Sub(dag.startTime)
|
||||
}
|
||||
|
||||
// isSynced returns whether or not the DAG believes it is synced. Several
|
||||
// factors are used to guess, but the key factors that allow the DAG to
|
||||
// believe it is synced are:
|
||||
// - Latest block has a timestamp newer than 24 hours ago
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) isSynced() bool {
|
||||
// Not synced if the virtual's selected parent has a timestamp
|
||||
// before 24 hours ago. If the DAG is empty, we take the genesis
|
||||
// block timestamp.
|
||||
//
|
||||
// The DAG appears to be syncned if none of the checks reported
|
||||
// otherwise.
|
||||
var dagTimestamp int64
|
||||
selectedTip := dag.selectedTip()
|
||||
if selectedTip == nil {
|
||||
dagTimestamp = dag.Params.GenesisBlock.Header.Timestamp.UnixMilliseconds()
|
||||
} else {
|
||||
dagTimestamp = selectedTip.timestamp
|
||||
}
|
||||
dagTime := mstime.UnixMilliseconds(dagTimestamp)
|
||||
return dag.Now().Sub(dagTime) <= isDAGCurrentMaxDiff*dag.Params.TargetTimePerBlock
|
||||
}
|
||||
|
||||
// IsSynced returns whether or not the DAG believes it is synced. Several
|
||||
// factors are used to guess, but the key factors that allow the DAG to
|
||||
// believe it is synced are:
|
||||
// - Latest block has a timestamp newer than 24 hours ago
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) IsSynced() bool {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
return dag.isSynced()
|
||||
}
|
||||
@@ -14,16 +14,16 @@ import (
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/database/ffldb/ldb"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/database/ffldb/ldb"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/syndtr/goleveldb/leveldb/opt"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -245,29 +245,12 @@ func opTrueAddress(prefix util.Bech32Prefix) (util.Address, error) {
|
||||
|
||||
// PrepareBlockForTest generates a block with the proper merkle roots, coinbase transaction etc. This function is used for test purposes only
|
||||
func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactions []*domainmessage.MsgTx) (*domainmessage.MsgBlock, error) {
|
||||
parents := newBlockSet()
|
||||
for _, hash := range parentHashes {
|
||||
parent, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("parent %s was not found", hash)
|
||||
}
|
||||
parents.add(parent)
|
||||
}
|
||||
node, _ := dag.newBlockNode(nil, parents)
|
||||
|
||||
_, selectedParentPastUTXO, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
newVirtual, err := GetVirtualFromParentsForTest(dag, parentHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
calculatedAccepetedIDMerkleRoot := calculateAcceptedIDMerkleRoot(txsAcceptanceData)
|
||||
|
||||
multiset, err := node.calcMultiset(dag, txsAcceptanceData, selectedParentPastUTXO)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
|
||||
oldVirtual := SetVirtualForTest(dag, newVirtual)
|
||||
defer SetVirtualForTest(dag, oldVirtual)
|
||||
|
||||
OpTrueAddr, err := opTrueAddress(dag.Params.Prefix)
|
||||
if err != nil {
|
||||
@@ -282,12 +265,7 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
|
||||
return nil, err
|
||||
}
|
||||
|
||||
coinbasePayloadScriptPubKey, err := txscript.PayToAddrScript(OpTrueAddr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockTransactions[0], err = node.expectedCoinbaseTransaction(dag, txsAcceptanceData, coinbasePayloadScriptPubKey, coinbasePayloadExtraData)
|
||||
blockTransactions[0], err = dag.NextCoinbaseFromAddress(OpTrueAddr, coinbasePayloadExtraData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -307,28 +285,14 @@ func PrepareBlockForTest(dag *BlockDAG, parentHashes []*daghash.Hash, transactio
|
||||
return subnetworkid.Less(&blockTransactions[i].MsgTx().SubnetworkID, &blockTransactions[j].MsgTx().SubnetworkID)
|
||||
})
|
||||
|
||||
// Create a new block ready to be solved.
|
||||
hashMerkleTree := BuildHashMerkleTreeStore(blockTransactions)
|
||||
|
||||
var msgBlock domainmessage.MsgBlock
|
||||
for _, tx := range blockTransactions {
|
||||
msgBlock.AddTransaction(tx.MsgTx())
|
||||
block, err := dag.BlockForMining(blockTransactions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
block.Header.Timestamp = dag.NextBlockMinimumTime()
|
||||
block.Header.Bits = dag.NextRequiredDifficulty(block.Header.Timestamp)
|
||||
|
||||
timestamp := node.parents.bluest().PastMedianTime(dag)
|
||||
msgBlock.Header = domainmessage.BlockHeader{
|
||||
Version: blockVersion,
|
||||
|
||||
// We use parents.hashes() and not parentHashes because parents.hashes() is sorted.
|
||||
ParentHashes: parents.hashes(),
|
||||
HashMerkleRoot: hashMerkleTree.Root(),
|
||||
AcceptedIDMerkleRoot: calculatedAccepetedIDMerkleRoot,
|
||||
UTXOCommitment: &calculatedMultisetHash,
|
||||
Timestamp: timestamp,
|
||||
Bits: dag.requiredDifficulty(node.parents.bluest(), timestamp),
|
||||
}
|
||||
|
||||
return &msgBlock, nil
|
||||
return block, nil
|
||||
}
|
||||
|
||||
// PrepareAndProcessBlockForTest prepares a block that points to the given parent
|
||||
356
blockdag/thresholdstate.go
Normal file
356
blockdag/thresholdstate.go
Normal file
@@ -0,0 +1,356 @@
|
||||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ThresholdState define the various threshold states used when voting on
|
||||
// consensus changes.
|
||||
type ThresholdState byte
|
||||
|
||||
// These constants are used to identify specific threshold states.
|
||||
const (
|
||||
// ThresholdDefined is the first state for each deployment and is the
|
||||
// state for the genesis block has by definition for all deployments.
|
||||
ThresholdDefined ThresholdState = iota
|
||||
|
||||
// ThresholdStarted is the state for a deployment once its start time
|
||||
// has been reached.
|
||||
ThresholdStarted
|
||||
|
||||
// ThresholdLockedIn is the state for a deployment during the retarget
|
||||
// period which is after the ThresholdStarted state period and the
|
||||
// number of blocks that have voted for the deployment equal or exceed
|
||||
// the required number of votes for the deployment.
|
||||
ThresholdLockedIn
|
||||
|
||||
// ThresholdActive is the state for a deployment for all blocks after a
|
||||
// retarget period in which the deployment was in the ThresholdLockedIn
|
||||
// state.
|
||||
ThresholdActive
|
||||
|
||||
// ThresholdFailed is the state for a deployment once its expiration
|
||||
// time has been reached and it did not reach the ThresholdLockedIn
|
||||
// state.
|
||||
ThresholdFailed
|
||||
|
||||
// numThresholdsStates is the maximum number of threshold states used in
|
||||
// tests.
|
||||
numThresholdsStates
|
||||
)
|
||||
|
||||
// thresholdStateStrings is a map of ThresholdState values back to their
|
||||
// constant names for pretty printing.
|
||||
var thresholdStateStrings = map[ThresholdState]string{
|
||||
ThresholdDefined: "ThresholdDefined",
|
||||
ThresholdStarted: "ThresholdStarted",
|
||||
ThresholdLockedIn: "ThresholdLockedIn",
|
||||
ThresholdActive: "ThresholdActive",
|
||||
ThresholdFailed: "ThresholdFailed",
|
||||
}
|
||||
|
||||
// String returns the ThresholdState as a human-readable name.
|
||||
func (t ThresholdState) String() string {
|
||||
if s := thresholdStateStrings[t]; s != "" {
|
||||
return s
|
||||
}
|
||||
return fmt.Sprintf("Unknown ThresholdState (%d)", int(t))
|
||||
}
|
||||
|
||||
// thresholdConditionChecker provides a generic interface that is invoked to
|
||||
// determine when a consensus rule change threshold should be changed.
|
||||
type thresholdConditionChecker interface {
|
||||
// BeginTime returns the unix timestamp for the median block time after
|
||||
// which voting on a rule change starts (at the next window).
|
||||
BeginTime() uint64
|
||||
|
||||
// EndTime returns the unix timestamp for the median block time after
|
||||
// which an attempted rule change fails if it has not already been
|
||||
// locked in or activated.
|
||||
EndTime() uint64
|
||||
|
||||
// RuleChangeActivationThreshold is the number of blocks for which the
|
||||
// condition must be true in order to lock in a rule change.
|
||||
RuleChangeActivationThreshold() uint64
|
||||
|
||||
// MinerConfirmationWindow is the number of blocks in each threshold
|
||||
// state retarget window.
|
||||
MinerConfirmationWindow() uint64
|
||||
|
||||
// Condition returns whether or not the rule change activation condition
|
||||
// has been met. This typically involves checking whether or not the
|
||||
// bit associated with the condition is set, but can be more complex as
|
||||
// needed.
|
||||
Condition(*blockNode) (bool, error)
|
||||
}
|
||||
|
||||
// thresholdStateCache provides a type to cache the threshold states of each
|
||||
// threshold window for a set of IDs.
|
||||
type thresholdStateCache struct {
|
||||
entries map[daghash.Hash]ThresholdState
|
||||
}
|
||||
|
||||
// Lookup returns the threshold state associated with the given hash along with
|
||||
// a boolean that indicates whether or not it is valid.
|
||||
func (c *thresholdStateCache) Lookup(hash *daghash.Hash) (ThresholdState, bool) {
|
||||
state, ok := c.entries[*hash]
|
||||
return state, ok
|
||||
}
|
||||
|
||||
// Update updates the cache to contain the provided hash to threshold state
|
||||
// mapping.
|
||||
func (c *thresholdStateCache) Update(hash *daghash.Hash, state ThresholdState) {
|
||||
c.entries[*hash] = state
|
||||
}
|
||||
|
||||
// newThresholdCaches returns a new array of caches to be used when calculating
|
||||
// threshold states.
|
||||
func newThresholdCaches(numCaches uint32) []thresholdStateCache {
|
||||
caches := make([]thresholdStateCache, numCaches)
|
||||
for i := 0; i < len(caches); i++ {
|
||||
caches[i] = thresholdStateCache{
|
||||
entries: make(map[daghash.Hash]ThresholdState),
|
||||
}
|
||||
}
|
||||
return caches
|
||||
}
|
||||
|
||||
// thresholdState returns the current rule change threshold state for the block
|
||||
// AFTER the given node and deployment ID. The cache is used to ensure the
|
||||
// threshold states for previous windows are only calculated once.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) thresholdState(prevNode *blockNode, checker thresholdConditionChecker, cache *thresholdStateCache) (ThresholdState, error) {
|
||||
// The threshold state for the window that contains the genesis block is
|
||||
// defined by definition.
|
||||
confirmationWindow := checker.MinerConfirmationWindow()
|
||||
if prevNode == nil || (prevNode.blueScore+1) < confirmationWindow {
|
||||
return ThresholdDefined, nil
|
||||
}
|
||||
|
||||
// Get the ancestor that is the last block of the previous confirmation
|
||||
// window in order to get its threshold state. This can be done because
|
||||
// the state is the same for all blocks within a given window.
|
||||
prevNode = prevNode.SelectedAncestor(prevNode.blueScore -
|
||||
(prevNode.blueScore+1)%confirmationWindow)
|
||||
|
||||
// Iterate backwards through each of the previous confirmation windows
|
||||
// to find the most recently cached threshold state.
|
||||
var neededStates []*blockNode
|
||||
for prevNode != nil {
|
||||
// Nothing more to do if the state of the block is already
|
||||
// cached.
|
||||
if _, ok := cache.Lookup(prevNode.hash); ok {
|
||||
break
|
||||
}
|
||||
|
||||
// The start and expiration times are based on the median block
|
||||
// time, so calculate it now.
|
||||
medianTime := prevNode.PastMedianTime(dag)
|
||||
|
||||
// The state is simply defined if the start time hasn't been
|
||||
// been reached yet.
|
||||
if uint64(medianTime.UnixMilliseconds()) < checker.BeginTime() {
|
||||
cache.Update(prevNode.hash, ThresholdDefined)
|
||||
break
|
||||
}
|
||||
|
||||
// Add this node to the list of nodes that need the state
|
||||
// calculated and cached.
|
||||
neededStates = append(neededStates, prevNode)
|
||||
|
||||
// Get the ancestor that is the last block of the previous
|
||||
// confirmation window.
|
||||
prevNode = prevNode.RelativeAncestor(confirmationWindow)
|
||||
}
|
||||
|
||||
// Start with the threshold state for the most recent confirmation
|
||||
// window that has a cached state.
|
||||
state := ThresholdDefined
|
||||
if prevNode != nil {
|
||||
var ok bool
|
||||
state, ok = cache.Lookup(prevNode.hash)
|
||||
if !ok {
|
||||
return ThresholdFailed, errors.Errorf(
|
||||
"thresholdState: cache lookup failed for %s",
|
||||
prevNode.hash)
|
||||
}
|
||||
}
|
||||
|
||||
// Since each threshold state depends on the state of the previous
|
||||
// window, iterate starting from the oldest unknown window.
|
||||
for neededNum := len(neededStates) - 1; neededNum >= 0; neededNum-- {
|
||||
prevNode := neededStates[neededNum]
|
||||
|
||||
switch state {
|
||||
case ThresholdDefined:
|
||||
// The deployment of the rule change fails if it expires
|
||||
// before it is accepted and locked in.
|
||||
medianTime := prevNode.PastMedianTime(dag)
|
||||
medianTimeUnix := uint64(medianTime.UnixMilliseconds())
|
||||
if medianTimeUnix >= checker.EndTime() {
|
||||
state = ThresholdFailed
|
||||
break
|
||||
}
|
||||
|
||||
// The state for the rule moves to the started state
|
||||
// once its start time has been reached (and it hasn't
|
||||
// already expired per the above).
|
||||
if medianTimeUnix >= checker.BeginTime() {
|
||||
state = ThresholdStarted
|
||||
}
|
||||
|
||||
case ThresholdStarted:
|
||||
// The deployment of the rule change fails if it expires
|
||||
// before it is accepted and locked in.
|
||||
medianTime := prevNode.PastMedianTime(dag)
|
||||
if uint64(medianTime.UnixMilliseconds()) >= checker.EndTime() {
|
||||
state = ThresholdFailed
|
||||
break
|
||||
}
|
||||
|
||||
// At this point, the rule change is still being voted
|
||||
// on by the miners, so iterate backwards through the
|
||||
// confirmation window to count all of the votes in it.
|
||||
var count uint64
|
||||
windowNodes := make([]*blockNode, 0, confirmationWindow)
|
||||
windowNodes = append(windowNodes, prevNode)
|
||||
windowNodes = append(windowNodes, blueBlockWindow(prevNode, confirmationWindow-1)...)
|
||||
for _, current := range windowNodes {
|
||||
condition, err := checker.Condition(current)
|
||||
if err != nil {
|
||||
return ThresholdFailed, err
|
||||
}
|
||||
if condition {
|
||||
count++
|
||||
}
|
||||
}
|
||||
|
||||
// The state is locked in if the number of blocks in the
|
||||
// period that voted for the rule change meets the
|
||||
// activation threshold.
|
||||
if count >= checker.RuleChangeActivationThreshold() {
|
||||
state = ThresholdLockedIn
|
||||
}
|
||||
|
||||
case ThresholdLockedIn:
|
||||
// The new rule becomes active when its previous state
|
||||
// was locked in.
|
||||
state = ThresholdActive
|
||||
|
||||
// Nothing to do if the previous state is active or failed since
|
||||
// they are both terminal states.
|
||||
case ThresholdActive:
|
||||
case ThresholdFailed:
|
||||
}
|
||||
|
||||
// Update the cache to avoid recalculating the state in the
|
||||
// future.
|
||||
cache.Update(prevNode.hash, state)
|
||||
}
|
||||
|
||||
return state, nil
|
||||
}
|
||||
|
||||
// ThresholdState returns the current rule change threshold state of the given
|
||||
// deployment ID for the block AFTER the blueScore of the current DAG.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) ThresholdState(deploymentID uint32) (ThresholdState, error) {
|
||||
dag.dagLock.Lock()
|
||||
defer dag.dagLock.Unlock()
|
||||
state, err := dag.deploymentState(dag.selectedTip(), deploymentID)
|
||||
|
||||
return state, err
|
||||
}
|
||||
|
||||
// IsDeploymentActive returns true if the target deploymentID is active, and
|
||||
// false otherwise.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) IsDeploymentActive(deploymentID uint32) (bool, error) {
|
||||
dag.dagLock.Lock()
|
||||
defer dag.dagLock.Unlock()
|
||||
state, err := dag.deploymentState(dag.selectedTip(), deploymentID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return state == ThresholdActive, nil
|
||||
}
|
||||
|
||||
// deploymentState returns the current rule change threshold for a given
|
||||
// deploymentID. The threshold is evaluated from the point of view of the block
|
||||
// node passed in as the first argument to this method.
|
||||
//
|
||||
// It is important to note that, as the variable name indicates, this function
|
||||
// expects the block node prior to the block for which the deployment state is
|
||||
// desired. In other words, the returned deployment state is for the block
|
||||
// AFTER the passed node.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) {
|
||||
if deploymentID > uint32(len(dag.Params.Deployments)) {
|
||||
return ThresholdFailed, errors.Errorf("deployment ID %d does not exist", deploymentID)
|
||||
}
|
||||
|
||||
deployment := &dag.Params.Deployments[deploymentID]
|
||||
checker := deploymentChecker{deployment: deployment, dag: dag}
|
||||
cache := &dag.deploymentCaches[deploymentID]
|
||||
|
||||
return dag.thresholdState(prevNode, checker, cache)
|
||||
}
|
||||
|
||||
// initThresholdCaches initializes the threshold state caches for each warning
|
||||
// bit and defined deployment and provides warnings if the DAG is current per
|
||||
// the warnUnknownVersions and warnUnknownRuleActivations functions.
|
||||
func (dag *BlockDAG) initThresholdCaches() error {
|
||||
// Initialize the warning and deployment caches by calculating the
|
||||
// threshold state for each of them. This will ensure the caches are
|
||||
// populated and any states that needed to be recalculated due to
|
||||
// definition changes is done now.
|
||||
prevNode := dag.selectedTip().selectedParent
|
||||
for bit := uint32(0); bit < vbNumBits; bit++ {
|
||||
checker := bitConditionChecker{bit: bit, dag: dag}
|
||||
cache := &dag.warningCaches[bit]
|
||||
_, err := dag.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for id := 0; id < len(dag.Params.Deployments); id++ {
|
||||
deployment := &dag.Params.Deployments[id]
|
||||
cache := &dag.deploymentCaches[id]
|
||||
checker := deploymentChecker{deployment: deployment, dag: dag}
|
||||
_, err := dag.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// No warnings about unknown rules or versions until the DAG is
|
||||
// synced.
|
||||
if dag.isSynced() {
|
||||
// Warn if a high enough percentage of the last blocks have
|
||||
// unexpected versions.
|
||||
bestNode := dag.selectedTip()
|
||||
if err := dag.warnUnknownVersions(bestNode); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Warn if any unknown new rules are either about to activate or
|
||||
// have already been activated.
|
||||
if err := dag.warnUnknownRuleActivations(bestNode); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
134
blockdag/thresholdstate_test.go
Normal file
134
blockdag/thresholdstate_test.go
Normal file
@@ -0,0 +1,134 @@
|
||||
// Copyright (c) 2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestThresholdStateStringer tests the stringized output for the
|
||||
// ThresholdState type.
|
||||
func TestThresholdStateStringer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in ThresholdState
|
||||
want string
|
||||
}{
|
||||
{ThresholdDefined, "ThresholdDefined"},
|
||||
{ThresholdStarted, "ThresholdStarted"},
|
||||
{ThresholdLockedIn, "ThresholdLockedIn"},
|
||||
{ThresholdActive, "ThresholdActive"},
|
||||
{ThresholdFailed, "ThresholdFailed"},
|
||||
{0xff, "Unknown ThresholdState (255)"},
|
||||
}
|
||||
|
||||
// Detect additional threshold states that don't have the stringer added.
|
||||
if len(tests)-1 != int(numThresholdsStates) {
|
||||
t.Errorf("It appears a threshold statewas added without " +
|
||||
"adding an associated stringer test")
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.String()
|
||||
if result != test.want {
|
||||
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestThresholdStateCache ensure the threshold state cache works as intended
|
||||
// including adding entries, updating existing entries, and flushing.
|
||||
func TestThresholdStateCache(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
numEntries int
|
||||
state ThresholdState
|
||||
}{
|
||||
{name: "2 entries defined", numEntries: 2, state: ThresholdDefined},
|
||||
{name: "7 entries started", numEntries: 7, state: ThresholdStarted},
|
||||
{name: "10 entries active", numEntries: 10, state: ThresholdActive},
|
||||
{name: "5 entries locked in", numEntries: 5, state: ThresholdLockedIn},
|
||||
{name: "3 entries failed", numEntries: 3, state: ThresholdFailed},
|
||||
}
|
||||
|
||||
nextTest:
|
||||
for _, test := range tests {
|
||||
cache := &newThresholdCaches(1)[0]
|
||||
for i := 0; i < test.numEntries; i++ {
|
||||
var hash daghash.Hash
|
||||
hash[0] = uint8(i + 1)
|
||||
|
||||
// Ensure the hash isn't available in the cache already.
|
||||
_, ok := cache.Lookup(&hash)
|
||||
if ok {
|
||||
t.Errorf("Lookup (%s): has entry for hash %v",
|
||||
test.name, hash)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure hash that was added to the cache reports it's
|
||||
// available and the state is the expected value.
|
||||
cache.Update(&hash, test.state)
|
||||
state, ok := cache.Lookup(&hash)
|
||||
if !ok {
|
||||
t.Errorf("Lookup (%s): missing entry for hash "+
|
||||
"%v", test.name, hash)
|
||||
continue nextTest
|
||||
}
|
||||
if state != test.state {
|
||||
t.Errorf("Lookup (%s): state mismatch - got "+
|
||||
"%v, want %v", test.name, state,
|
||||
test.state)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure adding an existing hash with the same state
|
||||
// doesn't break the existing entry.
|
||||
cache.Update(&hash, test.state)
|
||||
state, ok = cache.Lookup(&hash)
|
||||
if !ok {
|
||||
t.Errorf("Lookup (%s): missing entry after "+
|
||||
"second add for hash %v", test.name,
|
||||
hash)
|
||||
continue nextTest
|
||||
}
|
||||
if state != test.state {
|
||||
t.Errorf("Lookup (%s): state mismatch after "+
|
||||
"second add - got %v, want %v",
|
||||
test.name, state, test.state)
|
||||
continue nextTest
|
||||
}
|
||||
|
||||
// Ensure adding an existing hash with a different state
|
||||
// updates the existing entry.
|
||||
newState := ThresholdFailed
|
||||
if newState == test.state {
|
||||
newState = ThresholdStarted
|
||||
}
|
||||
cache.Update(&hash, newState)
|
||||
state, ok = cache.Lookup(&hash)
|
||||
if !ok {
|
||||
t.Errorf("Lookup (%s): missing entry after "+
|
||||
"state change for hash %v", test.name,
|
||||
hash)
|
||||
continue nextTest
|
||||
}
|
||||
if state != newState {
|
||||
t.Errorf("Lookup (%s): state mismatch after "+
|
||||
"state change - got %v, want %v",
|
||||
test.name, state, newState)
|
||||
continue nextTest
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
func addUTXOToMultiset(ms *secp256k1.MultiSet, entry *UTXOEntry, outpoint *domainmessage.Outpoint) (*secp256k1.MultiSet, error) {
|
||||
@@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/locks"
|
||||
)
|
||||
@@ -4,9 +4,9 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -2,7 +2,7 @@ package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -6,17 +6,16 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
@@ -128,55 +127,13 @@ func CalcBlockSubsidy(blueScore uint64, dagParams *dagconfig.Params) uint64 {
|
||||
// CheckTransactionSanity performs some preliminary checks on a transaction to
|
||||
// ensure it is sane. These checks are context free.
|
||||
func CheckTransactionSanity(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
|
||||
err := checkTransactionInputCount(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkTransactionAmountRanges(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkDuplicateTransactionInputs(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkCoinbaseLength(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkTransactionPayloadHash(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkGasInBuiltInOrNativeTransactions(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkSubnetworkRegistryTransaction(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkNativeTransactionPayload(tx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = checkTransactionSubnetwork(tx, subnetworkID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTransactionInputCount(tx *util.Tx) error {
|
||||
// A non-coinbase transaction must have at least one input.
|
||||
isCoinbase := tx.IsCoinBase()
|
||||
// A transaction must have at least one input.
|
||||
msgTx := tx.MsgTx()
|
||||
if !tx.IsCoinBase() && len(msgTx.TxIn) == 0 {
|
||||
if !isCoinbase && len(msgTx.TxIn) == 0 {
|
||||
return ruleError(ErrNoTxInputs, "transaction has no inputs")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTransactionAmountRanges(tx *util.Tx) error {
|
||||
// Ensure the transaction amounts are in range. Each transaction
|
||||
// output must not be negative or more than the max allowed per
|
||||
// transaction. Also, the total of all outputs must abide by the same
|
||||
@@ -184,7 +141,7 @@ func checkTransactionAmountRanges(tx *util.Tx) error {
|
||||
// as a sompi. One kaspa is a quantity of sompi as defined by the
|
||||
// SompiPerKaspa constant.
|
||||
var totalSompi uint64
|
||||
for _, txOut := range tx.MsgTx().TxOut {
|
||||
for _, txOut := range msgTx.TxOut {
|
||||
sompi := txOut.Value
|
||||
if sompi > util.MaxSompi {
|
||||
str := fmt.Sprintf("transaction output value of %d is "+
|
||||
@@ -212,25 +169,20 @@ func checkTransactionAmountRanges(tx *util.Tx) error {
|
||||
return ruleError(ErrBadTxOutValue, str)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDuplicateTransactionInputs(tx *util.Tx) error {
|
||||
// Check for duplicate transaction inputs.
|
||||
existingTxOut := make(map[domainmessage.Outpoint]struct{})
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
if _, exists := existingTxOut[txIn.PreviousOutpoint]; exists {
|
||||
return ruleError(ErrDuplicateTxInputs, "transaction "+
|
||||
"contains duplicate inputs")
|
||||
}
|
||||
existingTxOut[txIn.PreviousOutpoint] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkCoinbaseLength(tx *util.Tx) error {
|
||||
// Coinbase payload length must not exceed the max length.
|
||||
if tx.IsCoinBase() {
|
||||
payloadLen := len(tx.MsgTx().Payload)
|
||||
if isCoinbase {
|
||||
payloadLen := len(msgTx.Payload)
|
||||
if payloadLen > MaxCoinbasePayloadLen {
|
||||
str := fmt.Sprintf("coinbase transaction payload length "+
|
||||
"of %d is out of range (max: %d)",
|
||||
@@ -240,7 +192,7 @@ func checkCoinbaseLength(tx *util.Tx) error {
|
||||
} else {
|
||||
// Previous transaction outputs referenced by the inputs to this
|
||||
// transaction must not be null.
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
if isNullOutpoint(&txIn.PreviousOutpoint) {
|
||||
return ruleError(ErrBadTxInput, "transaction "+
|
||||
"input refers to previous output that "+
|
||||
@@ -248,11 +200,8 @@ func checkCoinbaseLength(tx *util.Tx) error {
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTransactionPayloadHash(tx *util.Tx) error {
|
||||
msgTx := tx.MsgTx()
|
||||
// Check payload's hash
|
||||
if !msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
payloadHash := daghash.DoubleHashH(msgTx.Payload)
|
||||
if !msgTx.PayloadHash.IsEqual(&payloadHash) {
|
||||
@@ -261,41 +210,32 @@ func checkTransactionPayloadHash(tx *util.Tx) error {
|
||||
} else if msgTx.PayloadHash != nil {
|
||||
return ruleError(ErrInvalidPayloadHash, "unexpected non-empty payload hash in native subnetwork")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkGasInBuiltInOrNativeTransactions(tx *util.Tx) error {
|
||||
// Transactions in native, registry and coinbase subnetworks must have Gas = 0
|
||||
msgTx := tx.MsgTx()
|
||||
if msgTx.SubnetworkID.IsBuiltInOrNative() && msgTx.Gas > 0 {
|
||||
if (msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
|
||||
msgTx.SubnetworkID.IsBuiltIn()) &&
|
||||
msgTx.Gas > 0 {
|
||||
|
||||
return ruleError(ErrInvalidGas, "transaction in the native or "+
|
||||
"registry subnetworks has gas > 0 ")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkSubnetworkRegistryTransaction(tx *util.Tx) error {
|
||||
if tx.MsgTx().SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) {
|
||||
err := validateSubnetworkRegistryTransaction(tx.MsgTx())
|
||||
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDRegistry) {
|
||||
err := validateSubnetworkRegistryTransaction(msgTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkNativeTransactionPayload(tx *util.Tx) error {
|
||||
msgTx := tx.MsgTx()
|
||||
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) && len(msgTx.Payload) > 0 {
|
||||
return ruleError(ErrInvalidPayload, "transaction in the native subnetwork includes a payload")
|
||||
if msgTx.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) &&
|
||||
len(msgTx.Payload) > 0 {
|
||||
|
||||
return ruleError(ErrInvalidPayload,
|
||||
"transaction in the native subnetwork includes a payload")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.SubnetworkID) error {
|
||||
// If we are a partial node, only transactions on built in subnetworks
|
||||
// or our own subnetwork may have a payload
|
||||
msgTx := tx.MsgTx()
|
||||
isLocalNodeFull := subnetworkID == nil
|
||||
shouldTxBeFull := msgTx.SubnetworkID.IsBuiltIn() ||
|
||||
msgTx.SubnetworkID.IsEqual(subnetworkID)
|
||||
@@ -304,6 +244,7 @@ func checkTransactionSubnetwork(tx *util.Tx, subnetworkID *subnetworkid.Subnetwo
|
||||
"transaction that was expected to be partial has a payload "+
|
||||
"with length > 0")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -451,28 +392,37 @@ func CalcTxMass(tx *util.Tx, previousScriptPubKeys [][]byte) uint64 {
|
||||
//
|
||||
// The flags do not modify the behavior of this function directly, however they
|
||||
// are needed to pass along to checkProofOfWork.
|
||||
func (dag *BlockDAG) checkBlockHeaderSanity(block *util.Block, flags BehaviorFlags) error {
|
||||
func (dag *BlockDAG) checkBlockHeaderSanity(block *util.Block, flags BehaviorFlags) (delay time.Duration, err 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.
|
||||
header := &block.MsgBlock().Header
|
||||
err := dag.checkProofOfWork(header, flags)
|
||||
err = dag.checkProofOfWork(header, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(header.ParentHashes) == 0 {
|
||||
if !header.BlockHash().IsEqual(dag.Params.GenesisHash) {
|
||||
return ruleError(ErrNoParents, "block has no parents")
|
||||
return 0, ruleError(ErrNoParents, "block has no parents")
|
||||
}
|
||||
} else {
|
||||
err = checkBlockParentsOrder(header)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
// Ensure the block time is not too far in the future. If it's too far, return
|
||||
// the duration of time that should be waited before the block becomes valid.
|
||||
// This check needs to be last as it does not return an error but rather marks the
|
||||
// header as delayed (and valid).
|
||||
maxTimestamp := dag.Now().Add(time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock)
|
||||
if header.Timestamp.After(maxTimestamp) {
|
||||
return header.Timestamp.Sub(maxTimestamp), nil
|
||||
}
|
||||
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
//checkBlockParentsOrder ensures that the block's parents are ordered by hash
|
||||
@@ -495,56 +445,56 @@ func checkBlockParentsOrder(header *domainmessage.BlockHeader) error {
|
||||
//
|
||||
// The flags do not modify the behavior of this function directly, however they
|
||||
// are needed to pass along to checkBlockHeaderSanity.
|
||||
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) error {
|
||||
err := dag.checkBlockHeaderSanity(block, flags)
|
||||
func (dag *BlockDAG) checkBlockSanity(block *util.Block, flags BehaviorFlags) (time.Duration, error) {
|
||||
delay, err := dag.checkBlockHeaderSanity(block, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkBlockContainsAtLeastOneTransaction(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkBlockContainsLessThanMaxBlockMassTransactions(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkFirstBlockTransactionIsCoinbase(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkBlockContainsOnlyOneCoinbase(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkBlockTransactionOrder(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkNoNonNativeTransactions(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkBlockTransactionSanity(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
err = dag.checkBlockHashMerkleRoot(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// The following check will be fairly quick since the transaction IDs
|
||||
// are already cached due to building the merkle tree above.
|
||||
err = dag.checkBlockDuplicateTransactions(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
|
||||
err = dag.checkBlockDoubleSpends(block)
|
||||
if err != nil {
|
||||
return err
|
||||
return 0, err
|
||||
}
|
||||
return nil
|
||||
return delay, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkBlockContainsAtLeastOneTransaction(block *util.Block) error {
|
||||
@@ -767,16 +717,11 @@ func (dag *BlockDAG) validateParents(blockHeader *domainmessage.BlockHeader, par
|
||||
// for how the flags modify its behavior.
|
||||
//
|
||||
// This function MUST be called with the dag state lock held (for writes).
|
||||
func (dag *BlockDAG) checkBlockContext(block *util.Block, flags BehaviorFlags) error {
|
||||
parents, err := lookupParentNodes(block, dag)
|
||||
if err != nil {
|
||||
return dag.handleLookupParentNodesError(block, err)
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkBlockContext(block *util.Block, parents blockSet, flags BehaviorFlags) error {
|
||||
bluestParent := parents.bluest()
|
||||
fastAdd := flags&BFFastAdd == BFFastAdd
|
||||
|
||||
err = dag.validateParents(&block.MsgBlock().Header, parents)
|
||||
err := dag.validateParents(&block.MsgBlock().Header, parents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -790,26 +735,10 @@ func (dag *BlockDAG) checkBlockContext(block *util.Block, flags BehaviorFlags) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) handleLookupParentNodesError(block *util.Block, err error) error {
|
||||
var ruleErr RuleError
|
||||
if ok := errors.As(err, &ruleErr); ok && ruleErr.ErrorCode == ErrInvalidAncestorBlock {
|
||||
err := dag.addNodeToIndexWithInvalidAncestor(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkBlockTransactionsFinalized(block *util.Block, node *blockNode, flags BehaviorFlags) error {
|
||||
fastAdd := flags&BFFastAdd == BFFastAdd || dag.index.NodeStatus(node).KnownValid()
|
||||
if fastAdd {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) validateAllTxsFinalized(block *util.Block, node *blockNode, bluestParent *blockNode) error {
|
||||
blockTime := block.MsgBlock().Header.Timestamp
|
||||
if !block.IsGenesis() {
|
||||
blockTime = node.selectedParent.PastMedianTime(dag)
|
||||
blockTime = bluestParent.PastMedianTime(dag)
|
||||
}
|
||||
|
||||
// Ensure all transactions in the block are finalized.
|
||||
@@ -1050,7 +979,7 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
|
||||
// A transaction can only be included within a block
|
||||
// once the sequence locks of *all* its inputs are
|
||||
// active.
|
||||
sequenceLock, err := dag.calcSequenceLock(block, pastUTXO, tx)
|
||||
sequenceLock, err := dag.calcSequenceLock(block, pastUTXO, tx, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -1075,18 +1004,6 @@ func (dag *BlockDAG) checkConnectToPastUTXO(block *blockNode, pastUTXO UTXOSet,
|
||||
return feeData, nil
|
||||
}
|
||||
|
||||
func (node *blockNode) validateUTXOCommitment(multiset *secp256k1.MultiSet) error {
|
||||
calculatedMultisetHash := daghash.Hash(*multiset.Finalize())
|
||||
if !calculatedMultisetHash.IsEqual(node.utxoCommitment) {
|
||||
str := fmt.Sprintf("block %s UTXO commitment is invalid - block "+
|
||||
"header indicates %s, but calculated value is %s", node.hash,
|
||||
node.utxoCommitment, calculatedMultisetHash)
|
||||
return ruleError(ErrBadUTXOCommitment, str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CheckConnectBlockTemplate fully validates that connecting the passed block to
|
||||
// the DAG does not violate any consensus rules, aside from the proof of
|
||||
// work requirement.
|
||||
@@ -1108,17 +1025,21 @@ func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
|
||||
|
||||
header := block.MsgBlock().Header
|
||||
|
||||
err := dag.checkBlockSanity(block, flags)
|
||||
delay, err := dag.checkBlockSanity(block, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, isDelayed := dag.shouldBlockBeDelayed(block)
|
||||
if isDelayed {
|
||||
if delay != 0 {
|
||||
return errors.Errorf("Block timestamp is too far in the future")
|
||||
}
|
||||
|
||||
err = dag.checkBlockContext(block, flags)
|
||||
parents, err := lookupParentNodes(block, dag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.checkBlockContext(block, parents, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1130,24 +1051,3 @@ func (dag *BlockDAG) CheckConnectBlockTemplateNoLock(block *util.Block) error {
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkDuplicateBlock(blockHash *daghash.Hash, flags BehaviorFlags) error {
|
||||
wasBlockStored := flags&BFWasStored == BFWasStored
|
||||
if dag.IsInDAG(blockHash) && !wasBlockStored {
|
||||
str := fmt.Sprintf("already have block %s", blockHash)
|
||||
return ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
// The block must not already exist as an orphan.
|
||||
if _, exists := dag.orphans[*blockHash]; exists {
|
||||
str := fmt.Sprintf("already have block (orphan) %s", blockHash)
|
||||
return ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
if dag.isKnownDelayedBlock(blockHash) {
|
||||
str := fmt.Sprintf("already have block (delayed) %s", blockHash)
|
||||
return ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
@@ -172,13 +172,16 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
if len(block.Transactions()) < 3 {
|
||||
t.Fatalf("Too few transactions in block, expect at least 3, got %v", len(block.Transactions()))
|
||||
}
|
||||
err = dag.checkBlockSanity(block, BFNone)
|
||||
delay, err := dag.checkBlockSanity(block, BFNone)
|
||||
if err != nil {
|
||||
t.Errorf("CheckBlockSanity: %v", err)
|
||||
}
|
||||
if delay != 0 {
|
||||
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
|
||||
}
|
||||
// Test with block with wrong transactions sorting order
|
||||
blockWithWrongTxOrder := util.NewBlock(&BlockWithWrongTxOrder)
|
||||
err = dag.checkBlockSanity(blockWithWrongTxOrder, BFNone)
|
||||
delay, err = dag.checkBlockSanity(blockWithWrongTxOrder, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("CheckBlockSanity: transactions disorder is not detected")
|
||||
}
|
||||
@@ -189,6 +192,9 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
t.Errorf("CheckBlockSanity: wrong error returned, expect ErrTransactionsNotSorted, got"+
|
||||
" %v, err %s", ruleErr.ErrorCode, err)
|
||||
}
|
||||
if delay != 0 {
|
||||
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
|
||||
}
|
||||
|
||||
var invalidParentsOrderBlock = domainmessage.MsgBlock{
|
||||
Header: domainmessage.BlockHeader{
|
||||
@@ -457,7 +463,7 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
}
|
||||
|
||||
utilInvalidBlock := util.NewBlock(&invalidParentsOrderBlock)
|
||||
err = dag.checkBlockSanity(utilInvalidBlock, BFNone)
|
||||
delay, err = dag.checkBlockSanity(utilInvalidBlock, BFNone)
|
||||
if err == nil {
|
||||
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
|
||||
}
|
||||
@@ -467,6 +473,21 @@ func TestCheckBlockSanity(t *testing.T) {
|
||||
} else if rError.ErrorCode != ErrWrongParentsOrder {
|
||||
t.Errorf("CheckBlockSanity: Expected error was ErrWrongParentsOrder but got %v", err)
|
||||
}
|
||||
if delay != 0 {
|
||||
t.Errorf("CheckBlockSanity: unexpected return %s delay", delay)
|
||||
}
|
||||
|
||||
blockInTheFuture := Block100000
|
||||
expectedDelay := 10 * time.Second
|
||||
deviationTolerance := time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock
|
||||
blockInTheFuture.Header.Timestamp = dag.Now().Add(deviationTolerance + expectedDelay)
|
||||
delay, err = dag.checkBlockSanity(util.NewBlock(&blockInTheFuture), BFNoPoWCheck)
|
||||
if err != nil {
|
||||
t.Errorf("CheckBlockSanity: %v", err)
|
||||
}
|
||||
if delay != expectedDelay {
|
||||
t.Errorf("CheckBlockSanity: expected %s delay but got %s", expectedDelay, delay)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPastMedianTime(t *testing.T) {
|
||||
294
blockdag/versionbits.go
Normal file
294
blockdag/versionbits.go
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
)
|
||||
|
||||
const (
|
||||
// vbTopBits defines the bits to set in the version to signal that the
|
||||
// version bits scheme is being used.
|
||||
vbTopBits = 0x10000000
|
||||
|
||||
// vbTopMask is the bitmask to use to determine whether or not the
|
||||
// version bits scheme is in use.
|
||||
vbTopMask = 0xe0000000
|
||||
|
||||
// vbNumBits is the total number of bits available for use with the
|
||||
// version bits scheme.
|
||||
vbNumBits = 29
|
||||
|
||||
// unknownVerNumToCheck is the number of previous blocks to consider
|
||||
// when checking for a threshold of unknown block versions for the
|
||||
// purposes of warning the user.
|
||||
unknownVerNumToCheck = 100
|
||||
|
||||
// unknownVerWarnNum is the threshold of previous blocks that have an
|
||||
// unknown version to use for the purposes of warning the user.
|
||||
unknownVerWarnNum = unknownVerNumToCheck / 2
|
||||
)
|
||||
|
||||
// bitConditionChecker provides a thresholdConditionChecker which can be used to
|
||||
// test whether or not a specific bit is set when it's not supposed to be
|
||||
// according to the expected version based on the known deployments and the
|
||||
// current state of the DAG. This is useful for detecting and warning about
|
||||
// unknown rule activations.
|
||||
type bitConditionChecker struct {
|
||||
bit uint32
|
||||
dag *BlockDAG
|
||||
}
|
||||
|
||||
// Ensure the bitConditionChecker type implements the thresholdConditionChecker
|
||||
// interface.
|
||||
var _ thresholdConditionChecker = bitConditionChecker{}
|
||||
|
||||
// BeginTime returns the unix timestamp for the median block time after which
|
||||
// voting on a rule change starts (at the next window).
|
||||
//
|
||||
// Since this implementation checks for unknown rules, it returns 0 so the rule
|
||||
// is always treated as active.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) BeginTime() uint64 {
|
||||
return 0
|
||||
}
|
||||
|
||||
// EndTime returns the unix timestamp for the median block time after which an
|
||||
// attempted rule change fails if it has not already been locked in or
|
||||
// activated.
|
||||
//
|
||||
// Since this implementation checks for unknown rules, it returns the maximum
|
||||
// possible timestamp so the rule is always treated as active.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) EndTime() uint64 {
|
||||
return math.MaxUint64
|
||||
}
|
||||
|
||||
// RuleChangeActivationThreshold is the number of blocks for which the condition
|
||||
// must be true in order to lock in a rule change.
|
||||
//
|
||||
// This implementation returns the value defined by the DAG params the checker
|
||||
// is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) RuleChangeActivationThreshold() uint64 {
|
||||
return c.dag.Params.RuleChangeActivationThreshold
|
||||
}
|
||||
|
||||
// MinerConfirmationWindow is the number of blocks in each threshold state
|
||||
// retarget window.
|
||||
//
|
||||
// This implementation returns the value defined by the DAG params the checker
|
||||
// is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) MinerConfirmationWindow() uint64 {
|
||||
return c.dag.Params.MinerConfirmationWindow
|
||||
}
|
||||
|
||||
// Condition returns true when the specific bit associated with the checker is
|
||||
// set and it's not supposed to be according to the expected version based on
|
||||
// the known deployments and the current state of the DAG.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c bitConditionChecker) Condition(node *blockNode) (bool, error) {
|
||||
conditionMask := uint32(1) << c.bit
|
||||
version := uint32(node.version)
|
||||
if version&vbTopMask != vbTopBits {
|
||||
return false, nil
|
||||
}
|
||||
if version&conditionMask == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
expectedVersion, err := c.dag.calcNextBlockVersion(node.selectedParent)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return uint32(expectedVersion)&conditionMask == 0, nil
|
||||
}
|
||||
|
||||
// deploymentChecker provides a thresholdConditionChecker which can be used to
|
||||
// test a specific deployment rule. This is required for properly detecting
|
||||
// and activating consensus rule changes.
|
||||
type deploymentChecker struct {
|
||||
deployment *dagconfig.ConsensusDeployment
|
||||
dag *BlockDAG
|
||||
}
|
||||
|
||||
// Ensure the deploymentChecker type implements the thresholdConditionChecker
|
||||
// interface.
|
||||
var _ thresholdConditionChecker = deploymentChecker{}
|
||||
|
||||
// BeginTime returns the unix timestamp for the median block time after which
|
||||
// voting on a rule change starts (at the next window).
|
||||
//
|
||||
// This implementation returns the value defined by the specific deployment the
|
||||
// checker is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) BeginTime() uint64 {
|
||||
return c.deployment.StartTime
|
||||
}
|
||||
|
||||
// EndTime returns the unix timestamp for the median block time after which an
|
||||
// attempted rule change fails if it has not already been locked in or
|
||||
// activated.
|
||||
//
|
||||
// This implementation returns the value defined by the specific deployment the
|
||||
// checker is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) EndTime() uint64 {
|
||||
return c.deployment.ExpireTime
|
||||
}
|
||||
|
||||
// RuleChangeActivationThreshold is the number of blocks for which the condition
|
||||
// must be true in order to lock in a rule change.
|
||||
//
|
||||
// This implementation returns the value defined by the DAG params the checker
|
||||
// is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) RuleChangeActivationThreshold() uint64 {
|
||||
return c.dag.Params.RuleChangeActivationThreshold
|
||||
}
|
||||
|
||||
// MinerConfirmationWindow is the number of blocks in each threshold state
|
||||
// retarget window.
|
||||
//
|
||||
// This implementation returns the value defined by the DAG params the checker
|
||||
// is associated with.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) MinerConfirmationWindow() uint64 {
|
||||
return c.dag.Params.MinerConfirmationWindow
|
||||
}
|
||||
|
||||
// Condition returns true when the specific bit defined by the deployment
|
||||
// associated with the checker is set.
|
||||
//
|
||||
// This is part of the thresholdConditionChecker interface implementation.
|
||||
func (c deploymentChecker) Condition(node *blockNode) (bool, error) {
|
||||
conditionMask := uint32(1) << c.deployment.BitNumber
|
||||
version := uint32(node.version)
|
||||
return (version&vbTopMask == vbTopBits) && (version&conditionMask != 0),
|
||||
nil
|
||||
}
|
||||
|
||||
// calcNextBlockVersion calculates the expected version of the block after the
|
||||
// passed previous block node based on the state of started and locked in
|
||||
// rule change deployments.
|
||||
//
|
||||
// This function differs from the exported CalcNextBlockVersion in that the
|
||||
// exported version uses the selected tip as the previous block node
|
||||
// while this function accepts any block node.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) calcNextBlockVersion(prevNode *blockNode) (int32, error) {
|
||||
// Set the appropriate bits for each actively defined rule deployment
|
||||
// that is either in the process of being voted on, or locked in for the
|
||||
// activation at the next threshold window change.
|
||||
expectedVersion := uint32(vbTopBits)
|
||||
for id := 0; id < len(dag.Params.Deployments); id++ {
|
||||
deployment := &dag.Params.Deployments[id]
|
||||
cache := &dag.deploymentCaches[id]
|
||||
checker := deploymentChecker{deployment: deployment, dag: dag}
|
||||
state, err := dag.thresholdState(prevNode, checker, cache)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if state == ThresholdStarted || state == ThresholdLockedIn {
|
||||
expectedVersion |= uint32(1) << deployment.BitNumber
|
||||
}
|
||||
}
|
||||
return int32(expectedVersion), nil
|
||||
}
|
||||
|
||||
// CalcNextBlockVersion calculates the expected version of the block after the
|
||||
// end of the current selected tip based on the state of started and locked in
|
||||
// rule change deployments.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) CalcNextBlockVersion() (int32, error) {
|
||||
version, err := dag.calcNextBlockVersion(dag.selectedTip())
|
||||
return version, err
|
||||
}
|
||||
|
||||
// warnUnknownRuleActivations displays a warning when any unknown new rules are
|
||||
// either about to activate or have been activated. This will only happen once
|
||||
// when new rules have been activated and every block for those about to be
|
||||
// activated.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes)
|
||||
func (dag *BlockDAG) warnUnknownRuleActivations(node *blockNode) error {
|
||||
// Warn if any unknown new rules are either about to activate or have
|
||||
// already been activated.
|
||||
for bit := uint32(0); bit < vbNumBits; bit++ {
|
||||
checker := bitConditionChecker{bit: bit, dag: dag}
|
||||
cache := &dag.warningCaches[bit]
|
||||
state, err := dag.thresholdState(node.selectedParent, checker, cache)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch state {
|
||||
case ThresholdActive:
|
||||
if !dag.unknownRulesWarned {
|
||||
log.Warnf("Unknown new rules activated (bit %d)",
|
||||
bit)
|
||||
dag.unknownRulesWarned = true
|
||||
}
|
||||
|
||||
case ThresholdLockedIn:
|
||||
window := checker.MinerConfirmationWindow()
|
||||
activationBlueScore := window - (node.blueScore % window)
|
||||
log.Warnf("Unknown new rules are about to activate in "+
|
||||
"%d blueScore (bit %d)", activationBlueScore, bit)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// warnUnknownVersions logs a warning if a high enough percentage of the last
|
||||
// blocks have unexpected versions.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes)
|
||||
func (dag *BlockDAG) warnUnknownVersions(node *blockNode) error {
|
||||
// Nothing to do if already warned.
|
||||
if dag.unknownVersionsWarned {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Warn if enough previous blocks have unexpected versions.
|
||||
numUpgraded := uint32(0)
|
||||
for i := uint32(0); i < unknownVerNumToCheck && node != nil; i++ {
|
||||
expectedVersion, err := dag.calcNextBlockVersion(node.selectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (node.version & ^expectedVersion) != 0 {
|
||||
|
||||
numUpgraded++
|
||||
}
|
||||
|
||||
node = node.selectedParent
|
||||
}
|
||||
if numUpgraded > unknownVerWarnNum {
|
||||
log.Warn("Unknown block versions are being mined, so new " +
|
||||
"rules might be in effect. Are you running the " +
|
||||
"latest version of the software?")
|
||||
dag.unknownVersionsWarned = true
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"reflect"
|
||||
"testing"
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/limits"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logs"
|
||||
"github.com/kaspanet/kaspad/limits"
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
|
||||
@@ -6,15 +6,15 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
@@ -16,7 +16,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
"github.com/kaspanet/kaspad/rpc/model"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/btcsuite/go-socks/socks"
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
"github.com/kaspanet/kaspad/rpc/model"
|
||||
)
|
||||
|
||||
// newHTTPClient returns a new HTTP client that is configured according to the
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
"github.com/kaspanet/kaspad/rpc/model"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/network/rpc/client"
|
||||
"github.com/kaspanet/kaspad/domainmessage"
|
||||
"github.com/kaspanet/kaspad/rpc/client"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
@@ -2,8 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logs"
|
||||
"github.com/kaspanet/kaspad/network/rpc/client"
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
"github.com/kaspanet/kaspad/rpc/client"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"os"
|
||||
)
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/signal"
|
||||
"github.com/kaspanet/kaspad/signal"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/kaspanet/kaspad/util/profiling"
|
||||
)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user