mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-22 03:32:55 +00:00
Compare commits
202 Commits
v0.6.11-de
...
v0.8.2-dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a9e0c33e5c | ||
|
|
2c1688909d | ||
|
|
6714e084e9 | ||
|
|
3354ac67c8 | ||
|
|
0d8f7bba40 | ||
|
|
e04f76b800 | ||
|
|
82fa8e6831 | ||
|
|
b7ca3f4461 | ||
|
|
37bf261da1 | ||
|
|
d90e18ec51 | ||
|
|
9962527793 | ||
|
|
78550d3639 | ||
|
|
7f899b0d09 | ||
|
|
4886425caf | ||
|
|
c3902ed7a8 | ||
|
|
33eaf9edac | ||
|
|
f97b8f7580 | ||
|
|
05979de705 | ||
|
|
32a04d1811 | ||
|
|
a585f32763 | ||
|
|
9866abb75a | ||
|
|
ab3c81c552 | ||
|
|
9756d64f28 | ||
|
|
21fc2d4219 | ||
|
|
f1c6df48c9 | ||
|
|
80c445c78b | ||
|
|
3b6eb73e53 | ||
|
|
f407c44a8d | ||
|
|
a1af992d15 | ||
|
|
048caebda3 | ||
|
|
baa4311a34 | ||
|
|
0e91b44fc6 | ||
|
|
f7fa823f17 | ||
|
|
546ea83123 | ||
|
|
f9c2137344 | ||
|
|
0fa13357c3 | ||
|
|
5b2fae0457 | ||
|
|
3bad9ec1eb | ||
|
|
45d9b63572 | ||
|
|
afc634d871 | ||
|
|
2334f8b4eb | ||
|
|
d65f382c80 | ||
|
|
2096a28d1c | ||
|
|
96d9e5800f | ||
|
|
8264369c81 | ||
|
|
bb2d7f72ac | ||
|
|
c1505b4748 | ||
|
|
dec9ef5f75 | ||
|
|
5211727206 | ||
|
|
fafe1d534f | ||
|
|
c56a5336f3 | ||
|
|
b3a3121725 | ||
|
|
950dd0cc8d | ||
|
|
bb244706ea | ||
|
|
ed386bbc8f | ||
|
|
75d21d39cc | ||
|
|
3f92ddd827 | ||
|
|
8500acd86b | ||
|
|
5b037950d8 | ||
|
|
184911f76e | ||
|
|
7479f5f5e8 | ||
|
|
891095563e | ||
|
|
60c24d8dea | ||
|
|
d4993c1d06 | ||
|
|
c785ca0e52 | ||
|
|
9eb5c4a0ed | ||
|
|
9d5d1b02dc | ||
|
|
213be67c47 | ||
|
|
14d7ab5fc6 | ||
|
|
7224d58940 | ||
|
|
b2188f5993 | ||
|
|
dbd15aecf5 | ||
|
|
66f5a5bd7d | ||
|
|
c994200878 | ||
|
|
7050ebeac9 | ||
|
|
f2df48139f | ||
|
|
48d8137604 | ||
|
|
310cf0bb9b | ||
|
|
b6c47fdd21 | ||
|
|
e6a2b7366f | ||
|
|
d8f72e2b27 | ||
|
|
08749deaeb | ||
|
|
83a88d9989 | ||
|
|
151910c27a | ||
|
|
fca8ed57bd | ||
|
|
56679818be | ||
|
|
f07f2edad2 | ||
|
|
2dcfe90850 | ||
|
|
dc80a39c54 | ||
|
|
34be898491 | ||
|
|
f4a2fbf64f | ||
|
|
a0c6076ccc | ||
|
|
fddce00d08 | ||
|
|
ae682d59f7 | ||
|
|
347f3de15c | ||
|
|
a34091991a | ||
|
|
efe1986a56 | ||
|
|
3ab507b66f | ||
|
|
afbad73c0b | ||
|
|
a1fa17d872 | ||
|
|
b50421beee | ||
|
|
aeded07815 | ||
|
|
7d14f24b84 | ||
|
|
c52b8100c6 | ||
|
|
f52cddc25c | ||
|
|
fc5e39f6cc | ||
|
|
8ccf381fc7 | ||
|
|
f320887bff | ||
|
|
eef5e3768c | ||
|
|
7a7821e1c8 | ||
|
|
37fbdcb453 | ||
|
|
135ffbd4f2 | ||
|
|
4736213ba4 | ||
|
|
8290fadd3a | ||
|
|
23c1ea6c31 | ||
|
|
31c5264430 | ||
|
|
32da4440ba | ||
|
|
e7a61c7edf | ||
|
|
6db337c8c5 | ||
|
|
2282e36196 | ||
|
|
72b5832f30 | ||
|
|
9a344152aa | ||
|
|
281944762d | ||
|
|
e3be67c3c7 | ||
|
|
215ab512cd | ||
|
|
d0fc728c23 | ||
|
|
1c710daf98 | ||
|
|
5566aaf95a | ||
|
|
52c73d3a08 | ||
|
|
baf8d25656 | ||
|
|
2eb0d946e5 | ||
|
|
ce95c6dc9d | ||
|
|
6a46cb2be6 | ||
|
|
f06dc7ea90 | ||
|
|
7f2ef708a6 | ||
|
|
4ab2e0d498 | ||
|
|
ca9161024f | ||
|
|
8dc246a2a7 | ||
|
|
c7f2de73df | ||
|
|
3f979399b1 | ||
|
|
2abd4a274b | ||
|
|
c5707f64dc | ||
|
|
62bb841e89 | ||
|
|
23cccb6396 | ||
|
|
87ad9dfc59 | ||
|
|
c59adaa4db | ||
|
|
14fbe50636 | ||
|
|
d3ede3a46f | ||
|
|
01c7c67aed | ||
|
|
971d50b684 | ||
|
|
9cf1557c37 | ||
|
|
126e2e49bb | ||
|
|
c88266afed | ||
|
|
7402f3fb0e | ||
|
|
eae8bce941 | ||
|
|
8c0275421a | ||
|
|
a436b30ebf | ||
|
|
a132f55302 | ||
|
|
be56fb7e8b | ||
|
|
4fbe130592 | ||
|
|
ed6d8243ef | ||
|
|
03790ad8a2 | ||
|
|
97b5b0b875 | ||
|
|
f62183473c | ||
|
|
aeb4b96560 | ||
|
|
b413760136 | ||
|
|
45882343e6 | ||
|
|
4c1f24da82 | ||
|
|
8c63835971 | ||
|
|
a96a5fd2ef | ||
|
|
9a62fae012 | ||
|
|
81a10e9f89 | ||
|
|
db475bd511 | ||
|
|
eef5f27a87 | ||
|
|
790dc74581 | ||
|
|
4f36accd81 | ||
|
|
04ead57731 | ||
|
|
e9951bc34a | ||
|
|
74d13e271e | ||
|
|
9181481fc8 | ||
|
|
62ddd8fe1c | ||
|
|
7891f73cb0 | ||
|
|
a359e2248b | ||
|
|
513ffa7e0c | ||
|
|
ef6c46a231 | ||
|
|
22237a4a8d | ||
|
|
6ab8ada9ff | ||
|
|
9a756939d8 | ||
|
|
aea3baf897 | ||
|
|
f8d0f7f67a | ||
|
|
fed34273a1 | ||
|
|
34a1b30006 | ||
|
|
798abf2103 | ||
|
|
75e539f4d2 | ||
|
|
946e65d1c6 | ||
|
|
b8e36eacfd | ||
|
|
cd49c1dac7 | ||
|
|
86411a5ca5 | ||
|
|
1186cad9ca | ||
|
|
e66de86a82 | ||
|
|
1e08bfca9c | ||
|
|
64f5b96295 |
359
app/app.go
359
app/app.go
@@ -2,243 +2,184 @@ package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
"github.com/kaspanet/kaspad/infrastructure/db/database/ldb"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/infrastructure/os/signal"
|
||||
"github.com/kaspanet/kaspad/util/profiling"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"github.com/kaspanet/kaspad/app/rpc"
|
||||
"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/db/dbaccess"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/os/execenv"
|
||||
"github.com/kaspanet/kaspad/infrastructure/os/limits"
|
||||
"github.com/kaspanet/kaspad/infrastructure/os/winservice"
|
||||
)
|
||||
|
||||
// App is a wrapper for all the kaspad services
|
||||
type App struct {
|
||||
cfg *config.Config
|
||||
addressManager *addressmanager.AddressManager
|
||||
protocolManager *protocol.Manager
|
||||
rpcManager *rpc.Manager
|
||||
connectionManager *connmanager.ConnectionManager
|
||||
netAdapter *netadapter.NetAdapter
|
||||
|
||||
started, shutdown int32
|
||||
var desiredLimits = &limits.DesiredLimits{
|
||||
FileLimitWant: 2048,
|
||||
FileLimitMin: 1024,
|
||||
}
|
||||
|
||||
// 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.netAdapter.Start()
|
||||
if err != nil {
|
||||
panics.Exit(log, fmt.Sprintf("Error starting the net adapter: %+v", err))
|
||||
}
|
||||
|
||||
a.maybeSeedFromDNS()
|
||||
|
||||
a.connectionManager.Start()
|
||||
var serviceDescription = &winservice.ServiceDescription{
|
||||
Name: "kaspadsvc",
|
||||
DisplayName: "Kaspad Service",
|
||||
Description: "Downloads and stays synchronized with the Kaspa blockDAG and " +
|
||||
"provides DAG services to applications.",
|
||||
}
|
||||
|
||||
// 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.netAdapter.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping the net adapter: %+v", err)
|
||||
}
|
||||
|
||||
err = a.addressManager.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping address manager: %s", err)
|
||||
}
|
||||
|
||||
return
|
||||
type kaspadApp struct {
|
||||
cfg *config.Config
|
||||
}
|
||||
|
||||
// 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)
|
||||
// StartApp starts the kaspad app, and blocks until it finishes running
|
||||
func StartApp() error {
|
||||
execenv.Initialize(desiredLimits)
|
||||
|
||||
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
|
||||
|
||||
// Create a new block DAG instance with the appropriate configuration.
|
||||
dag, err := setupDAG(cfg, databaseContext, interrupt, sigCache, indexManager)
|
||||
// Load configuration and parse command line. This function also
|
||||
// initializes logging and configures it accordingly.
|
||||
cfg, err := config.LoadConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
fmt.Fprintln(os.Stderr, err)
|
||||
return err
|
||||
}
|
||||
defer panics.HandlePanic(log, "MAIN", nil)
|
||||
|
||||
txMempool := setupMempool(cfg, dag, sigCache)
|
||||
app := &kaspadApp{cfg: cfg}
|
||||
|
||||
netAdapter, err := netadapter.NewNetAdapter(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
addressManager, err := addressmanager.New(cfg, databaseContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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
|
||||
}
|
||||
rpcManager := setupRPC(cfg, txMempool, dag, sigCache, netAdapter, protocolManager, connectionManager, addressManager, acceptanceIndex)
|
||||
|
||||
return &App{
|
||||
cfg: cfg,
|
||||
protocolManager: protocolManager,
|
||||
rpcManager: rpcManager,
|
||||
connectionManager: connectionManager,
|
||||
netAdapter: netAdapter,
|
||||
addressManager: addressManager,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func setupRPC(
|
||||
cfg *config.Config,
|
||||
txMempool *mempool.TxPool,
|
||||
dag *blockdag.BlockDAG,
|
||||
sigCache *txscript.SigCache,
|
||||
netAdapter *netadapter.NetAdapter,
|
||||
protocolManager *protocol.Manager,
|
||||
connectionManager *connmanager.ConnectionManager,
|
||||
addressManager *addressmanager.AddressManager,
|
||||
acceptanceIndex *indexers.AcceptanceIndex) *rpc.Manager {
|
||||
|
||||
blockTemplateGenerator := mining.NewBlkTmplGenerator(&mining.Policy{BlockMaxMass: cfg.BlockMaxMass}, txMempool, dag, sigCache)
|
||||
rpcManager := rpc.NewManager(cfg, netAdapter, dag, protocolManager, connectionManager, blockTemplateGenerator, txMempool, addressManager, acceptanceIndex)
|
||||
protocolManager.SetOnBlockAddedToDAGHandler(rpcManager.NotifyBlockAddedToDAG)
|
||||
protocolManager.SetOnTransactionAddedToMempoolHandler(rpcManager.NotifyTransactionAddedToMempool)
|
||||
dag.Subscribe(func(notification *blockdag.Notification) {
|
||||
err := handleBlockDAGNotifications(notification, acceptanceIndex, rpcManager)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
})
|
||||
return rpcManager
|
||||
}
|
||||
|
||||
func handleBlockDAGNotifications(notification *blockdag.Notification,
|
||||
acceptanceIndex *indexers.AcceptanceIndex, rpcManager *rpc.Manager) error {
|
||||
|
||||
if notification.Type == blockdag.NTChainChanged && acceptanceIndex != nil {
|
||||
chainChangedNotificationData := notification.Data.(*blockdag.ChainChangedNotificationData)
|
||||
err := rpcManager.NotifyChainChanged(chainChangedNotificationData.RemovedChainBlockHashes,
|
||||
chainChangedNotificationData.AddedChainBlockHashes)
|
||||
// Call serviceMain on Windows to handle running as a service. When
|
||||
// the return isService flag is true, exit now since we ran as a
|
||||
// service. Otherwise, just fall through to normal operation.
|
||||
if runtime.GOOS == "windows" {
|
||||
isService, err := winservice.WinServiceMain(app.main, serviceDescription, cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isService {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return app.main(nil)
|
||||
}
|
||||
|
||||
func (app *kaspadApp) main(startedChan chan<- struct{}) error {
|
||||
// Get a channel that will be closed when a shutdown signal has been
|
||||
// triggered either from an OS signal such as SIGINT (Ctrl+C) or from
|
||||
// another subsystem such as the RPC server.
|
||||
interrupt := signal.InterruptListener()
|
||||
defer log.Info("Shutdown complete")
|
||||
|
||||
// Show version at startup.
|
||||
log.Infof("Version %s", version.Version())
|
||||
|
||||
// Enable http profiling server if requested.
|
||||
if app.cfg.Profile != "" {
|
||||
profiling.Start(app.cfg.Profile, log)
|
||||
}
|
||||
|
||||
// Perform upgrades to kaspad as new versions require it.
|
||||
if err := doUpgrades(); err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
// Return now if an interrupt signal was triggered.
|
||||
if signal.InterruptRequested(interrupt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if app.cfg.ResetDatabase {
|
||||
err := removeDatabase(app.cfg)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Open the database
|
||||
databaseContext, err := openDB(app.cfg)
|
||||
if err != nil {
|
||||
log.Error(err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
log.Infof("Gracefully shutting down the database...")
|
||||
err := databaseContext.Close()
|
||||
if err != nil {
|
||||
log.Errorf("Failed to close the database: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// Return now if an interrupt signal was triggered.
|
||||
if signal.InterruptRequested(interrupt) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create componentManager and start it.
|
||||
componentManager, err := NewComponentManager(app.cfg, databaseContext, interrupt)
|
||||
if err != nil {
|
||||
log.Errorf("Unable to start kaspad: %+v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
defer func() {
|
||||
log.Infof("Gracefully shutting down kaspad...")
|
||||
|
||||
shutdownDone := make(chan struct{})
|
||||
go func() {
|
||||
componentManager.Stop()
|
||||
shutdownDone <- struct{}{}
|
||||
}()
|
||||
|
||||
const shutdownTimeout = 2 * time.Minute
|
||||
|
||||
select {
|
||||
case <-shutdownDone:
|
||||
case <-time.After(shutdownTimeout):
|
||||
log.Criticalf("Graceful shutdown timed out %s. Terminating...", shutdownTimeout)
|
||||
}
|
||||
log.Infof("Kaspad shutdown complete")
|
||||
}()
|
||||
|
||||
componentManager.Start()
|
||||
|
||||
if startedChan != nil {
|
||||
startedChan <- struct{}{}
|
||||
}
|
||||
|
||||
// Wait until the interrupt signal is received from an OS signal or
|
||||
// shutdown is requested through one of the subsystems such as the RPC
|
||||
// server.
|
||||
<-interrupt
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) maybeSeedFromDNS() {
|
||||
if !a.cfg.DisableDNSSeed {
|
||||
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, appmessage.SFNodeNetwork, false, nil,
|
||||
a.cfg.Lookup, func(addresses []*appmessage.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)
|
||||
})
|
||||
}
|
||||
|
||||
if a.cfg.GRPCSeed != "" {
|
||||
dnsseed.SeedFromGRPC(a.cfg.NetParams(), a.cfg.GRPCSeed, appmessage.SFNodeNetwork, false, nil,
|
||||
func(addresses []*appmessage.NetAddress) {
|
||||
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
|
||||
// doUpgrades performs upgrades to kaspad as new versions require it.
|
||||
// currently it's a placeholder we got from kaspad upstream, that does nothing
|
||||
func doUpgrades() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
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
|
||||
// dbPath returns the path to the block database given a database type.
|
||||
func databasePath(cfg *config.Config) string {
|
||||
return filepath.Join(cfg.DataDir, "db")
|
||||
}
|
||||
|
||||
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 removeDatabase(cfg *config.Config) error {
|
||||
dbPath := databasePath(cfg)
|
||||
return os.RemoveAll(dbPath)
|
||||
}
|
||||
|
||||
// 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
|
||||
func openDB(cfg *config.Config) (database.Database, error) {
|
||||
dbPath := databasePath(cfg)
|
||||
log.Infof("Loading database from '%s'", dbPath)
|
||||
return ldb.NewLevelDB(dbPath)
|
||||
}
|
||||
|
||||
@@ -1,431 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// genesisCoinbaseTx is the coinbase transaction for the genesis blocks for
|
||||
// the main network and test network.
|
||||
var genesisCoinbaseTxIns = []*TxIn{
|
||||
{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x45, /* |.......E| */
|
||||
0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, /* |The Time| */
|
||||
0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61, 0x6e, /* |s 03/Jan| */
|
||||
0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, /* |/2009 Ch| */
|
||||
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x72, /* |ancellor| */
|
||||
0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69, 0x6e, /* | on brin| */
|
||||
0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, /* |k of sec|*/
|
||||
0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6c, /* |ond bail| */
|
||||
0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, /* |out for |*/
|
||||
0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */
|
||||
},
|
||||
Sequence: math.MaxUint64,
|
||||
},
|
||||
}
|
||||
var genesisCoinbaseTxOuts = []*TxOut{
|
||||
{
|
||||
Value: 0x12a05f200,
|
||||
ScriptPubKey: []byte{
|
||||
0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, /* |A.g....U| */
|
||||
0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, /* |H'.g..q0| */
|
||||
0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, /* |..\..(.9| */
|
||||
0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, /* |..yb...a| */
|
||||
0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, /* |..I..?L.| */
|
||||
0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, /* |8..U....| */
|
||||
0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, /* |..\8M...| */
|
||||
0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, /* |.W.Lp+k.| */
|
||||
0x1d, 0x5f, 0xac, /* |._.| */
|
||||
},
|
||||
},
|
||||
}
|
||||
var genesisCoinbaseTx = NewNativeMsgTx(1, genesisCoinbaseTxIns, genesisCoinbaseTxOuts)
|
||||
|
||||
// BenchmarkWriteVarInt1 performs a benchmark on how long it takes to write
|
||||
// a single byte variable length integer.
|
||||
func BenchmarkWriteVarInt1(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 1)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarInt3 performs a benchmark on how long it takes to write
|
||||
// a three byte variable length integer.
|
||||
func BenchmarkWriteVarInt3(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 65535)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarInt5 performs a benchmark on how long it takes to write
|
||||
// a five byte variable length integer.
|
||||
func BenchmarkWriteVarInt5(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 4294967295)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarInt9 performs a benchmark on how long it takes to write
|
||||
// a nine byte variable length integer.
|
||||
func BenchmarkWriteVarInt9(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarInt(ioutil.Discard, 18446744073709551615)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt1 performs a benchmark on how long it takes to read
|
||||
// a single byte variable length integer.
|
||||
func BenchmarkReadVarInt1(b *testing.B) {
|
||||
buf := []byte{0x01}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt3 performs a benchmark on how long it takes to read
|
||||
// a three byte variable length integer.
|
||||
func BenchmarkReadVarInt3(b *testing.B) {
|
||||
buf := []byte{0x0fd, 0xff, 0xff}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt5 performs a benchmark on how long it takes to read
|
||||
// a five byte variable length integer.
|
||||
func BenchmarkReadVarInt5(b *testing.B) {
|
||||
buf := []byte{0xfe, 0xff, 0xff, 0xff, 0xff}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarInt9 performs a benchmark on how long it takes to read
|
||||
// a nine byte variable length integer.
|
||||
func BenchmarkReadVarInt9(b *testing.B) {
|
||||
buf := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarInt(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarStr4 performs a benchmark on how long it takes to read a
|
||||
// four byte variable length string.
|
||||
func BenchmarkReadVarStr4(b *testing.B) {
|
||||
buf := []byte{0x04, 't', 'e', 's', 't'}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarString(r, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadVarStr10 performs a benchmark on how long it takes to read a
|
||||
// ten byte variable length string.
|
||||
func BenchmarkReadVarStr10(b *testing.B) {
|
||||
buf := []byte{0x0a, 't', 'e', 's', 't', '0', '1', '2', '3', '4', '5'}
|
||||
r := bytes.NewReader(buf)
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
ReadVarString(r, 0)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarStr4 performs a benchmark on how long it takes to write a
|
||||
// four byte variable length string.
|
||||
func BenchmarkWriteVarStr4(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarString(ioutil.Discard, "test")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteVarStr10 performs a benchmark on how long it takes to write a
|
||||
// ten byte variable length string.
|
||||
func BenchmarkWriteVarStr10(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteVarString(ioutil.Discard, "test012345")
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadOutpoint performs a benchmark on how long it takes to read a
|
||||
// transaction outpoint.
|
||||
func BenchmarkReadOutpoint(b *testing.B) {
|
||||
buf := []byte{
|
||||
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, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Previous output index
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var op Outpoint
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readOutpoint(r, 0, 0, &op)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteOutpoint performs a benchmark on how long it takes to write a
|
||||
// transaction outpoint.
|
||||
func BenchmarkWriteOutpoint(b *testing.B) {
|
||||
op := &Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0,
|
||||
}
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeOutpoint(ioutil.Discard, 0, 0, op)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadTxOut performs a benchmark on how long it takes to read a
|
||||
// transaction output.
|
||||
func BenchmarkReadTxOut(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||
0xee, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var txOut TxOut
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readTxOut(r, 0, 0, &txOut)
|
||||
scriptPool.Return(txOut.ScriptPubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteTxOut performs a benchmark on how long it takes to write
|
||||
// a transaction output.
|
||||
func BenchmarkWriteTxOut(b *testing.B) {
|
||||
txOut := blockOne.Transactions[0].TxOut[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
WriteTxOut(ioutil.Discard, 0, 0, txOut)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadTxIn performs a benchmark on how long it takes to read a
|
||||
// transaction input.
|
||||
func BenchmarkReadTxIn(b *testing.B) {
|
||||
buf := []byte{
|
||||
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, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Previous output index
|
||||
0x07, // Varint for length of signature script
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var txIn TxIn
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readTxIn(r, 0, 0, &txIn)
|
||||
scriptPool.Return(txIn.SignatureScript)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteTxIn performs a benchmark on how long it takes to write
|
||||
// a transaction input.
|
||||
func BenchmarkWriteTxIn(b *testing.B) {
|
||||
txIn := blockOne.Transactions[0].TxIn[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeTxIn(ioutil.Discard, 0, 0, txIn, txEncodingFull)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDeserializeTx performs a benchmark on how long it takes to
|
||||
// deserialize a small transaction.
|
||||
func BenchmarkDeserializeTxSmall(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
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, // // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0x07, // Varint for length of signature script
|
||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
0x01, // Varint for number of output transactions
|
||||
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c,
|
||||
0x72, 0x6a, 0x2c, 0x91, 0xe6, 0x1e, 0xc1, 0x16,
|
||||
0x00, 0xae, 0x13, 0x90, 0x81, 0x3a, 0x62, 0x7c,
|
||||
0x66, 0xfb, 0x8b, 0xe7, 0x94, 0x7b, 0xe6, 0x3c,
|
||||
0x52, 0xda, 0x75, 0x89, 0x37, 0x95, 0x15, 0xd4,
|
||||
0xe0, 0xa6, 0x04, 0xf8, 0x14, 0x17, 0x81, 0xe6,
|
||||
0x22, 0x94, 0x72, 0x11, 0x66, 0xbf, 0x62, 0x1e,
|
||||
0x73, 0xa8, 0x2c, 0xbf, 0x23, 0x42, 0xc8, 0x58,
|
||||
0xee, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
var tx MsgTx
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
tx.Deserialize(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDeserializeTxLarge performs a benchmark on how long it takes to
|
||||
// deserialize a very large transaction.
|
||||
func BenchmarkDeserializeTxLarge(b *testing.B) {
|
||||
fi, err := os.Open("testdata/megatx.bin.bz2")
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to read transaction data: %v", err)
|
||||
}
|
||||
defer fi.Close()
|
||||
buf, err := ioutil.ReadAll(bzip2.NewReader(fi))
|
||||
if err != nil {
|
||||
b.Fatalf("Failed to read transaction data: %v", err)
|
||||
}
|
||||
|
||||
r := bytes.NewReader(buf)
|
||||
var tx MsgTx
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
tx.Deserialize(r)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSerializeTx performs a benchmark on how long it takes to serialize
|
||||
// a transaction.
|
||||
func BenchmarkSerializeTx(b *testing.B) {
|
||||
tx := blockOne.Transactions[0]
|
||||
for i := 0; i < b.N; i++ {
|
||||
tx.Serialize(ioutil.Discard)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkReadBlockHeader performs a benchmark on how long it takes to
|
||||
// deserialize a block header.
|
||||
func BenchmarkReadBlockHeader(b *testing.B) {
|
||||
buf := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, // PrevBlock
|
||||
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
||||
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a, // MerkleRoot
|
||||
0x29, 0xab, 0x5f, 0x49, 0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
0x00, // TxnCount Varint
|
||||
}
|
||||
r := bytes.NewReader(buf)
|
||||
var header BlockHeader
|
||||
for i := 0; i < b.N; i++ {
|
||||
r.Seek(0, 0)
|
||||
readBlockHeader(r, 0, &header)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWriteBlockHeader performs a benchmark on how long it takes to
|
||||
// serialize a block header.
|
||||
func BenchmarkWriteBlockHeader(b *testing.B) {
|
||||
header := blockOne.Header
|
||||
for i := 0; i < b.N; i++ {
|
||||
writeBlockHeader(ioutil.Discard, 0, &header)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkTxHash performs a benchmark on how long it takes to hash a
|
||||
// transaction.
|
||||
func BenchmarkTxHash(b *testing.B) {
|
||||
for i := 0; i < b.N; i++ {
|
||||
genesisCoinbaseTx.TxHash()
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDoubleHashB performs a benchmark on how long it takes to perform a
|
||||
// double hash returning a byte slice.
|
||||
func BenchmarkDoubleHashB(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
if err := genesisCoinbaseTx.Serialize(&buf); err != nil {
|
||||
b.Errorf("Serialize: unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
txBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = daghash.DoubleHashB(txBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDoubleHashH performs a benchmark on how long it takes to perform
|
||||
// a double hash returning a daghash.Hash.
|
||||
func BenchmarkDoubleHashH(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
if err := genesisCoinbaseTx.Serialize(&buf); err != nil {
|
||||
b.Errorf("Serialize: unexpected error: %v", err)
|
||||
return
|
||||
}
|
||||
txBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_ = daghash.DoubleHashH(txBytes)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkDoubleHashWriter performs a benchmark on how long it takes to perform
|
||||
// a double hash via the writer returning a daghash.Hash.
|
||||
func BenchmarkDoubleHashWriter(b *testing.B) {
|
||||
var buf bytes.Buffer
|
||||
err := genesisCoinbaseTx.Serialize(&buf)
|
||||
if err != nil {
|
||||
b.Fatalf("Serialize: unexpected error: %+v", err)
|
||||
}
|
||||
txBytes := buf.Bytes()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
_, _ = writer.Write(txBytes)
|
||||
writer.Finalize()
|
||||
}
|
||||
}
|
||||
@@ -5,16 +5,14 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
)
|
||||
|
||||
// MaxVarIntPayload is the maximum payload size for a variable length integer.
|
||||
@@ -23,16 +21,6 @@ const MaxVarIntPayload = 9
|
||||
// MaxInvPerMsg is the maximum number of inventory vectors that can be in any type of kaspa inv message.
|
||||
const MaxInvPerMsg = 1 << 17
|
||||
|
||||
var (
|
||||
// littleEndian is a convenience variable since binary.LittleEndian is
|
||||
// quite long.
|
||||
littleEndian = binary.LittleEndian
|
||||
|
||||
// bigEndian is a convenience variable since binary.BigEndian is quite
|
||||
// long.
|
||||
bigEndian = binary.BigEndian
|
||||
)
|
||||
|
||||
// errNonCanonicalVarInt is the common format string used for non-canonically
|
||||
// encoded variable length integer errors.
|
||||
var errNonCanonicalVarInt = "non-canonical varint %x - discriminant %x must " +
|
||||
@@ -53,7 +41,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
// type assertions first.
|
||||
switch e := element.(type) {
|
||||
case *int32:
|
||||
rv, err := binaryserializer.Uint32(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -61,7 +49,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
return nil
|
||||
|
||||
case *uint32:
|
||||
rv, err := binaryserializer.Uint32(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -69,7 +57,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
return nil
|
||||
|
||||
case *int64:
|
||||
rv, err := binaryserializer.Uint64(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint64(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -77,7 +65,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
return nil
|
||||
|
||||
case *uint64:
|
||||
rv, err := binaryserializer.Uint64(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint64(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -106,7 +94,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
|
||||
// Unix timestamp encoded as an int64.
|
||||
case *int64Time:
|
||||
rv, err := binaryserializer.Uint64(r, binary.LittleEndian)
|
||||
rv, err := binaryserializer.Uint64(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -123,7 +111,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
|
||||
// Message header command.
|
||||
case *MessageCommand:
|
||||
rv, err := binaryserializer.Uint32(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -138,7 +126,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
}
|
||||
return nil
|
||||
|
||||
case *daghash.Hash:
|
||||
case *externalapi.DomainHash:
|
||||
_, err := io.ReadFull(r, e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -148,7 +136,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
case *id.ID:
|
||||
return e.Deserialize(r)
|
||||
|
||||
case *subnetworkid.SubnetworkID:
|
||||
case *externalapi.DomainSubnetworkID:
|
||||
_, err := io.ReadFull(r, e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -156,7 +144,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
return nil
|
||||
|
||||
case *ServiceFlag:
|
||||
rv, err := binaryserializer.Uint64(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint64(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -164,7 +152,7 @@ func ReadElement(r io.Reader, element interface{}) error {
|
||||
return nil
|
||||
|
||||
case *KaspaNet:
|
||||
rv, err := binaryserializer.Uint32(r, littleEndian)
|
||||
rv, err := binaryserializer.Uint32(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -193,28 +181,28 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
// type assertions first.
|
||||
switch e := element.(type) {
|
||||
case int32:
|
||||
err := binaryserializer.PutUint32(w, littleEndian, uint32(e))
|
||||
err := binaryserializer.PutUint32(w, uint32(e))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case uint32:
|
||||
err := binaryserializer.PutUint32(w, littleEndian, e)
|
||||
err := binaryserializer.PutUint32(w, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case int64:
|
||||
err := binaryserializer.PutUint64(w, littleEndian, uint64(e))
|
||||
err := binaryserializer.PutUint64(w, uint64(e))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case uint64:
|
||||
err := binaryserializer.PutUint64(w, littleEndian, e)
|
||||
err := binaryserializer.PutUint64(w, e)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -249,7 +237,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
|
||||
// Message header command.
|
||||
case MessageCommand:
|
||||
err := binaryserializer.PutUint32(w, littleEndian, uint32(e))
|
||||
err := binaryserializer.PutUint32(w, uint32(e))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -263,7 +251,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
}
|
||||
return nil
|
||||
|
||||
case *daghash.Hash:
|
||||
case *externalapi.DomainHash:
|
||||
_, err := w.Write(e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -273,7 +261,7 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
case *id.ID:
|
||||
return e.Serialize(w)
|
||||
|
||||
case *subnetworkid.SubnetworkID:
|
||||
case *externalapi.DomainSubnetworkID:
|
||||
_, err := w.Write(e[:])
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -281,14 +269,14 @@ func WriteElement(w io.Writer, element interface{}) error {
|
||||
return nil
|
||||
|
||||
case ServiceFlag:
|
||||
err := binaryserializer.PutUint64(w, littleEndian, uint64(e))
|
||||
err := binaryserializer.PutUint64(w, uint64(e))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
||||
case KaspaNet:
|
||||
err := binaryserializer.PutUint32(w, littleEndian, uint32(e))
|
||||
err := binaryserializer.PutUint32(w, uint32(e))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -320,7 +308,7 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
var rv uint64
|
||||
switch discriminant {
|
||||
case 0xff:
|
||||
sv, err := binaryserializer.Uint64(r, littleEndian)
|
||||
sv, err := binaryserializer.Uint64(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -335,7 +323,7 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
}
|
||||
|
||||
case 0xfe:
|
||||
sv, err := binaryserializer.Uint32(r, littleEndian)
|
||||
sv, err := binaryserializer.Uint32(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -350,7 +338,7 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
}
|
||||
|
||||
case 0xfd:
|
||||
sv, err := binaryserializer.Uint16(r, littleEndian)
|
||||
sv, err := binaryserializer.Uint16(r)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@@ -370,142 +358,3 @@ func ReadVarInt(r io.Reader) (uint64, error) {
|
||||
|
||||
return rv, nil
|
||||
}
|
||||
|
||||
// WriteVarInt serializes val to w using a variable number of bytes depending
|
||||
// on its value.
|
||||
func WriteVarInt(w io.Writer, val uint64) error {
|
||||
if val < 0xfd {
|
||||
return binaryserializer.PutUint8(w, uint8(val))
|
||||
}
|
||||
|
||||
if val <= math.MaxUint16 {
|
||||
err := binaryserializer.PutUint8(w, 0xfd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binaryserializer.PutUint16(w, littleEndian, uint16(val))
|
||||
}
|
||||
|
||||
if val <= math.MaxUint32 {
|
||||
err := binaryserializer.PutUint8(w, 0xfe)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binaryserializer.PutUint32(w, littleEndian, uint32(val))
|
||||
}
|
||||
|
||||
err := binaryserializer.PutUint8(w, 0xff)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return binaryserializer.PutUint64(w, littleEndian, val)
|
||||
}
|
||||
|
||||
// VarIntSerializeSize returns the number of bytes it would take to serialize
|
||||
// val as a variable length integer.
|
||||
func VarIntSerializeSize(val uint64) int {
|
||||
// The value is small enough to be represented by itself, so it's
|
||||
// just 1 byte.
|
||||
if val < 0xfd {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Discriminant 1 byte plus 2 bytes for the uint16.
|
||||
if val <= math.MaxUint16 {
|
||||
return 3
|
||||
}
|
||||
|
||||
// Discriminant 1 byte plus 4 bytes for the uint32.
|
||||
if val <= math.MaxUint32 {
|
||||
return 5
|
||||
}
|
||||
|
||||
// Discriminant 1 byte plus 8 bytes for the uint64.
|
||||
return 9
|
||||
}
|
||||
|
||||
// ReadVarString reads a variable length string from r and returns it as a Go
|
||||
// string. A variable length string is encoded as a variable length integer
|
||||
// containing the length of the string followed by the bytes that represent the
|
||||
// string itself. An error is returned if the length is greater than the
|
||||
// maximum block payload size since it helps protect against memory exhaustion
|
||||
// attacks and forced panics through malformed messages.
|
||||
func ReadVarString(r io.Reader, pver uint32) (string, error) {
|
||||
count, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// Prevent variable length strings that are larger than the maximum
|
||||
// message size. It would be possible to cause memory exhaustion and
|
||||
// panics without a sane upper bound on this count.
|
||||
if count > MaxMessagePayload {
|
||||
str := fmt.Sprintf("variable length string is too long "+
|
||||
"[count %d, max %d]", count, MaxMessagePayload)
|
||||
return "", messageError("ReadVarString", str)
|
||||
}
|
||||
|
||||
buf := make([]byte, count)
|
||||
_, err = io.ReadFull(r, buf)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(buf), nil
|
||||
}
|
||||
|
||||
// WriteVarString serializes str to w as a variable length integer containing
|
||||
// the length of the string followed by the bytes that represent the string
|
||||
// itself.
|
||||
func WriteVarString(w io.Writer, str string) error {
|
||||
err := WriteVarInt(w, uint64(len(str)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = w.Write([]byte(str))
|
||||
return err
|
||||
}
|
||||
|
||||
// ReadVarBytes reads a variable length byte array. A byte array is encoded
|
||||
// as a varInt containing the length of the array followed by the bytes
|
||||
// themselves. An error is returned if the length is greater than the
|
||||
// passed maxAllowed parameter which helps protect against memory exhaustion
|
||||
// attacks and forced panics through malformed messages. The fieldName
|
||||
// parameter is only used for the error message so it provides more context in
|
||||
// the error.
|
||||
func ReadVarBytes(r io.Reader, pver uint32, maxAllowed uint32,
|
||||
fieldName string) ([]byte, error) {
|
||||
|
||||
count, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prevent byte array larger than the max message size. It would
|
||||
// be possible to cause memory exhaustion and panics without a sane
|
||||
// upper bound on this count.
|
||||
if count > uint64(maxAllowed) {
|
||||
str := fmt.Sprintf("%s is larger than the max allowed size "+
|
||||
"[count %d, max %d]", fieldName, count, maxAllowed)
|
||||
return nil, messageError("ReadVarBytes", str)
|
||||
}
|
||||
|
||||
b := make([]byte, count)
|
||||
_, err = io.ReadFull(r, b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// WriteVarBytes serializes a variable length byte array to w as a varInt
|
||||
// containing the number of bytes, followed by the bytes themselves.
|
||||
func WriteVarBytes(w io.Writer, pver uint32, bytes []byte) error {
|
||||
slen := uint64(len(bytes))
|
||||
err := WriteVarInt(w, slen)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(bytes)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -6,19 +6,19 @@ package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// mainnetGenesisHash is the hash of the first block in the block DAG for the
|
||||
// main network (genesis block).
|
||||
var mainnetGenesisHash = &daghash.Hash{
|
||||
var mainnetGenesisHash = &externalapi.DomainHash{
|
||||
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25,
|
||||
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
|
||||
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
|
||||
@@ -27,30 +27,30 @@ var mainnetGenesisHash = &daghash.Hash{
|
||||
|
||||
// simnetGenesisHash is the hash of the first block in the block DAG for the
|
||||
// simulation test network.
|
||||
var simnetGenesisHash = &daghash.Hash{
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a,
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
var simnetGenesisHash = &externalapi.DomainHash{
|
||||
0x9d, 0x89, 0xb0, 0x6e, 0xb3, 0x47, 0xb5, 0x6e,
|
||||
0xcd, 0x6c, 0x63, 0x99, 0x45, 0x91, 0xd5, 0xce,
|
||||
0x9b, 0x43, 0x05, 0xc1, 0xa5, 0x5e, 0x2a, 0xda,
|
||||
0x90, 0x4c, 0xf0, 0x6c, 0x4d, 0x5f, 0xd3, 0x62,
|
||||
}
|
||||
|
||||
// mainnetGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||
// block for the main network.
|
||||
var mainnetGenesisMerkleRoot = &daghash.Hash{
|
||||
var mainnetGenesisMerkleRoot = &externalapi.DomainHash{
|
||||
0x4a, 0x5e, 0x1e, 0x4b, 0xaa, 0xb8, 0x9f, 0x3a,
|
||||
0x32, 0x51, 0x8a, 0x88, 0xc3, 0x1b, 0xc8, 0x7f,
|
||||
0x61, 0x8f, 0x76, 0x67, 0x3e, 0x2c, 0xc7, 0x7a,
|
||||
0xb2, 0x12, 0x7b, 0x7a, 0xfd, 0xed, 0xa3, 0x3b,
|
||||
}
|
||||
|
||||
var exampleAcceptedIDMerkleRoot = &daghash.Hash{
|
||||
var exampleAcceptedIDMerkleRoot = &externalapi.DomainHash{
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C,
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
}
|
||||
|
||||
var exampleUTXOCommitment = &daghash.Hash{
|
||||
var exampleUTXOCommitment = &externalapi.DomainHash{
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C,
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
@@ -104,7 +104,7 @@ func TestElementEncoding(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
(*daghash.Hash)(&[daghash.HashSize]byte{
|
||||
(*externalapi.DomainHash)(&[externalapi.DomainHashSize]byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
@@ -193,7 +193,7 @@ func TestElementEncodingErrors(t *testing.T) {
|
||||
0, io.ErrShortWrite, io.EOF,
|
||||
},
|
||||
{
|
||||
(*daghash.Hash)(&[daghash.HashSize]byte{
|
||||
(*externalapi.DomainHash)(&[externalapi.DomainHashSize]byte{
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10,
|
||||
0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
|
||||
@@ -232,464 +232,3 @@ func TestElementEncodingErrors(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarIntEncoding tests appmessage encode and decode for variable length integers.
|
||||
func TestVarIntEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
value uint64 // Value to encode
|
||||
buf []byte // Encoded value
|
||||
}{
|
||||
// Latest protocol version.
|
||||
// Single byte
|
||||
{0, []byte{0x00}},
|
||||
// Max single byte
|
||||
{0xfc, []byte{0xfc}},
|
||||
// Min 2-byte
|
||||
{0xfd, []byte{0xfd, 0x0fd, 0x00}},
|
||||
// Max 2-byte
|
||||
{0xffff, []byte{0xfd, 0xff, 0xff}},
|
||||
// Min 4-byte
|
||||
{0x10000, []byte{0xfe, 0x00, 0x00, 0x01, 0x00}},
|
||||
// Max 4-byte
|
||||
{0xffffffff, []byte{0xfe, 0xff, 0xff, 0xff, 0xff}},
|
||||
// Min 8-byte
|
||||
{
|
||||
0x100000000,
|
||||
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00},
|
||||
},
|
||||
// Max 8-byte
|
||||
{
|
||||
0xffffffffffffffff,
|
||||
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
buf := &bytes.Buffer{}
|
||||
err := WriteVarInt(buf, test.value)
|
||||
if err != nil {
|
||||
t.Errorf("WriteVarInt #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("WriteVarInt #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
val, err := ReadVarInt(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("ReadVarInt #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if val != test.value {
|
||||
t.Errorf("ReadVarInt #%d\n got: %x want: %x", i,
|
||||
val, test.value)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarIntEncodingErrors performs negative tests against appmessage encode and decode
|
||||
// of variable length integers to confirm error paths work correctly.
|
||||
func TestVarIntEncodingErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
in uint64 // Value to encode
|
||||
buf []byte // Encoded value
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force errors on discriminant.
|
||||
{0, []byte{0x00}, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force errors on 2-byte read/write.
|
||||
{0xfd, []byte{0xfd}, 0, io.ErrShortWrite, io.EOF}, // error on writing length
|
||||
{0xfd, []byte{0xfd}, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, // error on writing actual data
|
||||
// Force errors on 4-byte read/write.
|
||||
{0x10000, []byte{0xfe}, 0, io.ErrShortWrite, io.EOF}, // error on writing length
|
||||
{0x10000, []byte{0xfe}, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, // error on writing actual data
|
||||
// Force errors on 8-byte read/write.
|
||||
{0x100000000, []byte{0xff}, 0, io.ErrShortWrite, io.EOF}, // error on writing length
|
||||
{0x100000000, []byte{0xff}, 2, io.ErrShortWrite, io.ErrUnexpectedEOF}, // error on writing actual data
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteVarInt(w, test.in)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("WriteVarInt #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
_, err = ReadVarInt(r)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadVarInt #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarIntNonCanonical ensures variable length integers that are not encoded
|
||||
// canonically return the expected error.
|
||||
func TestVarIntNonCanonical(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
name string // Test name for easier identification
|
||||
in []byte // Value to decode
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
{
|
||||
"0 encoded with 3 bytes", []byte{0xfd, 0x00, 0x00},
|
||||
pver,
|
||||
},
|
||||
{
|
||||
"max single-byte value encoded with 3 bytes",
|
||||
[]byte{0xfd, 0xfc, 0x00}, pver,
|
||||
},
|
||||
{
|
||||
"0 encoded with 5 bytes",
|
||||
[]byte{0xfe, 0x00, 0x00, 0x00, 0x00}, pver,
|
||||
},
|
||||
{
|
||||
"max three-byte value encoded with 5 bytes",
|
||||
[]byte{0xfe, 0xff, 0xff, 0x00, 0x00}, pver,
|
||||
},
|
||||
{
|
||||
"0 encoded with 9 bytes",
|
||||
[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
||||
pver,
|
||||
},
|
||||
{
|
||||
"max five-byte value encoded with 9 bytes",
|
||||
[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x00, 0x00},
|
||||
pver,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
rbuf := bytes.NewReader(test.in)
|
||||
val, err := ReadVarInt(rbuf)
|
||||
if msgErr := &(MessageError{}); !errors.As(err, &msgErr) {
|
||||
t.Errorf("ReadVarInt #%d (%s) unexpected error %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
if val != 0 {
|
||||
t.Errorf("ReadVarInt #%d (%s)\n got: %d want: 0", i,
|
||||
test.name, val)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarIntEncoding tests the serialize size for variable length integers.
|
||||
func TestVarIntSerializeSize(t *testing.T) {
|
||||
tests := []struct {
|
||||
val uint64 // Value to get the serialized size for
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// Single byte
|
||||
{0, 1},
|
||||
// Max single byte
|
||||
{0xfc, 1},
|
||||
// Min 2-byte
|
||||
{0xfd, 3},
|
||||
// Max 2-byte
|
||||
{0xffff, 3},
|
||||
// Min 4-byte
|
||||
{0x10000, 5},
|
||||
// Max 4-byte
|
||||
{0xffffffff, 5},
|
||||
// Min 8-byte
|
||||
{0x100000000, 9},
|
||||
// Max 8-byte
|
||||
{0xffffffffffffffff, 9},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := VarIntSerializeSize(test.val)
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("VarIntSerializeSize #%d got: %d, want: %d", i,
|
||||
serializedSize, test.size)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarStringEncoding tests appmessage encode and decode for variable length strings.
|
||||
func TestVarStringEncoding(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// str256 is a string that takes a 2-byte varint to encode.
|
||||
str256 := strings.Repeat("test", 64)
|
||||
|
||||
tests := []struct {
|
||||
in string // String to encode
|
||||
out string // String to decoded value
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
// Empty string
|
||||
{"", "", []byte{0x00}, pver},
|
||||
// Single byte varint + string
|
||||
{"Test", "Test", append([]byte{0x04}, []byte("Test")...), pver},
|
||||
// 2-byte varint + string
|
||||
{str256, str256, append([]byte{0xfd, 0x00, 0x01}, []byte(str256)...), pver},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := WriteVarString(&buf, test.in)
|
||||
if err != nil {
|
||||
t.Errorf("WriteVarString #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("WriteVarString #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
val, err := ReadVarString(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("ReadVarString #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if val != test.out {
|
||||
t.Errorf("ReadVarString #%d\n got: %s want: %s", i,
|
||||
val, test.out)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarStringEncodingErrors performs negative tests against appmessage encode and
|
||||
// decode of variable length strings to confirm error paths work correctly.
|
||||
func TestVarStringEncodingErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// str256 is a string that takes a 2-byte varint to encode.
|
||||
str256 := strings.Repeat("test", 64)
|
||||
|
||||
tests := []struct {
|
||||
in string // Value to encode
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Latest protocol version with intentional read/write errors.
|
||||
// Force errors on empty string.
|
||||
{"", []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error on single byte varint + string.
|
||||
{"Test", []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||
// Force errors on 2-byte varint + string.
|
||||
{str256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteVarString(w, test.in)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("WriteVarString #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
_, err = ReadVarString(r, test.pver)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadVarString #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarStringOverflowErrors performs tests to ensure deserializing variable
|
||||
// length strings intentionally crafted to use large values for the string
|
||||
// length are handled properly. This could otherwise potentially be used as an
|
||||
// attack vector.
|
||||
func TestVarStringOverflowErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
err error // Expected error
|
||||
}{
|
||||
{[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
pver, &MessageError{}},
|
||||
{[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
||||
pver, &MessageError{}},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
_, err := ReadVarString(rbuf, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("ReadVarString #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// TestVarBytesEncoding tests appmessage encode and decode for variable length byte array.
|
||||
func TestVarBytesEncoding(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// bytes256 is a byte array that takes a 2-byte varint to encode.
|
||||
bytes256 := bytes.Repeat([]byte{0x01}, 256)
|
||||
|
||||
tests := []struct {
|
||||
in []byte // Byte Array to write
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
// Empty byte array
|
||||
{[]byte{}, []byte{0x00}, pver},
|
||||
// Single byte varint + byte array
|
||||
{[]byte{0x01}, []byte{0x01, 0x01}, pver},
|
||||
// 2-byte varint + byte array
|
||||
{bytes256, append([]byte{0xfd, 0x00, 0x01}, bytes256...), pver},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := WriteVarBytes(&buf, test.pver, test.in)
|
||||
if err != nil {
|
||||
t.Errorf("WriteVarBytes #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("WriteVarBytes #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
val, err := ReadVarBytes(rbuf, test.pver, MaxMessagePayload,
|
||||
"test payload")
|
||||
if err != nil {
|
||||
t.Errorf("ReadVarBytes #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("ReadVarBytes #%d\n got: %s want: %s", i,
|
||||
val, test.buf)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarBytesEncodingErrors performs negative tests against appmessage encode and
|
||||
// decode of variable length byte arrays to confirm error paths work correctly.
|
||||
func TestVarBytesEncodingErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// bytes256 is a byte array that takes a 2-byte varint to encode.
|
||||
bytes256 := bytes.Repeat([]byte{0x01}, 256)
|
||||
|
||||
tests := []struct {
|
||||
in []byte // Byte Array to write
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Latest protocol version with intentional read/write errors.
|
||||
// Force errors on empty byte array.
|
||||
{[]byte{}, []byte{0x00}, pver, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error on single byte varint + byte array.
|
||||
{[]byte{0x01, 0x02, 0x03}, []byte{0x04}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||
// Force errors on 2-byte varint + byte array.
|
||||
{bytes256, []byte{0xfd}, pver, 2, io.ErrShortWrite, io.ErrUnexpectedEOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := WriteVarBytes(w, test.pver, test.in)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("WriteVarBytes #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
_, err = ReadVarBytes(r, test.pver, MaxMessagePayload,
|
||||
"test payload")
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("ReadVarBytes #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVarBytesOverflowErrors performs tests to ensure deserializing variable
|
||||
// length byte arrays intentionally crafted to use large values for the array
|
||||
// length are handled properly. This could otherwise potentially be used as an
|
||||
// attack vector.
|
||||
func TestVarBytesOverflowErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
err error // Expected error
|
||||
}{
|
||||
{[]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
||||
pver, &MessageError{}},
|
||||
{[]byte{0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01},
|
||||
pver, &MessageError{}},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
_, err := ReadVarBytes(rbuf, test.pver, MaxMessagePayload,
|
||||
"test payload")
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("ReadVarBytes #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
155
app/appmessage/domainconverters.go
Normal file
155
app/appmessage/domainconverters.go
Normal file
@@ -0,0 +1,155 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// DomainBlockToMsgBlock converts an externalapi.DomainBlock to MsgBlock
|
||||
func DomainBlockToMsgBlock(domainBlock *externalapi.DomainBlock) *MsgBlock {
|
||||
msgTxs := make([]*MsgTx, 0, len(domainBlock.Transactions))
|
||||
for _, domainTransaction := range domainBlock.Transactions {
|
||||
msgTxs = append(msgTxs, DomainTransactionToMsgTx(domainTransaction))
|
||||
}
|
||||
return &MsgBlock{
|
||||
Header: *DomainBlockHeaderToBlockHeader(domainBlock.Header),
|
||||
Transactions: msgTxs,
|
||||
}
|
||||
}
|
||||
|
||||
// DomainBlockHeaderToBlockHeader converts an externalapi.DomainBlockHeader to MsgBlockHeader
|
||||
func DomainBlockHeaderToBlockHeader(domainBlockHeader *externalapi.DomainBlockHeader) *MsgBlockHeader {
|
||||
return &MsgBlockHeader{
|
||||
Version: domainBlockHeader.Version,
|
||||
ParentHashes: domainBlockHeader.ParentHashes,
|
||||
HashMerkleRoot: &domainBlockHeader.HashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: &domainBlockHeader.AcceptedIDMerkleRoot,
|
||||
UTXOCommitment: &domainBlockHeader.UTXOCommitment,
|
||||
Timestamp: mstime.UnixMilliseconds(domainBlockHeader.TimeInMilliseconds),
|
||||
Bits: domainBlockHeader.Bits,
|
||||
Nonce: domainBlockHeader.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
// MsgBlockToDomainBlock converts a MsgBlock to externalapi.DomainBlock
|
||||
func MsgBlockToDomainBlock(msgBlock *MsgBlock) *externalapi.DomainBlock {
|
||||
transactions := make([]*externalapi.DomainTransaction, 0, len(msgBlock.Transactions))
|
||||
for _, msgTx := range msgBlock.Transactions {
|
||||
transactions = append(transactions, MsgTxToDomainTransaction(msgTx))
|
||||
}
|
||||
|
||||
return &externalapi.DomainBlock{
|
||||
Header: BlockHeaderToDomainBlockHeader(&msgBlock.Header),
|
||||
Transactions: transactions,
|
||||
}
|
||||
}
|
||||
|
||||
// BlockHeaderToDomainBlockHeader converts a MsgBlockHeader to externalapi.DomainBlockHeader
|
||||
func BlockHeaderToDomainBlockHeader(blockHeader *MsgBlockHeader) *externalapi.DomainBlockHeader {
|
||||
return &externalapi.DomainBlockHeader{
|
||||
Version: blockHeader.Version,
|
||||
ParentHashes: blockHeader.ParentHashes,
|
||||
HashMerkleRoot: *blockHeader.HashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: *blockHeader.AcceptedIDMerkleRoot,
|
||||
UTXOCommitment: *blockHeader.UTXOCommitment,
|
||||
TimeInMilliseconds: blockHeader.Timestamp.UnixMilliseconds(),
|
||||
Bits: blockHeader.Bits,
|
||||
Nonce: blockHeader.Nonce,
|
||||
}
|
||||
}
|
||||
|
||||
// DomainTransactionToMsgTx converts an externalapi.DomainTransaction into an MsgTx
|
||||
func DomainTransactionToMsgTx(domainTransaction *externalapi.DomainTransaction) *MsgTx {
|
||||
txIns := make([]*TxIn, 0, len(domainTransaction.Inputs))
|
||||
for _, input := range domainTransaction.Inputs {
|
||||
txIns = append(txIns, domainTransactionInputToTxIn(input))
|
||||
}
|
||||
|
||||
txOuts := make([]*TxOut, 0, len(domainTransaction.Outputs))
|
||||
for _, output := range domainTransaction.Outputs {
|
||||
txOuts = append(txOuts, domainTransactionOutputToTxOut(output))
|
||||
}
|
||||
|
||||
return &MsgTx{
|
||||
Version: domainTransaction.Version,
|
||||
TxIn: txIns,
|
||||
TxOut: txOuts,
|
||||
LockTime: domainTransaction.LockTime,
|
||||
SubnetworkID: domainTransaction.SubnetworkID,
|
||||
Gas: domainTransaction.Gas,
|
||||
PayloadHash: domainTransaction.PayloadHash,
|
||||
Payload: domainTransaction.Payload,
|
||||
}
|
||||
}
|
||||
|
||||
func domainTransactionOutputToTxOut(domainTransactionOutput *externalapi.DomainTransactionOutput) *TxOut {
|
||||
return &TxOut{
|
||||
Value: domainTransactionOutput.Value,
|
||||
ScriptPubKey: domainTransactionOutput.ScriptPublicKey,
|
||||
}
|
||||
}
|
||||
|
||||
func domainTransactionInputToTxIn(domainTransactionInput *externalapi.DomainTransactionInput) *TxIn {
|
||||
return &TxIn{
|
||||
PreviousOutpoint: *domainOutpointToOutpoint(domainTransactionInput.PreviousOutpoint),
|
||||
SignatureScript: domainTransactionInput.SignatureScript,
|
||||
Sequence: domainTransactionInput.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
func domainOutpointToOutpoint(domainOutpoint externalapi.DomainOutpoint) *Outpoint {
|
||||
return NewOutpoint(
|
||||
&domainOutpoint.TransactionID,
|
||||
domainOutpoint.Index)
|
||||
}
|
||||
|
||||
// MsgTxToDomainTransaction converts an MsgTx into externalapi.DomainTransaction
|
||||
func MsgTxToDomainTransaction(msgTx *MsgTx) *externalapi.DomainTransaction {
|
||||
transactionInputs := make([]*externalapi.DomainTransactionInput, 0, len(msgTx.TxIn))
|
||||
for _, txIn := range msgTx.TxIn {
|
||||
transactionInputs = append(transactionInputs, txInToDomainTransactionInput(txIn))
|
||||
}
|
||||
|
||||
transactionOutputs := make([]*externalapi.DomainTransactionOutput, 0, len(msgTx.TxOut))
|
||||
for _, txOut := range msgTx.TxOut {
|
||||
transactionOutputs = append(transactionOutputs, txOutToDomainTransactionOutput(txOut))
|
||||
}
|
||||
|
||||
payload := make([]byte, 0)
|
||||
if msgTx.Payload != nil {
|
||||
payload = msgTx.Payload
|
||||
}
|
||||
|
||||
return &externalapi.DomainTransaction{
|
||||
Version: msgTx.Version,
|
||||
Inputs: transactionInputs,
|
||||
Outputs: transactionOutputs,
|
||||
LockTime: msgTx.LockTime,
|
||||
SubnetworkID: msgTx.SubnetworkID,
|
||||
Gas: msgTx.Gas,
|
||||
PayloadHash: msgTx.PayloadHash,
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
func txOutToDomainTransactionOutput(txOut *TxOut) *externalapi.DomainTransactionOutput {
|
||||
return &externalapi.DomainTransactionOutput{
|
||||
Value: txOut.Value,
|
||||
ScriptPublicKey: txOut.ScriptPubKey,
|
||||
}
|
||||
}
|
||||
|
||||
func txInToDomainTransactionInput(txIn *TxIn) *externalapi.DomainTransactionInput {
|
||||
return &externalapi.DomainTransactionInput{
|
||||
PreviousOutpoint: *outpointToDomainOutpoint(&txIn.PreviousOutpoint), //TODO
|
||||
SignatureScript: txIn.SignatureScript,
|
||||
Sequence: txIn.Sequence,
|
||||
}
|
||||
}
|
||||
|
||||
func outpointToDomainOutpoint(outpoint *Outpoint) *externalapi.DomainOutpoint {
|
||||
return &externalapi.DomainOutpoint{
|
||||
TransactionID: outpoint.TxID,
|
||||
Index: outpoint.Index,
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
// MaxMessagePayload is the maximum bytes a message can be regardless of other
|
||||
// individual limits imposed by messages themselves.
|
||||
const MaxMessagePayload = (1024 * 1024 * 32) // 32MB
|
||||
const MaxMessagePayload = 1024 * 1024 * 32 // 32MB
|
||||
|
||||
// MessageCommand is a number in the header of a message that represents its type.
|
||||
type MessageCommand uint32
|
||||
@@ -34,24 +34,27 @@ const (
|
||||
CmdVerAck
|
||||
CmdRequestAddresses
|
||||
CmdAddresses
|
||||
CmdRequestIBDBlocks
|
||||
CmdRequestHeaders
|
||||
CmdBlock
|
||||
CmdTx
|
||||
CmdPing
|
||||
CmdPong
|
||||
CmdRequestBlockLocator
|
||||
CmdBlockLocator
|
||||
CmdSelectedTip
|
||||
CmdRequestSelectedTip
|
||||
CmdInvRelayBlock
|
||||
CmdRequestRelayBlocks
|
||||
CmdInvTransaction
|
||||
CmdRequestTransactions
|
||||
CmdIBDBlock
|
||||
CmdRequestNextIBDBlocks
|
||||
CmdDoneIBDBlocks
|
||||
CmdDoneHeaders
|
||||
CmdTransactionNotFound
|
||||
CmdReject
|
||||
CmdHeader
|
||||
CmdRequestNextHeaders
|
||||
CmdRequestIBDRootUTXOSetAndBlock
|
||||
CmdIBDRootUTXOSetAndBlock
|
||||
CmdRequestIBDBlocks
|
||||
CmdIBDRootNotFound
|
||||
|
||||
// rpc
|
||||
CmdGetCurrentNetworkRequestMessage
|
||||
@@ -91,73 +94,98 @@ const (
|
||||
CmdGetBlockCountResponseMessage
|
||||
CmdGetBlockDAGInfoRequestMessage
|
||||
CmdGetBlockDAGInfoResponseMessage
|
||||
CmdResolveFinalityConflictRequestMessage
|
||||
CmdResolveFinalityConflictResponseMessage
|
||||
CmdNotifyFinalityConflictsRequestMessage
|
||||
CmdNotifyFinalityConflictsResponseMessage
|
||||
CmdFinalityConflictNotificationMessage
|
||||
CmdFinalityConflictResolvedNotificationMessage
|
||||
CmdGetMempoolEntriesRequestMessage
|
||||
CmdGetMempoolEntriesResponseMessage
|
||||
CmdShutDownRequestMessage
|
||||
CmdShutDownResponseMessage
|
||||
CmdGetHeadersRequestMessage
|
||||
CmdGetHeadersResponseMessage
|
||||
)
|
||||
|
||||
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
||||
var ProtocolMessageCommandToString = map[MessageCommand]string{
|
||||
CmdVersion: "Version",
|
||||
CmdVerAck: "VerAck",
|
||||
CmdRequestAddresses: "RequestAddresses",
|
||||
CmdAddresses: "Addresses",
|
||||
CmdRequestIBDBlocks: "RequestBlocks",
|
||||
CmdBlock: "Block",
|
||||
CmdTx: "Tx",
|
||||
CmdPing: "Ping",
|
||||
CmdPong: "Pong",
|
||||
CmdRequestBlockLocator: "RequestBlockLocator",
|
||||
CmdBlockLocator: "BlockLocator",
|
||||
CmdSelectedTip: "SelectedTip",
|
||||
CmdRequestSelectedTip: "RequestSelectedTip",
|
||||
CmdInvRelayBlock: "InvRelayBlock",
|
||||
CmdRequestRelayBlocks: "RequestRelayBlocks",
|
||||
CmdInvTransaction: "InvTransaction",
|
||||
CmdRequestTransactions: "RequestTransactions",
|
||||
CmdIBDBlock: "IBDBlock",
|
||||
CmdRequestNextIBDBlocks: "RequestNextIBDBlocks",
|
||||
CmdDoneIBDBlocks: "DoneIBDBlocks",
|
||||
CmdTransactionNotFound: "TransactionNotFound",
|
||||
CmdReject: "Reject",
|
||||
CmdVersion: "Version",
|
||||
CmdVerAck: "VerAck",
|
||||
CmdRequestAddresses: "RequestAddresses",
|
||||
CmdAddresses: "Addresses",
|
||||
CmdRequestHeaders: "RequestHeaders",
|
||||
CmdBlock: "Block",
|
||||
CmdTx: "Tx",
|
||||
CmdPing: "Ping",
|
||||
CmdPong: "Pong",
|
||||
CmdRequestBlockLocator: "RequestBlockLocator",
|
||||
CmdBlockLocator: "BlockLocator",
|
||||
CmdInvRelayBlock: "InvRelayBlock",
|
||||
CmdRequestRelayBlocks: "RequestRelayBlocks",
|
||||
CmdInvTransaction: "InvTransaction",
|
||||
CmdRequestTransactions: "RequestTransactions",
|
||||
CmdIBDBlock: "IBDBlock",
|
||||
CmdDoneHeaders: "DoneHeaders",
|
||||
CmdTransactionNotFound: "TransactionNotFound",
|
||||
CmdReject: "Reject",
|
||||
CmdHeader: "Header",
|
||||
CmdRequestNextHeaders: "RequestNextHeaders",
|
||||
CmdRequestIBDRootUTXOSetAndBlock: "RequestPruningUTXOSetAndBlock",
|
||||
CmdIBDRootUTXOSetAndBlock: "IBDRootUTXOSetAndBlock",
|
||||
CmdRequestIBDBlocks: "RequestIBDBlocks",
|
||||
CmdIBDRootNotFound: "IBDRootNotFound",
|
||||
}
|
||||
|
||||
// RPCMessageCommandToString maps all MessageCommands to their string representation
|
||||
var RPCMessageCommandToString = map[MessageCommand]string{
|
||||
CmdGetCurrentNetworkRequestMessage: "GetCurrentNetworkRequest",
|
||||
CmdGetCurrentNetworkResponseMessage: "GetCurrentNetworkResponse",
|
||||
CmdSubmitBlockRequestMessage: "SubmitBlockRequest",
|
||||
CmdSubmitBlockResponseMessage: "SubmitBlockResponse",
|
||||
CmdGetBlockTemplateRequestMessage: "GetBlockTemplateRequest",
|
||||
CmdGetBlockTemplateResponseMessage: "GetBlockTemplateResponse",
|
||||
CmdGetBlockTemplateTransactionMessage: "CmdGetBlockTemplateTransaction",
|
||||
CmdNotifyBlockAddedRequestMessage: "NotifyBlockAddedRequest",
|
||||
CmdNotifyBlockAddedResponseMessage: "NotifyBlockAddedResponse",
|
||||
CmdBlockAddedNotificationMessage: "BlockAddedNotification",
|
||||
CmdGetPeerAddressesRequestMessage: "GetPeerAddressesRequest",
|
||||
CmdGetPeerAddressesResponseMessage: "GetPeerAddressesResponse",
|
||||
CmdGetSelectedTipHashRequestMessage: "GetSelectedTipHashRequest",
|
||||
CmdGetSelectedTipHashResponseMessage: "GetSelectedTipHashResponse",
|
||||
CmdGetMempoolEntryRequestMessage: "GetMempoolEntryRequest",
|
||||
CmdGetMempoolEntryResponseMessage: "GetMempoolEntryResponse",
|
||||
CmdGetConnectedPeerInfoRequestMessage: "GetConnectedPeerInfoRequest",
|
||||
CmdGetConnectedPeerInfoResponseMessage: "GetConnectedPeerInfoResponse",
|
||||
CmdAddPeerRequestMessage: "AddPeerRequest",
|
||||
CmdAddPeerResponseMessage: "AddPeerResponse",
|
||||
CmdSubmitTransactionRequestMessage: "SubmitTransactionRequest",
|
||||
CmdSubmitTransactionResponseMessage: "SubmitTransactionResponse",
|
||||
CmdNotifyChainChangedRequestMessage: "NotifyChainChangedRequest",
|
||||
CmdNotifyChainChangedResponseMessage: "NotifyChainChangedResponse",
|
||||
CmdChainChangedNotificationMessage: "ChainChangedNotification",
|
||||
CmdGetBlockRequestMessage: "GetBlockRequest",
|
||||
CmdGetBlockResponseMessage: "GetBlockResponse",
|
||||
CmdGetSubnetworkRequestMessage: "GetSubnetworkRequest",
|
||||
CmdGetSubnetworkResponseMessage: "GetSubnetworkResponse",
|
||||
CmdGetChainFromBlockRequestMessage: "GetChainFromBlockRequest",
|
||||
CmdGetChainFromBlockResponseMessage: "GetChainFromBlockResponse",
|
||||
CmdGetBlocksRequestMessage: "GetBlocksRequest",
|
||||
CmdGetBlocksResponseMessage: "GetBlocksResponse",
|
||||
CmdGetBlockCountRequestMessage: "GetBlockCountRequest",
|
||||
CmdGetBlockCountResponseMessage: "GetBlockCountResponse",
|
||||
CmdGetBlockDAGInfoRequestMessage: "GetBlockDAGInfoRequest",
|
||||
CmdGetBlockDAGInfoResponseMessage: "GetBlockDAGInfoResponse",
|
||||
CmdGetCurrentNetworkRequestMessage: "GetCurrentNetworkRequest",
|
||||
CmdGetCurrentNetworkResponseMessage: "GetCurrentNetworkResponse",
|
||||
CmdSubmitBlockRequestMessage: "SubmitBlockRequest",
|
||||
CmdSubmitBlockResponseMessage: "SubmitBlockResponse",
|
||||
CmdGetBlockTemplateRequestMessage: "GetBlockTemplateRequest",
|
||||
CmdGetBlockTemplateResponseMessage: "GetBlockTemplateResponse",
|
||||
CmdGetBlockTemplateTransactionMessage: "CmdGetBlockTemplateTransaction",
|
||||
CmdNotifyBlockAddedRequestMessage: "NotifyBlockAddedRequest",
|
||||
CmdNotifyBlockAddedResponseMessage: "NotifyBlockAddedResponse",
|
||||
CmdBlockAddedNotificationMessage: "BlockAddedNotification",
|
||||
CmdGetPeerAddressesRequestMessage: "GetPeerAddressesRequest",
|
||||
CmdGetPeerAddressesResponseMessage: "GetPeerAddressesResponse",
|
||||
CmdGetSelectedTipHashRequestMessage: "GetSelectedTipHashRequest",
|
||||
CmdGetSelectedTipHashResponseMessage: "GetSelectedTipHashResponse",
|
||||
CmdGetMempoolEntryRequestMessage: "GetMempoolEntryRequest",
|
||||
CmdGetMempoolEntryResponseMessage: "GetMempoolEntryResponse",
|
||||
CmdGetConnectedPeerInfoRequestMessage: "GetConnectedPeerInfoRequest",
|
||||
CmdGetConnectedPeerInfoResponseMessage: "GetConnectedPeerInfoResponse",
|
||||
CmdAddPeerRequestMessage: "AddPeerRequest",
|
||||
CmdAddPeerResponseMessage: "AddPeerResponse",
|
||||
CmdSubmitTransactionRequestMessage: "SubmitTransactionRequest",
|
||||
CmdSubmitTransactionResponseMessage: "SubmitTransactionResponse",
|
||||
CmdNotifyChainChangedRequestMessage: "NotifyChainChangedRequest",
|
||||
CmdNotifyChainChangedResponseMessage: "NotifyChainChangedResponse",
|
||||
CmdChainChangedNotificationMessage: "ChainChangedNotification",
|
||||
CmdGetBlockRequestMessage: "GetBlockRequest",
|
||||
CmdGetBlockResponseMessage: "GetBlockResponse",
|
||||
CmdGetSubnetworkRequestMessage: "GetSubnetworkRequest",
|
||||
CmdGetSubnetworkResponseMessage: "GetSubnetworkResponse",
|
||||
CmdGetChainFromBlockRequestMessage: "GetChainFromBlockRequest",
|
||||
CmdGetChainFromBlockResponseMessage: "GetChainFromBlockResponse",
|
||||
CmdGetBlocksRequestMessage: "GetBlocksRequest",
|
||||
CmdGetBlocksResponseMessage: "GetBlocksResponse",
|
||||
CmdGetBlockCountRequestMessage: "GetBlockCountRequest",
|
||||
CmdGetBlockCountResponseMessage: "GetBlockCountResponse",
|
||||
CmdGetBlockDAGInfoRequestMessage: "GetBlockDAGInfoRequest",
|
||||
CmdGetBlockDAGInfoResponseMessage: "GetBlockDAGInfoResponse",
|
||||
CmdResolveFinalityConflictRequestMessage: "ResolveFinalityConflictRequest",
|
||||
CmdResolveFinalityConflictResponseMessage: "ResolveFinalityConflictResponse",
|
||||
CmdNotifyFinalityConflictsRequestMessage: "NotifyFinalityConflictsRequest",
|
||||
CmdNotifyFinalityConflictsResponseMessage: "NotifyFinalityConflictsResponse",
|
||||
CmdFinalityConflictNotificationMessage: "FinalityConflictNotification",
|
||||
CmdFinalityConflictResolvedNotificationMessage: "FinalityConflictResolvedNotification",
|
||||
CmdGetMempoolEntriesRequestMessage: "GetMempoolEntriesRequestMessage",
|
||||
CmdGetMempoolEntriesResponseMessage: "GetMempoolEntriesResponseMessage",
|
||||
CmdGetHeadersRequestMessage: "GetHeadersRequest",
|
||||
CmdGetHeadersResponseMessage: "GetHeadersResponse",
|
||||
}
|
||||
|
||||
// Message is an interface that describes a kaspa message. A type that
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// BaseBlockHeaderPayload is the base number of bytes a block header can be,
|
||||
// not including the list of parent block headers.
|
||||
// Version 4 bytes + Timestamp 8 bytes + Bits 4 bytes + Nonce 8 bytes +
|
||||
// + NumParentBlocks 1 byte + HashMerkleRoot hash +
|
||||
// + AcceptedIDMerkleRoot hash + UTXOCommitment hash.
|
||||
// To get total size of block header len(ParentHashes) * daghash.HashSize should be
|
||||
// added to this value
|
||||
const BaseBlockHeaderPayload = 25 + 3*(daghash.HashSize)
|
||||
|
||||
// MaxNumParentBlocks is the maximum number of parent blocks a block can reference.
|
||||
// Currently set to 255 as the maximum number NumParentBlocks can be due to it being a byte
|
||||
const MaxNumParentBlocks = 255
|
||||
|
||||
// MaxBlockHeaderPayload is the maximum number of bytes a block header can be.
|
||||
// BaseBlockHeaderPayload + up to MaxNumParentBlocks hashes of parent blocks
|
||||
const MaxBlockHeaderPayload = BaseBlockHeaderPayload + (MaxNumParentBlocks * daghash.HashSize)
|
||||
|
||||
// BlockHeader defines information about a block and is used in the kaspa
|
||||
// block (MsgBlock) and headers (MsgHeader) messages.
|
||||
type BlockHeader struct {
|
||||
// Version of the block. This is not the same as the protocol version.
|
||||
Version int32
|
||||
|
||||
// Hashes of the parent block headers in the blockDAG.
|
||||
ParentHashes []*daghash.Hash
|
||||
|
||||
// HashMerkleRoot is the merkle tree reference to hash of all transactions for the block.
|
||||
HashMerkleRoot *daghash.Hash
|
||||
|
||||
// AcceptedIDMerkleRoot is merkle tree reference to hash all transactions
|
||||
// accepted form the block.Blues
|
||||
AcceptedIDMerkleRoot *daghash.Hash
|
||||
|
||||
// UTXOCommitment is an ECMH UTXO commitment to the block UTXO.
|
||||
UTXOCommitment *daghash.Hash
|
||||
|
||||
// Time the block was created.
|
||||
Timestamp mstime.Time
|
||||
|
||||
// Difficulty target for the block.
|
||||
Bits uint32
|
||||
|
||||
// Nonce used to generate the block.
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
// NumParentBlocks return the number of entries in ParentHashes
|
||||
func (h *BlockHeader) NumParentBlocks() byte {
|
||||
return byte(len(h.ParentHashes))
|
||||
}
|
||||
|
||||
// BlockHash computes the block identifier hash for the given block header.
|
||||
func (h *BlockHeader) BlockHash() *daghash.Hash {
|
||||
// Encode the header and double sha256 everything prior to the number of
|
||||
// transactions.
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
err := writeBlockHeader(writer, 0, h)
|
||||
if err != nil {
|
||||
// It seems like this could only happen if the writer returned an error.
|
||||
// and this writer should never return an error (no allocations or possible failures)
|
||||
// the only non-writer error path here is unknown types in `WriteElement`
|
||||
panic(fmt.Sprintf("BlockHash() failed. this should never fail unless BlockHeader was changed. err: %+v", err))
|
||||
}
|
||||
|
||||
res := writer.Finalize()
|
||||
return &res
|
||||
}
|
||||
|
||||
// IsGenesis returns true iff this block is a genesis block
|
||||
func (h *BlockHeader) IsGenesis() bool {
|
||||
return h.NumParentBlocks() == 0
|
||||
}
|
||||
|
||||
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Deserialize for decoding block headers stored to disk, such as in a
|
||||
// database, as opposed to decoding block headers from the appmessage.
|
||||
func (h *BlockHeader) KaspaDecode(r io.Reader, pver uint32) error {
|
||||
return readBlockHeader(r, pver, h)
|
||||
}
|
||||
|
||||
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Serialize for encoding block headers to be stored to disk, such as in a
|
||||
// database, as opposed to encoding block headers for the appmessage.
|
||||
func (h *BlockHeader) KaspaEncode(w io.Writer, pver uint32) error {
|
||||
return writeBlockHeader(w, pver, h)
|
||||
}
|
||||
|
||||
// Deserialize decodes a block header from r into the receiver using a format
|
||||
// that is suitable for long-term storage such as a database while respecting
|
||||
// the Version field.
|
||||
func (h *BlockHeader) Deserialize(r io.Reader) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of readBlockHeader.
|
||||
return readBlockHeader(r, 0, h)
|
||||
}
|
||||
|
||||
// Serialize encodes a block header from r into the receiver using a format
|
||||
// that is suitable for long-term storage such as a database while respecting
|
||||
// the Version field.
|
||||
func (h *BlockHeader) Serialize(w io.Writer) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of writeBlockHeader.
|
||||
return writeBlockHeader(w, 0, h)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// block header.
|
||||
func (h *BlockHeader) SerializeSize() int {
|
||||
return BaseBlockHeaderPayload + int(h.NumParentBlocks())*daghash.HashSize
|
||||
}
|
||||
|
||||
// NewBlockHeader returns a new BlockHeader using the provided version, previous
|
||||
// block hash, hash merkle root, accepted ID merkle root, difficulty bits, and nonce used to generate the
|
||||
// block with defaults or calclulated values for the remaining fields.
|
||||
func NewBlockHeader(version int32, parentHashes []*daghash.Hash, hashMerkleRoot *daghash.Hash,
|
||||
acceptedIDMerkleRoot *daghash.Hash, utxoCommitment *daghash.Hash, bits uint32, nonce uint64) *BlockHeader {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
return &BlockHeader{
|
||||
Version: version,
|
||||
ParentHashes: parentHashes,
|
||||
HashMerkleRoot: hashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
UTXOCommitment: utxoCommitment,
|
||||
Timestamp: mstime.Now(),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
}
|
||||
|
||||
// readBlockHeader reads a kaspa block header from r. See Deserialize for
|
||||
// decoding block headers stored to disk, such as in a database, as opposed to
|
||||
// decoding from the appmessage.
|
||||
func readBlockHeader(r io.Reader, pver uint32, bh *BlockHeader) error {
|
||||
var numParentBlocks byte
|
||||
err := readElements(r, &bh.Version, &numParentBlocks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
bh.ParentHashes = make([]*daghash.Hash, numParentBlocks)
|
||||
for i := byte(0); i < numParentBlocks; i++ {
|
||||
hash := &daghash.Hash{}
|
||||
err := ReadElement(r, hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
bh.ParentHashes[i] = hash
|
||||
}
|
||||
bh.HashMerkleRoot = &daghash.Hash{}
|
||||
bh.AcceptedIDMerkleRoot = &daghash.Hash{}
|
||||
bh.UTXOCommitment = &daghash.Hash{}
|
||||
return readElements(r, bh.HashMerkleRoot, bh.AcceptedIDMerkleRoot, bh.UTXOCommitment,
|
||||
(*int64Time)(&bh.Timestamp), &bh.Bits, &bh.Nonce)
|
||||
}
|
||||
|
||||
// writeBlockHeader writes a kaspa block header to w. See Serialize for
|
||||
// encoding block headers to be stored to disk, such as in a database, as
|
||||
// opposed to encoding for the appmessage.
|
||||
func writeBlockHeader(w io.Writer, pver uint32, bh *BlockHeader) error {
|
||||
timestamp := bh.Timestamp.UnixMilliseconds()
|
||||
if err := writeElements(w, bh.Version, bh.NumParentBlocks()); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, hash := range bh.ParentHashes {
|
||||
if err := WriteElement(w, hash); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return writeElements(w, bh.HashMerkleRoot, bh.AcceptedIDMerkleRoot, bh.UTXOCommitment, timestamp, bh.Bits, bh.Nonce)
|
||||
}
|
||||
@@ -1,345 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBlockHeader tests the BlockHeader API.
|
||||
func TestBlockHeader(t *testing.T) {
|
||||
nonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
t.Errorf("random.Uint64: Error generating nonce: %v", err)
|
||||
}
|
||||
|
||||
hashes := []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash}
|
||||
|
||||
merkleHash := mainnetGenesisMerkleRoot
|
||||
acceptedIDMerkleRoot := exampleAcceptedIDMerkleRoot
|
||||
bits := uint32(0x1d00ffff)
|
||||
bh := NewBlockHeader(1, hashes, merkleHash, acceptedIDMerkleRoot, exampleUTXOCommitment, bits, nonce)
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
if !reflect.DeepEqual(bh.ParentHashes, hashes) {
|
||||
t.Errorf("NewBlockHeader: wrong prev hashes - got %v, want %v",
|
||||
spew.Sprint(bh.ParentHashes), spew.Sprint(hashes))
|
||||
}
|
||||
if !bh.HashMerkleRoot.IsEqual(merkleHash) {
|
||||
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
|
||||
spew.Sprint(bh.HashMerkleRoot), spew.Sprint(merkleHash))
|
||||
}
|
||||
if bh.Bits != bits {
|
||||
t.Errorf("NewBlockHeader: wrong bits - got %v, want %v",
|
||||
bh.Bits, bits)
|
||||
}
|
||||
if bh.Nonce != nonce {
|
||||
t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v",
|
||||
bh.Nonce, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHeaderEncoding tests the BlockHeader appmessage encode and decode for various
|
||||
// protocol versions.
|
||||
func TestBlockHeaderEncoding(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x000000000001e0f3
|
||||
pver := ProtocolVersion
|
||||
|
||||
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||
bits := uint32(0x1d00ffff)
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
|
||||
UTXOCommitment: exampleUTXOCommitment,
|
||||
Timestamp: mstime.UnixMilliseconds(0x17315ed0f99),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
// baseBlockHdrEncoded is the appmessage encoded bytes of baseBlockHdr.
|
||||
baseBlockHdrEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x02, // NumParentBlocks
|
||||
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25, // mainnetGenesisHash
|
||||
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
|
||||
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
|
||||
0x8c, 0xfd, 0x9f, 0x69, 0xdd, 0xcf, 0xbb, 0x63,
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a, // simnetGenesisHash
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
0x4a, 0x5e, 0x1e, 0x4b, 0xaa, 0xb8, 0x9f, 0x3a, // HashMerkleRoot
|
||||
0x32, 0x51, 0x8a, 0x88, 0xc3, 0x1b, 0xc8, 0x7f,
|
||||
0x61, 0x8f, 0x76, 0x67, 0x3e, 0x2c, 0xc7, 0x7a,
|
||||
0xb2, 0x12, 0x7b, 0x7a, 0xfd, 0xed, 0xa3, 0x3b,
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // AcceptedIDMerkleRoot
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // UTXOCommitment
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x99, 0x0f, 0xed, 0x15, 0x73, 0x01, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *BlockHeader // Data to encode
|
||||
out *BlockHeader // Expected decoded data
|
||||
buf []byte // Encoded data
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
baseBlockHdr,
|
||||
baseBlockHdr,
|
||||
baseBlockHdrEncoded,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := writeBlockHeader(&buf, test.pver, test.in)
|
||||
if err != nil {
|
||||
t.Errorf("writeBlockHeader #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("writeBlockHeader #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
buf.Reset()
|
||||
err = test.in.KaspaEncode(&buf, pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the block header from appmessage format.
|
||||
var bh BlockHeader
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = readBlockHeader(rbuf, test.pver, &bh)
|
||||
if err != nil {
|
||||
t.Errorf("readBlockHeader #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&bh, test.out) {
|
||||
t.Errorf("readBlockHeader #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
|
||||
rbuf = bytes.NewReader(test.buf)
|
||||
err = bh.KaspaDecode(rbuf, pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&bh, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHeaderSerialize tests BlockHeader serialize and deserialize.
|
||||
func TestBlockHeaderSerialize(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x01e0f3
|
||||
|
||||
// baseBlockHdr is used in the various tests as a baseline BlockHeader.
|
||||
bits := uint32(0x1d00ffff)
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
|
||||
UTXOCommitment: exampleUTXOCommitment,
|
||||
Timestamp: mstime.UnixMilliseconds(0x17315ed0f99),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
// baseBlockHdrEncoded is the appmessage encoded bytes of baseBlockHdr.
|
||||
baseBlockHdrEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x02, // NumParentBlocks
|
||||
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25, // mainnetGenesisHash
|
||||
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
|
||||
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
|
||||
0x8c, 0xfd, 0x9f, 0x69, 0xdd, 0xcf, 0xbb, 0x63,
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a, // simnetGenesisHash
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
0x4a, 0x5e, 0x1e, 0x4b, 0xaa, 0xb8, 0x9f, 0x3a, // HashMerkleRoot
|
||||
0x32, 0x51, 0x8a, 0x88, 0xc3, 0x1b, 0xc8, 0x7f,
|
||||
0x61, 0x8f, 0x76, 0x67, 0x3e, 0x2c, 0xc7, 0x7a,
|
||||
0xb2, 0x12, 0x7b, 0x7a, 0xfd, 0xed, 0xa3, 0x3b,
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // AcceptedIDMerkleRoot
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // UTXOCommitment
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x99, 0x0f, 0xed, 0x15, 0x73, 0x01, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0xf3, 0xe0, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *BlockHeader // Data to encode
|
||||
out *BlockHeader // Expected decoded data
|
||||
buf []byte // Serialized data
|
||||
}{
|
||||
{
|
||||
baseBlockHdr,
|
||||
baseBlockHdr,
|
||||
baseBlockHdrEncoded,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the block header.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Errorf("Serialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block header.
|
||||
var bh BlockHeader
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = bh.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("Deserialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&bh, test.out) {
|
||||
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&bh), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHeaderSerializeSize performs tests to ensure the serialize size for
|
||||
// various block headers is accurate.
|
||||
func TestBlockHeaderSerializeSize(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x1e0f3
|
||||
bits := uint32(0x1d00ffff)
|
||||
timestamp := mstime.UnixMilliseconds(0x495fab29000)
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: &daghash.ZeroHash,
|
||||
UTXOCommitment: &daghash.ZeroHash,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
genesisBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: &daghash.ZeroHash,
|
||||
UTXOCommitment: &daghash.ZeroHash,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
tests := []struct {
|
||||
in *BlockHeader // Block header to encode
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// Block with no transactions.
|
||||
{genesisBlockHdr, 121},
|
||||
|
||||
// First block in the mainnet block DAG.
|
||||
{baseBlockHdr, 185},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := test.in.SerializeSize()
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("BlockHeader.SerializeSize: #%d got: %d, want: "+
|
||||
"%d", i, serializedSize, test.size)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGenesis(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x1e0f3
|
||||
bits := uint32(0x1d00ffff)
|
||||
timestamp := mstime.UnixMilliseconds(0x495fab29000)
|
||||
|
||||
baseBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
genesisBlockHdr := &BlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *BlockHeader // Block header to encode
|
||||
isGenesis bool // Expected result for call of .IsGenesis
|
||||
}{
|
||||
{genesisBlockHdr, true},
|
||||
{baseBlockHdr, false},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
isGenesis := test.in.IsGenesis()
|
||||
if isGenesis != test.isGenesis {
|
||||
t.Errorf("BlockHeader.IsGenesis: #%d got: %t, want: %t",
|
||||
i, isGenesis, test.isGenesis)
|
||||
}
|
||||
}
|
||||
}
|
||||
22
app/appmessage/p2p_ibdrootnotfound.go
Normal file
22
app/appmessage/p2p_ibdrootnotfound.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package appmessage
|
||||
|
||||
// MsgIBDRootNotFound implements the Message interface and represents a kaspa
|
||||
// IBDRootNotFound message. It is used to notify the IBD root that was requested
|
||||
// by other peer was not found.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgIBDRootNotFound struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDRootNotFound) Command() MessageCommand {
|
||||
return CmdIBDRootNotFound
|
||||
}
|
||||
|
||||
// NewMsgIBDRootNotFound returns a new kaspa IBDRootNotFound message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgIBDRootNotFound() *MsgDoneHeaders {
|
||||
return &MsgDoneHeaders{}
|
||||
}
|
||||
@@ -4,58 +4,15 @@
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MaxAddressesPerMsg is the maximum number of addresses that can be in a single
|
||||
// kaspa Addresses message (MsgAddresses).
|
||||
const MaxAddressesPerMsg = 1000
|
||||
|
||||
// MsgAddresses implements the Message interface and represents a kaspa
|
||||
// Addresses message. It is used to provide a list of known active peers on the
|
||||
// network. An active peer is considered one that has transmitted a message
|
||||
// within the last 3 hours. Nodes which have not transmitted in that time
|
||||
// frame should be forgotten. Each message is limited to a maximum number of
|
||||
// addresses, which is currently 1000. As a result, multiple messages must
|
||||
// be used to relay the full list.
|
||||
//
|
||||
// Use the AddAddress function to build up the list of known addresses when
|
||||
// sending an Addresses message to another peer.
|
||||
// Addresses message.
|
||||
type MsgAddresses struct {
|
||||
baseMessage
|
||||
IncludeAllSubnetworks bool
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
AddrList []*NetAddress
|
||||
}
|
||||
|
||||
// AddAddress adds a known active peer to the message.
|
||||
func (msg *MsgAddresses) AddAddress(na *NetAddress) error {
|
||||
if len(msg.AddrList)+1 > MaxAddressesPerMsg {
|
||||
str := fmt.Sprintf("too many addresses in message [max %d]",
|
||||
MaxAddressesPerMsg)
|
||||
return messageError("MsgAddresses.AddAddress", str)
|
||||
}
|
||||
|
||||
msg.AddrList = append(msg.AddrList, na)
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddAddresses adds multiple known active peers to the message.
|
||||
func (msg *MsgAddresses) AddAddresses(netAddrs ...*NetAddress) error {
|
||||
for _, na := range netAddrs {
|
||||
err := msg.AddAddress(na)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClearAddresses removes all addresses from the message.
|
||||
func (msg *MsgAddresses) ClearAddresses() {
|
||||
msg.AddrList = []*NetAddress{}
|
||||
AddressList []*NetAddress
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -66,10 +23,8 @@ func (msg *MsgAddresses) Command() MessageCommand {
|
||||
|
||||
// NewMsgAddresses returns a new kaspa Addresses message that conforms to the
|
||||
// Message interface. See MsgAddresses for details.
|
||||
func NewMsgAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgAddresses {
|
||||
func NewMsgAddresses(addressList []*NetAddress) *MsgAddresses {
|
||||
return &MsgAddresses{
|
||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
||||
SubnetworkID: subnetworkID,
|
||||
AddrList: make([]*NetAddress, 0, MaxAddressesPerMsg),
|
||||
AddressList: addressList,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestAddresses tests the MsgAddresses API.
|
||||
func TestAddresses(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(3)
|
||||
msg := NewMsgAddresses(false, nil)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgAddresses: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
|
||||
// Ensure NetAddresses are added properly.
|
||||
tcpAddr := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
na := NewNetAddress(tcpAddr, SFNodeNetwork)
|
||||
err := msg.AddAddress(na)
|
||||
if err != nil {
|
||||
t.Errorf("AddAddress: %v", err)
|
||||
}
|
||||
if msg.AddrList[0] != na {
|
||||
t.Errorf("AddAddress: wrong address added - got %v, want %v",
|
||||
spew.Sprint(msg.AddrList[0]), spew.Sprint(na))
|
||||
}
|
||||
|
||||
// Ensure the address list is cleared properly.
|
||||
msg.ClearAddresses()
|
||||
if len(msg.AddrList) != 0 {
|
||||
t.Errorf("ClearAddresses: address list is not empty - "+
|
||||
"got %v [%v], want %v", len(msg.AddrList),
|
||||
spew.Sprint(msg.AddrList[0]), 0)
|
||||
}
|
||||
|
||||
// Ensure adding more than the max allowed addresses per message returns
|
||||
// error.
|
||||
for i := 0; i < MaxAddressesPerMsg+1; i++ {
|
||||
err = msg.AddAddress(na)
|
||||
}
|
||||
if err == nil {
|
||||
t.Errorf("AddAddress: expected error on too many addresses " +
|
||||
"not received")
|
||||
}
|
||||
err = msg.AddAddresses(na)
|
||||
if err == nil {
|
||||
t.Errorf("AddAddresses: expected error on too many addresses " +
|
||||
"not received")
|
||||
}
|
||||
}
|
||||
@@ -5,13 +5,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// defaultTransactionAlloc is the default size used for the backing array
|
||||
@@ -21,15 +15,18 @@ import (
|
||||
// backing array multiple times.
|
||||
const defaultTransactionAlloc = 2048
|
||||
|
||||
// MaxMassPerBlock is the maximum total transaction mass a block may have.
|
||||
const MaxMassPerBlock = 10000000
|
||||
// MaxMassAcceptedByBlock is the maximum total transaction mass a block may accept.
|
||||
const MaxMassAcceptedByBlock = 10000000
|
||||
|
||||
// MaxMassPerTx is the maximum total mass a transaction may have.
|
||||
const MaxMassPerTx = MaxMassPerBlock / 2
|
||||
const MaxMassPerTx = MaxMassAcceptedByBlock / 2
|
||||
|
||||
// MaxTxPerBlock is the maximum number of transactions that could
|
||||
// possibly fit into a block.
|
||||
const MaxTxPerBlock = (MaxMassPerBlock / minTxPayload) + 1
|
||||
const MaxTxPerBlock = (MaxMassAcceptedByBlock / minTxPayload) + 1
|
||||
|
||||
// MaxBlockParents is the maximum allowed number of parents for block.
|
||||
const MaxBlockParents = 10
|
||||
|
||||
// TxLoc holds locator data for the offset and length of where a transaction is
|
||||
// located within a MsgBlock data buffer.
|
||||
@@ -43,7 +40,7 @@ type TxLoc struct {
|
||||
// response to a getdata message (MsgGetData) for a given block hash.
|
||||
type MsgBlock struct {
|
||||
baseMessage
|
||||
Header BlockHeader
|
||||
Header MsgBlockHeader
|
||||
Transactions []*MsgTx
|
||||
}
|
||||
|
||||
@@ -57,161 +54,6 @@ func (msg *MsgBlock) ClearTransactions() {
|
||||
msg.Transactions = make([]*MsgTx, 0, defaultTransactionAlloc)
|
||||
}
|
||||
|
||||
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Deserialize for decoding blocks stored to disk, such as in a database, as
|
||||
// opposed to decoding blocks from the appmessage.
|
||||
func (msg *MsgBlock) KaspaDecode(r io.Reader, pver uint32) error {
|
||||
err := readBlockHeader(r, pver, &msg.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txCount, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prevent more transactions than could possibly fit into a block.
|
||||
// It would be possible to cause memory exhaustion and panics without
|
||||
// a sane upper bound on this count.
|
||||
if txCount > MaxTxPerBlock {
|
||||
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||
"[count %d, max %d]", txCount, MaxTxPerBlock)
|
||||
return messageError("MsgBlock.KaspaDecode", str)
|
||||
}
|
||||
|
||||
msg.Transactions = make([]*MsgTx, 0, txCount)
|
||||
for i := uint64(0); i < txCount; i++ {
|
||||
tx := MsgTx{}
|
||||
err := tx.KaspaDecode(r, pver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Transactions = append(msg.Transactions, &tx)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserialize decodes a block from r into the receiver using a format that is
|
||||
// suitable for long-term storage such as a database while respecting the
|
||||
// Version field in the block. This function differs from KaspaDecode in that
|
||||
// KaspaDecode decodes from the kaspa appmessage protocol as it was sent across the
|
||||
// network. The appmessage encoding can technically differ depending on the protocol
|
||||
// version and doesn't even really need to match the format of a stored block at
|
||||
// all. As of the time this comment was written, the encoded block is the same
|
||||
// in both instances, but there is a distinct difference and separating the two
|
||||
// allows the API to be flexible enough to deal with changes.
|
||||
func (msg *MsgBlock) Deserialize(r io.Reader) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaDecode.
|
||||
return msg.KaspaDecode(r, 0)
|
||||
}
|
||||
|
||||
// DeserializeTxLoc decodes r in the same manner Deserialize does, but it takes
|
||||
// a byte buffer instead of a generic reader and returns a slice containing the
|
||||
// start and length of each transaction within the raw data that is being
|
||||
// deserialized.
|
||||
func (msg *MsgBlock) DeserializeTxLoc(r *bytes.Buffer) ([]TxLoc, error) {
|
||||
fullLen := r.Len()
|
||||
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of existing appmessage protocol functions.
|
||||
err := readBlockHeader(r, 0, &msg.Header)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txCount, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prevent more transactions than could possibly fit into a block.
|
||||
// It would be possible to cause memory exhaustion and panics without
|
||||
// a sane upper bound on this count.
|
||||
if txCount > MaxTxPerBlock {
|
||||
str := fmt.Sprintf("too many transactions to fit into a block "+
|
||||
"[count %d, max %d]", txCount, MaxTxPerBlock)
|
||||
return nil, messageError("MsgBlock.DeserializeTxLoc", str)
|
||||
}
|
||||
|
||||
// Deserialize each transaction while keeping track of its location
|
||||
// within the byte stream.
|
||||
msg.Transactions = make([]*MsgTx, 0, txCount)
|
||||
txLocs := make([]TxLoc, txCount)
|
||||
for i := uint64(0); i < txCount; i++ {
|
||||
txLocs[i].TxStart = fullLen - r.Len()
|
||||
tx := MsgTx{}
|
||||
err := tx.Deserialize(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msg.Transactions = append(msg.Transactions, &tx)
|
||||
txLocs[i].TxLen = (fullLen - r.Len()) - txLocs[i].TxStart
|
||||
}
|
||||
|
||||
return txLocs, nil
|
||||
}
|
||||
|
||||
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Serialize for encoding blocks to be stored to disk, such as in a
|
||||
// database, as opposed to encoding blocks for the appmessage.
|
||||
func (msg *MsgBlock) KaspaEncode(w io.Writer, pver uint32) error {
|
||||
err := writeBlockHeader(w, pver, &msg.Header)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WriteVarInt(w, uint64(len(msg.Transactions)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, tx := range msg.Transactions {
|
||||
err = tx.KaspaEncode(w, pver)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize encodes the block to w using a format that suitable for long-term
|
||||
// storage such as a database while respecting the Version field in the block.
|
||||
// This function differs from KaspaEncode in that KaspaEncode encodes the block to
|
||||
// the kaspa appmessage protocol in order to be sent across the network. The appmessage
|
||||
// encoding can technically differ depending on the protocol version and doesn't
|
||||
// even really need to match the format of a stored block at all. As of the
|
||||
// time this comment was written, the encoded block is the same in both
|
||||
// instances, but there is a distinct difference and separating the two allows
|
||||
// the API to be flexible enough to deal with changes.
|
||||
func (msg *MsgBlock) Serialize(w io.Writer) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaEncode.
|
||||
return msg.KaspaEncode(w, 0)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// block.
|
||||
func (msg *MsgBlock) SerializeSize() int {
|
||||
// Block header bytes + Serialized varint size for the number of
|
||||
// transactions.
|
||||
n := msg.Header.SerializeSize() + VarIntSerializeSize(uint64(len(msg.Transactions)))
|
||||
|
||||
for _, tx := range msg.Transactions {
|
||||
n += tx.SerializeSize()
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgBlock) Command() MessageCommand {
|
||||
@@ -224,17 +66,12 @@ func (msg *MsgBlock) MaxPayloadLength(pver uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
||||
|
||||
// BlockHash computes the block identifier hash for this block.
|
||||
func (msg *MsgBlock) BlockHash() *daghash.Hash {
|
||||
return msg.Header.BlockHash()
|
||||
}
|
||||
|
||||
// ConvertToPartial clears out all the payloads of the subnetworks that are
|
||||
// incompatible with the given subnetwork ID.
|
||||
// Note: this operation modifies the block in place.
|
||||
func (msg *MsgBlock) ConvertToPartial(subnetworkID *subnetworkid.SubnetworkID) {
|
||||
func (msg *MsgBlock) ConvertToPartial(subnetworkID *externalapi.DomainSubnetworkID) {
|
||||
for _, tx := range msg.Transactions {
|
||||
if !tx.SubnetworkID.IsEqual(subnetworkID) {
|
||||
if tx.SubnetworkID != *subnetworkID {
|
||||
tx.Payload = []byte{}
|
||||
}
|
||||
}
|
||||
@@ -242,7 +79,7 @@ func (msg *MsgBlock) ConvertToPartial(subnetworkID *subnetworkid.SubnetworkID) {
|
||||
|
||||
// NewMsgBlock returns a new kaspa block message that conforms to the
|
||||
// Message interface. See MsgBlock for details.
|
||||
func NewMsgBlock(blockHeader *BlockHeader) *MsgBlock {
|
||||
func NewMsgBlock(blockHeader *MsgBlockHeader) *MsgBlock {
|
||||
return &MsgBlock{
|
||||
Header: *blockHeader,
|
||||
Transactions: make([]*MsgTx, 0, defaultTransactionAlloc),
|
||||
|
||||
@@ -5,17 +5,15 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
// TestBlock tests the MsgBlock API.
|
||||
@@ -71,46 +69,31 @@ func TestBlock(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockHash tests the ability to generate the hash of a block accurately.
|
||||
func TestBlockHash(t *testing.T) {
|
||||
// Block 1 hash.
|
||||
hashStr := "55d71bd49a8233bc9f0edbcbd0ad5d3eaebffe1fc6a6443a1c1f310fd02c11a5"
|
||||
wantHash, err := daghash.NewHashFromStr(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the hash produced is expected.
|
||||
blockHash := blockOne.BlockHash()
|
||||
if !blockHash.IsEqual(wantHash) {
|
||||
t.Errorf("BlockHash: wrong hash - got %v, want %v",
|
||||
spew.Sprint(blockHash), spew.Sprint(wantHash))
|
||||
}
|
||||
}
|
||||
|
||||
func TestConvertToPartial(t *testing.T) {
|
||||
localSubnetworkID := &externalapi.DomainSubnetworkID{0x12}
|
||||
|
||||
transactions := []struct {
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
subnetworkID *externalapi.DomainSubnetworkID
|
||||
payload []byte
|
||||
expectedPayloadLength int
|
||||
}{
|
||||
{
|
||||
subnetworkID: subnetworkid.SubnetworkIDNative,
|
||||
subnetworkID: &subnetworks.SubnetworkIDNative,
|
||||
payload: []byte{},
|
||||
expectedPayloadLength: 0,
|
||||
},
|
||||
{
|
||||
subnetworkID: subnetworkid.SubnetworkIDRegistry,
|
||||
subnetworkID: &subnetworks.SubnetworkIDRegistry,
|
||||
payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
|
||||
expectedPayloadLength: 0,
|
||||
},
|
||||
{
|
||||
subnetworkID: &subnetworkid.SubnetworkID{123},
|
||||
subnetworkID: localSubnetworkID,
|
||||
payload: []byte{0x01},
|
||||
expectedPayloadLength: 1,
|
||||
},
|
||||
{
|
||||
subnetworkID: &subnetworkid.SubnetworkID{234},
|
||||
subnetworkID: &externalapi.DomainSubnetworkID{0x34},
|
||||
payload: []byte{0x02},
|
||||
expectedPayloadLength: 0,
|
||||
},
|
||||
@@ -122,388 +105,33 @@ func TestConvertToPartial(t *testing.T) {
|
||||
block.Transactions = append(block.Transactions, NewSubnetworkMsgTx(1, nil, nil, transaction.subnetworkID, 0, payload))
|
||||
}
|
||||
|
||||
block.ConvertToPartial(&subnetworkid.SubnetworkID{123})
|
||||
block.ConvertToPartial(localSubnetworkID)
|
||||
|
||||
for _, transaction := range transactions {
|
||||
for _, testTransaction := range transactions {
|
||||
var subnetworkTx *MsgTx
|
||||
for _, tx := range block.Transactions {
|
||||
if tx.SubnetworkID.IsEqual(transaction.subnetworkID) {
|
||||
subnetworkTx = tx
|
||||
for _, blockTransaction := range block.Transactions {
|
||||
if blockTransaction.SubnetworkID == *testTransaction.subnetworkID {
|
||||
subnetworkTx = blockTransaction
|
||||
}
|
||||
}
|
||||
if subnetworkTx == nil {
|
||||
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", transaction.subnetworkID)
|
||||
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", testTransaction.subnetworkID)
|
||||
continue
|
||||
}
|
||||
|
||||
payloadLength := len(subnetworkTx.Payload)
|
||||
if payloadLength != transaction.expectedPayloadLength {
|
||||
if payloadLength != testTransaction.expectedPayloadLength {
|
||||
t.Errorf("ConvertToPartial: unexpected payload length for subnetwork '%s': expected: %d, got: %d",
|
||||
transaction.subnetworkID, transaction.expectedPayloadLength, payloadLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockEncoding tests the MsgBlock appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestBlockEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgBlock // Message to encode
|
||||
out *MsgBlock // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
&blockOne,
|
||||
&blockOne,
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgBlock
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockEncodingErrors performs negative tests against appmessage encode and decode
|
||||
// of MsgBlock to confirm error paths work correctly.
|
||||
func TestBlockEncodingErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
in *MsgBlock // Value to encode
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{&blockOne, blockOneBytes, pver, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in num block hashes.
|
||||
{&blockOne, blockOneBytes, pver, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #1.
|
||||
{&blockOne, blockOneBytes, pver, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #2.
|
||||
{&blockOne, blockOneBytes, pver, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in hash merkle root.
|
||||
{&blockOne, blockOneBytes, pver, 69, io.ErrShortWrite, io.EOF},
|
||||
// Force error in accepted ID merkle root.
|
||||
{&blockOne, blockOneBytes, pver, 101, io.ErrShortWrite, io.EOF},
|
||||
// Force error in utxo commitment.
|
||||
{&blockOne, blockOneBytes, pver, 133, io.ErrShortWrite, io.EOF},
|
||||
// Force error in timestamp.
|
||||
{&blockOne, blockOneBytes, pver, 165, io.ErrShortWrite, io.EOF},
|
||||
// Force error in difficulty bits.
|
||||
{&blockOne, blockOneBytes, pver, 173, io.ErrShortWrite, io.EOF},
|
||||
// Force error in header nonce.
|
||||
{&blockOne, blockOneBytes, pver, 177, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction count.
|
||||
{&blockOne, blockOneBytes, pver, 185, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transactions.
|
||||
{&blockOne, blockOneBytes, pver, 186, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
var msg MsgBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockSerialize tests MsgBlock serialize and deserialize.
|
||||
func TestBlockSerialize(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgBlock // Message to encode
|
||||
out *MsgBlock // Expected decoded message
|
||||
buf []byte // Serialized data
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
}{
|
||||
{
|
||||
&blockOne,
|
||||
&blockOne,
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the block.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Errorf("Serialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("Serialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block.
|
||||
var block MsgBlock
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = block.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("Deserialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&block, test.out) {
|
||||
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&block), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block while gathering transaction location
|
||||
// information.
|
||||
var txLocBlock MsgBlock
|
||||
br := bytes.NewBuffer(test.buf)
|
||||
txLocs, err := txLocBlock.DeserializeTxLoc(br)
|
||||
if err != nil {
|
||||
t.Errorf("DeserializeTxLoc #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&txLocBlock, test.out) {
|
||||
t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&txLocBlock), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(txLocs, test.txLocs) {
|
||||
t.Errorf("DeserializeTxLoc #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(txLocs), spew.Sdump(test.txLocs))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockSerializeErrors performs negative tests against appmessage encode and
|
||||
// decode of MsgBlock to confirm error paths work correctly.
|
||||
func TestBlockSerializeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgBlock // Value to encode
|
||||
buf []byte // Serialized data
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{&blockOne, blockOneBytes, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in numParentBlocks.
|
||||
{&blockOne, blockOneBytes, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #1.
|
||||
{&blockOne, blockOneBytes, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in prev block hash #2.
|
||||
{&blockOne, blockOneBytes, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in hash merkle root.
|
||||
{&blockOne, blockOneBytes, 69, io.ErrShortWrite, io.EOF},
|
||||
// Force error in accepted ID merkle root.
|
||||
{&blockOne, blockOneBytes, 101, io.ErrShortWrite, io.EOF},
|
||||
// Force error in utxo commitment.
|
||||
{&blockOne, blockOneBytes, 133, io.ErrShortWrite, io.EOF},
|
||||
// Force error in timestamp.
|
||||
{&blockOne, blockOneBytes, 165, io.ErrShortWrite, io.EOF},
|
||||
// Force error in difficulty bits.
|
||||
{&blockOne, blockOneBytes, 173, io.ErrShortWrite, io.EOF},
|
||||
// Force error in header nonce.
|
||||
{&blockOne, blockOneBytes, 177, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction count.
|
||||
{&blockOne, blockOneBytes, 185, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transactions.
|
||||
{&blockOne, blockOneBytes, 186, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the block.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.Serialize(w)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the block.
|
||||
var block MsgBlock
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = block.Deserialize(r)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
|
||||
var txLocBlock MsgBlock
|
||||
br := bytes.NewBuffer(test.buf[0:test.max])
|
||||
_, err = txLocBlock.DeserializeTxLoc(br)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockOverflowErrors performs tests to ensure deserializing blocks which
|
||||
// are intentionally crafted to use large values for the number of transactions
|
||||
// are handled properly. This could otherwise potentially be used as an attack
|
||||
// vector.
|
||||
func TestBlockOverflowErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
err error // Expected error
|
||||
}{
|
||||
// Block that claims to have ~uint64(0) transactions.
|
||||
{
|
||||
[]byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version 1
|
||||
0x02, // NumParentBlocks
|
||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72, // mainnetGenesisHash
|
||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a, // simnetGenesisHash
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2, // HashMerkleRoot
|
||||
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
||||
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
||||
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a,
|
||||
0x09, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // AcceptedIDMerkleRoot
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C, // UTXOCommitment
|
||||
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
|
||||
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
|
||||
0x65, 0x9C, 0x79, 0x3C, 0xE3, 0x70, 0xD9, 0x5F,
|
||||
0x61, 0xbc, 0x66, 0x49, 0x00, 0x00, 0x00, 0x00, // Timestamp
|
||||
0xff, 0xff, 0x00, 0x1d, // Bits
|
||||
0x01, 0xe3, 0x62, 0x99, 0x00, 0x00, 0x00, 0x00, // Fake Nonce
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // TxnCount
|
||||
}, pver, &MessageError{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
var msg MsgBlock
|
||||
r := bytes.NewReader(test.buf)
|
||||
err := msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize from appmessage format.
|
||||
r = bytes.NewReader(test.buf)
|
||||
err = msg.Deserialize(r)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize with transaction location info from appmessage format.
|
||||
br := bytes.NewBuffer(test.buf)
|
||||
_, err = msg.DeserializeTxLoc(br)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("DeserializeTxLoc #%d wrong error got: %v, "+
|
||||
"want: %v", i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestBlockSerializeSize performs tests to ensure the serialize size for
|
||||
// various blocks is accurate.
|
||||
func TestBlockSerializeSize(t *testing.T) {
|
||||
// Block with no transactions.
|
||||
noTxBlock := NewMsgBlock(&blockOne.Header)
|
||||
|
||||
tests := []struct {
|
||||
in *MsgBlock // Block to encode
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// Block with no transactions.
|
||||
{noTxBlock, 186},
|
||||
|
||||
// First block in the mainnet block DAG.
|
||||
{&blockOne, len(blockOneBytes)},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := test.in.SerializeSize()
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("MsgBlock.SerializeSize: #%d got: %d, want: "+
|
||||
"%d", i, serializedSize, test.size)
|
||||
|
||||
continue
|
||||
testTransaction.subnetworkID, testTransaction.expectedPayloadLength, payloadLength)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// blockOne is the first block in the mainnet block DAG.
|
||||
var blockOne = MsgBlock{
|
||||
Header: BlockHeader{
|
||||
Header: MsgBlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
|
||||
ParentHashes: []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
|
||||
UTXOCommitment: exampleUTXOCommitment,
|
||||
@@ -516,7 +144,7 @@ var blockOne = MsgBlock{
|
||||
[]*TxIn{
|
||||
{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
TxID: externalapi.DomainTransactionID{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
|
||||
108
app/appmessage/p2p_msgblockheader.go
Normal file
108
app/appmessage/p2p_msgblockheader.go
Normal file
@@ -0,0 +1,108 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// BaseBlockHeaderPayload is the base number of bytes a block header can be,
|
||||
// not including the list of parent block headers.
|
||||
// Version 4 bytes + Timestamp 8 bytes + Bits 4 bytes + Nonce 8 bytes +
|
||||
// + NumParentBlocks 1 byte + HashMerkleRoot hash +
|
||||
// + AcceptedIDMerkleRoot hash + UTXOCommitment hash.
|
||||
// To get total size of block header len(ParentHashes) * externalapi.DomainHashSize should be
|
||||
// added to this value
|
||||
const BaseBlockHeaderPayload = 25 + 3*(externalapi.DomainHashSize)
|
||||
|
||||
// MaxNumParentBlocks is the maximum number of parent blocks a block can reference.
|
||||
// Currently set to 255 as the maximum number NumParentBlocks can be due to it being a byte
|
||||
const MaxNumParentBlocks = 255
|
||||
|
||||
// MaxBlockHeaderPayload is the maximum number of bytes a block header can be.
|
||||
// BaseBlockHeaderPayload + up to MaxNumParentBlocks hashes of parent blocks
|
||||
const MaxBlockHeaderPayload = BaseBlockHeaderPayload + (MaxNumParentBlocks * externalapi.DomainHashSize)
|
||||
|
||||
// MsgBlockHeader defines information about a block and is used in the kaspa
|
||||
// block (MsgBlock) and headers (MsgHeader) messages.
|
||||
type MsgBlockHeader struct {
|
||||
baseMessage
|
||||
|
||||
// Version of the block. This is not the same as the protocol version.
|
||||
Version int32
|
||||
|
||||
// Hashes of the parent block headers in the blockDAG.
|
||||
ParentHashes []*externalapi.DomainHash
|
||||
|
||||
// HashMerkleRoot is the merkle tree reference to hash of all transactions for the block.
|
||||
HashMerkleRoot *externalapi.DomainHash
|
||||
|
||||
// AcceptedIDMerkleRoot is merkle tree reference to hash all transactions
|
||||
// accepted form the block.Blues
|
||||
AcceptedIDMerkleRoot *externalapi.DomainHash
|
||||
|
||||
// UTXOCommitment is an ECMH UTXO commitment to the block UTXO.
|
||||
UTXOCommitment *externalapi.DomainHash
|
||||
|
||||
// Time the block was created.
|
||||
Timestamp mstime.Time
|
||||
|
||||
// Difficulty target for the block.
|
||||
Bits uint32
|
||||
|
||||
// Nonce used to generate the block.
|
||||
Nonce uint64
|
||||
}
|
||||
|
||||
// NumParentBlocks return the number of entries in ParentHashes
|
||||
func (h *MsgBlockHeader) NumParentBlocks() byte {
|
||||
numParents := len(h.ParentHashes)
|
||||
if numParents > math.MaxUint8 {
|
||||
panic(errors.Errorf("number of parents is %d, which is more than one byte can fit", numParents))
|
||||
}
|
||||
return byte(numParents)
|
||||
}
|
||||
|
||||
// BlockHash computes the block identifier hash for the given block header.
|
||||
func (h *MsgBlockHeader) BlockHash() *externalapi.DomainHash {
|
||||
return consensushashing.HeaderHash(BlockHeaderToDomainBlockHeader(h))
|
||||
}
|
||||
|
||||
// IsGenesis returns true iff this block is a genesis block
|
||||
func (h *MsgBlockHeader) IsGenesis() bool {
|
||||
return h.NumParentBlocks() == 0
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (h *MsgBlockHeader) Command() MessageCommand {
|
||||
return CmdHeader
|
||||
}
|
||||
|
||||
// NewBlockHeader returns a new MsgBlockHeader using the provided version, previous
|
||||
// block hash, hash merkle root, accepted ID merkle root, difficulty bits, and nonce used to generate the
|
||||
// block with defaults or calclulated values for the remaining fields.
|
||||
func NewBlockHeader(version int32, parentHashes []*externalapi.DomainHash, hashMerkleRoot *externalapi.DomainHash,
|
||||
acceptedIDMerkleRoot *externalapi.DomainHash, utxoCommitment *externalapi.DomainHash, bits uint32, nonce uint64) *MsgBlockHeader {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
return &MsgBlockHeader{
|
||||
Version: version,
|
||||
ParentHashes: parentHashes,
|
||||
HashMerkleRoot: hashMerkleRoot,
|
||||
AcceptedIDMerkleRoot: acceptedIDMerkleRoot,
|
||||
UTXOCommitment: utxoCommitment,
|
||||
Timestamp: mstime.Now(),
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
}
|
||||
88
app/appmessage/p2p_msgblockheader_test.go
Normal file
88
app/appmessage/p2p_msgblockheader_test.go
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
)
|
||||
|
||||
// TestBlockHeader tests the MsgBlockHeader API.
|
||||
func TestBlockHeader(t *testing.T) {
|
||||
nonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
t.Errorf("random.Uint64: Error generating nonce: %v", err)
|
||||
}
|
||||
|
||||
hashes := []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash}
|
||||
|
||||
merkleHash := mainnetGenesisMerkleRoot
|
||||
acceptedIDMerkleRoot := exampleAcceptedIDMerkleRoot
|
||||
bits := uint32(0x1d00ffff)
|
||||
bh := NewBlockHeader(1, hashes, merkleHash, acceptedIDMerkleRoot, exampleUTXOCommitment, bits, nonce)
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
if !reflect.DeepEqual(bh.ParentHashes, hashes) {
|
||||
t.Errorf("NewBlockHeader: wrong prev hashes - got %v, want %v",
|
||||
spew.Sprint(bh.ParentHashes), spew.Sprint(hashes))
|
||||
}
|
||||
if bh.HashMerkleRoot != merkleHash {
|
||||
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
|
||||
spew.Sprint(bh.HashMerkleRoot), spew.Sprint(merkleHash))
|
||||
}
|
||||
if bh.Bits != bits {
|
||||
t.Errorf("NewBlockHeader: wrong bits - got %v, want %v",
|
||||
bh.Bits, bits)
|
||||
}
|
||||
if bh.Nonce != nonce {
|
||||
t.Errorf("NewBlockHeader: wrong nonce - got %v, want %v",
|
||||
bh.Nonce, nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsGenesis(t *testing.T) {
|
||||
nonce := uint64(123123) // 0x1e0f3
|
||||
bits := uint32(0x1d00ffff)
|
||||
timestamp := mstime.UnixMilliseconds(0x495fab29000)
|
||||
|
||||
baseBlockHdr := &MsgBlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
genesisBlockHdr := &MsgBlockHeader{
|
||||
Version: 1,
|
||||
ParentHashes: []*externalapi.DomainHash{},
|
||||
HashMerkleRoot: mainnetGenesisMerkleRoot,
|
||||
Timestamp: timestamp,
|
||||
Bits: bits,
|
||||
Nonce: nonce,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *MsgBlockHeader // Block header to encode
|
||||
isGenesis bool // Expected result for call of .IsGenesis
|
||||
}{
|
||||
{genesisBlockHdr, true},
|
||||
{baseBlockHdr, false},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
isGenesis := test.in.IsGenesis()
|
||||
if isGenesis != test.isGenesis {
|
||||
t.Errorf("MsgBlockHeader.IsGenesis: #%d got: %t, want: %t",
|
||||
i, isGenesis, test.isGenesis)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MaxBlockLocatorsPerMsg is the maximum number of block locator hashes allowed
|
||||
@@ -13,7 +13,7 @@ const MaxBlockLocatorsPerMsg = 500
|
||||
// syncing with you.
|
||||
type MsgBlockLocator struct {
|
||||
baseMessage
|
||||
BlockLocatorHashes []*daghash.Hash
|
||||
BlockLocatorHashes []*externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgBlockLocator) Command() MessageCommand {
|
||||
|
||||
// NewMsgBlockLocator returns a new kaspa locator message that conforms to
|
||||
// the Message interface. See MsgBlockLocator for details.
|
||||
func NewMsgBlockLocator(locatorHashes []*daghash.Hash) *MsgBlockLocator {
|
||||
func NewMsgBlockLocator(locatorHashes []*externalapi.DomainHash) *MsgBlockLocator {
|
||||
return &MsgBlockLocator{
|
||||
BlockLocatorHashes: locatorHashes,
|
||||
}
|
||||
|
||||
@@ -3,19 +3,22 @@ package appmessage
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// TestBlockLocator tests the MsgBlockLocator API.
|
||||
func TestBlockLocator(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
locatorHash, err := daghash.NewHashFromStr(hashStr)
|
||||
locatorHash, err := hashes.FromString(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
msg := NewMsgBlockLocator([]*daghash.Hash{locatorHash})
|
||||
msg := NewMsgBlockLocator([]*externalapi.DomainHash{locatorHash})
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(10)
|
||||
|
||||
22
app/appmessage/p2p_msgdoneheaders.go
Normal file
22
app/appmessage/p2p_msgdoneheaders.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDoneHeaders implements the Message interface and represents a kaspa
|
||||
// DoneHeaders message. It is used to notify the IBD syncing peer that the
|
||||
// syncer sent all the requested headers.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgDoneHeaders struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgDoneHeaders) Command() MessageCommand {
|
||||
return CmdDoneHeaders
|
||||
}
|
||||
|
||||
// NewMsgDoneHeaders returns a new kaspa DoneIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgDoneHeaders() *MsgDoneHeaders {
|
||||
return &MsgDoneHeaders{}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgDoneIBDBlocks implements the Message interface and represents a kaspa
|
||||
// DoneIBDBlocks message. It is used to notify the IBD syncing peer that the
|
||||
// syncer sent all the requested blocks.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgDoneIBDBlocks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgDoneIBDBlocks) Command() MessageCommand {
|
||||
return CmdDoneIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgDoneIBDBlocks returns a new kaspa DoneIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgDoneIBDBlocks() *MsgDoneIBDBlocks {
|
||||
return &MsgDoneIBDBlocks{}
|
||||
}
|
||||
@@ -5,7 +5,6 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
@@ -26,7 +25,7 @@ func TestIBDBlock(t *testing.T) {
|
||||
bh := NewBlockHeader(1, parentHashes, hashMerkleRoot, acceptedIDMerkleRoot, utxoCommitment, bits, nonce)
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(17)
|
||||
wantCmd := MessageCommand(15)
|
||||
msg := NewMsgIBDBlock(NewMsgBlock(bh))
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgIBDBlock: wrong command - got %v want %v",
|
||||
@@ -64,55 +63,3 @@ func TestIBDBlock(t *testing.T) {
|
||||
len(msg.Transactions), 0)
|
||||
}
|
||||
}
|
||||
|
||||
// TestIBDBlockEncoding tests the MsgIBDBlock appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestIBDBlockEncoding(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgIBDBlock // Message to encode
|
||||
out *MsgIBDBlock // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
txLocs []TxLoc // Expected transaction locations
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version.
|
||||
{
|
||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
||||
&MsgIBDBlock{MsgBlock: &blockOne},
|
||||
blockOneBytes,
|
||||
blockOneTxLocs,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgIBDBlock
|
||||
msg.MsgBlock = new(MsgBlock)
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
23
app/appmessage/p2p_msgibdrootutxosetandblock.go
Normal file
23
app/appmessage/p2p_msgibdrootutxosetandblock.go
Normal file
@@ -0,0 +1,23 @@
|
||||
package appmessage
|
||||
|
||||
// MsgIBDRootUTXOSetAndBlock implements the Message interface and represents a kaspa
|
||||
// IBDRootUTXOSetAndBlock message. It is used to answer RequestIBDRootUTXOSetAndBlock messages.
|
||||
type MsgIBDRootUTXOSetAndBlock struct {
|
||||
baseMessage
|
||||
UTXOSet []byte
|
||||
Block *MsgBlock
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgIBDRootUTXOSetAndBlock) Command() MessageCommand {
|
||||
return CmdIBDRootUTXOSetAndBlock
|
||||
}
|
||||
|
||||
// NewMsgIBDRootUTXOSetAndBlock returns a new MsgIBDRootUTXOSetAndBlock.
|
||||
func NewMsgIBDRootUTXOSetAndBlock(utxoSet []byte, block *MsgBlock) *MsgIBDRootUTXOSetAndBlock {
|
||||
return &MsgIBDRootUTXOSetAndBlock{
|
||||
UTXOSet: utxoSet,
|
||||
Block: block,
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgInvRelayBlock implements the Message interface and represents a kaspa
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
// by sending their hash, and let the receiving node decide if it needs it.
|
||||
type MsgInvRelayBlock struct {
|
||||
baseMessage
|
||||
Hash *daghash.Hash
|
||||
Hash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -20,7 +20,7 @@ func (msg *MsgInvRelayBlock) Command() MessageCommand {
|
||||
|
||||
// NewMsgInvBlock returns a new kaspa invrelblk message that conforms to
|
||||
// the Message interface. See MsgInvRelayBlock for details.
|
||||
func NewMsgInvBlock(hash *daghash.Hash) *MsgInvRelayBlock {
|
||||
func NewMsgInvBlock(hash *externalapi.DomainHash) *MsgInvRelayBlock {
|
||||
return &MsgInvRelayBlock{
|
||||
Hash: hash,
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MaxInvPerTxInvMsg is the maximum number of hashes that can
|
||||
@@ -13,7 +13,7 @@ const MaxInvPerTxInvMsg = MaxInvPerMsg
|
||||
// by sending their ID, and let the receiving node decide if it needs it.
|
||||
type MsgInvTransaction struct {
|
||||
baseMessage
|
||||
TxIDs []*daghash.TxID
|
||||
TxIDs []*externalapi.DomainTransactionID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgInvTransaction) Command() MessageCommand {
|
||||
|
||||
// NewMsgInvTransaction returns a new kaspa TxInv message that conforms to
|
||||
// the Message interface. See MsgInvTransaction for details.
|
||||
func NewMsgInvTransaction(ids []*daghash.TxID) *MsgInvTransaction {
|
||||
func NewMsgInvTransaction(ids []*externalapi.DomainTransactionID) *MsgInvTransaction {
|
||||
return &MsgInvTransaction{
|
||||
TxIDs: ids,
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestAddresses implements the Message interface and represents a kaspa
|
||||
@@ -17,7 +17,7 @@ import (
|
||||
type MsgRequestAddresses struct {
|
||||
baseMessage
|
||||
IncludeAllSubnetworks bool
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
SubnetworkID *externalapi.DomainSubnetworkID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -28,7 +28,7 @@ func (msg *MsgRequestAddresses) Command() MessageCommand {
|
||||
|
||||
// NewMsgRequestAddresses returns a new kaspa RequestAddresses message that conforms to the
|
||||
// Message interface. See MsgRequestAddresses for details.
|
||||
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgRequestAddresses {
|
||||
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *externalapi.DomainSubnetworkID) *MsgRequestAddresses {
|
||||
return &MsgRequestAddresses{
|
||||
IncludeAllSubnetworks: includeAllSubnetworks,
|
||||
SubnetworkID: subnetworkID,
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestBlockLocator implements the Message interface and represents a kaspa
|
||||
// RequestBlockLocator message. It is used to request a block locator between high
|
||||
// and low hash.
|
||||
// RequestBlockLocator message. It is used to request a block locator between low
|
||||
// and high hash.
|
||||
// The locator is returned via a locator message (MsgBlockLocator).
|
||||
type MsgRequestBlockLocator struct {
|
||||
baseMessage
|
||||
HighHash *daghash.Hash
|
||||
LowHash *daghash.Hash
|
||||
LowHash *externalapi.DomainHash
|
||||
HighHash *externalapi.DomainHash
|
||||
Limit uint32
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -23,9 +24,10 @@ func (msg *MsgRequestBlockLocator) Command() MessageCommand {
|
||||
// NewMsgRequestBlockLocator returns a new RequestBlockLocator message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequestBlockLocator(highHash, lowHash *daghash.Hash) *MsgRequestBlockLocator {
|
||||
func NewMsgRequestBlockLocator(lowHash, highHash *externalapi.DomainHash, limit uint32) *MsgRequestBlockLocator {
|
||||
return &MsgRequestBlockLocator{
|
||||
HighHash: highHash,
|
||||
LowHash: lowHash,
|
||||
HighHash: highHash,
|
||||
Limit: limit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,22 @@ package appmessage
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
)
|
||||
|
||||
// TestRequestBlockLocator tests the MsgRequestBlockLocator API.
|
||||
func TestRequestBlockLocator(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
||||
highHash, err := hashes.FromString(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(9)
|
||||
msg := NewMsgRequestBlockLocator(highHash, &daghash.ZeroHash)
|
||||
msg := NewMsgRequestBlockLocator(highHash, &externalapi.DomainHash{}, 0)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequestBlockLocator: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
|
||||
34
app/appmessage/p2p_msgrequestheaders.go
Normal file
34
app/appmessage/p2p_msgrequestheaders.go
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestHeaders implements the Message interface and represents a kaspa
|
||||
// RequestHeaders message. It is used to request a list of blocks starting after the
|
||||
// low hash and until the high hash.
|
||||
type MsgRequestHeaders struct {
|
||||
baseMessage
|
||||
LowHash *externalapi.DomainHash
|
||||
HighHash *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestHeaders) Command() MessageCommand {
|
||||
return CmdRequestHeaders
|
||||
}
|
||||
|
||||
// NewMsgRequstHeaders returns a new kaspa RequestHeaders message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequstHeaders(lowHash, highHash *externalapi.DomainHash) *MsgRequestHeaders {
|
||||
return &MsgRequestHeaders{
|
||||
LowHash: lowHash,
|
||||
HighHash: highHash,
|
||||
}
|
||||
}
|
||||
@@ -7,34 +7,34 @@ package appmessage
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
)
|
||||
|
||||
// TestRequstIBDBlocks tests the MsgRequestIBDBlocks API.
|
||||
// TestRequstIBDBlocks tests the MsgRequestHeaders API.
|
||||
func TestRequstIBDBlocks(t *testing.T) {
|
||||
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
|
||||
lowHash, err := daghash.NewHashFromStr(hashStr)
|
||||
lowHash, err := hashes.FromString(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
hashStr = "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
highHash, err := daghash.NewHashFromStr(hashStr)
|
||||
hashStr = "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
highHash, err := hashes.FromString(hashStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewHashFromStr: %v", err)
|
||||
}
|
||||
|
||||
// Ensure we get the same data back out.
|
||||
msg := NewMsgRequstIBDBlocks(lowHash, highHash)
|
||||
if !msg.HighHash.IsEqual(highHash) {
|
||||
t.Errorf("NewMsgRequstIBDBlocks: wrong high hash - got %v, want %v",
|
||||
msg := NewMsgRequstHeaders(lowHash, highHash)
|
||||
if *msg.HighHash != *highHash {
|
||||
t.Errorf("NewMsgRequstHeaders: wrong high hash - got %v, want %v",
|
||||
msg.HighHash, highHash)
|
||||
}
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(4)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequstIBDBlocks: wrong command - got %v want %v",
|
||||
t.Errorf("NewMsgRequstHeaders: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,20 +1,15 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestIBDBlocks implements the Message interface and represents a kaspa
|
||||
// RequestIBDBlocks message. It is used to request a list of blocks starting after the
|
||||
// low hash and until the high hash.
|
||||
// RequestIBDBlocks message. It is used to request blocks as part of the IBD
|
||||
// protocol.
|
||||
type MsgRequestIBDBlocks struct {
|
||||
baseMessage
|
||||
LowHash *daghash.Hash
|
||||
HighHash *daghash.Hash
|
||||
Hashes []*externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -23,12 +18,9 @@ func (msg *MsgRequestIBDBlocks) Command() MessageCommand {
|
||||
return CmdRequestIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequstIBDBlocks returns a new kaspa RequestIBDBlocks message that conforms to the
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgRequstIBDBlocks(lowHash, highHash *daghash.Hash) *MsgRequestIBDBlocks {
|
||||
// NewMsgRequestIBDBlocks returns a new MsgRequestIBDBlocks.
|
||||
func NewMsgRequestIBDBlocks(hashes []*externalapi.DomainHash) *MsgRequestIBDBlocks {
|
||||
return &MsgRequestIBDBlocks{
|
||||
LowHash: lowHash,
|
||||
HighHash: highHash,
|
||||
Hashes: hashes,
|
||||
}
|
||||
}
|
||||
|
||||
26
app/appmessage/p2p_msgrequestibdrootutxosetandblock.go
Normal file
26
app/appmessage/p2p_msgrequestibdrootutxosetandblock.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestIBDRootUTXOSetAndBlock implements the Message interface and represents a kaspa
|
||||
// RequestIBDRootUTXOSetAndBlock message. It is used to request the UTXO set and block body
|
||||
// of the IBD root block.
|
||||
type MsgRequestIBDRootUTXOSetAndBlock struct {
|
||||
baseMessage
|
||||
IBDRoot *externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestIBDRootUTXOSetAndBlock) Command() MessageCommand {
|
||||
return CmdRequestIBDRootUTXOSetAndBlock
|
||||
}
|
||||
|
||||
// NewMsgRequestIBDRootUTXOSetAndBlock returns a new MsgRequestIBDRootUTXOSetAndBlock.
|
||||
func NewMsgRequestIBDRootUTXOSetAndBlock(ibdRoot *externalapi.DomainHash) *MsgRequestIBDRootUTXOSetAndBlock {
|
||||
return &MsgRequestIBDRootUTXOSetAndBlock{
|
||||
IBDRoot: ibdRoot,
|
||||
}
|
||||
}
|
||||
22
app/appmessage/p2p_msgrequestnextheaders.go
Normal file
22
app/appmessage/p2p_msgrequestnextheaders.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextHeaders implements the Message interface and represents a kaspa
|
||||
// RequestNextHeaders message. It is used to notify the IBD syncer peer to send
|
||||
// more headers.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestNextHeaders struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestNextHeaders) Command() MessageCommand {
|
||||
return CmdRequestNextHeaders
|
||||
}
|
||||
|
||||
// NewMsgRequestNextHeaders returns a new kaspa RequestNextHeaders message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestNextHeaders() *MsgRequestNextHeaders {
|
||||
return &MsgRequestNextHeaders{}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestNextIBDBlocks implements the Message interface and represents a kaspa
|
||||
// RequestNextIBDBlocks message. It is used to notify the IBD syncer peer to send
|
||||
// more blocks.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestNextIBDBlocks struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestNextIBDBlocks) Command() MessageCommand {
|
||||
return CmdRequestNextIBDBlocks
|
||||
}
|
||||
|
||||
// NewMsgRequestNextIBDBlocks returns a new kaspa RequestNextIBDBlocks message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestNextIBDBlocks() *MsgRequestNextIBDBlocks {
|
||||
return &MsgRequestNextIBDBlocks{}
|
||||
}
|
||||
@@ -1,19 +1,19 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgRequestRelayBlocksHashes is the maximum number of hashes that can
|
||||
// MaxRequestRelayBlocksHashes is the maximum number of hashes that can
|
||||
// be in a single RequestRelayBlocks message.
|
||||
const MsgRequestRelayBlocksHashes = MaxInvPerMsg
|
||||
const MaxRequestRelayBlocksHashes = MaxInvPerMsg
|
||||
|
||||
// MsgRequestRelayBlocks implements the Message interface and represents a kaspa
|
||||
// RequestRelayBlocks message. It is used to request blocks as part of the block
|
||||
// relay protocol.
|
||||
type MsgRequestRelayBlocks struct {
|
||||
baseMessage
|
||||
Hashes []*daghash.Hash
|
||||
Hashes []*externalapi.DomainHash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgRequestRelayBlocks) Command() MessageCommand {
|
||||
|
||||
// NewMsgRequestRelayBlocks returns a new kaspa RequestRelayBlocks message that conforms to
|
||||
// the Message interface. See MsgRequestRelayBlocks for details.
|
||||
func NewMsgRequestRelayBlocks(hashes []*daghash.Hash) *MsgRequestRelayBlocks {
|
||||
func NewMsgRequestRelayBlocks(hashes []*externalapi.DomainHash) *MsgRequestRelayBlocks {
|
||||
return &MsgRequestRelayBlocks{
|
||||
Hashes: hashes,
|
||||
}
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
// MsgRequestSelectedTip implements the Message interface and represents a kaspa
|
||||
// RequestSelectedTip message. It is used to request the selected tip of another peer.
|
||||
//
|
||||
// This message has no payload.
|
||||
type MsgRequestSelectedTip struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgRequestSelectedTip) Command() MessageCommand {
|
||||
return CmdRequestSelectedTip
|
||||
}
|
||||
|
||||
// NewMsgRequestSelectedTip returns a new kaspa RequestSelectedTip message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgRequestSelectedTip() *MsgRequestSelectedTip {
|
||||
return &MsgRequestSelectedTip{}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
// Copyright (c) 2013-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRequestSelectedTip tests the MsgRequestSelectedTip API.
|
||||
func TestRequestSelectedTip(t *testing.T) {
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(12)
|
||||
msg := NewMsgRequestSelectedTip()
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgRequestSelectedTip: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MaxInvPerRequestTransactionsMsg is the maximum number of hashes that can
|
||||
@@ -13,7 +13,7 @@ const MaxInvPerRequestTransactionsMsg = MaxInvPerMsg
|
||||
// transactions relay protocol.
|
||||
type MsgRequestTransactions struct {
|
||||
baseMessage
|
||||
IDs []*daghash.TxID
|
||||
IDs []*externalapi.DomainTransactionID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -24,7 +24,7 @@ func (msg *MsgRequestTransactions) Command() MessageCommand {
|
||||
|
||||
// NewMsgRequestTransactions returns a new kaspa RequestTransactions message that conforms to
|
||||
// the Message interface. See MsgRequestTransactions for details.
|
||||
func NewMsgRequestTransactions(ids []*daghash.TxID) *MsgRequestTransactions {
|
||||
func NewMsgRequestTransactions(ids []*externalapi.DomainTransactionID) *MsgRequestTransactions {
|
||||
return &MsgRequestTransactions{
|
||||
IDs: ids,
|
||||
}
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// MsgSelectedTip implements the Message interface and represents a kaspa
|
||||
// selectedtip message. It is used to answer getseltip messages and tell
|
||||
// the asking peer what is the selected tip of this peer.
|
||||
type MsgSelectedTip struct {
|
||||
baseMessage
|
||||
// The selected tip hash of the generator of the message.
|
||||
SelectedTipHash *daghash.Hash
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgSelectedTip) Command() MessageCommand {
|
||||
return CmdSelectedTip
|
||||
}
|
||||
|
||||
// NewMsgSelectedTip returns a new kaspa selectedtip message that conforms to the
|
||||
// Message interface.
|
||||
func NewMsgSelectedTip(selectedTipHash *daghash.Hash) *MsgSelectedTip {
|
||||
return &MsgSelectedTip{
|
||||
SelectedTipHash: selectedTipHash,
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestSelectedTip tests the MsgSelectedTip API.
|
||||
func TestSelectedTip(t *testing.T) {
|
||||
|
||||
// Ensure the command is expected value.
|
||||
wantCmd := MessageCommand(11)
|
||||
msg := NewMsgSelectedTip(&daghash.ZeroHash)
|
||||
if cmd := msg.Command(); cmd != wantCmd {
|
||||
t.Errorf("NewMsgSelectedTip: wrong command - got %v want %v",
|
||||
cmd, wantCmd)
|
||||
}
|
||||
}
|
||||
@@ -5,14 +5,14 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// MsgTransactionNotFound defines a kaspa TransactionNotFound message which is sent in response to
|
||||
// a RequestTransactions message if any of the requested data in not available on the peer.
|
||||
type MsgTransactionNotFound struct {
|
||||
baseMessage
|
||||
ID *daghash.TxID
|
||||
ID *externalapi.DomainTransactionID
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
@@ -23,7 +23,7 @@ func (msg *MsgTransactionNotFound) Command() MessageCommand {
|
||||
|
||||
// NewMsgTransactionNotFound returns a new kaspa transactionsnotfound message that conforms to the
|
||||
// Message interface. See MsgTransactionNotFound for details.
|
||||
func NewMsgTransactionNotFound(id *daghash.TxID) *MsgTransactionNotFound {
|
||||
func NewMsgTransactionNotFound(id *externalapi.DomainTransactionID) *MsgTransactionNotFound {
|
||||
return &MsgTransactionNotFound{
|
||||
ID: id,
|
||||
}
|
||||
|
||||
@@ -6,49 +6,24 @@ package appmessage
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"strconv"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/binaryserializer"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
const (
|
||||
// TxVersion is the current latest supported transaction version.
|
||||
TxVersion = 1
|
||||
|
||||
// MaxTxInSequenceNum is the maximum sequence number the sequence field
|
||||
// of a transaction input can be.
|
||||
MaxTxInSequenceNum uint64 = math.MaxUint64
|
||||
|
||||
// MaxPrevOutIndex is the maximum index the index field of a previous
|
||||
// outpoint can be.
|
||||
MaxPrevOutIndex uint32 = 0xffffffff
|
||||
|
||||
// SequenceLockTimeDisabled is a flag that if set on a transaction
|
||||
// input's sequence number, the sequence number will not be interpreted
|
||||
// as a relative locktime.
|
||||
SequenceLockTimeDisabled = 1 << 31
|
||||
|
||||
// SequenceLockTimeIsSeconds is a flag that if set on a transaction
|
||||
// input's sequence number, the relative locktime has units of 512
|
||||
// seconds.
|
||||
SequenceLockTimeIsSeconds = 1 << 22
|
||||
|
||||
// SequenceLockTimeMask is a mask that extracts the relative locktime
|
||||
// when masked against the transaction input sequence number.
|
||||
SequenceLockTimeMask = 0x0000ffff
|
||||
|
||||
// SequenceLockTimeGranularity is the defined time based granularity
|
||||
// for milliseconds-based relative time locks. When converting from milliseconds
|
||||
// to a sequence number, the value is right shifted by this amount,
|
||||
// therefore the granularity of relative time locks in 524288 or 2^19
|
||||
// seconds. Enforced relative lock times are multiples of 524288 milliseconds.
|
||||
SequenceLockTimeGranularity = 19
|
||||
|
||||
// defaultTxInOutAlloc is the default size used for the backing array for
|
||||
// transaction inputs and outputs. The array will dynamically grow as needed,
|
||||
// but this figure is intended to provide enough space for the number of
|
||||
@@ -59,7 +34,7 @@ const (
|
||||
// minTxInPayload is the minimum payload size for a transaction input.
|
||||
// PreviousOutpoint.TxID + PreviousOutpoint.Index 4 bytes + Varint for
|
||||
// SignatureScript length 1 byte + Sequence 4 bytes.
|
||||
minTxInPayload = 9 + daghash.HashSize
|
||||
minTxInPayload = 9 + externalapi.DomainHashSize
|
||||
|
||||
// maxTxInPerMessage is the maximum number of transactions inputs that
|
||||
// a transaction which fits into a message could possibly have.
|
||||
@@ -81,102 +56,18 @@ const (
|
||||
// number of transaction outputs 1 byte + LockTime 4 bytes + min input
|
||||
// payload + min output payload.
|
||||
minTxPayload = 10
|
||||
|
||||
// freeListMaxScriptSize is the size of each buffer in the free list
|
||||
// that is used for deserializing scripts from the appmessage before they are
|
||||
// concatenated into a single contiguous buffers. This value was chosen
|
||||
// because it is slightly more than twice the size of the vast majority
|
||||
// of all "standard" scripts. Larger scripts are still deserialized
|
||||
// properly as the free list will simply be bypassed for them.
|
||||
freeListMaxScriptSize = 512
|
||||
|
||||
// freeListMaxItems is the number of buffers to keep in the free list
|
||||
// to use for script deserialization. This value allows up to 100
|
||||
// scripts per transaction being simultaneously deserialized by 125
|
||||
// peers. Thus, the peak usage of the free list is 12,500 * 512 =
|
||||
// 6,400,000 bytes.
|
||||
freeListMaxItems = 12500
|
||||
)
|
||||
|
||||
// txEncoding is a bitmask defining which transaction fields we
|
||||
// want to encode and which to ignore.
|
||||
type txEncoding uint8
|
||||
|
||||
const (
|
||||
txEncodingFull txEncoding = 0
|
||||
|
||||
txEncodingExcludePayload txEncoding = 1 << iota
|
||||
|
||||
txEncodingExcludeSignatureScript
|
||||
)
|
||||
|
||||
// scriptFreeList defines a free list of byte slices (up to the maximum number
|
||||
// defined by the freeListMaxItems constant) that have a cap according to the
|
||||
// freeListMaxScriptSize constant. It is used to provide temporary buffers for
|
||||
// deserializing scripts in order to greatly reduce the number of allocations
|
||||
// required.
|
||||
//
|
||||
// The caller can obtain a buffer from the free list by calling the Borrow
|
||||
// function and should return it via the Return function when done using it.
|
||||
type scriptFreeList chan []byte
|
||||
|
||||
// Borrow returns a byte slice from the free list with a length according the
|
||||
// provided size. A new buffer is allocated if there are any items available.
|
||||
//
|
||||
// When the size is larger than the max size allowed for items on the free list
|
||||
// a new buffer of the appropriate size is allocated and returned. It is safe
|
||||
// to attempt to return said buffer via the Return function as it will be
|
||||
// ignored and allowed to go the garbage collector.
|
||||
func (c scriptFreeList) Borrow(size uint64) []byte {
|
||||
if size > freeListMaxScriptSize {
|
||||
return make([]byte, size)
|
||||
}
|
||||
|
||||
var buf []byte
|
||||
select {
|
||||
case buf = <-c:
|
||||
default:
|
||||
buf = make([]byte, freeListMaxScriptSize)
|
||||
}
|
||||
return buf[:size]
|
||||
}
|
||||
|
||||
// Return puts the provided byte slice back on the free list when it has a cap
|
||||
// of the expected length. The buffer is expected to have been obtained via
|
||||
// the Borrow function. Any slices that are not of the appropriate size, such
|
||||
// as those whose size is greater than the largest allowed free list item size
|
||||
// are simply ignored so they can go to the garbage collector.
|
||||
func (c scriptFreeList) Return(buf []byte) {
|
||||
// Ignore any buffers returned that aren't the expected size for the
|
||||
// free list.
|
||||
if cap(buf) != freeListMaxScriptSize {
|
||||
return
|
||||
}
|
||||
|
||||
// Return the buffer to the free list when it's not full. Otherwise let
|
||||
// it be garbage collected.
|
||||
select {
|
||||
case c <- buf:
|
||||
default:
|
||||
// Let it go to the garbage collector.
|
||||
}
|
||||
}
|
||||
|
||||
// Create the concurrent safe free list to use for script deserialization. As
|
||||
// previously described, this free list is maintained to significantly reduce
|
||||
// the number of allocations.
|
||||
var scriptPool scriptFreeList = make(chan []byte, freeListMaxItems)
|
||||
|
||||
// Outpoint defines a kaspa data type that is used to track previous
|
||||
// transaction outputs.
|
||||
type Outpoint struct {
|
||||
TxID daghash.TxID
|
||||
TxID externalapi.DomainTransactionID
|
||||
Index uint32
|
||||
}
|
||||
|
||||
// NewOutpoint returns a new kaspa transaction outpoint point with the
|
||||
// provided hash and index.
|
||||
func NewOutpoint(txID *daghash.TxID, index uint32) *Outpoint {
|
||||
func NewOutpoint(txID *externalapi.DomainTransactionID, index uint32) *Outpoint {
|
||||
return &Outpoint{
|
||||
TxID: *txID,
|
||||
Index: index,
|
||||
@@ -191,9 +82,9 @@ func (o Outpoint) String() string {
|
||||
// maximum message payload may increase in the future and this
|
||||
// optimization may go unnoticed, so allocate space for 10 decimal
|
||||
// digits, which will fit any uint32.
|
||||
buf := make([]byte, 2*daghash.HashSize+1, 2*daghash.HashSize+1+10)
|
||||
buf := make([]byte, 2*externalapi.DomainHashSize+1, 2*externalapi.DomainHashSize+1+10)
|
||||
copy(buf, o.TxID.String())
|
||||
buf[2*daghash.HashSize] = ':'
|
||||
buf[2*externalapi.DomainHashSize] = ':'
|
||||
buf = strconv.AppendUint(buf, uint64(o.Index), 10)
|
||||
return string(buf)
|
||||
}
|
||||
@@ -205,27 +96,6 @@ type TxIn struct {
|
||||
Sequence uint64
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// the transaction input.
|
||||
func (t *TxIn) SerializeSize() int {
|
||||
return t.serializeSize(txEncodingFull)
|
||||
}
|
||||
|
||||
func (t *TxIn) serializeSize(encodingFlags txEncoding) int {
|
||||
// Outpoint ID 32 bytes + Outpoint Index 4 bytes + Sequence 8 bytes +
|
||||
// serialized varint size for the length of SignatureScript +
|
||||
// SignatureScript bytes.
|
||||
return 44 + serializeSignatureScriptSize(t.SignatureScript, encodingFlags)
|
||||
}
|
||||
|
||||
func serializeSignatureScriptSize(signatureScript []byte, encodingFlags txEncoding) int {
|
||||
if encodingFlags&txEncodingExcludeSignatureScript != txEncodingExcludeSignatureScript {
|
||||
return VarIntSerializeSize(uint64(len(signatureScript))) +
|
||||
len(signatureScript)
|
||||
}
|
||||
return VarIntSerializeSize(0)
|
||||
}
|
||||
|
||||
// NewTxIn returns a new kaspa transaction input with the provided
|
||||
// previous outpoint point and signature script with a default sequence of
|
||||
// MaxTxInSequenceNum.
|
||||
@@ -233,7 +103,7 @@ func NewTxIn(prevOut *Outpoint, signatureScript []byte) *TxIn {
|
||||
return &TxIn{
|
||||
PreviousOutpoint: *prevOut,
|
||||
SignatureScript: signatureScript,
|
||||
Sequence: MaxTxInSequenceNum,
|
||||
Sequence: constants.MaxTxInSequenceNum,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -243,14 +113,6 @@ type TxOut struct {
|
||||
ScriptPubKey []byte
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize the
|
||||
// the transaction output.
|
||||
func (t *TxOut) SerializeSize() int {
|
||||
// Value 8 bytes + serialized varint size for the length of ScriptPubKey +
|
||||
// ScriptPubKey bytes.
|
||||
return 8 + VarIntSerializeSize(uint64(len(t.ScriptPubKey))) + len(t.ScriptPubKey)
|
||||
}
|
||||
|
||||
// NewTxOut returns a new kaspa transaction output with the provided
|
||||
// transaction value and public key script.
|
||||
func NewTxOut(value uint64, scriptPubKey []byte) *TxOut {
|
||||
@@ -272,9 +134,9 @@ type MsgTx struct {
|
||||
TxIn []*TxIn
|
||||
TxOut []*TxOut
|
||||
LockTime uint64
|
||||
SubnetworkID subnetworkid.SubnetworkID
|
||||
SubnetworkID externalapi.DomainSubnetworkID
|
||||
Gas uint64
|
||||
PayloadHash *daghash.Hash
|
||||
PayloadHash externalapi.DomainHash
|
||||
Payload []byte
|
||||
}
|
||||
|
||||
@@ -295,41 +157,17 @@ func (msg *MsgTx) AddTxOut(to *TxOut) {
|
||||
// value and reference the relevant block id, instead of previous transaction id.
|
||||
func (msg *MsgTx) IsCoinBase() bool {
|
||||
// A coinbase transaction must have subnetwork id SubnetworkIDCoinbase
|
||||
return msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase)
|
||||
return msg.SubnetworkID == subnetworks.SubnetworkIDCoinbase
|
||||
}
|
||||
|
||||
// TxHash generates the Hash for the transaction.
|
||||
func (msg *MsgTx) TxHash() *daghash.Hash {
|
||||
// Encode the transaction and calculate double sha256 on the result.
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
err := msg.serialize(writer, txEncodingExcludePayload)
|
||||
if err != nil {
|
||||
// this writer never return errors (no allocations or possible failures) so errors can only come from validity checks,
|
||||
// and we assume we never construct malformed transactions.
|
||||
panic(fmt.Sprintf("TxHash() failed. this should never fail for structurally-valid transactions. err: %+v", err))
|
||||
}
|
||||
|
||||
hash := writer.Finalize()
|
||||
return &hash
|
||||
func (msg *MsgTx) TxHash() *externalapi.DomainHash {
|
||||
return consensushashing.TransactionHash(MsgTxToDomainTransaction(msg))
|
||||
}
|
||||
|
||||
// TxID generates the Hash for the transaction without the signature script, gas and payload fields.
|
||||
func (msg *MsgTx) TxID() *daghash.TxID {
|
||||
// Encode the transaction, replace signature script with zeroes, cut off
|
||||
// payload and calculate double sha256 on the result.
|
||||
var encodingFlags txEncoding
|
||||
if !msg.IsCoinBase() {
|
||||
encodingFlags = txEncodingExcludeSignatureScript | txEncodingExcludePayload
|
||||
}
|
||||
writer := daghash.NewDoubleHashWriter()
|
||||
err := msg.serialize(writer, encodingFlags)
|
||||
if err != nil {
|
||||
// this writer never return errors (no allocations or possible failures) so errors can only come from validity checks,
|
||||
// and we assume we never construct malformed transactions.
|
||||
panic(fmt.Sprintf("TxID() failed. this should never fail for structurally-valid transactions. err: %+v", err))
|
||||
}
|
||||
txID := daghash.TxID(writer.Finalize())
|
||||
return &txID
|
||||
func (msg *MsgTx) TxID() *externalapi.DomainTransactionID {
|
||||
return consensushashing.TransactionID(MsgTxToDomainTransaction(msg))
|
||||
}
|
||||
|
||||
// Copy creates a deep copy of a transaction so that the original does not get
|
||||
@@ -357,7 +195,7 @@ func (msg *MsgTx) Copy() *MsgTx {
|
||||
// Deep copy the old previous outpoint.
|
||||
oldOutpoint := oldTxIn.PreviousOutpoint
|
||||
newOutpoint := Outpoint{}
|
||||
newOutpoint.TxID.SetBytes(oldOutpoint.TxID[:])
|
||||
newOutpoint.TxID = oldOutpoint.TxID
|
||||
newOutpoint.Index = oldOutpoint.Index
|
||||
|
||||
// Deep copy the old signature script.
|
||||
@@ -403,368 +241,6 @@ func (msg *MsgTx) Copy() *MsgTx {
|
||||
return &newTx
|
||||
}
|
||||
|
||||
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Deserialize for decoding transactions stored to disk, such as in a
|
||||
// database, as opposed to decoding transactions from the appmessage.
|
||||
func (msg *MsgTx) KaspaDecode(r io.Reader, pver uint32) error {
|
||||
version, err := binaryserializer.Uint32(r, littleEndian)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msg.Version = int32(version)
|
||||
|
||||
count, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Prevent more input transactions than could possibly fit into a
|
||||
// message. It would be possible to cause memory exhaustion and panics
|
||||
// without a sane upper bound on this count.
|
||||
if count > uint64(maxTxInPerMessage) {
|
||||
str := fmt.Sprintf("too many input transactions to fit into "+
|
||||
"max message size [count %d, max %d]", count,
|
||||
maxTxInPerMessage)
|
||||
return messageError("MsgTx.KaspaDecode", str)
|
||||
}
|
||||
|
||||
// returnScriptBuffers is a closure that returns any script buffers that
|
||||
// were borrowed from the pool when there are any deserialization
|
||||
// errors. This is only valid to call before the final step which
|
||||
// replaces the scripts with the location in a contiguous buffer and
|
||||
// returns them.
|
||||
returnScriptBuffers := func() {
|
||||
for _, txIn := range msg.TxIn {
|
||||
if txIn == nil || txIn.SignatureScript == nil {
|
||||
continue
|
||||
}
|
||||
scriptPool.Return(txIn.SignatureScript)
|
||||
}
|
||||
for _, txOut := range msg.TxOut {
|
||||
if txOut == nil || txOut.ScriptPubKey == nil {
|
||||
continue
|
||||
}
|
||||
scriptPool.Return(txOut.ScriptPubKey)
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize the inputs.
|
||||
var totalScriptSize uint64
|
||||
txIns := make([]TxIn, count)
|
||||
msg.TxIn = make([]*TxIn, count)
|
||||
for i := uint64(0); i < count; i++ {
|
||||
// The pointer is set now in case a script buffer is borrowed
|
||||
// and needs to be returned to the pool on error.
|
||||
ti := &txIns[i]
|
||||
msg.TxIn[i] = ti
|
||||
err = readTxIn(r, pver, msg.Version, ti)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
totalScriptSize += uint64(len(ti.SignatureScript))
|
||||
}
|
||||
|
||||
count, err = ReadVarInt(r)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
// Prevent more output transactions than could possibly fit into a
|
||||
// message. It would be possible to cause memory exhaustion and panics
|
||||
// without a sane upper bound on this count.
|
||||
if count > uint64(maxTxOutPerMessage) {
|
||||
returnScriptBuffers()
|
||||
str := fmt.Sprintf("too many output transactions to fit into "+
|
||||
"max message size [count %d, max %d]", count,
|
||||
maxTxOutPerMessage)
|
||||
return messageError("MsgTx.KaspaDecode", str)
|
||||
}
|
||||
|
||||
// Deserialize the outputs.
|
||||
txOuts := make([]TxOut, count)
|
||||
msg.TxOut = make([]*TxOut, count)
|
||||
for i := uint64(0); i < count; i++ {
|
||||
// The pointer is set now in case a script buffer is borrowed
|
||||
// and needs to be returned to the pool on error.
|
||||
to := &txOuts[i]
|
||||
msg.TxOut[i] = to
|
||||
err = readTxOut(r, pver, msg.Version, to)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
totalScriptSize += uint64(len(to.ScriptPubKey))
|
||||
}
|
||||
|
||||
lockTime, err := binaryserializer.Uint64(r, littleEndian)
|
||||
msg.LockTime = lockTime
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.ReadFull(r, msg.SubnetworkID[:])
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
if !msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
msg.Gas, err = binaryserializer.Uint64(r, littleEndian)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
var payloadHash daghash.Hash
|
||||
err = ReadElement(r, &payloadHash)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
msg.PayloadHash = &payloadHash
|
||||
|
||||
payloadLength, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
|
||||
msg.Payload = make([]byte, payloadLength)
|
||||
_, err = io.ReadFull(r, msg.Payload)
|
||||
if err != nil {
|
||||
returnScriptBuffers()
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a single allocation to house all of the scripts and set each
|
||||
// input signature script and output public key script to the
|
||||
// appropriate subslice of the overall contiguous buffer. Then, return
|
||||
// each individual script buffer back to the pool so they can be reused
|
||||
// for future deserializations. This is done because it significantly
|
||||
// reduces the number of allocations the garbage collector needs to
|
||||
// track, which in turn improves performance and drastically reduces the
|
||||
// amount of runtime overhead that would otherwise be needed to keep
|
||||
// track of millions of small allocations.
|
||||
//
|
||||
// NOTE: It is no longer valid to call the returnScriptBuffers closure
|
||||
// after these blocks of code run because it is already done and the
|
||||
// scripts in the transaction inputs and outputs no longer point to the
|
||||
// buffers.
|
||||
var offset uint64
|
||||
scripts := make([]byte, totalScriptSize)
|
||||
for i := 0; i < len(msg.TxIn); i++ {
|
||||
// Copy the signature script into the contiguous buffer at the
|
||||
// appropriate offset.
|
||||
signatureScript := msg.TxIn[i].SignatureScript
|
||||
copy(scripts[offset:], signatureScript)
|
||||
|
||||
// Reset the signature script of the transaction input to the
|
||||
// slice of the contiguous buffer where the script lives.
|
||||
scriptSize := uint64(len(signatureScript))
|
||||
end := offset + scriptSize
|
||||
msg.TxIn[i].SignatureScript = scripts[offset:end:end]
|
||||
offset += scriptSize
|
||||
|
||||
// Return the temporary script buffer to the pool.
|
||||
scriptPool.Return(signatureScript)
|
||||
}
|
||||
for i := 0; i < len(msg.TxOut); i++ {
|
||||
// Copy the public key script into the contiguous buffer at the
|
||||
// appropriate offset.
|
||||
scriptPubKey := msg.TxOut[i].ScriptPubKey
|
||||
copy(scripts[offset:], scriptPubKey)
|
||||
|
||||
// Reset the public key script of the transaction output to the
|
||||
// slice of the contiguous buffer where the script lives.
|
||||
scriptSize := uint64(len(scriptPubKey))
|
||||
end := offset + scriptSize
|
||||
msg.TxOut[i].ScriptPubKey = scripts[offset:end:end]
|
||||
offset += scriptSize
|
||||
|
||||
// Return the temporary script buffer to the pool.
|
||||
scriptPool.Return(scriptPubKey)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Deserialize decodes a transaction from r into the receiver using a format
|
||||
// that is suitable for long-term storage such as a database while respecting
|
||||
// the Version field in the transaction. This function differs from KaspaDecode
|
||||
// in that KaspaDecode decodes from the kaspa appmessage protocol as it was sent
|
||||
// across the network. The appmessage encoding can technically differ depending on
|
||||
// the protocol version and doesn't even really need to match the format of a
|
||||
// stored transaction at all. As of the time this comment was written, the
|
||||
// encoded transaction is the same in both instances, but there is a distinct
|
||||
// difference and separating the two allows the API to be flexible enough to
|
||||
// deal with changes.
|
||||
func (msg *MsgTx) Deserialize(r io.Reader) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaDecode.
|
||||
return msg.KaspaDecode(r, 0)
|
||||
}
|
||||
|
||||
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
|
||||
// This is part of the Message interface implementation.
|
||||
// See Serialize for encoding transactions to be stored to disk, such as in a
|
||||
// database, as opposed to encoding transactions for the appmessage.
|
||||
func (msg *MsgTx) KaspaEncode(w io.Writer, pver uint32) error {
|
||||
return msg.encode(w, pver, txEncodingFull)
|
||||
}
|
||||
|
||||
func (msg *MsgTx) encode(w io.Writer, pver uint32, encodingFlags txEncoding) error {
|
||||
err := binaryserializer.PutUint32(w, littleEndian, uint32(msg.Version))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
count := uint64(len(msg.TxIn))
|
||||
err = WriteVarInt(w, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ti := range msg.TxIn {
|
||||
err = writeTxIn(w, pver, msg.Version, ti, encodingFlags)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
count = uint64(len(msg.TxOut))
|
||||
err = WriteVarInt(w, count)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, to := range msg.TxOut {
|
||||
err = WriteTxOut(w, pver, msg.Version, to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = binaryserializer.PutUint64(w, littleEndian, msg.LockTime)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = w.Write(msg.SubnetworkID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
if msg.SubnetworkID.IsBuiltIn() && msg.Gas != 0 {
|
||||
str := "Transactions from built-in should have 0 gas"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
}
|
||||
|
||||
err = binaryserializer.PutUint64(w, littleEndian, msg.Gas)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = WriteElement(w, msg.PayloadHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if encodingFlags&txEncodingExcludePayload != txEncodingExcludePayload {
|
||||
err = WriteVarInt(w, uint64(len(msg.Payload)))
|
||||
w.Write(msg.Payload)
|
||||
} else {
|
||||
err = WriteVarInt(w, 0)
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if msg.Payload != nil {
|
||||
str := "Transactions from native subnetwork should have <nil> payload"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
} else if msg.PayloadHash != nil {
|
||||
str := "Transactions from native subnetwork should have <nil> payload hash"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
} else if msg.Gas != 0 {
|
||||
str := "Transactions from native subnetwork should have 0 gas"
|
||||
return messageError("MsgTx.KaspaEncode", str)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Serialize encodes the transaction to w using a format that suitable for
|
||||
// long-term storage such as a database while respecting the Version field in
|
||||
// the transaction. This function differs from KaspaEncode in that KaspaEncode
|
||||
// encodes the transaction to the kaspa appmessage protocol in order to be sent
|
||||
// across the network. The appmessage encoding can technically differ depending on
|
||||
// the protocol version and doesn't even really need to match the format of a
|
||||
// stored transaction at all. As of the time this comment was written, the
|
||||
// encoded transaction is the same in both instances, but there is a distinct
|
||||
// difference and separating the two allows the API to be flexible enough to
|
||||
// deal with changes.
|
||||
func (msg *MsgTx) Serialize(w io.Writer) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of KaspaEncode.
|
||||
return msg.KaspaEncode(w, 0)
|
||||
}
|
||||
|
||||
func (msg *MsgTx) serialize(w io.Writer, encodingFlags txEncoding) error {
|
||||
// At the current time, there is no difference between the appmessage encoding
|
||||
// at protocol version 0 and the stable long-term storage format. As
|
||||
// a result, make use of `encode`.
|
||||
return msg.encode(w, 0, encodingFlags)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize
|
||||
// the transaction.
|
||||
func (msg *MsgTx) SerializeSize() int {
|
||||
return msg.serializeSize(txEncodingFull)
|
||||
}
|
||||
|
||||
// SerializeSize returns the number of bytes it would take to serialize
|
||||
// the transaction.
|
||||
func (msg *MsgTx) serializeSize(encodingFlags txEncoding) int {
|
||||
// Version 4 bytes + LockTime 8 bytes + SubnetworkID 20
|
||||
// bytes + Serialized varint size for the number of transaction
|
||||
// inputs and outputs.
|
||||
n := 32 + VarIntSerializeSize(uint64(len(msg.TxIn))) +
|
||||
VarIntSerializeSize(uint64(len(msg.TxOut)))
|
||||
|
||||
if !msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
// Gas 8 bytes
|
||||
n += 8
|
||||
|
||||
// PayloadHash
|
||||
n += daghash.HashSize
|
||||
|
||||
// Serialized varint size for the length of the payload
|
||||
if encodingFlags&txEncodingExcludePayload != txEncodingExcludePayload {
|
||||
n += VarIntSerializeSize(uint64(len(msg.Payload)))
|
||||
n += len(msg.Payload)
|
||||
} else {
|
||||
n += VarIntSerializeSize(0)
|
||||
}
|
||||
}
|
||||
|
||||
for _, txIn := range msg.TxIn {
|
||||
n += txIn.serializeSize(encodingFlags)
|
||||
}
|
||||
|
||||
for _, txOut := range msg.TxOut {
|
||||
n += txOut.SerializeSize()
|
||||
}
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message. This is part
|
||||
// of the Message interface implementation.
|
||||
func (msg *MsgTx) Command() MessageCommand {
|
||||
@@ -777,52 +253,14 @@ func (msg *MsgTx) MaxPayloadLength(pver uint32) uint32 {
|
||||
return MaxMessagePayload
|
||||
}
|
||||
|
||||
// ScriptPubKeyLocs returns a slice containing the start of each public key script
|
||||
// within the raw serialized transaction. The caller can easily obtain the
|
||||
// length of each script by using len on the script available via the
|
||||
// appropriate transaction output entry.
|
||||
func (msg *MsgTx) ScriptPubKeyLocs() []int {
|
||||
numTxOut := len(msg.TxOut)
|
||||
if numTxOut == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// The starting offset in the serialized transaction of the first
|
||||
// transaction output is:
|
||||
//
|
||||
// Version 4 bytes + serialized varint size for the number of
|
||||
// transaction inputs and outputs + serialized size of each transaction
|
||||
// input.
|
||||
n := 4 + VarIntSerializeSize(uint64(len(msg.TxIn))) +
|
||||
VarIntSerializeSize(uint64(numTxOut))
|
||||
|
||||
for _, txIn := range msg.TxIn {
|
||||
n += txIn.SerializeSize()
|
||||
}
|
||||
|
||||
// Calculate and set the appropriate offset for each public key script.
|
||||
scriptPubKeyLocs := make([]int, numTxOut)
|
||||
for i, txOut := range msg.TxOut {
|
||||
// The offset of the script in the transaction output is:
|
||||
//
|
||||
// Value 8 bytes + serialized varint size for the length of
|
||||
// ScriptPubKey.
|
||||
n += 8 + VarIntSerializeSize(uint64(len(txOut.ScriptPubKey)))
|
||||
scriptPubKeyLocs[i] = n
|
||||
n += len(txOut.ScriptPubKey)
|
||||
}
|
||||
|
||||
return scriptPubKeyLocs
|
||||
}
|
||||
|
||||
// IsSubnetworkCompatible return true iff subnetworkID is one or more of the following:
|
||||
// 1. The SupportsAll subnetwork (full node)
|
||||
// 2. The native subnetwork
|
||||
// 3. The transaction's subnetwork
|
||||
func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *subnetworkid.SubnetworkID) bool {
|
||||
func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *externalapi.DomainSubnetworkID) bool {
|
||||
return subnetworkID == nil ||
|
||||
subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
|
||||
subnetworkID.IsEqual(&msg.SubnetworkID)
|
||||
*subnetworkID == subnetworks.SubnetworkIDNative ||
|
||||
*subnetworkID == msg.SubnetworkID
|
||||
}
|
||||
|
||||
// newMsgTx returns a new tx message that conforms to the Message interface.
|
||||
@@ -834,7 +272,7 @@ func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *subnetworkid.SubnetworkID
|
||||
// The payload hash is calculated automatically according to provided payload.
|
||||
// Also, the lock time is set to zero to indicate the transaction is valid
|
||||
// immediately as opposed to some time in future.
|
||||
func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetworkid.SubnetworkID,
|
||||
func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externalapi.DomainSubnetworkID,
|
||||
gas uint64, payload []byte, lockTime uint64) *MsgTx {
|
||||
|
||||
if txIn == nil {
|
||||
@@ -845,9 +283,9 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetw
|
||||
txOut = make([]*TxOut, 0, defaultTxInOutAlloc)
|
||||
}
|
||||
|
||||
var payloadHash *daghash.Hash
|
||||
if !subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
|
||||
payloadHash = daghash.DoubleHashP(payload)
|
||||
var payloadHash externalapi.DomainHash
|
||||
if *subnetworkID != subnetworks.SubnetworkIDNative {
|
||||
payloadHash = *hashes.HashData(payload)
|
||||
}
|
||||
|
||||
return &MsgTx{
|
||||
@@ -864,11 +302,11 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetw
|
||||
|
||||
// NewNativeMsgTx returns a new tx message in the native subnetwork
|
||||
func NewNativeMsgTx(version int32, txIn []*TxIn, txOut []*TxOut) *MsgTx {
|
||||
return newMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDNative, 0, nil, 0)
|
||||
return newMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDNative, 0, nil, 0)
|
||||
}
|
||||
|
||||
// NewSubnetworkMsgTx returns a new tx message in the specified subnetwork with specified gas and payload
|
||||
func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetworkid.SubnetworkID,
|
||||
func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externalapi.DomainSubnetworkID,
|
||||
gas uint64, payload []byte) *MsgTx {
|
||||
|
||||
return newMsgTx(version, txIn, txOut, subnetworkID, gas, payload, 0)
|
||||
@@ -878,7 +316,7 @@ func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkI
|
||||
//
|
||||
// See newMsgTx for further documntation of the parameters
|
||||
func NewNativeMsgTxWithLocktime(version int32, txIn []*TxIn, txOut []*TxOut, locktime uint64) *MsgTx {
|
||||
return newMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDNative, 0, nil, locktime)
|
||||
return newMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDNative, 0, nil, locktime)
|
||||
}
|
||||
|
||||
// NewRegistryMsgTx creates a new MsgTx that registers a new subnetwork
|
||||
@@ -886,119 +324,5 @@ func NewRegistryMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, gasLimit uint
|
||||
payload := make([]byte, 8)
|
||||
binary.LittleEndian.PutUint64(payload, gasLimit)
|
||||
|
||||
return NewSubnetworkMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDRegistry, 0, payload)
|
||||
}
|
||||
|
||||
// readOutpoint reads the next sequence of bytes from r as an Outpoint.
|
||||
func readOutpoint(r io.Reader, pver uint32, version int32, op *Outpoint) error {
|
||||
_, err := io.ReadFull(r, op.TxID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op.Index, err = binaryserializer.Uint32(r, littleEndian)
|
||||
return err
|
||||
}
|
||||
|
||||
// writeOutpoint encodes op to the kaspa protocol encoding for an Outpoint
|
||||
// to w.
|
||||
func writeOutpoint(w io.Writer, pver uint32, version int32, op *Outpoint) error {
|
||||
_, err := w.Write(op.TxID[:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binaryserializer.PutUint32(w, littleEndian, op.Index)
|
||||
}
|
||||
|
||||
// readScript reads a variable length byte array that represents a transaction
|
||||
// script. It is encoded as a varInt containing the length of the array
|
||||
// followed by the bytes themselves. An error is returned if the length is
|
||||
// greater than the passed maxAllowed parameter which helps protect against
|
||||
// memory exhaustion attacks and forced panics through malformed messages. The
|
||||
// fieldName parameter is only used for the error message so it provides more
|
||||
// context in the error.
|
||||
func readScript(r io.Reader, pver uint32, maxAllowed uint32, fieldName string) ([]byte, error) {
|
||||
count, err := ReadVarInt(r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Prevent byte array larger than the max message size. It would
|
||||
// be possible to cause memory exhaustion and panics without a sane
|
||||
// upper bound on this count.
|
||||
if count > uint64(maxAllowed) {
|
||||
str := fmt.Sprintf("%s is larger than the max allowed size "+
|
||||
"[count %d, max %d]", fieldName, count, maxAllowed)
|
||||
return nil, messageError("readScript", str)
|
||||
}
|
||||
|
||||
b := scriptPool.Borrow(count)
|
||||
_, err = io.ReadFull(r, b)
|
||||
if err != nil {
|
||||
scriptPool.Return(b)
|
||||
return nil, err
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// readTxIn reads the next sequence of bytes from r as a transaction input
|
||||
// (TxIn).
|
||||
func readTxIn(r io.Reader, pver uint32, version int32, ti *TxIn) error {
|
||||
err := readOutpoint(r, pver, version, &ti.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ti.SignatureScript, err = readScript(r, pver, MaxMessagePayload,
|
||||
"transaction input signature script")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ReadElement(r, &ti.Sequence)
|
||||
}
|
||||
|
||||
// writeTxIn encodes ti to the kaspa protocol encoding for a transaction
|
||||
// input (TxIn) to w.
|
||||
func writeTxIn(w io.Writer, pver uint32, version int32, ti *TxIn, encodingFlags txEncoding) error {
|
||||
err := writeOutpoint(w, pver, version, &ti.PreviousOutpoint)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if encodingFlags&txEncodingExcludeSignatureScript != txEncodingExcludeSignatureScript {
|
||||
err = WriteVarBytes(w, pver, ti.SignatureScript)
|
||||
} else {
|
||||
err = WriteVarBytes(w, pver, []byte{})
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return binaryserializer.PutUint64(w, littleEndian, ti.Sequence)
|
||||
}
|
||||
|
||||
// readTxOut reads the next sequence of bytes from r as a transaction output
|
||||
// (TxOut).
|
||||
func readTxOut(r io.Reader, pver uint32, version int32, to *TxOut) error {
|
||||
err := ReadElement(r, &to.Value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
to.ScriptPubKey, err = readScript(r, pver, MaxMessagePayload,
|
||||
"transaction output public key script")
|
||||
return err
|
||||
}
|
||||
|
||||
// WriteTxOut encodes to into the kaspa protocol encoding for a transaction
|
||||
// output (TxOut) to w.
|
||||
func WriteTxOut(w io.Writer, pver uint32, version int32, to *TxOut) error {
|
||||
err := binaryserializer.PutUint64(w, littleEndian, uint64(to.Value))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return WriteVarBytes(w, pver, to.ScriptPubKey)
|
||||
return NewSubnetworkMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDRegistry, 0, payload)
|
||||
}
|
||||
|
||||
@@ -7,24 +7,25 @@ package appmessage
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/pkg/errors"
|
||||
"io"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// TestTx tests the MsgTx API.
|
||||
func TestTx(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
txIDStr := "3ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
txID, err := daghash.NewTxIDFromStr(txIDStr)
|
||||
txIDStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
|
||||
txID, err := transactionid.FromString(txIDStr)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
}
|
||||
@@ -51,7 +52,7 @@ func TestTx(t *testing.T) {
|
||||
// testing package functionality.
|
||||
prevOutIndex := uint32(1)
|
||||
prevOut := NewOutpoint(txID, prevOutIndex)
|
||||
if !prevOut.TxID.IsEqual(txID) {
|
||||
if prevOut.TxID != *txID {
|
||||
t.Errorf("NewOutpoint: wrong ID - got %v, want %v",
|
||||
spew.Sprint(&prevOut.TxID), spew.Sprint(txID))
|
||||
}
|
||||
@@ -130,8 +131,8 @@ func TestTx(t *testing.T) {
|
||||
|
||||
// TestTxHash tests the ability to generate the hash of a transaction accurately.
|
||||
func TestTxHashAndID(t *testing.T) {
|
||||
txID1Str := "edca872f27279674c7a52192b32fd68b8b8be714bfea52d98b2c3c86c30e85c6"
|
||||
wantTxID1, err := daghash.NewTxIDFromStr(txID1Str)
|
||||
txID1Str := "a3d29c39bfb578235e4813cc8138a9ba10def63acad193a7a880159624840d7f"
|
||||
wantTxID1, err := transactionid.FromString(txID1Str)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
return
|
||||
@@ -140,7 +141,7 @@ func TestTxHashAndID(t *testing.T) {
|
||||
// A coinbase transaction
|
||||
txIn := &TxIn{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
TxID: externalapi.DomainTransactionID{},
|
||||
Index: math.MaxUint32,
|
||||
},
|
||||
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
|
||||
@@ -162,31 +163,31 @@ func TestTxHashAndID(t *testing.T) {
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
}
|
||||
tx1 := NewSubnetworkMsgTx(1, []*TxIn{txIn}, []*TxOut{txOut}, subnetworkid.SubnetworkIDCoinbase, 0, nil)
|
||||
tx1 := NewSubnetworkMsgTx(1, []*TxIn{txIn}, []*TxOut{txOut}, &subnetworks.SubnetworkIDCoinbase, 0, nil)
|
||||
|
||||
// Ensure the hash produced is expected.
|
||||
tx1Hash := tx1.TxHash()
|
||||
if !tx1Hash.IsEqual((*daghash.Hash)(wantTxID1)) {
|
||||
if *tx1Hash != (externalapi.DomainHash)(*wantTxID1) {
|
||||
t.Errorf("TxHash: wrong hash - got %v, want %v",
|
||||
spew.Sprint(tx1Hash), spew.Sprint(wantTxID1))
|
||||
}
|
||||
|
||||
// Ensure the TxID for coinbase transaction is the same as TxHash.
|
||||
tx1ID := tx1.TxID()
|
||||
if !tx1ID.IsEqual(wantTxID1) {
|
||||
if *tx1ID != *wantTxID1 {
|
||||
t.Errorf("TxID: wrong ID - got %v, want %v",
|
||||
spew.Sprint(tx1ID), spew.Sprint(wantTxID1))
|
||||
}
|
||||
|
||||
hash2Str := "b11924b7eeffea821522222576c53dc5b8ddd97602f81e5e124d2626646d74ca"
|
||||
wantHash2, err := daghash.NewHashFromStr(hash2Str)
|
||||
hash2Str := "c84f3009b337aaa3adeb2ffd41010d5f62dd773ca25b39c908a77da91f87b729"
|
||||
wantHash2, err := hashes.FromString(hash2Str)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
id2Str := "750499ae9e6d44961ef8bad8af27a44dd4bcbea166b71baf181e8d3997e1ff72"
|
||||
wantID2, err := daghash.NewTxIDFromStr(id2Str)
|
||||
id2Str := "7c919f676109743a1271a88beeb43849a6f9cc653f6082e59a7266f3df4802b9"
|
||||
wantID2, err := transactionid.FromString(id2Str)
|
||||
if err != nil {
|
||||
t.Errorf("NewTxIDFromStr: %v", err)
|
||||
return
|
||||
@@ -195,7 +196,7 @@ func TestTxHashAndID(t *testing.T) {
|
||||
txIns := []*TxIn{{
|
||||
PreviousOutpoint: Outpoint{
|
||||
Index: 0,
|
||||
TxID: daghash.TxID{1, 2, 3},
|
||||
TxID: externalapi.DomainTransactionID{1, 2, 3},
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xDA, 0x0D, 0xC6, 0xAE, 0xCE, 0xFE, 0x1E, 0x06, 0xEF, 0xDF,
|
||||
@@ -226,717 +227,29 @@ func TestTxHashAndID(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
tx2 := NewSubnetworkMsgTx(1, txIns, txOuts, &subnetworkid.SubnetworkID{1, 2, 3}, 0, payload)
|
||||
tx2 := NewSubnetworkMsgTx(1, txIns, txOuts, &externalapi.DomainSubnetworkID{1, 2, 3}, 0, payload)
|
||||
|
||||
// Ensure the hash produced is expected.
|
||||
tx2Hash := tx2.TxHash()
|
||||
if !tx2Hash.IsEqual(wantHash2) {
|
||||
if *tx2Hash != *wantHash2 {
|
||||
t.Errorf("TxHash: wrong hash - got %v, want %v",
|
||||
spew.Sprint(tx2Hash), spew.Sprint(wantHash2))
|
||||
}
|
||||
|
||||
// Ensure the TxID for coinbase transaction is the same as TxHash.
|
||||
tx2ID := tx2.TxID()
|
||||
if !tx2ID.IsEqual(wantID2) {
|
||||
if *tx2ID != *wantID2 {
|
||||
t.Errorf("TxID: wrong ID - got %v, want %v",
|
||||
spew.Sprint(tx2ID), spew.Sprint(wantID2))
|
||||
}
|
||||
|
||||
if tx2ID.IsEqual((*daghash.TxID)(tx2Hash)) {
|
||||
if *tx2ID == (externalapi.DomainTransactionID)(*tx2Hash) {
|
||||
t.Errorf("tx2ID and tx2Hash shouldn't be the same for non-coinbase transaction with signature and/or payload")
|
||||
}
|
||||
|
||||
tx2.TxIn[0].SignatureScript = []byte{}
|
||||
newTx2Hash := tx2.TxHash()
|
||||
if !tx2ID.IsEqual((*daghash.TxID)(newTx2Hash)) {
|
||||
if *tx2ID != (externalapi.DomainTransactionID)(*newTx2Hash) {
|
||||
t.Errorf("tx2ID and newTx2Hash should be the same for transaction with an empty signature")
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxEncoding tests the MsgTx appmessage encode and decode for various numbers
|
||||
// of transaction inputs and outputs and protocol versions.
|
||||
func TestTxEncoding(t *testing.T) {
|
||||
// Empty tx message.
|
||||
noTx := NewNativeMsgTx(1, nil, nil)
|
||||
noTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
in *MsgTx // Message to encode
|
||||
out *MsgTx // Expected decoded message
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
}{
|
||||
// Latest protocol version with no transactions.
|
||||
{
|
||||
noTx,
|
||||
noTx,
|
||||
noTxEncoded,
|
||||
ProtocolVersion,
|
||||
},
|
||||
|
||||
// Latest protocol version with multiple transactions.
|
||||
{
|
||||
multiTx,
|
||||
multiTx,
|
||||
multiTxEncoded,
|
||||
ProtocolVersion,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode the message to appmessage format.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.KaspaEncode(&buf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaEncode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode the message from appmessage format.
|
||||
var msg MsgTx
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = msg.KaspaDecode(rbuf, test.pver)
|
||||
if err != nil {
|
||||
t.Errorf("KaspaDecode #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&msg, test.out) {
|
||||
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&msg), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxEncodingErrors performs negative tests against appmessage encode and decode
|
||||
// of MsgTx to confirm error paths work correctly.
|
||||
func TestTxEncodingErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
tests := []struct {
|
||||
in *MsgTx // Value to encode
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{multiTx, multiTxEncoded, pver, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction inputs.
|
||||
{multiTx, multiTxEncoded, pver, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block hash.
|
||||
{multiTx, multiTxEncoded, pver, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block output index.
|
||||
{multiTx, multiTxEncoded, pver, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script length.
|
||||
{multiTx, multiTxEncoded, pver, 41, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script.
|
||||
{multiTx, multiTxEncoded, pver, 42, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input sequence.
|
||||
{multiTx, multiTxEncoded, pver, 49, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction outputs.
|
||||
{multiTx, multiTxEncoded, pver, 57, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output value.
|
||||
{multiTx, multiTxEncoded, pver, 58, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey length.
|
||||
{multiTx, multiTxEncoded, pver, 66, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey.
|
||||
{multiTx, multiTxEncoded, pver, 67, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output lock time.
|
||||
{multiTx, multiTxEncoded, pver, 210, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Encode to appmessage format.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.KaspaEncode(w, test.pver)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("KaspaEncode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
var msg MsgTx
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = msg.KaspaDecode(r, test.pver)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxSerialize tests MsgTx serialize and deserialize.
|
||||
func TestTxSerialize(t *testing.T) {
|
||||
noTx := NewNativeMsgTx(1, nil, nil)
|
||||
noTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
}
|
||||
|
||||
registryTx := NewRegistryMsgTx(1, nil, nil, 16)
|
||||
registryTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Gas
|
||||
0x77, 0x56, 0x36, 0xb4, 0x89, 0x32, 0xe9, 0xa8,
|
||||
0xbb, 0x67, 0xe6, 0x54, 0x84, 0x36, 0x93, 0x8d,
|
||||
0x9f, 0xc5, 0x62, 0x49, 0x79, 0x5c, 0x0d, 0x0a,
|
||||
0x86, 0xaf, 0x7c, 0x5d, 0x54, 0x45, 0x4c, 0x4b, // Payload hash
|
||||
0x08, // Payload length varint
|
||||
0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Payload / Gas limit
|
||||
}
|
||||
|
||||
subnetworkTx := NewSubnetworkMsgTx(1, nil, nil, &subnetworkid.SubnetworkID{0xff}, 5, []byte{0, 1, 2})
|
||||
|
||||
subnetworkTxEncoded := []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0x00, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Gas
|
||||
0x35, 0xf9, 0xf2, 0x93, 0x0e, 0xa3, 0x44, 0x61,
|
||||
0x88, 0x22, 0x79, 0x5e, 0xee, 0xc5, 0x68, 0xae,
|
||||
0x67, 0xab, 0x29, 0x87, 0xd8, 0xb1, 0x9e, 0x45,
|
||||
0x91, 0xe1, 0x05, 0x27, 0xba, 0xa1, 0xdf, 0x3d, // Payload hash
|
||||
0x03, // Payload length varint
|
||||
0x00, 0x01, 0x02, // Payload
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
in *MsgTx // Message to encode
|
||||
out *MsgTx // Expected decoded message
|
||||
buf []byte // Serialized data
|
||||
scriptPubKeyLocs []int // Expected output script locations
|
||||
}{
|
||||
// No transactions.
|
||||
{
|
||||
"noTx",
|
||||
noTx,
|
||||
noTx,
|
||||
noTxEncoded,
|
||||
nil,
|
||||
},
|
||||
|
||||
// Registry Transaction.
|
||||
{
|
||||
"registryTx",
|
||||
registryTx,
|
||||
registryTx,
|
||||
registryTxEncoded,
|
||||
nil,
|
||||
},
|
||||
|
||||
// Sub Network Transaction.
|
||||
{
|
||||
"subnetworkTx",
|
||||
subnetworkTx,
|
||||
subnetworkTx,
|
||||
subnetworkTxEncoded,
|
||||
nil,
|
||||
},
|
||||
|
||||
// Multiple transactions.
|
||||
{
|
||||
"multiTx",
|
||||
multiTx,
|
||||
multiTx,
|
||||
multiTxEncoded,
|
||||
multiTxScriptPubKeyLocs,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the transaction.
|
||||
var buf bytes.Buffer
|
||||
err := test.in.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Errorf("Serialize %s: error %v", test.name, err)
|
||||
continue
|
||||
}
|
||||
if !bytes.Equal(buf.Bytes(), test.buf) {
|
||||
t.Errorf("Serialize %s:\n got: %s want: %s", test.name,
|
||||
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the transaction.
|
||||
var tx MsgTx
|
||||
rbuf := bytes.NewReader(test.buf)
|
||||
err = tx.Deserialize(rbuf)
|
||||
if err != nil {
|
||||
t.Errorf("Deserialize #%d error %v", i, err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(&tx, test.out) {
|
||||
t.Errorf("Deserialize #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(&tx), spew.Sdump(test.out))
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the public key script locations are accurate.
|
||||
scriptPubKeyLocs := test.in.ScriptPubKeyLocs()
|
||||
if !reflect.DeepEqual(scriptPubKeyLocs, test.scriptPubKeyLocs) {
|
||||
t.Errorf("ScriptPubKeyLocs #%d\n got: %s want: %s", i,
|
||||
spew.Sdump(scriptPubKeyLocs),
|
||||
spew.Sdump(test.scriptPubKeyLocs))
|
||||
continue
|
||||
}
|
||||
for j, loc := range scriptPubKeyLocs {
|
||||
wantScriptPubKey := test.in.TxOut[j].ScriptPubKey
|
||||
gotScriptPubKey := test.buf[loc : loc+len(wantScriptPubKey)]
|
||||
if !bytes.Equal(gotScriptPubKey, wantScriptPubKey) {
|
||||
t.Errorf("ScriptPubKeyLocs #%d:%d\n unexpected "+
|
||||
"script got: %s want: %s", i, j,
|
||||
spew.Sdump(gotScriptPubKey),
|
||||
spew.Sdump(wantScriptPubKey))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxSerializeErrors performs negative tests against appmessage encode and decode
|
||||
// of MsgTx to confirm error paths work correctly.
|
||||
func TestTxSerializeErrors(t *testing.T) {
|
||||
tests := []struct {
|
||||
in *MsgTx // Value to encode
|
||||
buf []byte // Serialized data
|
||||
max int // Max size of fixed buffer to induce errors
|
||||
writeErr error // Expected write error
|
||||
readErr error // Expected read error
|
||||
}{
|
||||
// Force error in version.
|
||||
{multiTx, multiTxEncoded, 0, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction inputs.
|
||||
{multiTx, multiTxEncoded, 4, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block hash.
|
||||
{multiTx, multiTxEncoded, 5, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input previous block output index.
|
||||
{multiTx, multiTxEncoded, 37, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script length.
|
||||
{multiTx, multiTxEncoded, 41, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input signature script.
|
||||
{multiTx, multiTxEncoded, 42, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction input sequence.
|
||||
{multiTx, multiTxEncoded, 49, io.ErrShortWrite, io.EOF},
|
||||
// Force error in number of transaction outputs.
|
||||
{multiTx, multiTxEncoded, 57, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output value.
|
||||
{multiTx, multiTxEncoded, 58, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey length.
|
||||
{multiTx, multiTxEncoded, 66, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output scriptPubKey.
|
||||
{multiTx, multiTxEncoded, 67, io.ErrShortWrite, io.EOF},
|
||||
// Force error in transaction output lock time.
|
||||
{multiTx, multiTxEncoded, 210, io.ErrShortWrite, io.EOF},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Serialize the transaction.
|
||||
w := newFixedWriter(test.max)
|
||||
err := test.in.Serialize(w)
|
||||
if !errors.Is(err, test.writeErr) {
|
||||
t.Errorf("Serialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.writeErr)
|
||||
continue
|
||||
}
|
||||
|
||||
// Deserialize the transaction.
|
||||
var tx MsgTx
|
||||
r := newFixedReader(test.max, test.buf)
|
||||
err = tx.Deserialize(r)
|
||||
if !errors.Is(err, test.readErr) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, test.readErr)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
registryTx := NewSubnetworkMsgTx(1, nil, nil, subnetworkid.SubnetworkIDRegistry, 1, nil)
|
||||
|
||||
w := bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize()))
|
||||
err := registryTx.Serialize(w)
|
||||
str := "Transactions from built-in should have 0 gas"
|
||||
expectedErr := messageError("MsgTx.KaspaEncode", str)
|
||||
if err == nil || err.Error() != expectedErr.Error() {
|
||||
t.Errorf("TestTxSerializeErrors: expected error %v but got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
nativeTx := NewSubnetworkMsgTx(1, nil, nil, subnetworkid.SubnetworkIDNative, 1, nil)
|
||||
w = bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize()))
|
||||
err = nativeTx.Serialize(w)
|
||||
|
||||
str = "Transactions from native subnetwork should have 0 gas"
|
||||
expectedErr = messageError("MsgTx.KaspaEncode", str)
|
||||
if err == nil || err.Error() != expectedErr.Error() {
|
||||
t.Errorf("TestTxSerializeErrors: expected error %v but got %v", expectedErr, err)
|
||||
}
|
||||
|
||||
nativeTx.Gas = 0
|
||||
nativeTx.Payload = []byte{1, 2, 3}
|
||||
nativeTx.PayloadHash = daghash.DoubleHashP(nativeTx.Payload)
|
||||
w = bytes.NewBuffer(make([]byte, 0, registryTx.SerializeSize()))
|
||||
err = nativeTx.Serialize(w)
|
||||
|
||||
str = "Transactions from native subnetwork should have <nil> payload"
|
||||
expectedErr = messageError("MsgTx.KaspaEncode", str)
|
||||
if err == nil || err.Error() != expectedErr.Error() {
|
||||
t.Errorf("TestTxSerializeErrors: expected error %v but got %v", expectedErr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxOverflowErrors performs tests to ensure deserializing transactions
|
||||
// which are intentionally crafted to use large values for the variable number
|
||||
// of inputs and outputs are handled properly. This could otherwise potentially
|
||||
// be used as an attack vector.
|
||||
func TestTxOverflowErrors(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
txVer := uint32(1)
|
||||
|
||||
tests := []struct {
|
||||
buf []byte // Encoded value
|
||||
pver uint32 // Protocol version for appmessage encoding
|
||||
version uint32 // Transaction version
|
||||
err error // Expected error
|
||||
}{
|
||||
// Transaction that claims to have ~uint64(0) inputs.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for number of input transactions
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
|
||||
// Transaction that claims to have ~uint64(0) outputs.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0x00, // Varint for number of input transactions
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for number of output transactions
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
|
||||
// Transaction that has an input with a signature script that
|
||||
// claims to have ~uint64(0) length.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
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, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for length of signature script
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
|
||||
// Transaction that has an output with a public key script
|
||||
// that claims to have ~uint64(0) length.
|
||||
{
|
||||
[]byte{
|
||||
0x00, 0x00, 0x00, 0x01, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
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, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0x00, // Varint for length of signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
0x01, // Varint for number of output transactions
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
|
||||
0xff, // Varint for length of public key script
|
||||
}, pver, txVer, &MessageError{},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Decode from appmessage format.
|
||||
var msg MsgTx
|
||||
r := bytes.NewReader(test.buf)
|
||||
err := msg.KaspaDecode(r, test.pver)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("KaspaDecode #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
|
||||
// Decode from appmessage format.
|
||||
r = bytes.NewReader(test.buf)
|
||||
err = msg.Deserialize(r)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Deserialize #%d wrong error got: %v, want: %v",
|
||||
i, err, reflect.TypeOf(test.err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestTxSerializeSize performs tests to ensure the serialize size for
|
||||
// various transactions is accurate.
|
||||
func TestTxSerializeSize(t *testing.T) {
|
||||
// Empty tx message.
|
||||
noTx := NewNativeMsgTx(1, nil, nil)
|
||||
|
||||
tests := []struct {
|
||||
in *MsgTx // Tx to encode
|
||||
size int // Expected serialized size
|
||||
}{
|
||||
// No inputs or outpus.
|
||||
{noTx, 34},
|
||||
|
||||
// Transcaction with an input and an output.
|
||||
{multiTx, 238},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
serializedSize := test.in.SerializeSize()
|
||||
if serializedSize != test.size {
|
||||
t.Errorf("MsgTx.SerializeSize: #%d got: %d, want: %d", i,
|
||||
serializedSize, test.size)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsSubnetworkCompatible(t *testing.T) {
|
||||
testTx := NewSubnetworkMsgTx(1, nil, nil, &subnetworkid.SubnetworkID{123}, 0, []byte{})
|
||||
tests := []struct {
|
||||
name string
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "Native subnetwork",
|
||||
subnetworkID: subnetworkid.SubnetworkIDNative,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "same subnetwork as test tx",
|
||||
subnetworkID: &subnetworkid.SubnetworkID{123},
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "other subnetwork",
|
||||
subnetworkID: &subnetworkid.SubnetworkID{234},
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
result := testTx.IsSubnetworkCompatible(test.subnetworkID)
|
||||
if result != test.expectedResult {
|
||||
t.Errorf("IsSubnetworkCompatible got unexpected result in test '%s': "+
|
||||
"expected: %t, want: %t", test.name, test.expectedResult, result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestScriptFreeList(t *testing.T) {
|
||||
var list scriptFreeList = make(chan []byte, freeListMaxItems)
|
||||
|
||||
expectedCapacity := 512
|
||||
expectedLengthFirst := 12
|
||||
expectedLengthSecond := 13
|
||||
|
||||
first := list.Borrow(uint64(expectedLengthFirst))
|
||||
if cap(first) != expectedCapacity {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected capacity for first %d, but got %d",
|
||||
expectedCapacity, cap(first))
|
||||
}
|
||||
if len(first) != expectedLengthFirst {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected length for first %d, but got %d",
|
||||
expectedLengthFirst, len(first))
|
||||
}
|
||||
list.Return(first)
|
||||
|
||||
// Borrow again, and check that the underlying array is re-used for second
|
||||
second := list.Borrow(uint64(expectedLengthSecond))
|
||||
if cap(second) != expectedCapacity {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected capacity for second %d, but got %d",
|
||||
expectedCapacity, cap(second))
|
||||
}
|
||||
if len(second) != expectedLengthSecond {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected length for second %d, but got %d",
|
||||
expectedLengthSecond, len(second))
|
||||
}
|
||||
|
||||
firstArrayAddress := underlyingArrayAddress(first)
|
||||
secondArrayAddress := underlyingArrayAddress(second)
|
||||
|
||||
if firstArrayAddress != secondArrayAddress {
|
||||
t.Errorf("First underlying array is at address %d and second at address %d, "+
|
||||
"which means memory was not re-used", firstArrayAddress, secondArrayAddress)
|
||||
}
|
||||
|
||||
list.Return(second)
|
||||
|
||||
// test for buffers bigger than freeListMaxScriptSize
|
||||
expectedCapacityBig := freeListMaxScriptSize + 1
|
||||
expectedLengthBig := expectedCapacityBig
|
||||
big := list.Borrow(uint64(expectedCapacityBig))
|
||||
|
||||
if cap(big) != expectedCapacityBig {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected capacity for second %d, but got %d",
|
||||
expectedCapacityBig, cap(big))
|
||||
}
|
||||
if len(big) != expectedLengthBig {
|
||||
t.Errorf("MsgTx.TestScriptFreeList: Expected length for second %d, but got %d",
|
||||
expectedLengthBig, len(big))
|
||||
}
|
||||
|
||||
list.Return(big)
|
||||
|
||||
// test there's no crash when channel is full because borrowed too much
|
||||
buffers := make([][]byte, freeListMaxItems+1)
|
||||
for i := 0; i < freeListMaxItems+1; i++ {
|
||||
buffers[i] = list.Borrow(1)
|
||||
}
|
||||
for i := 0; i < freeListMaxItems+1; i++ {
|
||||
list.Return(buffers[i])
|
||||
}
|
||||
}
|
||||
|
||||
func underlyingArrayAddress(buf []byte) uint64 {
|
||||
return uint64((*reflect.SliceHeader)(unsafe.Pointer(&buf)).Data)
|
||||
}
|
||||
|
||||
// multiTx is a MsgTx with an input and output and used in various tests.
|
||||
var multiTxIns = []*TxIn{
|
||||
{
|
||||
PreviousOutpoint: Outpoint{
|
||||
TxID: daghash.TxID{},
|
||||
Index: 0xffffffff,
|
||||
},
|
||||
SignatureScript: []byte{
|
||||
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62,
|
||||
},
|
||||
Sequence: math.MaxUint64,
|
||||
},
|
||||
}
|
||||
var multiTxOuts = []*TxOut{
|
||||
{
|
||||
Value: 0x12a05f200,
|
||||
ScriptPubKey: []byte{
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
},
|
||||
{
|
||||
Value: 0x5f5e100,
|
||||
ScriptPubKey: []byte{
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
},
|
||||
},
|
||||
}
|
||||
var multiTx = NewNativeMsgTx(1, multiTxIns, multiTxOuts)
|
||||
|
||||
// multiTxEncoded is the appmessage encoded bytes for multiTx using protocol version
|
||||
// 60002 and is used in the various tests.
|
||||
var multiTxEncoded = []byte{
|
||||
0x01, 0x00, 0x00, 0x00, // Version
|
||||
0x01, // Varint for number of input transactions
|
||||
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, // Previous output hash
|
||||
0xff, 0xff, 0xff, 0xff, // Prevous output index
|
||||
0x07, // Varint for length of signature script
|
||||
0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62, // Signature script
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence
|
||||
0x02, // Varint for number of output transactions
|
||||
0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
0x00, 0xe1, 0xf5, 0x05, 0x00, 0x00, 0x00, 0x00, // Transaction amount
|
||||
0x43, // Varint for length of scriptPubKey
|
||||
0x41, // OP_DATA_65
|
||||
0x04, 0xd6, 0x4b, 0xdf, 0xd0, 0x9e, 0xb1, 0xc5,
|
||||
0xfe, 0x29, 0x5a, 0xbd, 0xeb, 0x1d, 0xca, 0x42,
|
||||
0x81, 0xbe, 0x98, 0x8e, 0x2d, 0xa0, 0xb6, 0xc1,
|
||||
0xc6, 0xa5, 0x9d, 0xc2, 0x26, 0xc2, 0x86, 0x24,
|
||||
0xe1, 0x81, 0x75, 0xe8, 0x51, 0xc9, 0x6b, 0x97,
|
||||
0x3d, 0x81, 0xb0, 0x1c, 0xc3, 0x1f, 0x04, 0x78,
|
||||
0x34, 0xbc, 0x06, 0xd6, 0xd6, 0xed, 0xf6, 0x20,
|
||||
0xd1, 0x84, 0x24, 0x1a, 0x6a, 0xed, 0x8b, 0x63,
|
||||
0xa6, // 65-byte signature
|
||||
0xac, // OP_CHECKSIG
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Lock time
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, // Sub Network ID
|
||||
}
|
||||
|
||||
// multiTxScriptPubKeyLocs is the location information for the public key scripts
|
||||
// located in multiTx.
|
||||
var multiTxScriptPubKeyLocs = []int{67, 143}
|
||||
|
||||
@@ -6,14 +6,13 @@ package appmessage
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// MaxUserAgentLen is the maximum allowed length for the user agent field in a
|
||||
@@ -54,14 +53,11 @@ type MsgVersion struct {
|
||||
// on the appmessage. This has a max length of MaxUserAgentLen.
|
||||
UserAgent string
|
||||
|
||||
// The selected tip hash of the generator of the version message.
|
||||
SelectedTipHash *daghash.Hash
|
||||
|
||||
// Don't announce transactions to peer.
|
||||
DisableRelayTx bool
|
||||
|
||||
// The subnetwork of the generator of the version message. Should be nil in full nodes
|
||||
SubnetworkID *subnetworkid.SubnetworkID
|
||||
SubnetworkID *externalapi.DomainSubnetworkID
|
||||
}
|
||||
|
||||
// HasService returns whether the specified service is supported by the peer
|
||||
@@ -86,7 +82,7 @@ func (msg *MsgVersion) Command() MessageCommand {
|
||||
// Message interface using the passed parameters and defaults for the remaining
|
||||
// fields.
|
||||
func NewMsgVersion(addr *NetAddress, id *id.ID, network string,
|
||||
selectedTipHash *daghash.Hash, subnetworkID *subnetworkid.SubnetworkID) *MsgVersion {
|
||||
subnetworkID *externalapi.DomainSubnetworkID) *MsgVersion {
|
||||
|
||||
// Limit the timestamp to one millisecond precision since the protocol
|
||||
// doesn't support better.
|
||||
@@ -98,7 +94,6 @@ func NewMsgVersion(addr *NetAddress, id *id.ID, network string,
|
||||
Address: addr,
|
||||
ID: id,
|
||||
UserAgent: DefaultUserAgent,
|
||||
SelectedTipHash: selectedTipHash,
|
||||
DisableRelayTx: false,
|
||||
SubnetworkID: subnetworkID,
|
||||
}
|
||||
|
||||
@@ -5,12 +5,12 @@
|
||||
package appmessage
|
||||
|
||||
import (
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"net"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
)
|
||||
|
||||
// TestVersion tests the MsgVersion API.
|
||||
@@ -18,7 +18,6 @@ func TestVersion(t *testing.T) {
|
||||
pver := ProtocolVersion
|
||||
|
||||
// Create version message data.
|
||||
selectedTipHash := &daghash.Hash{12, 34}
|
||||
tcpAddrMe := &net.TCPAddr{IP: net.ParseIP("127.0.0.1"), Port: 16111}
|
||||
me := NewNetAddress(tcpAddrMe, SFNodeNetwork)
|
||||
generatedID, err := id.GenerateID()
|
||||
@@ -27,7 +26,7 @@ func TestVersion(t *testing.T) {
|
||||
}
|
||||
|
||||
// Ensure we get the correct data back out.
|
||||
msg := NewMsgVersion(me, generatedID, "mainnet", selectedTipHash, nil)
|
||||
msg := NewMsgVersion(me, generatedID, "mainnet", nil)
|
||||
if msg.ProtocolVersion != pver {
|
||||
t.Errorf("NewMsgVersion: wrong protocol version - got %v, want %v",
|
||||
msg.ProtocolVersion, pver)
|
||||
@@ -44,10 +43,6 @@ func TestVersion(t *testing.T) {
|
||||
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
|
||||
msg.UserAgent, DefaultUserAgent)
|
||||
}
|
||||
if !msg.SelectedTipHash.IsEqual(selectedTipHash) {
|
||||
t.Errorf("NewMsgVersion: wrong selected tip hash - got %s, want %s",
|
||||
msg.SelectedTipHash, selectedTipHash)
|
||||
}
|
||||
if msg.DisableRelayTx {
|
||||
t.Errorf("NewMsgVersion: disable relay tx is not false by "+
|
||||
"default - got %v, want %v", msg.DisableRelayTx, false)
|
||||
|
||||
@@ -6,8 +6,6 @@ type GetBlockRequestMessage struct {
|
||||
baseMessage
|
||||
Hash string
|
||||
SubnetworkID string
|
||||
IncludeBlockHex bool
|
||||
IncludeBlockVerboseData bool
|
||||
IncludeTransactionVerboseData bool
|
||||
}
|
||||
|
||||
@@ -17,13 +15,10 @@ func (msg *GetBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockRequestMessage returns a instance of the message
|
||||
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeBlockHex bool,
|
||||
includeBlockVerboseData bool, includeTransactionVerboseData bool) *GetBlockRequestMessage {
|
||||
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeTransactionVerboseData bool) *GetBlockRequestMessage {
|
||||
return &GetBlockRequestMessage{
|
||||
Hash: hash,
|
||||
SubnetworkID: subnetworkID,
|
||||
IncludeBlockHex: includeBlockHex,
|
||||
IncludeBlockVerboseData: includeBlockVerboseData,
|
||||
IncludeTransactionVerboseData: includeTransactionVerboseData,
|
||||
}
|
||||
}
|
||||
@@ -32,7 +27,6 @@ func NewGetBlockRequestMessage(hash string, subnetworkID string, includeBlockHex
|
||||
// its respective RPC message
|
||||
type GetBlockResponseMessage struct {
|
||||
baseMessage
|
||||
BlockHex string
|
||||
BlockVerboseData *BlockVerboseData
|
||||
|
||||
Error *RPCError
|
||||
@@ -51,10 +45,6 @@ func NewGetBlockResponseMessage() *GetBlockResponseMessage {
|
||||
// BlockVerboseData holds verbose data about a block
|
||||
type BlockVerboseData struct {
|
||||
Hash string
|
||||
Confirmations uint64
|
||||
Size int32
|
||||
BlueScore uint64
|
||||
IsChainBlock bool
|
||||
Version int32
|
||||
VersionHex string
|
||||
HashMerkleRoot string
|
||||
@@ -68,16 +58,14 @@ type BlockVerboseData struct {
|
||||
Difficulty float64
|
||||
ParentHashes []string
|
||||
SelectedParentHash string
|
||||
ChildHashes []string
|
||||
AcceptedBlockHashes []string
|
||||
BlueScore uint64
|
||||
}
|
||||
|
||||
// TransactionVerboseData holds verbose data about a transaction
|
||||
type TransactionVerboseData struct {
|
||||
Hex string
|
||||
TxID string
|
||||
Hash string
|
||||
Size int32
|
||||
Size uint64
|
||||
Version int32
|
||||
LockTime uint64
|
||||
SubnetworkID string
|
||||
@@ -87,8 +75,6 @@ type TransactionVerboseData struct {
|
||||
TransactionVerboseInputs []*TransactionVerboseInput
|
||||
TransactionVerboseOutputs []*TransactionVerboseOutput
|
||||
BlockHash string
|
||||
AcceptedBy string
|
||||
IsInMempool bool
|
||||
Time uint64
|
||||
BlockTime uint64
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package appmessage
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
// GetBlockCountRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type GetBlockCountRequestMessage struct {
|
||||
@@ -20,7 +22,8 @@ func NewGetBlockCountRequestMessage() *GetBlockCountRequestMessage {
|
||||
// its respective RPC message
|
||||
type GetBlockCountResponseMessage struct {
|
||||
baseMessage
|
||||
BlockCount uint64
|
||||
BlockCount uint64
|
||||
HeaderCount uint64
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
@@ -31,8 +34,9 @@ func (msg *GetBlockCountResponseMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockCountResponseMessage returns a instance of the message
|
||||
func NewGetBlockCountResponseMessage(blockCount uint64) *GetBlockCountResponseMessage {
|
||||
func NewGetBlockCountResponseMessage(syncInfo *externalapi.SyncInfo) *GetBlockCountResponseMessage {
|
||||
return &GetBlockCountResponseMessage{
|
||||
BlockCount: blockCount,
|
||||
BlockCount: syncInfo.BlockCount,
|
||||
HeaderCount: syncInfo.HeaderCount,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,11 +20,13 @@ func NewGetBlockDAGInfoRequestMessage() *GetBlockDAGInfoRequestMessage {
|
||||
// its respective RPC message
|
||||
type GetBlockDAGInfoResponseMessage struct {
|
||||
baseMessage
|
||||
NetworkName string
|
||||
BlockCount uint64
|
||||
TipHashes []string
|
||||
Difficulty float64
|
||||
PastMedianTime int64
|
||||
NetworkName string
|
||||
BlockCount uint64
|
||||
HeaderCount uint64
|
||||
TipHashes []string
|
||||
VirtualParentHashes []string
|
||||
Difficulty float64
|
||||
PastMedianTime int64
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ package appmessage
|
||||
type GetBlockTemplateRequestMessage struct {
|
||||
baseMessage
|
||||
PayAddress string
|
||||
LongPollID string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -14,10 +13,9 @@ func (msg *GetBlockTemplateRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockTemplateRequestMessage returns a instance of the message
|
||||
func NewGetBlockTemplateRequestMessage(payAddress string, longPollID string) *GetBlockTemplateRequestMessage {
|
||||
func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateRequestMessage {
|
||||
return &GetBlockTemplateRequestMessage{
|
||||
PayAddress: payAddress,
|
||||
LongPollID: longPollID,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,23 +23,7 @@ func NewGetBlockTemplateRequestMessage(payAddress string, longPollID string) *Ge
|
||||
// its respective RPC message
|
||||
type GetBlockTemplateResponseMessage struct {
|
||||
baseMessage
|
||||
Bits string
|
||||
CurrentTime int64
|
||||
ParentHashes []string
|
||||
MassLimit int
|
||||
Transactions []GetBlockTemplateTransactionMessage
|
||||
HashMerkleRoot string
|
||||
AcceptedIDMerkleRoot string
|
||||
UTXOCommitment string
|
||||
Version int32
|
||||
LongPollID string
|
||||
TargetDifficulty string
|
||||
MinTime int64
|
||||
MaxTime int64
|
||||
MutableFields []string
|
||||
NonceRange string
|
||||
IsSynced bool
|
||||
IsConnected bool
|
||||
MsgBlock *MsgBlock
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
@@ -52,27 +34,6 @@ func (msg *GetBlockTemplateResponseMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetBlockTemplateResponseMessage returns a instance of the message
|
||||
func NewGetBlockTemplateResponseMessage() *GetBlockTemplateResponseMessage {
|
||||
return &GetBlockTemplateResponseMessage{}
|
||||
}
|
||||
|
||||
// GetBlockTemplateTransactionMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type GetBlockTemplateTransactionMessage struct {
|
||||
baseMessage
|
||||
Data string
|
||||
ID string
|
||||
Depends []int64
|
||||
Mass uint64
|
||||
Fee uint64
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *GetBlockTemplateTransactionMessage) Command() MessageCommand {
|
||||
return CmdGetBlockTemplateTransactionMessage
|
||||
}
|
||||
|
||||
// NewGetBlockTemplateTransactionMessage returns a instance of the message
|
||||
func NewGetBlockTemplateTransactionMessage() *GetBlockTemplateTransactionMessage {
|
||||
return &GetBlockTemplateTransactionMessage{}
|
||||
func NewGetBlockTemplateResponseMessage(msgBlock *MsgBlock) *GetBlockTemplateResponseMessage {
|
||||
return &GetBlockTemplateResponseMessage{MsgBlock: msgBlock}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,6 @@ type GetConnectedPeerInfoMessage struct {
|
||||
ID string
|
||||
Address string
|
||||
LastPingDuration int64
|
||||
SelectedTipHash string
|
||||
IsSyncNode bool
|
||||
IsOutbound bool
|
||||
TimeOffset int64
|
||||
UserAgent string
|
||||
|
||||
45
app/appmessage/rpc_get_headers.go
Normal file
45
app/appmessage/rpc_get_headers.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package appmessage
|
||||
|
||||
// GetHeadersRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type GetHeadersRequestMessage struct {
|
||||
baseMessage
|
||||
StartHash string
|
||||
Limit uint64
|
||||
IsAscending bool
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *GetHeadersRequestMessage) Command() MessageCommand {
|
||||
return CmdGetHeadersRequestMessage
|
||||
}
|
||||
|
||||
// NewGetHeadersRequestMessage returns a instance of the message
|
||||
func NewGetHeadersRequestMessage(startHash string, limit uint64, isAscending bool) *GetHeadersRequestMessage {
|
||||
return &GetHeadersRequestMessage{
|
||||
StartHash: startHash,
|
||||
Limit: limit,
|
||||
IsAscending: isAscending,
|
||||
}
|
||||
}
|
||||
|
||||
// GetHeadersResponseMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type GetHeadersResponseMessage struct {
|
||||
baseMessage
|
||||
Headers []string
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *GetHeadersResponseMessage) Command() MessageCommand {
|
||||
return CmdGetHeadersResponseMessage
|
||||
}
|
||||
|
||||
// NewGetHeadersResponseMessage returns a instance of the message
|
||||
func NewGetHeadersResponseMessage(headers []string) *GetHeadersResponseMessage {
|
||||
return &GetHeadersResponseMessage{
|
||||
Headers: headers,
|
||||
}
|
||||
}
|
||||
38
app/appmessage/rpc_get_mempool_entries.go
Normal file
38
app/appmessage/rpc_get_mempool_entries.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package appmessage
|
||||
|
||||
// GetMempoolEntriesRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type GetMempoolEntriesRequestMessage struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *GetMempoolEntriesRequestMessage) Command() MessageCommand {
|
||||
return CmdGetMempoolEntriesRequestMessage
|
||||
}
|
||||
|
||||
// NewGetMempoolEntriesRequestMessage returns a instance of the message
|
||||
func NewGetMempoolEntriesRequestMessage() *GetMempoolEntriesRequestMessage {
|
||||
return &GetMempoolEntriesRequestMessage{}
|
||||
}
|
||||
|
||||
// GetMempoolEntriesResponseMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type GetMempoolEntriesResponseMessage struct {
|
||||
baseMessage
|
||||
Entries []*MempoolEntry
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *GetMempoolEntriesResponseMessage) Command() MessageCommand {
|
||||
return CmdGetMempoolEntriesResponseMessage
|
||||
}
|
||||
|
||||
// NewGetMempoolEntriesResponseMessage returns a instance of the message
|
||||
func NewGetMempoolEntriesResponseMessage(entries []*MempoolEntry) *GetMempoolEntriesResponseMessage {
|
||||
return &GetMempoolEntriesResponseMessage{
|
||||
Entries: entries,
|
||||
}
|
||||
}
|
||||
@@ -21,15 +21,28 @@ func NewGetMempoolEntryRequestMessage(txID string) *GetMempoolEntryRequestMessag
|
||||
// its respective RPC message
|
||||
type GetMempoolEntryResponseMessage struct {
|
||||
baseMessage
|
||||
Entry *MempoolEntry
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// MempoolEntry represents a transaction in the mempool.
|
||||
type MempoolEntry struct {
|
||||
Fee uint64
|
||||
TransactionVerboseData *TransactionVerboseData
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *GetMempoolEntryResponseMessage) Command() MessageCommand {
|
||||
return CmdGetMempoolEntryResponseMessage
|
||||
}
|
||||
|
||||
// NewGetMempoolEntryResponseMessage returns a instance of the message
|
||||
func NewGetMempoolEntryResponseMessage() *GetMempoolEntryResponseMessage {
|
||||
return &GetMempoolEntryResponseMessage{}
|
||||
func NewGetMempoolEntryResponseMessage(fee uint64, transactionVerboseData *TransactionVerboseData) *GetMempoolEntryResponseMessage {
|
||||
return &GetMempoolEntryResponseMessage{
|
||||
Entry: &MempoolEntry{
|
||||
Fee: fee,
|
||||
TransactionVerboseData: transactionVerboseData,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ func NewGetPeerAddressesRequestMessage() *GetPeerAddressesRequestMessage {
|
||||
// its respective RPC message
|
||||
type GetPeerAddressesResponseMessage struct {
|
||||
baseMessage
|
||||
Addresses []*GetPeerAddressesKnownAddressMessage
|
||||
Addresses []*GetPeerAddressesKnownAddressMessage
|
||||
BannedAddresses []*GetPeerAddressesKnownAddressMessage
|
||||
|
||||
Error *RPCError
|
||||
}
|
||||
@@ -31,9 +32,10 @@ func (msg *GetPeerAddressesResponseMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewGetPeerAddressesResponseMessage returns a instance of the message
|
||||
func NewGetPeerAddressesResponseMessage(addresses []*GetPeerAddressesKnownAddressMessage) *GetPeerAddressesResponseMessage {
|
||||
func NewGetPeerAddressesResponseMessage(addresses []*GetPeerAddressesKnownAddressMessage, bannedAddresses []*GetPeerAddressesKnownAddressMessage) *GetPeerAddressesResponseMessage {
|
||||
return &GetPeerAddressesResponseMessage{
|
||||
Addresses: addresses,
|
||||
Addresses: addresses,
|
||||
BannedAddresses: bannedAddresses,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
72
app/appmessage/rpc_notify_finality_conflicts.go
Normal file
72
app/appmessage/rpc_notify_finality_conflicts.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package appmessage
|
||||
|
||||
// NotifyFinalityConflictsRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type NotifyFinalityConflictsRequestMessage struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *NotifyFinalityConflictsRequestMessage) Command() MessageCommand {
|
||||
return CmdNotifyFinalityConflictsRequestMessage
|
||||
}
|
||||
|
||||
// NewNotifyFinalityConflictsRequestMessage returns a instance of the message
|
||||
func NewNotifyFinalityConflictsRequestMessage() *NotifyFinalityConflictsRequestMessage {
|
||||
return &NotifyFinalityConflictsRequestMessage{}
|
||||
}
|
||||
|
||||
// NotifyFinalityConflictsResponseMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type NotifyFinalityConflictsResponseMessage struct {
|
||||
baseMessage
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *NotifyFinalityConflictsResponseMessage) Command() MessageCommand {
|
||||
return CmdNotifyFinalityConflictsResponseMessage
|
||||
}
|
||||
|
||||
// NewNotifyFinalityConflictsResponseMessage returns a instance of the message
|
||||
func NewNotifyFinalityConflictsResponseMessage() *NotifyFinalityConflictsResponseMessage {
|
||||
return &NotifyFinalityConflictsResponseMessage{}
|
||||
}
|
||||
|
||||
// FinalityConflictNotificationMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type FinalityConflictNotificationMessage struct {
|
||||
baseMessage
|
||||
ViolatingBlockHash string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *FinalityConflictNotificationMessage) Command() MessageCommand {
|
||||
return CmdFinalityConflictNotificationMessage
|
||||
}
|
||||
|
||||
// NewFinalityConflictNotificationMessage returns a instance of the message
|
||||
func NewFinalityConflictNotificationMessage(violatingBlockHash string) *FinalityConflictNotificationMessage {
|
||||
return &FinalityConflictNotificationMessage{
|
||||
ViolatingBlockHash: violatingBlockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// FinalityConflictResolvedNotificationMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type FinalityConflictResolvedNotificationMessage struct {
|
||||
baseMessage
|
||||
FinalityBlockHash string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *FinalityConflictResolvedNotificationMessage) Command() MessageCommand {
|
||||
return CmdFinalityConflictResolvedNotificationMessage
|
||||
}
|
||||
|
||||
// NewFinalityConflictResolvedNotificationMessage returns a instance of the message
|
||||
func NewFinalityConflictResolvedNotificationMessage(finalityBlockHash string) *FinalityConflictResolvedNotificationMessage {
|
||||
return &FinalityConflictResolvedNotificationMessage{
|
||||
FinalityBlockHash: finalityBlockHash,
|
||||
}
|
||||
}
|
||||
37
app/appmessage/rpc_resolve_finality_conflict.go
Normal file
37
app/appmessage/rpc_resolve_finality_conflict.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package appmessage
|
||||
|
||||
// ResolveFinalityConflictRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type ResolveFinalityConflictRequestMessage struct {
|
||||
baseMessage
|
||||
FinalityBlockHash string
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *ResolveFinalityConflictRequestMessage) Command() MessageCommand {
|
||||
return CmdResolveFinalityConflictRequestMessage
|
||||
}
|
||||
|
||||
// NewResolveFinalityConflictRequestMessage returns a instance of the message
|
||||
func NewResolveFinalityConflictRequestMessage(finalityBlockHash string) *ResolveFinalityConflictRequestMessage {
|
||||
return &ResolveFinalityConflictRequestMessage{
|
||||
FinalityBlockHash: finalityBlockHash,
|
||||
}
|
||||
}
|
||||
|
||||
// ResolveFinalityConflictResponseMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type ResolveFinalityConflictResponseMessage struct {
|
||||
baseMessage
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *ResolveFinalityConflictResponseMessage) Command() MessageCommand {
|
||||
return CmdResolveFinalityConflictResponseMessage
|
||||
}
|
||||
|
||||
// NewResolveFinalityConflictResponseMessage returns a instance of the message
|
||||
func NewResolveFinalityConflictResponseMessage() *ResolveFinalityConflictResponseMessage {
|
||||
return &ResolveFinalityConflictResponseMessage{}
|
||||
}
|
||||
34
app/appmessage/rpc_shut_down.go
Normal file
34
app/appmessage/rpc_shut_down.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package appmessage
|
||||
|
||||
// ShutDownRequestMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type ShutDownRequestMessage struct {
|
||||
baseMessage
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *ShutDownRequestMessage) Command() MessageCommand {
|
||||
return CmdShutDownRequestMessage
|
||||
}
|
||||
|
||||
// NewShutDownRequestMessage returns a instance of the message
|
||||
func NewShutDownRequestMessage() *ShutDownRequestMessage {
|
||||
return &ShutDownRequestMessage{}
|
||||
}
|
||||
|
||||
// ShutDownResponseMessage is an appmessage corresponding to
|
||||
// its respective RPC message
|
||||
type ShutDownResponseMessage struct {
|
||||
baseMessage
|
||||
Error *RPCError
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
func (msg *ShutDownResponseMessage) Command() MessageCommand {
|
||||
return CmdShutDownResponseMessage
|
||||
}
|
||||
|
||||
// NewShutDownResponseMessage returns a instance of the message
|
||||
func NewShutDownResponseMessage() *ShutDownResponseMessage {
|
||||
return &ShutDownResponseMessage{}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type SubmitBlockRequestMessage struct {
|
||||
baseMessage
|
||||
BlockHex string
|
||||
Block *MsgBlock
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -13,9 +13,9 @@ func (msg *SubmitBlockRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewSubmitBlockRequestMessage returns a instance of the message
|
||||
func NewSubmitBlockRequestMessage(blockHex string) *SubmitBlockRequestMessage {
|
||||
func NewSubmitBlockRequestMessage(block *MsgBlock) *SubmitBlockRequestMessage {
|
||||
return &SubmitBlockRequestMessage{
|
||||
BlockHex: blockHex,
|
||||
Block: block,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ package appmessage
|
||||
// its respective RPC message
|
||||
type SubmitTransactionRequestMessage struct {
|
||||
baseMessage
|
||||
TransactionHex string
|
||||
Transaction *MsgTx
|
||||
}
|
||||
|
||||
// Command returns the protocol command string for the message
|
||||
@@ -13,9 +13,9 @@ func (msg *SubmitTransactionRequestMessage) Command() MessageCommand {
|
||||
}
|
||||
|
||||
// NewSubmitTransactionRequestMessage returns a instance of the message
|
||||
func NewSubmitTransactionRequestMessage(transactionHex string) *SubmitTransactionRequestMessage {
|
||||
func NewSubmitTransactionRequestMessage(transaction *MsgTx) *SubmitTransactionRequestMessage {
|
||||
return &SubmitTransactionRequestMessage{
|
||||
TransactionHex: transactionHex,
|
||||
Transaction: transaction,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
159
app/component_manager.go
Normal file
159
app/component_manager.go
Normal file
@@ -0,0 +1,159 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
|
||||
infrastructuredatabase "github.com/kaspanet/kaspad/infrastructure/db/database"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"github.com/kaspanet/kaspad/app/rpc"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/dnsseed"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
// ComponentManager is a wrapper for all the kaspad services
|
||||
type ComponentManager struct {
|
||||
cfg *config.Config
|
||||
addressManager *addressmanager.AddressManager
|
||||
protocolManager *protocol.Manager
|
||||
rpcManager *rpc.Manager
|
||||
connectionManager *connmanager.ConnectionManager
|
||||
netAdapter *netadapter.NetAdapter
|
||||
|
||||
started, shutdown int32
|
||||
}
|
||||
|
||||
// Start launches all the kaspad services.
|
||||
func (a *ComponentManager) Start() {
|
||||
// Already started?
|
||||
if atomic.AddInt32(&a.started, 1) != 1 {
|
||||
return
|
||||
}
|
||||
|
||||
log.Trace("Starting kaspad")
|
||||
|
||||
err := a.netAdapter.Start()
|
||||
if err != nil {
|
||||
panics.Exit(log, fmt.Sprintf("Error starting the net adapter: %+v", err))
|
||||
}
|
||||
|
||||
a.maybeSeedFromDNS()
|
||||
|
||||
a.connectionManager.Start()
|
||||
}
|
||||
|
||||
// Stop gracefully shuts down all the kaspad services.
|
||||
func (a *ComponentManager) 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.netAdapter.Stop()
|
||||
if err != nil {
|
||||
log.Errorf("Error stopping the net adapter: %+v", err)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// NewComponentManager returns a new ComponentManager instance.
|
||||
// Use Start() to begin all services within this ComponentManager
|
||||
func NewComponentManager(cfg *config.Config, db infrastructuredatabase.Database, interrupt chan<- struct{}) (
|
||||
*ComponentManager, error) {
|
||||
|
||||
domain, err := domain.New(cfg.ActiveNetParams, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
netAdapter, err := netadapter.NewNetAdapter(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
addressManager, err := addressmanager.New(addressmanager.NewConfig(cfg))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
connectionManager, err := connmanager.New(cfg, netAdapter, addressManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
protocolManager, err := protocol.NewManager(cfg, domain, netAdapter, addressManager, connectionManager)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
rpcManager := setupRPC(cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, interrupt)
|
||||
|
||||
return &ComponentManager{
|
||||
cfg: cfg,
|
||||
protocolManager: protocolManager,
|
||||
rpcManager: rpcManager,
|
||||
connectionManager: connectionManager,
|
||||
netAdapter: netAdapter,
|
||||
addressManager: addressManager,
|
||||
}, nil
|
||||
|
||||
}
|
||||
|
||||
func setupRPC(
|
||||
cfg *config.Config,
|
||||
domain domain.Domain,
|
||||
netAdapter *netadapter.NetAdapter,
|
||||
protocolManager *protocol.Manager,
|
||||
connectionManager *connmanager.ConnectionManager,
|
||||
addressManager *addressmanager.AddressManager,
|
||||
shutDownChan chan<- struct{},
|
||||
) *rpc.Manager {
|
||||
|
||||
rpcManager := rpc.NewManager(
|
||||
cfg, domain, netAdapter, protocolManager, connectionManager, addressManager, shutDownChan)
|
||||
protocolManager.SetOnBlockAddedToDAGHandler(rpcManager.NotifyBlockAddedToDAG)
|
||||
|
||||
return rpcManager
|
||||
}
|
||||
|
||||
func (a *ComponentManager) maybeSeedFromDNS() {
|
||||
if !a.cfg.DisableDNSSeed {
|
||||
dnsseed.SeedFromDNS(a.cfg.NetParams(), a.cfg.DNSSeed, appmessage.SFNodeNetwork, false, nil,
|
||||
a.cfg.Lookup, func(addresses []*appmessage.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...)
|
||||
})
|
||||
|
||||
dnsseed.SeedFromGRPC(a.cfg.NetParams(), a.cfg.GRPCSeed, appmessage.SFNodeNetwork, false, nil,
|
||||
func(addresses []*appmessage.NetAddress) {
|
||||
a.addressManager.AddAddresses(addresses...)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// P2PNodeID returns the network ID associated with this ComponentManager
|
||||
func (a *ComponentManager) P2PNodeID() *id.ID {
|
||||
return a.netAdapter.ID()
|
||||
}
|
||||
|
||||
// AddressManager returns the AddressManager associated with this ComponentManager
|
||||
func (a *ComponentManager) AddressManager() *addressmanager.AddressManager {
|
||||
return a.addressManager
|
||||
}
|
||||
@@ -5,11 +5,11 @@
|
||||
package blocklogger
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -22,17 +22,17 @@ var (
|
||||
// LogBlock logs a new block blue score as an information message
|
||||
// to show progress to the user. In order to prevent spam, it limits logging to
|
||||
// one message every 10 seconds with duration and totals included.
|
||||
func LogBlock(block *util.Block) error {
|
||||
func LogBlock(block *externalapi.DomainBlock) {
|
||||
mtx.Lock()
|
||||
defer mtx.Unlock()
|
||||
|
||||
receivedLogBlocks++
|
||||
receivedLogTx += int64(len(block.MsgBlock().Transactions))
|
||||
receivedLogTx += int64(len(block.Transactions))
|
||||
|
||||
now := mstime.Now()
|
||||
duration := now.Sub(lastBlockLogTime)
|
||||
if duration < time.Second*10 {
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// Truncate the duration to 10s of milliseconds.
|
||||
@@ -48,17 +48,11 @@ func LogBlock(block *util.Block) error {
|
||||
txStr = "transaction"
|
||||
}
|
||||
|
||||
blueScore, err := block.BlueScore()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, blue score %d, %s)",
|
||||
log.Infof("Processed %d %s in the last %s (%d %s, %s)",
|
||||
receivedLogBlocks, blockStr, tDuration, receivedLogTx,
|
||||
txStr, blueScore, block.MsgBlock().Header.Timestamp)
|
||||
txStr, mstime.UnixMilliseconds(block.Header.TimeInMilliseconds))
|
||||
|
||||
receivedLogBlocks = 0
|
||||
receivedLogTx = 0
|
||||
lastBlockLogTime = now
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -3,44 +3,68 @@ package flowcontext
|
||||
import (
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// OnNewBlock updates the mempool after a new block arrival, and
|
||||
// relays newly unorphaned transactions and possibly rebroadcast
|
||||
// manually added transactions when not in IBD.
|
||||
func (f *FlowContext) OnNewBlock(block *util.Block) error {
|
||||
transactionsAcceptedToMempool, err := f.txPool.HandleNewBlock(block)
|
||||
func (f *FlowContext) OnNewBlock(block *externalapi.DomainBlock) error {
|
||||
hash := consensushashing.BlockHash(block)
|
||||
log.Debugf("OnNewBlock start for block %s", hash)
|
||||
defer log.Debugf("OnNewBlock end for block %s", hash)
|
||||
unorphanedBlocks, err := f.UnorphanBlocks(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if f.onBlockAddedToDAGHandler != nil {
|
||||
f.onBlockAddedToDAGHandler(block)
|
||||
|
||||
log.Debugf("OnNewBlock: block %s unorphaned %d blocks", hash, len(unorphanedBlocks))
|
||||
|
||||
newBlocks := append([]*externalapi.DomainBlock{block}, unorphanedBlocks...)
|
||||
for _, newBlock := range newBlocks {
|
||||
blocklogger.LogBlock(block)
|
||||
|
||||
log.Tracef("OnNewBlock: passing block %s transactions to mining manager", hash)
|
||||
_ = f.Domain().MiningManager().HandleNewBlockTransactions(newBlock.Transactions)
|
||||
|
||||
if f.onBlockAddedToDAGHandler != nil {
|
||||
log.Tracef("OnNewBlock: calling f.onBlockAddedToDAGHandler for block %s", hash)
|
||||
err := f.onBlockAddedToDAGHandler(newBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return f.broadcastTransactionsAfterBlockAdded(block, transactionsAcceptedToMempool)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(block *util.Block, transactionsAcceptedToMempool []*util.Tx) error {
|
||||
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
|
||||
block *externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
|
||||
|
||||
f.updateTransactionsToRebroadcast(block)
|
||||
|
||||
// Don't relay transactions when in IBD.
|
||||
if atomic.LoadUint32(&f.isInIBD) != 0 {
|
||||
if f.IsIBDRunning() {
|
||||
return nil
|
||||
}
|
||||
|
||||
var txIDsToRebroadcast []*daghash.TxID
|
||||
var txIDsToRebroadcast []*externalapi.DomainTransactionID
|
||||
if f.shouldRebroadcastTransactions() {
|
||||
txIDsToRebroadcast = f.txIDsToRebroadcast()
|
||||
}
|
||||
|
||||
txIDsToBroadcast := make([]*daghash.TxID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
|
||||
txIDsToBroadcast := make([]*externalapi.DomainTransactionID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
|
||||
for i, tx := range transactionsAcceptedToMempool {
|
||||
txIDsToBroadcast[i] = tx.ID()
|
||||
txIDsToBroadcast[i] = consensushashing.TransactionID(tx)
|
||||
}
|
||||
offset := len(transactionsAcceptedToMempool)
|
||||
for i, txID := range txIDsToRebroadcast {
|
||||
@@ -64,14 +88,42 @@ func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks
|
||||
}
|
||||
|
||||
// AddBlock adds the given block to the DAG and propagates it.
|
||||
func (f *FlowContext) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
|
||||
_, _, err := f.DAG().ProcessBlock(block, flags)
|
||||
func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error {
|
||||
err := f.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
if errors.As(err, &ruleerrors.RuleError{}) {
|
||||
log.Infof("Validation failed for block %s: %s", consensushashing.BlockHash(block), err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
err = f.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return f.Broadcast(appmessage.NewMsgInvBlock(block.Hash()))
|
||||
return f.Broadcast(appmessage.NewMsgInvBlock(consensushashing.BlockHash(block)))
|
||||
}
|
||||
|
||||
// IsIBDRunning returns true if IBD is currently marked as running
|
||||
func (f *FlowContext) IsIBDRunning() bool {
|
||||
return atomic.LoadUint32(&f.isInIBD) != 0
|
||||
}
|
||||
|
||||
// TrySetIBDRunning attempts to set `isInIBD`. Returns false
|
||||
// if it is already set
|
||||
func (f *FlowContext) TrySetIBDRunning() bool {
|
||||
succeeded := atomic.CompareAndSwapUint32(&f.isInIBD, 0, 1)
|
||||
if succeeded {
|
||||
log.Infof("IBD started")
|
||||
}
|
||||
return succeeded
|
||||
}
|
||||
|
||||
// UnsetIBDRunning unsets isInIBD
|
||||
func (f *FlowContext) UnsetIBDRunning() {
|
||||
succeeded := atomic.CompareAndSwapUint32(&f.isInIBD, 1, 0)
|
||||
if !succeeded {
|
||||
panic("attempted to unset isInIBD when it was not set to begin with")
|
||||
}
|
||||
log.Infof("IBD finished")
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
package flowcontext
|
||||
|
||||
import "github.com/kaspanet/kaspad/domain/blockdag"
|
||||
|
||||
// DAG returns the DAG associated to the flow context.
|
||||
func (f *FlowContext) DAG() *blockdag.BlockDAG {
|
||||
return f.dag
|
||||
}
|
||||
10
app/protocol/flowcontext/domain.go
Normal file
10
app/protocol/flowcontext/domain.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
)
|
||||
|
||||
// Domain returns the Domain object associated to the flow context.
|
||||
func (f *FlowContext) Domain() domain.Domain {
|
||||
return f.domain
|
||||
}
|
||||
@@ -4,23 +4,23 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// OnBlockAddedToDAGHandler is a handler function that's triggered
|
||||
// when a block is added to the DAG
|
||||
type OnBlockAddedToDAGHandler func(block *util.Block)
|
||||
type OnBlockAddedToDAGHandler func(block *externalapi.DomainBlock) error
|
||||
|
||||
// OnTransactionAddedToMempoolHandler is a handler function that's triggered
|
||||
// when a transaction is added to the mempool
|
||||
@@ -31,8 +31,7 @@ type OnTransactionAddedToMempoolHandler func()
|
||||
type FlowContext struct {
|
||||
cfg *config.Config
|
||||
netAdapter *netadapter.NetAdapter
|
||||
txPool *mempool.TxPool
|
||||
dag *blockdag.BlockDAG
|
||||
domain domain.Domain
|
||||
addressManager *addressmanager.AddressManager
|
||||
connectionManager *connmanager.ConnectionManager
|
||||
|
||||
@@ -40,36 +39,36 @@ type FlowContext struct {
|
||||
onTransactionAddedToMempoolHandler OnTransactionAddedToMempoolHandler
|
||||
|
||||
transactionsToRebroadcastLock sync.Mutex
|
||||
transactionsToRebroadcast map[daghash.TxID]*util.Tx
|
||||
transactionsToRebroadcast map[externalapi.DomainTransactionID]*externalapi.DomainTransaction
|
||||
lastRebroadcastTime time.Time
|
||||
sharedRequestedTransactions *relaytransactions.SharedRequestedTransactions
|
||||
sharedRequestedTransactions *transactionrelay.SharedRequestedTransactions
|
||||
|
||||
sharedRequestedBlocks *blockrelay.SharedRequestedBlocks
|
||||
|
||||
isInIBD uint32
|
||||
startIBDMutex sync.Mutex
|
||||
ibdPeer *peerpkg.Peer
|
||||
isInIBD uint32
|
||||
|
||||
peers map[id.ID]*peerpkg.Peer
|
||||
peersMutex sync.RWMutex
|
||||
|
||||
orphans map[externalapi.DomainHash]*externalapi.DomainBlock
|
||||
orphansMutex sync.RWMutex
|
||||
}
|
||||
|
||||
// New returns a new instance of FlowContext.
|
||||
func New(cfg *config.Config, dag *blockdag.BlockDAG, addressManager *addressmanager.AddressManager,
|
||||
txPool *mempool.TxPool, netAdapter *netadapter.NetAdapter,
|
||||
connectionManager *connmanager.ConnectionManager) *FlowContext {
|
||||
func New(cfg *config.Config, domain domain.Domain, addressManager *addressmanager.AddressManager,
|
||||
netAdapter *netadapter.NetAdapter, connectionManager *connmanager.ConnectionManager) *FlowContext {
|
||||
|
||||
return &FlowContext{
|
||||
cfg: cfg,
|
||||
netAdapter: netAdapter,
|
||||
dag: dag,
|
||||
domain: domain,
|
||||
addressManager: addressManager,
|
||||
connectionManager: connectionManager,
|
||||
txPool: txPool,
|
||||
sharedRequestedTransactions: relaytransactions.NewSharedRequestedTransactions(),
|
||||
sharedRequestedTransactions: transactionrelay.NewSharedRequestedTransactions(),
|
||||
sharedRequestedBlocks: blockrelay.NewSharedRequestedBlocks(),
|
||||
peers: make(map[id.ID]*peerpkg.Peer),
|
||||
transactionsToRebroadcast: make(map[daghash.TxID]*util.Tx),
|
||||
transactionsToRebroadcast: make(map[externalapi.DomainTransactionID]*externalapi.DomainTransaction),
|
||||
orphans: make(map[externalapi.DomainHash]*externalapi.DomainBlock),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
)
|
||||
|
||||
// StartIBDIfRequired selects a peer and starts IBD against it
|
||||
// if required
|
||||
func (f *FlowContext) StartIBDIfRequired() {
|
||||
f.startIBDMutex.Lock()
|
||||
defer f.startIBDMutex.Unlock()
|
||||
|
||||
if f.IsInIBD() {
|
||||
return
|
||||
}
|
||||
|
||||
peer := f.selectPeerForIBD(f.dag)
|
||||
if peer == nil {
|
||||
spawn("StartIBDIfRequired-requestSelectedTipsIfRequired", f.requestSelectedTipsIfRequired)
|
||||
return
|
||||
}
|
||||
|
||||
atomic.StoreUint32(&f.isInIBD, 1)
|
||||
f.ibdPeer = peer
|
||||
spawn("StartIBDIfRequired-peer.StartIBD", peer.StartIBD)
|
||||
}
|
||||
|
||||
// IsInIBD is true if IBD is currently running
|
||||
func (f *FlowContext) IsInIBD() bool {
|
||||
return atomic.LoadUint32(&f.isInIBD) != 0
|
||||
}
|
||||
|
||||
// selectPeerForIBD returns the first peer whose selected tip
|
||||
// hash is not in our DAG
|
||||
func (f *FlowContext) selectPeerForIBD(dag *blockdag.BlockDAG) *peerpkg.Peer {
|
||||
f.peersMutex.RLock()
|
||||
defer f.peersMutex.RUnlock()
|
||||
|
||||
for _, peer := range f.peers {
|
||||
peerSelectedTipHash := peer.SelectedTipHash()
|
||||
if !dag.IsInDAG(peerSelectedTipHash) {
|
||||
return peer
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *FlowContext) requestSelectedTipsIfRequired() {
|
||||
if f.isDAGTimeCurrent() {
|
||||
return
|
||||
}
|
||||
f.requestSelectedTips()
|
||||
}
|
||||
|
||||
func (f *FlowContext) isDAGTimeCurrent() bool {
|
||||
const minDurationToRequestSelectedTips = time.Minute
|
||||
return f.dag.Now().Sub(f.dag.SelectedTipHeader().Timestamp) > minDurationToRequestSelectedTips
|
||||
}
|
||||
|
||||
func (f *FlowContext) requestSelectedTips() {
|
||||
f.peersMutex.RLock()
|
||||
defer f.peersMutex.RUnlock()
|
||||
|
||||
for _, peer := range f.peers {
|
||||
peer.RequestSelectedTipIfRequired()
|
||||
}
|
||||
}
|
||||
|
||||
// FinishIBD finishes the current IBD flow and starts a new one if required.
|
||||
func (f *FlowContext) FinishIBD() {
|
||||
f.ibdPeer = nil
|
||||
|
||||
atomic.StoreUint32(&f.isInIBD, 0)
|
||||
|
||||
f.StartIBDIfRequired()
|
||||
}
|
||||
|
||||
// IBDPeer returns the currently active IBD peer.
|
||||
// Returns nil if we aren't currently in IBD
|
||||
func (f *FlowContext) IBDPeer() *peerpkg.Peer {
|
||||
if !f.IsInIBD() {
|
||||
return nil
|
||||
}
|
||||
return f.ibdPeer
|
||||
}
|
||||
130
app/protocol/flowcontext/orphans.go
Normal file
130
app/protocol/flowcontext/orphans.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AddOrphan adds the block to the orphan set
|
||||
func (f *FlowContext) AddOrphan(orphanBlock *externalapi.DomainBlock) {
|
||||
f.orphansMutex.Lock()
|
||||
defer f.orphansMutex.Unlock()
|
||||
|
||||
orphanHash := consensushashing.BlockHash(orphanBlock)
|
||||
f.orphans[*orphanHash] = orphanBlock
|
||||
|
||||
log.Infof("Received a block with missing parents, adding to orphan pool: %s", orphanHash)
|
||||
}
|
||||
|
||||
// IsOrphan returns whether the given blockHash belongs to an orphan block
|
||||
func (f *FlowContext) IsOrphan(blockHash *externalapi.DomainHash) bool {
|
||||
f.orphansMutex.RLock()
|
||||
defer f.orphansMutex.RUnlock()
|
||||
|
||||
_, ok := f.orphans[*blockHash]
|
||||
return ok
|
||||
}
|
||||
|
||||
// UnorphanBlocks removes the block from the orphan set, and remove all of the blocks that are not orphans anymore.
|
||||
func (f *FlowContext) UnorphanBlocks(rootBlock *externalapi.DomainBlock) ([]*externalapi.DomainBlock, error) {
|
||||
f.orphansMutex.Lock()
|
||||
defer f.orphansMutex.Unlock()
|
||||
|
||||
// Find all the children of rootBlock among the orphans
|
||||
// and add them to the process queue
|
||||
rootBlockHash := consensushashing.BlockHash(rootBlock)
|
||||
processQueue := f.addChildOrphansToProcessQueue(rootBlockHash, []externalapi.DomainHash{})
|
||||
|
||||
var unorphanedBlocks []*externalapi.DomainBlock
|
||||
for len(processQueue) > 0 {
|
||||
var orphanHash externalapi.DomainHash
|
||||
orphanHash, processQueue = processQueue[0], processQueue[1:]
|
||||
orphanBlock := f.orphans[orphanHash]
|
||||
|
||||
log.Tracef("Considering to unorphan block %s with parents %s",
|
||||
orphanHash, orphanBlock.Header.ParentHashes)
|
||||
|
||||
canBeUnorphaned := true
|
||||
for _, orphanBlockParentHash := range orphanBlock.Header.ParentHashes {
|
||||
orphanBlockParentInfo, err := f.domain.Consensus().GetBlockInfo(orphanBlockParentHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !orphanBlockParentInfo.Exists || orphanBlockParentInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
log.Tracef("Cannot unorphan block %s. It's missing at "+
|
||||
"least the following parent: %s", orphanHash, orphanBlockParentHash)
|
||||
|
||||
canBeUnorphaned = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if canBeUnorphaned {
|
||||
err := f.unorphanBlock(orphanHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
unorphanedBlocks = append(unorphanedBlocks, orphanBlock)
|
||||
processQueue = f.addChildOrphansToProcessQueue(&orphanHash, processQueue)
|
||||
}
|
||||
}
|
||||
|
||||
return unorphanedBlocks, nil
|
||||
}
|
||||
|
||||
// addChildOrphansToProcessQueue finds all child orphans of `blockHash`
|
||||
// and adds them to the given `processQueue` if they don't already exist
|
||||
// inside of it
|
||||
// Note that this method does not modify the given `processQueue`
|
||||
func (f *FlowContext) addChildOrphansToProcessQueue(blockHash *externalapi.DomainHash,
|
||||
processQueue []externalapi.DomainHash) []externalapi.DomainHash {
|
||||
|
||||
blockChildren := f.findChildOrphansOfBlock(blockHash)
|
||||
for _, blockChild := range blockChildren {
|
||||
exists := false
|
||||
for _, queueOrphan := range processQueue {
|
||||
if queueOrphan == blockChild {
|
||||
exists = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !exists {
|
||||
processQueue = append(processQueue, blockChild)
|
||||
}
|
||||
}
|
||||
return processQueue
|
||||
}
|
||||
|
||||
func (f *FlowContext) findChildOrphansOfBlock(blockHash *externalapi.DomainHash) []externalapi.DomainHash {
|
||||
var childOrphans []externalapi.DomainHash
|
||||
for orphanHash, orphanBlock := range f.orphans {
|
||||
for _, orphanBlockParentHash := range orphanBlock.Header.ParentHashes {
|
||||
if *orphanBlockParentHash == *blockHash {
|
||||
childOrphans = append(childOrphans, orphanHash)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return childOrphans
|
||||
}
|
||||
|
||||
func (f *FlowContext) unorphanBlock(orphanHash externalapi.DomainHash) error {
|
||||
orphanBlock, ok := f.orphans[orphanHash]
|
||||
if !ok {
|
||||
return errors.Errorf("attempted to unorphan a non-orphan block %s", orphanHash)
|
||||
}
|
||||
delete(f.orphans, orphanHash)
|
||||
|
||||
err := f.domain.Consensus().ValidateAndInsertBlock(orphanBlock)
|
||||
if err != nil {
|
||||
if errors.As(err, &ruleerrors.RuleError{}) {
|
||||
log.Infof("Validation failed for orphan block %s: %s", orphanHash, err)
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Unorphaned block %s", orphanHash)
|
||||
return nil
|
||||
}
|
||||
@@ -1,42 +1,38 @@
|
||||
package flowcontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
)
|
||||
|
||||
// AddTransaction adds transaction to the mempool and propagates it.
|
||||
func (f *FlowContext) AddTransaction(tx *util.Tx) error {
|
||||
func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction) error {
|
||||
f.transactionsToRebroadcastLock.Lock()
|
||||
defer f.transactionsToRebroadcastLock.Unlock()
|
||||
|
||||
transactionsAcceptedToMempool, err := f.txPool.ProcessTransaction(tx, false)
|
||||
err := f.Domain().MiningManager().ValidateAndInsertTransaction(tx, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(transactionsAcceptedToMempool) > 1 {
|
||||
return errors.New("got more than one accepted transactions when no orphans were allowed")
|
||||
}
|
||||
|
||||
f.transactionsToRebroadcast[*tx.ID()] = tx
|
||||
inv := appmessage.NewMsgInvTransaction([]*daghash.TxID{tx.ID()})
|
||||
transactionID := consensushashing.TransactionID(tx)
|
||||
f.transactionsToRebroadcast[*transactionID] = tx
|
||||
inv := appmessage.NewMsgInvTransaction([]*externalapi.DomainTransactionID{transactionID})
|
||||
return f.Broadcast(inv)
|
||||
}
|
||||
|
||||
func (f *FlowContext) updateTransactionsToRebroadcast(block *util.Block) {
|
||||
func (f *FlowContext) updateTransactionsToRebroadcast(block *externalapi.DomainBlock) {
|
||||
f.transactionsToRebroadcastLock.Lock()
|
||||
defer f.transactionsToRebroadcastLock.Unlock()
|
||||
// Note: if the block is red, its transactions won't be rebroadcasted
|
||||
// anymore, although they are not included in the UTXO set.
|
||||
// This is probably ok, since red blocks are quite rare.
|
||||
for _, tx := range block.Transactions() {
|
||||
delete(f.transactionsToRebroadcast, *tx.ID())
|
||||
for _, tx := range block.Transactions {
|
||||
delete(f.transactionsToRebroadcast, *consensushashing.TransactionID(tx))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,30 +41,25 @@ func (f *FlowContext) shouldRebroadcastTransactions() bool {
|
||||
return time.Since(f.lastRebroadcastTime) > rebroadcastInterval
|
||||
}
|
||||
|
||||
func (f *FlowContext) txIDsToRebroadcast() []*daghash.TxID {
|
||||
func (f *FlowContext) txIDsToRebroadcast() []*externalapi.DomainTransactionID {
|
||||
f.transactionsToRebroadcastLock.Lock()
|
||||
defer f.transactionsToRebroadcastLock.Unlock()
|
||||
|
||||
txIDs := make([]*daghash.TxID, len(f.transactionsToRebroadcast))
|
||||
txIDs := make([]*externalapi.DomainTransactionID, len(f.transactionsToRebroadcast))
|
||||
i := 0
|
||||
for _, tx := range f.transactionsToRebroadcast {
|
||||
txIDs[i] = tx.ID()
|
||||
txIDs[i] = consensushashing.TransactionID(tx)
|
||||
i++
|
||||
}
|
||||
return txIDs
|
||||
}
|
||||
|
||||
// SharedRequestedTransactions returns a *relaytransactions.SharedRequestedTransactions for sharing
|
||||
// SharedRequestedTransactions returns a *transactionrelay.SharedRequestedTransactions for sharing
|
||||
// data about requested transactions between different peers.
|
||||
func (f *FlowContext) SharedRequestedTransactions() *relaytransactions.SharedRequestedTransactions {
|
||||
func (f *FlowContext) SharedRequestedTransactions() *transactionrelay.SharedRequestedTransactions {
|
||||
return f.sharedRequestedTransactions
|
||||
}
|
||||
|
||||
// TxPool returns the transaction pool associated to the manager.
|
||||
func (f *FlowContext) TxPool() *mempool.TxPool {
|
||||
return f.txPool
|
||||
}
|
||||
|
||||
// OnTransactionAddedToMempool notifies the handler function that a transaction
|
||||
// has been added to the mempool
|
||||
func (f *FlowContext) OnTransactionAddedToMempool() {
|
||||
|
||||
@@ -20,10 +20,6 @@ type ReceiveAddressesContext interface {
|
||||
func ReceiveAddresses(context ReceiveAddressesContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
||||
peer *peerpkg.Peer) error {
|
||||
|
||||
if !context.AddressManager().NeedMoreAddresses() {
|
||||
return nil
|
||||
}
|
||||
|
||||
subnetworkID := peer.SubnetworkID()
|
||||
msgGetAddresses := appmessage.NewMsgRequestAddresses(false, subnetworkID)
|
||||
err := outgoingRoute.Enqueue(msgGetAddresses)
|
||||
@@ -37,21 +33,10 @@ func ReceiveAddresses(context ReceiveAddressesContext, incomingRoute *router.Rou
|
||||
}
|
||||
|
||||
msgAddresses := message.(*appmessage.MsgAddresses)
|
||||
if len(msgAddresses.AddrList) > addressmanager.GetAddressesMax {
|
||||
if len(msgAddresses.AddressList) > addressmanager.GetAddressesMax {
|
||||
return protocolerrors.Errorf(true, "address count exceeded %d", addressmanager.GetAddressesMax)
|
||||
}
|
||||
|
||||
if msgAddresses.IncludeAllSubnetworks {
|
||||
return protocolerrors.Errorf(true, "got unexpected "+
|
||||
"IncludeAllSubnetworks=true in [%s] command", msgAddresses.Command())
|
||||
}
|
||||
if !msgAddresses.SubnetworkID.IsEqual(context.Config().SubnetworkID) && msgAddresses.SubnetworkID != nil {
|
||||
return protocolerrors.Errorf(false, "only full nodes and %s subnetwork IDs "+
|
||||
"are allowed in [%s] command, but got subnetwork ID %s",
|
||||
context.Config().SubnetworkID, msgAddresses.Command(), msgAddresses.SubnetworkID)
|
||||
}
|
||||
|
||||
sourceAddress := peer.Connection().NetAddress()
|
||||
context.AddressManager().AddAddresses(msgAddresses.AddrList, sourceAddress, msgAddresses.SubnetworkID)
|
||||
context.AddressManager().AddAddresses(msgAddresses.AddressList...)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
package addressexchange
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"math/rand"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
// SendAddressesContext is the interface for the context needed for the SendAddresses flow.
|
||||
@@ -14,21 +16,25 @@ type SendAddressesContext interface {
|
||||
|
||||
// SendAddresses sends addresses to a peer that requests it.
|
||||
func SendAddresses(context SendAddressesContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgGetAddresses := message.(*appmessage.MsgRequestAddresses)
|
||||
addresses := context.AddressManager().AddressCache(msgGetAddresses.IncludeAllSubnetworks,
|
||||
msgGetAddresses.SubnetworkID)
|
||||
msgAddresses := appmessage.NewMsgAddresses(msgGetAddresses.IncludeAllSubnetworks, msgGetAddresses.SubnetworkID)
|
||||
err = msgAddresses.AddAddresses(shuffleAddresses(addresses)...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := message.(*appmessage.MsgRequestAddresses)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "unexpected message. "+
|
||||
"Expected: %s, got: %s", appmessage.CmdRequestAddresses, message.Command())
|
||||
}
|
||||
addresses := context.AddressManager().Addresses()
|
||||
msgAddresses := appmessage.NewMsgAddresses(shuffleAddresses(addresses))
|
||||
|
||||
return outgoingRoute.Enqueue(msgAddresses)
|
||||
err = outgoingRoute.Enqueue(msgAddresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// shuffleAddresses randomizes the given addresses sent if there are more than the maximum allowed in one message.
|
||||
|
||||
29
app/protocol/flows/blockrelay/block_locator.go
Normal file
29
app/protocol/flows/blockrelay/block_locator.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
func (flow *handleRelayInvsFlow) sendGetBlockLocator(lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, limit uint32) error {
|
||||
|
||||
msgGetBlockLocator := appmessage.NewMsgRequestBlockLocator(lowHash, highHash, limit)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockLocator)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveBlockLocator() (blockLocatorHashes []*externalapi.DomainHash, err error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgBlockLocator, ok := message.(*appmessage.MsgBlockLocator)
|
||||
if !ok {
|
||||
return nil,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdBlockLocator, message.Command())
|
||||
}
|
||||
return msgBlockLocator.BlockLocatorHashes, nil
|
||||
}
|
||||
52
app/protocol/flows/blockrelay/handle_ibd_block_requests.go
Normal file
52
app/protocol/flows/blockrelay/handle_ibd_block_requests.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleIBDBlockRequestsContext is the interface for the context needed for the HandleIBDBlockRequests flow.
|
||||
type HandleIBDBlockRequestsContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandleIBDBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
||||
// their corresponding blocks to the requesting peer.
|
||||
func HandleIBDBlockRequests(context HandleIBDBlockRequestsContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route) error {
|
||||
|
||||
for {
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestIBDBlocks)
|
||||
for _, hash := range msgRequestIBDBlocks.Hashes {
|
||||
// Fetch the block from the database.
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists || blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
||||
}
|
||||
block, err := context.Domain().Consensus().GetBlock(hash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
||||
}
|
||||
|
||||
// TODO (Partial nodes): Convert block to partial block if needed
|
||||
|
||||
blockMessage := appmessage.DomainBlockToMsgBlock(block)
|
||||
ibdBlockMessage := appmessage.NewMsgIBDBlock(blockMessage)
|
||||
err = outgoingRoute.Enqueue(ibdBlockMessage)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,15 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// RelayBlockRequestsContext is the interface for the context needed for the HandleRelayBlockRequests flow.
|
||||
type RelayBlockRequestsContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
// HandleRelayBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
|
||||
@@ -27,26 +28,21 @@ func HandleRelayBlockRequests(context RelayBlockRequestsContext, incomingRoute *
|
||||
getRelayBlocksMessage := message.(*appmessage.MsgRequestRelayBlocks)
|
||||
for _, hash := range getRelayBlocksMessage.Hashes {
|
||||
// Fetch the block from the database.
|
||||
block, err := context.DAG().BlockByHash(hash)
|
||||
if blockdag.IsNotInDAGErr(err) {
|
||||
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !blockInfo.Exists || blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
||||
return protocolerrors.Errorf(true, "block %s not found", hash)
|
||||
} else if err != nil {
|
||||
}
|
||||
block, err := context.Domain().Consensus().GetBlock(hash)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
|
||||
}
|
||||
msgBlock := block.MsgBlock()
|
||||
|
||||
// If we are a full node and the peer is a partial node, we must convert
|
||||
// the block to a partial block.
|
||||
nodeSubnetworkID := context.DAG().SubnetworkID()
|
||||
peerSubnetworkID := peer.SubnetworkID()
|
||||
// TODO (Partial nodes): Convert block to partial block if needed
|
||||
|
||||
isNodeFull := nodeSubnetworkID == nil
|
||||
isPeerFull := peerSubnetworkID == nil
|
||||
if isNodeFull && !isPeerFull {
|
||||
msgBlock.ConvertToPartial(peerSubnetworkID)
|
||||
}
|
||||
|
||||
err = outgoingRoute.Enqueue(msgBlock)
|
||||
err = outgoingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(block))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -2,28 +2,37 @@ package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
mathUtil "github.com/kaspanet/kaspad/util/math"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// orphanResolutionRange is the maximum amount of blockLocator hashes
|
||||
// to search for known blocks. See isBlockInOrphanResolutionRange for
|
||||
// further details
|
||||
var orphanResolutionRange uint32 = 5
|
||||
|
||||
// RelayInvsContext is the interface for the context needed for the HandleRelayInvs flow.
|
||||
type RelayInvsContext interface {
|
||||
Domain() domain.Domain
|
||||
Config() *config.Config
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
DAG() *blockdag.BlockDAG
|
||||
OnNewBlock(block *util.Block) error
|
||||
OnNewBlock(block *externalapi.DomainBlock) error
|
||||
SharedRequestedBlocks() *SharedRequestedBlocks
|
||||
StartIBDIfRequired()
|
||||
IsInIBD() bool
|
||||
Broadcast(message appmessage.Message) error
|
||||
AddOrphan(orphanBlock *externalapi.DomainBlock)
|
||||
IsOrphan(blockHash *externalapi.DomainHash) bool
|
||||
IsIBDRunning() bool
|
||||
TrySetIBDRunning() bool
|
||||
UnsetIBDRunning()
|
||||
}
|
||||
|
||||
type handleRelayInvsFlow struct {
|
||||
@@ -50,6 +59,7 @@ func HandleRelayInvs(context RelayInvsContext, incomingRoute *router.Route, outg
|
||||
|
||||
func (flow *handleRelayInvsFlow) start() error {
|
||||
for {
|
||||
log.Debugf("Waiting for inv")
|
||||
inv, err := flow.readInv()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -57,34 +67,68 @@ func (flow *handleRelayInvsFlow) start() error {
|
||||
|
||||
log.Debugf("Got relay inv for block %s", inv.Hash)
|
||||
|
||||
if flow.DAG().IsKnownBlock(inv.Hash) {
|
||||
if flow.DAG().IsKnownInvalid(inv.Hash) {
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(inv.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
if blockInfo.BlockStatus == externalapi.StatusInvalid {
|
||||
return protocolerrors.Errorf(true, "sent inv of an invalid block %s",
|
||||
inv.Hash)
|
||||
}
|
||||
log.Debugf("Block %s already exists. continuing...", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
flow.StartIBDIfRequired()
|
||||
if flow.IsInIBD() {
|
||||
// Block relay is disabled during IBD
|
||||
if flow.IsOrphan(inv.Hash) {
|
||||
log.Debugf("Block %s is a known orphan. continuing...", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
requestQueue := newHashesQueueSet()
|
||||
requestQueue.enqueueIfNotExists(inv.Hash)
|
||||
// Block relay is disabled during IBD
|
||||
if flow.IsIBDRunning() {
|
||||
log.Debugf("Got block %s while in IBD. continuing...", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
for requestQueue.len() > 0 {
|
||||
err := flow.requestBlocks(requestQueue)
|
||||
log.Debugf("Requesting block %s", inv.Hash)
|
||||
block, exists, err := flow.requestBlock(inv.Hash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if exists {
|
||||
log.Debugf("Aborting requesting block %s because it already exists", inv.Hash)
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Processing block %s", inv.Hash)
|
||||
missingParents, err := flow.processBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(missingParents) > 0 {
|
||||
log.Debugf("Block %s contains orphans: %s", inv.Hash, missingParents)
|
||||
err := flow.processOrphan(block, missingParents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
log.Debugf("Relaying block %s", inv.Hash)
|
||||
err = flow.relayBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Infof("Accepted block %s via relay", inv.Hash)
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error) {
|
||||
|
||||
if len(flow.invsQueue) > 0 {
|
||||
var inv *appmessage.MsgInvRelayBlock
|
||||
inv, flow.invsQueue = flow.invsQueue[0], flow.invsQueue[1:]
|
||||
@@ -104,72 +148,40 @@ func (flow *handleRelayInvsFlow) readInv() (*appmessage.MsgInvRelayBlock, error)
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) error {
|
||||
numHashesToRequest := mathUtil.MinInt(appmessage.MsgRequestRelayBlocksHashes, requestQueue.len())
|
||||
hashesToRequest := requestQueue.dequeue(numHashesToRequest)
|
||||
|
||||
pendingBlocks := map[daghash.Hash]struct{}{}
|
||||
var filteredHashesToRequest []*daghash.Hash
|
||||
for _, hash := range hashesToRequest {
|
||||
exists := flow.SharedRequestedBlocks().addIfNotExists(hash)
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
|
||||
// The block can become known from another peer in the process of orphan resolution
|
||||
if flow.DAG().IsKnownBlock(hash) {
|
||||
continue
|
||||
}
|
||||
|
||||
pendingBlocks[*hash] = struct{}{}
|
||||
filteredHashesToRequest = append(filteredHashesToRequest, hash)
|
||||
func (flow *handleRelayInvsFlow) requestBlock(requestHash *externalapi.DomainHash) (*externalapi.DomainBlock, bool, error) {
|
||||
exists := flow.SharedRequestedBlocks().addIfNotExists(requestHash)
|
||||
if exists {
|
||||
return nil, true, nil
|
||||
}
|
||||
|
||||
// Exit early if we've filtered out all the hashes
|
||||
if len(filteredHashesToRequest) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// In case the function returns earlier than expected, we want to make sure requestedBlocks is
|
||||
// In case the function returns earlier than expected, we want to make sure flow.SharedRequestedBlocks() is
|
||||
// clean from any pending blocks.
|
||||
defer flow.SharedRequestedBlocks().removeSet(pendingBlocks)
|
||||
defer flow.SharedRequestedBlocks().remove(requestHash)
|
||||
|
||||
getRelayBlocksMsg := appmessage.NewMsgRequestRelayBlocks(filteredHashesToRequest)
|
||||
getRelayBlocksMsg := appmessage.NewMsgRequestRelayBlocks([]*externalapi.DomainHash{requestHash})
|
||||
err := flow.outgoingRoute.Enqueue(getRelayBlocksMsg)
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
for len(pendingBlocks) > 0 {
|
||||
msgBlock, err := flow.readMsgBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
block := util.NewBlock(msgBlock)
|
||||
blockHash := block.Hash()
|
||||
|
||||
if _, ok := pendingBlocks[*blockHash]; !ok {
|
||||
return protocolerrors.Errorf(true, "got unrequested block %s", block.Hash())
|
||||
}
|
||||
|
||||
err = flow.processAndRelayBlock(requestQueue, block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(pendingBlocks, *blockHash)
|
||||
flow.SharedRequestedBlocks().remove(blockHash)
|
||||
msgBlock, err := flow.readMsgBlock()
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return nil
|
||||
|
||||
block := appmessage.MsgBlockToDomainBlock(msgBlock)
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
if *blockHash != *requestHash {
|
||||
return nil, false, protocolerrors.Errorf(true, "got unrequested block %s", blockHash)
|
||||
}
|
||||
|
||||
return block, false, nil
|
||||
}
|
||||
|
||||
// readMsgBlock returns the next msgBlock in msgChan, and populates invsQueue with any inv messages that meanwhile arrive.
|
||||
//
|
||||
// Note: this function assumes msgChan can contain only appmessage.MsgInvRelayBlock and appmessage.MsgBlock messages.
|
||||
func (flow *handleRelayInvsFlow) readMsgBlock() (
|
||||
msgBlock *appmessage.MsgBlock, err error) {
|
||||
|
||||
func (flow *handleRelayInvsFlow) readMsgBlock() (msgBlock *appmessage.MsgBlock, err error) {
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
@@ -187,59 +199,92 @@ func (flow *handleRelayInvsFlow) readMsgBlock() (
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueSet, block *util.Block) error {
|
||||
blockHash := block.Hash()
|
||||
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
|
||||
func (flow *handleRelayInvsFlow) processBlock(block *externalapi.DomainBlock) ([]*externalapi.DomainHash, error) {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
if !errors.As(err, &blockdag.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process block %s", blockHash)
|
||||
}
|
||||
log.Infof("Rejected block %s from %s: %s", blockHash, flow.peer, err)
|
||||
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
|
||||
}
|
||||
|
||||
if isDelayed {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isOrphan {
|
||||
blueScore, err := block.BlueScore()
|
||||
if err != nil {
|
||||
return protocolerrors.Errorf(true, "received an orphan "+
|
||||
"block %s with malformed blue score", blockHash)
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return nil, errors.Wrapf(err, "failed to process block %s", blockHash)
|
||||
}
|
||||
|
||||
const maxOrphanBlueScoreDiff = 10000
|
||||
selectedTipBlueScore := flow.DAG().SelectedTipBlueScore()
|
||||
if blueScore > selectedTipBlueScore+maxOrphanBlueScoreDiff {
|
||||
log.Infof("Orphan block %s has blue score %d and the selected tip blue score is "+
|
||||
"%d. Ignoring orphans with a blue score difference from the selected tip greater than %d",
|
||||
blockHash, blueScore, selectedTipBlueScore, maxOrphanBlueScoreDiff)
|
||||
return nil
|
||||
missingParentsError := &ruleerrors.ErrMissingParents{}
|
||||
if errors.As(err, missingParentsError) {
|
||||
return missingParentsError.MissingParentHashes, nil
|
||||
}
|
||||
log.Warnf("Rejected block %s from %s: %s", blockHash, flow.peer, err)
|
||||
|
||||
// Request the parents for the orphan block from the peer that sent it.
|
||||
missingAncestors := flow.DAG().GetOrphanMissingAncestorHashes(blockHash)
|
||||
for _, missingAncestor := range missingAncestors {
|
||||
requestQueue.enqueueIfNotExists(missingAncestor)
|
||||
}
|
||||
return nil
|
||||
return nil, protocolerrors.Wrapf(true, err, "got invalid block %s from relay", blockHash)
|
||||
}
|
||||
err = blocklogger.LogBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = flow.Broadcast(appmessage.NewMsgInvBlock(blockHash))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flow.StartIBDIfRequired()
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) relayBlock(block *externalapi.DomainBlock) error {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
return flow.Broadcast(appmessage.NewMsgInvBlock(blockHash))
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processOrphan(block *externalapi.DomainBlock, missingParents []*externalapi.DomainHash) error {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
|
||||
// Return if the block has been orphaned from elsewhere already
|
||||
if flow.IsOrphan(blockHash) {
|
||||
log.Debugf("Skipping orphan processing for block %s because it is already an orphan", blockHash)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add the block to the orphan set if it's within orphan resolution range
|
||||
isBlockInOrphanResolutionRange, err := flow.isBlockInOrphanResolutionRange(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if isBlockInOrphanResolutionRange {
|
||||
log.Debugf("Block %s is within orphan resolution range. "+
|
||||
"Adding it to the orphan set and requesting its missing parents", blockHash)
|
||||
flow.addToOrphanSetAndRequestMissingParents(block, missingParents)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Start IBD unless we already are in IBD
|
||||
log.Debugf("Block %s is out of orphan resolution range. "+
|
||||
"Attempting to start IBD against it.", blockHash)
|
||||
return flow.runIBDIfNotRunning(blockHash)
|
||||
}
|
||||
|
||||
// isBlockInOrphanResolutionRange finds out whether the given blockHash should be
|
||||
// retrieved via the unorphaning mechanism or via IBD. This method sends a
|
||||
// getBlockLocator request to the peer with a limit of orphanResolutionRange.
|
||||
// In the response, if we know none of the hashes, we should retrieve the given
|
||||
// blockHash via IBD. Otherwise, via unorphaning.
|
||||
func (flow *handleRelayInvsFlow) isBlockInOrphanResolutionRange(blockHash *externalapi.DomainHash) (bool, error) {
|
||||
lowHash := flow.Config().ActiveNetParams.GenesisHash
|
||||
err := flow.sendGetBlockLocator(lowHash, blockHash, orphanResolutionRange)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
blockLocatorHashes, err := flow.receiveBlockLocator()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, blockLocatorHash := range blockLocatorHashes {
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(blockLocatorHash)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if blockInfo.Exists && blockInfo.BlockStatus != externalapi.StatusHeaderOnly {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) addToOrphanSetAndRequestMissingParents(
|
||||
block *externalapi.DomainBlock, missingParents []*externalapi.DomainHash) {
|
||||
|
||||
flow.AddOrphan(block)
|
||||
invMessages := make([]*appmessage.MsgInvRelayBlock, len(missingParents))
|
||||
for i, missingParent := range missingParents {
|
||||
invMessages[i] = appmessage.NewMsgInvBlock(missingParent)
|
||||
}
|
||||
flow.invsQueue = append(invMessages, flow.invsQueue...)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
package ibd
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// RequestBlockLocatorContext is the interface for the context needed for the HandleRequestBlockLocator flow.
|
||||
type RequestBlockLocatorContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestBlockLocatorFlow struct {
|
||||
@@ -32,13 +32,16 @@ func HandleRequestBlockLocator(context RequestBlockLocatorContext, incomingRoute
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) start() error {
|
||||
for {
|
||||
lowHash, highHash, err := flow.receiveGetBlockLocator()
|
||||
lowHash, highHash, limit, err := flow.receiveGetBlockLocator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
locator, err := flow.DAG().BlockLocatorFromHashes(highHash, lowHash)
|
||||
locator, err := flow.Domain().Consensus().CreateBlockLocator(lowHash, highHash, limit)
|
||||
if err != nil || len(locator) == 0 {
|
||||
if err != nil {
|
||||
log.Debugf("Received error from CreateBlockLocator: %s", err)
|
||||
}
|
||||
return protocolerrors.Errorf(true, "couldn't build a block "+
|
||||
"locator between blocks %s and %s", lowHash, highHash)
|
||||
}
|
||||
@@ -50,19 +53,19 @@ func (flow *handleRequestBlockLocatorFlow) start() error {
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *daghash.Hash,
|
||||
highHash *daghash.Hash, err error) {
|
||||
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, limit uint32, err error) {
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, nil, 0, err
|
||||
}
|
||||
msgGetBlockLocator := message.(*appmessage.MsgRequestBlockLocator)
|
||||
|
||||
return msgGetBlockLocator.LowHash, msgGetBlockLocator.HighHash, nil
|
||||
return msgGetBlockLocator.LowHash, msgGetBlockLocator.HighHash, msgGetBlockLocator.Limit, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator blockdag.BlockLocator) error {
|
||||
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator externalapi.BlockLocator) error {
|
||||
msgBlockLocator := appmessage.NewMsgBlockLocator(locator)
|
||||
err := flow.outgoingRoute.Enqueue(msgBlockLocator)
|
||||
if err != nil {
|
||||
126
app/protocol/flows/blockrelay/handle_request_headers.go
Normal file
126
app/protocol/flows/blockrelay/handle_request_headers.go
Normal file
@@ -0,0 +1,126 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
const ibdBatchSize = router.DefaultMaxMessages
|
||||
const maxHeaders = appmessage.MaxInvPerMsg
|
||||
|
||||
// RequestIBDBlocksContext is the interface for the context needed for the HandleRequestHeaders flow.
|
||||
type RequestIBDBlocksContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestBlocksFlow struct {
|
||||
RequestIBDBlocksContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestHeaders handles RequestHeaders messages
|
||||
func HandleRequestHeaders(context RequestIBDBlocksContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestBlocksFlow{
|
||||
RequestIBDBlocksContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) start() error {
|
||||
for {
|
||||
lowHash, highHash, err := receiveRequestHeaders(flow.incomingRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgHeaders, err := flow.buildMsgBlockHeaders(lowHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(msgHeaders); offset += ibdBatchSize {
|
||||
end := offset + ibdBatchSize
|
||||
if end > len(msgHeaders) {
|
||||
end = len(msgHeaders)
|
||||
}
|
||||
|
||||
blocksToSend := msgHeaders[offset:end]
|
||||
err = flow.sendHeaders(blocksToSend)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exit the loop and don't wait for the GetNextIBDBlocks message if the last batch was
|
||||
// less than ibdBatchSize.
|
||||
if len(blocksToSend) < ibdBatchSize {
|
||||
break
|
||||
}
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := message.(*appmessage.MsgRequestNextHeaders); !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextHeaders, message.Command())
|
||||
}
|
||||
}
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneHeaders())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequestHeaders(incomingRoute *router.Route) (lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash, err error) {
|
||||
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestHeaders)
|
||||
|
||||
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) buildMsgBlockHeaders(lowHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash) ([]*appmessage.MsgBlockHeader, error) {
|
||||
|
||||
blockHashes, err := flow.Domain().Consensus().GetHashesBetween(lowHash, highHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(blockHashes) > maxHeaders {
|
||||
blockHashes = blockHashes[:maxHeaders]
|
||||
}
|
||||
|
||||
msgBlockHeaders := make([]*appmessage.MsgBlockHeader, len(blockHashes))
|
||||
for i, blockHash := range blockHashes {
|
||||
header, err := flow.Domain().Consensus().GetBlockHeader(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgBlockHeaders[i] = appmessage.DomainBlockHeaderToBlockHeader(header)
|
||||
}
|
||||
|
||||
return msgBlockHeaders, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) sendHeaders(headers []*appmessage.MsgBlockHeader) error {
|
||||
for _, msgBlockHeader := range headers {
|
||||
err := flow.outgoingRoute.Enqueue(msgBlockHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
15
app/protocol/flows/blockrelay/handle_request_headers_test.go
Normal file
15
app/protocol/flows/blockrelay/handle_request_headers_test.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||
"github.com/kaspanet/kaspad/domain/dagconfig"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMaxHeaders(t *testing.T) {
|
||||
testutils.ForAllNets(t, false, func(t *testing.T, params *dagconfig.Params) {
|
||||
if params.FinalityDepth() > maxHeaders {
|
||||
t.Errorf("FinalityDepth() in %s should be lower or equal to appmessage.MaxInvPerMsg", params.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
// HandleRequestIBDRootUTXOSetAndBlockContext is the interface for the context needed for the HandleRequestIBDRootUTXOSetAndBlock flow.
|
||||
type HandleRequestIBDRootUTXOSetAndBlockContext interface {
|
||||
Domain() domain.Domain
|
||||
}
|
||||
|
||||
type handleRequestIBDRootUTXOSetAndBlockFlow struct {
|
||||
HandleRequestIBDRootUTXOSetAndBlockContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestIBDRootUTXOSetAndBlock listens to appmessage.MsgRequestIBDRootUTXOSetAndBlock messages and sends
|
||||
// the IBD root UTXO set and block body.
|
||||
func HandleRequestIBDRootUTXOSetAndBlock(context HandleRequestIBDRootUTXOSetAndBlockContext, incomingRoute,
|
||||
outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestIBDRootUTXOSetAndBlockFlow{
|
||||
HandleRequestIBDRootUTXOSetAndBlockContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestIBDRootUTXOSetAndBlockFlow) start() error {
|
||||
for {
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
msgRequestIBDRootUTXOSetAndBlock := message.(*appmessage.MsgRequestIBDRootUTXOSetAndBlock)
|
||||
|
||||
utxoSet, err := flow.Domain().Consensus().GetPruningPointUTXOSet(msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
if err != nil {
|
||||
if errors.Is(err, ruleerrors.ErrWrongPruningPointHash) {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootNotFound())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
block, err := flow.Domain().Consensus().GetBlock(msgRequestIBDRootUTXOSetAndBlock.IBDRoot)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgIBDRootUTXOSetAndBlock(utxoSet,
|
||||
appmessage.DomainBlockToMsgBlock(block)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,15 @@
|
||||
package blockrelay
|
||||
|
||||
import "github.com/kaspanet/kaspad/util/daghash"
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
type hashesQueueSet struct {
|
||||
queue []*daghash.Hash
|
||||
set map[daghash.Hash]struct{}
|
||||
queue []*externalapi.DomainHash
|
||||
set map[externalapi.DomainHash]struct{}
|
||||
}
|
||||
|
||||
func (r *hashesQueueSet) enqueueIfNotExists(hash *daghash.Hash) {
|
||||
func (r *hashesQueueSet) enqueueIfNotExists(hash *externalapi.DomainHash) {
|
||||
if _, ok := r.set[*hash]; ok {
|
||||
return
|
||||
}
|
||||
@@ -15,8 +17,8 @@ func (r *hashesQueueSet) enqueueIfNotExists(hash *daghash.Hash) {
|
||||
r.set[*hash] = struct{}{}
|
||||
}
|
||||
|
||||
func (r *hashesQueueSet) dequeue(numItems int) []*daghash.Hash {
|
||||
var hashes []*daghash.Hash
|
||||
func (r *hashesQueueSet) dequeue(numItems int) []*externalapi.DomainHash {
|
||||
var hashes []*externalapi.DomainHash
|
||||
hashes, r.queue = r.queue[:numItems], r.queue[numItems:]
|
||||
for _, hash := range hashes {
|
||||
delete(r.set, *hash)
|
||||
@@ -30,6 +32,6 @@ func (r *hashesQueueSet) len() int {
|
||||
|
||||
func newHashesQueueSet() *hashesQueueSet {
|
||||
return &hashesQueueSet{
|
||||
set: make(map[daghash.Hash]struct{}),
|
||||
set: make(map[externalapi.DomainHash]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
307
app/protocol/flows/blockrelay/ibd.go
Normal file
307
app/protocol/flows/blockrelay/ibd.go
Normal file
@@ -0,0 +1,307 @@
|
||||
package blockrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (flow *handleRelayInvsFlow) runIBDIfNotRunning(highHash *externalapi.DomainHash) error {
|
||||
wasIBDNotRunning := flow.TrySetIBDRunning()
|
||||
if !wasIBDNotRunning {
|
||||
log.Debugf("IBD is already running")
|
||||
return nil
|
||||
}
|
||||
defer flow.UnsetIBDRunning()
|
||||
|
||||
log.Debugf("IBD started with peer %s and highHash %s", flow.peer, highHash)
|
||||
|
||||
// Fetch all the headers if we don't already have them
|
||||
log.Debugf("Downloading headers up to %s", highHash)
|
||||
err := flow.syncHeaders(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Finished downloading headers up to %s", highHash)
|
||||
|
||||
// Fetch the UTXO set if we don't already have it
|
||||
log.Debugf("Downloading the IBD root UTXO set under highHash %s", highHash)
|
||||
syncInfo, err := flow.Domain().Consensus().GetSyncInfo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if syncInfo.State == externalapi.SyncStateAwaitingUTXOSet {
|
||||
found, err := flow.fetchMissingUTXOSet(syncInfo.IBDRootUTXOBlockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !found {
|
||||
log.Infof("Cannot download the IBD root UTXO set under highHash %s", highHash)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
log.Debugf("Finished downloading the IBD root UTXO set under highHash %s", highHash)
|
||||
|
||||
// Fetch the block bodies
|
||||
log.Debugf("Downloading block bodies up to %s", highHash)
|
||||
err = flow.syncMissingBlockBodies(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Finished downloading block bodies up to %s", highHash)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncHeaders(highHash *externalapi.DomainHash) error {
|
||||
log.Debugf("Trying to find highest shared chain block with peer %s with high hash %s", flow.peer, highHash)
|
||||
highestSharedBlockHash, err := flow.findHighestSharedBlockHash(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Found highest shared chain block %s with peer %s", highestSharedBlockHash, flow.peer)
|
||||
|
||||
return flow.downloadHeaders(highestSharedBlockHash, highHash)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) findHighestSharedBlockHash(highHash *externalapi.DomainHash) (
|
||||
lowHash *externalapi.DomainHash, err error) {
|
||||
|
||||
lowHash = flow.Config().ActiveNetParams.GenesisHash
|
||||
currentHighHash := highHash
|
||||
|
||||
for {
|
||||
err := flow.sendGetBlockLocator(lowHash, currentHighHash, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockLocatorHashes, err := flow.receiveBlockLocator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We check whether the locator's highest hash is in the local DAG.
|
||||
// If it is, return it. If it isn't, we need to narrow our
|
||||
// getBlockLocator request and try again.
|
||||
locatorHighHash := blockLocatorHashes[0]
|
||||
locatorHighHashInfo, err := flow.Domain().Consensus().GetBlockInfo(locatorHighHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if locatorHighHashInfo.Exists {
|
||||
return locatorHighHash, nil
|
||||
}
|
||||
|
||||
lowHash, currentHighHash, err = flow.Domain().Consensus().FindNextBlockLocatorBoundaries(blockLocatorHashes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) downloadHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
highHash *externalapi.DomainHash) error {
|
||||
|
||||
err := flow.sendRequestHeaders(highestSharedBlockHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocksReceived := 0
|
||||
for {
|
||||
msgBlockHeader, doneIBD, err := flow.receiveHeader()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if doneIBD {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = flow.processHeader(msgBlockHeader)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocksReceived++
|
||||
if blocksReceived%ibdBatchSize == 0 {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestNextHeaders())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) sendRequestHeaders(highestSharedBlockHash *externalapi.DomainHash,
|
||||
peerSelectedTipHash *externalapi.DomainHash) error {
|
||||
|
||||
msgGetBlockInvs := appmessage.NewMsgRequstHeaders(highestSharedBlockHash, peerSelectedTipHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveHeader() (msgIBDBlock *appmessage.MsgBlockHeader, doneIBD bool, err error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgBlockHeader:
|
||||
return message, false, nil
|
||||
case *appmessage.MsgDoneHeaders:
|
||||
return nil, true, nil
|
||||
default:
|
||||
return nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s", appmessage.CmdHeader, appmessage.CmdDoneHeaders, message.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) processHeader(msgBlockHeader *appmessage.MsgBlockHeader) error {
|
||||
header := appmessage.BlockHeaderToDomainBlockHeader(msgBlockHeader)
|
||||
block := &externalapi.DomainBlock{
|
||||
Header: header,
|
||||
Transactions: nil,
|
||||
}
|
||||
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(blockHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if blockInfo.Exists {
|
||||
log.Debugf("Block header %s is already in the DAG. Skipping...", blockHash)
|
||||
return nil
|
||||
}
|
||||
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process header %s during IBD", blockHash)
|
||||
}
|
||||
log.Infof("Rejected block header %s from %s during IBD: %s", blockHash, flow.peer, err)
|
||||
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s during IBD", blockHash)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) fetchMissingUTXOSet(ibdRootHash *externalapi.DomainHash) (bool, error) {
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDRootUTXOSetAndBlock(ibdRootHash))
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
utxoSet, block, found, err := flow.receiveIBDRootUTXOSetAndBlock()
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if !found {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "got invalid block %s during IBD", blockHash)
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().SetPruningPointUTXOSet(utxoSet)
|
||||
if err != nil {
|
||||
return false, protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "error with IBD root UTXO set")
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) receiveIBDRootUTXOSetAndBlock() ([]byte, *externalapi.DomainBlock, bool, error) {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, nil, false, err
|
||||
}
|
||||
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgIBDRootUTXOSetAndBlock:
|
||||
return message.UTXOSet,
|
||||
appmessage.MsgBlockToDomainBlock(message.Block), true, nil
|
||||
case *appmessage.MsgIBDRootNotFound:
|
||||
return nil, nil, false, nil
|
||||
default:
|
||||
return nil, nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s or %s, got: %s",
|
||||
appmessage.CmdIBDRootUTXOSetAndBlock, appmessage.CmdIBDRootNotFound, message.Command(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayInvsFlow) syncMissingBlockBodies(highHash *externalapi.DomainHash) error {
|
||||
hashes, err := flow.Domain().Consensus().GetMissingBlockBodyHashes(highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(hashes); offset += ibdBatchSize {
|
||||
var hashesToRequest []*externalapi.DomainHash
|
||||
if offset+ibdBatchSize < len(hashes) {
|
||||
hashesToRequest = hashes[offset : offset+ibdBatchSize]
|
||||
} else {
|
||||
hashesToRequest = hashes[offset:]
|
||||
}
|
||||
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestIBDBlocks(hashesToRequest))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, expectedHash := range hashesToRequest {
|
||||
message, err := flow.dequeueIncomingMessageAndSkipInvs(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgIBDBlock, ok := message.(*appmessage.MsgIBDBlock)
|
||||
if !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
||||
}
|
||||
|
||||
block := appmessage.MsgBlockToDomainBlock(msgIBDBlock.MsgBlock)
|
||||
blockHash := consensushashing.BlockHash(block)
|
||||
if *expectedHash != *blockHash {
|
||||
return protocolerrors.Errorf(true, "expected block %s but got %s", expectedHash, blockHash)
|
||||
}
|
||||
|
||||
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
|
||||
if err != nil {
|
||||
return protocolerrors.ConvertToBanningProtocolErrorIfRuleError(err, "invalid block %s", blockHash)
|
||||
}
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dequeueIncomingMessageAndSkipInvs is a convenience method to be used during
|
||||
// IBD. Inv messages are expected to arrive at any given moment, but should be
|
||||
// ignored while we're in IBD
|
||||
func (flow *handleRelayInvsFlow) dequeueIncomingMessageAndSkipInvs(timeout time.Duration) (appmessage.Message, error) {
|
||||
for {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, ok := message.(*appmessage.MsgInvRelayBlock); !ok {
|
||||
return message, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,23 +3,23 @@ package blockrelay
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// SharedRequestedBlocks is a data structure that is shared between peers that
|
||||
// holds the hashes of all the requested blocks to prevent redundant requests.
|
||||
type SharedRequestedBlocks struct {
|
||||
blocks map[daghash.Hash]struct{}
|
||||
blocks map[externalapi.DomainHash]struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) remove(hash *daghash.Hash) {
|
||||
func (s *SharedRequestedBlocks) remove(hash *externalapi.DomainHash) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.blocks, *hash)
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) removeSet(blockHashes map[daghash.Hash]struct{}) {
|
||||
func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash]struct{}) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for hash := range blockHashes {
|
||||
@@ -27,7 +27,7 @@ func (s *SharedRequestedBlocks) removeSet(blockHashes map[daghash.Hash]struct{})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SharedRequestedBlocks) addIfNotExists(hash *daghash.Hash) (exists bool) {
|
||||
func (s *SharedRequestedBlocks) addIfNotExists(hash *externalapi.DomainHash) (exists bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.blocks[*hash]
|
||||
@@ -41,6 +41,6 @@ func (s *SharedRequestedBlocks) addIfNotExists(hash *daghash.Hash) (exists bool)
|
||||
// NewSharedRequestedBlocks returns a new instance of SharedRequestedBlocks.
|
||||
func NewSharedRequestedBlocks() *SharedRequestedBlocks {
|
||||
return &SharedRequestedBlocks{
|
||||
blocks: make(map[daghash.Hash]struct{}),
|
||||
blocks: make(map[externalapi.DomainHash]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,20 @@
|
||||
package handshake
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/locks"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -23,9 +22,8 @@ import (
|
||||
type HandleHandshakeContext interface {
|
||||
Config() *config.Config
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
DAG() *blockdag.BlockDAG
|
||||
Domain() domain.Domain
|
||||
AddressManager() *addressmanager.AddressManager
|
||||
StartIBDIfRequired()
|
||||
AddToPeers(peer *peerpkg.Peer) error
|
||||
HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error)
|
||||
}
|
||||
@@ -37,10 +35,12 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
) (*peerpkg.Peer, error) {
|
||||
|
||||
// For HandleHandshake to finish, we need to get from the other node
|
||||
// a version and verack messages, so we increase the wait group by 2
|
||||
// and block HandleHandshake with wg.Wait().
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
// a version and verack messages, so we set doneCount to 2, decrease it
|
||||
// when sending and receiving the version, and close the doneChan when
|
||||
// it's 0. Then we wait for on select for a tick from doneChan or from
|
||||
// errChan.
|
||||
doneCount := int32(2)
|
||||
doneChan := make(chan struct{})
|
||||
|
||||
isStopping := uint32(0)
|
||||
errChan := make(chan error)
|
||||
@@ -55,7 +55,9 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
return
|
||||
}
|
||||
peerAddress = address
|
||||
wg.Done()
|
||||
if atomic.AddInt32(&doneCount, -1) == 0 {
|
||||
close(doneChan)
|
||||
}
|
||||
})
|
||||
|
||||
spawn("HandleHandshake-SendVersion", func() {
|
||||
@@ -64,7 +66,9 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
handleError(err, "SendVersion", &isStopping, errChan)
|
||||
return
|
||||
}
|
||||
wg.Done()
|
||||
if atomic.AddInt32(&doneCount, -1) == 0 {
|
||||
close(doneChan)
|
||||
}
|
||||
})
|
||||
|
||||
select {
|
||||
@@ -73,7 +77,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
return nil, err
|
||||
}
|
||||
return nil, nil
|
||||
case <-locks.ReceiveFromChanWhenDone(func() { wg.Wait() }):
|
||||
case <-doneChan:
|
||||
}
|
||||
|
||||
err := context.AddToPeers(peer)
|
||||
@@ -85,13 +89,8 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
|
||||
}
|
||||
|
||||
if peerAddress != nil {
|
||||
subnetworkID := peer.SubnetworkID()
|
||||
context.AddressManager().AddAddress(peerAddress, peerAddress, subnetworkID)
|
||||
context.AddressManager().Good(peerAddress, subnetworkID)
|
||||
context.AddressManager().AddAddresses(peerAddress)
|
||||
}
|
||||
|
||||
context.StartIBDIfRequired()
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
)
|
||||
|
||||
@@ -41,11 +42,16 @@ func ReceiveVersion(context HandleHandshakeContext, incomingRoute *router.Route,
|
||||
}
|
||||
|
||||
func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "receiveVersionFlow.start()")
|
||||
defer onEnd()
|
||||
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.Debugf("Got version message")
|
||||
|
||||
msgVersion, ok := message.(*appmessage.MsgVersion)
|
||||
if !ok {
|
||||
return nil, protocolerrors.New(true, "a version message must precede all others")
|
||||
@@ -72,7 +78,7 @@ func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
||||
}
|
||||
|
||||
// Disconnect from partial nodes in networks that don't allow them
|
||||
if !flow.DAG().Params.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
|
||||
if !flow.Config().ActiveNetParams.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
|
||||
return nil, protocolerrors.New(true, "partial nodes are not allowed")
|
||||
}
|
||||
|
||||
@@ -84,7 +90,7 @@ func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
|
||||
isRemoteNodeFull := msgVersion.SubnetworkID == nil
|
||||
isOutbound := flow.peer.Connection().IsOutbound()
|
||||
if (isLocalNodeFull && !isRemoteNodeFull && isOutbound) ||
|
||||
(!isLocalNodeFull && !isRemoteNodeFull && !msgVersion.SubnetworkID.IsEqual(localSubnetworkID)) {
|
||||
(!isLocalNodeFull && !isRemoteNodeFull && *msgVersion.SubnetworkID != *localSubnetworkID) {
|
||||
|
||||
return nil, protocolerrors.New(false, "incompatible subnetworks")
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
)
|
||||
@@ -28,6 +29,7 @@ var (
|
||||
|
||||
type sendVersionFlow struct {
|
||||
HandleHandshakeContext
|
||||
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
}
|
||||
@@ -46,13 +48,14 @@ func SendVersion(context HandleHandshakeContext, incomingRoute *router.Route,
|
||||
}
|
||||
|
||||
func (flow *sendVersionFlow) start() error {
|
||||
selectedTipHash := flow.DAG().SelectedTipHash()
|
||||
subnetworkID := flow.Config().SubnetworkID
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "sendVersionFlow.start")
|
||||
defer onEnd()
|
||||
|
||||
// Version message.
|
||||
localAddress := flow.AddressManager().GetBestLocalAddress(flow.peer.Connection().NetAddress())
|
||||
localAddress := flow.AddressManager().BestLocalAddress(flow.peer.Connection().NetAddress())
|
||||
subnetworkID := flow.Config().SubnetworkID
|
||||
msg := appmessage.NewMsgVersion(localAddress, flow.NetAdapter().ID(),
|
||||
flow.Config().ActiveNetParams.Name, selectedTipHash, subnetworkID)
|
||||
flow.Config().ActiveNetParams.Name, subnetworkID)
|
||||
msg.AddUserAgent(userAgentName, userAgentVersion, flow.Config().UserAgentComments...)
|
||||
|
||||
// Advertise the services flag
|
||||
@@ -70,9 +73,11 @@ func (flow *sendVersionFlow) start() error {
|
||||
}
|
||||
|
||||
// Wait for verack
|
||||
log.Debugf("Waiting for verack")
|
||||
_, err = flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
log.Debugf("Got verack")
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,126 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
const ibdBatchSize = router.DefaultMaxMessages
|
||||
|
||||
// RequestIBDBlocksContext is the interface for the context needed for the HandleRequestIBDBlocks flow.
|
||||
type RequestIBDBlocksContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
}
|
||||
|
||||
type handleRequestBlocksFlow struct {
|
||||
RequestIBDBlocksContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestIBDBlocks handles getBlocks messages
|
||||
func HandleRequestIBDBlocks(context RequestIBDBlocksContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestBlocksFlow{
|
||||
RequestIBDBlocksContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) start() error {
|
||||
for {
|
||||
lowHash, highHash, err := receiveRequestIBDBlocks(flow.incomingRoute)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
msgIBDBlocks, err := flow.buildMsgIBDBlocks(lowHash, highHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for offset := 0; offset < len(msgIBDBlocks); offset += ibdBatchSize {
|
||||
end := offset + ibdBatchSize
|
||||
if end > len(msgIBDBlocks) {
|
||||
end = len(msgIBDBlocks)
|
||||
}
|
||||
|
||||
blocksToSend := msgIBDBlocks[offset:end]
|
||||
err = flow.sendMsgIBDBlocks(blocksToSend)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Exit the loop and don't wait for the GetNextIBDBlocks message if the last batch was
|
||||
// less than ibdBatchSize.
|
||||
if len(blocksToSend) < ibdBatchSize {
|
||||
break
|
||||
}
|
||||
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, ok := message.(*appmessage.MsgRequestNextIBDBlocks); !ok {
|
||||
return protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestNextIBDBlocks, message.Command())
|
||||
}
|
||||
}
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgDoneIBDBlocks())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func receiveRequestIBDBlocks(incomingRoute *router.Route) (lowHash *daghash.Hash,
|
||||
highHash *daghash.Hash, err error) {
|
||||
|
||||
message, err := incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
msgRequestIBDBlocks := message.(*appmessage.MsgRequestIBDBlocks)
|
||||
|
||||
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) buildMsgIBDBlocks(lowHash *daghash.Hash,
|
||||
highHash *daghash.Hash) ([]*appmessage.MsgIBDBlock, error) {
|
||||
|
||||
const maxHashesInMsgIBDBlocks = appmessage.MaxInvPerMsg
|
||||
blockHashes, err := flow.DAG().AntiPastHashesBetween(lowHash, highHash, maxHashesInMsgIBDBlocks)
|
||||
if err != nil {
|
||||
if errors.Is(err, blockdag.ErrInvalidParameter) {
|
||||
return nil, protocolerrors.Wrapf(true, err, "could not get antiPast between "+
|
||||
"%s and %s", lowHash, highHash)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
msgIBDBlocks := make([]*appmessage.MsgIBDBlock, len(blockHashes))
|
||||
for i, blockHash := range blockHashes {
|
||||
block, err := flow.DAG().BlockByHash(blockHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgIBDBlocks[i] = appmessage.NewMsgIBDBlock(block.MsgBlock())
|
||||
}
|
||||
|
||||
return msgIBDBlocks, nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestBlocksFlow) sendMsgIBDBlocks(msgIBDBlocks []*appmessage.MsgIBDBlock) error {
|
||||
for _, msgIBDBlock := range msgIBDBlocks {
|
||||
err := flow.outgoingRoute.Enqueue(msgIBDBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,212 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/blocklogger"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleIBDContext is the interface for the context needed for the HandleIBD flow.
|
||||
type HandleIBDContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
OnNewBlock(block *util.Block) error
|
||||
StartIBDIfRequired()
|
||||
FinishIBD()
|
||||
}
|
||||
|
||||
type handleIBDFlow struct {
|
||||
HandleIBDContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
}
|
||||
|
||||
// HandleIBD waits for IBD start and handles it when IBD is triggered for this peer
|
||||
func HandleIBD(context HandleIBDContext, incomingRoute *router.Route, outgoingRoute *router.Route,
|
||||
peer *peerpkg.Peer) error {
|
||||
|
||||
flow := &handleIBDFlow{
|
||||
HandleIBDContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
peer: peer,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) start() error {
|
||||
for {
|
||||
err := flow.runIBD()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) runIBD() error {
|
||||
flow.peer.WaitForIBDStart()
|
||||
defer flow.FinishIBD()
|
||||
|
||||
peerSelectedTipHash := flow.peer.SelectedTipHash()
|
||||
log.Debugf("Trying to find highest shared chain block with peer %s with selected tip %s", flow.peer, peerSelectedTipHash)
|
||||
highestSharedBlockHash, err := flow.findHighestSharedBlockHash(peerSelectedTipHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Debugf("Found highest shared chain block %s with peer %s", highestSharedBlockHash, flow.peer)
|
||||
if flow.DAG().IsKnownFinalizedBlock(highestSharedBlockHash) {
|
||||
return protocolerrors.Errorf(false, "cannot initiate "+
|
||||
"IBD with peer %s because the highest shared chain block (%s) is "+
|
||||
"below the finality point", flow.peer, highestSharedBlockHash)
|
||||
}
|
||||
|
||||
return flow.downloadBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *daghash.Hash) (lowHash *daghash.Hash,
|
||||
err error) {
|
||||
|
||||
lowHash = flow.DAG().Params.GenesisHash
|
||||
highHash := peerSelectedTipHash
|
||||
|
||||
for {
|
||||
err := flow.sendGetBlockLocator(lowHash, highHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockLocatorHashes, err := flow.receiveBlockLocator()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// We check whether the locator's highest hash is in the local DAG.
|
||||
// If it is, return it. If it isn't, we need to narrow our
|
||||
// getBlockLocator request and try again.
|
||||
locatorHighHash := blockLocatorHashes[0]
|
||||
if flow.DAG().IsInDAG(locatorHighHash) {
|
||||
return locatorHighHash, nil
|
||||
}
|
||||
|
||||
highHash, lowHash = flow.DAG().FindNextLocatorBoundaries(blockLocatorHashes)
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) sendGetBlockLocator(lowHash *daghash.Hash, highHash *daghash.Hash) error {
|
||||
|
||||
msgGetBlockLocator := appmessage.NewMsgRequestBlockLocator(highHash, lowHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockLocator)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*daghash.Hash, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgBlockLocator, ok := message.(*appmessage.MsgBlockLocator)
|
||||
if !ok {
|
||||
return nil,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdBlockLocator, message.Command())
|
||||
}
|
||||
return msgBlockLocator.BlockLocatorHashes, nil
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) downloadBlocks(highestSharedBlockHash *daghash.Hash,
|
||||
peerSelectedTipHash *daghash.Hash) error {
|
||||
|
||||
err := flow.sendGetBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocksReceived := 0
|
||||
for {
|
||||
msgIBDBlock, doneIBD, err := flow.receiveIBDBlock()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if doneIBD {
|
||||
return nil
|
||||
}
|
||||
|
||||
err = flow.processIBDBlock(msgIBDBlock)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
blocksReceived++
|
||||
if blocksReceived%ibdBatchSize == 0 {
|
||||
err = flow.outgoingRoute.Enqueue(appmessage.NewMsgRequestNextIBDBlocks())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) sendGetBlocks(highestSharedBlockHash *daghash.Hash,
|
||||
peerSelectedTipHash *daghash.Hash) error {
|
||||
|
||||
msgGetBlockInvs := appmessage.NewMsgRequstIBDBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
||||
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) receiveIBDBlock() (msgIBDBlock *appmessage.MsgIBDBlock, doneIBD bool, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
switch message := message.(type) {
|
||||
case *appmessage.MsgIBDBlock:
|
||||
return message, false, nil
|
||||
case *appmessage.MsgDoneIBDBlocks:
|
||||
return nil, true, nil
|
||||
default:
|
||||
return nil, false,
|
||||
protocolerrors.Errorf(true, "received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdIBDBlock, message.Command())
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleIBDFlow) processIBDBlock(msgIBDBlock *appmessage.MsgIBDBlock) error {
|
||||
block := util.NewBlock(msgIBDBlock.MsgBlock)
|
||||
if flow.DAG().IsInDAG(block.Hash()) {
|
||||
log.Debugf("IBD block %s is already in the DAG. Skipping...", block.Hash())
|
||||
return nil
|
||||
}
|
||||
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
|
||||
if err != nil {
|
||||
if !errors.As(err, &blockdag.RuleError{}) {
|
||||
return errors.Wrapf(err, "failed to process block %s during IBD", block.Hash())
|
||||
}
|
||||
log.Infof("Rejected block %s from %s during IBD: %s", block.Hash(), flow.peer, err)
|
||||
|
||||
return protocolerrors.Wrapf(true, err, "got invalid block %s during IBD", block.Hash())
|
||||
}
|
||||
if isOrphan {
|
||||
return protocolerrors.Errorf(true, "received orphan block %s "+
|
||||
"during IBD", block.Hash())
|
||||
}
|
||||
if isDelayed {
|
||||
return protocolerrors.Errorf(false, "received delayed block %s "+
|
||||
"during IBD", block.Hash())
|
||||
}
|
||||
err = flow.OnNewBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = blocklogger.LogBlock(block)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package ibd
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
"github.com/kaspanet/kaspad/util/panics"
|
||||
)
|
||||
|
||||
var log, _ = logger.Get(logger.SubsystemTags.IBDS)
|
||||
var spawn = panics.GoroutineWrapperFunc(log)
|
||||
@@ -1,61 +0,0 @@
|
||||
package selectedtip
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// HandleRequestSelectedTipContext is the interface for the context needed for the HandleRequestSelectedTip flow.
|
||||
type HandleRequestSelectedTipContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
}
|
||||
|
||||
type handleRequestSelectedTipFlow struct {
|
||||
HandleRequestSelectedTipContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
}
|
||||
|
||||
// HandleRequestSelectedTip handles getSelectedTip messages
|
||||
func HandleRequestSelectedTip(context HandleRequestSelectedTipContext, incomingRoute *router.Route, outgoingRoute *router.Route) error {
|
||||
flow := &handleRequestSelectedTipFlow{
|
||||
HandleRequestSelectedTipContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *handleRequestSelectedTipFlow) start() error {
|
||||
for {
|
||||
err := flow.receiveGetSelectedTip()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = flow.sendSelectedTipHash()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRequestSelectedTipFlow) receiveGetSelectedTip() error {
|
||||
message, err := flow.incomingRoute.Dequeue()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, ok := message.(*appmessage.MsgRequestSelectedTip)
|
||||
if !ok {
|
||||
return errors.Errorf("received unexpected message type. "+
|
||||
"expected: %s, got: %s", appmessage.CmdRequestSelectedTip, message.Command())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *handleRequestSelectedTipFlow) sendSelectedTipHash() error {
|
||||
msgSelectedTip := appmessage.NewMsgSelectedTip(flow.DAG().SelectedTipHash())
|
||||
return flow.outgoingRoute.Enqueue(msgSelectedTip)
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
package selectedtip
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// RequestSelectedTipContext is the interface for the context needed for the RequestSelectedTip flow.
|
||||
type RequestSelectedTipContext interface {
|
||||
DAG() *blockdag.BlockDAG
|
||||
StartIBDIfRequired()
|
||||
}
|
||||
|
||||
type requestSelectedTipFlow struct {
|
||||
RequestSelectedTipContext
|
||||
incomingRoute, outgoingRoute *router.Route
|
||||
peer *peerpkg.Peer
|
||||
}
|
||||
|
||||
// RequestSelectedTip waits for selected tip requests and handles them
|
||||
func RequestSelectedTip(context RequestSelectedTipContext, incomingRoute *router.Route,
|
||||
outgoingRoute *router.Route, peer *peerpkg.Peer) error {
|
||||
|
||||
flow := &requestSelectedTipFlow{
|
||||
RequestSelectedTipContext: context,
|
||||
incomingRoute: incomingRoute,
|
||||
outgoingRoute: outgoingRoute,
|
||||
peer: peer,
|
||||
}
|
||||
return flow.start()
|
||||
}
|
||||
|
||||
func (flow *requestSelectedTipFlow) start() error {
|
||||
for {
|
||||
err := flow.runSelectedTipRequest()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *requestSelectedTipFlow) runSelectedTipRequest() error {
|
||||
|
||||
flow.peer.WaitForSelectedTipRequests()
|
||||
defer flow.peer.FinishRequestingSelectedTip()
|
||||
|
||||
err := flow.requestSelectedTip()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
peerSelectedTipHash, err := flow.receiveSelectedTip()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
flow.peer.SetSelectedTipHash(peerSelectedTipHash)
|
||||
flow.StartIBDIfRequired()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (flow *requestSelectedTipFlow) requestSelectedTip() error {
|
||||
msgGetSelectedTip := appmessage.NewMsgRequestSelectedTip()
|
||||
return flow.outgoingRoute.Enqueue(msgGetSelectedTip)
|
||||
}
|
||||
|
||||
func (flow *requestSelectedTipFlow) receiveSelectedTip() (selectedTipHash *daghash.Hash, err error) {
|
||||
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
msgSelectedTip := message.(*appmessage.MsgSelectedTip)
|
||||
|
||||
return msgSelectedTip.SelectedTipHash, nil
|
||||
}
|
||||
@@ -1,15 +1,15 @@
|
||||
package relaytransactions
|
||||
package transactionrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol/common"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -17,9 +17,8 @@ import (
|
||||
// HandleRelayedTransactions and HandleRequestedTransactions flows.
|
||||
type TransactionsRelayContext interface {
|
||||
NetAdapter() *netadapter.NetAdapter
|
||||
DAG() *blockdag.BlockDAG
|
||||
Domain() domain.Domain
|
||||
SharedRequestedTransactions() *SharedRequestedTransactions
|
||||
TxPool() *mempool.TxPool
|
||||
Broadcast(message appmessage.Message) error
|
||||
OnTransactionAddedToMempool()
|
||||
}
|
||||
@@ -62,9 +61,9 @@ func (flow *handleRelayedTransactionsFlow) start() error {
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
||||
inv *appmessage.MsgInvTransaction) (requestedIDs []*daghash.TxID, err error) {
|
||||
inv *appmessage.MsgInvTransaction) (requestedIDs []*externalapi.DomainTransactionID, err error) {
|
||||
|
||||
idsToRequest := make([]*daghash.TxID, 0, len(inv.TxIDs))
|
||||
idsToRequest := make([]*externalapi.DomainTransactionID, 0, len(inv.TxIDs))
|
||||
for _, txID := range inv.TxIDs {
|
||||
if flow.isKnownTransaction(txID) {
|
||||
continue
|
||||
@@ -89,29 +88,13 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
|
||||
return idsToRequest, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *daghash.TxID) bool {
|
||||
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool {
|
||||
// Ask the transaction memory pool if the transaction is known
|
||||
// to it in any form (main pool or orphan).
|
||||
if flow.TxPool().HaveTransaction(txID) {
|
||||
if _, ok := flow.Domain().MiningManager().GetTransaction(txID); ok {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if the transaction exists from the point of view of the
|
||||
// DAG's virtual block. Note that this is only a best effort
|
||||
// since it is expensive to check existence of every output and
|
||||
// the only purpose of this check is to avoid downloading
|
||||
// already known transactions. Only the first two outputs are
|
||||
// checked because the vast majority of transactions consist of
|
||||
// two outputs where one is some form of "pay-to-somebody-else"
|
||||
// and the other is a change output.
|
||||
prevOut := appmessage.Outpoint{TxID: *txID}
|
||||
for i := uint32(0); i < 2; i++ {
|
||||
prevOut.Index = i
|
||||
_, ok := flow.DAG().GetUTXOEntry(prevOut)
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -135,12 +118,8 @@ func (flow *handleRelayedTransactionsFlow) readInv() (*appmessage.MsgInvTransact
|
||||
return inv, nil
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) broadcastAcceptedTransactions(acceptedTxs []*mempool.TxDesc) error {
|
||||
idsToBroadcast := make([]*daghash.TxID, len(acceptedTxs))
|
||||
for i, tx := range acceptedTxs {
|
||||
idsToBroadcast[i] = tx.Tx.ID()
|
||||
}
|
||||
inv := appmessage.NewMsgInvTransaction(idsToBroadcast)
|
||||
func (flow *handleRelayedTransactionsFlow) broadcastAcceptedTransactions(acceptedTxIDs []*externalapi.DomainTransactionID) error {
|
||||
inv := appmessage.NewMsgInvTransaction(acceptedTxIDs)
|
||||
return flow.Broadcast(inv)
|
||||
}
|
||||
|
||||
@@ -170,7 +149,7 @@ func (flow *handleRelayedTransactionsFlow) readMsgTxOrNotFound() (
|
||||
}
|
||||
}
|
||||
|
||||
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*daghash.TxID) error {
|
||||
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*externalapi.DomainTransactionID) error {
|
||||
// In case the function returns earlier than expected, we want to make sure sharedRequestedTransactions is
|
||||
// clean from any pending transactions.
|
||||
defer flow.SharedRequestedTransactions().removeMany(requestedTransactions)
|
||||
@@ -180,42 +159,41 @@ func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransact
|
||||
return err
|
||||
}
|
||||
if msgTxNotFound != nil {
|
||||
if !msgTxNotFound.ID.IsEqual(expectedID) {
|
||||
if msgTxNotFound.ID != expectedID {
|
||||
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
|
||||
expectedID, msgTxNotFound.ID)
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
tx := util.NewTx(msgTx)
|
||||
if !tx.ID().IsEqual(expectedID) {
|
||||
tx := appmessage.MsgTxToDomainTransaction(msgTx)
|
||||
txID := consensushashing.TransactionID(tx)
|
||||
if txID != expectedID {
|
||||
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
|
||||
expectedID, tx.ID())
|
||||
expectedID, txID)
|
||||
}
|
||||
|
||||
acceptedTxs, err := flow.TxPool().ProcessTransaction(tx, true)
|
||||
err = flow.Domain().MiningManager().ValidateAndInsertTransaction(tx, true)
|
||||
if err != nil {
|
||||
ruleErr := &mempool.RuleError{}
|
||||
if !errors.As(err, ruleErr) {
|
||||
return errors.Wrapf(err, "failed to process transaction %s", tx.ID())
|
||||
return errors.Wrapf(err, "failed to process transaction %s", txID)
|
||||
}
|
||||
|
||||
shouldBan := false
|
||||
shouldBan := true
|
||||
if txRuleErr := (&mempool.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) {
|
||||
if txRuleErr.RejectCode == mempool.RejectInvalid {
|
||||
shouldBan = true
|
||||
if txRuleErr.RejectCode != mempool.RejectInvalid {
|
||||
shouldBan = false
|
||||
}
|
||||
} else if dagRuleErr := (&blockdag.RuleError{}); errors.As(ruleErr.Err, dagRuleErr) {
|
||||
shouldBan = true
|
||||
}
|
||||
|
||||
if !shouldBan {
|
||||
continue
|
||||
}
|
||||
|
||||
return protocolerrors.Errorf(true, "rejected transaction %s", tx.ID())
|
||||
return protocolerrors.Errorf(true, "rejected transaction %s", txID)
|
||||
}
|
||||
err = flow.broadcastAcceptedTransactions(acceptedTxs)
|
||||
err = flow.broadcastAcceptedTransactions([]*externalapi.DomainTransactionID{txID})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package relaytransactions
|
||||
package transactionrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
@@ -30,7 +30,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
|
||||
}
|
||||
|
||||
for _, transactionID := range msgRequestTransactions.IDs {
|
||||
tx, ok := flow.TxPool().FetchTransaction(transactionID)
|
||||
tx, ok := flow.Domain().MiningManager().GetTransaction(transactionID)
|
||||
|
||||
if !ok {
|
||||
msgTransactionNotFound := appmessage.NewMsgTransactionNotFound(transactionID)
|
||||
@@ -41,7 +41,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
|
||||
continue
|
||||
}
|
||||
|
||||
err := flow.outgoingRoute.Enqueue(tx.MsgTx())
|
||||
err := flow.outgoingRoute.Enqueue(appmessage.DomainTransactionToMsgTx(tx))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1,24 +1,25 @@
|
||||
package relaytransactions
|
||||
package transactionrelay
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
// SharedRequestedTransactions is a data structure that is shared between peers that
|
||||
// holds the IDs of all the requested transactions to prevent redundant requests.
|
||||
type SharedRequestedTransactions struct {
|
||||
transactions map[daghash.TxID]struct{}
|
||||
transactions map[externalapi.DomainTransactionID]struct{}
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) remove(txID *daghash.TxID) {
|
||||
func (s *SharedRequestedTransactions) remove(txID *externalapi.DomainTransactionID) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
delete(s.transactions, *txID)
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) removeMany(txIDs []*daghash.TxID) {
|
||||
func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTransactionID) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
for _, txID := range txIDs {
|
||||
@@ -26,7 +27,7 @@ func (s *SharedRequestedTransactions) removeMany(txIDs []*daghash.TxID) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SharedRequestedTransactions) addIfNotExists(txID *daghash.TxID) (exists bool) {
|
||||
func (s *SharedRequestedTransactions) addIfNotExists(txID *externalapi.DomainTransactionID) (exists bool) {
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
_, ok := s.transactions[*txID]
|
||||
@@ -40,6 +41,6 @@ func (s *SharedRequestedTransactions) addIfNotExists(txID *daghash.TxID) (exists
|
||||
// NewSharedRequestedTransactions returns a new instance of SharedRequestedTransactions.
|
||||
func NewSharedRequestedTransactions() *SharedRequestedTransactions {
|
||||
return &SharedRequestedTransactions{
|
||||
transactions: make(map[daghash.TxID]struct{}),
|
||||
transactions: make(map[externalapi.DomainTransactionID]struct{}),
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,16 @@ package protocol
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/protocol/flowcontext"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/domain/blockdag"
|
||||
"github.com/kaspanet/kaspad/domain/mempool"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
)
|
||||
|
||||
// Manager manages the p2p protocol
|
||||
@@ -20,13 +21,13 @@ type Manager struct {
|
||||
}
|
||||
|
||||
// NewManager creates a new instance of the p2p protocol manager
|
||||
func NewManager(cfg *config.Config, dag *blockdag.BlockDAG, netAdapter *netadapter.NetAdapter,
|
||||
addressManager *addressmanager.AddressManager, txPool *mempool.TxPool,
|
||||
func NewManager(cfg *config.Config, domain domain.Domain, netAdapter *netadapter.NetAdapter, addressManager *addressmanager.AddressManager,
|
||||
connectionManager *connmanager.ConnectionManager) (*Manager, error) {
|
||||
|
||||
manager := Manager{
|
||||
context: flowcontext.New(cfg, dag, addressManager, txPool, netAdapter, connectionManager),
|
||||
context: flowcontext.New(cfg, domain, addressManager, netAdapter, connectionManager),
|
||||
}
|
||||
|
||||
netAdapter.SetP2PRouterInitializer(manager.routerInitializer)
|
||||
return &manager, nil
|
||||
}
|
||||
@@ -36,20 +37,14 @@ func (m *Manager) Peers() []*peerpkg.Peer {
|
||||
return m.context.Peers()
|
||||
}
|
||||
|
||||
// IBDPeer returns the currently active IBD peer.
|
||||
// Returns nil if we aren't currently in IBD
|
||||
func (m *Manager) IBDPeer() *peerpkg.Peer {
|
||||
return m.context.IBDPeer()
|
||||
}
|
||||
|
||||
// AddTransaction adds transaction to the mempool and propagates it.
|
||||
func (m *Manager) AddTransaction(tx *util.Tx) error {
|
||||
func (m *Manager) AddTransaction(tx *externalapi.DomainTransaction) error {
|
||||
return m.context.AddTransaction(tx)
|
||||
}
|
||||
|
||||
// AddBlock adds the given block to the DAG and propagates it.
|
||||
func (m *Manager) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
|
||||
return m.context.AddBlock(block, flags)
|
||||
func (m *Manager) AddBlock(block *externalapi.DomainBlock) error {
|
||||
return m.context.AddBlock(block)
|
||||
}
|
||||
|
||||
func (m *Manager) runFlows(flows []*flow, peer *peerpkg.Peer, errChan <-chan error) error {
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
package peer
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
mathUtil "github.com/kaspanet/kaspad/util/math"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/subnetworkid"
|
||||
)
|
||||
|
||||
// maxProtocolVersion version is the maximum supported protocol
|
||||
// version this kaspad node supports
|
||||
const maxProtocolVersion = 1
|
||||
|
||||
// Peer holds data about a peer.
|
||||
type Peer struct {
|
||||
connection *netadapter.NetConnection
|
||||
|
||||
selectedTipHashMtx sync.RWMutex
|
||||
selectedTipHash *daghash.Hash
|
||||
|
||||
userAgent string
|
||||
services appmessage.ServiceFlag
|
||||
advertisedProtocolVerion uint32 // protocol version advertised by remote
|
||||
protocolVersion uint32 // negotiated protocol version
|
||||
disableRelayTx bool
|
||||
subnetworkID *subnetworkid.SubnetworkID
|
||||
subnetworkID *externalapi.DomainSubnetworkID
|
||||
|
||||
timeOffset time.Duration
|
||||
connectionStarted time.Time
|
||||
@@ -35,21 +35,13 @@ type Peer struct {
|
||||
lastPingNonce uint64 // The nonce of the last ping we sent
|
||||
lastPingTime time.Time // Time we sent last ping
|
||||
lastPingDuration time.Duration // Time for last ping to return
|
||||
|
||||
isSelectedTipRequested uint32
|
||||
selectedTipRequestChan chan struct{}
|
||||
lastSelectedTipRequest mstime.Time
|
||||
|
||||
ibdStartChan chan struct{}
|
||||
}
|
||||
|
||||
// New returns a new Peer
|
||||
func New(connection *netadapter.NetConnection) *Peer {
|
||||
return &Peer{
|
||||
connection: connection,
|
||||
selectedTipRequestChan: make(chan struct{}),
|
||||
ibdStartChan: make(chan struct{}),
|
||||
connectionStarted: time.Now(),
|
||||
connection: connection,
|
||||
connectionStarted: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,23 +50,9 @@ func (p *Peer) Connection() *netadapter.NetConnection {
|
||||
return p.connection
|
||||
}
|
||||
|
||||
// SelectedTipHash returns the selected tip of the peer.
|
||||
func (p *Peer) SelectedTipHash() *daghash.Hash {
|
||||
p.selectedTipHashMtx.RLock()
|
||||
defer p.selectedTipHashMtx.RUnlock()
|
||||
return p.selectedTipHash
|
||||
}
|
||||
|
||||
// SetSelectedTipHash sets the selected tip of the peer.
|
||||
func (p *Peer) SetSelectedTipHash(hash *daghash.Hash) {
|
||||
p.selectedTipHashMtx.Lock()
|
||||
defer p.selectedTipHashMtx.Unlock()
|
||||
p.selectedTipHash = hash
|
||||
}
|
||||
|
||||
// SubnetworkID returns the subnetwork the peer is associated with.
|
||||
// It is nil in full nodes.
|
||||
func (p *Peer) SubnetworkID() *subnetworkid.SubnetworkID {
|
||||
func (p *Peer) SubnetworkID() *externalapi.DomainSubnetworkID {
|
||||
return p.subnetworkID
|
||||
}
|
||||
|
||||
@@ -112,9 +90,9 @@ func (p *Peer) IsOutbound() bool {
|
||||
func (p *Peer) UpdateFieldsFromMsgVersion(msg *appmessage.MsgVersion) {
|
||||
// Negotiate the protocol version.
|
||||
p.advertisedProtocolVerion = msg.ProtocolVersion
|
||||
p.protocolVersion = mathUtil.MinUint32(p.protocolVersion, p.advertisedProtocolVerion)
|
||||
p.protocolVersion = mathUtil.MinUint32(maxProtocolVersion, p.advertisedProtocolVerion)
|
||||
log.Debugf("Negotiated protocol version %d for peer %s",
|
||||
p.protocolVersion, p.ID())
|
||||
p.protocolVersion, p)
|
||||
|
||||
// Set the supported services for the peer to what the remote peer
|
||||
// advertised.
|
||||
@@ -124,7 +102,6 @@ func (p *Peer) UpdateFieldsFromMsgVersion(msg *appmessage.MsgVersion) {
|
||||
p.userAgent = msg.UserAgent
|
||||
|
||||
p.disableRelayTx = msg.DisableRelayTx
|
||||
p.selectedTipHash = msg.SelectedTipHash
|
||||
p.subnetworkID = msg.SubnetworkID
|
||||
|
||||
p.timeOffset = mstime.Since(msg.Timestamp)
|
||||
@@ -152,46 +129,6 @@ func (p *Peer) String() string {
|
||||
return p.connection.String()
|
||||
}
|
||||
|
||||
// RequestSelectedTipIfRequired notifies the peer that requesting
|
||||
// a selected tip is required. This triggers the selected tip
|
||||
// request flow.
|
||||
func (p *Peer) RequestSelectedTipIfRequired() {
|
||||
if atomic.SwapUint32(&p.isSelectedTipRequested, 1) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
const minGetSelectedTipInterval = time.Minute
|
||||
if mstime.Since(p.lastSelectedTipRequest) < minGetSelectedTipInterval {
|
||||
return
|
||||
}
|
||||
|
||||
p.lastSelectedTipRequest = mstime.Now()
|
||||
p.selectedTipRequestChan <- struct{}{}
|
||||
}
|
||||
|
||||
// WaitForSelectedTipRequests blocks the current thread until
|
||||
// a selected tip is requested from this peer
|
||||
func (p *Peer) WaitForSelectedTipRequests() {
|
||||
<-p.selectedTipRequestChan
|
||||
}
|
||||
|
||||
// FinishRequestingSelectedTip finishes requesting the selected
|
||||
// tip from this peer
|
||||
func (p *Peer) FinishRequestingSelectedTip() {
|
||||
atomic.StoreUint32(&p.isSelectedTipRequested, 0)
|
||||
}
|
||||
|
||||
// StartIBD starts the IBD process for this peer
|
||||
func (p *Peer) StartIBD() {
|
||||
p.ibdStartChan <- struct{}{}
|
||||
}
|
||||
|
||||
// WaitForIBDStart blocks the current thread until
|
||||
// IBD start is requested from this peer
|
||||
func (p *Peer) WaitForIBDStart() {
|
||||
<-p.ibdStartChan
|
||||
}
|
||||
|
||||
// Address returns the address associated with this connection
|
||||
func (p *Peer) Address() string {
|
||||
return p.connection.Address()
|
||||
|
||||
@@ -8,10 +8,8 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/addressexchange"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/blockrelay"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/handshake"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/ibd"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/ibd/selectedtip"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/ping"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
|
||||
"github.com/kaspanet/kaspad/app/protocol/flows/transactionrelay"
|
||||
peerpkg "github.com/kaspanet/kaspad/app/protocol/peer"
|
||||
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
@@ -50,15 +48,9 @@ func (m *Manager) routerInitializer(router *routerpkg.Router, netConnection *net
|
||||
return
|
||||
}
|
||||
|
||||
spawn("Manager.routerInitializer-netConnection.DequeueInvalidMessage", func() {
|
||||
for {
|
||||
isOpen, err := netConnection.DequeueInvalidMessage()
|
||||
if !isOpen {
|
||||
return
|
||||
}
|
||||
if atomic.AddUint32(&isStopping, 1) == 1 {
|
||||
errChan <- protocolerrors.Wrap(true, err, "received bad message")
|
||||
}
|
||||
netConnection.SetOnInvalidMessageHandler(func(err error) {
|
||||
if atomic.AddUint32(&isStopping, 1) == 1 {
|
||||
errChan <- protocolerrors.Wrap(true, err, "received bad message")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -113,7 +105,6 @@ func (m *Manager) registerFlows(router *routerpkg.Router, errChan chan error, is
|
||||
flows = m.registerAddressFlows(router, isStopping, errChan)
|
||||
flows = append(flows, m.registerBlockRelayFlows(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerPingFlows(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerIBDFlows(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerTransactionRelayFlow(router, isStopping, errChan)...)
|
||||
flows = append(flows, m.registerRejectsFlow(router, isStopping, errChan)...)
|
||||
|
||||
@@ -124,7 +115,7 @@ func (m *Manager) registerAddressFlows(router *routerpkg.Router, isStopping *uin
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerOneTimeFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
||||
m.registerFlow("SendAddresses", router, []appmessage.MessageCommand{appmessage.CmdRequestAddresses}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return addressexchange.SendAddresses(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
@@ -142,7 +133,10 @@ func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("HandleRelayInvs", router, []appmessage.MessageCommand{appmessage.CmdInvRelayBlock, appmessage.CmdBlock}, isStopping, errChan,
|
||||
m.registerFlow("HandleRelayInvs", router, []appmessage.MessageCommand{
|
||||
appmessage.CmdInvRelayBlock, appmessage.CmdBlock, appmessage.CmdBlockLocator, appmessage.CmdIBDBlock,
|
||||
appmessage.CmdDoneHeaders, appmessage.CmdIBDRootNotFound, appmessage.CmdIBDRootUTXOSetAndBlock,
|
||||
appmessage.CmdHeader}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRelayInvs(m.context, incomingRoute,
|
||||
outgoingRoute, peer)
|
||||
@@ -154,6 +148,34 @@ func (m *Manager) registerBlockRelayFlows(router *routerpkg.Router, isStopping *
|
||||
return blockrelay.HandleRelayBlockRequests(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestBlockLocator", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestBlockLocator(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestHeaders", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestHeaders, appmessage.CmdRequestNextHeaders}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestHeaders(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestIBDRootUTXOSetAndBlock", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDRootUTXOSetAndBlock}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleRequestIBDRootUTXOSetAndBlock(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleIBDBlockRequests", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return blockrelay.HandleIBDBlockRequests(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,43 +197,6 @@ func (m *Manager) registerPingFlows(router *routerpkg.Router, isStopping *uint32
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerIBDFlows(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
return []*flow{
|
||||
m.registerFlow("HandleIBD", router, []appmessage.MessageCommand{appmessage.CmdBlockLocator, appmessage.CmdIBDBlock,
|
||||
appmessage.CmdDoneIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleIBD(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("RequestSelectedTip", router, []appmessage.MessageCommand{appmessage.CmdSelectedTip}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return selectedtip.RequestSelectedTip(m.context, incomingRoute, outgoingRoute, peer)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestSelectedTip", router, []appmessage.MessageCommand{appmessage.CmdRequestSelectedTip}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return selectedtip.HandleRequestSelectedTip(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestBlockLocator", router, []appmessage.MessageCommand{appmessage.CmdRequestBlockLocator}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleRequestBlockLocator(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
|
||||
m.registerFlow("HandleRequestIBDBlocks", router, []appmessage.MessageCommand{appmessage.CmdRequestIBDBlocks, appmessage.CmdRequestNextIBDBlocks}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return ibd.HandleRequestIBDBlocks(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) registerTransactionRelayFlow(router *routerpkg.Router, isStopping *uint32, errChan chan error) []*flow {
|
||||
outgoingRoute := router.OutgoingRoute()
|
||||
|
||||
@@ -219,13 +204,13 @@ func (m *Manager) registerTransactionRelayFlow(router *routerpkg.Router, isStopp
|
||||
m.registerFlow("HandleRelayedTransactions", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdInvTransaction, appmessage.CmdTx, appmessage.CmdTransactionNotFound}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return relaytransactions.HandleRelayedTransactions(m.context, incomingRoute, outgoingRoute)
|
||||
return transactionrelay.HandleRelayedTransactions(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
m.registerFlow("HandleRequestTransactions", router,
|
||||
[]appmessage.MessageCommand{appmessage.CmdRequestTransactions}, isStopping, errChan,
|
||||
func(incomingRoute *routerpkg.Route, peer *peerpkg.Peer) error {
|
||||
return relaytransactions.HandleRequestedTransactions(m.context, incomingRoute, outgoingRoute)
|
||||
return transactionrelay.HandleRequestedTransactions(m.context, incomingRoute, outgoingRoute)
|
||||
},
|
||||
),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package protocolerrors
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// ProtocolError is an error that signifies a violation
|
||||
// of the peer-to-peer protocol
|
||||
@@ -50,3 +53,14 @@ func Wrapf(shouldBan bool, err error, format string, args ...interface{}) error
|
||||
Cause: errors.Wrapf(err, format, args...),
|
||||
}
|
||||
}
|
||||
|
||||
// ConvertToBanningProtocolErrorIfRuleError converts the given error to
|
||||
// a banning protocol error if it's a rule error, and otherwise keep it
|
||||
// as is.
|
||||
func ConvertToBanningProtocolErrorIfRuleError(err error, format string, args ...interface{}) error {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
return err
|
||||
}
|
||||
|
||||
return Wrapf(true, err, format, args...)
|
||||
}
|
||||
|
||||
@@ -4,16 +4,12 @@ import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
|
||||
"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"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
)
|
||||
|
||||
// Manager is an RPC manager
|
||||
@@ -24,26 +20,22 @@ type Manager struct {
|
||||
// NewManager creates a new RPC Manager
|
||||
func NewManager(
|
||||
cfg *config.Config,
|
||||
domain domain.Domain,
|
||||
netAdapter *netadapter.NetAdapter,
|
||||
dag *blockdag.BlockDAG,
|
||||
protocolManager *protocol.Manager,
|
||||
connectionManager *connmanager.ConnectionManager,
|
||||
blockTemplateGenerator *mining.BlkTmplGenerator,
|
||||
mempool *mempool.TxPool,
|
||||
addressManager *addressmanager.AddressManager,
|
||||
acceptanceIndex *indexers.AcceptanceIndex) *Manager {
|
||||
shutDownChan chan<- struct{}) *Manager {
|
||||
|
||||
manager := Manager{
|
||||
context: rpccontext.NewContext(
|
||||
cfg,
|
||||
domain,
|
||||
netAdapter,
|
||||
dag,
|
||||
protocolManager,
|
||||
connectionManager,
|
||||
blockTemplateGenerator,
|
||||
mempool,
|
||||
addressManager,
|
||||
acceptanceIndex,
|
||||
shutDownChan,
|
||||
),
|
||||
}
|
||||
netAdapter.SetRPCRouterInitializer(manager.routerInitializer)
|
||||
@@ -52,29 +44,19 @@ func NewManager(
|
||||
}
|
||||
|
||||
// NotifyBlockAddedToDAG notifies the manager that a block has been added to the DAG
|
||||
func (m *Manager) NotifyBlockAddedToDAG(block *util.Block) {
|
||||
m.context.BlockTemplateState.NotifyBlockAdded(block)
|
||||
|
||||
notification := appmessage.NewBlockAddedNotificationMessage(block.MsgBlock())
|
||||
m.context.NotificationManager.NotifyBlockAdded(notification)
|
||||
func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock) error {
|
||||
notification := appmessage.NewBlockAddedNotificationMessage(appmessage.DomainBlockToMsgBlock(block))
|
||||
return m.context.NotificationManager.NotifyBlockAdded(notification)
|
||||
}
|
||||
|
||||
// NotifyChainChanged notifies the manager that the DAG's selected parent chain has changed
|
||||
func (m *Manager) NotifyChainChanged(removedChainBlockHashes []*daghash.Hash, addedChainBlockHashes []*daghash.Hash) error {
|
||||
addedChainBlocks, err := m.context.CollectChainBlocks(addedChainBlockHashes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
removedChainBlockHashStrings := make([]string, len(removedChainBlockHashes))
|
||||
for i, removedChainBlockHash := range removedChainBlockHashes {
|
||||
removedChainBlockHashStrings[i] = removedChainBlockHash.String()
|
||||
}
|
||||
notification := appmessage.NewChainChangedNotificationMessage(removedChainBlockHashStrings, addedChainBlocks)
|
||||
m.context.NotificationManager.NotifyChainChanged(notification)
|
||||
return nil
|
||||
// NotifyFinalityConflict notifies the manager that there's a finality conflict in the DAG
|
||||
func (m *Manager) NotifyFinalityConflict(violatingBlockHash string) error {
|
||||
notification := appmessage.NewFinalityConflictNotificationMessage(violatingBlockHash)
|
||||
return m.context.NotificationManager.NotifyFinalityConflict(notification)
|
||||
}
|
||||
|
||||
// NotifyTransactionAddedToMempool notifies the manager that a transaction has been added to the mempool
|
||||
func (m *Manager) NotifyTransactionAddedToMempool() {
|
||||
m.context.BlockTemplateState.NotifyMempoolTx()
|
||||
// NotifyFinalityConflictResolved notifies the manager that a finality conflict in the DAG has been resolved
|
||||
func (m *Manager) NotifyFinalityConflictResolved(finalityBlockHash string) error {
|
||||
notification := appmessage.NewFinalityConflictResolvedNotificationMessage(finalityBlockHash)
|
||||
return m.context.NotificationManager.NotifyFinalityConflictResolved(notification)
|
||||
}
|
||||
|
||||
@@ -12,23 +12,28 @@ import (
|
||||
type handler func(context *rpccontext.Context, router *router.Router, request appmessage.Message) (appmessage.Message, error)
|
||||
|
||||
var handlers = map[appmessage.MessageCommand]handler{
|
||||
appmessage.CmdGetCurrentNetworkRequestMessage: rpchandlers.HandleGetCurrentNetwork,
|
||||
appmessage.CmdSubmitBlockRequestMessage: rpchandlers.HandleSubmitBlock,
|
||||
appmessage.CmdGetBlockTemplateRequestMessage: rpchandlers.HandleGetBlockTemplate,
|
||||
appmessage.CmdNotifyBlockAddedRequestMessage: rpchandlers.HandleNotifyBlockAdded,
|
||||
appmessage.CmdGetPeerAddressesRequestMessage: rpchandlers.HandleGetPeerAddresses,
|
||||
appmessage.CmdGetSelectedTipHashRequestMessage: rpchandlers.HandleGetSelectedTipHash,
|
||||
appmessage.CmdGetMempoolEntryRequestMessage: rpchandlers.HandleGetMempoolEntry,
|
||||
appmessage.CmdGetConnectedPeerInfoRequestMessage: rpchandlers.HandleGetConnectedPeerInfo,
|
||||
appmessage.CmdAddPeerRequestMessage: rpchandlers.HandleAddPeer,
|
||||
appmessage.CmdSubmitTransactionRequestMessage: rpchandlers.HandleSubmitTransaction,
|
||||
appmessage.CmdNotifyChainChangedRequestMessage: rpchandlers.HandleNotifyChainChanged,
|
||||
appmessage.CmdGetBlockRequestMessage: rpchandlers.HandleGetBlock,
|
||||
appmessage.CmdGetSubnetworkRequestMessage: rpchandlers.HandleGetSubnetwork,
|
||||
appmessage.CmdGetChainFromBlockRequestMessage: rpchandlers.HandleGetChainFromBlock,
|
||||
appmessage.CmdGetBlocksRequestMessage: rpchandlers.HandleGetBlocks,
|
||||
appmessage.CmdGetBlockCountRequestMessage: rpchandlers.HandleGetBlockCount,
|
||||
appmessage.CmdGetBlockDAGInfoRequestMessage: rpchandlers.HandleGetBlockDAGInfo,
|
||||
appmessage.CmdGetCurrentNetworkRequestMessage: rpchandlers.HandleGetCurrentNetwork,
|
||||
appmessage.CmdSubmitBlockRequestMessage: rpchandlers.HandleSubmitBlock,
|
||||
appmessage.CmdGetBlockTemplateRequestMessage: rpchandlers.HandleGetBlockTemplate,
|
||||
appmessage.CmdNotifyBlockAddedRequestMessage: rpchandlers.HandleNotifyBlockAdded,
|
||||
appmessage.CmdGetPeerAddressesRequestMessage: rpchandlers.HandleGetPeerAddresses,
|
||||
appmessage.CmdGetSelectedTipHashRequestMessage: rpchandlers.HandleGetSelectedTipHash,
|
||||
appmessage.CmdGetMempoolEntryRequestMessage: rpchandlers.HandleGetMempoolEntry,
|
||||
appmessage.CmdGetConnectedPeerInfoRequestMessage: rpchandlers.HandleGetConnectedPeerInfo,
|
||||
appmessage.CmdAddPeerRequestMessage: rpchandlers.HandleAddPeer,
|
||||
appmessage.CmdSubmitTransactionRequestMessage: rpchandlers.HandleSubmitTransaction,
|
||||
appmessage.CmdNotifyChainChangedRequestMessage: rpchandlers.HandleNotifyChainChanged,
|
||||
appmessage.CmdGetBlockRequestMessage: rpchandlers.HandleGetBlock,
|
||||
appmessage.CmdGetSubnetworkRequestMessage: rpchandlers.HandleGetSubnetwork,
|
||||
appmessage.CmdGetChainFromBlockRequestMessage: rpchandlers.HandleGetChainFromBlock,
|
||||
appmessage.CmdGetBlocksRequestMessage: rpchandlers.HandleGetBlocks,
|
||||
appmessage.CmdGetBlockCountRequestMessage: rpchandlers.HandleGetBlockCount,
|
||||
appmessage.CmdGetBlockDAGInfoRequestMessage: rpchandlers.HandleGetBlockDAGInfo,
|
||||
appmessage.CmdResolveFinalityConflictRequestMessage: rpchandlers.HandleResolveFinalityConflict,
|
||||
appmessage.CmdNotifyFinalityConflictsRequestMessage: rpchandlers.HandleNotifyFinalityConflicts,
|
||||
appmessage.CmdGetMempoolEntriesRequestMessage: rpchandlers.HandleGetMempoolEntries,
|
||||
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleShutDown,
|
||||
appmessage.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
|
||||
}
|
||||
|
||||
func (m *Manager) routerInitializer(router *router.Router, netConnection *netadapter.NetConnection) {
|
||||
@@ -40,16 +45,12 @@ func (m *Manager) routerInitializer(router *router.Router, netConnection *netada
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
spawn("routerInitializer-handleIncomingMessages", func() {
|
||||
err := m.handleIncomingMessages(router, incomingRoute)
|
||||
m.handleError(err, netConnection)
|
||||
})
|
||||
m.context.NotificationManager.AddListener(router)
|
||||
|
||||
notificationListener := m.context.NotificationManager.AddListener(router)
|
||||
spawn("routerInitializer-handleOutgoingNotifications", func() {
|
||||
spawn("routerInitializer-handleIncomingMessages", func() {
|
||||
defer m.context.NotificationManager.RemoveListener(router)
|
||||
|
||||
err := m.handleOutgoingNotifications(notificationListener)
|
||||
err := m.handleIncomingMessages(router, incomingRoute)
|
||||
m.handleError(err, netConnection)
|
||||
})
|
||||
}
|
||||
@@ -76,15 +77,6 @@ func (m *Manager) handleIncomingMessages(router *router.Router, incomingRoute *r
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) handleOutgoingNotifications(notificationListener *rpccontext.NotificationListener) error {
|
||||
for {
|
||||
err := notificationListener.ProcessNextNotification()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Manager) handleError(err error, netConnection *netadapter.NetConnection) {
|
||||
if errors.Is(err, router.ErrTimeout) {
|
||||
log.Warnf("Got timeout from %s. Disconnecting...", netConnection)
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
package rpccontext
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/domain/mining"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/kaspanet/kaspad/util/mstime"
|
||||
"github.com/kaspanet/kaspad/util/random"
|
||||
"github.com/pkg/errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// blockTemplateNonceRange is two 64-bit big-endian hexadecimal integers which
|
||||
// represent the valid ranges of nonces returned by the getBlockTemplate
|
||||
// RPC.
|
||||
blockTemplateNonceRange = "000000000000ffffffffffff"
|
||||
|
||||
// blockTemplateRegenerateSeconds is the number of seconds that must pass before
|
||||
// a new template is generated when the parent block hashes has not
|
||||
// changed and there have been changes to the available transactions
|
||||
// in the memory pool.
|
||||
blockTemplateRegenerateSeconds = 60
|
||||
)
|
||||
|
||||
var (
|
||||
// blockTemplateMutableFields are the manipulations the server allows to be made
|
||||
// to block templates generated by the getBlockTemplate RPC. It is
|
||||
// declared here to avoid the overhead of creating the slice on every
|
||||
// invocation for constant data.
|
||||
blockTemplateMutableFields = []string{
|
||||
"time", "transactions/add", "parentblock", "coinbase/append",
|
||||
}
|
||||
)
|
||||
|
||||
// BlockTemplateState houses state that is used in between multiple RPC invocations to
|
||||
// getBlockTemplate.
|
||||
type BlockTemplateState struct {
|
||||
sync.Mutex
|
||||
|
||||
context *Context
|
||||
|
||||
lastTxUpdate mstime.Time
|
||||
lastGenerated mstime.Time
|
||||
tipHashes []*daghash.Hash
|
||||
minTimestamp mstime.Time
|
||||
template *mining.BlockTemplate
|
||||
notifyMap map[string]map[int64]chan struct{}
|
||||
payAddress util.Address
|
||||
}
|
||||
|
||||
// NewBlockTemplateState returns a new instance of a BlockTemplateState with all internal
|
||||
// fields initialized and ready to use.
|
||||
func NewBlockTemplateState(context *Context) *BlockTemplateState {
|
||||
return &BlockTemplateState{
|
||||
context: context,
|
||||
notifyMap: make(map[string]map[int64]chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Update updates the block template state
|
||||
func (bt *BlockTemplateState) Update(payAddress util.Address) error {
|
||||
generator := bt.context.BlockTemplateGenerator
|
||||
lastTxUpdate := generator.TxSource().LastUpdated()
|
||||
if lastTxUpdate.IsZero() {
|
||||
lastTxUpdate = mstime.Now()
|
||||
}
|
||||
|
||||
// Generate a new block template when the current best block has
|
||||
// changed or the transactions in the memory pool have been updated and
|
||||
// it has been at least gbtRegenerateSecond since the last template was
|
||||
// generated.
|
||||
var msgBlock *appmessage.MsgBlock
|
||||
var targetDifficulty string
|
||||
tipHashes := bt.context.DAG.TipHashes()
|
||||
template := bt.template
|
||||
if template == nil || bt.tipHashes == nil ||
|
||||
!daghash.AreEqual(bt.tipHashes, tipHashes) ||
|
||||
bt.payAddress.String() != payAddress.String() ||
|
||||
(bt.lastTxUpdate != lastTxUpdate &&
|
||||
mstime.Now().After(bt.lastGenerated.Add(time.Second*
|
||||
blockTemplateRegenerateSeconds))) {
|
||||
|
||||
// Reset the previous best hash the block template was generated
|
||||
// against so any errors below cause the next invocation to try
|
||||
// again.
|
||||
bt.tipHashes = nil
|
||||
|
||||
// Create a new block template that has a coinbase which anyone
|
||||
// can redeem. This is only acceptable because the returned
|
||||
// block template doesn't include the coinbase, so the caller
|
||||
// will ultimately create their own coinbase which pays to the
|
||||
// appropriate address(es).
|
||||
|
||||
extraNonce, err := random.Uint64()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to randomize extra nonce")
|
||||
}
|
||||
|
||||
blockTemplate, err := generator.NewBlockTemplate(payAddress, extraNonce)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create new block template")
|
||||
}
|
||||
template = blockTemplate
|
||||
msgBlock = template.Block
|
||||
targetDifficulty = fmt.Sprintf("%064x", util.CompactToBig(msgBlock.Header.Bits))
|
||||
|
||||
// Get the minimum allowed timestamp for the block based on the
|
||||
// median timestamp of the last several blocks per the DAG
|
||||
// consensus rules.
|
||||
minTimestamp := bt.context.DAG.NextBlockMinimumTime()
|
||||
|
||||
// Update work state to ensure another block template isn't
|
||||
// generated until needed.
|
||||
bt.template = template
|
||||
bt.lastGenerated = mstime.Now()
|
||||
bt.lastTxUpdate = lastTxUpdate
|
||||
bt.tipHashes = tipHashes
|
||||
bt.minTimestamp = minTimestamp
|
||||
bt.payAddress = payAddress
|
||||
|
||||
log.Debugf("Generated block template (timestamp %s, "+
|
||||
"target %s, merkle root %s)",
|
||||
msgBlock.Header.Timestamp, targetDifficulty,
|
||||
msgBlock.Header.HashMerkleRoot)
|
||||
|
||||
// Notify any clients that are long polling about the new
|
||||
// template.
|
||||
bt.notifyLongPollers(tipHashes, lastTxUpdate)
|
||||
} else {
|
||||
// At this point, there is a saved block template and another
|
||||
// request for a template was made, but either the available
|
||||
// transactions haven't change or it hasn't been long enough to
|
||||
// trigger a new block template to be generated. So, update the
|
||||
// existing block template.
|
||||
|
||||
// Set locals for convenience.
|
||||
msgBlock = template.Block
|
||||
targetDifficulty = fmt.Sprintf("%064x",
|
||||
util.CompactToBig(msgBlock.Header.Bits))
|
||||
|
||||
// Update the time of the block template to the current time
|
||||
// while accounting for the median time of the past several
|
||||
// blocks per the DAG consensus rules.
|
||||
err := generator.UpdateBlockTime(msgBlock)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to update block time")
|
||||
}
|
||||
msgBlock.Header.Nonce = 0
|
||||
|
||||
log.Debugf("Updated block template (timestamp %s, "+
|
||||
"target %s)", msgBlock.Header.Timestamp,
|
||||
targetDifficulty)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Response builds a GetBlockTemplateResponseMessage from the current state
|
||||
func (bt *BlockTemplateState) Response() (*appmessage.GetBlockTemplateResponseMessage, error) {
|
||||
dag := bt.context.DAG
|
||||
// Ensure the timestamps are still in valid range for the template.
|
||||
// This should really only ever happen if the local clock is changed
|
||||
// after the template is generated, but it's important to avoid serving
|
||||
// block templates that will be delayed on other nodes.
|
||||
template := bt.template
|
||||
msgBlock := template.Block
|
||||
header := &msgBlock.Header
|
||||
adjustedTime := dag.Now()
|
||||
maxTime := adjustedTime.Add(time.Millisecond * time.Duration(dag.TimestampDeviationTolerance))
|
||||
if header.Timestamp.After(maxTime) {
|
||||
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
|
||||
errorMessage.Error = appmessage.RPCErrorf("The template time is after the "+
|
||||
"maximum allowed time for a block - template "+
|
||||
"time %s, maximum time %s", adjustedTime,
|
||||
maxTime)
|
||||
return errorMessage, nil
|
||||
}
|
||||
|
||||
// Convert each transaction in the block template to a template result
|
||||
// transaction. The result does not include the coinbase, so notice
|
||||
// the adjustments to the various lengths and indices.
|
||||
numTx := len(msgBlock.Transactions)
|
||||
transactions := make([]appmessage.GetBlockTemplateTransactionMessage, 0, numTx-1)
|
||||
txIndex := make(map[daghash.TxID]int64, numTx)
|
||||
for i, tx := range msgBlock.Transactions {
|
||||
txID := tx.TxID()
|
||||
txIndex[*txID] = int64(i)
|
||||
|
||||
// Create an array of 1-based indices to transactions that come
|
||||
// before this one in the transactions list which this one
|
||||
// depends on. This is necessary since the created block must
|
||||
// ensure proper ordering of the dependencies. A map is used
|
||||
// before creating the final array to prevent duplicate entries
|
||||
// when multiple inputs reference the same transaction.
|
||||
dependsMap := make(map[int64]struct{})
|
||||
for _, txIn := range tx.TxIn {
|
||||
if idx, ok := txIndex[txIn.PreviousOutpoint.TxID]; ok {
|
||||
dependsMap[idx] = struct{}{}
|
||||
}
|
||||
}
|
||||
depends := make([]int64, 0, len(dependsMap))
|
||||
for idx := range dependsMap {
|
||||
depends = append(depends, idx)
|
||||
}
|
||||
|
||||
// Serialize the transaction for later conversion to hex.
|
||||
txBuf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
|
||||
if err := tx.Serialize(txBuf); err != nil {
|
||||
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
|
||||
errorMessage.Error = appmessage.RPCErrorf("Failed to serialize transaction: %s", err)
|
||||
return errorMessage, nil
|
||||
}
|
||||
|
||||
resultTx := appmessage.GetBlockTemplateTransactionMessage{
|
||||
Data: hex.EncodeToString(txBuf.Bytes()),
|
||||
ID: txID.String(),
|
||||
Depends: depends,
|
||||
Mass: template.TxMasses[i],
|
||||
Fee: template.Fees[i],
|
||||
}
|
||||
transactions = append(transactions, resultTx)
|
||||
}
|
||||
|
||||
// Generate the block template reply. Note that following mutations are
|
||||
// implied by the included or omission of fields:
|
||||
// Including MinTime -> time/decrement
|
||||
// Omitting CoinbaseTxn -> coinbase, generation
|
||||
targetDifficulty := fmt.Sprintf("%064x", util.CompactToBig(header.Bits))
|
||||
longPollID := bt.encodeLongPollID(bt.tipHashes, bt.payAddress, bt.lastGenerated)
|
||||
|
||||
// Check whether this node is synced with the rest of of the
|
||||
// network. There's almost never a good reason to mine on top
|
||||
// of an unsynced DAG, and miners are generally expected not to
|
||||
// mine when isSynced is false.
|
||||
// This is not a straight-up error because the choice of whether
|
||||
// to mine or not is the responsibility of the miner rather
|
||||
// than the node's.
|
||||
isSynced := bt.context.BlockTemplateGenerator.IsSynced()
|
||||
isConnected := len(bt.context.ProtocolManager.Peers()) > 0
|
||||
|
||||
reply := appmessage.GetBlockTemplateResponseMessage{
|
||||
Bits: strconv.FormatInt(int64(header.Bits), 16),
|
||||
CurrentTime: header.Timestamp.UnixMilliseconds(),
|
||||
ParentHashes: daghash.Strings(header.ParentHashes),
|
||||
MassLimit: appmessage.MaxMassPerBlock,
|
||||
Transactions: transactions,
|
||||
HashMerkleRoot: header.HashMerkleRoot.String(),
|
||||
AcceptedIDMerkleRoot: header.AcceptedIDMerkleRoot.String(),
|
||||
UTXOCommitment: header.UTXOCommitment.String(),
|
||||
Version: header.Version,
|
||||
LongPollID: longPollID,
|
||||
TargetDifficulty: targetDifficulty,
|
||||
MinTime: bt.minTimestamp.UnixMilliseconds(),
|
||||
MaxTime: maxTime.UnixMilliseconds(),
|
||||
MutableFields: blockTemplateMutableFields,
|
||||
NonceRange: blockTemplateNonceRange,
|
||||
IsSynced: isSynced,
|
||||
IsConnected: isConnected,
|
||||
}
|
||||
|
||||
return &reply, nil
|
||||
}
|
||||
|
||||
// notifyLongPollers notifies any channels that have been registered to be
|
||||
// notified when block templates are stale.
|
||||
//
|
||||
// This function MUST be called with the state locked.
|
||||
func (bt *BlockTemplateState) notifyLongPollers(tipHashes []*daghash.Hash, lastGenerated mstime.Time) {
|
||||
// Notify anything that is waiting for a block template update from
|
||||
// hashes which are not the current tip hashes.
|
||||
tipHashesStr := daghash.JoinHashesStrings(tipHashes, "")
|
||||
for hashesStr, channels := range bt.notifyMap {
|
||||
if hashesStr != tipHashesStr {
|
||||
for _, c := range channels {
|
||||
close(c)
|
||||
}
|
||||
delete(bt.notifyMap, hashesStr)
|
||||
}
|
||||
}
|
||||
|
||||
// Return now if the provided last generated timestamp has not been
|
||||
// initialized.
|
||||
if lastGenerated.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
// Return now if there is nothing registered for updates to the current
|
||||
// best block hash.
|
||||
channels, ok := bt.notifyMap[tipHashesStr]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// Notify anything that is waiting for a block template update from a
|
||||
// block template generated before the most recently generated block
|
||||
// template.
|
||||
lastGeneratedUnix := lastGenerated.UnixSeconds()
|
||||
for lastGen, c := range channels {
|
||||
if lastGen < lastGeneratedUnix {
|
||||
close(c)
|
||||
delete(channels, lastGen)
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the entry altogether if there are no more registered
|
||||
// channels.
|
||||
if len(channels) == 0 {
|
||||
delete(bt.notifyMap, tipHashesStr)
|
||||
}
|
||||
}
|
||||
|
||||
// NotifyBlockAdded uses the newly-added block to notify any long poll
|
||||
// clients with a new block template when their existing block template is
|
||||
// stale due to the newly added block.
|
||||
func (bt *BlockTemplateState) NotifyBlockAdded(block *util.Block) {
|
||||
spawn("BlockTemplateState.NotifyBlockAdded", func() {
|
||||
bt.Lock()
|
||||
defer bt.Unlock()
|
||||
|
||||
bt.notifyLongPollers(block.MsgBlock().Header.ParentHashes, bt.lastTxUpdate)
|
||||
})
|
||||
}
|
||||
|
||||
// NotifyMempoolTx uses the new last updated time for the transaction memory
|
||||
// pool to notify any long poll clients with a new block template when their
|
||||
// existing block template is stale due to enough time passing and the contents
|
||||
// of the memory pool changing.
|
||||
func (bt *BlockTemplateState) NotifyMempoolTx() {
|
||||
lastUpdated := bt.context.Mempool.LastUpdated()
|
||||
spawn("BlockTemplateState", func() {
|
||||
bt.Lock()
|
||||
defer bt.Unlock()
|
||||
|
||||
// No need to notify anything if no block templates have been generated
|
||||
// yet.
|
||||
if bt.tipHashes == nil || bt.lastGenerated.IsZero() {
|
||||
return
|
||||
}
|
||||
|
||||
if mstime.Now().After(bt.lastGenerated.Add(time.Second *
|
||||
blockTemplateRegenerateSeconds)) {
|
||||
|
||||
bt.notifyLongPollers(bt.tipHashes, lastUpdated)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// BlockTemplateOrLongPollChan returns a block template if the
|
||||
// template identified by the provided long poll ID is stale or
|
||||
// invalid. Otherwise, it returns a channel that will notify
|
||||
// when there's a more current template.
|
||||
func (bt *BlockTemplateState) BlockTemplateOrLongPollChan(longPollID string,
|
||||
payAddress util.Address) (*appmessage.GetBlockTemplateResponseMessage, chan struct{}, error) {
|
||||
|
||||
bt.Lock()
|
||||
defer bt.Unlock()
|
||||
|
||||
if err := bt.Update(payAddress); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// Just return the current block template if the long poll ID provided by
|
||||
// the caller is invalid.
|
||||
parentHashes, lastGenerated, err := bt.decodeLongPollID(longPollID)
|
||||
if err != nil {
|
||||
result, err := bt.Response()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return result, nil, nil
|
||||
}
|
||||
|
||||
// Return the block template now if the specific block template
|
||||
// identified by the long poll ID no longer matches the current block
|
||||
// template as this means the provided template is stale.
|
||||
areHashesEqual := daghash.AreEqual(bt.template.Block.Header.ParentHashes, parentHashes)
|
||||
if !areHashesEqual ||
|
||||
lastGenerated != bt.lastGenerated.UnixSeconds() {
|
||||
|
||||
// Include whether or not it is valid to submit work against the
|
||||
// old block template depending on whether or not a solution has
|
||||
// already been found and added to the block DAG.
|
||||
result, err := bt.Response()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return result, nil, nil
|
||||
}
|
||||
|
||||
// Register the parent hashes and last generated time for notifications
|
||||
// Get a channel that will be notified when the template associated with
|
||||
// the provided ID is stale and a new block template should be returned to
|
||||
// the caller.
|
||||
longPollChan := bt.templateUpdateChan(parentHashes, lastGenerated)
|
||||
return nil, longPollChan, nil
|
||||
}
|
||||
|
||||
// templateUpdateChan returns a channel that will be closed once the block
|
||||
// template associated with the passed parent hashes and last generated time
|
||||
// is stale. The function will return existing channels for duplicate
|
||||
// parameters which allows multiple clients to wait for the same block template
|
||||
// without requiring a different channel for each client.
|
||||
//
|
||||
// This function MUST be called with the state locked.
|
||||
func (bt *BlockTemplateState) templateUpdateChan(tipHashes []*daghash.Hash, lastGenerated int64) chan struct{} {
|
||||
tipHashesStr := daghash.JoinHashesStrings(tipHashes, "")
|
||||
// Either get the current list of channels waiting for updates about
|
||||
// changes to block template for the parent hashes or create a new one.
|
||||
channels, ok := bt.notifyMap[tipHashesStr]
|
||||
if !ok {
|
||||
m := make(map[int64]chan struct{})
|
||||
bt.notifyMap[tipHashesStr] = m
|
||||
channels = m
|
||||
}
|
||||
|
||||
// Get the current channel associated with the time the block template
|
||||
// was last generated or create a new one.
|
||||
c, ok := channels[lastGenerated]
|
||||
if !ok {
|
||||
c = make(chan struct{})
|
||||
channels[lastGenerated] = c
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// encodeLongPollID encodes the passed details into an ID that can be used to
|
||||
// uniquely identify a block template.
|
||||
func (bt *BlockTemplateState) encodeLongPollID(parentHashes []*daghash.Hash, miningAddress util.Address, lastGenerated mstime.Time) string {
|
||||
return fmt.Sprintf("%s-%s-%d", daghash.JoinHashesStrings(parentHashes, ""), miningAddress, lastGenerated.UnixSeconds())
|
||||
}
|
||||
|
||||
// decodeLongPollID decodes an ID that is used to uniquely identify a block
|
||||
// template. This is mainly used as a mechanism to track when to update clients
|
||||
// that are using long polling for block templates. The ID consists of the
|
||||
// parent blocks hashes for the associated template and the time the associated
|
||||
// template was generated.
|
||||
func (bt *BlockTemplateState) decodeLongPollID(longPollID string) ([]*daghash.Hash, int64, error) {
|
||||
fields := strings.Split(longPollID, "-")
|
||||
if len(fields) != 2 {
|
||||
return nil, 0, errors.New("decodeLongPollID: invalid number of fields")
|
||||
}
|
||||
|
||||
parentHashesStr := fields[0]
|
||||
if len(parentHashesStr)%daghash.HashSize != 0 {
|
||||
return nil, 0, errors.New("decodeLongPollID: invalid parent hashes format")
|
||||
}
|
||||
numberOfHashes := len(parentHashesStr) / daghash.HashSize
|
||||
|
||||
parentHashes := make([]*daghash.Hash, 0, numberOfHashes)
|
||||
|
||||
for i := 0; i < len(parentHashesStr); i += daghash.HashSize {
|
||||
hash, err := daghash.NewHashFromStr(parentHashesStr[i : i+daghash.HashSize])
|
||||
if err != nil {
|
||||
return nil, 0, errors.Errorf("decodeLongPollID: NewHashFromStr: %s", err)
|
||||
}
|
||||
parentHashes = append(parentHashes, hash)
|
||||
}
|
||||
|
||||
lastGenerated, err := strconv.ParseInt(fields[1], 10, 64)
|
||||
if err != nil {
|
||||
return nil, 0, errors.Errorf("decodeLongPollID: Cannot parse timestamp %s: %s", fields[1], err)
|
||||
}
|
||||
|
||||
return parentHashes, lastGenerated, nil
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
package rpccontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/util/daghash"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// CollectChainBlocks creates a slice of chain blocks from the given hashes
|
||||
func (ctx *Context) CollectChainBlocks(hashes []*daghash.Hash) ([]*appmessage.ChainBlock, error) {
|
||||
chainBlocks := make([]*appmessage.ChainBlock, 0, len(hashes))
|
||||
for _, hash := range hashes {
|
||||
acceptanceData, err := ctx.AcceptanceIndex.TxsAcceptanceData(hash)
|
||||
if err != nil {
|
||||
return nil, errors.Errorf("could not retrieve acceptance data for block %s", hash)
|
||||
}
|
||||
|
||||
acceptedBlocks := make([]*appmessage.AcceptedBlock, 0, len(acceptanceData))
|
||||
for _, blockAcceptanceData := range acceptanceData {
|
||||
acceptedTxIds := make([]string, 0, len(blockAcceptanceData.TxAcceptanceData))
|
||||
for _, txAcceptanceData := range blockAcceptanceData.TxAcceptanceData {
|
||||
if txAcceptanceData.IsAccepted {
|
||||
acceptedTxIds = append(acceptedTxIds, txAcceptanceData.Tx.ID().String())
|
||||
}
|
||||
}
|
||||
acceptedBlock := &appmessage.AcceptedBlock{
|
||||
Hash: blockAcceptanceData.BlockHash.String(),
|
||||
AcceptedTxIDs: acceptedTxIds,
|
||||
}
|
||||
acceptedBlocks = append(acceptedBlocks, acceptedBlock)
|
||||
}
|
||||
|
||||
chainBlock := &appmessage.ChainBlock{
|
||||
Hash: hash.String(),
|
||||
AcceptedBlocks: acceptedBlocks,
|
||||
}
|
||||
chainBlocks = append(chainBlocks, chainBlock)
|
||||
}
|
||||
return chainBlocks, nil
|
||||
}
|
||||
@@ -2,10 +2,7 @@ package rpccontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/protocol"
|
||||
"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"
|
||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
|
||||
@@ -14,43 +11,35 @@ import (
|
||||
|
||||
// Context represents the RPC context
|
||||
type Context struct {
|
||||
Config *config.Config
|
||||
NetAdapter *netadapter.NetAdapter
|
||||
DAG *blockdag.BlockDAG
|
||||
ProtocolManager *protocol.Manager
|
||||
ConnectionManager *connmanager.ConnectionManager
|
||||
BlockTemplateGenerator *mining.BlkTmplGenerator
|
||||
Mempool *mempool.TxPool
|
||||
AddressManager *addressmanager.AddressManager
|
||||
AcceptanceIndex *indexers.AcceptanceIndex
|
||||
Config *config.Config
|
||||
NetAdapter *netadapter.NetAdapter
|
||||
Domain domain.Domain
|
||||
ProtocolManager *protocol.Manager
|
||||
ConnectionManager *connmanager.ConnectionManager
|
||||
AddressManager *addressmanager.AddressManager
|
||||
ShutDownChan chan<- struct{}
|
||||
|
||||
BlockTemplateState *BlockTemplateState
|
||||
NotificationManager *NotificationManager
|
||||
}
|
||||
|
||||
// NewContext creates a new RPC context
|
||||
func NewContext(
|
||||
cfg *config.Config,
|
||||
func NewContext(cfg *config.Config,
|
||||
domain domain.Domain,
|
||||
netAdapter *netadapter.NetAdapter,
|
||||
dag *blockdag.BlockDAG,
|
||||
protocolManager *protocol.Manager,
|
||||
connectionManager *connmanager.ConnectionManager,
|
||||
blockTemplateGenerator *mining.BlkTmplGenerator,
|
||||
mempool *mempool.TxPool,
|
||||
addressManager *addressmanager.AddressManager,
|
||||
acceptanceIndex *indexers.AcceptanceIndex) *Context {
|
||||
shutDownChan chan<- struct{}) *Context {
|
||||
|
||||
context := &Context{
|
||||
Config: cfg,
|
||||
NetAdapter: netAdapter,
|
||||
DAG: dag,
|
||||
ProtocolManager: protocolManager,
|
||||
ConnectionManager: connectionManager,
|
||||
BlockTemplateGenerator: blockTemplateGenerator,
|
||||
Mempool: mempool,
|
||||
AddressManager: addressManager,
|
||||
AcceptanceIndex: acceptanceIndex,
|
||||
Config: cfg,
|
||||
NetAdapter: netAdapter,
|
||||
Domain: domain,
|
||||
ProtocolManager: protocolManager,
|
||||
ConnectionManager: connectionManager,
|
||||
AddressManager: addressManager,
|
||||
ShutDownChan: shutDownChan,
|
||||
}
|
||||
context.BlockTemplateState = NewBlockTemplateState(context)
|
||||
context.NotificationManager = NewNotificationManager()
|
||||
return context
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package rpccontext
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
|
||||
"github.com/pkg/errors"
|
||||
"sync"
|
||||
)
|
||||
@@ -10,58 +10,43 @@ import (
|
||||
// NotificationManager manages notifications for the RPC
|
||||
type NotificationManager struct {
|
||||
sync.RWMutex
|
||||
listeners map[*router.Router]*NotificationListener
|
||||
listeners map[*routerpkg.Router]*NotificationListener
|
||||
}
|
||||
|
||||
// OnBlockAddedListener is a listener function for when a block is added to the DAG
|
||||
type OnBlockAddedListener func(notification *appmessage.BlockAddedNotificationMessage) error
|
||||
|
||||
// OnChainChangedListener is a listener function for when the DAG's selected parent chain changes
|
||||
type OnChainChangedListener func(notification *appmessage.ChainChangedNotificationMessage) error
|
||||
|
||||
// NotificationListener represents a registered RPC notification listener
|
||||
type NotificationListener struct {
|
||||
onBlockAddedListener OnBlockAddedListener
|
||||
onBlockAddedNotificationChan chan *appmessage.BlockAddedNotificationMessage
|
||||
onChainChangedListener OnChainChangedListener
|
||||
onChainChangedNotificationChan chan *appmessage.ChainChangedNotificationMessage
|
||||
|
||||
closeChan chan struct{}
|
||||
propagateBlockAddedNotifications bool
|
||||
propagateChainChangedNotifications bool
|
||||
propagateFinalityConflictNotifications bool
|
||||
propagateFinalityConflictResolvedNotifications bool
|
||||
}
|
||||
|
||||
// NewNotificationManager creates a new NotificationManager
|
||||
func NewNotificationManager() *NotificationManager {
|
||||
return &NotificationManager{
|
||||
listeners: make(map[*router.Router]*NotificationListener),
|
||||
listeners: make(map[*routerpkg.Router]*NotificationListener),
|
||||
}
|
||||
}
|
||||
|
||||
// AddListener registers a listener with the given router
|
||||
func (nm *NotificationManager) AddListener(router *router.Router) *NotificationListener {
|
||||
func (nm *NotificationManager) AddListener(router *routerpkg.Router) {
|
||||
nm.Lock()
|
||||
defer nm.Unlock()
|
||||
|
||||
listener := newNotificationListener()
|
||||
nm.listeners[router] = listener
|
||||
return listener
|
||||
}
|
||||
|
||||
// RemoveListener unregisters the given router
|
||||
func (nm *NotificationManager) RemoveListener(router *router.Router) {
|
||||
func (nm *NotificationManager) RemoveListener(router *routerpkg.Router) {
|
||||
nm.Lock()
|
||||
defer nm.Unlock()
|
||||
|
||||
listener, ok := nm.listeners[router]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
listener.close()
|
||||
|
||||
delete(nm.listeners, router)
|
||||
}
|
||||
|
||||
// Listener retrieves the listener registered with the given router
|
||||
func (nm *NotificationManager) Listener(router *router.Router) (*NotificationListener, error) {
|
||||
func (nm *NotificationManager) Listener(router *routerpkg.Router) (*NotificationListener, error) {
|
||||
nm.RLock()
|
||||
defer nm.RUnlock()
|
||||
|
||||
@@ -73,67 +58,100 @@ func (nm *NotificationManager) Listener(router *router.Router) (*NotificationLis
|
||||
}
|
||||
|
||||
// NotifyBlockAdded notifies the notification manager that a block has been added to the DAG
|
||||
func (nm *NotificationManager) NotifyBlockAdded(notification *appmessage.BlockAddedNotificationMessage) {
|
||||
func (nm *NotificationManager) NotifyBlockAdded(notification *appmessage.BlockAddedNotificationMessage) error {
|
||||
nm.RLock()
|
||||
defer nm.RUnlock()
|
||||
|
||||
for _, listener := range nm.listeners {
|
||||
if listener.onBlockAddedListener != nil {
|
||||
select {
|
||||
case listener.onBlockAddedNotificationChan <- notification:
|
||||
case <-listener.closeChan:
|
||||
continue
|
||||
for router, listener := range nm.listeners {
|
||||
if listener.propagateBlockAddedNotifications {
|
||||
err := router.OutgoingRoute().Enqueue(notification)
|
||||
if errors.Is(err, routerpkg.ErrRouteClosed) {
|
||||
log.Warnf("Couldn't send notification: %s", err)
|
||||
} else if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyChainChanged notifies the notification manager that the DAG's selected parent chain has changed
|
||||
func (nm *NotificationManager) NotifyChainChanged(message *appmessage.ChainChangedNotificationMessage) {
|
||||
func (nm *NotificationManager) NotifyChainChanged(notification *appmessage.ChainChangedNotificationMessage) error {
|
||||
nm.RLock()
|
||||
defer nm.RUnlock()
|
||||
|
||||
for _, listener := range nm.listeners {
|
||||
if listener.onChainChangedListener != nil {
|
||||
select {
|
||||
case listener.onChainChangedNotificationChan <- message:
|
||||
case <-listener.closeChan:
|
||||
continue
|
||||
for router, listener := range nm.listeners {
|
||||
if listener.propagateChainChangedNotifications {
|
||||
err := router.OutgoingRoute().Enqueue(notification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyFinalityConflict notifies the notification manager that there's a finality conflict in the DAG
|
||||
func (nm *NotificationManager) NotifyFinalityConflict(notification *appmessage.FinalityConflictNotificationMessage) error {
|
||||
nm.RLock()
|
||||
defer nm.RUnlock()
|
||||
|
||||
for router, listener := range nm.listeners {
|
||||
if listener.propagateFinalityConflictNotifications {
|
||||
err := router.OutgoingRoute().Enqueue(notification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NotifyFinalityConflictResolved notifies the notification manager that a finality conflict in the DAG has been resolved
|
||||
func (nm *NotificationManager) NotifyFinalityConflictResolved(notification *appmessage.FinalityConflictResolvedNotificationMessage) error {
|
||||
nm.RLock()
|
||||
defer nm.RUnlock()
|
||||
|
||||
for router, listener := range nm.listeners {
|
||||
if listener.propagateFinalityConflictResolvedNotifications {
|
||||
err := router.OutgoingRoute().Enqueue(notification)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newNotificationListener() *NotificationListener {
|
||||
return &NotificationListener{
|
||||
onBlockAddedNotificationChan: make(chan *appmessage.BlockAddedNotificationMessage),
|
||||
onChainChangedNotificationChan: make(chan *appmessage.ChainChangedNotificationMessage),
|
||||
closeChan: make(chan struct{}, 1),
|
||||
propagateBlockAddedNotifications: false,
|
||||
propagateChainChangedNotifications: false,
|
||||
propagateFinalityConflictNotifications: false,
|
||||
propagateFinalityConflictResolvedNotifications: false,
|
||||
}
|
||||
}
|
||||
|
||||
// SetOnBlockAddedListener sets the onBlockAddedListener handler for this listener
|
||||
func (nl *NotificationListener) SetOnBlockAddedListener(onBlockAddedListener OnBlockAddedListener) {
|
||||
nl.onBlockAddedListener = onBlockAddedListener
|
||||
// PropagateBlockAddedNotifications instructs the listener to send block added notifications
|
||||
// to the remote listener
|
||||
func (nl *NotificationListener) PropagateBlockAddedNotifications() {
|
||||
nl.propagateBlockAddedNotifications = true
|
||||
}
|
||||
|
||||
// SetOnChainChangedListener sets the onChainChangedListener handler for this listener
|
||||
func (nl *NotificationListener) SetOnChainChangedListener(onChainChangedListener OnChainChangedListener) {
|
||||
nl.onChainChangedListener = onChainChangedListener
|
||||
// PropagateChainChangedNotifications instructs the listener to send chain changed notifications
|
||||
// to the remote listener
|
||||
func (nl *NotificationListener) PropagateChainChangedNotifications() {
|
||||
nl.propagateChainChangedNotifications = true
|
||||
}
|
||||
|
||||
// ProcessNextNotification waits until a notification arrives and processes it
|
||||
func (nl *NotificationListener) ProcessNextNotification() error {
|
||||
select {
|
||||
case block := <-nl.onBlockAddedNotificationChan:
|
||||
return nl.onBlockAddedListener(block)
|
||||
case notification := <-nl.onChainChangedNotificationChan:
|
||||
return nl.onChainChangedListener(notification)
|
||||
case <-nl.closeChan:
|
||||
return nil
|
||||
}
|
||||
// PropagateFinalityConflictNotifications instructs the listener to send finality conflict notifications
|
||||
// to the remote listener
|
||||
func (nl *NotificationListener) PropagateFinalityConflictNotifications() {
|
||||
nl.propagateFinalityConflictNotifications = true
|
||||
}
|
||||
|
||||
func (nl *NotificationListener) close() {
|
||||
nl.closeChan <- struct{}{}
|
||||
// PropagateFinalityConflictResolvedNotifications instructs the listener to send finality conflict resolved notifications
|
||||
// to the remote listener
|
||||
func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications() {
|
||||
nl.propagateFinalityConflictResolvedNotifications = true
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user