mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-21 19:22:53 +00:00
Compare commits
157 Commits
v0.4.0-dev
...
v0.6.3-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1f04f30ea7 | ||
|
|
3e4e8d8b6b | ||
|
|
31c0399484 | ||
|
|
f2a3ccd9ab | ||
|
|
31b5cd8d28 | ||
|
|
96bd1fa99b | ||
|
|
48d498e820 | ||
|
|
32c5cfeaf5 | ||
|
|
d55f4e8164 | ||
|
|
1927e81202 | ||
|
|
8a4ece1101 | ||
|
|
0bf1052abf | ||
|
|
2af03c1ccf | ||
|
|
a2aa58c8a4 | ||
|
|
7e74fc0b2b | ||
|
|
0653e59e16 | ||
|
|
32463ce906 | ||
|
|
23a3594c18 | ||
|
|
ffe153efa7 | ||
|
|
ca3172dad0 | ||
|
|
22dc3f998f | ||
|
|
91f4ed9825 | ||
|
|
aa9556aa59 | ||
|
|
91f0fe5740 | ||
|
|
b0fecc9f87 | ||
|
|
53cccd405f | ||
|
|
5b84184921 | ||
|
|
af1df425a2 | ||
|
|
8e170cf327 | ||
|
|
b55cfee8c8 | ||
|
|
420c3d4258 | ||
|
|
b92943a98c | ||
|
|
e1318aa326 | ||
|
|
2bd4a71913 | ||
|
|
5b206f4c9d | ||
|
|
3f969a2921 | ||
|
|
90be14fd57 | ||
|
|
1a5d9fc65c | ||
|
|
ec03a094e5 | ||
|
|
9d60bb1ee7 | ||
|
|
cd10de2dce | ||
|
|
658fb08c02 | ||
|
|
3b40488877 | ||
|
|
d3d0ad0cf3 | ||
|
|
473cc37a75 | ||
|
|
966cba4a4e | ||
|
|
da90755530 | ||
|
|
fa58623815 | ||
|
|
26af4da507 | ||
|
|
b527470153 | ||
|
|
e70561141d | ||
|
|
20b547984e | ||
|
|
16a658a5be | ||
|
|
42e50e6dc2 | ||
|
|
3d942ce355 | ||
|
|
94f617b06a | ||
|
|
211c4d05e8 | ||
|
|
a9f3bdf4ab | ||
|
|
2303aecab4 | ||
|
|
7655841e9f | ||
|
|
c4bbcf9de6 | ||
|
|
0cec1ce23e | ||
|
|
089fe828aa | ||
|
|
24a09fb3df | ||
|
|
b2901454d6 | ||
|
|
6cf589dc9b | ||
|
|
683ceda3a7 | ||
|
|
6a18b56587 | ||
|
|
2c9e5be816 | ||
|
|
5d5a0ef335 | ||
|
|
428f16ffef | ||
|
|
f93e54b63c | ||
|
|
c30b350e8e | ||
|
|
8fdb5aa024 | ||
|
|
83a3c30d01 | ||
|
|
63646c8c92 | ||
|
|
097e7ab42a | ||
|
|
3d45c8de50 | ||
|
|
8e1958c20b | ||
|
|
3e6c1792ef | ||
|
|
6b5b4bfb2a | ||
|
|
b797436884 | ||
|
|
2de3c1d0d4 | ||
|
|
7e81757e2f | ||
|
|
4773f87875 | ||
|
|
aa5bc34280 | ||
|
|
b9a25c1141 | ||
|
|
b42b8b16fd | ||
|
|
e0aac68759 | ||
|
|
9939671ccc | ||
|
|
eaa8515442 | ||
|
|
04b578cee1 | ||
|
|
f8e53d309c | ||
|
|
6076309b3e | ||
|
|
05db135d23 | ||
|
|
433cdb6006 | ||
|
|
4a4dca1926 | ||
|
|
6d591dde74 | ||
|
|
8e624e057e | ||
|
|
eb2642ba90 | ||
|
|
1a43cabfb9 | ||
|
|
580e37943b | ||
|
|
749775c7ea | ||
|
|
8ff8c30fb4 | ||
|
|
9893b7396c | ||
|
|
8c90344f28 | ||
|
|
e4955729d2 | ||
|
|
8a7b0314e5 | ||
|
|
e87d00c9cf | ||
|
|
336347b3c5 | ||
|
|
15d0899406 | ||
|
|
ad096f9781 | ||
|
|
d3c6a3dffc | ||
|
|
57b1653383 | ||
|
|
a86255ba51 | ||
|
|
0a7a4ce7d6 | ||
|
|
4c3735a897 | ||
|
|
22fd38c053 | ||
|
|
895f67a8d4 | ||
|
|
56e807b663 | ||
|
|
af64c7dc2d | ||
|
|
1e6458973b | ||
|
|
7bf8bb5436 | ||
|
|
1358911d95 | ||
|
|
1271d2f113 | ||
|
|
bc0227b49b | ||
|
|
dc643c2d76 | ||
|
|
0744e8ebc0 | ||
|
|
d4c9fdf6ac | ||
|
|
829979b6c7 | ||
|
|
32cd29bf70 | ||
|
|
03cb6cbd4d | ||
|
|
ba4a89488e | ||
|
|
b0d4a92e47 | ||
|
|
3e5a840c5a | ||
|
|
d6d34238d2 | ||
|
|
8bbced5925 | ||
|
|
20da1b9c9a | ||
|
|
b6a6e577c4 | ||
|
|
84888221ae | ||
|
|
222477b33e | ||
|
|
4a50d94633 | ||
|
|
b4dba782fb | ||
|
|
9c78a797e4 | ||
|
|
35c733a4c1 | ||
|
|
e5810d023e | ||
|
|
96930bd6ea | ||
|
|
e09ce32146 | ||
|
|
d15c009b3c | ||
|
|
95c8b8e9d8 | ||
|
|
2d798a5611 | ||
|
|
3a22249be9 | ||
|
|
a4c1898624 | ||
|
|
672f02490a | ||
|
|
fc00275d9c | ||
|
|
3a4571d671 | ||
|
|
96052ac69a |
File diff suppressed because it is too large
Load Diff
@@ -1,25 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addrmgr
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
func TstKnownAddressIsBad(ka *KnownAddress) bool {
|
||||
return ka.isBad()
|
||||
}
|
||||
|
||||
func TstKnownAddressChance(ka *KnownAddress) float64 {
|
||||
return ka.chance()
|
||||
}
|
||||
|
||||
func TstNewKnownAddress(na *wire.NetAddress, attempts int,
|
||||
lastattempt, lastsuccess time.Time, tried bool, refs int) *KnownAddress {
|
||||
return &KnownAddress{na: na, attempts: attempts, lastattempt: lastattempt,
|
||||
lastsuccess: lastsuccess, tried: tried, refs: refs}
|
||||
}
|
||||
@@ -1,114 +0,0 @@
|
||||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addrmgr_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/addrmgr"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
func TestChance(t *testing.T) {
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
var tests = []struct {
|
||||
addr *addrmgr.KnownAddress
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
//Test normal case
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1.0,
|
||||
}, {
|
||||
//Test case in which lastseen < 0
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)},
|
||||
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1.0,
|
||||
}, {
|
||||
//Test case in which lastattempt < 0
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(30*time.Minute), time.Now(), false, 0),
|
||||
1.0 * .01,
|
||||
}, {
|
||||
//Test case in which lastattempt < ten minutes
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(-5*time.Minute), time.Now(), false, 0),
|
||||
1.0 * .01,
|
||||
}, {
|
||||
//Test case with several failed attempts.
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
2, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1 / 1.5 / 1.5,
|
||||
},
|
||||
}
|
||||
|
||||
err := .0001
|
||||
for i, test := range tests {
|
||||
chance := addrmgr.TstKnownAddressChance(test.addr)
|
||||
if math.Abs(test.expected-chance) >= err {
|
||||
t.Errorf("case %d: got %f, expected %f", i, chance, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBad(t *testing.T) {
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
future := now.Add(35 * time.Minute)
|
||||
monthOld := now.Add(-43 * time.Hour * 24)
|
||||
secondsOld := now.Add(-2 * time.Second)
|
||||
minutesOld := now.Add(-27 * time.Minute)
|
||||
hoursOld := now.Add(-5 * time.Hour)
|
||||
zeroTime := time.Time{}
|
||||
|
||||
futureNa := &wire.NetAddress{Timestamp: future}
|
||||
minutesOldNa := &wire.NetAddress{Timestamp: minutesOld}
|
||||
monthOldNa := &wire.NetAddress{Timestamp: monthOld}
|
||||
currentNa := &wire.NetAddress{Timestamp: secondsOld}
|
||||
|
||||
//Test addresses that have been tried in the last minute.
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) {
|
||||
t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) {
|
||||
t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
|
||||
//Test address that claims to be from the future.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 6: addresses that claim to be from the future are bad.")
|
||||
}
|
||||
|
||||
//Test address that has not been seen in over a month.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 7: addresses more than a month old are bad.")
|
||||
}
|
||||
|
||||
//It has failed at least three times and never succeeded.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) {
|
||||
t.Errorf("test case 8: addresses that have never succeeded are bad.")
|
||||
}
|
||||
|
||||
//It has failed ten times in the last week
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) {
|
||||
t.Errorf("test case 9: addresses that have not succeeded in too long are bad.")
|
||||
}
|
||||
|
||||
//Test an address that should work.
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 10: This should be a valid address.")
|
||||
}
|
||||
}
|
||||
247
app/app.go
Normal file
247
app/app.go
Normal file
@@ -0,0 +1,247 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/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/util"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
// App is a wrapper for all the kaspad services
|
||||
type App struct {
|
||||
cfg *config.Config
|
||||
rpcServer *rpc.Server
|
||||
addressManager *addressmanager.AddressManager
|
||||
protocolManager *protocol.Manager
|
||||
connectionManager *connmanager.ConnectionManager
|
||||
netAdapter *netadapter.NetAdapter
|
||||
|
||||
started, shutdown int32
|
||||
}
|
||||
|
||||
// Start launches all the kaspad services.
|
||||
func (a *App) Start() {
|
||||
// Already started?
|
||||
if atomic.AddInt32(&a.started, 1) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Starting kaspad")
|
||||
|
||||
err := a.protocolManager.Start()
|
||||
if err != nil {
|
||||
panics.Exit(log, fmt.Sprintf("Error starting the p2p protocol: %+v", err))
|
||||
}
|
||||
|
||||
a.maybeSeedFromDNS()
|
||||
|
||||
a.connectionManager.Start()
|
||||
|
||||
if !a.cfg.DisableRPC {
|
||||
a.rpcServer.Start()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down all the kaspad services.
|
||||
func (a *App) Stop() {
|
||||
// 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
|
||||
}
|
||||
|
||||
log.Warnf("Kaspad shutting down")
|
||||
|
||||
a.connectionManager.Stop()
|
||||
|
||||
err := a.protocolManager.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping the p2p protocol: %+v", err)
|
||||
}
|
||||
|
||||
// Shutdown the RPC server if it's not disabled.
|
||||
if !a.cfg.DisableRPC {
|
||||
err := a.rpcServer.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping rpcServer: %+v", err)
|
||||
}
|
||||
}
|
||||
|
||||
err = a.addressManager.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping address manager: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// New returns a new App instance configured to listen on addr for the
|
||||
// kaspa network type specified by dagParams. Use start to begin accepting
|
||||
// connections from peers.
|
||||
func New(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{}) (*App, error) {
|
||||
indexManager, acceptanceIndex := setupIndexes(cfg)
|
||||
|
||||
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
|
||||
|
||||
// Create a new block DAG instance with the appropriate configuration.
|
||||
dag, err := setupDAG(cfg, databaseContext, interrupt, sigCache, indexManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txMempool := setupMempool(cfg, dag, sigCache)
|
||||
|
||||
netAdapter, err := netadapter.NewNetAdapter(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addressManager := addressmanager.New(cfg, databaseContext)
|
||||
|
||||
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
protocolManager, err := protocol.NewManager(cfg, dag, netAdapter, addressManager, txMempool, connectionManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcServer, err := setupRPC(
|
||||
cfg, dag, txMempool, sigCache, acceptanceIndex, connectionManager, addressManager, protocolManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &App{
|
||||
cfg: cfg,
|
||||
rpcServer: rpcServer,
|
||||
protocolManager: protocolManager,
|
||||
connectionManager: connectionManager,
|
||||
netAdapter: netAdapter,
|
||||
addressManager: addressManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (a *App) maybeSeedFromDNS() {
|
||||
if !a.cfg.DisableDNSSeed {
|
||||
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, domainmessage.SFNodeNetwork, false, nil,
|
||||
a.cfg.Lookup, func(addresses []*domainmessage.NetAddress) {
|
||||
// Kaspad uses a lookup of the dns seeder here. Since seeder returns
|
||||
// IPs of nodes and not its own IP, we can not know real IP of
|
||||
// source. So we'll take first returned address as source.
|
||||
a.addressManager.AddAddresses(addresses, addresses[0], nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
func setupDAG(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{},
|
||||
sigCache *txscript.SigCache, indexManager blockdag.IndexManager) (*blockdag.BlockDAG, error) {
|
||||
|
||||
dag, err := blockdag.New(&blockdag.Config{
|
||||
Interrupt: interrupt,
|
||||
DatabaseContext: databaseContext,
|
||||
DAGParams: cfg.NetParams(),
|
||||
TimeSource: blockdag.NewTimeSource(),
|
||||
SigCache: sigCache,
|
||||
IndexManager: indexManager,
|
||||
SubnetworkID: cfg.SubnetworkID,
|
||||
})
|
||||
return dag, err
|
||||
}
|
||||
|
||||
func setupIndexes(cfg *config.Config) (blockdag.IndexManager, *indexers.AcceptanceIndex) {
|
||||
// Create indexes if needed.
|
||||
var indexes []indexers.Indexer
|
||||
var acceptanceIndex *indexers.AcceptanceIndex
|
||||
if cfg.AcceptanceIndex {
|
||||
log.Info("acceptance index is enabled")
|
||||
acceptanceIndex = indexers.NewAcceptanceIndex()
|
||||
indexes = append(indexes, acceptanceIndex)
|
||||
}
|
||||
|
||||
// Create an index manager if any of the optional indexes are enabled.
|
||||
if len(indexes) < 0 {
|
||||
return nil, nil
|
||||
}
|
||||
indexManager := indexers.NewManager(indexes)
|
||||
return indexManager, acceptanceIndex
|
||||
}
|
||||
|
||||
func setupMempool(cfg *config.Config, dag *blockdag.BlockDAG, sigCache *txscript.SigCache) *mempool.TxPool {
|
||||
mempoolConfig := mempool.Config{
|
||||
Policy: mempool.Policy{
|
||||
AcceptNonStd: cfg.RelayNonStd,
|
||||
MaxOrphanTxs: cfg.MaxOrphanTxs,
|
||||
MaxOrphanTxSize: config.DefaultMaxOrphanTxSize,
|
||||
MinRelayTxFee: cfg.MinRelayTxFee,
|
||||
MaxTxVersion: 1,
|
||||
},
|
||||
CalcSequenceLockNoLock: func(tx *util.Tx, utxoSet blockdag.UTXOSet) (*blockdag.SequenceLock, error) {
|
||||
return dag.CalcSequenceLockNoLock(tx, utxoSet)
|
||||
},
|
||||
SigCache: sigCache,
|
||||
DAG: dag,
|
||||
}
|
||||
|
||||
return mempool.New(&mempoolConfig)
|
||||
}
|
||||
|
||||
func setupRPC(cfg *config.Config,
|
||||
dag *blockdag.BlockDAG,
|
||||
txMempool *mempool.TxPool,
|
||||
sigCache *txscript.SigCache,
|
||||
acceptanceIndex *indexers.AcceptanceIndex,
|
||||
connectionManager *connmanager.ConnectionManager,
|
||||
addressManager *addressmanager.AddressManager,
|
||||
protocolManager *protocol.Manager) (*rpc.Server, error) {
|
||||
|
||||
if !cfg.DisableRPC {
|
||||
policy := mining.Policy{
|
||||
BlockMaxMass: cfg.BlockMaxMass,
|
||||
}
|
||||
blockTemplateGenerator := mining.NewBlkTmplGenerator(&policy, txMempool, dag, sigCache)
|
||||
|
||||
rpcServer, err := rpc.NewRPCServer(cfg, dag, txMempool, acceptanceIndex, blockTemplateGenerator,
|
||||
connectionManager, addressManager, protocolManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Signal process shutdown when the RPC server requests it.
|
||||
spawn("setupRPC-handleShutdownRequest", func() {
|
||||
<-rpcServer.RequestedProcessShutdown()
|
||||
signal.ShutdownRequestChannel <- struct{}{}
|
||||
})
|
||||
|
||||
return rpcServer, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// P2PNodeID returns the network ID associated with this App
|
||||
func (a *App) P2PNodeID() *id.ID {
|
||||
return a.netAdapter.ID()
|
||||
}
|
||||
|
||||
// AddressManager returns the AddressManager associated with this App
|
||||
func (a *App) AddressManager() *addressmanager.AddressManager {
|
||||
return a.addressManager
|
||||
}
|
||||
14
app/log.go
Normal file
14
app/log.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Copyright (c) 2017 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package app
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.KASD)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
@@ -1,155 +0,0 @@
|
||||
// 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 := dbaccess.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 := dbaccess.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
|
||||
}
|
||||
}
|
||||
|
||||
block.SetBlueScore(newNode.blueScore)
|
||||
|
||||
// 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, blockDAG *BlockDAG) (blockSet, error) {
|
||||
header := block.MsgBlock().Header
|
||||
parentHashes := header.ParentHashes
|
||||
|
||||
nodes := newBlockSet()
|
||||
for _, parentHash := range parentHashes {
|
||||
node := blockDAG.index.LookupNode(parentHash)
|
||||
if node == nil {
|
||||
str := fmt.Sprintf("parent block %s is unknown", parentHash)
|
||||
return nil, ruleError(ErrParentBlockUnknown, str)
|
||||
} else if blockDAG.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,104 +0,0 @@
|
||||
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 := dag.index.LookupNode(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
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
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 := dag.index.LookupNode(highHash)
|
||||
lowNode := dag.index.LookupNode(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 := dag.index.LookupNode(hash)
|
||||
if node != nil {
|
||||
lowNode = node
|
||||
nextBlockLocatorIndex = int64(i) - 1
|
||||
break
|
||||
}
|
||||
}
|
||||
if nextBlockLocatorIndex < 0 {
|
||||
return nil, lowNode.hash
|
||||
}
|
||||
return locator[nextBlockLocatorIndex], lowNode.hash
|
||||
}
|
||||
2103
blockdag/dag.go
2103
blockdag/dag.go
File diff suppressed because it is too large
Load Diff
@@ -1,277 +0,0 @@
|
||||
// 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/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
|
||||
"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
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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 Nanosecond to ensure that parent process time will be after its child.
|
||||
delay += time.Nanosecond
|
||||
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.dagParams.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,577 +0,0 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// reachabilityInterval represents an interval to be used within the
|
||||
// tree reachability algorithm. See reachabilityTreeNode for further
|
||||
// details.
|
||||
type reachabilityInterval struct {
|
||||
start uint64
|
||||
end uint64
|
||||
}
|
||||
|
||||
func newReachabilityInterval(start uint64, end uint64) *reachabilityInterval {
|
||||
return &reachabilityInterval{start: start, end: end}
|
||||
}
|
||||
|
||||
// size returns the size of this interval. Note that intervals are
|
||||
// inclusive from both sides.
|
||||
func (ri *reachabilityInterval) size() uint64 {
|
||||
return ri.end - ri.start + 1
|
||||
}
|
||||
|
||||
// splitInHalf splits this interval by a fraction of 0.5.
|
||||
// See splitFraction for further details.
|
||||
func (ri *reachabilityInterval) splitInHalf() (
|
||||
left *reachabilityInterval, right *reachabilityInterval, err error) {
|
||||
|
||||
return ri.splitFraction(0.5)
|
||||
}
|
||||
|
||||
// splitFraction splits this interval to two parts such that their
|
||||
// union is equal to the original interval and the first (left) part
|
||||
// contains the given fraction of the original interval's size.
|
||||
// Note: if the split results in fractional parts, this method rounds
|
||||
// the first part up and the last part down.
|
||||
func (ri *reachabilityInterval) splitFraction(fraction float64) (
|
||||
left *reachabilityInterval, right *reachabilityInterval, err error) {
|
||||
|
||||
if fraction < 0 || fraction > 1 {
|
||||
return nil, nil, errors.Errorf("fraction must be between 0 and 1")
|
||||
}
|
||||
if ri.size() == 0 {
|
||||
return nil, nil, errors.Errorf("cannot split an empty interval")
|
||||
}
|
||||
|
||||
allocationSize := uint64(math.Ceil(float64(ri.size()) * fraction))
|
||||
left = newReachabilityInterval(ri.start, ri.start+allocationSize-1)
|
||||
right = newReachabilityInterval(ri.start+allocationSize, ri.end)
|
||||
return left, right, nil
|
||||
}
|
||||
|
||||
// splitExact splits this interval to exactly |sizes| parts where
|
||||
// |part_i| = sizes[i]. This method expects sum(sizes) to be exactly
|
||||
// equal to the interval's size.
|
||||
func (ri *reachabilityInterval) splitExact(sizes []uint64) ([]*reachabilityInterval, error) {
|
||||
sizesSum := uint64(0)
|
||||
for _, size := range sizes {
|
||||
sizesSum += size
|
||||
}
|
||||
if sizesSum != ri.size() {
|
||||
return nil, errors.Errorf("sum of sizes must be equal to the interval's size")
|
||||
}
|
||||
|
||||
intervals := make([]*reachabilityInterval, len(sizes))
|
||||
start := ri.start
|
||||
for i, size := range sizes {
|
||||
intervals[i] = newReachabilityInterval(start, start+size-1)
|
||||
start += size
|
||||
}
|
||||
return intervals, nil
|
||||
}
|
||||
|
||||
// splitWithExponentialBias splits this interval to |sizes| parts
|
||||
// by the allocation rule described below. This method expects sum(sizes)
|
||||
// to be smaller or equal to the interval's size. Every part_i is
|
||||
// allocated at least sizes[i] capacity. The remaining budget is
|
||||
// split by an exponentially biased rule described below.
|
||||
//
|
||||
// This rule follows the GHOSTDAG protocol behavior where the child
|
||||
// with the largest subtree is expected to dominate the competition
|
||||
// for new blocks and thus grow the most. However, we may need to
|
||||
// add slack for non-largest subtrees in order to make CPU reindexing
|
||||
// attacks unworthy.
|
||||
func (ri *reachabilityInterval) splitWithExponentialBias(sizes []uint64) ([]*reachabilityInterval, error) {
|
||||
intervalSize := ri.size()
|
||||
sizesSum := uint64(0)
|
||||
for _, size := range sizes {
|
||||
sizesSum += size
|
||||
}
|
||||
if sizesSum > intervalSize {
|
||||
return nil, errors.Errorf("sum of sizes must be less than or equal to the interval's size")
|
||||
}
|
||||
if sizesSum == intervalSize {
|
||||
return ri.splitExact(sizes)
|
||||
}
|
||||
|
||||
// Add a fractional bias to every size in the given sizes
|
||||
totalBias := intervalSize - sizesSum
|
||||
remainingBias := totalBias
|
||||
biasedSizes := make([]uint64, len(sizes))
|
||||
fractions := exponentialFractions(sizes)
|
||||
for i, fraction := range fractions {
|
||||
var bias uint64
|
||||
if i == len(fractions)-1 {
|
||||
bias = remainingBias
|
||||
} else {
|
||||
bias = uint64(math.Round(float64(totalBias) * fraction))
|
||||
if bias > remainingBias {
|
||||
bias = remainingBias
|
||||
}
|
||||
}
|
||||
biasedSizes[i] = sizes[i] + bias
|
||||
remainingBias -= bias
|
||||
}
|
||||
return ri.splitExact(biasedSizes)
|
||||
}
|
||||
|
||||
// exponentialFractions returns a fraction of each size in sizes
|
||||
// as follows:
|
||||
// fraction[i] = 2^size[i] / sum_j(2^size[j])
|
||||
// In the code below the above equation is divided by 2^max(size)
|
||||
// to avoid exploding numbers. Note that in 1 / 2^(max(size)-size[i])
|
||||
// we divide 1 by potentially a very large number, which will
|
||||
// result in loss of float precision. This is not a problem - all
|
||||
// numbers close to 0 bear effectively the same weight.
|
||||
func exponentialFractions(sizes []uint64) []float64 {
|
||||
maxSize := uint64(0)
|
||||
for _, size := range sizes {
|
||||
if size > maxSize {
|
||||
maxSize = size
|
||||
}
|
||||
}
|
||||
fractions := make([]float64, len(sizes))
|
||||
for i, size := range sizes {
|
||||
fractions[i] = 1 / math.Pow(2, float64(maxSize-size))
|
||||
}
|
||||
fractionsSum := float64(0)
|
||||
for _, fraction := range fractions {
|
||||
fractionsSum += fraction
|
||||
}
|
||||
for i, fraction := range fractions {
|
||||
fractions[i] = fraction / fractionsSum
|
||||
}
|
||||
return fractions
|
||||
}
|
||||
|
||||
// isAncestorOf checks if this interval's node is a reachability tree
|
||||
// ancestor of the other interval's node. The condition below is relying on the
|
||||
// property of reachability intervals that intervals are either completely disjoint,
|
||||
// or one strictly contains the other.
|
||||
func (ri *reachabilityInterval) isAncestorOf(other *reachabilityInterval) bool {
|
||||
return ri.start <= other.end && other.end <= ri.end
|
||||
}
|
||||
|
||||
// String returns a string representation of the interval.
|
||||
func (ri *reachabilityInterval) String() string {
|
||||
return fmt.Sprintf("[%d,%d]", ri.start, ri.end)
|
||||
}
|
||||
|
||||
// reachabilityTreeNode represents a node in the reachability tree
|
||||
// of some DAG block. It mainly provides the ability to query *tree*
|
||||
// reachability with O(1) query time. It does so by managing an
|
||||
// index interval for each node and making sure all nodes in its
|
||||
// subtree are indexed within the interval, so the query
|
||||
// B ∈ subtree(A) simply becomes B.interval ⊂ A.interval.
|
||||
//
|
||||
// The main challenge of maintaining such intervals is that our tree
|
||||
// is an ever-growing tree and as such pre-allocated intervals may
|
||||
// not suffice as per future events. This is where the reindexing
|
||||
// algorithm below comes into place.
|
||||
// We use the reasonable assumption that the initial root interval
|
||||
// (e.g., [0, 2^64-1]) should always suffice for any practical use-
|
||||
// case, and so reindexing should always succeed unless more than
|
||||
// 2^64 blocks are added to the DAG/tree.
|
||||
type reachabilityTreeNode struct {
|
||||
blockNode *blockNode
|
||||
|
||||
children []*reachabilityTreeNode
|
||||
parent *reachabilityTreeNode
|
||||
|
||||
// interval is the index interval containing all intervals of
|
||||
// blocks in this node's subtree
|
||||
interval *reachabilityInterval
|
||||
|
||||
// remainingInterval is the not-yet allocated interval (within
|
||||
// this node's interval) awaiting new children
|
||||
remainingInterval *reachabilityInterval
|
||||
}
|
||||
|
||||
func newReachabilityTreeNode(blockNode *blockNode) *reachabilityTreeNode {
|
||||
// Please see the comment above reachabilityTreeNode to understand why
|
||||
// we use these initial values.
|
||||
interval := newReachabilityInterval(1, math.MaxUint64-1)
|
||||
// We subtract 1 from the end of the remaining interval to prevent the node from allocating
|
||||
// the entire interval to its child, so its interval would *strictly* contain the interval of its child.
|
||||
remainingInterval := newReachabilityInterval(interval.start, interval.end-1)
|
||||
return &reachabilityTreeNode{blockNode: blockNode, interval: interval, remainingInterval: remainingInterval}
|
||||
}
|
||||
|
||||
// addChild adds child to this tree node. If this node has no
|
||||
// remaining interval to allocate, a reindexing is triggered.
|
||||
// This method returns a list of reachabilityTreeNodes modified
|
||||
// by it.
|
||||
func (rtn *reachabilityTreeNode) addChild(child *reachabilityTreeNode) ([]*reachabilityTreeNode, error) {
|
||||
// Set the parent-child relationship
|
||||
rtn.children = append(rtn.children, child)
|
||||
child.parent = rtn
|
||||
|
||||
// No allocation space left -- reindex
|
||||
if rtn.remainingInterval.size() == 0 {
|
||||
reindexStartTime := time.Now()
|
||||
modifiedNodes, err := rtn.reindexIntervals()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
reindexTimeElapsed := time.Since(reindexStartTime)
|
||||
log.Debugf("Reachability reindex triggered for "+
|
||||
"block %s. Modified %d tree nodes and took %dms.",
|
||||
rtn.blockNode.hash, len(modifiedNodes), reindexTimeElapsed.Milliseconds())
|
||||
return modifiedNodes, nil
|
||||
}
|
||||
|
||||
// Allocate from the remaining space
|
||||
allocated, remaining, err := rtn.remainingInterval.splitInHalf()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
child.setInterval(allocated)
|
||||
rtn.remainingInterval = remaining
|
||||
return []*reachabilityTreeNode{rtn, child}, nil
|
||||
}
|
||||
|
||||
// setInterval sets the reachability interval for this node.
|
||||
func (rtn *reachabilityTreeNode) setInterval(interval *reachabilityInterval) {
|
||||
rtn.interval = interval
|
||||
|
||||
// Reserve a single interval index for the current node. This
|
||||
// is necessary to ensure that ancestor intervals are strictly
|
||||
// supersets of any descendant intervals and not equal
|
||||
rtn.remainingInterval = newReachabilityInterval(interval.start, interval.end-1)
|
||||
}
|
||||
|
||||
// reindexIntervals traverses the reachability subtree that's
|
||||
// defined by this node and reallocates reachability interval space
|
||||
// such that another reindexing is unlikely to occur shortly
|
||||
// thereafter. It does this by traversing down the reachability
|
||||
// tree until it finds a node with a subreeSize that's greater than
|
||||
// its interval size. See propagateInterval for further details.
|
||||
// This method returns a list of reachabilityTreeNodes modified by it.
|
||||
func (rtn *reachabilityTreeNode) reindexIntervals() ([]*reachabilityTreeNode, error) {
|
||||
current := rtn
|
||||
|
||||
// Initial interval and subtree sizes
|
||||
intervalSize := current.interval.size()
|
||||
subTreeSizeMap := make(map[*reachabilityTreeNode]uint64)
|
||||
current.countSubtrees(subTreeSizeMap)
|
||||
currentSubtreeSize := subTreeSizeMap[current]
|
||||
|
||||
// Find the first ancestor that has sufficient interval space
|
||||
for intervalSize < currentSubtreeSize {
|
||||
if current.parent == nil {
|
||||
// If we ended up here it means that there are more
|
||||
// than 2^64 blocks, which shouldn't ever happen.
|
||||
return nil, errors.Errorf("missing tree " +
|
||||
"parent during reindexing. Theoretically, this " +
|
||||
"should only ever happen if there are more " +
|
||||
"than 2^64 blocks in the DAG.")
|
||||
}
|
||||
current = current.parent
|
||||
intervalSize = current.interval.size()
|
||||
current.countSubtrees(subTreeSizeMap)
|
||||
currentSubtreeSize = subTreeSizeMap[current]
|
||||
}
|
||||
|
||||
// Propagate the interval to the subtree
|
||||
return current.propagateInterval(subTreeSizeMap)
|
||||
}
|
||||
|
||||
// countSubtrees counts the size of each subtree under this node,
|
||||
// and populates the provided subTreeSizeMap with the results.
|
||||
// It is equivalent to the following recursive implementation:
|
||||
//
|
||||
// func (rtn *reachabilityTreeNode) countSubtrees() uint64 {
|
||||
// subtreeSize := uint64(0)
|
||||
// for _, child := range rtn.children {
|
||||
// subtreeSize += child.countSubtrees()
|
||||
// }
|
||||
// return subtreeSize + 1
|
||||
// }
|
||||
//
|
||||
// However, we are expecting (linearly) deep trees, and so a
|
||||
// recursive stack-based approach is inefficient and will hit
|
||||
// recursion limits. Instead, the same logic was implemented
|
||||
// using a (queue-based) BFS method. At a high level, the
|
||||
// algorithm uses BFS for reaching all leaves and pushes
|
||||
// intermediate updates from leaves via parent chains until all
|
||||
// size information is gathered at the root of the operation
|
||||
// (i.e. at rtn).
|
||||
func (rtn *reachabilityTreeNode) countSubtrees(subTreeSizeMap map[*reachabilityTreeNode]uint64) {
|
||||
queue := []*reachabilityTreeNode{rtn}
|
||||
calculatedChildrenCount := make(map[*reachabilityTreeNode]uint64)
|
||||
for len(queue) > 0 {
|
||||
var current *reachabilityTreeNode
|
||||
current, queue = queue[0], queue[1:]
|
||||
if len(current.children) == 0 {
|
||||
// We reached a leaf
|
||||
subTreeSizeMap[current] = 1
|
||||
} else if _, ok := subTreeSizeMap[current]; !ok {
|
||||
// We haven't yet calculated the subtree size of
|
||||
// the current node. Add all its children to the
|
||||
// queue
|
||||
queue = append(queue, current.children...)
|
||||
continue
|
||||
}
|
||||
|
||||
// We reached a leaf or a pre-calculated subtree.
|
||||
// Push information up
|
||||
for current != rtn {
|
||||
current = current.parent
|
||||
calculatedChildrenCount[current]++
|
||||
if calculatedChildrenCount[current] != uint64(len(current.children)) {
|
||||
// Not all subtrees of the current node are ready
|
||||
break
|
||||
}
|
||||
// All children of `current` have calculated their subtree size.
|
||||
// Sum them all together and add 1 to get the sub tree size of
|
||||
// `current`.
|
||||
childSubtreeSizeSum := uint64(0)
|
||||
for _, child := range current.children {
|
||||
childSubtreeSizeSum += subTreeSizeMap[child]
|
||||
}
|
||||
subTreeSizeMap[current] = childSubtreeSizeSum + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// propagateInterval propagates the new interval using a BFS traversal.
|
||||
// Subtree intervals are recursively allocated according to subtree sizes and
|
||||
// the allocation rule in splitWithExponentialBias. This method returns
|
||||
// a list of reachabilityTreeNodes modified by it.
|
||||
func (rtn *reachabilityTreeNode) propagateInterval(subTreeSizeMap map[*reachabilityTreeNode]uint64) ([]*reachabilityTreeNode, error) {
|
||||
// We set the interval to reset its remainingInterval, so we could reallocate it while reindexing.
|
||||
rtn.setInterval(rtn.interval)
|
||||
queue := []*reachabilityTreeNode{rtn}
|
||||
var modifiedNodes []*reachabilityTreeNode
|
||||
for len(queue) > 0 {
|
||||
var current *reachabilityTreeNode
|
||||
current, queue = queue[0], queue[1:]
|
||||
if len(current.children) > 0 {
|
||||
sizes := make([]uint64, len(current.children))
|
||||
for i, child := range current.children {
|
||||
sizes[i] = subTreeSizeMap[child]
|
||||
}
|
||||
intervals, err := current.remainingInterval.splitWithExponentialBias(sizes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i, child := range current.children {
|
||||
childInterval := intervals[i]
|
||||
child.setInterval(childInterval)
|
||||
queue = append(queue, child)
|
||||
}
|
||||
|
||||
// Empty up remaining interval
|
||||
current.remainingInterval.start = current.remainingInterval.end + 1
|
||||
}
|
||||
|
||||
modifiedNodes = append(modifiedNodes, current)
|
||||
}
|
||||
return modifiedNodes, nil
|
||||
}
|
||||
|
||||
// isAncestorOf checks if this node is a reachability tree ancestor
|
||||
// of the other node.
|
||||
func (rtn *reachabilityTreeNode) isAncestorOf(other *reachabilityTreeNode) bool {
|
||||
return rtn.interval.isAncestorOf(other.interval)
|
||||
}
|
||||
|
||||
// String returns a string representation of a reachability tree node
|
||||
// and its children.
|
||||
func (rtn *reachabilityTreeNode) String() string {
|
||||
queue := []*reachabilityTreeNode{rtn}
|
||||
lines := []string{rtn.interval.String()}
|
||||
for len(queue) > 0 {
|
||||
var current *reachabilityTreeNode
|
||||
current, queue = queue[0], queue[1:]
|
||||
if len(current.children) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
line := ""
|
||||
for _, child := range current.children {
|
||||
line += child.interval.String()
|
||||
queue = append(queue, child)
|
||||
}
|
||||
lines = append([]string{line}, lines...)
|
||||
}
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
// futureCoveringBlockSet represents a collection of blocks in the future of
|
||||
// a certain block. Once a block B is added to the DAG, every block A_i in
|
||||
// B's selected parent anticone must register B in its futureCoveringBlockSet. This allows
|
||||
// to relatively quickly (O(log(|futureCoveringBlockSet|))) query whether B
|
||||
// is a descendent (is in the "future") of any block that previously
|
||||
// registered it.
|
||||
//
|
||||
// Note that futureCoveringBlockSet is meant to be queried only if B is not
|
||||
// a reachability tree descendant of the block in question, as reachability
|
||||
// tree queries are always O(1).
|
||||
//
|
||||
// See insertBlock, isInFuture, and dag.isAncestorOf for further details.
|
||||
type futureCoveringBlockSet []*futureCoveringBlock
|
||||
|
||||
// futureCoveringBlock represents a block in the future of some other block.
|
||||
type futureCoveringBlock struct {
|
||||
blockNode *blockNode
|
||||
treeNode *reachabilityTreeNode
|
||||
}
|
||||
|
||||
// insertBlock inserts the given block into this futureCoveringBlockSet
|
||||
// while keeping futureCoveringBlockSet ordered by interval.
|
||||
// If a block B ∈ futureCoveringBlockSet exists such that its interval
|
||||
// contains block's interval, block need not be added. If block's
|
||||
// interval contains B's interval, it replaces it.
|
||||
//
|
||||
// Notes:
|
||||
// * Intervals never intersect unless one contains the other
|
||||
// (this follows from the tree structure and the indexing rule).
|
||||
// * Since futureCoveringBlockSet is kept ordered, a binary search can be
|
||||
// used for insertion/queries.
|
||||
// * Although reindexing may change a block's interval, the
|
||||
// is-superset relation will by definition
|
||||
// be always preserved.
|
||||
func (fb *futureCoveringBlockSet) insertBlock(block *futureCoveringBlock) {
|
||||
blockInterval := block.treeNode.interval
|
||||
i := fb.findIndex(block)
|
||||
if i > 0 {
|
||||
candidate := (*fb)[i-1]
|
||||
candidateInterval := candidate.treeNode.interval
|
||||
if candidateInterval.isAncestorOf(blockInterval) {
|
||||
// candidate is an ancestor of block, no need to insert
|
||||
return
|
||||
}
|
||||
if blockInterval.isAncestorOf(candidateInterval) {
|
||||
// block is an ancestor of candidate, and can thus replace it
|
||||
(*fb)[i-1] = block
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Insert block in the correct index to maintain futureCoveringBlockSet as
|
||||
// a sorted-by-interval list.
|
||||
// Note that i might be equal to len(futureCoveringBlockSet)
|
||||
left := (*fb)[:i]
|
||||
right := append([]*futureCoveringBlock{block}, (*fb)[i:]...)
|
||||
*fb = append(left, right...)
|
||||
}
|
||||
|
||||
// isInFuture resolves whether the given block is in the subtree of
|
||||
// any block in this futureCoveringBlockSet.
|
||||
// See insertBlock method for the complementary insertion behavior.
|
||||
//
|
||||
// Like the insert method, this method also relies on the fact that
|
||||
// futureCoveringBlockSet is kept ordered by interval to efficiently perform a
|
||||
// binary search over futureCoveringBlockSet and answer the query in
|
||||
// O(log(|futureCoveringBlockSet|)).
|
||||
func (fb futureCoveringBlockSet) isInFuture(block *futureCoveringBlock) bool {
|
||||
i := fb.findIndex(block)
|
||||
if i == 0 {
|
||||
// No candidate to contain block
|
||||
return false
|
||||
}
|
||||
|
||||
candidate := fb[i-1]
|
||||
return candidate.treeNode.isAncestorOf(block.treeNode)
|
||||
}
|
||||
|
||||
// findIndex finds the index of the block with the maximum start that is below
|
||||
// the given block.
|
||||
func (fb futureCoveringBlockSet) findIndex(block *futureCoveringBlock) int {
|
||||
blockInterval := block.treeNode.interval
|
||||
end := blockInterval.end
|
||||
|
||||
low := 0
|
||||
high := len(fb)
|
||||
for low < high {
|
||||
middle := (low + high) / 2
|
||||
middleInterval := fb[middle].treeNode.interval
|
||||
if end < middleInterval.start {
|
||||
high = middle
|
||||
} else {
|
||||
low = middle + 1
|
||||
}
|
||||
}
|
||||
return low
|
||||
}
|
||||
|
||||
// String returns a string representation of the intervals in this futureCoveringBlockSet.
|
||||
func (fb futureCoveringBlockSet) String() string {
|
||||
intervalsString := ""
|
||||
for _, block := range fb {
|
||||
intervalsString += block.treeNode.interval.String()
|
||||
}
|
||||
return intervalsString
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) updateReachability(node *blockNode, selectedParentAnticone []*blockNode) error {
|
||||
// Allocate a new reachability tree node
|
||||
newTreeNode := newReachabilityTreeNode(node)
|
||||
|
||||
// If this is the genesis node, simply initialize it and return
|
||||
if node.isGenesis() {
|
||||
dag.reachabilityStore.setTreeNode(newTreeNode)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Insert the node into the selected parent's reachability tree
|
||||
selectedParentTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(node.selectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
modifiedTreeNodes, err := selectedParentTreeNode.addChild(newTreeNode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, modifiedTreeNode := range modifiedTreeNodes {
|
||||
dag.reachabilityStore.setTreeNode(modifiedTreeNode)
|
||||
}
|
||||
|
||||
// Add the block to the futureCoveringSets of all the blocks
|
||||
// in the selected parent's anticone
|
||||
for _, current := range selectedParentAnticone {
|
||||
currentFutureCoveringSet, err := dag.reachabilityStore.futureCoveringSetByBlockNode(current)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
currentFutureCoveringSet.insertBlock(&futureCoveringBlock{blockNode: node, treeNode: newTreeNode})
|
||||
err = dag.reachabilityStore.setFutureCoveringSet(current, currentFutureCoveringSet)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// isAncestorOf returns true if this node is in the past of the other node
|
||||
// in the DAG. The complexity of this method is O(log(|this.futureCoveringBlockSet|))
|
||||
func (dag *BlockDAG) isAncestorOf(this *blockNode, other *blockNode) (bool, error) {
|
||||
// First, check if this node is a reachability tree ancestor of the
|
||||
// other node
|
||||
thisTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(this)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
otherTreeNode, err := dag.reachabilityStore.treeNodeByBlockNode(other)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if thisTreeNode.isAncestorOf(otherTreeNode) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// Otherwise, use previously registered future blocks to complete the
|
||||
// reachability test
|
||||
thisFutureCoveringSet, err := dag.reachabilityStore.futureCoveringSetByBlockNode(this)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return thisFutureCoveringSet.isInFuture(&futureCoveringBlock{blockNode: other, treeNode: otherTreeNode}), nil
|
||||
}
|
||||
@@ -1,688 +0,0 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddChild(t *testing.T) {
|
||||
// Scenario 1: test addChild in a chain
|
||||
// root -> a -> b -> c...
|
||||
// Create the root node of a new reachability tree
|
||||
root := newReachabilityTreeNode(&blockNode{})
|
||||
root.setInterval(newReachabilityInterval(1, 100))
|
||||
|
||||
// Add a chain of child nodes just before a reindex occurs (2^6=64 < 100)
|
||||
currentTip := root
|
||||
for i := 0; i < 6; i++ {
|
||||
node := newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err := currentTip.addChild(node)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
|
||||
// Expect only the node and its parent to be affected
|
||||
expectedModifiedNodes := []*reachabilityTreeNode{currentTip, node}
|
||||
if !reflect.DeepEqual(modifiedNodes, expectedModifiedNodes) {
|
||||
t.Fatalf("TestAddChild: unexpected modifiedNodes. "+
|
||||
"want: %s, got: %s", expectedModifiedNodes, modifiedNodes)
|
||||
}
|
||||
|
||||
currentTip = node
|
||||
}
|
||||
|
||||
// Add another node to the tip of the chain to trigger a reindex (100 < 2^7=128)
|
||||
lastChild := newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err := currentTip.addChild(lastChild)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
|
||||
// Expect more than just the node and its parent to be modified but not
|
||||
// all the nodes
|
||||
if len(modifiedNodes) <= 2 && len(modifiedNodes) >= 7 {
|
||||
t.Fatalf("TestAddChild: unexpected amount of modifiedNodes.")
|
||||
}
|
||||
|
||||
// Expect the tip to have an interval of 1 and remaining interval of 0
|
||||
tipInterval := lastChild.interval.size()
|
||||
if tipInterval != 1 {
|
||||
t.Fatalf("TestAddChild: unexpected tip interval size: want: 1, got: %d", tipInterval)
|
||||
}
|
||||
tipRemainingInterval := lastChild.remainingInterval.size()
|
||||
if tipRemainingInterval != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected tip interval size: want: 0, got: %d", tipRemainingInterval)
|
||||
}
|
||||
|
||||
// Expect all nodes to be descendant nodes of root
|
||||
currentNode := currentTip
|
||||
for currentNode != nil {
|
||||
if !root.isAncestorOf(currentNode) {
|
||||
t.Fatalf("TestAddChild: currentNode is not a descendant of root")
|
||||
}
|
||||
currentNode = currentNode.parent
|
||||
}
|
||||
|
||||
// Scenario 2: test addChild where all nodes are direct descendants of root
|
||||
// root -> a, b, c...
|
||||
// Create the root node of a new reachability tree
|
||||
root = newReachabilityTreeNode(&blockNode{})
|
||||
root.setInterval(newReachabilityInterval(1, 100))
|
||||
|
||||
// Add child nodes to root just before a reindex occurs (2^6=64 < 100)
|
||||
childNodes := make([]*reachabilityTreeNode, 6)
|
||||
for i := 0; i < len(childNodes); i++ {
|
||||
childNodes[i] = newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err := root.addChild(childNodes[i])
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
|
||||
// Expect only the node and the root to be affected
|
||||
expectedModifiedNodes := []*reachabilityTreeNode{root, childNodes[i]}
|
||||
if !reflect.DeepEqual(modifiedNodes, expectedModifiedNodes) {
|
||||
t.Fatalf("TestAddChild: unexpected modifiedNodes. "+
|
||||
"want: %s, got: %s", expectedModifiedNodes, modifiedNodes)
|
||||
}
|
||||
}
|
||||
|
||||
// Add another node to the root to trigger a reindex (100 < 2^7=128)
|
||||
lastChild = newReachabilityTreeNode(&blockNode{})
|
||||
modifiedNodes, err = root.addChild(lastChild)
|
||||
if err != nil {
|
||||
t.Fatalf("TestAddChild: addChild failed: %s", err)
|
||||
}
|
||||
|
||||
// Expect more than just the node and the root to be modified but not
|
||||
// all the nodes
|
||||
if len(modifiedNodes) <= 2 && len(modifiedNodes) >= 7 {
|
||||
t.Fatalf("TestAddChild: unexpected amount of modifiedNodes.")
|
||||
}
|
||||
|
||||
// Expect the last-added child to have an interval of 1 and remaining interval of 0
|
||||
lastChildInterval := lastChild.interval.size()
|
||||
if lastChildInterval != 1 {
|
||||
t.Fatalf("TestAddChild: unexpected lastChild interval size: want: 1, got: %d", lastChildInterval)
|
||||
}
|
||||
lastChildRemainingInterval := lastChild.remainingInterval.size()
|
||||
if lastChildRemainingInterval != 0 {
|
||||
t.Fatalf("TestAddChild: unexpected lastChild interval size: want: 0, got: %d", lastChildRemainingInterval)
|
||||
}
|
||||
|
||||
// Expect all nodes to be descendant nodes of root
|
||||
for _, childNode := range childNodes {
|
||||
if !root.isAncestorOf(childNode) {
|
||||
t.Fatalf("TestAddChild: childNode is not a descendant of root")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitFraction(t *testing.T) {
|
||||
tests := []struct {
|
||||
interval *reachabilityInterval
|
||||
fraction float64
|
||||
expectedLeft *reachabilityInterval
|
||||
expectedRight *reachabilityInterval
|
||||
}{
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
fraction: 0.5,
|
||||
expectedLeft: newReachabilityInterval(1, 50),
|
||||
expectedRight: newReachabilityInterval(51, 100),
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(2, 100),
|
||||
fraction: 0.5,
|
||||
expectedLeft: newReachabilityInterval(2, 51),
|
||||
expectedRight: newReachabilityInterval(52, 100),
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 99),
|
||||
fraction: 0.5,
|
||||
expectedLeft: newReachabilityInterval(1, 50),
|
||||
expectedRight: newReachabilityInterval(51, 99),
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
fraction: 0.2,
|
||||
expectedLeft: newReachabilityInterval(1, 20),
|
||||
expectedRight: newReachabilityInterval(21, 100),
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
fraction: 0,
|
||||
expectedLeft: newReachabilityInterval(1, 0),
|
||||
expectedRight: newReachabilityInterval(1, 100),
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
fraction: 1,
|
||||
expectedLeft: newReachabilityInterval(1, 100),
|
||||
expectedRight: newReachabilityInterval(101, 100),
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
left, right, err := test.interval.splitFraction(test.fraction)
|
||||
if err != nil {
|
||||
t.Fatalf("TestSplitFraction: splitFraction unexpectedly failed in test #%d: %s", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(left, test.expectedLeft) {
|
||||
t.Errorf("TestSplitFraction: unexpected left in test #%d. "+
|
||||
"want: %s, got: %s", i, test.expectedLeft, left)
|
||||
}
|
||||
if !reflect.DeepEqual(right, test.expectedRight) {
|
||||
t.Errorf("TestSplitFraction: unexpected right in test #%d. "+
|
||||
"want: %s, got: %s", i, test.expectedRight, right)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitExact(t *testing.T) {
|
||||
tests := []struct {
|
||||
interval *reachabilityInterval
|
||||
sizes []uint64
|
||||
expectedIntervals []*reachabilityInterval
|
||||
}{
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{100},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{50, 50},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 50),
|
||||
newReachabilityInterval(51, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{10, 20, 30, 40},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 10),
|
||||
newReachabilityInterval(11, 30),
|
||||
newReachabilityInterval(31, 60),
|
||||
newReachabilityInterval(61, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{0, 100},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 0),
|
||||
newReachabilityInterval(1, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{100, 0},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 100),
|
||||
newReachabilityInterval(101, 100),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
intervals, err := test.interval.splitExact(test.sizes)
|
||||
if err != nil {
|
||||
t.Fatalf("TestSplitExact: splitExact unexpectedly failed in test #%d: %s", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(intervals, test.expectedIntervals) {
|
||||
t.Errorf("TestSplitExact: unexpected intervals in test #%d. "+
|
||||
"want: %s, got: %s", i, test.expectedIntervals, intervals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitWithExponentialBias(t *testing.T) {
|
||||
tests := []struct {
|
||||
interval *reachabilityInterval
|
||||
sizes []uint64
|
||||
expectedIntervals []*reachabilityInterval
|
||||
}{
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{100},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{50, 50},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 50),
|
||||
newReachabilityInterval(51, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{10, 20, 30, 40},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 10),
|
||||
newReachabilityInterval(11, 30),
|
||||
newReachabilityInterval(31, 60),
|
||||
newReachabilityInterval(61, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{25, 25},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 50),
|
||||
newReachabilityInterval(51, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{1, 1},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 50),
|
||||
newReachabilityInterval(51, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{33, 33, 33},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 33),
|
||||
newReachabilityInterval(34, 66),
|
||||
newReachabilityInterval(67, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{10, 15, 25},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 10),
|
||||
newReachabilityInterval(11, 25),
|
||||
newReachabilityInterval(26, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100),
|
||||
sizes: []uint64{25, 15, 10},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 75),
|
||||
newReachabilityInterval(76, 90),
|
||||
newReachabilityInterval(91, 100),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 10_000),
|
||||
sizes: []uint64{10, 10, 20},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 20),
|
||||
newReachabilityInterval(21, 40),
|
||||
newReachabilityInterval(41, 10_000),
|
||||
},
|
||||
},
|
||||
{
|
||||
interval: newReachabilityInterval(1, 100_000),
|
||||
sizes: []uint64{31_000, 31_000, 30_001},
|
||||
expectedIntervals: []*reachabilityInterval{
|
||||
newReachabilityInterval(1, 35_000),
|
||||
newReachabilityInterval(35_001, 69_999),
|
||||
newReachabilityInterval(70_000, 100_000),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
intervals, err := test.interval.splitWithExponentialBias(test.sizes)
|
||||
if err != nil {
|
||||
t.Fatalf("TestSplitWithExponentialBias: splitWithExponentialBias unexpectedly failed in test #%d: %s", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(intervals, test.expectedIntervals) {
|
||||
t.Errorf("TestSplitWithExponentialBias: unexpected intervals in test #%d. "+
|
||||
"want: %s, got: %s", i, test.expectedIntervals, intervals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsInFuture(t *testing.T) {
|
||||
blocks := futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(2, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
block *futureCoveringBlock
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 1)}},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)}},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 76)}},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(78, 100)}},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1980, 2000)}},
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
block: &futureCoveringBlock{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1920)}},
|
||||
expectedResult: true,
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
result := blocks.isInFuture(test.block)
|
||||
if result != test.expectedResult {
|
||||
t.Errorf("TestIsInFuture: unexpected result in test #%d. Want: %t, got: %t",
|
||||
i, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInsertBlock(t *testing.T) {
|
||||
blocks := futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
toInsert []*futureCoveringBlock
|
||||
expectedResult futureCoveringBlockSet
|
||||
}{
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(5, 7)}},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
},
|
||||
},
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(65, 78)}},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(65, 78)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
},
|
||||
},
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
},
|
||||
},
|
||||
{
|
||||
toInsert: []*futureCoveringBlock{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)}},
|
||||
},
|
||||
expectedResult: futureCoveringBlockSet{
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1, 3)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(4, 67)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(67, 77)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(88, 97)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(657, 789)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1000, 1000)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(1920, 1921)}},
|
||||
{treeNode: &reachabilityTreeNode{interval: newReachabilityInterval(3000, 3010)}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
// Create a clone of blocks so that we have a clean start for every test
|
||||
blocksClone := make(futureCoveringBlockSet, len(blocks))
|
||||
for i, block := range blocks {
|
||||
blocksClone[i] = block
|
||||
}
|
||||
|
||||
for _, block := range test.toInsert {
|
||||
blocksClone.insertBlock(block)
|
||||
}
|
||||
if !reflect.DeepEqual(blocksClone, test.expectedResult) {
|
||||
t.Errorf("TestInsertBlock: unexpected result in test #%d. Want: %s, got: %s",
|
||||
i, test.expectedResult, blocksClone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitFractionErrors(t *testing.T) {
|
||||
interval := newReachabilityInterval(100, 200)
|
||||
|
||||
// Negative fraction
|
||||
_, _, err := interval.splitFraction(-0.5)
|
||||
if err == nil {
|
||||
t.Fatalf("TestSplitFractionErrors: splitFraction unexpectedly " +
|
||||
"didn't return an error for a negative fraction")
|
||||
}
|
||||
expectedErrSubstring := "fraction must be between 0 and 1"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestSplitFractionErrors: splitFraction returned wrong error "+
|
||||
"for a negative fraction. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
|
||||
// Fraction > 1
|
||||
_, _, err = interval.splitFraction(1.5)
|
||||
if err == nil {
|
||||
t.Fatalf("TestSplitFractionErrors: splitFraction unexpectedly " +
|
||||
"didn't return an error for a fraction greater than 1")
|
||||
}
|
||||
expectedErrSubstring = "fraction must be between 0 and 1"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestSplitFractionErrors: splitFraction returned wrong error "+
|
||||
"for a fraction greater than 1. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
|
||||
// Splitting an empty interval
|
||||
emptyInterval := newReachabilityInterval(1, 0)
|
||||
_, _, err = emptyInterval.splitFraction(0.5)
|
||||
if err == nil {
|
||||
t.Fatalf("TestSplitFractionErrors: splitFraction unexpectedly " +
|
||||
"didn't return an error for an empty interval")
|
||||
}
|
||||
expectedErrSubstring = "cannot split an empty interval"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestSplitFractionErrors: splitFraction returned wrong error "+
|
||||
"for an empty interval. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitExactErrors(t *testing.T) {
|
||||
interval := newReachabilityInterval(100, 199)
|
||||
|
||||
// Sum of sizes greater than the size of the interval
|
||||
sizes := []uint64{50, 51}
|
||||
_, err := interval.splitExact(sizes)
|
||||
if err == nil {
|
||||
t.Fatalf("TestSplitExactErrors: splitExact unexpectedly " +
|
||||
"didn't return an error for (sum of sizes) > (size of interval)")
|
||||
}
|
||||
expectedErrSubstring := "sum of sizes must be equal to the interval's size"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestSplitExactErrors: splitExact returned wrong error "+
|
||||
"for (sum of sizes) > (size of interval). "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
|
||||
// Sum of sizes smaller than the size of the interval
|
||||
sizes = []uint64{50, 49}
|
||||
_, err = interval.splitExact(sizes)
|
||||
if err == nil {
|
||||
t.Fatalf("TestSplitExactErrors: splitExact unexpectedly " +
|
||||
"didn't return an error for (sum of sizes) < (size of interval)")
|
||||
}
|
||||
expectedErrSubstring = "sum of sizes must be equal to the interval's size"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestSplitExactErrors: splitExact returned wrong error "+
|
||||
"for (sum of sizes) < (size of interval). "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSplitWithExponentialBiasErrors(t *testing.T) {
|
||||
interval := newReachabilityInterval(100, 199)
|
||||
|
||||
// Sum of sizes greater than the size of the interval
|
||||
sizes := []uint64{50, 51}
|
||||
_, err := interval.splitWithExponentialBias(sizes)
|
||||
if err == nil {
|
||||
t.Fatalf("TestSplitWithExponentialBiasErrors: splitWithExponentialBias " +
|
||||
"unexpectedly didn't return an error")
|
||||
}
|
||||
expectedErrSubstring := "sum of sizes must be less than or equal to the interval's size"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestSplitWithExponentialBiasErrors: splitWithExponentialBias "+
|
||||
"returned wrong error. Want: %s, got: %s", expectedErrSubstring, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReindexIntervalErrors(t *testing.T) {
|
||||
// Create a treeNode and give it size = 100
|
||||
treeNode := newReachabilityTreeNode(&blockNode{})
|
||||
treeNode.setInterval(newReachabilityInterval(0, 99))
|
||||
|
||||
// Add a chain of 100 child treeNodes to treeNode
|
||||
var err error
|
||||
currentTreeNode := treeNode
|
||||
for i := 0; i < 100; i++ {
|
||||
childTreeNode := newReachabilityTreeNode(&blockNode{})
|
||||
_, err = currentTreeNode.addChild(childTreeNode)
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
currentTreeNode = childTreeNode
|
||||
}
|
||||
|
||||
// At the 100th addChild we expect a reindex. This reindex should
|
||||
// fail because our initial treeNode only has size = 100, and the
|
||||
// reindex requires size > 100.
|
||||
// This simulates the case when (somehow) there's more than 2^64
|
||||
// blocks in the DAG, since the genesis block has size = 2^64.
|
||||
if err == nil {
|
||||
t.Fatalf("TestReindexIntervalErrors: reindexIntervals " +
|
||||
"unexpectedly didn't return an error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "missing tree parent during reindexing") {
|
||||
t.Fatalf("TestReindexIntervalErrors: reindexIntervals "+
|
||||
"returned an expected error: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkReindexInterval(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
b.StopTimer()
|
||||
root := newReachabilityTreeNode(&blockNode{})
|
||||
|
||||
const subTreeSize = 70000
|
||||
// We set the interval of the root to subTreeSize*2 because
|
||||
// its first child gets half of the interval, so a reindex
|
||||
// from the root should happen after adding subTreeSize
|
||||
// nodes.
|
||||
root.setInterval(newReachabilityInterval(0, subTreeSize*2))
|
||||
|
||||
currentTreeNode := root
|
||||
for i := 0; i < subTreeSize; i++ {
|
||||
childTreeNode := newReachabilityTreeNode(&blockNode{})
|
||||
_, err := currentTreeNode.addChild(childTreeNode)
|
||||
if err != nil {
|
||||
b.Fatalf("addChild: %s", err)
|
||||
}
|
||||
|
||||
currentTreeNode = childTreeNode
|
||||
}
|
||||
|
||||
remainingIntervalBefore := *root.remainingInterval
|
||||
// After we added subTreeSize nodes, adding the next
|
||||
// node should lead to a reindex from root.
|
||||
fullReindexTriggeringNode := newReachabilityTreeNode(&blockNode{})
|
||||
b.StartTimer()
|
||||
_, err := currentTreeNode.addChild(fullReindexTriggeringNode)
|
||||
b.StopTimer()
|
||||
if err != nil {
|
||||
b.Fatalf("addChild: %s", err)
|
||||
}
|
||||
|
||||
if *root.remainingInterval == remainingIntervalBefore {
|
||||
b.Fatal("Expected a reindex from root, but it didn't happen")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFutureCoveringBlockSetString(t *testing.T) {
|
||||
treeNodeA := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeA.setInterval(newReachabilityInterval(123, 456))
|
||||
treeNodeB := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeB.setInterval(newReachabilityInterval(457, 789))
|
||||
futureCoveringSet := futureCoveringBlockSet{
|
||||
&futureCoveringBlock{treeNode: treeNodeA},
|
||||
&futureCoveringBlock{treeNode: treeNodeB},
|
||||
}
|
||||
|
||||
str := futureCoveringSet.String()
|
||||
expectedStr := "[123,456][457,789]"
|
||||
if str != expectedStr {
|
||||
t.Fatalf("TestFutureCoveringBlockSetString: unexpected "+
|
||||
"string. Want: %s, got: %s", expectedStr, str)
|
||||
}
|
||||
}
|
||||
|
||||
func TestReachabilityTreeNodeString(t *testing.T) {
|
||||
treeNodeA := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeA.setInterval(newReachabilityInterval(100, 199))
|
||||
treeNodeB1 := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeB1.setInterval(newReachabilityInterval(100, 150))
|
||||
treeNodeB2 := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeB2.setInterval(newReachabilityInterval(150, 199))
|
||||
treeNodeC := newReachabilityTreeNode(&blockNode{})
|
||||
treeNodeC.setInterval(newReachabilityInterval(100, 149))
|
||||
treeNodeA.children = []*reachabilityTreeNode{treeNodeB1, treeNodeB2}
|
||||
treeNodeB2.children = []*reachabilityTreeNode{treeNodeC}
|
||||
|
||||
str := treeNodeA.String()
|
||||
expectedStr := "[100,149]\n[100,150][150,199]\n[100,199]"
|
||||
if str != expectedStr {
|
||||
t.Fatalf("TestReachabilityTreeNodeString: unexpected "+
|
||||
"string. Want: %s, got: %s", expectedStr, str)
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package blockdag
|
||||
|
||||
import "time"
|
||||
|
||||
const syncRateWindowDuration = 15 * time.Minute
|
||||
|
||||
// addBlockProcessingTimestamp adds the last block processing timestamp in order to measure the recent sync rate.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) addBlockProcessingTimestamp() {
|
||||
now := time.Now()
|
||||
dag.recentBlockProcessingTimestamps = append(dag.recentBlockProcessingTimestamps, now)
|
||||
dag.removeNonRecentTimestampsFromRecentBlockProcessingTimestamps()
|
||||
}
|
||||
|
||||
// removeNonRecentTimestampsFromRecentBlockProcessingTimestamps removes timestamps older than syncRateWindowDuration
|
||||
// from dag.recentBlockProcessingTimestamps
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) removeNonRecentTimestampsFromRecentBlockProcessingTimestamps() {
|
||||
dag.recentBlockProcessingTimestamps = dag.recentBlockProcessingTimestampsRelevantWindow()
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) recentBlockProcessingTimestampsRelevantWindow() []time.Time {
|
||||
minTime := time.Now().Add(-syncRateWindowDuration)
|
||||
windowStartIndex := len(dag.recentBlockProcessingTimestamps)
|
||||
for i, processTime := range dag.recentBlockProcessingTimestamps {
|
||||
if processTime.After(minTime) {
|
||||
windowStartIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
return dag.recentBlockProcessingTimestamps[windowStartIndex:]
|
||||
}
|
||||
|
||||
// syncRate returns the rate of processed
|
||||
// blocks in the last syncRateWindowDuration
|
||||
// duration.
|
||||
func (dag *BlockDAG) syncRate() float64 {
|
||||
dag.RLock()
|
||||
defer dag.RUnlock()
|
||||
return float64(len(dag.recentBlockProcessingTimestampsRelevantWindow())) / syncRateWindowDuration.Seconds()
|
||||
}
|
||||
|
||||
// IsSyncRateBelowThreshold checks whether the sync rate
|
||||
// is below the expected threshold.
|
||||
func (dag *BlockDAG) IsSyncRateBelowThreshold(maxDeviation float64) bool {
|
||||
if dag.uptime() < syncRateWindowDuration {
|
||||
return false
|
||||
}
|
||||
|
||||
return dag.syncRate() < 1/dag.dagParams.TargetTimePerBlock.Seconds()*maxDeviation
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) uptime() time.Duration {
|
||||
return time.Now().Sub(dag.startTime)
|
||||
}
|
||||
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
BIN
blockdag/testdata/blk_0_to_4.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3A.dat
vendored
BIN
blockdag/testdata/blk_3A.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3B.dat
vendored
BIN
blockdag/testdata/blk_3B.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3C.dat
vendored
BIN
blockdag/testdata/blk_3C.dat
vendored
Binary file not shown.
BIN
blockdag/testdata/blk_3D.dat
vendored
BIN
blockdag/testdata/blk_3D.dat
vendored
Binary file not shown.
@@ -1,356 +0,0 @@
|
||||
// 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.Unix()) < 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.Unix())
|
||||
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.Unix()) >= 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.dagParams.Deployments)) {
|
||||
return ThresholdFailed, errors.Errorf("deployment ID %d does not exist", deploymentID)
|
||||
}
|
||||
|
||||
deployment := &dag.dagParams.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.dagParams.Deployments); id++ {
|
||||
deployment := &dag.dagParams.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
|
||||
// current.
|
||||
if dag.isCurrent() {
|
||||
// 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
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,294 +0,0 @@
|
||||
// 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.dagParams.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.dagParams.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.dagParams.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.dagParams.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.dagParams.Deployments); id++ {
|
||||
deployment := &dag.dagParams.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
|
||||
}
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"os"
|
||||
"runtime"
|
||||
|
||||
"github.com/kaspanet/kaspad/limits"
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
"github.com/kaspanet/kaspad/infrastructure/limits"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logs"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
var (
|
||||
cfg *ConfigFlags
|
||||
log *logs.Logger
|
||||
spawn func(func())
|
||||
spawn func(string, func())
|
||||
)
|
||||
|
||||
// realMain is the real main function for the utility. It is necessary to work
|
||||
|
||||
@@ -7,7 +7,7 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
flags "github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
|
||||
@@ -6,15 +6,16 @@ package main
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/kaspad/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// importResults houses the stats and result as an import operation.
|
||||
@@ -39,8 +40,8 @@ type blockImporter struct {
|
||||
receivedLogBlocks int64
|
||||
receivedLogTx int64
|
||||
lastHeight int64
|
||||
lastBlockTime time.Time
|
||||
lastLogTime time.Time
|
||||
lastBlockTime mstime.Time
|
||||
lastLogTime mstime.Time
|
||||
}
|
||||
|
||||
// readBlock reads the next block from the input file.
|
||||
@@ -67,10 +68,10 @@ func (bi *blockImporter) readBlock() ([]byte, error) {
|
||||
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if blockLen > wire.MaxMessagePayload {
|
||||
if blockLen > domainmessage.MaxMessagePayload {
|
||||
return nil, errors.Errorf("block payload of %d bytes is larger "+
|
||||
"than the max allowed %d bytes", blockLen,
|
||||
wire.MaxMessagePayload)
|
||||
domainmessage.MaxMessagePayload)
|
||||
}
|
||||
|
||||
serializedBlock := make([]byte, blockLen)
|
||||
@@ -170,7 +171,7 @@ out:
|
||||
func (bi *blockImporter) logProgress() {
|
||||
bi.receivedLogBlocks++
|
||||
|
||||
now := time.Now()
|
||||
now := mstime.Now()
|
||||
duration := now.Sub(bi.lastLogTime)
|
||||
if duration < time.Second*time.Duration(cfg.Progress) {
|
||||
return
|
||||
@@ -264,12 +265,12 @@ func (bi *blockImporter) Import() chan *importResults {
|
||||
// Start up the read and process handling goroutines. This setup allows
|
||||
// blocks to be read from disk in parallel while being processed.
|
||||
bi.wg.Add(2)
|
||||
spawn(bi.readHandler)
|
||||
spawn(bi.processHandler)
|
||||
spawn("blockImporter.readHandler", bi.readHandler)
|
||||
spawn("blockImporter.processHandler", bi.processHandler)
|
||||
|
||||
// Wait for the import to finish in a separate goroutine and signal
|
||||
// the status handler when done.
|
||||
spawn(func() {
|
||||
spawn("blockImporter.sendToDoneChan", func() {
|
||||
bi.wg.Wait()
|
||||
bi.doneChan <- true
|
||||
})
|
||||
@@ -277,7 +278,7 @@ func (bi *blockImporter) Import() chan *importResults {
|
||||
// Start the status handler and return the result channel that it will
|
||||
// send the results on when the import is done.
|
||||
resultChan := make(chan *importResults)
|
||||
spawn(func() {
|
||||
spawn("blockImporter.statusHandler", func() {
|
||||
bi.statusHandler(resultChan)
|
||||
})
|
||||
return resultChan
|
||||
@@ -315,6 +316,6 @@ func newBlockImporter(r io.ReadSeeker) (*blockImporter, error) {
|
||||
errChan: make(chan error),
|
||||
quit: make(chan struct{}),
|
||||
dag: dag,
|
||||
lastLogTime: time.Now(),
|
||||
lastLogTime: mstime.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/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/rpcmodel"
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ const (
|
||||
// unusableFlags are the command usage flags which this utility are not
|
||||
// able to use. In particular it doesn't support websockets and
|
||||
// consequently notifications.
|
||||
unusableFlags = rpcmodel.UFWebsocketOnly | rpcmodel.UFNotification
|
||||
unusableFlags = model.UFWebsocketOnly | model.UFNotification
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -45,10 +45,10 @@ func listCommands() {
|
||||
)
|
||||
|
||||
// Get a list of registered commands and categorize and filter them.
|
||||
cmdMethods := rpcmodel.RegisteredCmdMethods()
|
||||
cmdMethods := model.RegisteredCmdMethods()
|
||||
categorized := make([][]string, numCategories)
|
||||
for _, method := range cmdMethods {
|
||||
flags, err := rpcmodel.MethodUsageFlags(method)
|
||||
flags, err := model.MethodUsageFlags(method)
|
||||
if err != nil {
|
||||
// This should never happen since the method was just
|
||||
// returned from the package, but be safe.
|
||||
@@ -60,7 +60,7 @@ func listCommands() {
|
||||
continue
|
||||
}
|
||||
|
||||
usage, err := rpcmodel.MethodUsageText(method)
|
||||
usage, err := model.MethodUsageText(method)
|
||||
if err != nil {
|
||||
// This should never happen since the method was just
|
||||
// returned from the package, but be safe.
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/btcsuite/go-socks/socks"
|
||||
"github.com/kaspanet/kaspad/rpcmodel"
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
)
|
||||
|
||||
// newHTTPClient returns a new HTTP client that is configured according to the
|
||||
@@ -117,7 +117,7 @@ func sendPostRequest(marshalledJSON []byte, cfg *ConfigFlags) ([]byte, error) {
|
||||
}
|
||||
|
||||
// Unmarshal the response.
|
||||
var resp rpcmodel.Response
|
||||
var resp model.Response
|
||||
if err := json.Unmarshal(respBytes, &resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/rpcmodel"
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -21,7 +21,7 @@ const (
|
||||
|
||||
// commandUsage display the usage for a specific command.
|
||||
func commandUsage(method string) {
|
||||
usage, err := rpcmodel.MethodUsageText(method)
|
||||
usage, err := model.MethodUsageText(method)
|
||||
if err != nil {
|
||||
// This should never happen since the method was already checked
|
||||
// before calling this function, but be safe.
|
||||
@@ -60,7 +60,7 @@ func main() {
|
||||
// Ensure the specified method identifies a valid registered command and
|
||||
// is one of the usable types.
|
||||
method := args[0]
|
||||
usageFlags, err := rpcmodel.MethodUsageFlags(method)
|
||||
usageFlags, err := model.MethodUsageFlags(method)
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Unrecognized command '%s'\n", method)
|
||||
fmt.Fprintln(os.Stderr, listCmdMessage)
|
||||
@@ -105,13 +105,13 @@ func main() {
|
||||
|
||||
// Attempt to create the appropriate command using the arguments
|
||||
// provided by the user.
|
||||
cmd, err := rpcmodel.NewCommand(method, params...)
|
||||
cmd, err := model.NewCommand(method, params...)
|
||||
if err != nil {
|
||||
// Show the error along with its error code when it's a
|
||||
// rpcmodel.Error as it reallistcally will always be since the
|
||||
// model.Error as it reallistcally will always be since the
|
||||
// NewCommand function is only supposed to return errors of that
|
||||
// type.
|
||||
var rpcModelErr rpcmodel.Error
|
||||
var rpcModelErr model.Error
|
||||
if ok := errors.As(err, &rpcModelErr); ok {
|
||||
fmt.Fprintf(os.Stderr, "%s error: %s (command code: %s)\n",
|
||||
method, err, rpcModelErr.ErrorCode)
|
||||
@@ -119,7 +119,7 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
// The error is not a rpcmodel.Error and this really should not
|
||||
// The error is not a model.Error and this really should not
|
||||
// happen. Nevertheless, fallback to just showing the error
|
||||
// if it should happen due to a bug in the package.
|
||||
fmt.Fprintf(os.Stderr, "%s error: %s\n", method, err)
|
||||
@@ -129,7 +129,7 @@ func main() {
|
||||
|
||||
// Marshal the command into a JSON-RPC byte slice in preparation for
|
||||
// sending it to the RPC server.
|
||||
marshalledJSON, err := rpcmodel.MarshalCommand(1, cmd)
|
||||
marshalledJSON, err := model.MarshalCommand(1, cmd)
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
os.Exit(1)
|
||||
|
||||
@@ -1,39 +1,39 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/network/rpc/client"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
)
|
||||
|
||||
type minerClient struct {
|
||||
*rpcclient.Client
|
||||
*client.Client
|
||||
onBlockAdded chan struct{}
|
||||
}
|
||||
|
||||
func newMinerClient(connCfg *rpcclient.ConnConfig) (*minerClient, error) {
|
||||
client := &minerClient{
|
||||
func newMinerClient(connCfg *client.ConnConfig) (*minerClient, error) {
|
||||
minerClient := &minerClient{
|
||||
onBlockAdded: make(chan struct{}, 1),
|
||||
}
|
||||
notificationHandlers := &rpcclient.NotificationHandlers{
|
||||
OnFilteredBlockAdded: func(_ uint64, header *wire.BlockHeader,
|
||||
notificationHandlers := &client.NotificationHandlers{
|
||||
OnFilteredBlockAdded: func(_ uint64, header *domainmessage.BlockHeader,
|
||||
txs []*util.Tx) {
|
||||
client.onBlockAdded <- struct{}{}
|
||||
minerClient.onBlockAdded <- struct{}{}
|
||||
},
|
||||
}
|
||||
var err error
|
||||
client.Client, err = rpcclient.New(connCfg, notificationHandlers)
|
||||
minerClient.Client, err = client.New(connCfg, notificationHandlers)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Error connecting to address %s: %s", connCfg.Host, err)
|
||||
}
|
||||
|
||||
if err = client.NotifyBlocks(); err != nil {
|
||||
return nil, errors.Errorf("Error while registering client %s for block notifications: %s", client.Host(), err)
|
||||
if err = minerClient.NotifyBlocks(); err != nil {
|
||||
return nil, errors.Wrapf(err, "error while registering minerClient %s for block notifications", minerClient.Host())
|
||||
}
|
||||
return client, nil
|
||||
return minerClient, nil
|
||||
}
|
||||
|
||||
func connectToServer(cfg *configFlags) (*minerClient, error) {
|
||||
@@ -47,7 +47,7 @@ func connectToServer(cfg *configFlags) (*minerClient, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connCfg := &rpcclient.ConnConfig{
|
||||
connCfg := &client.ConnConfig{
|
||||
Host: rpcAddr,
|
||||
Endpoint: "ws",
|
||||
User: cfg.RPCUser,
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
@@ -36,6 +36,7 @@ type configFlags struct {
|
||||
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
|
||||
RPCCert string `short:"c" long:"rpccert" description:"RPC server certificate chain for validation"`
|
||||
DisableTLS bool `long:"notls" description:"Disable TLS"`
|
||||
MiningAddr string `long:"miningaddr" description:"Address to mine to"`
|
||||
Verbose bool `long:"verbose" short:"v" description:"Enable logging of RPC requests"`
|
||||
NumberOfBlocks uint64 `short:"n" long:"numblocks" description:"Number of blocks to mine. If omitted, will mine until the process is interrupted."`
|
||||
BlockDelay uint64 `long:"block-delay" description:"Delay for block submission (in milliseconds). This is used only for testing purposes."`
|
||||
|
||||
@@ -2,8 +2,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/logs"
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logs"
|
||||
"github.com/kaspanet/kaspad/network/rpc/client"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"os"
|
||||
)
|
||||
@@ -28,5 +28,5 @@ func initLog(logFile, errLogFile string) {
|
||||
}
|
||||
|
||||
func enableRPCLogging() {
|
||||
rpcclient.UseLogger(backendLog, logs.LevelTrace)
|
||||
client.UseLogger(backendLog, logs.LevelTrace)
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"os"
|
||||
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
@@ -10,13 +11,13 @@ import (
|
||||
|
||||
_ "net/http/pprof"
|
||||
|
||||
"github.com/kaspanet/kaspad/signal"
|
||||
"github.com/kaspanet/kaspad/infrastructure/signal"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
"github.com/kaspanet/kaspad/util/profiling"
|
||||
)
|
||||
|
||||
func main() {
|
||||
defer panics.HandlePanic(log, nil)
|
||||
defer panics.HandlePanic(log, "MAIN", nil)
|
||||
interrupt := signal.InterruptListener()
|
||||
|
||||
cfg, err := parseConfig()
|
||||
@@ -39,15 +40,20 @@ func main() {
|
||||
|
||||
client, err := connectToServer(cfg)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "Error connecting to the RPC server"))
|
||||
panic(errors.Wrap(err, "error connecting to the RPC server"))
|
||||
}
|
||||
defer client.Disconnect()
|
||||
|
||||
miningAddr, err := util.DecodeAddress(cfg.MiningAddr, cfg.ActiveNetParams.Prefix)
|
||||
if err != nil {
|
||||
panic(errors.Wrap(err, "error decoding mining address"))
|
||||
}
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
spawn(func() {
|
||||
err = mineLoop(client, cfg.NumberOfBlocks, cfg.BlockDelay, cfg.MineWhenNotSynced)
|
||||
spawn("mineLoop", func() {
|
||||
err = mineLoop(client, cfg.NumberOfBlocks, cfg.BlockDelay, cfg.MineWhenNotSynced, miningAddr)
|
||||
if err != nil {
|
||||
panic(errors.Errorf("Error in mine loop: %s", err))
|
||||
panic(errors.Wrap(err, "error in mine loop"))
|
||||
}
|
||||
doneChan <- struct{}{}
|
||||
})
|
||||
|
||||
@@ -1,23 +1,17 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
nativeerrors "errors"
|
||||
"math/rand"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/rpcclient"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/rpcmodel"
|
||||
clientpkg "github.com/kaspanet/kaspad/network/rpc/client"
|
||||
"github.com/kaspanet/kaspad/network/rpc/model"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var random = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
@@ -25,21 +19,23 @@ var hashesTried uint64
|
||||
|
||||
const logHashRateInterval = 10 * time.Second
|
||||
|
||||
func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64, mineWhenNotSynced bool) error {
|
||||
func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64, mineWhenNotSynced bool,
|
||||
miningAddr util.Address) error {
|
||||
|
||||
errChan := make(chan error)
|
||||
|
||||
templateStopChan := make(chan struct{})
|
||||
|
||||
doneChan := make(chan struct{})
|
||||
spawn(func() {
|
||||
spawn("mineLoop-internalLoop", func() {
|
||||
wg := sync.WaitGroup{}
|
||||
for i := uint64(0); numberOfBlocks == 0 || i < numberOfBlocks; i++ {
|
||||
foundBlock := make(chan *util.Block)
|
||||
mineNextBlock(client, foundBlock, mineWhenNotSynced, templateStopChan, errChan)
|
||||
mineNextBlock(client, miningAddr, foundBlock, mineWhenNotSynced, templateStopChan, errChan)
|
||||
block := <-foundBlock
|
||||
templateStopChan <- struct{}{}
|
||||
wg.Add(1)
|
||||
spawn(func() {
|
||||
spawn("mineLoop-handleFoundBlock", func() {
|
||||
if blockDelay != 0 {
|
||||
time.Sleep(time.Duration(blockDelay) * time.Millisecond)
|
||||
}
|
||||
@@ -65,7 +61,7 @@ func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64, min
|
||||
}
|
||||
|
||||
func logHashRate() {
|
||||
spawn(func() {
|
||||
spawn("logHashRate", func() {
|
||||
lastCheck := time.Now()
|
||||
for range time.Tick(logHashRateInterval) {
|
||||
currentHashesTried := hashesTried
|
||||
@@ -80,14 +76,14 @@ func logHashRate() {
|
||||
})
|
||||
}
|
||||
|
||||
func mineNextBlock(client *minerClient, foundBlock chan *util.Block, mineWhenNotSynced bool,
|
||||
func mineNextBlock(client *minerClient, miningAddr util.Address, foundBlock chan *util.Block, mineWhenNotSynced bool,
|
||||
templateStopChan chan struct{}, errChan chan error) {
|
||||
|
||||
newTemplateChan := make(chan *rpcmodel.GetBlockTemplateResult)
|
||||
spawn(func() {
|
||||
templatesLoop(client, newTemplateChan, errChan, templateStopChan)
|
||||
newTemplateChan := make(chan *model.GetBlockTemplateResult)
|
||||
spawn("templatesLoop", func() {
|
||||
templatesLoop(client, miningAddr, newTemplateChan, errChan, templateStopChan)
|
||||
})
|
||||
spawn(func() {
|
||||
spawn("solveLoop", func() {
|
||||
solveLoop(newTemplateChan, foundBlock, mineWhenNotSynced, errChan)
|
||||
})
|
||||
}
|
||||
@@ -95,64 +91,18 @@ func mineNextBlock(client *minerClient, foundBlock chan *util.Block, mineWhenNot
|
||||
func handleFoundBlock(client *minerClient, block *util.Block) error {
|
||||
log.Infof("Found block %s with parents %s. Submitting to %s", block.Hash(), block.MsgBlock().Header.ParentHashes, client.Host())
|
||||
|
||||
err := client.SubmitBlock(block, &rpcmodel.SubmitBlockOptions{})
|
||||
err := client.SubmitBlock(block, &model.SubmitBlockOptions{})
|
||||
if err != nil {
|
||||
return errors.Errorf("Error submitting block %s to %s: %s", block.Hash(), client.Host(), err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseBlock(template *rpcmodel.GetBlockTemplateResult) (*util.Block, error) {
|
||||
// parse parent hashes
|
||||
parentHashes := make([]*daghash.Hash, len(template.ParentHashes))
|
||||
for i, parentHash := range template.ParentHashes {
|
||||
hash, err := daghash.NewHashFromStr(parentHash)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Error decoding hash %s: %s", parentHash, err)
|
||||
}
|
||||
parentHashes[i] = hash
|
||||
}
|
||||
|
||||
// parse Bits
|
||||
bitsUint64, err := strconv.ParseUint(template.Bits, 16, 32)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Error decoding bits %s: %s", template.Bits, err)
|
||||
}
|
||||
bits := uint32(bitsUint64)
|
||||
|
||||
// parseAcceptedIDMerkleRoot
|
||||
acceptedIDMerkleRoot, err := daghash.NewHashFromStr(template.AcceptedIDMerkleRoot)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Error parsing acceptedIDMerkleRoot: %s", err)
|
||||
}
|
||||
utxoCommitment, err := daghash.NewHashFromStr(template.UTXOCommitment)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("Error parsing utxoCommitment: %s", err)
|
||||
}
|
||||
// parse rest of block
|
||||
msgBlock := wire.NewMsgBlock(
|
||||
wire.NewBlockHeader(template.Version, parentHashes, &daghash.Hash{},
|
||||
acceptedIDMerkleRoot, utxoCommitment, bits, 0))
|
||||
|
||||
for i, txResult := range append([]rpcmodel.GetBlockTemplateResultTx{*template.CoinbaseTxn}, template.Transactions...) {
|
||||
reader := hex.NewDecoder(strings.NewReader(txResult.Data))
|
||||
tx := &wire.MsgTx{}
|
||||
if err := tx.KaspaDecode(reader, 0); err != nil {
|
||||
return nil, errors.Errorf("Error decoding tx #%d: %s", i, err)
|
||||
}
|
||||
msgBlock.AddTransaction(tx)
|
||||
}
|
||||
|
||||
block := util.NewBlock(msgBlock)
|
||||
msgBlock.Header.HashMerkleRoot = blockdag.BuildHashMerkleTreeStore(block.Transactions()).Root()
|
||||
return block, nil
|
||||
}
|
||||
|
||||
func solveBlock(block *util.Block, stopChan chan struct{}, foundBlock chan *util.Block) {
|
||||
msgBlock := block.MsgBlock()
|
||||
targetDifficulty := util.CompactToBig(msgBlock.Header.Bits)
|
||||
initialNonce := random.Uint64()
|
||||
for i := random.Uint64(); i != initialNonce-1; i++ {
|
||||
for i := initialNonce; i != initialNonce-1; i++ {
|
||||
select {
|
||||
case <-stopChan:
|
||||
return
|
||||
@@ -169,7 +119,9 @@ func solveBlock(block *util.Block, stopChan chan struct{}, foundBlock chan *util
|
||||
|
||||
}
|
||||
|
||||
func templatesLoop(client *minerClient, newTemplateChan chan *rpcmodel.GetBlockTemplateResult, errChan chan error, stopChan chan struct{}) {
|
||||
func templatesLoop(client *minerClient, miningAddr util.Address,
|
||||
newTemplateChan chan *model.GetBlockTemplateResult, errChan chan error, stopChan chan struct{}) {
|
||||
|
||||
longPollID := ""
|
||||
getBlockTemplateLongPoll := func() {
|
||||
if longPollID != "" {
|
||||
@@ -177,8 +129,8 @@ func templatesLoop(client *minerClient, newTemplateChan chan *rpcmodel.GetBlockT
|
||||
} else {
|
||||
log.Infof("Requesting template without longPollID from %s", client.Host())
|
||||
}
|
||||
template, err := getBlockTemplate(client, longPollID)
|
||||
if nativeerrors.Is(err, rpcclient.ErrResponseTimedOut) {
|
||||
template, err := getBlockTemplate(client, miningAddr, longPollID)
|
||||
if nativeerrors.Is(err, clientpkg.ErrResponseTimedOut) {
|
||||
log.Infof("Got timeout while requesting template '%s' from %s", longPollID, client.Host())
|
||||
return
|
||||
} else if err != nil {
|
||||
@@ -205,11 +157,11 @@ func templatesLoop(client *minerClient, newTemplateChan chan *rpcmodel.GetBlockT
|
||||
}
|
||||
}
|
||||
|
||||
func getBlockTemplate(client *minerClient, longPollID string) (*rpcmodel.GetBlockTemplateResult, error) {
|
||||
return client.GetBlockTemplate([]string{"coinbasetxn"}, longPollID)
|
||||
func getBlockTemplate(client *minerClient, miningAddr util.Address, longPollID string) (*model.GetBlockTemplateResult, error) {
|
||||
return client.GetBlockTemplate(miningAddr.String(), longPollID)
|
||||
}
|
||||
|
||||
func solveLoop(newTemplateChan chan *rpcmodel.GetBlockTemplateResult, foundBlock chan *util.Block,
|
||||
func solveLoop(newTemplateChan chan *model.GetBlockTemplateResult, foundBlock chan *util.Block,
|
||||
mineWhenNotSynced bool, errChan chan error) {
|
||||
|
||||
var stopOldTemplateSolving chan struct{}
|
||||
@@ -227,13 +179,13 @@ func solveLoop(newTemplateChan chan *rpcmodel.GetBlockTemplateResult, foundBlock
|
||||
}
|
||||
|
||||
stopOldTemplateSolving = make(chan struct{})
|
||||
block, err := parseBlock(template)
|
||||
block, err := clientpkg.ConvertGetBlockTemplateResultToBlock(template)
|
||||
if err != nil {
|
||||
errChan <- errors.Errorf("Error parsing block: %s", err)
|
||||
return
|
||||
}
|
||||
|
||||
spawn(func() {
|
||||
spawn("solveBlock", func() {
|
||||
solveBlock(block, stopOldTemplateSolving, foundBlock)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package main
|
||||
|
||||
import (
|
||||
"github.com/jessevdk/go-flags"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
)
|
||||
|
||||
var activeConfig *ConfigFlags
|
||||
|
||||
@@ -5,9 +5,9 @@ import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"os"
|
||||
)
|
||||
@@ -58,12 +58,12 @@ func parsePrivateKey(privateKeyHex string) (*secp256k1.PrivateKey, error) {
|
||||
return secp256k1.DeserializePrivateKeyFromSlice(privateKeyBytes)
|
||||
}
|
||||
|
||||
func parseTransaction(transactionHex string) (*wire.MsgTx, error) {
|
||||
func parseTransaction(transactionHex string) (*domainmessage.MsgTx, error) {
|
||||
serializedTx, err := hex.DecodeString(transactionHex)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "couldn't decode transaction hex")
|
||||
}
|
||||
var transaction wire.MsgTx
|
||||
var transaction domainmessage.MsgTx
|
||||
err = transaction.Deserialize(bytes.NewReader(serializedTx))
|
||||
return &transaction, err
|
||||
}
|
||||
@@ -81,7 +81,7 @@ func createScriptPubKey(publicKey *secp256k1.SchnorrPublicKey) ([]byte, error) {
|
||||
return scriptPubKey, err
|
||||
}
|
||||
|
||||
func signTransaction(transaction *wire.MsgTx, privateKey *secp256k1.PrivateKey, scriptPubKey []byte) error {
|
||||
func signTransaction(transaction *domainmessage.MsgTx, privateKey *secp256k1.PrivateKey, scriptPubKey []byte) error {
|
||||
for i, transactionInput := range transaction.TxIn {
|
||||
signatureScript, err := txscript.SignatureScript(transaction, i, scriptPubKey, txscript.SigHashAll, privateKey, true)
|
||||
if err != nil {
|
||||
@@ -92,7 +92,7 @@ func signTransaction(transaction *wire.MsgTx, privateKey *secp256k1.PrivateKey,
|
||||
return nil
|
||||
}
|
||||
|
||||
func serializeTransaction(transaction *wire.MsgTx) (string, error) {
|
||||
func serializeTransaction(transaction *domainmessage.MsgTx) (string, error) {
|
||||
buf := bytes.NewBuffer(make([]byte, 0, transaction.SerializeSize()))
|
||||
err := transaction.Serialize(buf)
|
||||
serializedTransaction := hex.EncodeToString(buf.Bytes())
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
connmgr
|
||||
=======
|
||||
|
||||
[](https://choosealicense.com/licenses/isc/)
|
||||
[](http://godoc.org/github.com/kaspanet/kaspad/connmgr)
|
||||
|
||||
Package connmgr implements a generic Kaspa network connection manager.
|
||||
|
||||
## Overview
|
||||
|
||||
Connection Manager handles all the general connection concerns such as
|
||||
maintaining a set number of outbound connections, sourcing peers, banning,
|
||||
limiting max connections, etc.
|
||||
|
||||
The package provides a generic connection manager which is able to accept
|
||||
connection requests from a source or a set of given addresses, dial them and
|
||||
notify the caller on connections. The main intended use is to initialize a pool
|
||||
of active connections and maintain them to remain connected to the P2P network.
|
||||
|
||||
In addition the connection manager provides the following utilities:
|
||||
|
||||
- Notifications on connections or disconnections
|
||||
- Handle failures and retry new addresses from the source
|
||||
- Connect only to specified addresses
|
||||
- Permanent connections with increasing backoff retry timers
|
||||
- Disconnect or Remove an established connection
|
||||
|
||||
@@ -1,779 +0,0 @@
|
||||
// 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 connmgr
|
||||
|
||||
import (
|
||||
nativeerrors "errors"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/addrmgr"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// maxFailedAttempts is the maximum number of successive failed connection
|
||||
// attempts after which network failure is assumed and new connections will
|
||||
// be delayed by the configured retry duration.
|
||||
const maxFailedAttempts = 25
|
||||
|
||||
var (
|
||||
// maxRetryDuration is the max duration of time retrying of a persistent
|
||||
// connection is allowed to grow to. This is necessary since the retry
|
||||
// logic uses a backoff mechanism which increases the interval base times
|
||||
// the number of retries that have been done.
|
||||
maxRetryDuration = time.Minute * 5
|
||||
|
||||
// defaultRetryDuration is the default duration of time for retrying
|
||||
// persistent connections.
|
||||
defaultRetryDuration = time.Second * 5
|
||||
)
|
||||
|
||||
var (
|
||||
//ErrDialNil is used to indicate that Dial cannot be nil in the configuration.
|
||||
ErrDialNil = errors.New("Config: Dial cannot be nil")
|
||||
|
||||
// ErrMaxOutboundPeers is an error that is thrown when the max amount of peers had
|
||||
// been reached.
|
||||
ErrMaxOutboundPeers = errors.New("max outbound peers reached")
|
||||
|
||||
// ErrAlreadyConnected is an error that is thrown if the peer is already
|
||||
// connected.
|
||||
ErrAlreadyConnected = errors.New("peer already connected")
|
||||
|
||||
// ErrAlreadyPermanent is an error that is thrown if the peer is already
|
||||
// connected as a permanent peer.
|
||||
ErrAlreadyPermanent = errors.New("peer exists as a permanent peer")
|
||||
|
||||
// ErrPeerNotFound is an error that is thrown if the peer was not found.
|
||||
ErrPeerNotFound = errors.New("peer not found")
|
||||
|
||||
//ErrAddressManagerNil is used to indicate that Address Manager cannot be nil in the configuration.
|
||||
ErrAddressManagerNil = errors.New("Config: Address manager cannot be nil")
|
||||
)
|
||||
|
||||
// ConnState represents the state of the requested connection.
|
||||
type ConnState uint8
|
||||
|
||||
// ConnState can be either pending, established, disconnected or failed. When
|
||||
// a new connection is requested, it is attempted and categorized as
|
||||
// established or failed depending on the connection result. An established
|
||||
// connection which was disconnected is categorized as disconnected.
|
||||
const (
|
||||
ConnPending ConnState = iota
|
||||
ConnFailing
|
||||
ConnCanceled
|
||||
ConnEstablished
|
||||
ConnDisconnected
|
||||
)
|
||||
|
||||
// ConnReq is the connection request to a network address. If permanent, the
|
||||
// connection will be retried on disconnection.
|
||||
type ConnReq struct {
|
||||
// The following variables must only be used atomically.
|
||||
id uint64
|
||||
|
||||
Addr *net.TCPAddr
|
||||
Permanent bool
|
||||
|
||||
conn net.Conn
|
||||
state ConnState
|
||||
stateMtx sync.RWMutex
|
||||
retryCount uint32
|
||||
}
|
||||
|
||||
// updateState updates the state of the connection request.
|
||||
func (c *ConnReq) updateState(state ConnState) {
|
||||
c.stateMtx.Lock()
|
||||
defer c.stateMtx.Unlock()
|
||||
c.state = state
|
||||
}
|
||||
|
||||
// ID returns a unique identifier for the connection request.
|
||||
func (c *ConnReq) ID() uint64 {
|
||||
return atomic.LoadUint64(&c.id)
|
||||
}
|
||||
|
||||
// State is the connection state of the requested connection.
|
||||
func (c *ConnReq) State() ConnState {
|
||||
c.stateMtx.RLock()
|
||||
defer c.stateMtx.RUnlock()
|
||||
state := c.state
|
||||
return state
|
||||
}
|
||||
|
||||
// String returns a human-readable string for the connection request.
|
||||
func (c *ConnReq) String() string {
|
||||
if c.Addr == nil || c.Addr.String() == "" {
|
||||
return fmt.Sprintf("reqid %d", atomic.LoadUint64(&c.id))
|
||||
}
|
||||
return fmt.Sprintf("%s (reqid %d)", c.Addr, atomic.LoadUint64(&c.id))
|
||||
}
|
||||
|
||||
// Config holds the configuration options related to the connection manager.
|
||||
type Config struct {
|
||||
// Listeners defines a slice of listeners for which the connection
|
||||
// manager will take ownership of and accept connections. When a
|
||||
// connection is accepted, the OnAccept handler will be invoked with the
|
||||
// connection. Since the connection manager takes ownership of these
|
||||
// listeners, they will be closed when the connection manager is
|
||||
// stopped.
|
||||
//
|
||||
// This field will not have any effect if the OnAccept field is not
|
||||
// also specified. It may be nil if the caller does not wish to listen
|
||||
// for incoming connections.
|
||||
Listeners []net.Listener
|
||||
|
||||
// OnAccept is a callback that is fired when an inbound connection is
|
||||
// accepted. It is the caller's responsibility to close the connection.
|
||||
// Failure to close the connection will result in the connection manager
|
||||
// believing the connection is still active and thus have undesirable
|
||||
// side effects such as still counting toward maximum connection limits.
|
||||
//
|
||||
// This field will not have any effect if the Listeners field is not
|
||||
// also specified since there couldn't possibly be any accepted
|
||||
// connections in that case.
|
||||
OnAccept func(net.Conn)
|
||||
|
||||
// TargetOutbound is the number of outbound network connections to
|
||||
// maintain. Defaults to 8.
|
||||
TargetOutbound uint32
|
||||
|
||||
// RetryDuration is the duration to wait before retrying connection
|
||||
// requests. Defaults to 5s.
|
||||
RetryDuration time.Duration
|
||||
|
||||
// OnConnection is a callback that is fired when a new outbound
|
||||
// connection is established.
|
||||
OnConnection func(*ConnReq, net.Conn)
|
||||
|
||||
// OnConnectionFailed is a callback that is fired when a new outbound
|
||||
// connection has failed to be established.
|
||||
OnConnectionFailed func(*ConnReq)
|
||||
|
||||
// OnDisconnection is a callback that is fired when an outbound
|
||||
// connection is disconnected.
|
||||
OnDisconnection func(*ConnReq)
|
||||
|
||||
AddrManager *addrmgr.AddrManager
|
||||
|
||||
// Dial connects to the address on the named network. It cannot be nil.
|
||||
Dial func(net.Addr) (net.Conn, error)
|
||||
}
|
||||
|
||||
// registerPending is used to register a pending connection attempt. By
|
||||
// registering pending connection attempts we allow callers to cancel pending
|
||||
// connection attempts before their successful or in the case they're not
|
||||
// longer wanted.
|
||||
type registerPending struct {
|
||||
c *ConnReq
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// handleConnected is used to queue a successful connection.
|
||||
type handleConnected struct {
|
||||
c *ConnReq
|
||||
conn net.Conn
|
||||
}
|
||||
|
||||
// handleDisconnected is used to remove a connection.
|
||||
type handleDisconnected struct {
|
||||
id uint64
|
||||
retry bool
|
||||
}
|
||||
|
||||
// handleFailed is used to remove a pending connection.
|
||||
type handleFailed struct {
|
||||
c *ConnReq
|
||||
err error
|
||||
}
|
||||
|
||||
// ConnManager provides a manager to handle network connections.
|
||||
type ConnManager struct {
|
||||
// The following variables must only be used atomically.
|
||||
connReqCount uint64
|
||||
start int32
|
||||
stop int32
|
||||
|
||||
addressMtx sync.Mutex
|
||||
usedOutboundGroups map[string]int64
|
||||
usedAddresses map[string]struct{}
|
||||
|
||||
cfg Config
|
||||
wg sync.WaitGroup
|
||||
failedAttempts uint64
|
||||
requests chan interface{}
|
||||
quit chan struct{}
|
||||
}
|
||||
|
||||
// handleFailedConn handles a connection failed due to a disconnect or any
|
||||
// other failure. If permanent, it retries the connection after the configured
|
||||
// retry duration. Otherwise, if required, it makes a new connection request.
|
||||
// After maxFailedConnectionAttempts new connections will be retried after the
|
||||
// configured retry duration.
|
||||
func (cm *ConnManager) handleFailedConn(c *ConnReq, err error) {
|
||||
if atomic.LoadInt32(&cm.stop) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Don't write throttled logs more than once every throttledConnFailedLogInterval
|
||||
shouldWriteLog := shouldWriteConnFailedLog(err)
|
||||
if shouldWriteLog {
|
||||
// If we are to write a log, set its lastLogTime to now
|
||||
setConnFailedLastLogTime(err, time.Now())
|
||||
}
|
||||
|
||||
if c.Permanent {
|
||||
c.retryCount++
|
||||
d := time.Duration(c.retryCount) * cm.cfg.RetryDuration
|
||||
if d > maxRetryDuration {
|
||||
d = maxRetryDuration
|
||||
}
|
||||
if shouldWriteLog {
|
||||
log.Debugf("Retrying further connections to %s every %s", c, d)
|
||||
}
|
||||
spawnAfter(d, func() {
|
||||
cm.connect(c)
|
||||
})
|
||||
} else {
|
||||
if c.Addr != nil {
|
||||
cm.releaseAddress(c.Addr)
|
||||
}
|
||||
cm.failedAttempts++
|
||||
if cm.failedAttempts >= maxFailedAttempts {
|
||||
if shouldWriteLog {
|
||||
log.Debugf("Max failed connection attempts reached: [%d] "+
|
||||
"-- retrying further connections every %s", maxFailedAttempts,
|
||||
cm.cfg.RetryDuration)
|
||||
}
|
||||
spawnAfter(cm.cfg.RetryDuration, cm.NewConnReq)
|
||||
} else {
|
||||
spawn(cm.NewConnReq)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnManager) releaseAddress(addr *net.TCPAddr) {
|
||||
cm.addressMtx.Lock()
|
||||
defer cm.addressMtx.Unlock()
|
||||
|
||||
groupKey := usedOutboundGroupsKey(addr)
|
||||
cm.usedOutboundGroups[groupKey]--
|
||||
if cm.usedOutboundGroups[groupKey] < 0 {
|
||||
panic(fmt.Errorf("cm.usedOutboundGroups[%s] has a negative value of %d. This should never happen", groupKey, cm.usedOutboundGroups[groupKey]))
|
||||
}
|
||||
delete(cm.usedAddresses, usedAddressesKey(addr))
|
||||
}
|
||||
|
||||
func (cm *ConnManager) markAddressAsUsed(addr *net.TCPAddr) {
|
||||
cm.usedOutboundGroups[usedOutboundGroupsKey(addr)]++
|
||||
cm.usedAddresses[usedAddressesKey(addr)] = struct{}{}
|
||||
}
|
||||
|
||||
func (cm *ConnManager) isOutboundGroupUsed(addr *net.TCPAddr) bool {
|
||||
_, ok := cm.usedOutboundGroups[usedOutboundGroupsKey(addr)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (cm *ConnManager) isAddressUsed(addr *net.TCPAddr) bool {
|
||||
_, ok := cm.usedAddresses[usedAddressesKey(addr)]
|
||||
return ok
|
||||
}
|
||||
|
||||
func usedOutboundGroupsKey(addr *net.TCPAddr) string {
|
||||
// A fake service flag is used since it doesn't affect the group key.
|
||||
na := wire.NewNetAddress(addr, wire.SFNodeNetwork)
|
||||
return addrmgr.GroupKey(na)
|
||||
}
|
||||
|
||||
func usedAddressesKey(addr *net.TCPAddr) string {
|
||||
return addr.String()
|
||||
}
|
||||
|
||||
// throttledError defines an error type whose logs get throttled. This is to
|
||||
// prevent flooding the logs with identical errors.
|
||||
type throttledError error
|
||||
|
||||
var (
|
||||
// throttledConnFailedLogInterval is the minimum duration of time between
|
||||
// the logs defined in throttledConnFailedLogs.
|
||||
throttledConnFailedLogInterval = time.Minute * 10
|
||||
|
||||
// throttledConnFailedLogs are logs that get written at most every
|
||||
// throttledConnFailedLogInterval. Each entry in this map defines a type
|
||||
// of error that we want to throttle. The value of each entry is the last
|
||||
// time that type of log had been written.
|
||||
throttledConnFailedLogs = map[throttledError]time.Time{
|
||||
ErrNoAddress: {},
|
||||
}
|
||||
|
||||
// ErrNoAddress is an error that is thrown when there aren't any
|
||||
// valid connection addresses.
|
||||
ErrNoAddress throttledError = errors.New("no valid connect address")
|
||||
)
|
||||
|
||||
// shouldWriteConnFailedLog resolves whether to write logs related to connection
|
||||
// failures. Errors that had not been previously registered in throttledConnFailedLogs
|
||||
// and non-error (nil values) must always be logged.
|
||||
func shouldWriteConnFailedLog(err error) bool {
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
lastLogTime, ok := throttledConnFailedLogs[err]
|
||||
return !ok || lastLogTime.Add(throttledConnFailedLogInterval).Before(time.Now())
|
||||
}
|
||||
|
||||
// setConnFailedLastLogTime sets the last log time of the specified error
|
||||
func setConnFailedLastLogTime(err error, lastLogTime time.Time) {
|
||||
var throttledErr throttledError
|
||||
nativeerrors.As(err, &throttledErr)
|
||||
throttledConnFailedLogs[err] = lastLogTime
|
||||
}
|
||||
|
||||
// connHandler handles all connection related requests. It must be run as a
|
||||
// goroutine.
|
||||
//
|
||||
// The connection handler makes sure that we maintain a pool of active outbound
|
||||
// connections so that we remain connected to the network. Connection requests
|
||||
// are processed and mapped by their assigned ids.
|
||||
func (cm *ConnManager) connHandler() {
|
||||
|
||||
var (
|
||||
// pending holds all registered conn requests that have yet to
|
||||
// succeed.
|
||||
pending = make(map[uint64]*ConnReq)
|
||||
|
||||
// conns represents the set of all actively connected peers.
|
||||
conns = make(map[uint64]*ConnReq, cm.cfg.TargetOutbound)
|
||||
)
|
||||
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case req := <-cm.requests:
|
||||
switch msg := req.(type) {
|
||||
|
||||
case registerPending:
|
||||
connReq := msg.c
|
||||
connReq.updateState(ConnPending)
|
||||
pending[msg.c.id] = connReq
|
||||
close(msg.done)
|
||||
|
||||
case handleConnected:
|
||||
connReq := msg.c
|
||||
|
||||
if _, ok := pending[connReq.id]; !ok {
|
||||
if msg.conn != nil {
|
||||
msg.conn.Close()
|
||||
}
|
||||
log.Debugf("Ignoring connection for "+
|
||||
"canceled connreq=%s", connReq)
|
||||
continue
|
||||
}
|
||||
|
||||
connReq.updateState(ConnEstablished)
|
||||
connReq.conn = msg.conn
|
||||
conns[connReq.id] = connReq
|
||||
log.Debugf("Connected to %s", connReq)
|
||||
connReq.retryCount = 0
|
||||
|
||||
delete(pending, connReq.id)
|
||||
|
||||
if cm.cfg.OnConnection != nil {
|
||||
cm.cfg.OnConnection(connReq, msg.conn)
|
||||
}
|
||||
|
||||
case handleDisconnected:
|
||||
connReq, ok := conns[msg.id]
|
||||
if !ok {
|
||||
connReq, ok = pending[msg.id]
|
||||
if !ok {
|
||||
log.Errorf("Unknown connid=%d",
|
||||
msg.id)
|
||||
continue
|
||||
}
|
||||
|
||||
// Pending connection was found, remove
|
||||
// it from pending map if we should
|
||||
// ignore a later, successful
|
||||
// connection.
|
||||
connReq.updateState(ConnCanceled)
|
||||
log.Debugf("Canceling: %s", connReq)
|
||||
delete(pending, msg.id)
|
||||
continue
|
||||
|
||||
}
|
||||
|
||||
// An existing connection was located, mark as
|
||||
// disconnected and execute disconnection
|
||||
// callback.
|
||||
log.Debugf("Disconnected from %s", connReq)
|
||||
delete(conns, msg.id)
|
||||
|
||||
if connReq.conn != nil {
|
||||
connReq.conn.Close()
|
||||
}
|
||||
|
||||
if cm.cfg.OnDisconnection != nil {
|
||||
spawn(func() {
|
||||
cm.cfg.OnDisconnection(connReq)
|
||||
})
|
||||
}
|
||||
|
||||
// All internal state has been cleaned up, if
|
||||
// this connection is being removed, we will
|
||||
// make no further attempts with this request.
|
||||
if !msg.retry {
|
||||
connReq.updateState(ConnDisconnected)
|
||||
continue
|
||||
}
|
||||
|
||||
// Otherwise, we will attempt a reconnection.
|
||||
// The connection request is re added to the
|
||||
// pending map, so that subsequent processing
|
||||
// of connections and failures do not ignore
|
||||
// the request.
|
||||
connReq.updateState(ConnPending)
|
||||
log.Debugf("Reconnecting to %s",
|
||||
connReq)
|
||||
pending[msg.id] = connReq
|
||||
cm.handleFailedConn(connReq, nil)
|
||||
|
||||
case handleFailed:
|
||||
connReq := msg.c
|
||||
|
||||
if _, ok := pending[connReq.id]; !ok {
|
||||
log.Debugf("Ignoring connection for "+
|
||||
"canceled conn req: %s", connReq)
|
||||
continue
|
||||
}
|
||||
|
||||
connReq.updateState(ConnFailing)
|
||||
if shouldWriteConnFailedLog(msg.err) {
|
||||
log.Debugf("Failed to connect to %s: %s",
|
||||
connReq, msg.err)
|
||||
}
|
||||
cm.handleFailedConn(connReq, msg.err)
|
||||
|
||||
if cm.cfg.OnConnectionFailed != nil {
|
||||
cm.cfg.OnConnectionFailed(connReq)
|
||||
}
|
||||
}
|
||||
|
||||
case <-cm.quit:
|
||||
break out
|
||||
}
|
||||
}
|
||||
|
||||
cm.wg.Done()
|
||||
log.Trace("Connection handler done")
|
||||
}
|
||||
|
||||
// NotifyConnectionRequestComplete notifies the connection
|
||||
// manager that a peer had been successfully connected and
|
||||
// marked as good.
|
||||
func (cm *ConnManager) NotifyConnectionRequestComplete() {
|
||||
cm.failedAttempts = 0
|
||||
}
|
||||
|
||||
// NewConnReq creates a new connection request and connects to the
|
||||
// corresponding address.
|
||||
func (cm *ConnManager) NewConnReq() {
|
||||
if atomic.LoadInt32(&cm.stop) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
c := &ConnReq{}
|
||||
atomic.StoreUint64(&c.id, atomic.AddUint64(&cm.connReqCount, 1))
|
||||
|
||||
// Submit a request of a pending connection attempt to the connection
|
||||
// manager. By registering the id before the connection is even
|
||||
// established, we'll be able to later cancel the connection via the
|
||||
// Remove method.
|
||||
done := make(chan struct{})
|
||||
select {
|
||||
case cm.requests <- registerPending{c, done}:
|
||||
case <-cm.quit:
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for the registration to successfully add the pending conn req to
|
||||
// the conn manager's internal state.
|
||||
select {
|
||||
case <-done:
|
||||
case <-cm.quit:
|
||||
return
|
||||
}
|
||||
err := cm.associateAddressToConnReq(c)
|
||||
if err != nil {
|
||||
select {
|
||||
case cm.requests <- handleFailed{c, err}:
|
||||
case <-cm.quit:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
cm.connect(c)
|
||||
}
|
||||
|
||||
func (cm *ConnManager) associateAddressToConnReq(c *ConnReq) error {
|
||||
cm.addressMtx.Lock()
|
||||
defer cm.addressMtx.Unlock()
|
||||
|
||||
addr, err := cm.getNewAddress()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm.markAddressAsUsed(addr)
|
||||
c.Addr = addr
|
||||
return nil
|
||||
}
|
||||
|
||||
// Connect assigns an id and dials a connection to the address of the
|
||||
// connection request.
|
||||
func (cm *ConnManager) Connect(c *ConnReq) error {
|
||||
err := func() error {
|
||||
cm.addressMtx.Lock()
|
||||
defer cm.addressMtx.Unlock()
|
||||
|
||||
if cm.isAddressUsed(c.Addr) {
|
||||
return fmt.Errorf("address %s is already in use", c.Addr)
|
||||
}
|
||||
cm.markAddressAsUsed(c.Addr)
|
||||
return nil
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cm.connect(c)
|
||||
return nil
|
||||
}
|
||||
|
||||
// connect assigns an id and dials a connection to the address of the
|
||||
// connection request. This function assumes that the connection address
|
||||
// has checked and already marked as used.
|
||||
func (cm *ConnManager) connect(c *ConnReq) {
|
||||
if atomic.LoadInt32(&cm.stop) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
if atomic.LoadUint64(&c.id) == 0 {
|
||||
atomic.StoreUint64(&c.id, atomic.AddUint64(&cm.connReqCount, 1))
|
||||
|
||||
// Submit a request of a pending connection attempt to the
|
||||
// connection manager. By registering the id before the
|
||||
// connection is even established, we'll be able to later
|
||||
// cancel the connection via the Remove method.
|
||||
done := make(chan struct{})
|
||||
select {
|
||||
case cm.requests <- registerPending{c, done}:
|
||||
case <-cm.quit:
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for the registration to successfully add the pending
|
||||
// conn req to the conn manager's internal state.
|
||||
select {
|
||||
case <-done:
|
||||
case <-cm.quit:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Attempting to connect to %s", c)
|
||||
|
||||
conn, err := cm.cfg.Dial(c.Addr)
|
||||
if err != nil {
|
||||
select {
|
||||
case cm.requests <- handleFailed{c, err}:
|
||||
case <-cm.quit:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case cm.requests <- handleConnected{c, conn}:
|
||||
case <-cm.quit:
|
||||
}
|
||||
}
|
||||
|
||||
// Disconnect disconnects the connection corresponding to the given connection
|
||||
// id. If permanent, the connection will be retried with an increasing backoff
|
||||
// duration.
|
||||
func (cm *ConnManager) Disconnect(id uint64) {
|
||||
if atomic.LoadInt32(&cm.stop) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case cm.requests <- handleDisconnected{id, true}:
|
||||
case <-cm.quit:
|
||||
}
|
||||
}
|
||||
|
||||
// Remove removes the connection corresponding to the given connection id from
|
||||
// known connections.
|
||||
//
|
||||
// NOTE: This method can also be used to cancel a lingering connection attempt
|
||||
// that hasn't yet succeeded.
|
||||
func (cm *ConnManager) Remove(id uint64) {
|
||||
if atomic.LoadInt32(&cm.stop) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
select {
|
||||
case cm.requests <- handleDisconnected{id, false}:
|
||||
case <-cm.quit:
|
||||
}
|
||||
}
|
||||
|
||||
// listenHandler accepts incoming connections on a given listener. It must be
|
||||
// run as a goroutine.
|
||||
func (cm *ConnManager) listenHandler(listener net.Listener) {
|
||||
log.Infof("Server listening on %s", listener.Addr())
|
||||
for atomic.LoadInt32(&cm.stop) == 0 {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
// Only log the error if not forcibly shutting down.
|
||||
if atomic.LoadInt32(&cm.stop) == 0 {
|
||||
log.Errorf("Can't accept connection: %s", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
spawn(func() {
|
||||
cm.cfg.OnAccept(conn)
|
||||
})
|
||||
}
|
||||
|
||||
cm.wg.Done()
|
||||
log.Tracef("Listener handler done for %s", listener.Addr())
|
||||
}
|
||||
|
||||
// Start launches the connection manager and begins connecting to the network.
|
||||
func (cm *ConnManager) Start() {
|
||||
// Already started?
|
||||
if atomic.AddInt32(&cm.start, 1) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Connection manager started")
|
||||
cm.wg.Add(1)
|
||||
spawn(cm.connHandler)
|
||||
|
||||
// Start all the listeners so long as the caller requested them and
|
||||
// provided a callback to be invoked when connections are accepted.
|
||||
if cm.cfg.OnAccept != nil {
|
||||
for _, listener := range cm.cfg.Listeners {
|
||||
// Declaring this variable is necessary as it needs be declared in the same
|
||||
// scope of the anonymous function below it.
|
||||
listenerCopy := listener
|
||||
cm.wg.Add(1)
|
||||
spawn(func() {
|
||||
cm.listenHandler(listenerCopy)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for i := atomic.LoadUint64(&cm.connReqCount); i < uint64(cm.cfg.TargetOutbound); i++ {
|
||||
spawn(cm.NewConnReq)
|
||||
}
|
||||
}
|
||||
|
||||
// Wait blocks until the connection manager halts gracefully.
|
||||
func (cm *ConnManager) Wait() {
|
||||
cm.wg.Wait()
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down the connection manager.
|
||||
func (cm *ConnManager) Stop() {
|
||||
if atomic.AddInt32(&cm.stop, 1) != 1 {
|
||||
log.Warnf("Connection manager already stopped")
|
||||
return
|
||||
}
|
||||
|
||||
// Stop all the listeners. There will not be any listeners if
|
||||
// listening is disabled.
|
||||
for _, listener := range cm.cfg.Listeners {
|
||||
// Ignore the error since this is shutdown and there is no way
|
||||
// to recover anyways.
|
||||
_ = listener.Close()
|
||||
}
|
||||
|
||||
close(cm.quit)
|
||||
log.Trace("Connection manager stopped")
|
||||
}
|
||||
|
||||
func (cm *ConnManager) getNewAddress() (*net.TCPAddr, error) {
|
||||
for tries := 0; tries < 100; tries++ {
|
||||
addr := cm.cfg.AddrManager.GetAddress()
|
||||
if addr == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Check if there's already a connection to the same address.
|
||||
netAddr := addr.NetAddress().TCPAddress()
|
||||
if cm.isAddressUsed(netAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// Address will not be invalid, local or unroutable
|
||||
// because addrmanager rejects those on addition.
|
||||
// Just check that we don't already have an address
|
||||
// in the same group so that we are not connecting
|
||||
// to the same network segment at the expense of
|
||||
// others.
|
||||
//
|
||||
// Networks that accept unroutable connections are exempt
|
||||
// from this rule, since they're meant to run within a
|
||||
// private subnet, like 10.0.0.0/16.
|
||||
if !config.ActiveConfig().NetParams().AcceptUnroutable && cm.isOutboundGroupUsed(netAddr) {
|
||||
continue
|
||||
}
|
||||
|
||||
// only allow recent nodes (10mins) after we failed 30
|
||||
// times
|
||||
if tries < 30 && time.Since(addr.LastAttempt()) < 10*time.Minute {
|
||||
continue
|
||||
}
|
||||
|
||||
// allow nondefault ports after 50 failed tries.
|
||||
if tries < 50 && fmt.Sprintf("%d", netAddr.Port) !=
|
||||
config.ActiveConfig().NetParams().DefaultPort {
|
||||
continue
|
||||
}
|
||||
|
||||
return netAddr, nil
|
||||
}
|
||||
return nil, ErrNoAddress
|
||||
}
|
||||
|
||||
// New returns a new connection manager.
|
||||
// Use Start to start connecting to the network.
|
||||
func New(cfg *Config) (*ConnManager, error) {
|
||||
if cfg.Dial == nil {
|
||||
return nil, errors.WithStack(ErrDialNil)
|
||||
}
|
||||
if cfg.AddrManager == nil {
|
||||
return nil, errors.WithStack(ErrAddressManagerNil)
|
||||
}
|
||||
// Default to sane values
|
||||
if cfg.RetryDuration <= 0 {
|
||||
cfg.RetryDuration = defaultRetryDuration
|
||||
}
|
||||
cm := ConnManager{
|
||||
cfg: *cfg, // Copy so caller can't mutate
|
||||
requests: make(chan interface{}),
|
||||
quit: make(chan struct{}),
|
||||
usedAddresses: make(map[string]struct{}),
|
||||
usedOutboundGroups: make(map[string]int64),
|
||||
}
|
||||
return &cm, nil
|
||||
}
|
||||
@@ -1,972 +0,0 @@
|
||||
// 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 connmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/addrmgr"
|
||||
"github.com/kaspanet/kaspad/config"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
// Override the max retry duration when running tests.
|
||||
maxRetryDuration = 2 * time.Millisecond
|
||||
}
|
||||
|
||||
// mockAddr mocks a network address
|
||||
type mockAddr struct {
|
||||
net, address string
|
||||
}
|
||||
|
||||
func (m mockAddr) Network() string { return m.net }
|
||||
func (m mockAddr) String() string { return m.address }
|
||||
|
||||
// mockConn mocks a network connection by implementing the net.Conn interface.
|
||||
type mockConn struct {
|
||||
io.Reader
|
||||
io.Writer
|
||||
io.Closer
|
||||
|
||||
// local network, address for the connection.
|
||||
lnet, laddr string
|
||||
|
||||
// remote network, address for the connection.
|
||||
rAddr net.Addr
|
||||
}
|
||||
|
||||
// LocalAddr returns the local address for the connection.
|
||||
func (c mockConn) LocalAddr() net.Addr {
|
||||
return &mockAddr{c.lnet, c.laddr}
|
||||
}
|
||||
|
||||
// RemoteAddr returns the remote address for the connection.
|
||||
func (c mockConn) RemoteAddr() net.Addr {
|
||||
return &mockAddr{c.rAddr.Network(), c.rAddr.String()}
|
||||
}
|
||||
|
||||
// Close handles closing the connection.
|
||||
func (c mockConn) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c mockConn) SetDeadline(t time.Time) error { return nil }
|
||||
func (c mockConn) SetReadDeadline(t time.Time) error { return nil }
|
||||
func (c mockConn) SetWriteDeadline(t time.Time) error { return nil }
|
||||
|
||||
// mockDialer mocks the net.Dial interface by returning a mock connection to
|
||||
// the given address.
|
||||
func mockDialer(addr net.Addr) (net.Conn, error) {
|
||||
r, w := io.Pipe()
|
||||
c := &mockConn{rAddr: addr}
|
||||
c.Reader = r
|
||||
c.Writer = w
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// TestNewConfig tests that new ConnManager config is validated as expected.
|
||||
func TestNewConfig(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
_, err := New(&Config{})
|
||||
if !errors.Is(err, ErrDialNil) {
|
||||
t.Fatalf("New expected error: %s, got %s", ErrDialNil, err)
|
||||
}
|
||||
|
||||
_, err = New(&Config{
|
||||
Dial: mockDialer,
|
||||
})
|
||||
if !errors.Is(err, ErrAddressManagerNil) {
|
||||
t.Fatalf("New expected error: %s, got %s", ErrAddressManagerNil, err)
|
||||
}
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestNewConfig", 10)
|
||||
defer teardown()
|
||||
|
||||
_, err = New(&Config{
|
||||
Dial: mockDialer,
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("New unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStartStop tests that the connection manager starts and stops as
|
||||
// expected.
|
||||
func TestStartStop(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
connected := make(chan *ConnReq)
|
||||
disconnected := make(chan *ConnReq)
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestStartStop", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
TargetOutbound: 1,
|
||||
AddrManager: amgr,
|
||||
Dial: mockDialer,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- c
|
||||
},
|
||||
OnDisconnection: func(c *ConnReq) {
|
||||
disconnected <- c
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
gotConnReq := <-connected
|
||||
cmgr.Stop()
|
||||
// already stopped
|
||||
cmgr.Stop()
|
||||
// ignored
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
err = cmgr.Connect(cr)
|
||||
if err != nil {
|
||||
t.Fatalf("Connect error: %s", err)
|
||||
}
|
||||
if cr.ID() != 0 {
|
||||
t.Fatalf("start/stop: got id: %v, want: 0", cr.ID())
|
||||
}
|
||||
cmgr.Disconnect(gotConnReq.ID())
|
||||
cmgr.Remove(gotConnReq.ID())
|
||||
select {
|
||||
case <-disconnected:
|
||||
t.Fatalf("start/stop: unexpected disconnection")
|
||||
case <-time.Tick(10 * time.Millisecond):
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func overrideActiveConfig() func() {
|
||||
originalActiveCfg := config.ActiveConfig()
|
||||
config.SetActiveConfig(&config.Config{
|
||||
Flags: &config.Flags{
|
||||
NetworkFlags: config.NetworkFlags{
|
||||
ActiveNetParams: &dagconfig.SimnetParams},
|
||||
},
|
||||
})
|
||||
return func() {
|
||||
// Give some extra time to all open NewConnReq goroutines
|
||||
// to finish before restoring the active config to prevent
|
||||
// potential panics.
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
config.SetActiveConfig(originalActiveCfg)
|
||||
}
|
||||
}
|
||||
|
||||
func addressManagerForTest(t *testing.T, testName string, numAddresses uint8) (*addrmgr.AddrManager, func()) {
|
||||
amgr, teardown := createEmptyAddressManagerForTest(t, testName)
|
||||
|
||||
for i := uint8(0); i < numAddresses; i++ {
|
||||
ip := fmt.Sprintf("173.%d.115.66:16511", i)
|
||||
err := amgr.AddAddressByIP(ip, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddAddressByIP unexpectedly failed to add IP %s: %s", ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
return amgr, teardown
|
||||
}
|
||||
|
||||
func createEmptyAddressManagerForTest(t *testing.T, testName string) (*addrmgr.AddrManager, func()) {
|
||||
path, err := ioutil.TempDir("", fmt.Sprintf("%s-addressmanager", testName))
|
||||
if err != nil {
|
||||
t.Fatalf("createEmptyAddressManagerForTest: TempDir unexpectedly "+
|
||||
"failed: %s", err)
|
||||
}
|
||||
|
||||
return addrmgr.New(path, nil, nil), func() {
|
||||
// Wait for the connection manager to finish
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
err := os.RemoveAll(path)
|
||||
if err != nil {
|
||||
t.Fatalf("couldn't remove path %s", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestConnectMode tests that the connection manager works in the connect mode.
|
||||
//
|
||||
// In connect mode, automatic connections are disabled, so we test that
|
||||
// requests using Connect are handled and that no other connections are made.
|
||||
func TestConnectMode(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
connected := make(chan *ConnReq)
|
||||
amgr, teardown := addressManagerForTest(t, "TestConnectMode", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
TargetOutbound: 0,
|
||||
Dial: mockDialer,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- c
|
||||
},
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
cmgr.Start()
|
||||
cmgr.Connect(cr)
|
||||
gotConnReq := <-connected
|
||||
wantID := cr.ID()
|
||||
gotID := gotConnReq.ID()
|
||||
if gotID != wantID {
|
||||
t.Fatalf("connect mode: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID)
|
||||
}
|
||||
gotState := cr.State()
|
||||
wantState := ConnEstablished
|
||||
if gotState != wantState {
|
||||
t.Fatalf("connect mode: %v - want state %v, got state %v", cr.Addr, wantState, gotState)
|
||||
}
|
||||
select {
|
||||
case c := <-connected:
|
||||
t.Fatalf("connect mode: got unexpected connection - %v", c.Addr)
|
||||
case <-time.After(time.Millisecond):
|
||||
break
|
||||
}
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestTargetOutbound tests the target number of outbound connections.
|
||||
//
|
||||
// We wait until all connections are established, then test they there are the
|
||||
// only connections made.
|
||||
func TestTargetOutbound(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
const numAddressesInAddressManager = 10
|
||||
targetOutbound := uint32(numAddressesInAddressManager - 2)
|
||||
connected := make(chan *ConnReq)
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestTargetOutbound", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
TargetOutbound: targetOutbound,
|
||||
Dial: mockDialer,
|
||||
AddrManager: amgr,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- c
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
for i := uint32(0); i < targetOutbound; i++ {
|
||||
<-connected
|
||||
}
|
||||
|
||||
select {
|
||||
case c := <-connected:
|
||||
t.Fatalf("target outbound: got unexpected connection - %v", c.Addr)
|
||||
case <-time.After(time.Millisecond):
|
||||
break
|
||||
}
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestDuplicateOutboundConnections tests that connection requests cannot use an already used address.
|
||||
// It checks it by creating one connection request for each address in the address manager, so that
|
||||
// the next connection request will have to fail because no unused address will be available.
|
||||
func TestDuplicateOutboundConnections(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
const numAddressesInAddressManager = 10
|
||||
targetOutbound := uint32(numAddressesInAddressManager - 1)
|
||||
connected := make(chan struct{})
|
||||
failedConnections := make(chan struct{})
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestDuplicateOutboundConnections", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
TargetOutbound: targetOutbound,
|
||||
Dial: mockDialer,
|
||||
AddrManager: amgr,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- struct{}{}
|
||||
},
|
||||
OnConnectionFailed: func(_ *ConnReq) {
|
||||
failedConnections <- struct{}{}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
for i := uint32(0); i < targetOutbound; i++ {
|
||||
<-connected
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond)
|
||||
|
||||
// Here we check that making a manual connection request beyond the target outbound connection
|
||||
// doesn't fail, so we can know that the reason such connection request will fail is an address
|
||||
// related issue.
|
||||
cmgr.NewConnReq()
|
||||
select {
|
||||
case <-connected:
|
||||
break
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Fatalf("connection request unexpectedly didn't connect")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-failedConnections:
|
||||
t.Fatalf("a connection request unexpectedly failed")
|
||||
case <-time.After(time.Millisecond):
|
||||
break
|
||||
}
|
||||
|
||||
// After we created numAddressesInAddressManager connection requests, this request should fail
|
||||
// because there aren't any more available addresses.
|
||||
cmgr.NewConnReq()
|
||||
select {
|
||||
case <-connected:
|
||||
t.Fatalf("connection request unexpectedly succeeded")
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Fatalf("connection request didn't fail as expected")
|
||||
case <-failedConnections:
|
||||
break
|
||||
}
|
||||
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestSameOutboundGroupConnections tests that connection requests cannot use an address with an already used
|
||||
// address CIDR group.
|
||||
// It checks it by creating an address manager with only two addresses, that both belong to the same CIDR group
|
||||
// and checks that the second connection request fails.
|
||||
func TestSameOutboundGroupConnections(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
amgr, teardown := createEmptyAddressManagerForTest(t, "TestSameOutboundGroupConnections")
|
||||
defer teardown()
|
||||
|
||||
err := amgr.AddAddressByIP("173.190.115.66:16511", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddAddressByIP unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
err = amgr.AddAddressByIP("173.190.115.67:16511", nil)
|
||||
if err != nil {
|
||||
t.Fatalf("AddAddressByIP unexpectedly failed: %s", err)
|
||||
}
|
||||
|
||||
connected := make(chan struct{})
|
||||
failedConnections := make(chan struct{})
|
||||
cmgr, err := New(&Config{
|
||||
TargetOutbound: 0,
|
||||
Dial: mockDialer,
|
||||
AddrManager: amgr,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- struct{}{}
|
||||
},
|
||||
OnConnectionFailed: func(_ *ConnReq) {
|
||||
failedConnections <- struct{}{}
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
|
||||
cmgr.Start()
|
||||
|
||||
cmgr.NewConnReq()
|
||||
select {
|
||||
case <-connected:
|
||||
break
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Fatalf("connection request unexpectedly didn't connect")
|
||||
}
|
||||
|
||||
select {
|
||||
case <-failedConnections:
|
||||
t.Fatalf("a connection request unexpectedly failed")
|
||||
case <-time.After(time.Millisecond):
|
||||
break
|
||||
}
|
||||
|
||||
cmgr.NewConnReq()
|
||||
select {
|
||||
case <-connected:
|
||||
t.Fatalf("connection request unexpectedly succeeded")
|
||||
case <-time.After(time.Millisecond):
|
||||
t.Fatalf("connection request didn't fail as expected")
|
||||
case <-failedConnections:
|
||||
break
|
||||
}
|
||||
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestRetryPermanent tests that permanent connection requests are retried.
|
||||
//
|
||||
// We make a permanent connection request using Connect, disconnect it using
|
||||
// Disconnect and we wait for it to be connected back.
|
||||
func TestRetryPermanent(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
connected := make(chan *ConnReq)
|
||||
disconnected := make(chan *ConnReq)
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestRetryPermanent", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
RetryDuration: time.Millisecond,
|
||||
TargetOutbound: 0,
|
||||
Dial: mockDialer,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- c
|
||||
},
|
||||
OnDisconnection: func(c *ConnReq) {
|
||||
disconnected <- c
|
||||
},
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
go cmgr.Connect(cr)
|
||||
cmgr.Start()
|
||||
gotConnReq := <-connected
|
||||
wantID := cr.ID()
|
||||
gotID := gotConnReq.ID()
|
||||
if gotID != wantID {
|
||||
t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID)
|
||||
}
|
||||
gotState := cr.State()
|
||||
wantState := ConnEstablished
|
||||
if gotState != wantState {
|
||||
t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState)
|
||||
}
|
||||
|
||||
cmgr.Disconnect(cr.ID())
|
||||
gotConnReq = <-disconnected
|
||||
wantID = cr.ID()
|
||||
gotID = gotConnReq.ID()
|
||||
if gotID != wantID {
|
||||
t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID)
|
||||
}
|
||||
gotState = cr.State()
|
||||
wantState = ConnPending
|
||||
if gotState != wantState {
|
||||
// There is a small chance that connection has already been established,
|
||||
// so check for that as well
|
||||
if gotState != ConnEstablished {
|
||||
t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState)
|
||||
}
|
||||
}
|
||||
|
||||
gotConnReq = <-connected
|
||||
wantID = cr.ID()
|
||||
gotID = gotConnReq.ID()
|
||||
if gotID != wantID {
|
||||
t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID)
|
||||
}
|
||||
gotState = cr.State()
|
||||
wantState = ConnEstablished
|
||||
if gotState != wantState {
|
||||
t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState)
|
||||
}
|
||||
|
||||
cmgr.Remove(cr.ID())
|
||||
gotConnReq = <-disconnected
|
||||
|
||||
// Wait for status to be updated
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
wantID = cr.ID()
|
||||
gotID = gotConnReq.ID()
|
||||
if gotID != wantID {
|
||||
t.Fatalf("retry: %v - want ID %v, got ID %v", cr.Addr, wantID, gotID)
|
||||
}
|
||||
gotState = cr.State()
|
||||
wantState = ConnDisconnected
|
||||
if gotState != wantState {
|
||||
t.Fatalf("retry: %v - want state %v, got state %v", cr.Addr, wantState, gotState)
|
||||
}
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestMaxRetryDuration tests the maximum retry duration.
|
||||
//
|
||||
// We have a timed dialer which initially returns err but after RetryDuration
|
||||
// hits maxRetryDuration returns a mock conn.
|
||||
func TestMaxRetryDuration(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
networkUp := make(chan struct{})
|
||||
time.AfterFunc(5*time.Millisecond, func() {
|
||||
close(networkUp)
|
||||
})
|
||||
timedDialer := func(addr net.Addr) (net.Conn, error) {
|
||||
select {
|
||||
case <-networkUp:
|
||||
return mockDialer(addr)
|
||||
default:
|
||||
return nil, errors.New("network down")
|
||||
}
|
||||
}
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestMaxRetryDuration", 10)
|
||||
defer teardown()
|
||||
|
||||
connected := make(chan *ConnReq)
|
||||
cmgr, err := New(&Config{
|
||||
RetryDuration: time.Millisecond,
|
||||
TargetOutbound: 1,
|
||||
Dial: timedDialer,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- c
|
||||
},
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
go cmgr.Connect(cr)
|
||||
cmgr.Start()
|
||||
// retry in 1ms
|
||||
// retry in 2ms - max retry duration reached
|
||||
// retry in 2ms - timedDialer returns mockDial
|
||||
select {
|
||||
case <-connected:
|
||||
case <-time.Tick(100 * time.Millisecond):
|
||||
t.Fatalf("max retry duration: connection timeout")
|
||||
}
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestNetworkFailure tests that the connection manager handles a network
|
||||
// failure gracefully.
|
||||
func TestNetworkFailure(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
var dials uint32
|
||||
errDialer := func(net net.Addr) (net.Conn, error) {
|
||||
atomic.AddUint32(&dials, 1)
|
||||
return nil, errors.New("network down")
|
||||
}
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestNetworkFailure", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
TargetOutbound: 5,
|
||||
RetryDuration: 5 * time.Millisecond,
|
||||
Dial: errDialer,
|
||||
AddrManager: amgr,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
t.Fatalf("network failure: got unexpected connection - %v", c.Addr)
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
wantMaxDials := uint32(75)
|
||||
if atomic.LoadUint32(&dials) > wantMaxDials {
|
||||
t.Fatalf("network failure: unexpected number of dials - got %v, want < %v",
|
||||
atomic.LoadUint32(&dials), wantMaxDials)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStopFailed tests that failed connections are ignored after connmgr is
|
||||
// stopped.
|
||||
//
|
||||
// We have a dailer which sets the stop flag on the conn manager and returns an
|
||||
// err so that the handler assumes that the conn manager is stopped and ignores
|
||||
// the failure.
|
||||
func TestStopFailed(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
done := make(chan struct{}, 1)
|
||||
waitDialer := func(addr net.Addr) (net.Conn, error) {
|
||||
done <- struct{}{}
|
||||
time.Sleep(time.Millisecond)
|
||||
return nil, errors.New("network down")
|
||||
}
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestStopFailed", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
Dial: waitDialer,
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
go func() {
|
||||
<-done
|
||||
atomic.StoreInt32(&cmgr.stop, 1)
|
||||
time.Sleep(2 * time.Millisecond)
|
||||
atomic.StoreInt32(&cmgr.stop, 0)
|
||||
cmgr.Stop()
|
||||
}()
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
go cmgr.Connect(cr)
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestRemovePendingConnection tests that it's possible to cancel a pending
|
||||
// connection, removing its internal state from the ConnMgr.
|
||||
func TestRemovePendingConnection(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
// Create a ConnMgr instance with an instance of a dialer that'll never
|
||||
// succeed.
|
||||
wait := make(chan struct{})
|
||||
indefiniteDialer := func(addr net.Addr) (net.Conn, error) {
|
||||
<-wait
|
||||
return nil, errors.Errorf("error")
|
||||
}
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestRemovePendingConnection", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
Dial: indefiniteDialer,
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
|
||||
// Establish a connection request to a random IP we've chosen.
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
go cmgr.Connect(cr)
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
if cr.State() != ConnPending {
|
||||
t.Fatalf("pending request hasn't been registered, status: %v",
|
||||
cr.State())
|
||||
}
|
||||
|
||||
// The request launched above will actually never be able to establish
|
||||
// a connection. So we'll cancel it _before_ it's able to be completed.
|
||||
cmgr.Remove(cr.ID())
|
||||
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
|
||||
// Now examine the status of the connection request, it should read a
|
||||
// status of failed.
|
||||
if cr.State() != ConnCanceled {
|
||||
t.Fatalf("request wasn't canceled, status is: %v", cr.State())
|
||||
}
|
||||
|
||||
close(wait)
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestCancelIgnoreDelayedConnection tests that a canceled connection request will
|
||||
// not execute the on connection callback, even if an outstanding retry
|
||||
// succeeds.
|
||||
func TestCancelIgnoreDelayedConnection(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
retryTimeout := 10 * time.Millisecond
|
||||
|
||||
// Setup a dialer that will continue to return an error until the
|
||||
// connect chan is signaled, the dial attempt immediately after will
|
||||
// succeed in returning a connection.
|
||||
connect := make(chan struct{})
|
||||
failingDialer := func(addr net.Addr) (net.Conn, error) {
|
||||
select {
|
||||
case <-connect:
|
||||
return mockDialer(addr)
|
||||
default:
|
||||
}
|
||||
|
||||
return nil, errors.Errorf("error")
|
||||
}
|
||||
|
||||
connected := make(chan *ConnReq)
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestCancelIgnoreDelayedConnection", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
Dial: failingDialer,
|
||||
RetryDuration: retryTimeout,
|
||||
OnConnection: func(c *ConnReq, conn net.Conn) {
|
||||
connected <- c
|
||||
},
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
|
||||
// Establish a connection request to a random IP we've chosen.
|
||||
cr := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
}
|
||||
cmgr.Connect(cr)
|
||||
|
||||
// Allow for the first retry timeout to elapse.
|
||||
time.Sleep(2 * retryTimeout)
|
||||
|
||||
// Connection be marked as failed, even after reattempting to
|
||||
// connect.
|
||||
if cr.State() != ConnFailing {
|
||||
t.Fatalf("failing request should have status failed, status: %v",
|
||||
cr.State())
|
||||
}
|
||||
|
||||
// Remove the connection, and then immediately allow the next connection
|
||||
// to succeed.
|
||||
cmgr.Remove(cr.ID())
|
||||
close(connect)
|
||||
|
||||
// Allow the connection manager to process the removal.
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
|
||||
// Now examine the status of the connection request, it should read a
|
||||
// status of canceled.
|
||||
if cr.State() != ConnCanceled {
|
||||
t.Fatalf("request wasn't canceled, status is: %v", cr.State())
|
||||
}
|
||||
|
||||
// Finally, the connection manager should not signal the on-connection
|
||||
// callback, since we explicitly canceled this request. We give a
|
||||
// generous window to ensure the connection manager's lienar backoff is
|
||||
// allowed to properly elapse.
|
||||
select {
|
||||
case <-connected:
|
||||
t.Fatalf("on-connect should not be called for canceled req")
|
||||
case <-time.After(5 * retryTimeout):
|
||||
}
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// mockListener implements the net.Listener interface and is used to test
|
||||
// code that deals with net.Listeners without having to actually make any real
|
||||
// connections.
|
||||
type mockListener struct {
|
||||
localAddr string
|
||||
provideConn chan net.Conn
|
||||
}
|
||||
|
||||
// Accept returns a mock connection when it receives a signal via the Connect
|
||||
// function.
|
||||
//
|
||||
// This is part of the net.Listener interface.
|
||||
func (m *mockListener) Accept() (net.Conn, error) {
|
||||
for conn := range m.provideConn {
|
||||
return conn, nil
|
||||
}
|
||||
return nil, errors.New("network connection closed")
|
||||
}
|
||||
|
||||
// Close closes the mock listener which will cause any blocked Accept
|
||||
// operations to be unblocked and return errors.
|
||||
//
|
||||
// This is part of the net.Listener interface.
|
||||
func (m *mockListener) Close() error {
|
||||
close(m.provideConn)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Addr returns the address the mock listener was configured with.
|
||||
//
|
||||
// This is part of the net.Listener interface.
|
||||
func (m *mockListener) Addr() net.Addr {
|
||||
return &mockAddr{"tcp", m.localAddr}
|
||||
}
|
||||
|
||||
// Connect fakes a connection to the mock listener from the provided remote
|
||||
// address. It will cause the Accept function to return a mock connection
|
||||
// configured with the provided remote address and the local address for the
|
||||
// mock listener.
|
||||
func (m *mockListener) Connect(ip string, port int) {
|
||||
m.provideConn <- &mockConn{
|
||||
laddr: m.localAddr,
|
||||
lnet: "tcp",
|
||||
rAddr: &net.TCPAddr{
|
||||
IP: net.ParseIP(ip),
|
||||
Port: port,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newMockListener returns a new mock listener for the provided local address
|
||||
// and port. No ports are actually opened.
|
||||
func newMockListener(localAddr string) *mockListener {
|
||||
return &mockListener{
|
||||
localAddr: localAddr,
|
||||
provideConn: make(chan net.Conn),
|
||||
}
|
||||
}
|
||||
|
||||
// TestListeners ensures providing listeners to the connection manager along
|
||||
// with an accept callback works properly.
|
||||
func TestListeners(t *testing.T) {
|
||||
restoreConfig := overrideActiveConfig()
|
||||
defer restoreConfig()
|
||||
|
||||
// Setup a connection manager with a couple of mock listeners that
|
||||
// notify a channel when they receive mock connections.
|
||||
receivedConns := make(chan net.Conn)
|
||||
listener1 := newMockListener("127.0.0.1:16111")
|
||||
listener2 := newMockListener("127.0.0.1:9333")
|
||||
listeners := []net.Listener{listener1, listener2}
|
||||
|
||||
amgr, teardown := addressManagerForTest(t, "TestListeners", 10)
|
||||
defer teardown()
|
||||
|
||||
cmgr, err := New(&Config{
|
||||
Listeners: listeners,
|
||||
OnAccept: func(conn net.Conn) {
|
||||
receivedConns <- conn
|
||||
},
|
||||
Dial: mockDialer,
|
||||
AddrManager: amgr,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error from New: %s", err)
|
||||
}
|
||||
cmgr.Start()
|
||||
|
||||
// Fake a couple of mock connections to each of the listeners.
|
||||
go func() {
|
||||
for i, listener := range listeners {
|
||||
l := listener.(*mockListener)
|
||||
l.Connect("127.0.0.1", 10000+i*2)
|
||||
l.Connect("127.0.0.1", 10000+i*2+1)
|
||||
}
|
||||
}()
|
||||
|
||||
// Tally the receive connections to ensure the expected number are
|
||||
// received. Also, fail the test after a timeout so it will not hang
|
||||
// forever should the test not work.
|
||||
expectedNumConns := len(listeners) * 2
|
||||
var numConns int
|
||||
out:
|
||||
for {
|
||||
select {
|
||||
case <-receivedConns:
|
||||
numConns++
|
||||
if numConns == expectedNumConns {
|
||||
break out
|
||||
}
|
||||
|
||||
case <-time.After(time.Millisecond * 50):
|
||||
t.Fatalf("Timeout waiting for %d expected connections",
|
||||
expectedNumConns)
|
||||
}
|
||||
}
|
||||
|
||||
cmgr.Stop()
|
||||
cmgr.Wait()
|
||||
}
|
||||
|
||||
// TestConnReqString ensures that ConnReq.String() does not crash
|
||||
func TestConnReqString(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
t.Fatalf("ConnReq.String crashed %v", r)
|
||||
}
|
||||
}()
|
||||
cr1 := &ConnReq{
|
||||
Addr: &net.TCPAddr{
|
||||
IP: net.ParseIP("127.0.0.1"),
|
||||
Port: 18555,
|
||||
},
|
||||
Permanent: true,
|
||||
}
|
||||
_ = cr1.String()
|
||||
cr2 := &ConnReq{}
|
||||
_ = cr2.String()
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
/*
|
||||
Package connmgr implements a generic Kaspa network connection manager.
|
||||
|
||||
Connection Manager Overview
|
||||
|
||||
Connection Manager handles all the general connection concerns such as
|
||||
maintaining a set number of outbound connections, sourcing peers, banning,
|
||||
limiting max connections, etc.
|
||||
|
||||
The package provides a generic connection manager which is able to accept
|
||||
connection requests from a source or a set of given addresses, dial them and
|
||||
notify the caller on connections. The main intended use is to initialize a pool
|
||||
of active connections and maintain them to remain connected to the P2P network.
|
||||
|
||||
In addition the connection manager provides the following utilities:
|
||||
|
||||
- Notifications on connections or disconnections
|
||||
- Handle failures and retry new addresses from the source
|
||||
- Connect only to specified addresses
|
||||
- Permanent connections with increasing backoff retry timers
|
||||
- Disconnect or Remove an established connection
|
||||
*/
|
||||
package connmgr
|
||||
@@ -1,144 +0,0 @@
|
||||
// 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 connmgr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// Halflife defines the time (in seconds) by which the transient part
|
||||
// of the ban score decays to one half of it's original value.
|
||||
Halflife = 60
|
||||
|
||||
// lambda is the decaying constant.
|
||||
lambda = math.Ln2 / Halflife
|
||||
|
||||
// Lifetime defines the maximum age of the transient part of the ban
|
||||
// score to be considered a non-zero score (in seconds).
|
||||
Lifetime = 1800
|
||||
|
||||
// precomputedLen defines the amount of decay factors (one per second) that
|
||||
// should be precomputed at initialization.
|
||||
precomputedLen = 64
|
||||
)
|
||||
|
||||
// precomputedFactor stores precomputed exponential decay factors for the first
|
||||
// 'precomputedLen' seconds starting from t == 0.
|
||||
var precomputedFactor [precomputedLen]float64
|
||||
|
||||
// init precomputes decay factors.
|
||||
func init() {
|
||||
for i := range precomputedFactor {
|
||||
precomputedFactor[i] = math.Exp(-1.0 * float64(i) * lambda)
|
||||
}
|
||||
}
|
||||
|
||||
// decayFactor returns the decay factor at t seconds, using precalculated values
|
||||
// if available, or calculating the factor if needed.
|
||||
func decayFactor(t int64) float64 {
|
||||
if t < precomputedLen {
|
||||
return precomputedFactor[t]
|
||||
}
|
||||
return math.Exp(-1.0 * float64(t) * lambda)
|
||||
}
|
||||
|
||||
// DynamicBanScore provides dynamic ban scores consisting of a persistent and a
|
||||
// decaying component.
|
||||
//
|
||||
// The decaying score enables the creation of evasive logic which handles
|
||||
// misbehaving peers (especially application layer DoS attacks) gracefully
|
||||
// by disconnecting and banning peers attempting various kinds of flooding.
|
||||
// DynamicBanScore allows these two approaches to be used in tandem.
|
||||
//
|
||||
// Zero value: Values of type DynamicBanScore are immediately ready for use upon
|
||||
// declaration.
|
||||
type DynamicBanScore struct {
|
||||
lastUnix int64
|
||||
transient float64
|
||||
persistent uint32
|
||||
mtx sync.Mutex
|
||||
}
|
||||
|
||||
// String returns the ban score as a human-readable string.
|
||||
func (s *DynamicBanScore) String() string {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
r := fmt.Sprintf("persistent %d + transient %f at %d = %d as of now",
|
||||
s.persistent, s.transient, s.lastUnix, s.Int())
|
||||
return r
|
||||
}
|
||||
|
||||
// Int returns the current ban score, the sum of the persistent and decaying
|
||||
// scores.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (s *DynamicBanScore) Int() uint32 {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
r := s.int(time.Now())
|
||||
return r
|
||||
}
|
||||
|
||||
// Increase increases both the persistent and decaying scores by the values
|
||||
// passed as parameters. The resulting score is returned.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (s *DynamicBanScore) Increase(persistent, transient uint32) uint32 {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
r := s.increase(persistent, transient, time.Now())
|
||||
return r
|
||||
}
|
||||
|
||||
// Reset set both persistent and decaying scores to zero.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (s *DynamicBanScore) Reset() {
|
||||
s.mtx.Lock()
|
||||
defer s.mtx.Unlock()
|
||||
s.persistent = 0
|
||||
s.transient = 0
|
||||
s.lastUnix = 0
|
||||
}
|
||||
|
||||
// int returns the ban score, the sum of the persistent and decaying scores at a
|
||||
// given point in time.
|
||||
//
|
||||
// This function is not safe for concurrent access. It is intended to be used
|
||||
// internally and during testing.
|
||||
func (s *DynamicBanScore) int(t time.Time) uint32 {
|
||||
dt := t.Unix() - s.lastUnix
|
||||
if s.transient < 1 || dt < 0 || Lifetime < dt {
|
||||
return s.persistent
|
||||
}
|
||||
return s.persistent + uint32(s.transient*decayFactor(dt))
|
||||
}
|
||||
|
||||
// increase increases the persistent, the decaying or both scores by the values
|
||||
// passed as parameters. The resulting score is calculated as if the action was
|
||||
// carried out at the point time represented by the third parameter. The
|
||||
// resulting score is returned.
|
||||
//
|
||||
// This function is not safe for concurrent access.
|
||||
func (s *DynamicBanScore) increase(persistent, transient uint32, t time.Time) uint32 {
|
||||
s.persistent += persistent
|
||||
tu := t.Unix()
|
||||
dt := tu - s.lastUnix
|
||||
|
||||
if transient > 0 {
|
||||
if Lifetime < dt {
|
||||
s.transient = 0
|
||||
} else if s.transient > 1 && dt > 0 {
|
||||
s.transient *= decayFactor(dt)
|
||||
}
|
||||
s.transient += float64(transient)
|
||||
s.lastUnix = tu
|
||||
}
|
||||
return s.persistent + uint32(s.transient)
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
// 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 connmgr
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TestDynamicBanScoreDecay tests the exponential decay implemented in
|
||||
// DynamicBanScore.
|
||||
func TestDynamicBanScoreDecay(t *testing.T) {
|
||||
var bs DynamicBanScore
|
||||
base := time.Now()
|
||||
|
||||
r := bs.increase(100, 50, base)
|
||||
if r != 150 {
|
||||
t.Errorf("Unexpected result %d after ban score increase.", r)
|
||||
}
|
||||
|
||||
r = bs.int(base.Add(time.Minute))
|
||||
if r != 125 {
|
||||
t.Errorf("Halflife check failed - %d instead of 125", r)
|
||||
}
|
||||
|
||||
r = bs.int(base.Add(7 * time.Minute))
|
||||
if r != 100 {
|
||||
t.Errorf("Decay after 7m - %d instead of 100", r)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDynamicBanScoreLifetime tests that DynamicBanScore properly yields zero
|
||||
// once the maximum age is reached.
|
||||
func TestDynamicBanScoreLifetime(t *testing.T) {
|
||||
var bs DynamicBanScore
|
||||
base := time.Now()
|
||||
|
||||
bs.increase(0, math.MaxUint32, base)
|
||||
r := bs.int(base.Add(Lifetime * time.Second))
|
||||
if r != 3 { // 3, not 4 due to precision loss and truncating 3.999...
|
||||
t.Errorf("Pre max age check with MaxUint32 failed - %d", r)
|
||||
}
|
||||
r = bs.int(base.Add((Lifetime + 1) * time.Second))
|
||||
if r != 0 {
|
||||
t.Errorf("Zero after max age check failed - %d instead of 0", r)
|
||||
}
|
||||
}
|
||||
|
||||
// TestDynamicBanScore tests exported functions of DynamicBanScore. Exponential
|
||||
// decay or other time based behavior is tested by other functions.
|
||||
func TestDynamicBanScoreReset(t *testing.T) {
|
||||
var bs DynamicBanScore
|
||||
if bs.Int() != 0 {
|
||||
t.Errorf("Initial state is not zero.")
|
||||
}
|
||||
bs.Increase(100, 0)
|
||||
r := bs.Int()
|
||||
if r != 100 {
|
||||
t.Errorf("Unexpected result %d after ban score increase.", r)
|
||||
}
|
||||
bs.Reset()
|
||||
if bs.Int() != 0 {
|
||||
t.Errorf("Failed to reset ban score.")
|
||||
}
|
||||
}
|
||||
@@ -1,334 +0,0 @@
|
||||
// Copyright (c) 2014-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package dagconfig
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestGenesisBlock tests the genesis block of the main network for validity by
|
||||
// checking the encoded bytes and hashes.
|
||||
func TestGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := MainnetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the encoded block matches the expected bytes.
|
||||
if !bytes.Equal(buf.Bytes(), genesisBlockBytes) {
|
||||
t.Fatalf("TestGenesisBlock: Genesis block does not appear valid - "+
|
||||
"got %v, want %v", spew.Sdump(buf.Bytes()),
|
||||
spew.Sdump(genesisBlockBytes))
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := MainnetParams.GenesisBlock.BlockHash()
|
||||
if !MainnetParams.GenesisHash.IsEqual(hash) {
|
||||
t.Fatalf("TestGenesisBlock: Genesis block hash does not "+
|
||||
"appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(MainnetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegtestGenesisBlock tests the genesis block of the regression test
|
||||
// network for validity by checking the encoded bytes and hashes.
|
||||
func TestRegtestGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := RegressionNetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestRegtestGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the encoded block matches the expected bytes.
|
||||
if !bytes.Equal(buf.Bytes(), regtestGenesisBlockBytes) {
|
||||
t.Fatalf("TestRegtestGenesisBlock: Genesis block does not "+
|
||||
"appear valid - got %v, want %v",
|
||||
spew.Sdump(buf.Bytes()),
|
||||
spew.Sdump(regtestGenesisBlockBytes))
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := RegressionNetParams.GenesisBlock.BlockHash()
|
||||
if !RegressionNetParams.GenesisHash.IsEqual(hash) {
|
||||
t.Fatalf("TestRegtestGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(RegressionNetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTestnetGenesisBlock tests the genesis block of the test network for
|
||||
// validity by checking the encoded bytes and hashes.
|
||||
func TestTestnetGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := TestnetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTestnetGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the encoded block matches the expected bytes.
|
||||
if !bytes.Equal(buf.Bytes(), testnetGenesisBlockBytes) {
|
||||
t.Fatalf("TestTestnetGenesisBlock: Genesis block does not "+
|
||||
"appear valid - got %v, want %v",
|
||||
spew.Sdump(buf.Bytes()),
|
||||
spew.Sdump(testnetGenesisBlockBytes))
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := TestnetParams.GenesisBlock.BlockHash()
|
||||
if !TestnetParams.GenesisHash.IsEqual(hash) {
|
||||
t.Fatalf("TestTestnetGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(TestnetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSimnetGenesisBlock tests the genesis block of the simulation test network
|
||||
// for validity by checking the encoded bytes and hashes.
|
||||
func TestSimnetGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := SimnetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestSimnetGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the encoded block matches the expected bytes.
|
||||
if !bytes.Equal(buf.Bytes(), simnetGenesisBlockBytes) {
|
||||
t.Fatalf("TestSimnetGenesisBlock: Genesis block does not "+
|
||||
"appear valid - got %v, want %v",
|
||||
spew.Sdump(buf.Bytes()),
|
||||
spew.Sdump(simnetGenesisBlockBytes))
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := SimnetParams.GenesisBlock.BlockHash()
|
||||
if !SimnetParams.GenesisHash.IsEqual(hash) {
|
||||
t.Fatalf("TestSimnetGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(SimnetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestDevnetGenesisBlock tests the genesis block of the development network
|
||||
// for validity by checking the encoded bytes and hashes.
|
||||
func TestDevnetGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := DevnetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestDevnetGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the encoded block matches the expected bytes.
|
||||
if !bytes.Equal(buf.Bytes(), devnetGenesisBlockBytes) {
|
||||
t.Fatalf("TestDevnetGenesisBlock: Genesis block does not "+
|
||||
"appear valid - got %v, want %v",
|
||||
spew.Sdump(buf.Bytes()),
|
||||
spew.Sdump(simnetGenesisBlockBytes))
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := DevnetParams.GenesisBlock.BlockHash()
|
||||
if !DevnetParams.GenesisHash.IsEqual(hash) {
|
||||
t.Fatalf("TestDevnetGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(DevnetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// genesisBlockBytes are the wire encoded bytes for the genesis block of the
|
||||
// main network as of protocol version 1.
|
||||
var genesisBlockBytes = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x72, 0x10, 0x35, 0x85, 0xdd, 0xac, 0x82, 0x5c, 0x49, 0x13, 0x9f,
|
||||
0xc0, 0x0e, 0x37, 0xc0, 0x45, 0x71, 0xdf, 0xd9, 0xf6, 0x36, 0xdf, 0x4c, 0x42, 0x72, 0x7b, 0x9e,
|
||||
0x86, 0xdd, 0x37, 0xd2, 0xbd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xc4, 0xda, 0x5c, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
|
||||
0x20, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x0b, 0x2f, 0x50, 0x32, 0x53, 0x48, 0x2f, 0x62, 0x74, 0x63,
|
||||
0x64, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd2,
|
||||
0xea, 0x82, 0x4e, 0xb8, 0x87, 0x42, 0xd0, 0x6d, 0x1f, 0x8d, 0xc3, 0xad, 0x9f, 0x43, 0x9e, 0xed,
|
||||
0x6f, 0x43, 0x3c, 0x02, 0x71, 0x71, 0x69, 0xfb, 0xbc, 0x91, 0x44, 0xac, 0xf1, 0x93, 0xd3, 0x18,
|
||||
0x17, 0xa9, 0x14, 0xda, 0x17, 0x45, 0xe9, 0xb5, 0x49, 0xbd, 0x0b, 0xfa, 0x1a, 0x56, 0x99, 0x71,
|
||||
0xc7, 0x7e, 0xba, 0x30, 0xcd, 0x5a, 0x4b, 0x87,
|
||||
}
|
||||
|
||||
// regtestGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
||||
// the regression test network as of protocol version 1.
|
||||
var regtestGenesisBlockBytes = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x3a, 0x9f, 0x62,
|
||||
0xc9, 0x2b, 0x16, 0x17, 0xb3, 0x41, 0x6d, 0x9e,
|
||||
0x2d, 0x87, 0x93, 0xfd, 0x72, 0x77, 0x4d, 0x1d,
|
||||
0x6f, 0x6d, 0x38, 0x5b, 0xf1, 0x24, 0x1b, 0xdc,
|
||||
0x96, 0xce, 0xbf, 0xa1, 0x09, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xd8, 0xe2, 0x15,
|
||||
0x5e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
|
||||
0x1e, 0xa6, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x0b, 0x2f,
|
||||
0x50, 0x32, 0x53, 0x48, 0x2f, 0x62, 0x74, 0x63,
|
||||
0x64, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xed,
|
||||
0x32, 0xec, 0xb4, 0xf8, 0x3c, 0x7a, 0x32, 0x0f,
|
||||
0xd2, 0xe5, 0x24, 0x77, 0x89, 0x43, 0x3a, 0x78,
|
||||
0x0a, 0xda, 0x68, 0x2d, 0xf6, 0xaa, 0xb1, 0x19,
|
||||
0xdd, 0xd8, 0x97, 0x15, 0x4b, 0xcb, 0x42, 0x25,
|
||||
0x17, 0xa9, 0x14, 0xda, 0x17, 0x45, 0xe9, 0xb5,
|
||||
0x49, 0xbd, 0x0b, 0xfa, 0x1a, 0x56, 0x99, 0x71,
|
||||
0xc7, 0x7e, 0xba, 0x30, 0xcd, 0x5a, 0x4b, 0x87,
|
||||
0x6b, 0x61, 0x73, 0x70, 0x61, 0x2d, 0x72, 0x65,
|
||||
0x67, 0x74, 0x65, 0x73, 0x74,
|
||||
}
|
||||
|
||||
// testnetGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
||||
// the test network as of protocol version 1.
|
||||
var testnetGenesisBlockBytes = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x88, 0x05, 0xd0,
|
||||
0xe7, 0x8f, 0x41, 0x77, 0x39, 0x2c, 0xb6, 0xbb,
|
||||
0xb4, 0x19, 0xa8, 0x48, 0x4a, 0xdf, 0x77, 0xb0,
|
||||
0x82, 0xd6, 0x70, 0xd8, 0x24, 0x6a, 0x36, 0x05,
|
||||
0xaa, 0xbd, 0x7a, 0xd1, 0x62, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0xad, 0x15,
|
||||
0x5e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
|
||||
0x1e, 0xa1, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x0b, 0x2f,
|
||||
0x50, 0x32, 0x53, 0x48, 0x2f, 0x62, 0x74, 0x63,
|
||||
0x64, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc,
|
||||
0x72, 0xe6, 0x7e, 0x37, 0xa1, 0x34, 0x89, 0x23,
|
||||
0x24, 0xaf, 0xae, 0x99, 0x1f, 0x89, 0x09, 0x41,
|
||||
0x1a, 0x4d, 0x58, 0xfe, 0x5a, 0x04, 0xb0, 0x3e,
|
||||
0xeb, 0x1b, 0x5b, 0xb8, 0x65, 0xa8, 0x65, 0x0f,
|
||||
0x01, 0x00, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x2d,
|
||||
0x74, 0x65, 0x73, 0x74, 0x6e, 0x65, 0x74,
|
||||
}
|
||||
|
||||
// simnetGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
||||
// the simulation test network as of protocol version 1.
|
||||
var simnetGenesisBlockBytes = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x1c, 0x3b,
|
||||
0x9e, 0x0d, 0x9a, 0xc0, 0x80, 0x0a, 0x08, 0x42,
|
||||
0x50, 0x02, 0xa3, 0xea, 0xdb, 0xed, 0xc8, 0xd0,
|
||||
0xad, 0x35, 0x03, 0xd8, 0x0e, 0x11, 0x3c, 0x7b,
|
||||
0xb2, 0xb5, 0x20, 0xe5, 0x84, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xd3, 0x15,
|
||||
0x5e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
|
||||
0x20, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x0b, 0x2f,
|
||||
0x50, 0x32, 0x53, 0x48, 0x2f, 0x62, 0x74, 0x63,
|
||||
0x64, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x89,
|
||||
0x48, 0xd3, 0x23, 0x9c, 0xf9, 0x88, 0x2b, 0x63,
|
||||
0xc7, 0x33, 0x0f, 0xa3, 0x64, 0xf2, 0xdb, 0x39,
|
||||
0x73, 0x5f, 0x2b, 0xa8, 0xd5, 0x7b, 0x5c, 0x31,
|
||||
0x68, 0xc9, 0x63, 0x37, 0x5c, 0xe7, 0x41, 0x24,
|
||||
0x17, 0xa9, 0x14, 0xda, 0x17, 0x45, 0xe9, 0xb5,
|
||||
0x49, 0xbd, 0x0b, 0xfa, 0x1a, 0x56, 0x99, 0x71,
|
||||
0xc7, 0x7e, 0xba, 0x30, 0xcd, 0x5a, 0x4b, 0x87,
|
||||
0x6b, 0x61, 0x73, 0x70, 0x61, 0x2d, 0x73, 0x69,
|
||||
0x6d, 0x6e, 0x65, 0x74,
|
||||
}
|
||||
|
||||
// devnetGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
||||
// the development network as of protocol version 1.
|
||||
var devnetGenesisBlockBytes = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x16, 0x0a, 0xc6,
|
||||
0x8b, 0x77, 0x08, 0xf4, 0x96, 0xa3, 0x07, 0x05,
|
||||
0xbc, 0x92, 0xda, 0xee, 0x73, 0x26, 0x5e, 0xd0,
|
||||
0x85, 0x78, 0xa2, 0x5d, 0x02, 0x49, 0x8a, 0x2a,
|
||||
0x22, 0xef, 0x41, 0xc9, 0xc3, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x58, 0xe7, 0x15,
|
||||
0x5e, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0x7f,
|
||||
0x1e, 0xac, 0x82, 0x02, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff,
|
||||
0xff, 0xff, 0xff, 0x0e, 0x00, 0x00, 0x0b, 0x2f,
|
||||
0x50, 0x32, 0x53, 0x48, 0x2f, 0x62, 0x74, 0x63,
|
||||
0x64, 0x2f, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x11,
|
||||
0xc7, 0x0c, 0x02, 0x9e, 0xb2, 0x2e, 0xb3, 0xad,
|
||||
0x24, 0x10, 0xfe, 0x2c, 0xdb, 0x8e, 0x1d, 0xde,
|
||||
0x81, 0x5b, 0xbb, 0x42, 0xfe, 0xb4, 0x93, 0xd6,
|
||||
0xe3, 0xbe, 0x86, 0x02, 0xe6, 0x3a, 0x65, 0x24,
|
||||
0x17, 0xa9, 0x14, 0xda, 0x17, 0x45, 0xe9, 0xb5,
|
||||
0x49, 0xbd, 0x0b, 0xfa, 0x1a, 0x56, 0x99, 0x71,
|
||||
0xc7, 0x7e, 0xba, 0x30, 0xcd, 0x5a, 0x4b, 0x87,
|
||||
0x6b, 0x61, 0x73, 0x70, 0x61, 0x2d, 0x64, 0x65,
|
||||
0x76, 0x6e, 0x65, 0x74,
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
package dbaccess
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/database/ffldb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// dbSingleton is a handle to an instance of the kaspad database
|
||||
var dbSingleton database.Database
|
||||
|
||||
// db returns a handle to the database
|
||||
func db() (database.Database, error) {
|
||||
if dbSingleton == nil {
|
||||
return nil, errors.New("database is not open")
|
||||
}
|
||||
return dbSingleton, nil
|
||||
}
|
||||
|
||||
// Open opens the database for given path
|
||||
func Open(path string) error {
|
||||
if dbSingleton != nil {
|
||||
return errors.New("database is already open")
|
||||
}
|
||||
|
||||
db, err := ffldb.Open(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dbSingleton = db
|
||||
return nil
|
||||
}
|
||||
|
||||
// Close closes the database, if it's open
|
||||
func Close() error {
|
||||
if dbSingleton == nil {
|
||||
return nil
|
||||
}
|
||||
err := dbSingleton.Close()
|
||||
dbSingleton = nil
|
||||
return err
|
||||
}
|
||||
2
doc.go
2
doc.go
@@ -6,7 +6,7 @@ Copyright (c) 2013-2014 Conformal Systems LLC.
|
||||
Use of this source code is governed by an ISC
|
||||
license that can be found in the LICENSE file.
|
||||
|
||||
kaspad is a full-node kaspa implementation written in Go.
|
||||
Kaspad is a full-node kaspa implementation written in Go.
|
||||
|
||||
The default options are sane for most users. This means kaspad will work 'out of
|
||||
the box' for most users. However, there are also a wide variety of flags that
|
||||
|
||||
44
domain/blockdag/behavior_flags.go
Normal file
44
domain/blockdag/behavior_flags.go
Normal file
@@ -0,0 +1,44 @@
|
||||
package blockdag
|
||||
|
||||
// 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
|
||||
|
||||
// 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
|
||||
)
|
||||
|
||||
func isBehaviorFlagRaised(flags BehaviorFlags, flag BehaviorFlags) bool {
|
||||
return flags&flag == flag
|
||||
}
|
||||
302
domain/blockdag/block_utxo.go
Normal file
302
domain/blockdag/block_utxo.go
Normal file
@@ -0,0 +1,302 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// TxAcceptanceData stores a transaction together with an indication
|
||||
// if it was accepted or not by some block
|
||||
type TxAcceptanceData struct {
|
||||
Tx *util.Tx
|
||||
IsAccepted bool
|
||||
}
|
||||
|
||||
// BlockTxsAcceptanceData stores all transactions in a block with an indication
|
||||
// if they were accepted or not by some other block
|
||||
type BlockTxsAcceptanceData struct {
|
||||
BlockHash daghash.Hash
|
||||
TxAcceptanceData []TxAcceptanceData
|
||||
}
|
||||
|
||||
// MultiBlockTxsAcceptanceData stores data about which transactions were accepted by a block
|
||||
// It's a slice of the block's blues block IDs and their transaction acceptance data
|
||||
type MultiBlockTxsAcceptanceData []BlockTxsAcceptanceData
|
||||
|
||||
// FindAcceptanceData finds the BlockTxsAcceptanceData that matches blockHash
|
||||
func (data MultiBlockTxsAcceptanceData) FindAcceptanceData(blockHash *daghash.Hash) (*BlockTxsAcceptanceData, bool) {
|
||||
for _, acceptanceData := range data {
|
||||
if acceptanceData.BlockHash.IsEqual(blockHash) {
|
||||
return &acceptanceData, true
|
||||
}
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// TxsAcceptedByVirtual retrieves transactions accepted by the current virtual block
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
func (dag *BlockDAG) TxsAcceptedByVirtual() (MultiBlockTxsAcceptanceData, error) {
|
||||
_, _, txsAcceptanceData, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
return txsAcceptanceData, err
|
||||
}
|
||||
|
||||
// TxsAcceptedByBlockHash retrieves transactions accepted by the given block
|
||||
//
|
||||
// This function MUST be called with the DAG read-lock held
|
||||
func (dag *BlockDAG) TxsAcceptedByBlockHash(blockHash *daghash.Hash) (MultiBlockTxsAcceptanceData, error) {
|
||||
node, ok := dag.index.LookupNode(blockHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Couldn't find block %s", blockHash)
|
||||
}
|
||||
_, _, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
return txsAcceptanceData, err
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) meldVirtualUTXO(newVirtualUTXODiffSet *DiffUTXOSet) error {
|
||||
return newVirtualUTXODiffSet.meldToBase()
|
||||
}
|
||||
|
||||
// checkDoubleSpendsWithBlockPast checks that each block transaction
|
||||
// has a corresponding UTXO in the block pastUTXO.
|
||||
func checkDoubleSpendsWithBlockPast(pastUTXO UTXOSet, blockTransactions []*util.Tx) error {
|
||||
for _, tx := range blockTransactions {
|
||||
if tx.IsCoinBase() {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
if _, ok := pastUTXO.Get(txIn.PreviousOutpoint); !ok {
|
||||
return ruleError(ErrMissingTxOut, fmt.Sprintf("missing transaction "+
|
||||
"output %s in the utxo set", txIn.PreviousOutpoint))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyAndBuildUTXO verifies all transactions in the given block and builds its UTXO
|
||||
// to save extra traversals it returns the transactions acceptance data, the compactFeeData
|
||||
// for the new block and its multiset.
|
||||
func (node *blockNode) verifyAndBuildUTXO(dag *BlockDAG, transactions []*util.Tx, fastAdd bool) (
|
||||
newBlockUTXO UTXOSet, txsAcceptanceData MultiBlockTxsAcceptanceData, newBlockFeeData compactFeeData, multiset *secp256k1.MultiSet, err error) {
|
||||
|
||||
pastUTXO, selectedParentPastUTXO, txsAcceptanceData, err := dag.pastUTXO(node)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = node.validateAcceptedIDMerkleRoot(dag, txsAcceptanceData)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
feeData, err := dag.checkConnectToPastUTXO(node, pastUTXO, transactions, fastAdd)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
multiset, err = node.calcMultiset(dag, txsAcceptanceData, selectedParentPastUTXO)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
err = node.validateUTXOCommitment(multiset)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, err
|
||||
}
|
||||
|
||||
return pastUTXO, txsAcceptanceData, feeData, multiset, nil
|
||||
}
|
||||
|
||||
func genesisPastUTXO(virtual *virtualBlock) UTXOSet {
|
||||
// The genesis has no past UTXO, so we create an empty UTXO
|
||||
// set by creating a diff UTXO set with the virtual UTXO
|
||||
// set, and adding all of its entries in toRemove
|
||||
diff := NewUTXODiff()
|
||||
for outpoint, entry := range virtual.utxoSet.utxoCollection {
|
||||
diff.toRemove[outpoint] = entry
|
||||
}
|
||||
genesisPastUTXO := UTXOSet(NewDiffUTXOSet(virtual.utxoSet, diff))
|
||||
return genesisPastUTXO
|
||||
}
|
||||
|
||||
// applyBlueBlocks adds all transactions in the blue blocks to the selectedParent's past UTXO set
|
||||
// Purposefully ignoring failures - these are just unaccepted transactions
|
||||
// Writing down which transactions were accepted or not in txsAcceptanceData
|
||||
func (node *blockNode) applyBlueBlocks(selectedParentPastUTXO UTXOSet, blueBlocks []*util.Block) (
|
||||
pastUTXO UTXOSet, multiBlockTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) {
|
||||
|
||||
pastUTXO = selectedParentPastUTXO.(*DiffUTXOSet).cloneWithoutBase()
|
||||
multiBlockTxsAcceptanceData = make(MultiBlockTxsAcceptanceData, len(blueBlocks))
|
||||
|
||||
// Add blueBlocks to multiBlockTxsAcceptanceData in topological order. This
|
||||
// is so that anyone who iterates over it would process blocks (and transactions)
|
||||
// in their order of appearance in the DAG.
|
||||
for i := 0; i < len(blueBlocks); i++ {
|
||||
blueBlock := blueBlocks[i]
|
||||
transactions := blueBlock.Transactions()
|
||||
blockTxsAcceptanceData := BlockTxsAcceptanceData{
|
||||
BlockHash: *blueBlock.Hash(),
|
||||
TxAcceptanceData: make([]TxAcceptanceData, len(transactions)),
|
||||
}
|
||||
isSelectedParent := i == 0
|
||||
|
||||
for j, tx := range blueBlock.Transactions() {
|
||||
var isAccepted bool
|
||||
|
||||
// Coinbase transaction outputs are added to the UTXO
|
||||
// only if they are in the selected parent chain.
|
||||
if !isSelectedParent && tx.IsCoinBase() {
|
||||
isAccepted = false
|
||||
} else {
|
||||
isAccepted, err = pastUTXO.AddTx(tx.MsgTx(), node.blueScore)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
blockTxsAcceptanceData.TxAcceptanceData[j] = TxAcceptanceData{Tx: tx, IsAccepted: isAccepted}
|
||||
}
|
||||
multiBlockTxsAcceptanceData[i] = blockTxsAcceptanceData
|
||||
}
|
||||
|
||||
return pastUTXO, multiBlockTxsAcceptanceData, nil
|
||||
}
|
||||
|
||||
// pastUTXO returns the UTXO of a given block's past
|
||||
// To save traversals over the blue blocks, it also returns the transaction acceptance data for
|
||||
// all blue blocks
|
||||
func (dag *BlockDAG) pastUTXO(node *blockNode) (
|
||||
pastUTXO, selectedParentPastUTXO UTXOSet, bluesTxsAcceptanceData MultiBlockTxsAcceptanceData, err error) {
|
||||
|
||||
if node.isGenesis() {
|
||||
return genesisPastUTXO(dag.virtual), nil, MultiBlockTxsAcceptanceData{}, nil
|
||||
}
|
||||
|
||||
selectedParentPastUTXO, err = dag.restorePastUTXO(node.selectedParent)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
blueBlocks, err := dag.fetchBlueBlocks(node)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
pastUTXO, bluesTxsAcceptanceData, err = node.applyBlueBlocks(selectedParentPastUTXO, blueBlocks)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
|
||||
return pastUTXO, selectedParentPastUTXO, bluesTxsAcceptanceData, nil
|
||||
}
|
||||
|
||||
// restorePastUTXO restores the UTXO of a given block from its diff
|
||||
func (dag *BlockDAG) restorePastUTXO(node *blockNode) (UTXOSet, error) {
|
||||
stack := []*blockNode{}
|
||||
|
||||
// Iterate over the chain of diff-childs from node till virtual and add them
|
||||
// all into a stack
|
||||
for current := node; current != nil; {
|
||||
stack = append(stack, current)
|
||||
var err error
|
||||
current, err = dag.utxoDiffStore.diffChildByNode(current)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Start with the top item in the stack, going over it top-to-bottom,
|
||||
// applying the UTXO-diff one-by-one.
|
||||
topNode, stack := stack[len(stack)-1], stack[:len(stack)-1] // pop the top item in the stack
|
||||
topNodeDiff, err := dag.utxoDiffStore.diffByNode(topNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
accumulatedDiff := topNodeDiff.clone()
|
||||
|
||||
for i := len(stack) - 1; i >= 0; i-- {
|
||||
diff, err := dag.utxoDiffStore.diffByNode(stack[i])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Use withDiffInPlace, otherwise copying the diffs again and again create a polynomial overhead
|
||||
err = accumulatedDiff.withDiffInPlace(diff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return NewDiffUTXOSet(dag.virtual.utxoSet, accumulatedDiff), nil
|
||||
}
|
||||
|
||||
// updateTipsUTXO builds and applies new diff UTXOs for all the DAG's tips
|
||||
func updateTipsUTXO(dag *BlockDAG, virtualUTXO UTXOSet) error {
|
||||
for tip := range dag.virtual.parents {
|
||||
tipPastUTXO, err := dag.restorePastUTXO(tip)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diff, err := virtualUTXO.diffFrom(tipPastUTXO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dag.utxoDiffStore.setBlockDiff(tip, diff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateParents adds this block to the children sets of its parents
|
||||
// and updates the diff of any parent whose DiffChild is this block
|
||||
func (node *blockNode) updateParents(dag *BlockDAG, newBlockUTXO UTXOSet) error {
|
||||
node.updateParentsChildren()
|
||||
return node.updateParentsDiffs(dag, newBlockUTXO)
|
||||
}
|
||||
|
||||
// updateParentsDiffs updates the diff of any parent whose DiffChild is this block
|
||||
func (node *blockNode) updateParentsDiffs(dag *BlockDAG, newBlockUTXO UTXOSet) error {
|
||||
virtualDiffFromNewBlock, err := dag.virtual.utxoSet.diffFrom(newBlockUTXO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.utxoDiffStore.setBlockDiff(node, virtualDiffFromNewBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for parent := range node.parents {
|
||||
diffChild, err := dag.utxoDiffStore.diffChildByNode(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if diffChild == nil {
|
||||
parentPastUTXO, err := dag.restorePastUTXO(parent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dag.utxoDiffStore.setBlockDiffChild(parent, node)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diff, err := newBlockUTXO.diffFrom(parentPastUTXO)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dag.utxoDiffStore.setBlockDiff(parent, diff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -11,14 +11,14 @@ import (
|
||||
func TestBlockHeap(t *testing.T) {
|
||||
// Create a new database and DAG instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestBlockHeap", true, Config{
|
||||
DAGParams: &dagconfig.MainnetParams,
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("TestBlockHeap: Failed to setup DAG instance: %s", err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
block0Header := dagconfig.MainnetParams.GenesisBlock.Header
|
||||
block0Header := dagconfig.SimnetParams.GenesisBlock.Header
|
||||
block0, _ := dag.newBlockNode(&block0Header, newBlockSet())
|
||||
|
||||
block100000Header := Block100000.Header
|
||||
@@ -5,10 +5,12 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -50,11 +52,11 @@ func (bi *blockIndex) HaveBlock(hash *daghash.Hash) bool {
|
||||
// return nil if there is no entry for the hash.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (bi *blockIndex) LookupNode(hash *daghash.Hash) *blockNode {
|
||||
func (bi *blockIndex) LookupNode(hash *daghash.Hash) (*blockNode, bool) {
|
||||
bi.RLock()
|
||||
defer bi.RUnlock()
|
||||
node := bi.index[*hash]
|
||||
return node
|
||||
node, ok := bi.index[*hash]
|
||||
return node, ok
|
||||
}
|
||||
|
||||
// AddNode adds the provided node to the block index and marks it as dirty.
|
||||
@@ -134,3 +136,42 @@ 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,10 +1,9 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
)
|
||||
|
||||
func TestAncestorErrors(t *testing.T) {
|
||||
@@ -18,7 +17,7 @@ func TestAncestorErrors(t *testing.T) {
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
node := newTestNode(dag, newBlockSet(), int32(0x10000000), 0, time.Unix(0, 0))
|
||||
node := newTestNode(dag, newBlockSet(), int32(0x10000000), 0, mstime.Now())
|
||||
node.blueScore = 2
|
||||
ancestor := node.SelectedAncestor(3)
|
||||
if ancestor != nil {
|
||||
243
domain/blockdag/blocklocator.go
Normal file
243
domain/blockdag/blocklocator.go
Normal file
@@ -0,0 +1,243 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"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
|
||||
}
|
||||
|
||||
// antiPastHashesBetween returns the hashes of the blocks between the
|
||||
// lowHash's antiPast and highHash's antiPast, or up to the provided
|
||||
// max number of block hashes.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) antiPastHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
|
||||
nodes, err := dag.antiPastBetween(lowHash, highHash, maxHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
hashes := make([]*daghash.Hash, len(nodes))
|
||||
for i, node := range nodes {
|
||||
hashes[i] = node.hash
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// antiPastBetween returns the blockNodes between the lowHash's antiPast
|
||||
// and highHash's antiPast, or up to the provided max number of blocks.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries uint64) ([]*blockNode, error) {
|
||||
lowNode, ok := dag.index.LookupNode(lowHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Couldn't find low hash %s", lowHash)
|
||||
}
|
||||
highNode, ok := dag.index.LookupNode(highHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Couldn't find high hash %s", highHash)
|
||||
}
|
||||
if lowNode.blueScore >= highNode.blueScore {
|
||||
return nil, errors.Errorf("Low hash blueScore >= high hash blueScore (%d >= %d)",
|
||||
lowNode.blueScore, highNode.blueScore)
|
||||
}
|
||||
|
||||
// In order to get no more then maxEntries blocks from the
|
||||
// future of the lowNode (including itself), we iterate the
|
||||
// selected parent chain of the highNode and stop once we reach
|
||||
// highNode.blueScore-lowNode.blueScore+1 <= maxEntries. That
|
||||
// stop point becomes the new highNode.
|
||||
// Using blueScore as an approximation is considered to be
|
||||
// fairly accurate because we presume that most DAG blocks are
|
||||
// blue.
|
||||
for highNode.blueScore-lowNode.blueScore+1 > maxEntries {
|
||||
highNode = highNode.selectedParent
|
||||
}
|
||||
|
||||
// Collect every node in highNode's past (including itself) but
|
||||
// NOT in the lowNode's past (excluding itself) into an up-heap
|
||||
// (a heap sorted by blueScore from lowest to greatest).
|
||||
visited := newBlockSet()
|
||||
candidateNodes := newUpHeap()
|
||||
queue := newDownHeap()
|
||||
queue.Push(highNode)
|
||||
for queue.Len() > 0 {
|
||||
current := queue.pop()
|
||||
if visited.contains(current) {
|
||||
continue
|
||||
}
|
||||
visited.add(current)
|
||||
isCurrentAncestorOfLowNode, err := dag.isInPast(current, lowNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isCurrentAncestorOfLowNode {
|
||||
continue
|
||||
}
|
||||
candidateNodes.Push(current)
|
||||
for parent := range current.parents {
|
||||
queue.Push(parent)
|
||||
}
|
||||
}
|
||||
|
||||
// Pop candidateNodes into a slice. Since candidateNodes is
|
||||
// an up-heap, it's guaranteed to be ordered from low to high
|
||||
nodesLen := int(maxEntries)
|
||||
if candidateNodes.Len() < nodesLen {
|
||||
nodesLen = candidateNodes.Len()
|
||||
}
|
||||
nodes := make([]*blockNode, nodesLen)
|
||||
for i := 0; i < nodesLen; i++ {
|
||||
nodes[i] = candidateNodes.pop()
|
||||
}
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
// AntiPastHashesBetween returns the hashes of the blocks between the
|
||||
// lowHash's antiPast and highHash's antiPast, or up to the provided
|
||||
// max number of block hashes.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) AntiPastHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
hashes, err := dag.antiPastHashesBetween(lowHash, highHash, maxHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// antiPastHeadersBetween returns the headers of the blocks between the
|
||||
// lowHash's antiPast and highHash's antiPast, or up to the provided
|
||||
// max number of block headers.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) antiPastHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*domainmessage.BlockHeader, error) {
|
||||
nodes, err := dag.antiPastBetween(lowHash, highHash, maxHeaders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
headers := make([]*domainmessage.BlockHeader, len(nodes))
|
||||
for i, node := range nodes {
|
||||
headers[i] = node.Header()
|
||||
}
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
// AntiPastHeadersBetween returns the headers of the blocks between the
|
||||
// lowHash's antiPast and highHash's antiPast, or up to
|
||||
// domainmessage.MaxBlockHeadersPerMsg block headers.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) AntiPastHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*domainmessage.BlockHeader, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
headers, err := dag.antiPastHeadersBetween(lowHash, highHash, maxHeaders)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return headers, nil
|
||||
}
|
||||
@@ -6,13 +6,14 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// blockStatus is a bit field representing the validation state of the block.
|
||||
@@ -105,12 +106,12 @@ type blockNode struct {
|
||||
// anticone of its selected parent (parent with highest blue score).
|
||||
// selectedParentAnticone is used to update reachability data we store for future reachability queries.
|
||||
// This function is NOT safe for concurrent access.
|
||||
func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
|
||||
func (dag *BlockDAG) newBlockNode(blockHeader *domainmessage.BlockHeader, parents blockSet) (node *blockNode, selectedParentAnticone []*blockNode) {
|
||||
node = &blockNode{
|
||||
parents: parents,
|
||||
children: make(blockSet),
|
||||
blueScore: math.MaxUint64, // Initialized to the max value to avoid collisions with the genesis block
|
||||
timestamp: dag.Now().Unix(),
|
||||
timestamp: dag.Now().UnixMilliseconds(),
|
||||
bluesAnticoneSizes: make(map[*blockNode]dagconfig.KType),
|
||||
}
|
||||
|
||||
@@ -120,7 +121,7 @@ func (dag *BlockDAG) newBlockNode(blockHeader *wire.BlockHeader, parents blockSe
|
||||
node.version = blockHeader.Version
|
||||
node.bits = blockHeader.Bits
|
||||
node.nonce = blockHeader.Nonce
|
||||
node.timestamp = blockHeader.Timestamp.Unix()
|
||||
node.timestamp = blockHeader.Timestamp.UnixMilliseconds()
|
||||
node.hashMerkleRoot = blockHeader.HashMerkleRoot
|
||||
node.acceptedIDMerkleRoot = blockHeader.AcceptedIDMerkleRoot
|
||||
node.utxoCommitment = blockHeader.UTXOCommitment
|
||||
@@ -159,15 +160,15 @@ func (node *blockNode) less(other *blockNode) bool {
|
||||
// Header constructs a block header from the node and returns it.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (node *blockNode) Header() *wire.BlockHeader {
|
||||
func (node *blockNode) Header() *domainmessage.BlockHeader {
|
||||
// No lock is needed because all accessed fields are immutable.
|
||||
return &wire.BlockHeader{
|
||||
return &domainmessage.BlockHeader{
|
||||
Version: node.version,
|
||||
ParentHashes: node.ParentHashes(),
|
||||
HashMerkleRoot: node.hashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: node.acceptedIDMerkleRoot,
|
||||
UTXOCommitment: node.utxoCommitment,
|
||||
Timestamp: time.Unix(node.timestamp, 0),
|
||||
Timestamp: node.time(),
|
||||
Bits: node.bits,
|
||||
Nonce: node.nonce,
|
||||
}
|
||||
@@ -204,13 +205,13 @@ func (node *blockNode) RelativeAncestor(distance uint64) *blockNode {
|
||||
// prior to, and including, the block node.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (node *blockNode) PastMedianTime(dag *BlockDAG) time.Time {
|
||||
func (node *blockNode) PastMedianTime(dag *BlockDAG) mstime.Time {
|
||||
window := blueBlockWindow(node, 2*dag.TimestampDeviationTolerance-1)
|
||||
medianTimestamp, err := window.medianTimestamp()
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("blueBlockWindow: %s", err))
|
||||
}
|
||||
return time.Unix(medianTimestamp, 0)
|
||||
return mstime.UnixMilliseconds(medianTimestamp)
|
||||
}
|
||||
|
||||
func (node *blockNode) ParentHashes() []*daghash.Hash {
|
||||
@@ -223,10 +224,14 @@ func (node *blockNode) isGenesis() bool {
|
||||
}
|
||||
|
||||
func (node *blockNode) finalityScore(dag *BlockDAG) uint64 {
|
||||
return node.blueScore / uint64(dag.dagParams.FinalityInterval)
|
||||
return node.blueScore / uint64(dag.FinalityInterval())
|
||||
}
|
||||
|
||||
// String returns a string that contains the block hash.
|
||||
func (node blockNode) String() string {
|
||||
return node.hash.String()
|
||||
}
|
||||
|
||||
func (node *blockNode) time() mstime.Time {
|
||||
return mstime.UnixMilliseconds(node.timestamp)
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"testing"
|
||||
)
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"reflect"
|
||||
@@ -53,12 +53,12 @@ func TestBlueBlockWindow(t *testing.T) {
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
id: "E",
|
||||
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"C", "D", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"C", "D"},
|
||||
id: "F",
|
||||
expectedWindowWithGenesisPadding: []string{"D", "C", "B", "A", "A", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"C", "D", "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", "D", "C", "B", "A", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"F", "C", "D", "B", "A", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"I"},
|
||||
id: "J",
|
||||
expectedWindowWithGenesisPadding: []string{"I", "F", "D", "C", "B", "A", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"I", "F", "C", "D", "B", "A", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"J"},
|
||||
id: "K",
|
||||
expectedWindowWithGenesisPadding: []string{"J", "I", "F", "D", "C", "B", "A", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"J", "I", "F", "C", "D", "B", "A", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"K"},
|
||||
id: "L",
|
||||
expectedWindowWithGenesisPadding: []string{"K", "J", "I", "F", "D", "C", "B", "A", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"K", "J", "I", "F", "C", "D", "B", "A", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"L"},
|
||||
id: "M",
|
||||
expectedWindowWithGenesisPadding: []string{"L", "K", "J", "I", "F", "D", "C", "B", "A", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"L", "K", "J", "I", "F", "C", "D", "B", "A", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"M"},
|
||||
id: "N",
|
||||
expectedWindowWithGenesisPadding: []string{"M", "L", "K", "J", "I", "F", "D", "C", "B", "A"},
|
||||
expectedWindowWithGenesisPadding: []string{"M", "L", "K", "J", "I", "F", "C", "D", "B", "A"},
|
||||
},
|
||||
{
|
||||
parents: []string{"N"},
|
||||
id: "O",
|
||||
expectedWindowWithGenesisPadding: []string{"N", "M", "L", "K", "J", "I", "F", "D", "C", "B"},
|
||||
expectedWindowWithGenesisPadding: []string{"N", "M", "L", "K", "J", "I", "F", "C", "D", "B"},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -133,7 +133,10 @@ func TestBlueBlockWindow(t *testing.T) {
|
||||
t.Fatalf("block %v was unexpectedly orphan", blockData.id)
|
||||
}
|
||||
|
||||
node := dag.index.LookupNode(utilBlock.Hash())
|
||||
node, ok := dag.index.LookupNode(utilBlock.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", utilBlock.Hash())
|
||||
}
|
||||
|
||||
blockByIDMap[blockData.id] = node
|
||||
idByBlockMap[node] = blockData.id
|
||||
@@ -4,16 +4,16 @@ import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/coinbasepayload"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/util/txsort"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// compactFeeData is a specialized data type to store a compact list of fees
|
||||
@@ -76,11 +76,11 @@ func (cfr *compactFeeIterator) next() (uint64, error) {
|
||||
|
||||
// getBluesFeeData returns the compactFeeData for all nodes's blues,
|
||||
// used to calculate the fees this blockNode needs to pay
|
||||
func (node *blockNode) getBluesFeeData(dag *BlockDAG) (map[daghash.Hash]compactFeeData, error) {
|
||||
func (dag *BlockDAG) getBluesFeeData(node *blockNode) (map[daghash.Hash]compactFeeData, error) {
|
||||
bluesFeeData := make(map[daghash.Hash]compactFeeData)
|
||||
|
||||
for _, blueBlock := range node.blues {
|
||||
feeData, err := dbaccess.FetchFeeData(dbaccess.NoTx(), blueBlock.hash)
|
||||
feeData, err := dbaccess.FetchFeeData(dag.databaseContext, blueBlock.hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -98,7 +98,10 @@ func (node *blockNode) validateCoinbaseTransaction(dag *BlockDAG, block *util.Bl
|
||||
return nil
|
||||
}
|
||||
blockCoinbaseTx := block.CoinbaseTransaction().MsgTx()
|
||||
scriptPubKey, extraData, err := DeserializeCoinbasePayload(blockCoinbaseTx)
|
||||
_, scriptPubKey, extraData, err := coinbasepayload.DeserializeCoinbasePayload(blockCoinbaseTx)
|
||||
if errors.Is(err, coinbasepayload.ErrIncorrectScriptPubKeyLen) {
|
||||
return ruleError(ErrBadCoinbaseTransaction, err.Error())
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -116,132 +119,102 @@ func (node *blockNode) validateCoinbaseTransaction(dag *BlockDAG, block *util.Bl
|
||||
|
||||
// expectedCoinbaseTransaction returns the coinbase transaction for the current block
|
||||
func (node *blockNode) expectedCoinbaseTransaction(dag *BlockDAG, txsAcceptanceData MultiBlockTxsAcceptanceData, scriptPubKey []byte, extraData []byte) (*util.Tx, error) {
|
||||
bluesFeeData, err := node.getBluesFeeData(dag)
|
||||
bluesFeeData, err := dag.getBluesFeeData(node)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txIns := []*wire.TxIn{}
|
||||
txOuts := []*wire.TxOut{}
|
||||
txIns := []*domainmessage.TxIn{}
|
||||
txOuts := []*domainmessage.TxOut{}
|
||||
|
||||
for _, blue := range node.blues {
|
||||
txIn, txOut, err := coinbaseInputAndOutputForBlueBlock(dag, blue, txsAcceptanceData, bluesFeeData)
|
||||
txOut, err := coinbaseOutputForBlueBlock(dag, blue, txsAcceptanceData, bluesFeeData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
txIns = append(txIns, txIn)
|
||||
if txOut != nil {
|
||||
txOuts = append(txOuts, txOut)
|
||||
}
|
||||
}
|
||||
payload, err := SerializeCoinbasePayload(scriptPubKey, extraData)
|
||||
payload, err := coinbasepayload.SerializeCoinbasePayload(node.blueScore, scriptPubKey, extraData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
coinbaseTx := wire.NewSubnetworkMsgTx(wire.TxVersion, txIns, txOuts, subnetworkid.SubnetworkIDCoinbase, 0, payload)
|
||||
coinbaseTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, txIns, txOuts, subnetworkid.SubnetworkIDCoinbase, 0, payload)
|
||||
sortedCoinbaseTx := txsort.Sort(coinbaseTx)
|
||||
return util.NewTx(sortedCoinbaseTx), nil
|
||||
}
|
||||
|
||||
// SerializeCoinbasePayload builds the coinbase payload based on the provided scriptPubKey and extra data.
|
||||
func SerializeCoinbasePayload(scriptPubKey []byte, extraData []byte) ([]byte, error) {
|
||||
w := &bytes.Buffer{}
|
||||
err := wire.WriteVarInt(w, uint64(len(scriptPubKey)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = w.Write(scriptPubKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = w.Write(extraData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return w.Bytes(), nil
|
||||
}
|
||||
|
||||
// DeserializeCoinbasePayload deserialize the coinbase payload to its component (scriptPubKey and extra data).
|
||||
func DeserializeCoinbasePayload(tx *wire.MsgTx) (scriptPubKey []byte, extraData []byte, err error) {
|
||||
r := bytes.NewReader(tx.Payload)
|
||||
scriptPubKeyLen, err := wire.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
scriptPubKey = make([]byte, scriptPubKeyLen)
|
||||
_, err = r.Read(scriptPubKey)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
extraData = make([]byte, r.Len())
|
||||
if r.Len() != 0 {
|
||||
_, err = r.Read(extraData)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
return scriptPubKey, extraData, nil
|
||||
}
|
||||
|
||||
// feeInputAndOutputForBlueBlock calculates the input and output that should go into the coinbase transaction of blueBlock
|
||||
// If blueBlock gets no fee - returns only txIn and nil for txOut
|
||||
func coinbaseInputAndOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
|
||||
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData map[daghash.Hash]compactFeeData) (
|
||||
*wire.TxIn, *wire.TxOut, error) {
|
||||
// coinbaseOutputForBlueBlock calculates the output that should go into the coinbase transaction of blueBlock
|
||||
// If blueBlock gets no fee - returns nil for txOut
|
||||
func coinbaseOutputForBlueBlock(dag *BlockDAG, blueBlock *blockNode,
|
||||
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData map[daghash.Hash]compactFeeData) (*domainmessage.TxOut, error) {
|
||||
|
||||
blockTxsAcceptanceData, ok := txsAcceptanceData.FindAcceptanceData(blueBlock.hash)
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("No txsAcceptanceData for block %s", blueBlock.hash)
|
||||
return nil, errors.Errorf("No txsAcceptanceData for block %s", blueBlock.hash)
|
||||
}
|
||||
blockFeeData, ok := feeData[*blueBlock.hash]
|
||||
if !ok {
|
||||
return nil, nil, errors.Errorf("No feeData for block %s", blueBlock.hash)
|
||||
return nil, errors.Errorf("No feeData for block %s", blueBlock.hash)
|
||||
}
|
||||
|
||||
if len(blockTxsAcceptanceData.TxAcceptanceData) != blockFeeData.Len() {
|
||||
return nil, nil, errors.Errorf(
|
||||
return nil, errors.Errorf(
|
||||
"length of accepted transaction data(%d) and fee data(%d) is not equal for block %s",
|
||||
len(blockTxsAcceptanceData.TxAcceptanceData), blockFeeData.Len(), blueBlock.hash)
|
||||
}
|
||||
|
||||
txIn := &wire.TxIn{
|
||||
SignatureScript: []byte{},
|
||||
PreviousOutpoint: wire.Outpoint{
|
||||
TxID: daghash.TxID(*blueBlock.hash),
|
||||
Index: math.MaxUint32,
|
||||
},
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
}
|
||||
|
||||
totalFees := uint64(0)
|
||||
feeIterator := blockFeeData.iterator()
|
||||
|
||||
for _, txAcceptanceData := range blockTxsAcceptanceData.TxAcceptanceData {
|
||||
fee, err := feeIterator.next()
|
||||
if err != nil {
|
||||
return nil, nil, errors.Errorf("Error retrieving fee from compactFeeData iterator: %s", err)
|
||||
return nil, errors.Errorf("Error retrieving fee from compactFeeData iterator: %s", err)
|
||||
}
|
||||
if txAcceptanceData.IsAccepted {
|
||||
totalFees += fee
|
||||
}
|
||||
}
|
||||
|
||||
totalReward := CalcBlockSubsidy(blueBlock.blueScore, dag.dagParams) + totalFees
|
||||
totalReward := CalcBlockSubsidy(blueBlock.blueScore, dag.Params) + totalFees
|
||||
|
||||
if totalReward == 0 {
|
||||
return txIn, nil, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// the ScriptPubKey for the coinbase is parsed from the coinbase payload
|
||||
scriptPubKey, _, err := DeserializeCoinbasePayload(blockTxsAcceptanceData.TxAcceptanceData[0].Tx.MsgTx())
|
||||
_, scriptPubKey, _, err := coinbasepayload.DeserializeCoinbasePayload(blockTxsAcceptanceData.TxAcceptanceData[0].Tx.MsgTx())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txOut := &wire.TxOut{
|
||||
txOut := &domainmessage.TxOut{
|
||||
Value: totalReward,
|
||||
ScriptPubKey: scriptPubKey,
|
||||
}
|
||||
|
||||
return txIn, txOut, nil
|
||||
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)
|
||||
}
|
||||
@@ -12,14 +12,15 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// loadUTXOSet returns a utxo view loaded from a file.
|
||||
@@ -77,7 +78,7 @@ func loadUTXOSet(filename string) (UTXOSet, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
utxoSet.utxoCollection[wire.Outpoint{TxID: txID, Index: index}] = entry
|
||||
utxoSet.utxoCollection[domainmessage.Outpoint{TxID: txID, Index: index}] = entry
|
||||
}
|
||||
|
||||
return utxoSet, nil
|
||||
@@ -86,7 +87,7 @@ func loadUTXOSet(filename string) (UTXOSet, error) {
|
||||
// TestSetCoinbaseMaturity makes the ability to set the coinbase maturity
|
||||
// available when running tests.
|
||||
func (dag *BlockDAG) TestSetCoinbaseMaturity(maturity uint64) {
|
||||
dag.dagParams.BlockCoinbaseMaturity = maturity
|
||||
dag.Params.BlockCoinbaseMaturity = maturity
|
||||
}
|
||||
|
||||
// newTestDAG returns a DAG that is usable for syntetic tests. It is
|
||||
@@ -95,17 +96,13 @@ func (dag *BlockDAG) TestSetCoinbaseMaturity(maturity uint64) {
|
||||
// use of it.
|
||||
func newTestDAG(params *dagconfig.Params) *BlockDAG {
|
||||
index := newBlockIndex(params)
|
||||
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
|
||||
dag := &BlockDAG{
|
||||
dagParams: params,
|
||||
Params: params,
|
||||
timeSource: NewTimeSource(),
|
||||
targetTimePerBlock: targetTimePerBlock,
|
||||
difficultyAdjustmentWindowSize: params.DifficultyAdjustmentWindowSize,
|
||||
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
|
||||
@@ -119,9 +116,9 @@ func newTestDAG(params *dagconfig.Params) *BlockDAG {
|
||||
|
||||
// newTestNode creates a block node connected to the passed parent with the
|
||||
// provided fields populated and fake values for the other fields.
|
||||
func newTestNode(dag *BlockDAG, parents blockSet, blockVersion int32, bits uint32, timestamp time.Time) *blockNode {
|
||||
func newTestNode(dag *BlockDAG, parents blockSet, blockVersion int32, bits uint32, timestamp mstime.Time) *blockNode {
|
||||
// Make up a header and create a block node from it.
|
||||
header := &wire.BlockHeader{
|
||||
header := &domainmessage.BlockHeader{
|
||||
Version: blockVersion,
|
||||
ParentHashes: parents.hashes(),
|
||||
Bits: bits,
|
||||
@@ -169,7 +166,7 @@ func checkRuleError(gotErr, wantErr error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*wire.MsgBlock) *wire.MsgBlock {
|
||||
func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parents ...*domainmessage.MsgBlock) *domainmessage.MsgBlock {
|
||||
parentHashes := make([]*daghash.Hash, len(parents))
|
||||
for i, parent := range parents {
|
||||
parentHashes[i] = parent.BlockHash()
|
||||
@@ -177,22 +174,22 @@ func prepareAndProcessBlockByParentMsgBlocks(t *testing.T, dag *BlockDAG, parent
|
||||
return PrepareAndProcessBlockForTest(t, dag, parentHashes, nil)
|
||||
}
|
||||
|
||||
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *wire.MsgBlock) *blockNode {
|
||||
node := dag.index.LookupNode(block.BlockHash())
|
||||
if node == nil {
|
||||
func nodeByMsgBlock(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock) *blockNode {
|
||||
node, ok := dag.index.LookupNode(block.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("couldn't find block node with hash %s", block.BlockHash())
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
type fakeTimeSource struct {
|
||||
time time.Time
|
||||
time mstime.Time
|
||||
}
|
||||
|
||||
func (fts *fakeTimeSource) Now() time.Time {
|
||||
return time.Unix(fts.time.Unix(), 0)
|
||||
func (fts *fakeTimeSource) Now() mstime.Time {
|
||||
return fts.time
|
||||
}
|
||||
|
||||
func newFakeTimeSource(fakeTime time.Time) TimeSource {
|
||||
func newFakeTimeSource(fakeTime mstime.Time) TimeSource {
|
||||
return &fakeTimeSource{time: fakeTime}
|
||||
}
|
||||
54
domain/blockdag/config.go
Normal file
54
domain/blockdag/config.go
Normal file
@@ -0,0 +1,54 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// Config is a descriptor which specifies the blockDAG instance configuration.
|
||||
type Config struct {
|
||||
// Interrupt specifies a channel the caller can close to signal that
|
||||
// long running operations, such as catching up indexes or performing
|
||||
// database migrations, should be interrupted.
|
||||
//
|
||||
// This field can be nil if the caller does not desire the behavior.
|
||||
Interrupt <-chan struct{}
|
||||
|
||||
// DAGParams identifies which DAG parameters the DAG is associated
|
||||
// with.
|
||||
//
|
||||
// This field is required.
|
||||
DAGParams *dagconfig.Params
|
||||
|
||||
// TimeSource defines the time source to use for things such as
|
||||
// block processing and determining whether or not the DAG is current.
|
||||
TimeSource TimeSource
|
||||
|
||||
// SigCache defines a signature cache to use when when validating
|
||||
// signatures. This is typically most useful when individual
|
||||
// transactions are already being validated prior to their inclusion in
|
||||
// a block such as what is usually done via a transaction memory pool.
|
||||
//
|
||||
// This field can be nil if the caller is not interested in using a
|
||||
// signature cache.
|
||||
SigCache *txscript.SigCache
|
||||
|
||||
// IndexManager defines an index manager to use when initializing the
|
||||
// DAG and connecting blocks.
|
||||
//
|
||||
// This field can be nil if the caller does not wish to make use of an
|
||||
// index manager.
|
||||
IndexManager IndexManager
|
||||
|
||||
// SubnetworkID identifies which subnetwork the DAG is associated
|
||||
// with.
|
||||
//
|
||||
// This field is required.
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
|
||||
// DatabaseContext is the context in which all database queries related to
|
||||
// this DAG are going to run.
|
||||
DatabaseContext *dbaccess.DatabaseContext
|
||||
}
|
||||
137
domain/blockdag/confirmations.go
Normal file
137
domain/blockdag/confirmations.go
Normal file
@@ -0,0 +1,137 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BlockConfirmationsByHash returns the confirmations number for a block with the
|
||||
// given hash. See blockConfirmations for further details.
|
||||
//
|
||||
// This function is safe for concurrent access
|
||||
func (dag *BlockDAG) BlockConfirmationsByHash(hash *daghash.Hash) (uint64, error) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
return dag.BlockConfirmationsByHashNoLock(hash)
|
||||
}
|
||||
|
||||
// BlockConfirmationsByHashNoLock is lock free version of BlockConfirmationsByHash
|
||||
//
|
||||
// This function is unsafe for concurrent access.
|
||||
func (dag *BlockDAG) BlockConfirmationsByHashNoLock(hash *daghash.Hash) (uint64, error) {
|
||||
if hash.IsEqual(&daghash.ZeroHash) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return 0, errors.Errorf("block %s is unknown", hash)
|
||||
}
|
||||
|
||||
return dag.blockConfirmations(node)
|
||||
}
|
||||
|
||||
// UTXOConfirmations returns the confirmations for the given outpoint, if it exists
|
||||
// in the DAG's UTXO set.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) UTXOConfirmations(outpoint *domainmessage.Outpoint) (uint64, bool) {
|
||||
dag.dagLock.RLock()
|
||||
defer dag.dagLock.RUnlock()
|
||||
|
||||
utxoEntry, ok := dag.GetUTXOEntry(*outpoint)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
confirmations := dag.SelectedTipBlueScore() - utxoEntry.BlockBlueScore() + 1
|
||||
|
||||
return confirmations, true
|
||||
}
|
||||
|
||||
// blockConfirmations returns the current confirmations number of the given node
|
||||
// The confirmations number is defined as follows:
|
||||
// * If the node is in the selected tip red set -> 0
|
||||
// * If the node is the selected tip -> 1
|
||||
// * Otherwise -> selectedTip.blueScore - acceptingBlock.blueScore + 2
|
||||
func (dag *BlockDAG) blockConfirmations(node *blockNode) (uint64, error) {
|
||||
acceptingBlock, err := dag.acceptingBlock(node)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// if acceptingBlock is nil, the node is red
|
||||
if acceptingBlock == nil {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
return dag.selectedTip().blueScore - acceptingBlock.blueScore + 1, nil
|
||||
}
|
||||
|
||||
// acceptingBlock finds the node in the selected-parent chain that had accepted
|
||||
// the given node
|
||||
func (dag *BlockDAG) acceptingBlock(node *blockNode) (*blockNode, error) {
|
||||
// Return an error if the node is the virtual block
|
||||
if node == &dag.virtual.blockNode {
|
||||
return nil, errors.New("cannot get acceptingBlock for virtual")
|
||||
}
|
||||
|
||||
// If the node is a chain-block itself, the accepting block is its chain-child
|
||||
isNodeInSelectedParentChain, err := dag.IsInSelectedParentChain(node.hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isNodeInSelectedParentChain {
|
||||
if len(node.children) == 0 {
|
||||
// If the node is the selected tip, it doesn't have an accepting block
|
||||
return nil, nil
|
||||
}
|
||||
for child := range node.children {
|
||||
isChildInSelectedParentChain, err := dag.IsInSelectedParentChain(child.hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isChildInSelectedParentChain {
|
||||
return child, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.Errorf("chain block %s does not have a chain child", node.hash)
|
||||
}
|
||||
|
||||
// Find the only chain block that may contain the node in its blues
|
||||
candidateAcceptingBlock := dag.oldestChainBlockWithBlueScoreGreaterThan(node.blueScore)
|
||||
|
||||
// if no candidate is found, it means that the node has same or more
|
||||
// blue score than the selected tip and is found in its anticone, so
|
||||
// it doesn't have an accepting block
|
||||
if candidateAcceptingBlock == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// candidateAcceptingBlock is the accepting block only if it actually contains
|
||||
// the node in its blues
|
||||
for _, blue := range candidateAcceptingBlock.blues {
|
||||
if blue == node {
|
||||
return candidateAcceptingBlock, nil
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, the node is red or in the selected tip anticone, and
|
||||
// doesn't have an accepting block
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// oldestChainBlockWithBlueScoreGreaterThan finds the oldest chain block with a blue score
|
||||
// greater than blueScore. If no such block exists, this method returns nil
|
||||
func (dag *BlockDAG) oldestChainBlockWithBlueScoreGreaterThan(blueScore uint64) *blockNode {
|
||||
chainBlockIndex, ok := util.SearchSlice(len(dag.virtual.selectedParentChainSlice), func(i int) bool {
|
||||
selectedPathNode := dag.virtual.selectedParentChainSlice[i]
|
||||
return selectedPathNode.blueScore > blueScore
|
||||
})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
return dag.virtual.selectedParentChainSlice[chainBlockIndex]
|
||||
}
|
||||
432
domain/blockdag/dag.go
Normal file
432
domain/blockdag/dag.go
Normal file
@@ -0,0 +1,432 @@
|
||||
// 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/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// BlockDAG provides functions for working with the kaspa block DAG.
|
||||
// It includes functionality such as rejecting duplicate blocks, ensuring blocks
|
||||
// follow all rules, and orphan handling.
|
||||
type BlockDAG struct {
|
||||
// The following fields are set when the instance is created and can't
|
||||
// be changed afterwards, so there is no need to protect them with a
|
||||
// separate mutex.
|
||||
Params *dagconfig.Params
|
||||
databaseContext *dbaccess.DatabaseContext
|
||||
timeSource TimeSource
|
||||
sigCache *txscript.SigCache
|
||||
indexManager IndexManager
|
||||
genesis *blockNode
|
||||
|
||||
// The following fields are calculated based upon the provided DAG
|
||||
// parameters. They are also set when the instance is created and
|
||||
// can't be changed afterwards, so there is no need to protect them with
|
||||
// a separate mutex.
|
||||
difficultyAdjustmentWindowSize uint64
|
||||
TimestampDeviationTolerance uint64
|
||||
|
||||
// powMaxBits defines the highest allowed proof of work value for a
|
||||
// block in compact form.
|
||||
powMaxBits uint32
|
||||
|
||||
// dagLock protects concurrent access to the vast majority of the
|
||||
// fields in this struct below this point.
|
||||
dagLock sync.RWMutex
|
||||
|
||||
// index and virtual are related to the memory block index. They both
|
||||
// have their own locks, however they are often also protected by the
|
||||
// DAG lock to help prevent logic races when blocks are being processed.
|
||||
|
||||
// index houses the entire block index in memory. The block index is
|
||||
// a tree-shaped structure.
|
||||
index *blockIndex
|
||||
|
||||
// blockCount holds the number of blocks in the DAG
|
||||
blockCount uint64
|
||||
|
||||
// virtual tracks the current tips.
|
||||
virtual *virtualBlock
|
||||
|
||||
// subnetworkID holds the subnetwork ID of the DAG
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
|
||||
// These fields are related to handling of orphan blocks. They are
|
||||
// protected by a combination of the DAG lock and the orphan lock.
|
||||
orphanLock sync.RWMutex
|
||||
orphans map[daghash.Hash]*orphanBlock
|
||||
prevOrphans map[daghash.Hash][]*orphanBlock
|
||||
newestOrphan *orphanBlock
|
||||
|
||||
// delayedBlocks is a list of all delayed blocks. We are maintaining this
|
||||
// list for the case where a new block with a valid timestamp points to a delayed block.
|
||||
// In that case we will delay the processing of the child block so it would be processed
|
||||
// after its parent.
|
||||
delayedBlocks map[daghash.Hash]*delayedBlock
|
||||
delayedBlocksQueue delayedBlocksHeap
|
||||
|
||||
// The notifications field stores a slice of callbacks to be executed on
|
||||
// certain blockDAG events.
|
||||
notificationsLock sync.RWMutex
|
||||
notifications []NotificationCallback
|
||||
|
||||
lastFinalityPoint *blockNode
|
||||
|
||||
utxoDiffStore *utxoDiffStore
|
||||
multisetStore *multisetStore
|
||||
|
||||
reachabilityTree *reachabilityTree
|
||||
|
||||
recentBlockProcessingTimestamps []mstime.Time
|
||||
startTime mstime.Time
|
||||
}
|
||||
|
||||
// New returns a BlockDAG instance using the provided configuration details.
|
||||
func New(config *Config) (*BlockDAG, error) {
|
||||
params := config.DAGParams
|
||||
|
||||
dag := &BlockDAG{
|
||||
Params: params,
|
||||
databaseContext: config.DatabaseContext,
|
||||
timeSource: config.TimeSource,
|
||||
sigCache: config.SigCache,
|
||||
indexManager: config.IndexManager,
|
||||
difficultyAdjustmentWindowSize: params.DifficultyAdjustmentWindowSize,
|
||||
TimestampDeviationTolerance: params.TimestampDeviationTolerance,
|
||||
powMaxBits: util.BigToCompact(params.PowMax),
|
||||
index: newBlockIndex(params),
|
||||
orphans: make(map[daghash.Hash]*orphanBlock),
|
||||
prevOrphans: make(map[daghash.Hash][]*orphanBlock),
|
||||
delayedBlocks: make(map[daghash.Hash]*delayedBlock),
|
||||
delayedBlocksQueue: newDelayedBlocksHeap(),
|
||||
blockCount: 0,
|
||||
subnetworkID: config.SubnetworkID,
|
||||
startTime: mstime.Now(),
|
||||
}
|
||||
|
||||
dag.virtual = newVirtualBlock(dag, nil)
|
||||
dag.utxoDiffStore = newUTXODiffStore(dag)
|
||||
dag.multisetStore = newMultisetStore(dag)
|
||||
dag.reachabilityTree = newReachabilityTree(dag)
|
||||
|
||||
// Initialize the DAG state from the passed database. When the db
|
||||
// does not yet contain any DAG state, both it and the DAG state
|
||||
// will be initialized to contain only the genesis block.
|
||||
err := dag.initDAGState()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Initialize and catch up all of the currently active optional indexes
|
||||
// as needed.
|
||||
if config.IndexManager != nil {
|
||||
err = config.IndexManager.Init(dag, dag.databaseContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
genesis, ok := dag.index.LookupNode(params.GenesisHash)
|
||||
|
||||
if !ok {
|
||||
genesisBlock := util.NewBlock(dag.Params.GenesisBlock)
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(genesisBlock, BFNone)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isDelayed {
|
||||
return nil, errors.New("genesis block shouldn't be in the future")
|
||||
}
|
||||
if isOrphan {
|
||||
return nil, errors.New("genesis block is unexpectedly orphan")
|
||||
}
|
||||
genesis, ok = dag.index.LookupNode(params.GenesisHash)
|
||||
if !ok {
|
||||
return nil, errors.New("genesis is not found in the DAG after it was proccessed")
|
||||
}
|
||||
}
|
||||
|
||||
// Save a reference to the genesis block.
|
||||
dag.genesis = genesis
|
||||
|
||||
selectedTip := dag.selectedTip()
|
||||
log.Infof("DAG state (blue score %d, hash %s)",
|
||||
selectedTip.blueScore, selectedTip.hash)
|
||||
|
||||
return dag, nil
|
||||
}
|
||||
|
||||
// Lock locks the DAG for writing.
|
||||
func (dag *BlockDAG) Lock() {
|
||||
dag.dagLock.Lock()
|
||||
}
|
||||
|
||||
// Unlock unlocks the DAG for writing.
|
||||
func (dag *BlockDAG) Unlock() {
|
||||
dag.dagLock.Unlock()
|
||||
}
|
||||
|
||||
// RLock locks the DAG for reading.
|
||||
func (dag *BlockDAG) RLock() {
|
||||
dag.dagLock.RLock()
|
||||
}
|
||||
|
||||
// RUnlock unlocks the DAG for reading.
|
||||
func (dag *BlockDAG) RUnlock() {
|
||||
dag.dagLock.RUnlock()
|
||||
}
|
||||
|
||||
// Now returns the adjusted time according to
|
||||
// dag.timeSource. See TimeSource.Now for
|
||||
// more details.
|
||||
func (dag *BlockDAG) Now() mstime.Time {
|
||||
return dag.timeSource.Now()
|
||||
}
|
||||
|
||||
// selectedTip returns the current selected tip for the DAG.
|
||||
// It will return nil if there is no tip.
|
||||
func (dag *BlockDAG) selectedTip() *blockNode {
|
||||
return dag.virtual.selectedParent
|
||||
}
|
||||
|
||||
// SelectedTipHeader returns the header of the current selected tip for the DAG.
|
||||
// It will return nil if there is no tip.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) SelectedTipHeader() *domainmessage.BlockHeader {
|
||||
selectedTip := dag.selectedTip()
|
||||
if selectedTip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return selectedTip.Header()
|
||||
}
|
||||
|
||||
// SelectedTipHash returns the hash of the current selected tip for the DAG.
|
||||
// It will return nil if there is no tip.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) SelectedTipHash() *daghash.Hash {
|
||||
selectedTip := dag.selectedTip()
|
||||
if selectedTip == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return selectedTip.hash
|
||||
}
|
||||
|
||||
// UTXOSet returns the DAG's UTXO set
|
||||
func (dag *BlockDAG) UTXOSet() *FullUTXOSet {
|
||||
return dag.virtual.utxoSet
|
||||
}
|
||||
|
||||
// CalcPastMedianTime returns the past median time of the DAG.
|
||||
func (dag *BlockDAG) CalcPastMedianTime() mstime.Time {
|
||||
return dag.virtual.tips().bluest().PastMedianTime(dag)
|
||||
}
|
||||
|
||||
// GetUTXOEntry returns the requested unspent transaction output. The returned
|
||||
// instance must be treated as immutable since it is shared by all callers.
|
||||
//
|
||||
// This function is safe for concurrent access. However, the returned entry (if
|
||||
// any) is NOT.
|
||||
func (dag *BlockDAG) GetUTXOEntry(outpoint domainmessage.Outpoint) (*UTXOEntry, bool) {
|
||||
return dag.virtual.utxoSet.get(outpoint)
|
||||
}
|
||||
|
||||
// BlueScoreByBlockHash returns the blue score of a block with the given hash.
|
||||
func (dag *BlockDAG) BlueScoreByBlockHash(hash *daghash.Hash) (uint64, error) {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return 0, errors.Errorf("block %s is unknown", hash)
|
||||
}
|
||||
|
||||
return node.blueScore, nil
|
||||
}
|
||||
|
||||
// BluesByBlockHash returns the blues of the block for the given hash.
|
||||
func (dag *BlockDAG) BluesByBlockHash(hash *daghash.Hash) ([]*daghash.Hash, error) {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("block %s is unknown", hash)
|
||||
}
|
||||
|
||||
hashes := make([]*daghash.Hash, len(node.blues))
|
||||
for i, blue := range node.blues {
|
||||
hashes[i] = blue.hash
|
||||
}
|
||||
|
||||
return hashes, nil
|
||||
}
|
||||
|
||||
// SelectedTipBlueScore returns the blue score of the selected tip.
|
||||
func (dag *BlockDAG) SelectedTipBlueScore() uint64 {
|
||||
return dag.selectedTip().blueScore
|
||||
}
|
||||
|
||||
// VirtualBlueScore returns the blue score of the current virtual block
|
||||
func (dag *BlockDAG) VirtualBlueScore() uint64 {
|
||||
return dag.virtual.blueScore
|
||||
}
|
||||
|
||||
// BlockCount returns the number of blocks in the DAG
|
||||
func (dag *BlockDAG) BlockCount() uint64 {
|
||||
return dag.blockCount
|
||||
}
|
||||
|
||||
// TipHashes returns the hashes of the DAG's tips
|
||||
func (dag *BlockDAG) TipHashes() []*daghash.Hash {
|
||||
return dag.virtual.tips().hashes()
|
||||
}
|
||||
|
||||
// HeaderByHash returns the block header identified by the given hash or an
|
||||
// error if it doesn't exist.
|
||||
func (dag *BlockDAG) HeaderByHash(hash *daghash.Hash) (*domainmessage.BlockHeader, error) {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
err := errors.Errorf("block %s is not known", hash)
|
||||
return &domainmessage.BlockHeader{}, err
|
||||
}
|
||||
|
||||
return node.Header(), nil
|
||||
}
|
||||
|
||||
// ChildHashesByHash returns the child hashes of the block with the given hash in the
|
||||
// DAG.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) ChildHashesByHash(hash *daghash.Hash) ([]*daghash.Hash, error) {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
str := fmt.Sprintf("block %s is not in the DAG", hash)
|
||||
return nil, ErrNotInDAG(str)
|
||||
|
||||
}
|
||||
|
||||
return node.children.hashes(), nil
|
||||
}
|
||||
|
||||
// SelectedParentHash returns the selected parent hash of the block with the given hash in the
|
||||
// DAG.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) SelectedParentHash(blockHash *daghash.Hash) (*daghash.Hash, error) {
|
||||
node, ok := dag.index.LookupNode(blockHash)
|
||||
if !ok {
|
||||
str := fmt.Sprintf("block %s is not in the DAG", blockHash)
|
||||
return nil, ErrNotInDAG(str)
|
||||
|
||||
}
|
||||
|
||||
if node.selectedParent == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return node.selectedParent.hash, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) isInPast(this *blockNode, other *blockNode) (bool, error) {
|
||||
return dag.reachabilityTree.isInPast(this, other)
|
||||
}
|
||||
|
||||
// GetTopHeaders returns the top domainmessage.MaxBlockHeadersPerMsg block headers ordered by blue score.
|
||||
func (dag *BlockDAG) GetTopHeaders(highHash *daghash.Hash, maxHeaders uint64) ([]*domainmessage.BlockHeader, error) {
|
||||
highNode := &dag.virtual.blockNode
|
||||
if highHash != nil {
|
||||
var ok bool
|
||||
highNode, ok = dag.index.LookupNode(highHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Couldn't find the high hash %s in the dag", highHash)
|
||||
}
|
||||
}
|
||||
headers := make([]*domainmessage.BlockHeader, 0, highNode.blueScore)
|
||||
queue := newDownHeap()
|
||||
queue.pushSet(highNode.parents)
|
||||
|
||||
visited := newBlockSet()
|
||||
for i := uint32(0); queue.Len() > 0 && uint64(len(headers)) < maxHeaders; i++ {
|
||||
current := queue.pop()
|
||||
if !visited.contains(current) {
|
||||
visited.add(current)
|
||||
headers = append(headers, current.Header())
|
||||
queue.pushSet(current.parents)
|
||||
}
|
||||
}
|
||||
return headers, nil
|
||||
}
|
||||
|
||||
// ForEachHash runs the given fn on every hash that's currently known to
|
||||
// the DAG.
|
||||
//
|
||||
// This function is NOT safe for concurrent access. It is meant to be
|
||||
// used either on initialization or when the dag lock is held for reads.
|
||||
func (dag *BlockDAG) ForEachHash(fn func(hash daghash.Hash) error) error {
|
||||
for hash := range dag.index.index {
|
||||
err := fn(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// IsKnownBlock returns whether or not the DAG instance has the block represented
|
||||
// by the passed hash. This includes checking the various places a block can
|
||||
// be in, like part of the DAG or the orphan pool.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) IsKnownBlock(hash *daghash.Hash) bool {
|
||||
return dag.IsInDAG(hash) || dag.IsKnownOrphan(hash) || dag.isKnownDelayedBlock(hash) || dag.IsKnownInvalid(hash)
|
||||
}
|
||||
|
||||
// AreKnownBlocks returns whether or not the DAG instances has all blocks represented
|
||||
// by the passed hashes. This includes checking the various places a block can
|
||||
// be in, like part of the DAG or the orphan pool.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) AreKnownBlocks(hashes []*daghash.Hash) bool {
|
||||
for _, hash := range hashes {
|
||||
haveBlock := dag.IsKnownBlock(hash)
|
||||
if !haveBlock {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsKnownInvalid returns whether the passed hash is known to be an invalid block.
|
||||
// Note that if the block is not found this method will return false.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) IsKnownInvalid(hash *daghash.Hash) bool {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return dag.index.NodeStatus(node).KnownInvalid()
|
||||
}
|
||||
@@ -6,9 +6,6 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@@ -16,12 +13,16 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/infrastructure/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/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
func TestBlockCount(t *testing.T) {
|
||||
@@ -207,10 +208,10 @@ func TestIsKnownBlock(t *testing.T) {
|
||||
{hash: dagconfig.SimnetParams.GenesisHash.String(), want: true},
|
||||
|
||||
// Block 3b should be present (as a second child of Block 2).
|
||||
{hash: "48a752afbe36ad66357f751f8dee4f75665d24e18f644d83a3409b398405b46b", want: true},
|
||||
{hash: "46314ca17e117b31b467fe1b26fd36c98ee83e750aa5e3b3c1c32870afbe5984", want: true},
|
||||
|
||||
// Block 100000 should be present (as an orphan).
|
||||
{hash: "65b20b048a074793ebfd1196e49341c8d194dabfc6b44a4fd0c607406e122baf", want: true},
|
||||
{hash: "732c891529619d43b5aeb3df42ba25dea483a8c0aded1cf585751ebabea28f29", want: true},
|
||||
|
||||
// Random hashes should not be available.
|
||||
{hash: "123", want: false},
|
||||
@@ -254,7 +255,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// Create a utxo view with a fake utxo for the inputs used in the
|
||||
// transactions created below. This utxo is added such that it has an
|
||||
// age of 4 blocks.
|
||||
msgTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{ScriptPubKey: nil, Value: 10}})
|
||||
msgTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, nil, []*domainmessage.TxOut{{ScriptPubKey: nil, Value: 10}})
|
||||
targetTx := util.NewTx(msgTx)
|
||||
utxoSet := NewFullUTXOSet()
|
||||
blueScore := uint64(numBlocksToGenerate) - 4
|
||||
@@ -269,7 +270,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// that the sequence lock heights are always calculated from the same
|
||||
// point of view that they were originally calculated from for a given
|
||||
// utxo. That is to say, the height prior to it.
|
||||
utxo := wire.Outpoint{
|
||||
utxo := domainmessage.Outpoint{
|
||||
TxID: *targetTx.ID(),
|
||||
Index: 0,
|
||||
}
|
||||
@@ -278,19 +279,19 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// Obtain the past median time from the PoV of the input created above.
|
||||
// The past median time for the input is the past median time from the PoV
|
||||
// of the block *prior* to the one that included it.
|
||||
medianTime := node.RelativeAncestor(5).PastMedianTime(dag).Unix()
|
||||
medianTime := node.RelativeAncestor(5).PastMedianTime(dag).UnixMilliseconds()
|
||||
|
||||
// The median time calculated from the PoV of the best block in the
|
||||
// test DAG. For unconfirmed inputs, this value will be used since
|
||||
// the MTP will be calculated from the PoV of the yet-to-be-mined
|
||||
// block.
|
||||
nextMedianTime := node.PastMedianTime(dag).Unix()
|
||||
nextMedianTime := node.PastMedianTime(dag).UnixMilliseconds()
|
||||
nextBlockBlueScore := int32(numBlocksToGenerate) + 1
|
||||
|
||||
// Add an additional transaction which will serve as our unconfirmed
|
||||
// output.
|
||||
unConfTx := wire.NewNativeMsgTx(wire.TxVersion, nil, []*wire.TxOut{{ScriptPubKey: nil, Value: 5}})
|
||||
unConfUtxo := wire.Outpoint{
|
||||
unConfTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, nil, []*domainmessage.TxOut{{ScriptPubKey: nil, Value: 5}})
|
||||
unConfUtxo := domainmessage.Outpoint{
|
||||
TxID: *unConfTx.TxID(),
|
||||
Index: 0,
|
||||
}
|
||||
@@ -302,9 +303,8 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tx *wire.MsgTx
|
||||
tx *domainmessage.MsgTx
|
||||
utxoSet UTXOSet
|
||||
mempool bool
|
||||
want *SequenceLock
|
||||
}{
|
||||
// A transaction with a single input with max sequence number.
|
||||
@@ -312,38 +312,37 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// should be disabled.
|
||||
{
|
||||
name: "single input, max sequence number",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: wire.MaxTxInSequenceNum}}, nil),
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: domainmessage.MaxTxInSequenceNum}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
Milliseconds: -1,
|
||||
BlockBlueScore: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with a single input whose lock time is
|
||||
// expressed in seconds. However, the specified lock time is
|
||||
// below the required floor for time based lock times since
|
||||
// they have time granularity of 512 seconds. As a result, the
|
||||
// seconds lock-time should be just before the median time of
|
||||
// they have time granularity of 524288 milliseconds. As a result, the
|
||||
// milliseconds lock-time should be just before the median time of
|
||||
// the targeted block.
|
||||
{
|
||||
name: "single input, seconds lock time below time granularity",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
|
||||
name: "single input, milliseconds lock time below time granularity",
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 2)}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime - 1,
|
||||
Milliseconds: medianTime - 1,
|
||||
BlockBlueScore: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with a single input whose lock time is
|
||||
// expressed in seconds. The number of seconds should be 1023
|
||||
// seconds after the median past time of the last block in the
|
||||
// chain.
|
||||
// expressed in seconds. The number of seconds should be 1048575
|
||||
// milliseconds after the median past time of the DAG.
|
||||
{
|
||||
name: "single input, 1023 seconds after median time",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1024)}}, nil),
|
||||
name: "single input, 1048575 milliseconds after median time",
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(true, 1048576)}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + 1023,
|
||||
Milliseconds: medianTime + 1048575,
|
||||
BlockBlueScore: -1,
|
||||
},
|
||||
},
|
||||
@@ -355,22 +354,22 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// latest lock that isn't disabled.
|
||||
{
|
||||
name: "multiple varied inputs",
|
||||
tx: wire.NewNativeMsgTx(1,
|
||||
[]*wire.TxIn{{
|
||||
tx: domainmessage.NewNativeMsgTx(1,
|
||||
[]*domainmessage.TxIn{{
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2560),
|
||||
Sequence: LockTimeToSequence(true, 2621440),
|
||||
}, {
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 4),
|
||||
}, {
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 5) |
|
||||
wire.SequenceLockTimeDisabled,
|
||||
domainmessage.SequenceLockTimeDisabled,
|
||||
}},
|
||||
nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
|
||||
Milliseconds: medianTime + (5 << domainmessage.SequenceLockTimeGranularity) - 1,
|
||||
BlockBlueScore: int64(prevUtxoBlueScore) + 3,
|
||||
},
|
||||
},
|
||||
@@ -380,10 +379,10 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// height of 2 meaning it can be included at height 3.
|
||||
{
|
||||
name: "single input, lock-time in blocks",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: utxo, Sequence: LockTimeToSequence(false, 3)}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
Milliseconds: -1,
|
||||
BlockBlueScore: int64(prevUtxoBlueScore) + 2,
|
||||
},
|
||||
},
|
||||
@@ -392,16 +391,16 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// be the time further in the future.
|
||||
{
|
||||
name: "two inputs, lock-times in seconds",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 5120),
|
||||
Sequence: LockTimeToSequence(true, 5242880),
|
||||
}, {
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2560),
|
||||
Sequence: LockTimeToSequence(true, 2621440),
|
||||
}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
|
||||
Milliseconds: medianTime + (10 << domainmessage.SequenceLockTimeGranularity) - 1,
|
||||
BlockBlueScore: -1,
|
||||
},
|
||||
},
|
||||
@@ -411,8 +410,8 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// indicating it can be included at height 11.
|
||||
{
|
||||
name: "two inputs, lock-times in blocks",
|
||||
tx: wire.NewNativeMsgTx(1,
|
||||
[]*wire.TxIn{{
|
||||
tx: domainmessage.NewNativeMsgTx(1,
|
||||
[]*domainmessage.TxIn{{
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 1),
|
||||
}, {
|
||||
@@ -422,7 +421,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
Milliseconds: -1,
|
||||
BlockBlueScore: int64(prevUtxoBlueScore) + 10,
|
||||
},
|
||||
},
|
||||
@@ -431,13 +430,13 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// further into the future for both inputs should be chosen.
|
||||
{
|
||||
name: "four inputs, two lock-times in time, two lock-times in blocks",
|
||||
tx: wire.NewNativeMsgTx(1,
|
||||
[]*wire.TxIn{{
|
||||
tx: domainmessage.NewNativeMsgTx(1,
|
||||
[]*domainmessage.TxIn{{
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2560),
|
||||
Sequence: LockTimeToSequence(true, 2621440),
|
||||
}, {
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 6656),
|
||||
Sequence: LockTimeToSequence(true, 6815744),
|
||||
}, {
|
||||
PreviousOutpoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 3),
|
||||
@@ -448,7 +447,7 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
nil),
|
||||
utxoSet: utxoSet,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
|
||||
Milliseconds: medianTime + (13 << domainmessage.SequenceLockTimeGranularity) - 1,
|
||||
BlockBlueScore: int64(prevUtxoBlueScore) + 8,
|
||||
},
|
||||
},
|
||||
@@ -460,11 +459,10 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// after that.
|
||||
{
|
||||
name: "single input, unconfirmed, lock-time in blocks",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
|
||||
tx: domainmessage.NewNativeMsgTx(1, []*domainmessage.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(false, 2)}}, nil),
|
||||
utxoSet: utxoSet,
|
||||
mempool: true,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
Milliseconds: -1,
|
||||
BlockBlueScore: int64(nextBlockBlueScore) + 1,
|
||||
},
|
||||
},
|
||||
@@ -472,12 +470,11 @@ func TestCalcSequenceLock(t *testing.T) {
|
||||
// a time based lock, so the lock time should be based off the
|
||||
// MTP of the *next* block.
|
||||
{
|
||||
name: "single input, unconfirmed, lock-time in seoncds",
|
||||
tx: wire.NewNativeMsgTx(1, []*wire.TxIn{{PreviousOutpoint: unConfUtxo, Sequence: LockTimeToSequence(true, 1024)}}, nil),
|
||||
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{
|
||||
Seconds: nextMedianTime + 1023,
|
||||
Milliseconds: nextMedianTime + 1048575,
|
||||
BlockBlueScore: -1,
|
||||
},
|
||||
},
|
||||
@@ -486,14 +483,14 @@ 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, test.mempool)
|
||||
seqLock, err := dag.CalcSequenceLock(utilTx, utxoSet)
|
||||
if err != nil {
|
||||
t.Fatalf("test '%s', unable to calc sequence lock: %v", test.name, err)
|
||||
}
|
||||
|
||||
if seqLock.Seconds != test.want.Seconds {
|
||||
t.Fatalf("test '%s' got %v seconds want %v seconds",
|
||||
test.name, seqLock.Seconds, test.want.Seconds)
|
||||
if seqLock.Milliseconds != test.want.Milliseconds {
|
||||
t.Fatalf("test '%s' got %v milliseconds want %v milliseconds",
|
||||
test.name, seqLock.Milliseconds, test.want.Milliseconds)
|
||||
}
|
||||
if seqLock.BlockBlueScore != test.want.BlockBlueScore {
|
||||
t.Fatalf("test '%s' got blue score of %v want blue score of %v ",
|
||||
@@ -519,31 +516,35 @@ func TestCalcPastMedianTime(t *testing.T) {
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
blockNumber uint32
|
||||
expectedSecondsSinceGenesis int64
|
||||
blockNumber uint32
|
||||
expectedMillisecondsSinceGenesis int64
|
||||
}{
|
||||
{
|
||||
blockNumber: 262,
|
||||
expectedSecondsSinceGenesis: 130,
|
||||
blockNumber: 262,
|
||||
expectedMillisecondsSinceGenesis: 130000,
|
||||
},
|
||||
{
|
||||
blockNumber: 270,
|
||||
expectedSecondsSinceGenesis: 138,
|
||||
blockNumber: 270,
|
||||
expectedMillisecondsSinceGenesis: 138000,
|
||||
},
|
||||
{
|
||||
blockNumber: 240,
|
||||
expectedSecondsSinceGenesis: 108,
|
||||
blockNumber: 240,
|
||||
expectedMillisecondsSinceGenesis: 108000,
|
||||
},
|
||||
{
|
||||
blockNumber: 5,
|
||||
expectedSecondsSinceGenesis: 0,
|
||||
blockNumber: 5,
|
||||
expectedMillisecondsSinceGenesis: 0,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
secondsSinceGenesis := nodes[test.blockNumber].PastMedianTime(dag).Unix() - dag.genesis.Header().Timestamp.Unix()
|
||||
if secondsSinceGenesis != test.expectedSecondsSinceGenesis {
|
||||
t.Errorf("TestCalcPastMedianTime: expected past median time of block %v to be %v seconds from genesis but got %v", test.blockNumber, test.expectedSecondsSinceGenesis, secondsSinceGenesis)
|
||||
millisecondsSinceGenesis := nodes[test.blockNumber].PastMedianTime(dag).UnixMilliseconds() -
|
||||
dag.genesis.Header().Timestamp.UnixMilliseconds()
|
||||
|
||||
if millisecondsSinceGenesis != test.expectedMillisecondsSinceGenesis {
|
||||
t.Errorf("TestCalcPastMedianTime: expected past median time of block %v to be %v milliseconds "+
|
||||
"from genesis but got %v",
|
||||
test.blockNumber, test.expectedMillisecondsSinceGenesis, millisecondsSinceGenesis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -553,18 +554,19 @@ func TestNew(t *testing.T) {
|
||||
|
||||
dbPath := filepath.Join(tempDir, "TestNew")
|
||||
_ = os.RemoveAll(dbPath)
|
||||
err := dbaccess.Open(dbPath)
|
||||
databaseContext, err := dbaccess.New(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
dbaccess.Close()
|
||||
databaseContext.Close()
|
||||
os.RemoveAll(dbPath)
|
||||
}()
|
||||
config := &Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
TimeSource: NewTimeSource(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
DatabaseContext: databaseContext,
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
TimeSource: NewTimeSource(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
}
|
||||
_, err = New(config)
|
||||
if err != nil {
|
||||
@@ -592,20 +594,21 @@ func TestAcceptingInInit(t *testing.T) {
|
||||
// Create a test database
|
||||
dbPath := filepath.Join(tempDir, "TestAcceptingInInit")
|
||||
_ = os.RemoveAll(dbPath)
|
||||
err := dbaccess.Open(dbPath)
|
||||
databaseContext, err := dbaccess.New(dbPath)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %s", err)
|
||||
}
|
||||
defer func() {
|
||||
dbaccess.Close()
|
||||
databaseContext.Close()
|
||||
os.RemoveAll(dbPath)
|
||||
}()
|
||||
|
||||
// Create a DAG to add the test block into
|
||||
config := &Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
TimeSource: NewTimeSource(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
DatabaseContext: databaseContext,
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
TimeSource: NewTimeSource(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
}
|
||||
dag, err := New(config)
|
||||
if err != nil {
|
||||
@@ -621,12 +624,15 @@ func TestAcceptingInInit(t *testing.T) {
|
||||
testBlock := blocks[1]
|
||||
|
||||
// Create a test blockNode with an unvalidated status
|
||||
genesisNode := dag.index.LookupNode(genesisBlock.Hash())
|
||||
genesisNode, ok := dag.index.LookupNode(genesisBlock.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("genesis block does not exist in the DAG")
|
||||
}
|
||||
testNode, _ := dag.newBlockNode(&testBlock.MsgBlock().Header, blockSetFromSlice(genesisNode))
|
||||
testNode.status = statusDataStored
|
||||
|
||||
// Manually add the test block to the database
|
||||
dbTx, err := dbaccess.NewTx()
|
||||
dbTx, err := databaseContext.NewTx()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open database "+
|
||||
"transaction: %s", err)
|
||||
@@ -659,7 +665,11 @@ func TestAcceptingInInit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Make sure that the test node's status is valid
|
||||
testNode = dag.index.LookupNode(testBlock.Hash())
|
||||
testNode, ok = dag.index.LookupNode(testBlock.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", testBlock.Hash())
|
||||
}
|
||||
|
||||
if testNode.status&statusValid == 0 {
|
||||
t.Fatalf("testNode is unexpectedly invalid")
|
||||
}
|
||||
@@ -688,8 +698,8 @@ func TestConfirmations(t *testing.T) {
|
||||
}
|
||||
|
||||
// Add a chain of blocks
|
||||
chainBlocks := make([]*wire.MsgBlock, 5)
|
||||
chainBlocks[0] = dag.dagParams.GenesisBlock
|
||||
chainBlocks := make([]*domainmessage.MsgBlock, 5)
|
||||
chainBlocks[0] = dag.Params.GenesisBlock
|
||||
for i := uint32(1); i < 5; i++ {
|
||||
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
|
||||
}
|
||||
@@ -708,7 +718,7 @@ func TestConfirmations(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
branchingBlocks := make([]*wire.MsgBlock, 2)
|
||||
branchingBlocks := make([]*domainmessage.MsgBlock, 2)
|
||||
// Add two branching blocks
|
||||
branchingBlocks[0] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[1])
|
||||
branchingBlocks[1] = prepareAndProcessBlockByParentMsgBlocks(t, dag, branchingBlocks[0])
|
||||
@@ -781,7 +791,7 @@ func TestAcceptingBlock(t *testing.T) {
|
||||
defer teardownFunc()
|
||||
dag.TestSetCoinbaseMaturity(0)
|
||||
|
||||
acceptingBlockByMsgBlock := func(block *wire.MsgBlock) (*blockNode, error) {
|
||||
acceptingBlockByMsgBlock := func(block *domainmessage.MsgBlock) (*blockNode, error) {
|
||||
node := nodeByMsgBlock(t, dag, block)
|
||||
return dag.acceptingBlock(node)
|
||||
}
|
||||
@@ -797,8 +807,8 @@ func TestAcceptingBlock(t *testing.T) {
|
||||
}
|
||||
|
||||
numChainBlocks := uint32(10)
|
||||
chainBlocks := make([]*wire.MsgBlock, numChainBlocks)
|
||||
chainBlocks[0] = dag.dagParams.GenesisBlock
|
||||
chainBlocks := make([]*domainmessage.MsgBlock, numChainBlocks)
|
||||
chainBlocks[0] = dag.Params.GenesisBlock
|
||||
for i := uint32(1); i <= numChainBlocks-1; i++ {
|
||||
chainBlocks[i] = prepareAndProcessBlockByParentMsgBlocks(t, dag, chainBlocks[i-1])
|
||||
}
|
||||
@@ -914,7 +924,7 @@ func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
|
||||
blockTime := dag.genesis.Header().Timestamp
|
||||
|
||||
flushUTXODiffStore := func() {
|
||||
dbTx, err := dbaccess.NewTx()
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to open database transaction: %s", err)
|
||||
}
|
||||
@@ -944,7 +954,7 @@ func testFinalizeNodesBelowFinalityPoint(t *testing.T, deleteDiffData bool) {
|
||||
flushUTXODiffStore()
|
||||
return node
|
||||
}
|
||||
finalityInterval := dag.dagParams.FinalityInterval
|
||||
finalityInterval := dag.FinalityInterval()
|
||||
nodes := make([]*blockNode, 0, finalityInterval)
|
||||
currentNode := dag.genesis
|
||||
nodes = append(nodes, currentNode)
|
||||
@@ -1017,11 +1027,11 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
invalidCbTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{}, []*wire.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
|
||||
invalidCbTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{}, []*domainmessage.TxOut{}, subnetworkid.SubnetworkIDCoinbase, 0, []byte{})
|
||||
txs := []*util.Tx{util.NewTx(invalidCbTx)}
|
||||
hashMerkleRoot := BuildHashMerkleTreeStore(txs).Root()
|
||||
invalidMsgBlock := wire.NewMsgBlock(
|
||||
wire.NewBlockHeader(
|
||||
invalidMsgBlock := domainmessage.NewMsgBlock(
|
||||
domainmessage.NewBlockHeader(
|
||||
1,
|
||||
[]*daghash.Hash{params.GenesisHash}, hashMerkleRoot,
|
||||
&daghash.Hash{},
|
||||
@@ -1045,16 +1055,16 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
"is an orphan\n")
|
||||
}
|
||||
|
||||
invalidBlockNode := dag.index.LookupNode(invalidBlock.Hash())
|
||||
if invalidBlockNode == nil {
|
||||
invalidBlockNode, ok := dag.index.LookupNode(invalidBlock.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("invalidBlockNode wasn't added to the block index as expected")
|
||||
}
|
||||
if invalidBlockNode.status&statusValidateFailed != statusValidateFailed {
|
||||
t.Fatalf("invalidBlockNode status to have %b flags raised (got: %b)", statusValidateFailed, invalidBlockNode.status)
|
||||
}
|
||||
|
||||
invalidMsgBlockChild := wire.NewMsgBlock(
|
||||
wire.NewBlockHeader(1, []*daghash.Hash{
|
||||
invalidMsgBlockChild := domainmessage.NewMsgBlock(
|
||||
domainmessage.NewBlockHeader(1, []*daghash.Hash{
|
||||
invalidBlock.Hash(),
|
||||
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
|
||||
)
|
||||
@@ -1074,16 +1084,16 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
t.Fatalf("ProcessBlock incorrectly returned invalidBlockChild " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
invalidBlockChildNode := dag.index.LookupNode(invalidBlockChild.Hash())
|
||||
if invalidBlockChildNode == nil {
|
||||
invalidBlockChildNode, ok := dag.index.LookupNode(invalidBlockChild.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("invalidBlockChild wasn't added to the block index as expected")
|
||||
}
|
||||
if invalidBlockChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
|
||||
t.Fatalf("invalidBlockNode status to have %b flags raised (got %b)", statusInvalidAncestor, invalidBlockChildNode.status)
|
||||
}
|
||||
|
||||
invalidMsgBlockGrandChild := wire.NewMsgBlock(
|
||||
wire.NewBlockHeader(1, []*daghash.Hash{
|
||||
invalidMsgBlockGrandChild := domainmessage.NewMsgBlock(
|
||||
domainmessage.NewBlockHeader(1, []*daghash.Hash{
|
||||
invalidBlockChild.Hash(),
|
||||
}, hashMerkleRoot, &daghash.Hash{}, &daghash.Hash{}, dag.genesis.bits, 0),
|
||||
)
|
||||
@@ -1102,8 +1112,8 @@ func TestDAGIndexFailedStatus(t *testing.T) {
|
||||
t.Fatalf("ProcessBlock incorrectly returned invalidBlockGrandChild " +
|
||||
"is an orphan\n")
|
||||
}
|
||||
invalidBlockGrandChildNode := dag.index.LookupNode(invalidBlockGrandChild.Hash())
|
||||
if invalidBlockGrandChildNode == nil {
|
||||
invalidBlockGrandChildNode, ok := dag.index.LookupNode(invalidBlockGrandChild.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("invalidBlockGrandChild wasn't added to the block index as expected")
|
||||
}
|
||||
if invalidBlockGrandChildNode.status&statusInvalidAncestor != statusInvalidAncestor {
|
||||
@@ -1111,22 +1121,7 @@ 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.TargetTimePerBlock*time.Duration(params.FinalityInterval) < isDAGCurrentMaxDiff {
|
||||
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 *wire.MsgBlock, expectedRuleErr error) {
|
||||
func testProcessBlockRuleError(t *testing.T, dag *BlockDAG, block *domainmessage.MsgBlock, expectedRuleErr error) {
|
||||
isOrphan, isDelayed, err := dag.ProcessBlock(util.NewBlock(block), BFNoPoWCheck)
|
||||
|
||||
err = checkRuleError(err, expectedRuleErr)
|
||||
@@ -1162,24 +1157,24 @@ func TestDoubleSpends(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build signature script: %s", err)
|
||||
}
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
|
||||
txIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
txOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: OpTrueScript,
|
||||
Value: uint64(1),
|
||||
}
|
||||
tx1 := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
|
||||
tx1 := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
|
||||
|
||||
doubleSpendTxOut := &wire.TxOut{
|
||||
doubleSpendTxOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: OpTrueScript,
|
||||
Value: uint64(2),
|
||||
}
|
||||
doubleSpendTx1 := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{doubleSpendTxOut})
|
||||
doubleSpendTx1 := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{doubleSpendTxOut})
|
||||
|
||||
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{tx1})
|
||||
blockWithTx1 := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*domainmessage.MsgTx{tx1})
|
||||
|
||||
// Check that a block will be rejected if it has a transaction that already exists in its past.
|
||||
anotherBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{blockWithTx1.BlockHash()}, nil)
|
||||
@@ -1214,7 +1209,7 @@ func TestDoubleSpends(t *testing.T) {
|
||||
|
||||
testProcessBlockRuleError(t, dag, blockWithDoubleSpendForTx1, ruleError(ErrMissingTxOut, ""))
|
||||
|
||||
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*wire.MsgTx{doubleSpendTx1})
|
||||
blockInAnticoneOfBlockWithTx1, err := PrepareBlockForTest(dag, []*daghash.Hash{fundingBlock.BlockHash()}, []*domainmessage.MsgTx{doubleSpendTx1})
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -1257,7 +1252,7 @@ func TestDoubleSpends(t *testing.T) {
|
||||
|
||||
func TestUTXOCommitment(t *testing.T) {
|
||||
// Create a new database and dag instance to run tests against.
|
||||
params := dagconfig.DevnetParams
|
||||
params := dagconfig.SimnetParams
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
dag, teardownFunc, err := DAGSetup("TestUTXOCommitment", true, Config{
|
||||
DAGParams: ¶ms,
|
||||
@@ -1269,7 +1264,7 @@ func TestUTXOCommitment(t *testing.T) {
|
||||
|
||||
resetExtraNonceForTest()
|
||||
|
||||
createTx := func(txToSpend *wire.MsgTx) *wire.MsgTx {
|
||||
createTx := func(txToSpend *domainmessage.MsgTx) *domainmessage.MsgTx {
|
||||
scriptPubKey, err := txscript.PayToScriptHashScript(OpTrueScript)
|
||||
if err != nil {
|
||||
t.Fatalf("TestUTXOCommitment: failed to build script pub key: %s", err)
|
||||
@@ -1278,16 +1273,16 @@ func TestUTXOCommitment(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("TestUTXOCommitment: failed to build signature script: %s", err)
|
||||
}
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{TxID: *txToSpend.TxID(), Index: 0},
|
||||
txIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: domainmessage.Outpoint{TxID: *txToSpend.TxID(), Index: 0},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
txOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: scriptPubKey,
|
||||
Value: uint64(1),
|
||||
}
|
||||
return wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
|
||||
return domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
|
||||
}
|
||||
|
||||
// Build the following DAG:
|
||||
@@ -1303,17 +1298,17 @@ func TestUTXOCommitment(t *testing.T) {
|
||||
|
||||
// Block C:
|
||||
txSpendBlockACoinbase := createTx(blockA.Transactions[0])
|
||||
blockCTxs := []*wire.MsgTx{txSpendBlockACoinbase}
|
||||
blockCTxs := []*domainmessage.MsgTx{txSpendBlockACoinbase}
|
||||
blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockA.BlockHash()}, blockCTxs)
|
||||
|
||||
// Block D:
|
||||
txSpendTxInBlockC := createTx(txSpendBlockACoinbase)
|
||||
blockDTxs := []*wire.MsgTx{txSpendTxInBlockC}
|
||||
blockDTxs := []*domainmessage.MsgTx{txSpendTxInBlockC}
|
||||
blockD := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash(), blockC.BlockHash()}, blockDTxs)
|
||||
|
||||
// Get the pastUTXO of blockD
|
||||
blockNodeD := dag.index.LookupNode(blockD.BlockHash())
|
||||
if blockNodeD == nil {
|
||||
blockNodeD, ok := dag.index.LookupNode(blockD.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("TestUTXOCommitment: blockNode for block D not found")
|
||||
}
|
||||
blockDPastUTXO, _, _, _ := dag.pastUTXO(blockNodeD)
|
||||
@@ -1372,8 +1367,8 @@ func TestPastUTXOMultiSet(t *testing.T) {
|
||||
blockC := PrepareAndProcessBlockForTest(t, dag, []*daghash.Hash{blockB.BlockHash()}, nil)
|
||||
|
||||
// Take blockC's selectedParentMultiset
|
||||
blockNodeC := dag.index.LookupNode(blockC.BlockHash())
|
||||
if blockNodeC == nil {
|
||||
blockNodeC, ok := dag.index.LookupNode(blockC.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("TestPastUTXOMultiSet: blockNode for blockC not found")
|
||||
}
|
||||
blockCSelectedParentMultiset, err := blockNodeC.selectedParentMultiset(dag)
|
||||
@@ -11,15 +11,15 @@ import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -28,19 +28,19 @@ var (
|
||||
byteOrder = binary.LittleEndian
|
||||
)
|
||||
|
||||
// errNotInDAG signifies that a block hash or height that is not in the
|
||||
// ErrNotInDAG signifies that a block hash that is not in the
|
||||
// DAG was requested.
|
||||
type errNotInDAG string
|
||||
type ErrNotInDAG string
|
||||
|
||||
// Error implements the error interface.
|
||||
func (e errNotInDAG) Error() string {
|
||||
func (e ErrNotInDAG) Error() string {
|
||||
return string(e)
|
||||
}
|
||||
|
||||
// isNotInDAGErr returns whether or not the passed error is an
|
||||
// errNotInDAG error.
|
||||
func isNotInDAGErr(err error) bool {
|
||||
var notInDAGErr errNotInDAG
|
||||
// IsNotInDAGErr returns whether or not the passed error is an
|
||||
// ErrNotInDAG error.
|
||||
func IsNotInDAGErr(err error) bool {
|
||||
var notInDAGErr ErrNotInDAG
|
||||
return errors.As(err, ¬InDAGErr)
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ func isNotInDAGErr(err error) bool {
|
||||
// keys will be iterated in an ascending order by the outpoint index.
|
||||
var outpointIndexByteOrder = binary.BigEndian
|
||||
|
||||
func serializeOutpoint(w io.Writer, outpoint *wire.Outpoint) error {
|
||||
func serializeOutpoint(w io.Writer, outpoint *domainmessage.Outpoint) error {
|
||||
_, err := w.Write(outpoint.TxID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -61,10 +61,10 @@ func serializeOutpoint(w io.Writer, outpoint *wire.Outpoint) error {
|
||||
var outpointSerializeSize = daghash.TxIDSize + 4
|
||||
|
||||
// deserializeOutpoint decodes an outpoint from the passed serialized byte
|
||||
// slice into a new wire.Outpoint using a format that is suitable for long-
|
||||
// slice into a new domainmessage.Outpoint using a format that is suitable for long-
|
||||
// term storage. This format is described in detail above.
|
||||
func deserializeOutpoint(r io.Reader) (*wire.Outpoint, error) {
|
||||
outpoint := &wire.Outpoint{}
|
||||
func deserializeOutpoint(r io.Reader) (*domainmessage.Outpoint, error) {
|
||||
outpoint := &domainmessage.Outpoint{}
|
||||
_, err := r.Read(outpoint.TxID[:])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -164,9 +164,9 @@ func saveDAGState(dbContext dbaccess.Context, state *dagState) error {
|
||||
// createDAGState initializes the DAG state to the
|
||||
// genesis block and the node's local subnetwork id.
|
||||
func (dag *BlockDAG) createDAGState(localSubnetworkID *subnetworkid.SubnetworkID) error {
|
||||
return saveDAGState(dbaccess.NoTx(), &dagState{
|
||||
TipHashes: []*daghash.Hash{dag.dagParams.GenesisHash},
|
||||
LastFinalityPoint: dag.dagParams.GenesisHash,
|
||||
return saveDAGState(dag.databaseContext, &dagState{
|
||||
TipHashes: []*daghash.Hash{dag.Params.GenesisHash},
|
||||
LastFinalityPoint: dag.Params.GenesisHash,
|
||||
LocalSubnetworkID: localSubnetworkID,
|
||||
})
|
||||
}
|
||||
@@ -177,7 +177,7 @@ func (dag *BlockDAG) createDAGState(localSubnetworkID *subnetworkid.SubnetworkID
|
||||
func (dag *BlockDAG) initDAGState() error {
|
||||
// Fetch the stored DAG state from the database. If it doesn't exist,
|
||||
// it means that kaspad is running for the first time.
|
||||
serializedDAGState, err := dbaccess.FetchDAGState(dbaccess.NoTx())
|
||||
serializedDAGState, err := dbaccess.FetchDAGState(dag.databaseContext)
|
||||
if dbaccess.IsNotFoundError(err) {
|
||||
// Initialize the database and the DAG state to the genesis block.
|
||||
return dag.createDAGState(dag.subnetworkID)
|
||||
@@ -209,13 +209,13 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
}
|
||||
|
||||
log.Debugf("Loading reachability data...")
|
||||
err = dag.reachabilityStore.init(dbaccess.NoTx())
|
||||
err = dag.reachabilityTree.init(dag.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Loading multiset data...")
|
||||
err = dag.multisetStore.init(dbaccess.NoTx())
|
||||
err = dag.multisetStore.init(dag.databaseContext)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -233,7 +233,12 @@ func (dag *BlockDAG) initDAGState() error {
|
||||
}
|
||||
|
||||
log.Debugf("Setting the last finality point...")
|
||||
dag.lastFinalityPoint = dag.index.LookupNode(dagState.LastFinalityPoint)
|
||||
var ok bool
|
||||
dag.lastFinalityPoint, ok = dag.index.LookupNode(dagState.LastFinalityPoint)
|
||||
if !ok {
|
||||
return errors.Errorf("finality point block %s "+
|
||||
"does not exist in the DAG", dagState.LastFinalityPoint)
|
||||
}
|
||||
dag.finalizeNodesBelowFinalityPoint(false)
|
||||
|
||||
log.Debugf("Processing unprocessed blockNodes...")
|
||||
@@ -258,7 +263,7 @@ func (dag *BlockDAG) validateLocalSubnetworkID(state *dagState) error {
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) initBlockIndex() (unprocessedBlockNodes []*blockNode, err error) {
|
||||
blockIndexCursor, err := dbaccess.BlockIndexCursor(dbaccess.NoTx())
|
||||
blockIndexCursor, err := dbaccess.BlockIndexCursor(dag.databaseContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -288,7 +293,7 @@ func (dag *BlockDAG) initBlockIndex() (unprocessedBlockNodes []*blockNode, err e
|
||||
}
|
||||
|
||||
if dag.blockCount == 0 {
|
||||
if !node.hash.IsEqual(dag.dagParams.GenesisHash) {
|
||||
if !node.hash.IsEqual(dag.Params.GenesisHash) {
|
||||
return nil, errors.Errorf("Expected "+
|
||||
"first entry in block index to be genesis block, "+
|
||||
"found %s", node.hash)
|
||||
@@ -312,7 +317,7 @@ func (dag *BlockDAG) initBlockIndex() (unprocessedBlockNodes []*blockNode, err e
|
||||
|
||||
func (dag *BlockDAG) initUTXOSet() (fullUTXOCollection utxoCollection, err error) {
|
||||
fullUTXOCollection = make(utxoCollection)
|
||||
cursor, err := dbaccess.UTXOSetCursor(dbaccess.NoTx())
|
||||
cursor, err := dbaccess.UTXOSetCursor(dag.databaseContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -348,8 +353,8 @@ func (dag *BlockDAG) initUTXOSet() (fullUTXOCollection utxoCollection, err error
|
||||
func (dag *BlockDAG) initVirtualBlockTips(state *dagState) error {
|
||||
tips := newBlockSet()
|
||||
for _, tipHash := range state.TipHashes {
|
||||
tip := dag.index.LookupNode(tipHash)
|
||||
if tip == nil {
|
||||
tip, ok := dag.index.LookupNode(tipHash)
|
||||
if !ok {
|
||||
return errors.Errorf("cannot find "+
|
||||
"DAG tip %s in block index", state.TipHashes)
|
||||
}
|
||||
@@ -363,7 +368,7 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
|
||||
for _, node := range unprocessedBlockNodes {
|
||||
// Check to see if the block exists in the block DB. If it
|
||||
// doesn't, the database has certainly been corrupted.
|
||||
blockExists, err := dbaccess.HasBlock(dbaccess.NoTx(), node.hash)
|
||||
blockExists, err := dbaccess.HasBlock(dag.databaseContext, node.hash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "HasBlock "+
|
||||
"for block %s failed: %s", node.hash, err)
|
||||
@@ -374,7 +379,7 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
|
||||
}
|
||||
|
||||
// Attempt to accept the block.
|
||||
block, err := fetchBlockByHash(dbaccess.NoTx(), node.hash)
|
||||
block, err := dag.fetchBlockByHash(node.hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -405,7 +410,7 @@ func (dag *BlockDAG) processUnprocessedBlockNodes(unprocessedBlockNodes []*block
|
||||
func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
buffer := bytes.NewReader(blockRow)
|
||||
|
||||
var header wire.BlockHeader
|
||||
var header domainmessage.BlockHeader
|
||||
err := header.Deserialize(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -416,7 +421,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
version: header.Version,
|
||||
bits: header.Bits,
|
||||
nonce: header.Nonce,
|
||||
timestamp: header.Timestamp.Unix(),
|
||||
timestamp: header.Timestamp.UnixMilliseconds(),
|
||||
hashMerkleRoot: header.HashMerkleRoot,
|
||||
acceptedIDMerkleRoot: header.AcceptedIDMerkleRoot,
|
||||
utxoCommitment: header.UTXOCommitment,
|
||||
@@ -426,8 +431,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
node.parents = newBlockSet()
|
||||
|
||||
for _, hash := range header.ParentHashes {
|
||||
parent := dag.index.LookupNode(hash)
|
||||
if parent == nil {
|
||||
parent, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("deserializeBlockNode: Could "+
|
||||
"not find parent %s for block %s", hash, header.BlockHash())
|
||||
}
|
||||
@@ -447,7 +452,11 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
|
||||
// Because genesis doesn't have selected parent, it's serialized as zero hash
|
||||
if !selectedParentHash.IsEqual(&daghash.ZeroHash) {
|
||||
node.selectedParent = dag.index.LookupNode(selectedParentHash)
|
||||
var ok bool
|
||||
node.selectedParent, ok = dag.index.LookupNode(selectedParentHash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("block %s does not exist in the DAG", selectedParentHash)
|
||||
}
|
||||
}
|
||||
|
||||
node.blueScore, err = binaryserializer.Uint64(buffer, byteOrder)
|
||||
@@ -455,7 +464,7 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bluesCount, err := wire.ReadVarInt(buffer)
|
||||
bluesCount, err := domainmessage.ReadVarInt(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -466,10 +475,15 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
if _, err := io.ReadFull(buffer, hash[:]); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
node.blues[i] = dag.index.LookupNode(hash)
|
||||
|
||||
var ok bool
|
||||
node.blues[i], ok = dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("block %s does not exist in the DAG", selectedParentHash)
|
||||
}
|
||||
}
|
||||
|
||||
bluesAnticoneSizesLen, err := wire.ReadVarInt(buffer)
|
||||
bluesAnticoneSizesLen, err := domainmessage.ReadVarInt(buffer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -484,8 +498,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
blue := dag.index.LookupNode(hash)
|
||||
if blue == nil {
|
||||
blue, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("couldn't find block with hash %s", hash)
|
||||
}
|
||||
node.bluesAnticoneSizes[blue] = dagconfig.KType(bluesAnticoneSize)
|
||||
@@ -496,8 +510,8 @@ func (dag *BlockDAG) deserializeBlockNode(blockRow []byte) (*blockNode, error) {
|
||||
|
||||
// fetchBlockByHash retrieves the raw block for the provided hash,
|
||||
// deserializes it, and returns a util.Block of it.
|
||||
func fetchBlockByHash(dbContext dbaccess.Context, hash *daghash.Hash) (*util.Block, error) {
|
||||
blockBytes, err := dbaccess.FetchBlock(dbContext, hash)
|
||||
func (dag *BlockDAG) fetchBlockByHash(hash *daghash.Hash) (*util.Block, error) {
|
||||
blockBytes, err := dbaccess.FetchBlock(dag.databaseContext, hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -513,7 +527,7 @@ func storeBlock(dbContext *dbaccess.TxContext, block *util.Block) error {
|
||||
}
|
||||
|
||||
func serializeBlockNode(node *blockNode) ([]byte, error) {
|
||||
w := bytes.NewBuffer(make([]byte, 0, wire.MaxBlockHeaderPayload+1))
|
||||
w := bytes.NewBuffer(make([]byte, 0, domainmessage.MaxBlockHeaderPayload+1))
|
||||
header := node.Header()
|
||||
err := header.Serialize(w)
|
||||
if err != nil {
|
||||
@@ -540,7 +554,7 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = wire.WriteVarInt(w, uint64(len(node.blues)))
|
||||
err = domainmessage.WriteVarInt(w, uint64(len(node.blues)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -552,7 +566,7 @@ func serializeBlockNode(node *blockNode) ([]byte, error) {
|
||||
}
|
||||
}
|
||||
|
||||
err = wire.WriteVarInt(w, uint64(len(node.bluesAnticoneSizes)))
|
||||
err = domainmessage.WriteVarInt(w, uint64(len(node.bluesAnticoneSizes)))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -590,13 +604,13 @@ func blockHashFromBlockIndexKey(BlockIndexKey []byte) (*daghash.Hash, error) {
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) BlockByHash(hash *daghash.Hash) (*util.Block, error) {
|
||||
// Lookup the block hash in block index and ensure it is in the DAG
|
||||
node := dag.index.LookupNode(hash)
|
||||
if node == nil {
|
||||
node, ok := dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
str := fmt.Sprintf("block %s is not in the DAG", hash)
|
||||
return nil, errNotInDAG(str)
|
||||
return nil, ErrNotInDAG(str)
|
||||
}
|
||||
|
||||
block, err := fetchBlockByHash(dbaccess.NoTx(), node.hash)
|
||||
block, err := dag.fetchBlockByHash(node.hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -625,7 +639,7 @@ func (dag *BlockDAG) BlockHashesFrom(lowHash *daghash.Hash, limit int) ([]*dagha
|
||||
}
|
||||
|
||||
key := blockIndexKey(lowHash, blueScore)
|
||||
cursor, err := dbaccess.BlockIndexCursorFrom(dbaccess.NoTx(), key)
|
||||
cursor, err := dbaccess.BlockIndexCursorFrom(dag.databaseContext, key)
|
||||
if dbaccess.IsNotFoundError(err) {
|
||||
return nil, errors.Wrapf(err, "block %s not in block index", lowHash)
|
||||
}
|
||||
@@ -648,3 +662,16 @@ 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
|
||||
}
|
||||
@@ -14,25 +14,25 @@ import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestErrNotInDAG ensures the functions related to errNotInDAG work
|
||||
// TestErrNotInDAG ensures the functions related to ErrNotInDAG work
|
||||
// as expected.
|
||||
func TestErrNotInDAG(t *testing.T) {
|
||||
errStr := "no block at height 1 exists"
|
||||
err := error(errNotInDAG(errStr))
|
||||
err := error(ErrNotInDAG(errStr))
|
||||
|
||||
// Ensure the stringized output for the error is as expected.
|
||||
if err.Error() != errStr {
|
||||
t.Fatalf("errNotInDAG retuned unexpected error string - "+
|
||||
t.Fatalf("ErrNotInDAG retuned unexpected error string - "+
|
||||
"got %q, want %q", err.Error(), errStr)
|
||||
}
|
||||
|
||||
// Ensure error is detected as the correct type.
|
||||
if !isNotInDAGErr(err) {
|
||||
t.Fatalf("isNotInDAGErr did not detect as expected type")
|
||||
if !IsNotInDAGErr(err) {
|
||||
t.Fatalf("IsNotInDAGErr did not detect as expected type")
|
||||
}
|
||||
err = errors.New("something else")
|
||||
if isNotInDAGErr(err) {
|
||||
t.Fatalf("isNotInDAGErr detected incorrect type")
|
||||
if IsNotInDAGErr(err) {
|
||||
t.Fatalf("IsNotInDAGErr detected incorrect type")
|
||||
}
|
||||
}
|
||||
|
||||
95
domain/blockdag/delayed_blocks.go
Normal file
95
domain/blockdag/delayed_blocks.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// delayedBlock represents a block which has a delayed timestamp and will be processed at processTime
|
||||
type delayedBlock struct {
|
||||
block *util.Block
|
||||
processTime mstime.Time
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) isKnownDelayedBlock(hash *daghash.Hash) bool {
|
||||
_, exists := dag.delayedBlocks[*hash]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) addDelayedBlock(block *util.Block, delay time.Duration) error {
|
||||
processTime := dag.Now().Add(delay)
|
||||
log.Debugf("Adding block to delayed blocks queue (block hash: %s, process time: %s)", block.Hash().String(), processTime)
|
||||
delayedBlock := &delayedBlock{
|
||||
block: block,
|
||||
processTime: processTime,
|
||||
}
|
||||
|
||||
dag.delayedBlocks[*block.Hash()] = delayedBlock
|
||||
dag.delayedBlocksQueue.Push(delayedBlock)
|
||||
return dag.processDelayedBlocks()
|
||||
}
|
||||
|
||||
// processDelayedBlocks loops over all delayed blocks and processes blocks which are due.
|
||||
// This method is invoked after processing a block (ProcessBlock method).
|
||||
func (dag *BlockDAG) processDelayedBlocks() error {
|
||||
// Check if the delayed block with the earliest process time should be processed
|
||||
for dag.delayedBlocksQueue.Len() > 0 {
|
||||
earliestDelayedBlockProcessTime := dag.peekDelayedBlock().processTime
|
||||
if earliestDelayedBlockProcessTime.After(dag.Now()) {
|
||||
break
|
||||
}
|
||||
delayedBlock := dag.popDelayedBlock()
|
||||
_, _, err := dag.processBlockNoLock(delayedBlock.block, BFAfterDelay)
|
||||
if err != nil {
|
||||
log.Errorf("Error while processing delayed block (block %s): %s", delayedBlock.block.Hash().String(), err)
|
||||
// Rule errors should not be propagated as they refer only to the delayed block,
|
||||
// while this function runs in the context of another block
|
||||
if !errors.As(err, &RuleError{}) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
log.Debugf("Processed delayed block (block %s)", delayedBlock.block.Hash().String())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// popDelayedBlock removes the topmost (delayed block with earliest process time) of the queue and returns it.
|
||||
func (dag *BlockDAG) popDelayedBlock() *delayedBlock {
|
||||
delayedBlock := dag.delayedBlocksQueue.pop()
|
||||
delete(dag.delayedBlocks, *delayedBlock.block.Hash())
|
||||
return delayedBlock
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) peekDelayedBlock() *delayedBlock {
|
||||
return dag.delayedBlocksQueue.peek()
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) shouldBlockBeDelayed(block *util.Block) (delay time.Duration, isDelayed bool) {
|
||||
header := &block.MsgBlock().Header
|
||||
|
||||
maxTimestamp := dag.Now().Add(time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock)
|
||||
if header.Timestamp.After(maxTimestamp) {
|
||||
return header.Timestamp.Sub(maxTimestamp), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
32
domain/blockdag/delayed_blocks_test.go
Normal file
32
domain/blockdag/delayed_blocks_test.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestShouldBlockBeDelayed(t *testing.T) {
|
||||
// Create a new database and dag instance to run tests against.
|
||||
dag, teardownFunc, err := DAGSetup("TestShouldBlockBeDelayed", true, Config{
|
||||
DAGParams: &dagconfig.SimnetParams,
|
||||
})
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup dag instance: %v", err)
|
||||
return
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
blockInTheFuture := Block100000
|
||||
expectedDelay := 10 * time.Second
|
||||
deviationTolerance := time.Duration(dag.TimestampDeviationTolerance) * dag.Params.TargetTimePerBlock
|
||||
blockInTheFuture.Header.Timestamp = dag.Now().Add(deviationTolerance + expectedDelay)
|
||||
delay, isDelayed := dag.shouldBlockBeDelayed(util.NewBlock(&blockInTheFuture))
|
||||
if !isDelayed {
|
||||
t.Errorf("TestShouldBlockBeDelayed: block unexpectedly not delayed")
|
||||
}
|
||||
if delay != expectedDelay {
|
||||
t.Errorf("TestShouldBlockBeDelayed: expected %s delay but got %s", expectedDelay, delay)
|
||||
}
|
||||
}
|
||||
@@ -6,16 +6,16 @@ package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/bigintpool"
|
||||
"time"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
// requiredDifficulty calculates the required difficulty for a
|
||||
// block given its bluest parent.
|
||||
func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime time.Time) uint32 {
|
||||
func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime mstime.Time) uint32 {
|
||||
// Genesis block.
|
||||
if bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
|
||||
if dag.Params.DisableDifficultyAdjustment || bluestParent == nil || bluestParent.blueScore < dag.difficultyAdjustmentWindowSize+1 {
|
||||
return dag.powMaxBits
|
||||
}
|
||||
|
||||
@@ -34,7 +34,7 @@ func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime ti
|
||||
defer bigintpool.Release(newTarget)
|
||||
windowTimeStampDifference := bigintpool.Acquire(windowMaxTimeStamp - windowMinTimestamp)
|
||||
defer bigintpool.Release(windowTimeStampDifference)
|
||||
targetTimePerBlock := bigintpool.Acquire(dag.targetTimePerBlock)
|
||||
targetTimePerBlock := bigintpool.Acquire(dag.Params.TargetTimePerBlock.Milliseconds())
|
||||
defer bigintpool.Release(targetTimePerBlock)
|
||||
difficultyAdjustmentWindowSize := bigintpool.Acquire(int64(dag.difficultyAdjustmentWindowSize))
|
||||
defer bigintpool.Release(difficultyAdjustmentWindowSize)
|
||||
@@ -44,7 +44,7 @@ func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime ti
|
||||
Mul(newTarget, windowTimeStampDifference).
|
||||
Div(newTarget, targetTimePerBlock).
|
||||
Div(newTarget, difficultyAdjustmentWindowSize)
|
||||
if newTarget.Cmp(dag.dagParams.PowMax) > 0 {
|
||||
if newTarget.Cmp(dag.Params.PowMax) > 0 {
|
||||
return dag.powMaxBits
|
||||
}
|
||||
newTargetBits := util.BigToCompact(newTarget)
|
||||
@@ -55,7 +55,7 @@ func (dag *BlockDAG) requiredDifficulty(bluestParent *blockNode, newBlockTime ti
|
||||
// be built on top of the current tips.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) NextRequiredDifficulty(timestamp time.Time) uint32 {
|
||||
func (dag *BlockDAG) NextRequiredDifficulty(timestamp mstime.Time) uint32 {
|
||||
difficulty := dag.requiredDifficulty(dag.virtual.parents.bluest(), timestamp)
|
||||
return difficulty
|
||||
}
|
||||
@@ -5,7 +5,8 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -79,7 +80,7 @@ func TestCalcWork(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestDifficulty(t *testing.T) {
|
||||
params := dagconfig.SimnetParams
|
||||
params := dagconfig.MainnetParams
|
||||
params.K = 1
|
||||
params.DifficultyAdjustmentWindowSize = 264
|
||||
dag, teardownFunc, err := DAGSetup("TestDifficulty", true, Config{
|
||||
@@ -90,11 +91,11 @@ func TestDifficulty(t *testing.T) {
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
zeroTime := time.Unix(0, 0)
|
||||
addNode := func(parents blockSet, blockTime time.Time) *blockNode {
|
||||
zeroTime := mstime.Time{}
|
||||
addNode := func(parents blockSet, blockTime mstime.Time) *blockNode {
|
||||
bluestParent := parents.bluest()
|
||||
if blockTime == zeroTime {
|
||||
blockTime = time.Unix(bluestParent.timestamp, 0)
|
||||
if blockTime.IsZero() {
|
||||
blockTime = bluestParent.time()
|
||||
blockTime = blockTime.Add(params.TargetTimePerBlock)
|
||||
}
|
||||
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
|
||||
@@ -114,7 +115,11 @@ func TestDifficulty(t *testing.T) {
|
||||
if isOrphan {
|
||||
t.Fatalf("block was unexpectedly orphan")
|
||||
}
|
||||
return dag.index.LookupNode(block.BlockHash())
|
||||
node, ok := dag.index.LookupNode(block.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", block.BlockHash())
|
||||
}
|
||||
return node
|
||||
}
|
||||
tip := dag.genesis
|
||||
for i := uint64(0); i < dag.difficultyAdjustmentWindowSize; i++ {
|
||||
@@ -170,7 +175,7 @@ func TestDifficulty(t *testing.T) {
|
||||
sameBitsCount = 0
|
||||
}
|
||||
}
|
||||
slowBlockTime := time.Unix(tip.timestamp, 0)
|
||||
slowBlockTime := tip.time()
|
||||
slowBlockTime = slowBlockTime.Add(params.TargetTimePerBlock + time.Second)
|
||||
slowNode := addNode(blockSetFromSlice(tip), slowBlockTime)
|
||||
if slowNode.bits != tip.bits {
|
||||
@@ -28,11 +28,6 @@ const (
|
||||
// to a newer version.
|
||||
ErrBlockVersionTooOld
|
||||
|
||||
// ErrInvalidTime indicates the time in the passed block has a precision
|
||||
// that is more than one second. The DAG consensus rules require
|
||||
// timestamps to have a maximum precision of one second.
|
||||
ErrInvalidTime
|
||||
|
||||
// ErrTimeTooOld indicates the time is either before the median time of
|
||||
// the last several blocks per the DAG consensus rules.
|
||||
ErrTimeTooOld
|
||||
@@ -69,6 +64,9 @@ const (
|
||||
// the expected value.
|
||||
ErrBadUTXOCommitment
|
||||
|
||||
// ErrInvalidSubnetwork indicates the subnetwork is now allowed.
|
||||
ErrInvalidSubnetwork
|
||||
|
||||
// ErrFinalityPointTimeTooOld indicates a block has a timestamp before the
|
||||
// last finality point.
|
||||
ErrFinalityPointTimeTooOld
|
||||
@@ -208,6 +206,10 @@ const (
|
||||
// ErrDelayedBlockIsNotAllowed indicates that a block with a delayed timestamp was
|
||||
// submitted with BFDisallowDelay flag raised.
|
||||
ErrDelayedBlockIsNotAllowed
|
||||
|
||||
// ErrOrphanBlockIsNotAllowed indicates that an orphan block was submitted with
|
||||
// BFDisallowOrphans flag raised.
|
||||
ErrOrphanBlockIsNotAllowed
|
||||
)
|
||||
|
||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||
@@ -215,7 +217,6 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrDuplicateBlock: "ErrDuplicateBlock",
|
||||
ErrBlockMassTooHigh: "ErrBlockMassTooHigh",
|
||||
ErrBlockVersionTooOld: "ErrBlockVersionTooOld",
|
||||
ErrInvalidTime: "ErrInvalidTime",
|
||||
ErrTimeTooOld: "ErrTimeTooOld",
|
||||
ErrTimeTooNew: "ErrTimeTooNew",
|
||||
ErrNoParents: "ErrNoParents",
|
||||
@@ -257,6 +258,7 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||
ErrInvalidPayloadHash: "ErrInvalidPayloadHash",
|
||||
ErrInvalidParentsRelation: "ErrInvalidParentsRelation",
|
||||
ErrDelayedBlockIsNotAllowed: "ErrDelayedBlockIsNotAllowed",
|
||||
ErrOrphanBlockIsNotAllowed: "ErrOrphanBlockIsNotAllowed",
|
||||
}
|
||||
|
||||
// String returns the ErrorCode as a human-readable name.
|
||||
@@ -285,3 +287,7 @@ func (e RuleError) Error() string {
|
||||
func ruleError(c ErrorCode, desc string) error {
|
||||
return errors.WithStack(RuleError{ErrorCode: c, Description: desc})
|
||||
}
|
||||
|
||||
// ErrInvalidParameter signifies that an invalid parameter has been
|
||||
// supplied to one of the BlockDAG functions.
|
||||
var ErrInvalidParameter = errors.New("invalid parameter")
|
||||
@@ -17,7 +17,6 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||
{ErrDuplicateBlock, "ErrDuplicateBlock"},
|
||||
{ErrBlockMassTooHigh, "ErrBlockMassTooHigh"},
|
||||
{ErrBlockVersionTooOld, "ErrBlockVersionTooOld"},
|
||||
{ErrInvalidTime, "ErrInvalidTime"},
|
||||
{ErrTimeTooOld, "ErrTimeTooOld"},
|
||||
{ErrTimeTooNew, "ErrTimeTooNew"},
|
||||
{ErrNoParents, "ErrNoParents"},
|
||||
@@ -58,6 +57,7 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||
{ErrInvalidPayloadHash, "ErrInvalidPayloadHash"},
|
||||
{ErrInvalidParentsRelation, "ErrInvalidParentsRelation"},
|
||||
{ErrDelayedBlockIsNotAllowed, "ErrDelayedBlockIsNotAllowed"},
|
||||
{ErrOrphanBlockIsNotAllowed, "ErrOrphanBlockIsNotAllowed"},
|
||||
{0xffff, "Unknown ErrorCode (65535)"},
|
||||
}
|
||||
|
||||
@@ -2,22 +2,23 @@ package blockdag_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"math"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/testtools"
|
||||
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/mining"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"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/util"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// TestFinality checks that the finality mechanism works as expected.
|
||||
@@ -40,7 +41,7 @@ import (
|
||||
func TestFinality(t *testing.T) {
|
||||
params := dagconfig.SimnetParams
|
||||
params.K = 1
|
||||
params.FinalityInterval = 100
|
||||
params.FinalityDuration = 100 * params.TargetTimePerBlock
|
||||
dag, teardownFunc, err := blockdag.DAGSetup("TestFinality", true, blockdag.Config{
|
||||
DAGParams: ¶ms,
|
||||
})
|
||||
@@ -49,7 +50,7 @@ func TestFinality(t *testing.T) {
|
||||
}
|
||||
defer teardownFunc()
|
||||
buildNodeToDag := func(parentHashes []*daghash.Hash) (*util.Block, error) {
|
||||
msgBlock, err := mining.PrepareBlockForTest(dag, ¶ms, parentHashes, nil, false)
|
||||
msgBlock, err := mining.PrepareBlockForTest(dag, parentHashes, nil, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -74,7 +75,7 @@ func TestFinality(t *testing.T) {
|
||||
currentNode := genesis
|
||||
|
||||
// First we build a chain of params.FinalityInterval blocks for future use
|
||||
for i := uint64(0); i < params.FinalityInterval; i++ {
|
||||
for i := uint64(0); i < dag.FinalityInterval(); i++ {
|
||||
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
|
||||
if err != nil {
|
||||
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||
@@ -86,7 +87,7 @@ func TestFinality(t *testing.T) {
|
||||
// Now we build a new chain of 2 * params.FinalityInterval blocks, pointed to genesis, and
|
||||
// we expect the block with height 1 * params.FinalityInterval to be the last finality point
|
||||
currentNode = genesis
|
||||
for i := uint64(0); i < params.FinalityInterval; i++ {
|
||||
for i := uint64(0); i < dag.FinalityInterval(); i++ {
|
||||
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
|
||||
if err != nil {
|
||||
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||
@@ -95,7 +96,7 @@ func TestFinality(t *testing.T) {
|
||||
|
||||
expectedFinalityPoint := currentNode
|
||||
|
||||
for i := uint64(0); i < params.FinalityInterval; i++ {
|
||||
for i := uint64(0); i < dag.FinalityInterval(); i++ {
|
||||
currentNode, err = buildNodeToDag([]*daghash.Hash{currentNode.Hash()})
|
||||
if err != nil {
|
||||
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||
@@ -124,7 +125,7 @@ func TestFinality(t *testing.T) {
|
||||
t.Errorf("NextBlockCoinbaseTransaction: %s", err)
|
||||
}
|
||||
merkleRoot := blockdag.BuildHashMerkleTreeStore([]*util.Tx{fakeCoinbaseTx}).Root()
|
||||
beforeFinalityBlock := wire.NewMsgBlock(&wire.BlockHeader{
|
||||
beforeFinalityBlock := domainmessage.NewMsgBlock(&domainmessage.BlockHeader{
|
||||
Version: 0x10000000,
|
||||
ParentHashes: []*daghash.Hash{genesis.Hash()},
|
||||
HashMerkleRoot: merkleRoot,
|
||||
@@ -163,7 +164,7 @@ func TestFinality(t *testing.T) {
|
||||
}
|
||||
|
||||
// TestFinalityInterval tests that the finality interval is
|
||||
// smaller then wire.MaxInvPerMsg, so when a peer receives
|
||||
// smaller then domainmessage.MaxInvPerMsg, so when a peer receives
|
||||
// a getblocks message it should always be able to send
|
||||
// all the necessary invs.
|
||||
func TestFinalityInterval(t *testing.T) {
|
||||
@@ -175,9 +176,19 @@ func TestFinalityInterval(t *testing.T) {
|
||||
&dagconfig.SimnetParams,
|
||||
}
|
||||
for _, params := range netParams {
|
||||
if params.FinalityInterval > wire.MaxInvPerMsg {
|
||||
t.Errorf("FinalityInterval in %s should be lower or equal to wire.MaxInvPerMsg", params.Name)
|
||||
}
|
||||
func() {
|
||||
dag, teardownFunc, err := blockdag.DAGSetup("TestFinalityInterval", true, blockdag.Config{
|
||||
DAGParams: params,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to setup dag instance for %s: %v", params.Name, err)
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
if dag.FinalityInterval() > domainmessage.MaxInvPerMsg {
|
||||
t.Errorf("FinalityInterval in %s should be lower or equal to domainmessage.MaxInvPerMsg", params.Name)
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,6 +197,7 @@ func TestSubnetworkRegistry(t *testing.T) {
|
||||
params := dagconfig.SimnetParams
|
||||
params.K = 1
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
params.EnableNonNativeSubnetworks = true
|
||||
dag, teardownFunc, err := blockdag.DAGSetup("TestSubnetworkRegistry", true, blockdag.Config{
|
||||
DAGParams: ¶ms,
|
||||
})
|
||||
@@ -199,7 +211,7 @@ func TestSubnetworkRegistry(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("could not register network: %s", err)
|
||||
}
|
||||
limit, err := blockdag.GasLimit(subnetworkID)
|
||||
limit, err := dag.GasLimit(subnetworkID)
|
||||
if err != nil {
|
||||
t.Fatalf("could not retrieve gas limit: %s", err)
|
||||
}
|
||||
@@ -220,7 +232,7 @@ func TestChainedTransactions(t *testing.T) {
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
block1, err := mining.PrepareBlockForTest(dag, ¶ms, []*daghash.Hash{params.GenesisHash}, nil, false)
|
||||
block1, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{params.GenesisHash}, nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -241,34 +253,34 @@ func TestChainedTransactions(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build signature script: %s", err)
|
||||
}
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
|
||||
txIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
txOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: blockdag.OpTrueScript,
|
||||
Value: uint64(1),
|
||||
}
|
||||
tx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut})
|
||||
tx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut})
|
||||
|
||||
chainedTxIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{TxID: *tx.TxID(), Index: 0},
|
||||
chainedTxIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: domainmessage.Outpoint{TxID: *tx.TxID(), Index: 0},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
}
|
||||
|
||||
scriptPubKey, err := txscript.PayToScriptHashScript(blockdag.OpTrueScript)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build public key script: %s", err)
|
||||
}
|
||||
chainedTxOut := &wire.TxOut{
|
||||
chainedTxOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: scriptPubKey,
|
||||
Value: uint64(1),
|
||||
}
|
||||
chainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{chainedTxIn}, []*wire.TxOut{chainedTxOut})
|
||||
chainedTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{chainedTxIn}, []*domainmessage.TxOut{chainedTxOut})
|
||||
|
||||
block2, err := mining.PrepareBlockForTest(dag, ¶ms, []*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{tx}, false)
|
||||
block2, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*domainmessage.MsgTx{tx}, false)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -303,18 +315,18 @@ func TestChainedTransactions(t *testing.T) {
|
||||
t.Errorf("ProcessBlock: block2 got unexpectedly orphaned")
|
||||
}
|
||||
|
||||
nonChainedTxIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{TxID: *cbTx.TxID(), Index: 0},
|
||||
nonChainedTxIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: domainmessage.Outpoint{TxID: *cbTx.TxID(), Index: 0},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
}
|
||||
nonChainedTxOut := &wire.TxOut{
|
||||
nonChainedTxOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: scriptPubKey,
|
||||
Value: uint64(1),
|
||||
}
|
||||
nonChainedTx := wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{nonChainedTxIn}, []*wire.TxOut{nonChainedTxOut})
|
||||
nonChainedTx := domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{nonChainedTxIn}, []*domainmessage.TxOut{nonChainedTxOut})
|
||||
|
||||
block3, err := mining.PrepareBlockForTest(dag, ¶ms, []*daghash.Hash{block1.BlockHash()}, []*wire.MsgTx{nonChainedTx}, false)
|
||||
block3, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{block1.BlockHash()}, []*domainmessage.MsgTx{nonChainedTx}, false)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -351,27 +363,27 @@ func TestOrderInDiffFromAcceptanceData(t *testing.T) {
|
||||
|
||||
createBlock := func(previousBlock *util.Block) *util.Block {
|
||||
// Prepare a transaction that spends the previous block's coinbase transaction
|
||||
var txs []*wire.MsgTx
|
||||
var txs []*domainmessage.MsgTx
|
||||
if !previousBlock.IsGenesis() {
|
||||
previousCoinbaseTx := previousBlock.MsgBlock().Transactions[0]
|
||||
signatureScript, err := txscript.PayToScriptHashSignatureScript(blockdag.OpTrueScript, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to build signature script: %s", err)
|
||||
}
|
||||
txIn := &wire.TxIn{
|
||||
PreviousOutpoint: wire.Outpoint{TxID: *previousCoinbaseTx.TxID(), Index: 0},
|
||||
txIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: domainmessage.Outpoint{TxID: *previousCoinbaseTx.TxID(), Index: 0},
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
}
|
||||
txOut := &wire.TxOut{
|
||||
txOut := &domainmessage.TxOut{
|
||||
ScriptPubKey: blockdag.OpTrueScript,
|
||||
Value: uint64(1),
|
||||
}
|
||||
txs = append(txs, wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn}, []*wire.TxOut{txOut}))
|
||||
txs = append(txs, domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn}, []*domainmessage.TxOut{txOut}))
|
||||
}
|
||||
|
||||
// Create the block
|
||||
msgBlock, err := mining.PrepareBlockForTest(dag, ¶ms, []*daghash.Hash{previousBlock.Hash()}, txs, false)
|
||||
msgBlock, err := mining.PrepareBlockForTest(dag, []*daghash.Hash{previousBlock.Hash()}, txs, false)
|
||||
if err != nil {
|
||||
t.Fatalf("TestOrderInDiffFromAcceptanceData: Failed to prepare block: %s", err)
|
||||
}
|
||||
@@ -410,6 +422,7 @@ func TestGasLimit(t *testing.T) {
|
||||
params := dagconfig.SimnetParams
|
||||
params.K = 1
|
||||
params.BlockCoinbaseMaturity = 0
|
||||
params.EnableNonNativeSubnetworks = true
|
||||
dag, teardownFunc, err := blockdag.DAGSetup("TestSubnetworkRegistry", true, blockdag.Config{
|
||||
DAGParams: ¶ms,
|
||||
})
|
||||
@@ -425,9 +438,9 @@ func TestGasLimit(t *testing.T) {
|
||||
t.Fatalf("could not register network: %s", err)
|
||||
}
|
||||
|
||||
cbTxs := []*wire.MsgTx{}
|
||||
cbTxs := []*domainmessage.MsgTx{}
|
||||
for i := 0; i < 4; i++ {
|
||||
fundsBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), nil, false)
|
||||
fundsBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), nil, false)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -456,30 +469,30 @@ func TestGasLimit(t *testing.T) {
|
||||
t.Fatalf("Failed to build public key script: %s", err)
|
||||
}
|
||||
|
||||
tx1In := &wire.TxIn{
|
||||
PreviousOutpoint: *wire.NewOutpoint(cbTxs[0].TxID(), 0),
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
tx1In := &domainmessage.TxIn{
|
||||
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[0].TxID(), 0),
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
SignatureScript: signatureScript,
|
||||
}
|
||||
tx1Out := &wire.TxOut{
|
||||
tx1Out := &domainmessage.TxOut{
|
||||
Value: cbTxs[0].TxOut[0].Value,
|
||||
ScriptPubKey: scriptPubKey,
|
||||
}
|
||||
tx1 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx1In}, []*wire.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
|
||||
tx1 := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{tx1In}, []*domainmessage.TxOut{tx1Out}, subnetworkID, 10000, []byte{})
|
||||
|
||||
tx2In := &wire.TxIn{
|
||||
PreviousOutpoint: *wire.NewOutpoint(cbTxs[1].TxID(), 0),
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
tx2In := &domainmessage.TxIn{
|
||||
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[1].TxID(), 0),
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
SignatureScript: signatureScript,
|
||||
}
|
||||
tx2Out := &wire.TxOut{
|
||||
tx2Out := &domainmessage.TxOut{
|
||||
Value: cbTxs[1].TxOut[0].Value,
|
||||
ScriptPubKey: scriptPubKey,
|
||||
}
|
||||
tx2 := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{tx2In}, []*wire.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
|
||||
tx2 := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{tx2In}, []*domainmessage.TxOut{tx2Out}, subnetworkID, 10000, []byte{})
|
||||
|
||||
// Here we check that we can't process a block that has transactions that exceed the gas limit
|
||||
overLimitBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{tx1, tx2}, true)
|
||||
overLimitBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1, tx2}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -501,20 +514,20 @@ func TestGasLimit(t *testing.T) {
|
||||
t.Fatalf("ProcessBlock: overLimitBlock got unexpectedly orphan")
|
||||
}
|
||||
|
||||
overflowGasTxIn := &wire.TxIn{
|
||||
PreviousOutpoint: *wire.NewOutpoint(cbTxs[2].TxID(), 0),
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
overflowGasTxIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[2].TxID(), 0),
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
SignatureScript: signatureScript,
|
||||
}
|
||||
overflowGasTxOut := &wire.TxOut{
|
||||
overflowGasTxOut := &domainmessage.TxOut{
|
||||
Value: cbTxs[2].TxOut[0].Value,
|
||||
ScriptPubKey: scriptPubKey,
|
||||
}
|
||||
overflowGasTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{overflowGasTxIn}, []*wire.TxOut{overflowGasTxOut},
|
||||
overflowGasTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{overflowGasTxIn}, []*domainmessage.TxOut{overflowGasTxOut},
|
||||
subnetworkID, math.MaxUint64, []byte{})
|
||||
|
||||
// Here we check that we can't process a block that its transactions' gas overflows uint64
|
||||
overflowGasBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{tx1, overflowGasTx}, true)
|
||||
overflowGasBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1, overflowGasTx}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -536,19 +549,19 @@ func TestGasLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
nonExistentSubnetwork := &subnetworkid.SubnetworkID{123}
|
||||
nonExistentSubnetworkTxIn := &wire.TxIn{
|
||||
PreviousOutpoint: *wire.NewOutpoint(cbTxs[3].TxID(), 0),
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
nonExistentSubnetworkTxIn := &domainmessage.TxIn{
|
||||
PreviousOutpoint: *domainmessage.NewOutpoint(cbTxs[3].TxID(), 0),
|
||||
Sequence: domainmessage.MaxTxInSequenceNum,
|
||||
SignatureScript: signatureScript,
|
||||
}
|
||||
nonExistentSubnetworkTxOut := &wire.TxOut{
|
||||
nonExistentSubnetworkTxOut := &domainmessage.TxOut{
|
||||
Value: cbTxs[3].TxOut[0].Value,
|
||||
ScriptPubKey: scriptPubKey,
|
||||
}
|
||||
nonExistentSubnetworkTx := wire.NewSubnetworkMsgTx(wire.TxVersion, []*wire.TxIn{nonExistentSubnetworkTxIn},
|
||||
[]*wire.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})
|
||||
nonExistentSubnetworkTx := domainmessage.NewSubnetworkMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{nonExistentSubnetworkTxIn},
|
||||
[]*domainmessage.TxOut{nonExistentSubnetworkTxOut}, nonExistentSubnetwork, 1, []byte{})
|
||||
|
||||
nonExistentSubnetworkBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{nonExistentSubnetworkTx, overflowGasTx}, true)
|
||||
nonExistentSubnetworkBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{nonExistentSubnetworkTx, overflowGasTx}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
@@ -569,7 +582,7 @@ func TestGasLimit(t *testing.T) {
|
||||
}
|
||||
|
||||
// Here we check that we can process a block with a transaction that doesn't exceed the gas limit
|
||||
validBlock, err := mining.PrepareBlockForTest(dag, ¶ms, dag.TipHashes(), []*wire.MsgTx{tx1}, true)
|
||||
validBlock, err := mining.PrepareBlockForTest(dag, dag.TipHashes(), []*domainmessage.MsgTx{tx1}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("PrepareBlockForTest: %v", err)
|
||||
}
|
||||
113
domain/blockdag/finality.go
Normal file
113
domain/blockdag/finality.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// LastFinalityPointHash returns the hash of the last finality point
|
||||
func (dag *BlockDAG) LastFinalityPointHash() *daghash.Hash {
|
||||
if dag.lastFinalityPoint == nil {
|
||||
return nil
|
||||
}
|
||||
return dag.lastFinalityPoint.hash
|
||||
}
|
||||
|
||||
// FinalityInterval is the interval that determines the finality window of the DAG.
|
||||
func (dag *BlockDAG) FinalityInterval() uint64 {
|
||||
return uint64(dag.Params.FinalityDuration / dag.Params.TargetTimePerBlock)
|
||||
}
|
||||
|
||||
// checkFinalityViolation checks the new block does not violate the finality rules
|
||||
// specifically - the new block selectedParent chain should contain the old finality point.
|
||||
func (dag *BlockDAG) checkFinalityViolation(newNode *blockNode) error {
|
||||
// the genesis block can not violate finality rules
|
||||
if newNode.isGenesis() {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Because newNode doesn't have reachability data we
|
||||
// need to check if the last finality point is in the
|
||||
// selected parent chain of newNode.selectedParent, so
|
||||
// we explicitly check if newNode.selectedParent is
|
||||
// the finality point.
|
||||
if dag.lastFinalityPoint == newNode.selectedParent {
|
||||
return nil
|
||||
}
|
||||
|
||||
isInSelectedChain, err := dag.isInSelectedParentChainOf(dag.lastFinalityPoint, newNode.selectedParent)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isInSelectedChain {
|
||||
return ruleError(ErrFinality, "the last finality point is not in the selected parent chain of this block")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateFinalityPoint updates the dag's last finality point if necessary.
|
||||
func (dag *BlockDAG) updateFinalityPoint() {
|
||||
selectedTip := dag.selectedTip()
|
||||
// if the selected tip is the genesis block - it should be the new finality point
|
||||
if selectedTip.isGenesis() {
|
||||
dag.lastFinalityPoint = selectedTip
|
||||
return
|
||||
}
|
||||
// We are looking for a new finality point only if the new block's finality score is higher
|
||||
// by 2 than the existing finality point's
|
||||
if selectedTip.finalityScore(dag) < dag.lastFinalityPoint.finalityScore(dag)+2 {
|
||||
return
|
||||
}
|
||||
|
||||
var currentNode *blockNode
|
||||
for currentNode = selectedTip.selectedParent; ; currentNode = currentNode.selectedParent {
|
||||
// We look for the first node in the selected parent chain that has a higher finality score than the last finality point.
|
||||
if currentNode.selectedParent.finalityScore(dag) == dag.lastFinalityPoint.finalityScore(dag) {
|
||||
break
|
||||
}
|
||||
}
|
||||
dag.lastFinalityPoint = currentNode
|
||||
spawn("dag.finalizeNodesBelowFinalityPoint", func() {
|
||||
dag.finalizeNodesBelowFinalityPoint(true)
|
||||
})
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) finalizeNodesBelowFinalityPoint(deleteDiffData bool) {
|
||||
queue := make([]*blockNode, 0, len(dag.lastFinalityPoint.parents))
|
||||
for parent := range dag.lastFinalityPoint.parents {
|
||||
queue = append(queue, parent)
|
||||
}
|
||||
var nodesToDelete []*blockNode
|
||||
if deleteDiffData {
|
||||
nodesToDelete = make([]*blockNode, 0, dag.FinalityInterval())
|
||||
}
|
||||
for len(queue) > 0 {
|
||||
var current *blockNode
|
||||
current, queue = queue[0], queue[1:]
|
||||
if !current.isFinalized {
|
||||
current.isFinalized = true
|
||||
if deleteDiffData {
|
||||
nodesToDelete = append(nodesToDelete, current)
|
||||
}
|
||||
for parent := range current.parents {
|
||||
queue = append(queue, parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
if deleteDiffData {
|
||||
err := dag.utxoDiffStore.removeBlocksDiffData(dag.databaseContext, nodesToDelete)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Error removing diff data from utxoDiffStore: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// IsKnownFinalizedBlock returns whether the block is below the finality point.
|
||||
// IsKnownFinalizedBlock might be false-negative because node finality status is
|
||||
// updated in a separate goroutine. To get a definite answer if a block
|
||||
// is finalized or not, use dag.checkFinalityViolation.
|
||||
func (dag *BlockDAG) IsKnownFinalizedBlock(blockHash *daghash.Hash) bool {
|
||||
node, ok := dag.index.LookupNode(blockHash)
|
||||
return ok && node.isFinalized
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/pkg/errors"
|
||||
"sort"
|
||||
)
|
||||
@@ -57,7 +57,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
// newNode is always in the future of blueCandidate, so there's
|
||||
// no point in checking it.
|
||||
if chainBlock != newNode {
|
||||
if isAncestorOfBlueCandidate, err := dag.isAncestorOf(chainBlock, blueCandidate); err != nil {
|
||||
if isAncestorOfBlueCandidate, err := dag.isInPast(chainBlock, blueCandidate); err != nil {
|
||||
return nil, err
|
||||
} else if isAncestorOfBlueCandidate {
|
||||
break
|
||||
@@ -66,7 +66,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
|
||||
for _, block := range chainBlock.blues {
|
||||
// Skip blocks that exist in the past of blueCandidate.
|
||||
if isAncestorOfBlueCandidate, err := dag.isAncestorOf(block, blueCandidate); err != nil {
|
||||
if isAncestorOfBlueCandidate, err := dag.isInPast(block, blueCandidate); err != nil {
|
||||
return nil, err
|
||||
} else if isAncestorOfBlueCandidate {
|
||||
continue
|
||||
@@ -78,13 +78,13 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
}
|
||||
candidateAnticoneSize++
|
||||
|
||||
if candidateAnticoneSize > dag.dagParams.K {
|
||||
if candidateAnticoneSize > dag.Params.K {
|
||||
// k-cluster violation: The candidate's blue anticone exceeded k
|
||||
possiblyBlue = false
|
||||
break
|
||||
}
|
||||
|
||||
if candidateBluesAnticoneSizes[block] == dag.dagParams.K {
|
||||
if candidateBluesAnticoneSizes[block] == dag.Params.K {
|
||||
// k-cluster violation: A block in candidate's blue anticone already
|
||||
// has k blue blocks in its own anticone
|
||||
possiblyBlue = false
|
||||
@@ -93,7 +93,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
|
||||
// This is a sanity check that validates that a blue
|
||||
// block's blue anticone is not already larger than K.
|
||||
if candidateBluesAnticoneSizes[block] > dag.dagParams.K {
|
||||
if candidateBluesAnticoneSizes[block] > dag.Params.K {
|
||||
return nil, errors.New("found blue anticone size larger than k")
|
||||
}
|
||||
}
|
||||
@@ -109,7 +109,7 @@ func (dag *BlockDAG) ghostdag(newNode *blockNode) (selectedParentAnticone []*blo
|
||||
|
||||
// The maximum length of node.blues can be K+1 because
|
||||
// it contains the selected parent.
|
||||
if dagconfig.KType(len(newNode.blues)) == dag.dagParams.K+1 {
|
||||
if dagconfig.KType(len(newNode.blues)) == dag.Params.K+1 {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -148,7 +148,7 @@ func (dag *BlockDAG) selectedParentAnticone(node *blockNode) ([]*blockNode, erro
|
||||
if anticoneSet.contains(parent) || selectedParentPast.contains(parent) {
|
||||
continue
|
||||
}
|
||||
isAncestorOfSelectedParent, err := dag.isAncestorOf(parent, node.selectedParent)
|
||||
isAncestorOfSelectedParent, err := dag.isInPast(parent, node.selectedParent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -2,14 +2,15 @@ package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
type testBlockData struct {
|
||||
@@ -33,7 +34,7 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
}{
|
||||
{
|
||||
k: 3,
|
||||
expectedReds: []string{"F", "G", "H", "I", "N", "O"},
|
||||
expectedReds: []string{"F", "G", "H", "I", "O", "P"},
|
||||
dagData: []*testBlockData{
|
||||
{
|
||||
parents: []string{"A"},
|
||||
@@ -166,7 +167,7 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
id: "T",
|
||||
expectedScore: 13,
|
||||
expectedSelectedParent: "S",
|
||||
expectedBlues: []string{"S", "P", "Q"},
|
||||
expectedBlues: []string{"S", "Q", "N"},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -215,7 +216,10 @@ func TestGHOSTDAG(t *testing.T) {
|
||||
t.Fatalf("TestGHOSTDAG: block %v was unexpectedly orphan", blockData.id)
|
||||
}
|
||||
|
||||
node := dag.index.LookupNode(utilBlock.Hash())
|
||||
node, ok := dag.index.LookupNode(utilBlock.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", utilBlock.Hash())
|
||||
}
|
||||
|
||||
blockByIDMap[blockData.id] = node
|
||||
idByBlockMap[node] = blockData.id
|
||||
@@ -291,22 +295,29 @@ func TestBlueAnticoneSizeErrors(t *testing.T) {
|
||||
defer teardownFunc()
|
||||
|
||||
// Prepare a block chain with size K beginning with the genesis block
|
||||
currentBlockA := dag.dagParams.GenesisBlock
|
||||
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
|
||||
currentBlockA := dag.Params.GenesisBlock
|
||||
for i := dagconfig.KType(0); i < dag.Params.K; i++ {
|
||||
newBlock := prepareAndProcessBlockByParentMsgBlocks(t, dag, currentBlockA)
|
||||
currentBlockA = newBlock
|
||||
}
|
||||
|
||||
// Prepare another block chain with size K beginning with the genesis block
|
||||
currentBlockB := dag.dagParams.GenesisBlock
|
||||
for i := dagconfig.KType(0); i < dag.dagParams.K; i++ {
|
||||
currentBlockB := dag.Params.GenesisBlock
|
||||
for i := dagconfig.KType(0); i < dag.Params.K; i++ {
|
||||
newBlock := prepareAndProcessBlockByParentMsgBlocks(t, dag, currentBlockB)
|
||||
currentBlockB = newBlock
|
||||
}
|
||||
|
||||
// Get references to the tips of the two chains
|
||||
blockNodeA := dag.index.LookupNode(currentBlockA.BlockHash())
|
||||
blockNodeB := dag.index.LookupNode(currentBlockB.BlockHash())
|
||||
blockNodeA, ok := dag.index.LookupNode(currentBlockA.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", currentBlockA.BlockHash())
|
||||
}
|
||||
|
||||
blockNodeB, ok := dag.index.LookupNode(currentBlockB.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", currentBlockB.BlockHash())
|
||||
}
|
||||
|
||||
// Try getting the blueAnticoneSize between them. Since the two
|
||||
// blocks are not in the anticones of eachother, this should fail.
|
||||
@@ -332,16 +343,16 @@ func TestGHOSTDAGErrors(t *testing.T) {
|
||||
defer teardownFunc()
|
||||
|
||||
// Add two child blocks to the genesis
|
||||
block1 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
|
||||
block2 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.dagParams.GenesisBlock)
|
||||
block1 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.Params.GenesisBlock)
|
||||
block2 := prepareAndProcessBlockByParentMsgBlocks(t, dag, dag.Params.GenesisBlock)
|
||||
|
||||
// Add a child block to the previous two blocks
|
||||
block3 := prepareAndProcessBlockByParentMsgBlocks(t, dag, block1, block2)
|
||||
|
||||
// Clear the reachability store
|
||||
dag.reachabilityStore.loaded = map[daghash.Hash]*reachabilityData{}
|
||||
dag.reachabilityTree.store.loaded = map[daghash.Hash]*reachabilityData{}
|
||||
|
||||
dbTx, err := dbaccess.NewTx()
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
t.Fatalf("NewTx: %s", err)
|
||||
}
|
||||
@@ -359,12 +370,15 @@ func TestGHOSTDAGErrors(t *testing.T) {
|
||||
|
||||
// Try to rerun GHOSTDAG on the last block. GHOSTDAG uses
|
||||
// reachability data, so we expect it to fail.
|
||||
blockNode3 := dag.index.LookupNode(block3.BlockHash())
|
||||
blockNode3, ok := dag.index.LookupNode(block3.BlockHash())
|
||||
if !ok {
|
||||
t.Fatalf("block %s does not exist in the DAG", block3.BlockHash())
|
||||
}
|
||||
_, err = dag.ghostdag(blockNode3)
|
||||
if err == nil {
|
||||
t.Fatalf("TestGHOSTDAGErrors: ghostdag unexpectedly succeeded")
|
||||
}
|
||||
expectedErrSubstring := "Couldn't find reachability data"
|
||||
expectedErrSubstring := "couldn't find reachability data"
|
||||
if !strings.Contains(err.Error(), expectedErrSubstring) {
|
||||
t.Fatalf("TestGHOSTDAGErrors: ghostdag returned wrong error. "+
|
||||
"Want: %s, got: %s", expectedErrSubstring, err)
|
||||
18
domain/blockdag/index_manager.go
Normal file
18
domain/blockdag/index_manager.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// IndexManager provides a generic interface that is called when blocks are
|
||||
// connected to the DAG for the purpose of supporting optional indexes.
|
||||
type IndexManager interface {
|
||||
// Init is invoked during DAG initialize in order to allow the index
|
||||
// manager to initialize itself and any indexes it is managing.
|
||||
Init(*BlockDAG, *dbaccess.DatabaseContext) error
|
||||
|
||||
// ConnectBlock is invoked when a new block has been connected to the
|
||||
// DAG.
|
||||
ConnectBlock(dbContext *dbaccess.TxContext, blockHash *daghash.Hash, acceptedTxsData MultiBlockTxsAcceptanceData) error
|
||||
}
|
||||
@@ -3,18 +3,20 @@ package indexers
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
)
|
||||
|
||||
// AcceptanceIndex implements a txAcceptanceData by block hash index. That is to say,
|
||||
// it stores a mapping between a block's hash and the set of transactions that the
|
||||
// block accepts among its blue blocks.
|
||||
type AcceptanceIndex struct {
|
||||
dag *blockdag.BlockDAG
|
||||
dag *blockdag.BlockDAG
|
||||
databaseContext *dbaccess.DatabaseContext
|
||||
}
|
||||
|
||||
// Ensure the AcceptanceIndex type implements the Indexer interface.
|
||||
@@ -31,8 +33,8 @@ func NewAcceptanceIndex() *AcceptanceIndex {
|
||||
}
|
||||
|
||||
// DropAcceptanceIndex drops the acceptance index.
|
||||
func DropAcceptanceIndex() error {
|
||||
dbTx, err := dbaccess.NewTx()
|
||||
func DropAcceptanceIndex(databaseContext *dbaccess.DatabaseContext) error {
|
||||
dbTx, err := databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -49,8 +51,9 @@ func DropAcceptanceIndex() error {
|
||||
// Init initializes the hash-based acceptance index.
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AcceptanceIndex) Init(dag *blockdag.BlockDAG) error {
|
||||
func (idx *AcceptanceIndex) Init(dag *blockdag.BlockDAG, databaseContext *dbaccess.DatabaseContext) error {
|
||||
idx.dag = dag
|
||||
idx.databaseContext = databaseContext
|
||||
return idx.recover()
|
||||
}
|
||||
|
||||
@@ -59,13 +62,13 @@ func (idx *AcceptanceIndex) Init(dag *blockdag.BlockDAG) error {
|
||||
//
|
||||
// This is part of the Indexer interface.
|
||||
func (idx *AcceptanceIndex) recover() error {
|
||||
dbTx, err := dbaccess.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
return idx.dag.ForEachHash(func(hash daghash.Hash) error {
|
||||
dbTx, err := idx.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
|
||||
err = idx.dag.ForEachHash(func(hash daghash.Hash) error {
|
||||
exists, err := dbaccess.HasAcceptanceData(dbTx, &hash)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -77,13 +80,13 @@ func (idx *AcceptanceIndex) recover() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return idx.ConnectBlock(dbTx, &hash, txAcceptanceData)
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = idx.ConnectBlock(dbTx, &hash, txAcceptanceData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return dbTx.Commit()
|
||||
return dbTx.Commit()
|
||||
})
|
||||
}
|
||||
|
||||
// ConnectBlock is invoked by the index manager when a new block has been
|
||||
@@ -102,7 +105,7 @@ func (idx *AcceptanceIndex) ConnectBlock(dbContext *dbaccess.TxContext, blockHas
|
||||
// TxsAcceptanceData returns the acceptance data of all the transactions that
|
||||
// were accepted by the block with hash blockHash.
|
||||
func (idx *AcceptanceIndex) TxsAcceptanceData(blockHash *daghash.Hash) (blockdag.MultiBlockTxsAcceptanceData, error) {
|
||||
serializedTxsAcceptanceData, err := dbaccess.FetchAcceptanceData(dbaccess.NoTx(), blockHash)
|
||||
serializedTxsAcceptanceData, err := dbaccess.FetchAcceptanceData(idx.databaseContext, blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -110,7 +113,7 @@ func (idx *AcceptanceIndex) TxsAcceptanceData(blockHash *daghash.Hash) (blockdag
|
||||
}
|
||||
|
||||
type serializableTxAcceptanceData struct {
|
||||
MsgTx wire.MsgTx
|
||||
MsgTx domainmessage.MsgTx
|
||||
IsAccepted bool
|
||||
}
|
||||
|
||||
@@ -1,13 +1,6 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@@ -15,24 +8,32 @@ import (
|
||||
"reflect"
|
||||
"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/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func TestAcceptanceIndexSerializationAndDeserialization(t *testing.T) {
|
||||
// Create test data
|
||||
hash, _ := daghash.NewHashFromStr("1111111111111111111111111111111111111111111111111111111111111111")
|
||||
txIn1 := &wire.TxIn{SignatureScript: []byte{1}, PreviousOutpoint: wire.Outpoint{Index: 1}, Sequence: 0}
|
||||
txIn2 := &wire.TxIn{SignatureScript: []byte{2}, PreviousOutpoint: wire.Outpoint{Index: 2}, Sequence: 0}
|
||||
txOut1 := &wire.TxOut{ScriptPubKey: []byte{1}, Value: 10}
|
||||
txOut2 := &wire.TxOut{ScriptPubKey: []byte{2}, Value: 20}
|
||||
txIn1 := &domainmessage.TxIn{SignatureScript: []byte{1}, PreviousOutpoint: domainmessage.Outpoint{Index: 1}, Sequence: 0}
|
||||
txIn2 := &domainmessage.TxIn{SignatureScript: []byte{2}, PreviousOutpoint: domainmessage.Outpoint{Index: 2}, Sequence: 0}
|
||||
txOut1 := &domainmessage.TxOut{ScriptPubKey: []byte{1}, Value: 10}
|
||||
txOut2 := &domainmessage.TxOut{ScriptPubKey: []byte{2}, Value: 20}
|
||||
blockTxsAcceptanceData := blockdag.BlockTxsAcceptanceData{
|
||||
BlockHash: *hash,
|
||||
TxAcceptanceData: []blockdag.TxAcceptanceData{
|
||||
{
|
||||
Tx: util.NewTx(wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn1}, []*wire.TxOut{txOut1})),
|
||||
Tx: util.NewTx(domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn1}, []*domainmessage.TxOut{txOut1})),
|
||||
IsAccepted: true,
|
||||
},
|
||||
{
|
||||
Tx: util.NewTx(wire.NewNativeMsgTx(wire.TxVersion, []*wire.TxIn{txIn2}, []*wire.TxOut{txOut2})),
|
||||
Tx: util.NewTx(domainmessage.NewNativeMsgTx(domainmessage.TxVersion, []*domainmessage.TxIn{txIn2}, []*domainmessage.TxOut{txOut2})),
|
||||
IsAccepted: false,
|
||||
},
|
||||
},
|
||||
@@ -96,14 +97,15 @@ func TestAcceptanceIndexRecover(t *testing.T) {
|
||||
}
|
||||
defer os.RemoveAll(db1Path)
|
||||
|
||||
err = dbaccess.Open(db1Path)
|
||||
databaseContext1, err := dbaccess.New(db1Path)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %s", err)
|
||||
}
|
||||
|
||||
db1Config := blockdag.Config{
|
||||
IndexManager: db1IndexManager,
|
||||
DAGParams: params,
|
||||
IndexManager: db1IndexManager,
|
||||
DAGParams: params,
|
||||
DatabaseContext: databaseContext1,
|
||||
}
|
||||
|
||||
db1DAG, teardown, err := blockdag.DAGSetup("", false, db1Config)
|
||||
@@ -160,17 +162,18 @@ func TestAcceptanceIndexRecover(t *testing.T) {
|
||||
t.Fatalf("Error fetching acceptance data: %s", err)
|
||||
}
|
||||
|
||||
err = dbaccess.Close()
|
||||
err = databaseContext1.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Error closing the database: %s", err)
|
||||
}
|
||||
err = dbaccess.Open(db2Path)
|
||||
databaseContext2, err := dbaccess.New(db2Path)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %s", err)
|
||||
}
|
||||
|
||||
db2Config := blockdag.Config{
|
||||
DAGParams: params,
|
||||
DAGParams: params,
|
||||
DatabaseContext: databaseContext2,
|
||||
}
|
||||
|
||||
db2DAG, teardown, err := blockdag.DAGSetup("", false, db2Config)
|
||||
@@ -206,11 +209,11 @@ func TestAcceptanceIndexRecover(t *testing.T) {
|
||||
t.Fatalf("copyDirectory: %s", err)
|
||||
}
|
||||
|
||||
err = dbaccess.Close()
|
||||
err = databaseContext2.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("Error closing the database: %s", err)
|
||||
}
|
||||
err = dbaccess.Open(db3Path)
|
||||
databaseContext3, err := dbaccess.New(db3Path)
|
||||
if err != nil {
|
||||
t.Fatalf("error creating db: %s", err)
|
||||
}
|
||||
@@ -218,8 +221,9 @@ func TestAcceptanceIndexRecover(t *testing.T) {
|
||||
db3AcceptanceIndex := NewAcceptanceIndex()
|
||||
db3IndexManager := NewManager([]Indexer{db3AcceptanceIndex})
|
||||
db3Config := blockdag.Config{
|
||||
IndexManager: db3IndexManager,
|
||||
DAGParams: params,
|
||||
IndexManager: db3IndexManager,
|
||||
DAGParams: params,
|
||||
DatabaseContext: databaseContext3,
|
||||
}
|
||||
|
||||
_, teardown, err = blockdag.DAGSetup("", false, db3Config)
|
||||
@@ -8,8 +8,8 @@ Package indexers implements optional block DAG indexes.
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ import (
|
||||
type Indexer interface {
|
||||
// Init is invoked when the index manager is first initializing the
|
||||
// index.
|
||||
Init(dag *blockdag.BlockDAG) error
|
||||
Init(dag *blockdag.BlockDAG, databaseContext *dbaccess.DatabaseContext) error
|
||||
|
||||
// ConnectBlock is invoked when the index manager is notified that a new
|
||||
// block has been connected to the DAG.
|
||||
@@ -5,7 +5,7 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.INDX)
|
||||
@@ -5,8 +5,8 @@
|
||||
package indexers
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/blockdag"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -22,9 +22,9 @@ var _ blockdag.IndexManager = (*Manager)(nil)
|
||||
|
||||
// Init initializes the enabled indexes.
|
||||
// This is part of the blockdag.IndexManager interface.
|
||||
func (m *Manager) Init(dag *blockdag.BlockDAG) error {
|
||||
func (m *Manager) Init(dag *blockdag.BlockDAG, databaseContext *dbaccess.DatabaseContext) error {
|
||||
for _, indexer := range m.enabledIndexes {
|
||||
if err := indexer.Init(dag); err != nil {
|
||||
if err := indexer.Init(dag, databaseContext); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
@@ -132,3 +134,49 @@ 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,37 +3,36 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/txscript"
|
||||
"github.com/kaspanet/kaspad/domain/txscript"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"time"
|
||||
"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.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for reads).
|
||||
func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, error) {
|
||||
func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*domainmessage.MsgBlock, error) {
|
||||
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()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var msgBlock wire.MsgBlock
|
||||
var msgBlock domainmessage.MsgBlock
|
||||
for _, tx := range transactions {
|
||||
msgBlock.AddTransaction(tx.MsgTx())
|
||||
}
|
||||
@@ -43,8 +42,8 @@ func (dag *BlockDAG) BlockForMining(transactions []*util.Tx) (*wire.MsgBlock, er
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgBlock.Header = wire.BlockHeader{
|
||||
Version: nextBlockVersion,
|
||||
msgBlock.Header = domainmessage.BlockHeader{
|
||||
Version: blockVersion,
|
||||
ParentHashes: dag.TipHashes(),
|
||||
HashMerkleRoot: hashMerkleTree.Root(),
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
@@ -104,19 +103,19 @@ func (dag *BlockDAG) NextCoinbaseFromAddress(payToAddress util.Address, extraDat
|
||||
// on the end of the DAG. In particular, it is one second after
|
||||
// the median timestamp of the last several blocks per the DAG consensus
|
||||
// rules.
|
||||
func (dag *BlockDAG) NextBlockMinimumTime() time.Time {
|
||||
return dag.CalcPastMedianTime().Add(time.Second)
|
||||
func (dag *BlockDAG) NextBlockMinimumTime() mstime.Time {
|
||||
return dag.CalcPastMedianTime().Add(time.Millisecond)
|
||||
}
|
||||
|
||||
// NextBlockTime returns a valid block time for the
|
||||
// next block that will point to the existing DAG tips.
|
||||
func (dag *BlockDAG) NextBlockTime() time.Time {
|
||||
func (dag *BlockDAG) NextBlockTime() mstime.Time {
|
||||
// The timestamp for the block must not be before the median timestamp
|
||||
// of the last several blocks. Thus, choose the maximum between the
|
||||
// current time and one second after the past median time. The current
|
||||
// timestamp is truncated to a second boundary before comparison since a
|
||||
// timestamp is truncated to a millisecond boundary before comparison since a
|
||||
// block timestamp does not supported a precision greater than one
|
||||
// second.
|
||||
// millisecond.
|
||||
newTimestamp := dag.Now()
|
||||
minTimestamp := dag.NextBlockMinimumTime()
|
||||
if newTimestamp.Before(minTimestamp) {
|
||||
@@ -125,3 +124,15 @@ func (dag *BlockDAG) NextBlockTime() time.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
|
||||
}
|
||||
84
domain/blockdag/multiset.go
Normal file
84
domain/blockdag/multiset.go
Normal file
@@ -0,0 +1,84 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// calcMultiset returns the multiset of the past UTXO of the given block.
|
||||
func (node *blockNode) calcMultiset(dag *BlockDAG, acceptanceData MultiBlockTxsAcceptanceData,
|
||||
selectedParentPastUTXO UTXOSet) (*secp256k1.MultiSet, error) {
|
||||
|
||||
return node.pastUTXOMultiSet(dag, acceptanceData, selectedParentPastUTXO)
|
||||
}
|
||||
|
||||
func (node *blockNode) pastUTXOMultiSet(dag *BlockDAG, acceptanceData MultiBlockTxsAcceptanceData,
|
||||
selectedParentPastUTXO UTXOSet) (*secp256k1.MultiSet, error) {
|
||||
|
||||
ms, err := node.selectedParentMultiset(dag)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, blockAcceptanceData := range acceptanceData {
|
||||
for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData {
|
||||
if !txAcceptanceData.IsAccepted {
|
||||
continue
|
||||
}
|
||||
|
||||
tx := txAcceptanceData.Tx.MsgTx()
|
||||
|
||||
var err error
|
||||
ms, err = addTxToMultiset(ms, tx, selectedParentPastUTXO, node.blueScore)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
// selectedParentMultiset returns the multiset of the node's selected
|
||||
// parent. If the node is the genesis blockNode then it does not have
|
||||
// a selected parent, in which case return a new, empty multiset.
|
||||
func (node *blockNode) selectedParentMultiset(dag *BlockDAG) (*secp256k1.MultiSet, error) {
|
||||
if node.isGenesis() {
|
||||
return secp256k1.NewMultiset(), nil
|
||||
}
|
||||
|
||||
ms, err := dag.multisetStore.multisetByBlockNode(node.selectedParent)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ms, nil
|
||||
}
|
||||
|
||||
func addTxToMultiset(ms *secp256k1.MultiSet, tx *domainmessage.MsgTx, pastUTXO UTXOSet, blockBlueScore uint64) (*secp256k1.MultiSet, error) {
|
||||
for _, txIn := range tx.TxIn {
|
||||
entry, ok := pastUTXO.Get(txIn.PreviousOutpoint)
|
||||
if !ok {
|
||||
return nil, errors.Errorf("Couldn't find entry for outpoint %s", txIn.PreviousOutpoint)
|
||||
}
|
||||
|
||||
var err error
|
||||
ms, err = removeUTXOFromMultiset(ms, entry, &txIn.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
isCoinbase := tx.IsCoinBase()
|
||||
for i, txOut := range tx.TxOut {
|
||||
outpoint := *domainmessage.NewOutpoint(tx.TxID(), uint32(i))
|
||||
entry := NewUTXOEntry(txOut, isCoinbase, blockBlueScore)
|
||||
|
||||
var err error
|
||||
ms, err = addUTXOToMultiset(ms, entry, &outpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ms, nil
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package blockdag
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/infrastructure/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/dagconfig"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
)
|
||||
|
||||
// TestNotifications ensures that notification callbacks are fired on events.
|
||||
235
domain/blockdag/orphan_blocks.go
Normal file
235
domain/blockdag/orphan_blocks.go
Normal file
@@ -0,0 +1,235 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// maxOrphanBlocks is the maximum number of orphan blocks that can be
|
||||
// queued.
|
||||
const maxOrphanBlocks = 100
|
||||
|
||||
// orphanBlock represents a block that we don't yet have the parent for. It
|
||||
// is a normal block plus an expiration time to prevent caching the orphan
|
||||
// forever.
|
||||
type orphanBlock struct {
|
||||
block *util.Block
|
||||
expiration mstime.Time
|
||||
}
|
||||
|
||||
// IsKnownOrphan returns whether the passed hash is currently a known orphan.
|
||||
// Keep in mind that only a limited number of orphans are held onto for a
|
||||
// limited amount of time, so this function must not be used as an absolute
|
||||
// way to test if a block is an orphan block. A full block (as opposed to just
|
||||
// its hash) must be passed to ProcessBlock for that purpose. However, calling
|
||||
// ProcessBlock with an orphan that already exists results in an error, so this
|
||||
// function provides a mechanism for a caller to intelligently detect *recent*
|
||||
// duplicate orphans and react accordingly.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) IsKnownOrphan(hash *daghash.Hash) bool {
|
||||
// Protect concurrent access. Using a read lock only so multiple
|
||||
// readers can query without blocking each other.
|
||||
dag.orphanLock.RLock()
|
||||
defer dag.orphanLock.RUnlock()
|
||||
_, exists := dag.orphans[*hash]
|
||||
|
||||
return exists
|
||||
}
|
||||
|
||||
// GetOrphanMissingAncestorHashes returns all of the missing parents in the orphan's sub-DAG
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (dag *BlockDAG) GetOrphanMissingAncestorHashes(orphanHash *daghash.Hash) []*daghash.Hash {
|
||||
// Protect concurrent access. Using a read lock only so multiple
|
||||
// readers can query without blocking each other.
|
||||
dag.orphanLock.RLock()
|
||||
defer dag.orphanLock.RUnlock()
|
||||
|
||||
missingAncestorsHashes := make([]*daghash.Hash, 0)
|
||||
|
||||
visited := make(map[daghash.Hash]bool)
|
||||
queue := []*daghash.Hash{orphanHash}
|
||||
for len(queue) > 0 {
|
||||
var current *daghash.Hash
|
||||
current, queue = queue[0], queue[1:]
|
||||
if !visited[*current] {
|
||||
visited[*current] = true
|
||||
orphan, orphanExists := dag.orphans[*current]
|
||||
if orphanExists {
|
||||
queue = append(queue, orphan.block.MsgBlock().Header.ParentHashes...)
|
||||
} else {
|
||||
if !dag.IsInDAG(current) && current != orphanHash {
|
||||
missingAncestorsHashes = append(missingAncestorsHashes, current)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return missingAncestorsHashes
|
||||
}
|
||||
|
||||
// removeOrphanBlock removes the passed orphan block from the orphan pool and
|
||||
// previous orphan index.
|
||||
func (dag *BlockDAG) removeOrphanBlock(orphan *orphanBlock) {
|
||||
// Protect concurrent access.
|
||||
dag.orphanLock.Lock()
|
||||
defer dag.orphanLock.Unlock()
|
||||
|
||||
// Remove the orphan block from the orphan pool.
|
||||
orphanHash := orphan.block.Hash()
|
||||
delete(dag.orphans, *orphanHash)
|
||||
|
||||
// Remove the reference from the previous orphan index too.
|
||||
for _, parentHash := range orphan.block.MsgBlock().Header.ParentHashes {
|
||||
// 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.
|
||||
orphans := dag.prevOrphans[*parentHash]
|
||||
for i := 0; i < len(orphans); i++ {
|
||||
hash := orphans[i].block.Hash()
|
||||
if hash.IsEqual(orphanHash) {
|
||||
orphans = append(orphans[:i], orphans[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the map entry altogether if there are no longer any orphans
|
||||
// which depend on the parent hash.
|
||||
if len(orphans) == 0 {
|
||||
delete(dag.prevOrphans, *parentHash)
|
||||
continue
|
||||
}
|
||||
|
||||
dag.prevOrphans[*parentHash] = orphans
|
||||
}
|
||||
}
|
||||
|
||||
// addOrphanBlock adds the passed block (which is already determined to be
|
||||
// an orphan prior calling this function) to the orphan pool. It lazily cleans
|
||||
// up any expired blocks so a separate cleanup poller doesn't need to be run.
|
||||
// It also imposes a maximum limit on the number of outstanding orphan
|
||||
// blocks and will remove the oldest received orphan block if the limit is
|
||||
// exceeded.
|
||||
func (dag *BlockDAG) addOrphanBlock(block *util.Block) {
|
||||
log.Infof("Adding orphan block %s", block.Hash())
|
||||
|
||||
// Remove expired orphan blocks.
|
||||
for _, oBlock := range dag.orphans {
|
||||
if mstime.Now().After(oBlock.expiration) {
|
||||
dag.removeOrphanBlock(oBlock)
|
||||
continue
|
||||
}
|
||||
|
||||
// Update the newest orphan block pointer so it can be discarded
|
||||
// in case the orphan pool fills up.
|
||||
if dag.newestOrphan == nil || oBlock.block.Timestamp().After(dag.newestOrphan.block.Timestamp()) {
|
||||
dag.newestOrphan = oBlock
|
||||
}
|
||||
}
|
||||
|
||||
// Limit orphan blocks to prevent memory exhaustion.
|
||||
if len(dag.orphans)+1 > maxOrphanBlocks {
|
||||
// If the new orphan is newer than the newest orphan on the orphan
|
||||
// pool, don't add it.
|
||||
if block.Timestamp().After(dag.newestOrphan.block.Timestamp()) {
|
||||
return
|
||||
}
|
||||
// Remove the newest orphan to make room for the added one.
|
||||
dag.removeOrphanBlock(dag.newestOrphan)
|
||||
dag.newestOrphan = nil
|
||||
}
|
||||
|
||||
// Protect concurrent access. This is intentionally done here instead
|
||||
// of near the top since removeOrphanBlock does its own locking and
|
||||
// the range iterator is not invalidated by removing map entries.
|
||||
dag.orphanLock.Lock()
|
||||
defer dag.orphanLock.Unlock()
|
||||
|
||||
// Insert the block into the orphan map with an expiration time
|
||||
// 1 hour from now.
|
||||
expiration := mstime.Now().Add(time.Hour)
|
||||
oBlock := &orphanBlock{
|
||||
block: block,
|
||||
expiration: expiration,
|
||||
}
|
||||
dag.orphans[*block.Hash()] = oBlock
|
||||
|
||||
// Add to parent hash lookup index for faster dependency lookups.
|
||||
for _, parentHash := range block.MsgBlock().Header.ParentHashes {
|
||||
dag.prevOrphans[*parentHash] = append(dag.prevOrphans[*parentHash], oBlock)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
458
domain/blockdag/process.go
Normal file
458
domain/blockdag/process.go
Normal file
@@ -0,0 +1,458 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/go-secp256k1"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
// chainUpdates represents the updates made to the selected parent chain after
|
||||
// a block had been added to the DAG.
|
||||
type chainUpdates struct {
|
||||
removedChainBlockHashes []*daghash.Hash
|
||||
addedChainBlockHashes []*daghash.Hash
|
||||
}
|
||||
|
||||
// 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.
|
||||
//
|
||||
// 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) {
|
||||
blockHash := block.Hash()
|
||||
log.Tracef("Processing block %s", blockHash)
|
||||
|
||||
err = dag.checkDuplicateBlock(blockHash, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
err = dag.checkBlockSanity(block, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
isOrphan, isDelayed, err = dag.checkDelayedAndOrphanBlocks(block, flags)
|
||||
if isOrphan || isDelayed || err != nil {
|
||||
return isOrphan, isDelayed, err
|
||||
}
|
||||
|
||||
err = dag.maybeAcceptBlock(block, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
err = dag.processOrphansAndDelayedBlocks(blockHash, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
||||
log.Debugf("Accepted block %s", blockHash)
|
||||
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkDelayedAndOrphanBlocks(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
|
||||
if !isBehaviorFlagRaised(flags, BFAfterDelay) {
|
||||
isDelayed, err := dag.checkBlockDelay(block, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
if isDelayed {
|
||||
return false, true, nil
|
||||
}
|
||||
}
|
||||
return dag.checkMissingParents(block, flags)
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkBlockDelay(block *util.Block, flags BehaviorFlags) (isDelayed bool, err error) {
|
||||
delay, isDelayed := dag.shouldBlockBeDelayed(block)
|
||||
if isDelayed && isBehaviorFlagRaised(flags, BFDisallowDelay) {
|
||||
str := fmt.Sprintf("cannot process blocks beyond the "+
|
||||
"allowed time offset while the BFDisallowDelay flag is "+
|
||||
"raised %s", block.Hash())
|
||||
return false, ruleError(ErrDelayedBlockIsNotAllowed, str)
|
||||
}
|
||||
|
||||
if isDelayed {
|
||||
err := dag.addDelayedBlock(block, delay)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) checkMissingParents(block *util.Block, flags BehaviorFlags) (isOrphan bool, isDelayed bool, err error) {
|
||||
var missingParents []*daghash.Hash
|
||||
for _, parentHash := range block.MsgBlock().Header.ParentHashes {
|
||||
if !dag.IsInDAG(parentHash) {
|
||||
missingParents = append(missingParents, parentHash)
|
||||
}
|
||||
}
|
||||
|
||||
if len(missingParents) > 0 && isBehaviorFlagRaised(flags, BFDisallowOrphans) {
|
||||
str := fmt.Sprintf("cannot process orphan blocks while the "+
|
||||
"BFDisallowOrphans flag is raised %s", block.Hash())
|
||||
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, nil
|
||||
}
|
||||
|
||||
// Handle orphan blocks.
|
||||
if len(missingParents) > 0 {
|
||||
dag.addOrphanBlock(block)
|
||||
return true, false, nil
|
||||
}
|
||||
return false, false, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) processOrphansAndDelayedBlocks(blockHash *daghash.Hash, flags BehaviorFlags) error {
|
||||
err := dag.processOrphans(blockHash, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !isBehaviorFlagRaised(flags, BFAfterDelay) {
|
||||
err = dag.processDelayedBlocks()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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 connectBlock. 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 {
|
||||
err := dag.checkBlockContext(block, flags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
newNode, selectedParentAnticone, err := dag.createBlockNodeFromBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
chainUpdates, err := dag.connectBlock(newNode, block, selectedParentAnticone, flags)
|
||||
if err != nil {
|
||||
return dag.handleConnectBlockError(err, newNode)
|
||||
}
|
||||
|
||||
dag.notifyBlockAccepted(block, chainUpdates, flags)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// createBlockNodeFromBlock generates a new block node for the given block
|
||||
// and stores it in the block index with statusDataStored.
|
||||
func (dag *BlockDAG) createBlockNodeFromBlock(block *util.Block) (
|
||||
newNode *blockNode, selectedParentAnticone []*blockNode, err error) {
|
||||
|
||||
// Create a new block node for the block and add it to the node index.
|
||||
parents, err := lookupParentNodes(block, dag)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
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 nil, nil, err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
blockExists, err := dbaccess.HasBlock(dbTx, block.Hash())
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if !blockExists {
|
||||
err := storeBlock(dbTx, block)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
err = dag.index.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return newNode, selectedParentAnticone, nil
|
||||
}
|
||||
|
||||
// connectBlock handles connecting the passed node/block to the DAG.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) connectBlock(node *blockNode,
|
||||
block *util.Block, selectedParentAnticone []*blockNode, flags BehaviorFlags) (*chainUpdates, error) {
|
||||
|
||||
err := dag.checkBlockTransactionsFinalized(block, node, flags)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err = dag.checkFinalityViolation(node); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := dag.validateGasLimit(block); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
newBlockPastUTXO, txsAcceptanceData, newBlockFeeData, newBlockMultiSet, err :=
|
||||
node.verifyAndBuildUTXO(dag, block.Transactions(), isBehaviorFlagRaised(flags, BFFastAdd))
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "error verifying UTXO for %s", node)
|
||||
}
|
||||
|
||||
err = node.validateCoinbaseTransaction(dag, block, txsAcceptanceData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
virtualUTXODiff, chainUpdates, err :=
|
||||
dag.applyDAGChanges(node, newBlockPastUTXO, newBlockMultiSet, selectedParentAnticone)
|
||||
if err != nil {
|
||||
// Since all validation logic has already ran, if applyDAGChanges errors out,
|
||||
// this means we have a problem in the internal structure of the DAG - a problem which is
|
||||
// irrecoverable, and it would be a bad idea to attempt adding any more blocks to the DAG.
|
||||
// Therefore - in such cases we panic.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = dag.saveChangesFromBlock(block, virtualUTXODiff, txsAcceptanceData, newBlockFeeData)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dag.addBlockProcessingTimestamp()
|
||||
dag.blockCount++
|
||||
|
||||
return chainUpdates, nil
|
||||
}
|
||||
|
||||
// applyDAGChanges does the following:
|
||||
// 1. Connects each of the new block's parents to the block.
|
||||
// 2. Adds the new block to the DAG's tips.
|
||||
// 3. Updates the DAG's full UTXO set.
|
||||
// 4. Updates each of the tips' utxoDiff.
|
||||
// 5. Applies the new virtual's blue score to all the unaccepted UTXOs
|
||||
// 6. Adds the block to the reachability structures
|
||||
// 7. Adds the multiset of the block to the multiset store.
|
||||
// 8. Updates the finality point of the DAG (if required).
|
||||
//
|
||||
// It returns the diff in the virtual block's UTXO set.
|
||||
//
|
||||
// This function MUST be called with the DAG state lock held (for writes).
|
||||
func (dag *BlockDAG) applyDAGChanges(node *blockNode, newBlockPastUTXO UTXOSet,
|
||||
newBlockMultiset *secp256k1.MultiSet, selectedParentAnticone []*blockNode) (
|
||||
virtualUTXODiff *UTXODiff, chainUpdates *chainUpdates, err error) {
|
||||
|
||||
// Add the block to the reachability tree
|
||||
err = dag.reachabilityTree.addBlock(node, selectedParentAnticone)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed adding block to the reachability tree")
|
||||
}
|
||||
|
||||
dag.multisetStore.setMultiset(node, newBlockMultiset)
|
||||
|
||||
if err = node.updateParents(dag, newBlockPastUTXO); err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "failed updating parents of %s", node)
|
||||
}
|
||||
|
||||
// Update the virtual block's parents (the DAG tips) to include the new block.
|
||||
chainUpdates = dag.virtual.AddTip(node)
|
||||
|
||||
// Build a UTXO set for the new virtual block
|
||||
newVirtualUTXO, _, _, err := dag.pastUTXO(&dag.virtual.blockNode)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "could not restore past UTXO for virtual")
|
||||
}
|
||||
|
||||
// Apply new utxoDiffs to all the tips
|
||||
err = updateTipsUTXO(dag, newVirtualUTXO)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed updating the tips' UTXO")
|
||||
}
|
||||
|
||||
// It is now safe to meld the UTXO set to base.
|
||||
diffSet := newVirtualUTXO.(*DiffUTXOSet)
|
||||
virtualUTXODiff = diffSet.UTXODiff
|
||||
err = dag.meldVirtualUTXO(diffSet)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "failed melding the virtual UTXO")
|
||||
}
|
||||
|
||||
dag.index.SetStatusFlags(node, statusValid)
|
||||
|
||||
// And now we can update the finality point of the DAG (if required)
|
||||
dag.updateFinalityPoint()
|
||||
|
||||
return virtualUTXODiff, chainUpdates, nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) saveChangesFromBlock(block *util.Block, virtualUTXODiff *UTXODiff,
|
||||
txsAcceptanceData MultiBlockTxsAcceptanceData, feeData compactFeeData) error {
|
||||
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
|
||||
err = dag.index.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.utxoDiffStore.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.reachabilityTree.storeState(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dag.multisetStore.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update DAG state.
|
||||
state := &dagState{
|
||||
TipHashes: dag.TipHashes(),
|
||||
LastFinalityPoint: dag.lastFinalityPoint.hash,
|
||||
LocalSubnetworkID: dag.subnetworkID,
|
||||
}
|
||||
err = saveDAGState(dbTx, state)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Update the UTXO set using the diffSet that was melded into the
|
||||
// full UTXO set.
|
||||
err = updateUTXOSet(dbTx, virtualUTXODiff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Scan all accepted transactions and register any subnetwork registry
|
||||
// transaction. If any subnetwork registry transaction is not well-formed,
|
||||
// fail the entire block.
|
||||
err = registerSubnetworks(dbTx, block.Transactions())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Allow the index manager to call each of the currently active
|
||||
// optional indexes with the block being connected so they can
|
||||
// update themselves accordingly.
|
||||
if dag.indexManager != nil {
|
||||
err := dag.indexManager.ConnectBlock(dbTx, block.Hash(), txsAcceptanceData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Apply the fee data into the database
|
||||
err = dbaccess.StoreFeeData(dbTx, block.Hash(), feeData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
dag.index.clearDirtyEntries()
|
||||
dag.utxoDiffStore.clearDirtyEntries()
|
||||
dag.utxoDiffStore.clearOldEntries()
|
||||
dag.reachabilityTree.store.clearDirtyEntries()
|
||||
dag.multisetStore.clearNewEntries()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dag *BlockDAG) handleConnectBlockError(err error, newNode *blockNode) error {
|
||||
if errors.As(err, &RuleError{}) {
|
||||
dag.index.SetStatusFlags(newNode, statusValidateFailed)
|
||||
|
||||
dbTx, err := dag.databaseContext.NewTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer dbTx.RollbackUnlessClosed()
|
||||
|
||||
err = dag.index.flushToDB(dbTx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = dbTx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// notifyBlockAccepted notifies 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.
|
||||
func (dag *BlockDAG) notifyBlockAccepted(block *util.Block, chainUpdates *chainUpdates, flags BehaviorFlags) {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
package blockdag
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
@@ -63,8 +65,8 @@ func TestProcessOrphans(t *testing.T) {
|
||||
}
|
||||
|
||||
// Make sure that the child block had been rejected
|
||||
node := dag.index.LookupNode(childBlock.Hash())
|
||||
if node == nil {
|
||||
node, ok := dag.index.LookupNode(childBlock.Hash())
|
||||
if !ok {
|
||||
t.Fatalf("TestProcessOrphans: child block missing from block index")
|
||||
}
|
||||
if !dag.index.NodeStatus(node).KnownInvalid() {
|
||||
@@ -89,20 +91,23 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
}
|
||||
}()
|
||||
|
||||
initialTime := dag1.dagParams.GenesisBlock.Header.Timestamp
|
||||
initialTime := dag1.Params.GenesisBlock.Header.Timestamp
|
||||
// Here we use a fake time source that returns a timestamp
|
||||
// one hour into the future to make delayedBlock artificially
|
||||
// valid.
|
||||
dag1.timeSource = newFakeTimeSource(initialTime.Add(time.Hour))
|
||||
|
||||
delayedBlock, err := PrepareBlockForTest(dag1, []*daghash.Hash{dag1.dagParams.GenesisBlock.BlockHash()}, nil)
|
||||
delayedBlock, err := PrepareBlockForTest(dag1, []*daghash.Hash{dag1.Params.GenesisBlock.BlockHash()}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
|
||||
blockDelay := time.Duration(dag1.dagParams.TimestampDeviationTolerance*uint64(dag1.targetTimePerBlock)+5) * time.Second
|
||||
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)
|
||||
@@ -177,7 +182,7 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
t.Errorf("dag.IsKnownBlock should return true for a child of a delayed block")
|
||||
}
|
||||
|
||||
blockBeforeDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()}, nil)
|
||||
blockBeforeDelay, err := PrepareBlockForTest(dag2, []*daghash.Hash{dag2.Params.GenesisBlock.BlockHash()}, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
}
|
||||
@@ -202,12 +207,15 @@ func TestProcessDelayedBlocks(t *testing.T) {
|
||||
}
|
||||
|
||||
// We advance the clock to the point where delayedBlock timestamp is valid.
|
||||
deviationTolerance := int64(dag2.TimestampDeviationTolerance) * dag2.targetTimePerBlock
|
||||
secondsUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.Unix() - deviationTolerance - dag2.Now().Unix() + 1
|
||||
dag2.timeSource = newFakeTimeSource(initialTime.Add(time.Duration(secondsUntilDelayedBlockIsValid) * time.Second))
|
||||
deviationTolerance := time.Duration(dag2.TimestampDeviationTolerance) * dag2.Params.TargetTimePerBlock
|
||||
timeUntilDelayedBlockIsValid := delayedBlock.Header.Timestamp.
|
||||
Add(-deviationTolerance).
|
||||
Sub(dag2.Now()) +
|
||||
time.Second
|
||||
dag2.timeSource = newFakeTimeSource(initialTime.Add(timeUntilDelayedBlockIsValid))
|
||||
|
||||
blockAfterDelay, err := PrepareBlockForTest(dag2,
|
||||
[]*daghash.Hash{dag2.dagParams.GenesisBlock.BlockHash()},
|
||||
[]*daghash.Hash{dag2.Params.GenesisBlock.BlockHash()},
|
||||
nil)
|
||||
if err != nil {
|
||||
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
||||
@@ -232,3 +240,101 @@ 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
|
||||
}
|
||||
1206
domain/blockdag/reachability.go
Normal file
1206
domain/blockdag/reachability.go
Normal file
File diff suppressed because it is too large
Load Diff
1083
domain/blockdag/reachability_test.go
Normal file
1083
domain/blockdag/reachability_test.go
Normal file
File diff suppressed because it is too large
Load Diff
@@ -2,17 +2,17 @@ package blockdag
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/database"
|
||||
"github.com/kaspanet/kaspad/dbaccess"
|
||||
"github.com/kaspanet/kaspad/infrastructure/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/dbaccess"
|
||||
"github.com/kaspanet/kaspad/network/domainmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/wire"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
type reachabilityData struct {
|
||||
treeNode *reachabilityTreeNode
|
||||
futureCoveringSet futureCoveringBlockSet
|
||||
futureCoveringSet futureCoveringTreeNodeSet
|
||||
}
|
||||
|
||||
type reachabilityStore struct {
|
||||
@@ -41,11 +41,11 @@ func (store *reachabilityStore) setTreeNode(treeNode *reachabilityTreeNode) {
|
||||
store.setBlockAsDirty(node.hash)
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringBlockSet) error {
|
||||
func (store *reachabilityStore) setFutureCoveringSet(node *blockNode, futureCoveringSet futureCoveringTreeNodeSet) error {
|
||||
// load the reachability data from DB to store.loaded
|
||||
_, exists := store.reachabilityDataByHash(node.hash)
|
||||
if !exists {
|
||||
return reachabilityNotFoundError(node)
|
||||
return reachabilityNotFoundError(node.hash)
|
||||
}
|
||||
|
||||
store.loaded[*node.hash].futureCoveringSet = futureCoveringSet
|
||||
@@ -57,22 +57,26 @@ func (store *reachabilityStore) setBlockAsDirty(blockHash *daghash.Hash) {
|
||||
store.dirty[*blockHash] = struct{}{}
|
||||
}
|
||||
|
||||
func reachabilityNotFoundError(node *blockNode) error {
|
||||
return errors.Errorf("Couldn't find reachability data for block %s", node.hash)
|
||||
func reachabilityNotFoundError(hash *daghash.Hash) error {
|
||||
return errors.Errorf("couldn't find reachability data for block %s", hash)
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
|
||||
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
||||
func (store *reachabilityStore) treeNodeByBlockHash(hash *daghash.Hash) (*reachabilityTreeNode, error) {
|
||||
reachabilityData, exists := store.reachabilityDataByHash(hash)
|
||||
if !exists {
|
||||
return nil, reachabilityNotFoundError(node)
|
||||
return nil, reachabilityNotFoundError(hash)
|
||||
}
|
||||
return reachabilityData.treeNode, nil
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringBlockSet, error) {
|
||||
func (store *reachabilityStore) treeNodeByBlockNode(node *blockNode) (*reachabilityTreeNode, error) {
|
||||
return store.treeNodeByBlockHash(node.hash)
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) futureCoveringSetByBlockNode(node *blockNode) (futureCoveringTreeNodeSet, error) {
|
||||
reachabilityData, exists := store.reachabilityDataByHash(node.hash)
|
||||
if !exists {
|
||||
return nil, reachabilityNotFoundError(node)
|
||||
return nil, reachabilityNotFoundError(node.hash)
|
||||
}
|
||||
return reachabilityData.futureCoveringSet, nil
|
||||
}
|
||||
@@ -176,7 +180,10 @@ func (store *reachabilityStore) loadReachabilityDataFromCursor(cursor database.C
|
||||
}
|
||||
|
||||
// Connect the treeNode with its blockNode
|
||||
reachabilityData.treeNode.blockNode = store.dag.index.LookupNode(hash)
|
||||
reachabilityData.treeNode.blockNode, ok = store.dag.index.LookupNode(hash)
|
||||
if !ok {
|
||||
return errors.Errorf("block %s does not exist in the DAG", hash)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -212,32 +219,26 @@ func (store *reachabilityStore) serializeTreeNode(w io.Writer, treeNode *reachab
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize the remaining interval
|
||||
err = store.serializeReachabilityInterval(w, treeNode.remainingInterval)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize the parent
|
||||
// If this is the genesis block, write the zero hash instead
|
||||
parentHash := &daghash.ZeroHash
|
||||
if treeNode.parent != nil {
|
||||
parentHash = treeNode.parent.blockNode.hash
|
||||
}
|
||||
err = wire.WriteElement(w, parentHash)
|
||||
err = domainmessage.WriteElement(w, parentHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize the amount of children
|
||||
err = wire.WriteVarInt(w, uint64(len(treeNode.children)))
|
||||
err = domainmessage.WriteVarInt(w, uint64(len(treeNode.children)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize the children
|
||||
for _, child := range treeNode.children {
|
||||
err = wire.WriteElement(w, child.blockNode.hash)
|
||||
err = domainmessage.WriteElement(w, child.blockNode.hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -248,13 +249,13 @@ func (store *reachabilityStore) serializeTreeNode(w io.Writer, treeNode *reachab
|
||||
|
||||
func (store *reachabilityStore) serializeReachabilityInterval(w io.Writer, interval *reachabilityInterval) error {
|
||||
// Serialize start
|
||||
err := wire.WriteElement(w, interval.start)
|
||||
err := domainmessage.WriteElement(w, interval.start)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize end
|
||||
err = wire.WriteElement(w, interval.end)
|
||||
err = domainmessage.WriteElement(w, interval.end)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -262,16 +263,16 @@ func (store *reachabilityStore) serializeReachabilityInterval(w io.Writer, inter
|
||||
return nil
|
||||
}
|
||||
|
||||
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringBlockSet) error {
|
||||
func (store *reachabilityStore) serializeFutureCoveringSet(w io.Writer, futureCoveringSet futureCoveringTreeNodeSet) error {
|
||||
// Serialize the set size
|
||||
err := wire.WriteVarInt(w, uint64(len(futureCoveringSet)))
|
||||
err := domainmessage.WriteVarInt(w, uint64(len(futureCoveringSet)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Serialize each block in the set
|
||||
for _, block := range futureCoveringSet {
|
||||
err = wire.WriteElement(w, block.blockNode.hash)
|
||||
// Serialize each node in the set
|
||||
for _, node := range futureCoveringSet {
|
||||
err = domainmessage.WriteElement(w, node.blockNode.hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -308,17 +309,10 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
|
||||
}
|
||||
destination.treeNode.interval = interval
|
||||
|
||||
// Deserialize the remaining interval
|
||||
remainingInterval, err := store.deserializeReachabilityInterval(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destination.treeNode.remainingInterval = remainingInterval
|
||||
|
||||
// Deserialize the parent
|
||||
// If this is the zero hash, this node is the genesis and as such doesn't have a parent
|
||||
parentHash := &daghash.Hash{}
|
||||
err = wire.ReadElement(r, parentHash)
|
||||
err = domainmessage.ReadElement(r, parentHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -331,7 +325,7 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
|
||||
}
|
||||
|
||||
// Deserialize the amount of children
|
||||
childCount, err := wire.ReadVarInt(r)
|
||||
childCount, err := domainmessage.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -340,7 +334,7 @@ func (store *reachabilityStore) deserializeTreeNode(r io.Reader, destination *re
|
||||
children := make([]*reachabilityTreeNode, childCount)
|
||||
for i := uint64(0); i < childCount; i++ {
|
||||
childHash := &daghash.Hash{}
|
||||
err = wire.ReadElement(r, childHash)
|
||||
err = domainmessage.ReadElement(r, childHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -360,7 +354,7 @@ func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*r
|
||||
|
||||
// Deserialize start
|
||||
start := uint64(0)
|
||||
err := wire.ReadElement(r, &start)
|
||||
err := domainmessage.ReadElement(r, &start)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -368,7 +362,7 @@ func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*r
|
||||
|
||||
// Deserialize end
|
||||
end := uint64(0)
|
||||
err = wire.ReadElement(r, &end)
|
||||
err = domainmessage.ReadElement(r, &end)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -379,31 +373,24 @@ func (store *reachabilityStore) deserializeReachabilityInterval(r io.Reader) (*r
|
||||
|
||||
func (store *reachabilityStore) deserializeFutureCoveringSet(r io.Reader, destination *reachabilityData) error {
|
||||
// Deserialize the set size
|
||||
setSize, err := wire.ReadVarInt(r)
|
||||
setSize, err := domainmessage.ReadVarInt(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Deserialize each block in the set
|
||||
futureCoveringSet := make(futureCoveringBlockSet, setSize)
|
||||
futureCoveringSet := make(futureCoveringTreeNodeSet, setSize)
|
||||
for i := uint64(0); i < setSize; i++ {
|
||||
blockHash := &daghash.Hash{}
|
||||
err = wire.ReadElement(r, blockHash)
|
||||
err = domainmessage.ReadElement(r, blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
blockNode := store.dag.index.LookupNode(blockHash)
|
||||
if blockNode == nil {
|
||||
return errors.Errorf("blockNode not found for hash %s", blockHash)
|
||||
}
|
||||
blockReachabilityData, ok := store.reachabilityDataByHash(blockHash)
|
||||
if !ok {
|
||||
return errors.Errorf("block reachability data not found for hash: %s", blockHash)
|
||||
}
|
||||
futureCoveringSet[i] = &futureCoveringBlock{
|
||||
blockNode: blockNode,
|
||||
treeNode: blockReachabilityData.treeNode,
|
||||
}
|
||||
futureCoveringSet[i] = blockReachabilityData.treeNode
|
||||
}
|
||||
destination.futureCoveringSet = futureCoveringSet
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user