Compare commits

..

3 Commits

Author SHA1 Message Date
Ori Newman
88497f5793 [NOD-1444] Implement getHeaders RPC command (#944)
* [NOD-1444] Implement getHeaders RPC command

* [NOD-1444] Fix tests and comments

* [NOD-1444] Fix error message

* [NOD-1444] Make GetHeaders propagate header serialization errors

* [NOD-1444] RLock the dag on GetHeaders

* [NOD-1444] Change the error field number to 1000
2020-10-05 12:03:51 +03:00
Mike Zak
5c5afa2360 [NOD-1406] Remove UTXO diff from mempool 2020-10-05 08:51:17 +03:00
stasatdaglabs
3d8c131fa8 [NOD-1404] Remove most of the notification manager to fix a deadlock (#935)
* [NOD-1404] Remove most of the notification manager to fix a deadlock.

* [NOD-1404] Rename a couple of fields.
2020-09-23 13:11:31 +03:00
588 changed files with 44363 additions and 22820 deletions

View File

@@ -2,184 +2,240 @@ package app
import (
"fmt"
"os"
"path/filepath"
"runtime"
"time"
"sync/atomic"
"github.com/kaspanet/kaspad/infrastructure/db/database"
"github.com/kaspanet/kaspad/infrastructure/db/database/ldb"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/os/signal"
"github.com/kaspanet/kaspad/util/profiling"
"github.com/kaspanet/kaspad/version"
"github.com/kaspanet/kaspad/util/panics"
"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/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/os/execenv"
"github.com/kaspanet/kaspad/infrastructure/os/limits"
"github.com/kaspanet/kaspad/infrastructure/os/winservice"
"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/panics"
)
var desiredLimits = &limits.DesiredLimits{
FileLimitWant: 2048,
FileLimitMin: 1024,
// 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 serviceDescription = &winservice.ServiceDescription{
Name: "kaspadsvc",
DisplayName: "Kaspad Service",
Description: "Downloads and stays synchronized with the Kaspa blockDAG and " +
"provides DAG services to applications.",
}
type kaspadApp struct {
cfg *config.Config
}
// StartApp starts the kaspad app, and blocks until it finishes running
func StartApp() error {
execenv.Initialize(desiredLimits)
// Load configuration and parse command line. This function also
// initializes logging and configures it accordingly.
cfg, err := config.LoadConfig()
if err != nil {
fmt.Fprint(os.Stderr, err)
return err
// Start launches all the kaspad services.
func (a *App) Start() {
// Already started?
if atomic.AddInt32(&a.started, 1) != 1 {
return
}
defer panics.HandlePanic(log, "MAIN", nil)
app := &kaspadApp{cfg: cfg}
log.Trace("Starting kaspad")
// 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)
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 *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
}
// New returns a new App instance configured to listen on addr for the
// kaspa network type specified by dagParams. Use start to begin accepting
// connections from peers.
func New(cfg *config.Config, databaseContext *dbaccess.DatabaseContext, interrupt <-chan struct{}) (*App, error) {
indexManager, acceptanceIndex := setupIndexes(cfg)
sigCache := txscript.NewSigCache(cfg.SigCacheMaxSize)
// Create a new block DAG instance with the appropriate configuration.
dag, err := setupDAG(cfg, databaseContext, interrupt, sigCache, indexManager)
if err != nil {
return nil, err
}
txMempool := setupMempool(cfg, dag, sigCache)
netAdapter, err := netadapter.NewNetAdapter(cfg)
if err != nil {
return nil, err
}
addressManager, 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)
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
}
// 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 (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
}
// 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 setupIndexes(cfg *config.Config) (blockdag.IndexManager, *indexers.AcceptanceIndex) {
// Create indexes if needed.
var indexes []indexers.Indexer
var acceptanceIndex *indexers.AcceptanceIndex
if cfg.AcceptanceIndex {
log.Info("acceptance index is enabled")
acceptanceIndex = indexers.NewAcceptanceIndex()
indexes = append(indexes, acceptanceIndex)
}
// Create an index manager if any of the optional indexes are enabled.
if len(indexes) < 0 {
return nil, nil
}
indexManager := indexers.NewManager(indexes)
return indexManager, acceptanceIndex
}
func removeDatabase(cfg *config.Config) error {
dbPath := databasePath(cfg)
return os.RemoveAll(dbPath)
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,
},
CalcTxSequenceLockFromReferencedUTXOEntries: dag.CalcTxSequenceLockFromReferencedUTXOEntries,
SigCache: sigCache,
DAG: dag,
}
return mempool.New(&mempoolConfig)
}
func openDB(cfg *config.Config) (database.Database, error) {
dbPath := databasePath(cfg)
log.Infof("Loading database from '%s'", dbPath)
return ldb.NewLevelDB(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
}

View File

@@ -0,0 +1,431 @@
// 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()
}
}

View File

@@ -7,14 +7,14 @@ package appmessage
import (
"encoding/binary"
"fmt"
"io"
"math"
"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.
@@ -138,7 +138,7 @@ func ReadElement(r io.Reader, element interface{}) error {
}
return nil
case *externalapi.DomainHash:
case *daghash.Hash:
_, err := io.ReadFull(r, e[:])
if err != nil {
return err
@@ -148,7 +148,7 @@ func ReadElement(r io.Reader, element interface{}) error {
case *id.ID:
return e.Deserialize(r)
case *externalapi.DomainSubnetworkID:
case *subnetworkid.SubnetworkID:
_, err := io.ReadFull(r, e[:])
if err != nil {
return err
@@ -263,7 +263,7 @@ func WriteElement(w io.Writer, element interface{}) error {
}
return nil
case *externalapi.DomainHash:
case *daghash.Hash:
_, err := w.Write(e[:])
if err != nil {
return err
@@ -273,7 +273,7 @@ func WriteElement(w io.Writer, element interface{}) error {
case *id.ID:
return e.Serialize(w)
case *externalapi.DomainSubnetworkID:
case *subnetworkid.SubnetworkID:
_, err := w.Write(e[:])
if err != nil {
return err
@@ -375,31 +375,30 @@ func ReadVarInt(r io.Reader) (uint64, error) {
// on its value.
func WriteVarInt(w io.Writer, val uint64) error {
if val < 0xfd {
_, err := w.Write([]byte{uint8(val)})
return errors.WithStack(err)
return binaryserializer.PutUint8(w, uint8(val))
}
if val <= math.MaxUint16 {
var buf [3]byte
buf[0] = 0xfd
littleEndian.PutUint16(buf[1:], uint16(val))
_, err := w.Write(buf[:])
return errors.WithStack(err)
err := binaryserializer.PutUint8(w, 0xfd)
if err != nil {
return err
}
return binaryserializer.PutUint16(w, littleEndian, uint16(val))
}
if val <= math.MaxUint32 {
var buf [5]byte
buf[0] = 0xfe
littleEndian.PutUint32(buf[1:], uint32(val))
_, err := w.Write(buf[:])
return errors.WithStack(err)
err := binaryserializer.PutUint8(w, 0xfe)
if err != nil {
return err
}
return binaryserializer.PutUint32(w, littleEndian, uint32(val))
}
var buf [9]byte
buf[0] = 0xff
littleEndian.PutUint64(buf[1:], val)
_, err := w.Write(buf[:])
return errors.WithStack(err)
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

View File

@@ -6,20 +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 = &externalapi.DomainHash{
var mainnetGenesisHash = &daghash.Hash{
0xdc, 0x5f, 0x5b, 0x5b, 0x1d, 0xc2, 0xa7, 0x25,
0x49, 0xd5, 0x1d, 0x4d, 0xee, 0xd7, 0xa4, 0x8b,
0xaf, 0xd3, 0x14, 0x4b, 0x56, 0x78, 0x98, 0xb1,
@@ -28,30 +27,30 @@ var mainnetGenesisHash = &externalapi.DomainHash{
// simnetGenesisHash is the hash of the first block in the block DAG for the
// simulation test network.
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,
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,
}
// mainnetGenesisMerkleRoot is the hash of the first transaction in the genesis
// block for the main network.
var mainnetGenesisMerkleRoot = &externalapi.DomainHash{
var mainnetGenesisMerkleRoot = &daghash.Hash{
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 = &externalapi.DomainHash{
var exampleAcceptedIDMerkleRoot = &daghash.Hash{
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 = &externalapi.DomainHash{
var exampleUTXOCommitment = &daghash.Hash{
0x10, 0x3B, 0xC7, 0xE3, 0x67, 0x11, 0x7B, 0x3C,
0x30, 0xC1, 0xF8, 0xFD, 0xD0, 0xD9, 0x72, 0x87,
0x7F, 0x16, 0xC5, 0x96, 0x2E, 0x8B, 0xD9, 0x63,
@@ -105,7 +104,7 @@ func TestElementEncoding(t *testing.T) {
},
},
{
(*externalapi.DomainHash)(&[externalapi.DomainHashSize]byte{
(*daghash.Hash)(&[daghash.HashSize]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,
@@ -194,7 +193,7 @@ func TestElementEncodingErrors(t *testing.T) {
0, io.ErrShortWrite, io.EOF,
},
{
(*externalapi.DomainHash)(&[externalapi.DomainHashSize]byte{
(*daghash.Hash)(&[daghash.HashSize]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,

View File

@@ -1,149 +0,0 @@
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 BlockHeader
func DomainBlockHeaderToBlockHeader(domainBlockHeader *externalapi.DomainBlockHeader) *BlockHeader {
return &BlockHeader{
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 BlockHeader to externalapi.DomainBlockHeader
func BlockHeaderToDomainBlockHeader(blockHeader *BlockHeader) *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))
}
return &externalapi.DomainTransaction{
Version: msgTx.Version,
Inputs: transactionInputs,
Outputs: transactionOutputs,
LockTime: msgTx.LockTime,
SubnetworkID: msgTx.SubnetworkID,
Gas: msgTx.Gas,
PayloadHash: *msgTx.PayloadHash,
Payload: msgTx.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,
}
}

View File

@@ -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
@@ -91,16 +91,6 @@ const (
CmdGetBlockCountResponseMessage
CmdGetBlockDAGInfoRequestMessage
CmdGetBlockDAGInfoResponseMessage
CmdResolveFinalityConflictRequestMessage
CmdResolveFinalityConflictResponseMessage
CmdNotifyFinalityConflictsRequestMessage
CmdNotifyFinalityConflictsResponseMessage
CmdFinalityConflictNotificationMessage
CmdFinalityConflictResolvedNotificationMessage
CmdGetMempoolEntriesRequestMessage
CmdGetMempoolEntriesResponseMessage
CmdShutDownRequestMessage
CmdShutDownResponseMessage
CmdGetHeadersRequestMessage
CmdGetHeadersResponseMessage
)
@@ -133,53 +123,45 @@ var ProtocolMessageCommandToString = map[MessageCommand]string{
// 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",
CmdResolveFinalityConflictRequestMessage: "ResolveFinalityConflictRequest",
CmdResolveFinalityConflictResponseMessage: "ResolveFinalityConflictResponse",
CmdNotifyFinalityConflictsRequestMessage: "NotifyFinalityConflictsRequest",
CmdNotifyFinalityConflictsResponseMessage: "NotifyFinalityConflictsResponse",
CmdFinalityConflictNotificationMessage: "FinalityConflictNotification",
CmdFinalityConflictResolvedNotificationMessage: "FinalityConflictResolvedNotification",
CmdGetMempoolEntriesRequestMessage: "GetMempoolEntriesRequestMessage",
CmdGetMempoolEntriesResponseMessage: "GetMempoolEntriesResponseMessage",
CmdGetHeadersRequestMessage: "GetHeadersRequest",
CmdGetHeadersResponseMessage: "GetHeadersResponse",
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",
CmdGetHeadersRequestMessage: "GetHeadersRequest",
CmdGetHeadersResponseMessage: "GetHeadersResponse",
}
// Message is an interface that describes a kaspa message. A type that

View File

@@ -5,13 +5,11 @@
package appmessage
import (
"math"
"fmt"
"io"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
)
// BaseBlockHeaderPayload is the base number of bytes a block header can be,
@@ -19,9 +17,9 @@ import (
// 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
// To get total size of block header len(ParentHashes) * daghash.HashSize should be
// added to this value
const BaseBlockHeaderPayload = 25 + 3*(externalapi.DomainHashSize)
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
@@ -29,7 +27,7 @@ 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)
const MaxBlockHeaderPayload = BaseBlockHeaderPayload + (MaxNumParentBlocks * daghash.HashSize)
// BlockHeader defines information about a block and is used in the kaspa
// block (MsgBlock) and headers (MsgHeader) messages.
@@ -38,17 +36,17 @@ type BlockHeader struct {
Version int32
// Hashes of the parent block headers in the blockDAG.
ParentHashes []*externalapi.DomainHash
ParentHashes []*daghash.Hash
// HashMerkleRoot is the merkle tree reference to hash of all transactions for the block.
HashMerkleRoot *externalapi.DomainHash
HashMerkleRoot *daghash.Hash
// AcceptedIDMerkleRoot is merkle tree reference to hash all transactions
// accepted form the block.Blues
AcceptedIDMerkleRoot *externalapi.DomainHash
AcceptedIDMerkleRoot *daghash.Hash
// UTXOCommitment is an ECMH UTXO commitment to the block UTXO.
UTXOCommitment *externalapi.DomainHash
UTXOCommitment *daghash.Hash
// Time the block was created.
Timestamp mstime.Time
@@ -62,16 +60,24 @@ type BlockHeader struct {
// NumParentBlocks return the number of entries in ParentHashes
func (h *BlockHeader) 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)
return byte(len(h.ParentHashes))
}
// BlockHash computes the block identifier hash for the given block header.
func (h *BlockHeader) BlockHash() *externalapi.DomainHash {
return consensusserialization.HeaderHash(BlockHeaderToDomainBlockHeader(h))
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
@@ -79,11 +85,53 @@ 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 []*externalapi.DomainHash, hashMerkleRoot *externalapi.DomainHash,
acceptedIDMerkleRoot *externalapi.DomainHash, utxoCommitment *externalapi.DomainHash, bits uint32, nonce uint64) *BlockHeader {
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.
@@ -98,3 +146,45 @@ func NewBlockHeader(version int32, parentHashes []*externalapi.DomainHash, hashM
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)
}

View File

@@ -5,13 +5,13 @@
package appmessage
import (
"reflect"
"testing"
"bytes"
"github.com/davecgh/go-spew/spew"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"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.
@@ -21,7 +21,7 @@ func TestBlockHeader(t *testing.T) {
t.Errorf("random.Uint64: Error generating nonce: %v", err)
}
hashes := []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash}
hashes := []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash}
merkleHash := mainnetGenesisMerkleRoot
acceptedIDMerkleRoot := exampleAcceptedIDMerkleRoot
@@ -33,7 +33,7 @@ func TestBlockHeader(t *testing.T) {
t.Errorf("NewBlockHeader: wrong prev hashes - got %v, want %v",
spew.Sprint(bh.ParentHashes), spew.Sprint(hashes))
}
if bh.HashMerkleRoot != merkleHash {
if !bh.HashMerkleRoot.IsEqual(merkleHash) {
t.Errorf("NewBlockHeader: wrong merkle root - got %v, want %v",
spew.Sprint(bh.HashMerkleRoot), spew.Sprint(merkleHash))
}
@@ -47,6 +47,263 @@ func TestBlockHeader(t *testing.T) {
}
}
// 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)
@@ -54,7 +311,7 @@ func TestIsGenesis(t *testing.T) {
baseBlockHdr := &BlockHeader{
Version: 1,
ParentHashes: []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash},
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
HashMerkleRoot: mainnetGenesisMerkleRoot,
Timestamp: timestamp,
Bits: bits,
@@ -62,7 +319,7 @@ func TestIsGenesis(t *testing.T) {
}
genesisBlockHdr := &BlockHeader{
Version: 1,
ParentHashes: []*externalapi.DomainHash{},
ParentHashes: []*daghash.Hash{},
HashMerkleRoot: mainnetGenesisMerkleRoot,
Timestamp: timestamp,
Bits: bits,

View File

@@ -6,8 +6,7 @@ package appmessage
import (
"fmt"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// MaxAddressesPerMsg is the maximum number of addresses that can be in a single
@@ -27,7 +26,7 @@ const MaxAddressesPerMsg = 1000
type MsgAddresses struct {
baseMessage
IncludeAllSubnetworks bool
SubnetworkID *externalapi.DomainSubnetworkID
SubnetworkID *subnetworkid.SubnetworkID
AddrList []*NetAddress
}
@@ -67,7 +66,7 @@ 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 *externalapi.DomainSubnetworkID) *MsgAddresses {
func NewMsgAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgAddresses {
return &MsgAddresses{
IncludeAllSubnetworks: includeAllSubnetworks,
SubnetworkID: subnetworkID,

View File

@@ -5,7 +5,13 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"bytes"
"fmt"
"io"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/util/daghash"
)
// defaultTransactionAlloc is the default size used for the backing array
@@ -15,18 +21,15 @@ import (
// backing array multiple times.
const defaultTransactionAlloc = 2048
// MaxMassAcceptedByBlock is the maximum total transaction mass a block may accept.
const MaxMassAcceptedByBlock = 10000000
// MaxMassPerBlock is the maximum total transaction mass a block may have.
const MaxMassPerBlock = 10000000
// MaxMassPerTx is the maximum total mass a transaction may have.
const MaxMassPerTx = MaxMassAcceptedByBlock / 2
const MaxMassPerTx = MaxMassPerBlock / 2
// MaxTxPerBlock is the maximum number of transactions that could
// possibly fit into a block.
const MaxTxPerBlock = (MaxMassAcceptedByBlock / minTxPayload) + 1
// MaxBlockParents is the maximum allowed number of parents for block.
const MaxBlockParents = 10
const MaxTxPerBlock = (MaxMassPerBlock / minTxPayload) + 1
// TxLoc holds locator data for the offset and length of where a transaction is
// located within a MsgBlock data buffer.
@@ -54,6 +57,161 @@ 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 {
@@ -66,12 +224,17 @@ 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 *externalapi.DomainSubnetworkID) {
func (msg *MsgBlock) ConvertToPartial(subnetworkID *subnetworkid.SubnetworkID) {
for _, tx := range msg.Transactions {
if tx.SubnetworkID != *subnetworkID {
if !tx.SubnetworkID.IsEqual(subnetworkID) {
tx.Payload = []byte{}
}
}

View File

@@ -5,15 +5,17 @@
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/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util/daghash"
)
// TestBlock tests the MsgBlock API.
@@ -69,31 +71,46 @@ func TestBlock(t *testing.T) {
}
}
func TestConvertToPartial(t *testing.T) {
localSubnetworkID := &externalapi.DomainSubnetworkID{0x12}
// 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) {
transactions := []struct {
subnetworkID *externalapi.DomainSubnetworkID
subnetworkID *subnetworkid.SubnetworkID
payload []byte
expectedPayloadLength int
}{
{
subnetworkID: &subnetworks.SubnetworkIDNative,
subnetworkID: subnetworkid.SubnetworkIDNative,
payload: []byte{},
expectedPayloadLength: 0,
},
{
subnetworkID: &subnetworks.SubnetworkIDRegistry,
subnetworkID: subnetworkid.SubnetworkIDRegistry,
payload: []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08},
expectedPayloadLength: 0,
},
{
subnetworkID: localSubnetworkID,
subnetworkID: &subnetworkid.SubnetworkID{123},
payload: []byte{0x01},
expectedPayloadLength: 1,
},
{
subnetworkID: &externalapi.DomainSubnetworkID{0x34},
subnetworkID: &subnetworkid.SubnetworkID{234},
payload: []byte{0x02},
expectedPayloadLength: 0,
},
@@ -105,24 +122,379 @@ func TestConvertToPartial(t *testing.T) {
block.Transactions = append(block.Transactions, NewSubnetworkMsgTx(1, nil, nil, transaction.subnetworkID, 0, payload))
}
block.ConvertToPartial(localSubnetworkID)
block.ConvertToPartial(&subnetworkid.SubnetworkID{123})
for _, testTransaction := range transactions {
for _, transaction := range transactions {
var subnetworkTx *MsgTx
for _, blockTransaction := range block.Transactions {
if blockTransaction.SubnetworkID == *testTransaction.subnetworkID {
subnetworkTx = blockTransaction
for _, tx := range block.Transactions {
if tx.SubnetworkID.IsEqual(transaction.subnetworkID) {
subnetworkTx = tx
}
}
if subnetworkTx == nil {
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", testTransaction.subnetworkID)
t.Errorf("ConvertToPartial: subnetworkID '%s' not found in block!", transaction.subnetworkID)
continue
}
payloadLength := len(subnetworkTx.Payload)
if payloadLength != testTransaction.expectedPayloadLength {
if payloadLength != transaction.expectedPayloadLength {
t.Errorf("ConvertToPartial: unexpected payload length for subnetwork '%s': expected: %d, got: %d",
testTransaction.subnetworkID, testTransaction.expectedPayloadLength, payloadLength)
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
}
}
}
@@ -131,7 +503,7 @@ func TestConvertToPartial(t *testing.T) {
var blockOne = MsgBlock{
Header: BlockHeader{
Version: 1,
ParentHashes: []*externalapi.DomainHash{mainnetGenesisHash, simnetGenesisHash},
ParentHashes: []*daghash.Hash{mainnetGenesisHash, simnetGenesisHash},
HashMerkleRoot: mainnetGenesisMerkleRoot,
AcceptedIDMerkleRoot: exampleAcceptedIDMerkleRoot,
UTXOCommitment: exampleUTXOCommitment,
@@ -144,7 +516,7 @@ var blockOne = MsgBlock{
[]*TxIn{
{
PreviousOutpoint: Outpoint{
TxID: externalapi.DomainTransactionID{},
TxID: daghash.TxID{},
Index: 0xffffffff,
},
SignatureScript: []byte{

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// 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 []*externalapi.DomainHash
BlockLocatorHashes []*daghash.Hash
}
// 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 []*externalapi.DomainHash) *MsgBlockLocator {
func NewMsgBlockLocator(locatorHashes []*daghash.Hash) *MsgBlockLocator {
return &MsgBlockLocator{
BlockLocatorHashes: locatorHashes,
}

View File

@@ -3,22 +3,19 @@ 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 := hashes.FromString(hashStr)
locatorHash, err := daghash.NewHashFromStr(hashStr)
if err != nil {
t.Errorf("NewHashFromStr: %v", err)
}
msg := NewMsgBlockLocator([]*externalapi.DomainHash{locatorHash})
msg := NewMsgBlockLocator([]*daghash.Hash{locatorHash})
// Ensure the command is expected value.
wantCmd := MessageCommand(10)

View File

@@ -5,6 +5,7 @@
package appmessage
import (
"bytes"
"reflect"
"testing"
@@ -63,3 +64,55 @@ 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
}
}
}

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// 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 *externalapi.DomainHash
Hash *daghash.Hash
}
// 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 *externalapi.DomainHash) *MsgInvRelayBlock {
func NewMsgInvBlock(hash *daghash.Hash) *MsgInvRelayBlock {
return &MsgInvRelayBlock{
Hash: hash,
}

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// 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 []*externalapi.DomainTransactionID
TxIDs []*daghash.TxID
}
// 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 []*externalapi.DomainTransactionID) *MsgInvTransaction {
func NewMsgInvTransaction(ids []*daghash.TxID) *MsgInvTransaction {
return &MsgInvTransaction{
TxIDs: ids,
}

View File

@@ -5,7 +5,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// MsgRequestAddresses implements the Message interface and represents a kaspa
@@ -17,7 +17,7 @@ import (
type MsgRequestAddresses struct {
baseMessage
IncludeAllSubnetworks bool
SubnetworkID *externalapi.DomainSubnetworkID
SubnetworkID *subnetworkid.SubnetworkID
}
// 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 *externalapi.DomainSubnetworkID) *MsgRequestAddresses {
func NewMsgRequestAddresses(includeAllSubnetworks bool, subnetworkID *subnetworkid.SubnetworkID) *MsgRequestAddresses {
return &MsgRequestAddresses{
IncludeAllSubnetworks: includeAllSubnetworks,
SubnetworkID: subnetworkID,

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// MsgRequestBlockLocator implements the Message interface and represents a kaspa
@@ -10,8 +10,8 @@ import (
// The locator is returned via a locator message (MsgBlockLocator).
type MsgRequestBlockLocator struct {
baseMessage
HighHash *externalapi.DomainHash
LowHash *externalapi.DomainHash
HighHash *daghash.Hash
LowHash *daghash.Hash
}
// Command returns the protocol command string for the message. This is part
@@ -23,7 +23,7 @@ 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 *externalapi.DomainHash) *MsgRequestBlockLocator {
func NewMsgRequestBlockLocator(highHash, lowHash *daghash.Hash) *MsgRequestBlockLocator {
return &MsgRequestBlockLocator{
HighHash: highHash,
LowHash: lowHash,

View File

@@ -3,22 +3,20 @@ package appmessage
import (
"testing"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/util/daghash"
)
// TestRequestBlockLocator tests the MsgRequestBlockLocator API.
func TestRequestBlockLocator(t *testing.T) {
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
highHash, err := hashes.FromString(hashStr)
highHash, err := daghash.NewHashFromStr(hashStr)
if err != nil {
t.Errorf("NewHashFromStr: %v", err)
}
// Ensure the command is expected value.
wantCmd := MessageCommand(9)
msg := NewMsgRequestBlockLocator(highHash, &externalapi.DomainHash{})
msg := NewMsgRequestBlockLocator(highHash, &daghash.ZeroHash)
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgRequestBlockLocator: wrong command - got %v want %v",
cmd, wantCmd)

View File

@@ -5,7 +5,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// MsgRequestIBDBlocks implements the Message interface and represents a kaspa
@@ -13,8 +13,8 @@ import (
// low hash and until the high hash.
type MsgRequestIBDBlocks struct {
baseMessage
LowHash *externalapi.DomainHash
HighHash *externalapi.DomainHash
LowHash *daghash.Hash
HighHash *daghash.Hash
}
// Command returns the protocol command string for the message. This is part
@@ -26,7 +26,7 @@ func (msg *MsgRequestIBDBlocks) Command() MessageCommand {
// 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 *externalapi.DomainHash) *MsgRequestIBDBlocks {
func NewMsgRequstIBDBlocks(lowHash, highHash *daghash.Hash) *MsgRequestIBDBlocks {
return &MsgRequestIBDBlocks{
LowHash: lowHash,
HighHash: highHash,

View File

@@ -7,26 +7,26 @@ package appmessage
import (
"testing"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/util/daghash"
)
// TestRequstIBDBlocks tests the MsgRequestIBDBlocks API.
func TestRequstIBDBlocks(t *testing.T) {
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
lowHash, err := hashes.FromString(hashStr)
lowHash, err := daghash.NewHashFromStr(hashStr)
if err != nil {
t.Errorf("NewHashFromStr: %v", err)
}
hashStr = "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
highHash, err := hashes.FromString(hashStr)
highHash, err := daghash.NewHashFromStr(hashStr)
if err != nil {
t.Errorf("NewHashFromStr: %v", err)
}
// Ensure we get the same data back out.
msg := NewMsgRequstIBDBlocks(lowHash, highHash)
if *msg.HighHash != *highHash {
if !msg.HighHash.IsEqual(highHash) {
t.Errorf("NewMsgRequstIBDBlocks: wrong high hash - got %v, want %v",
msg.HighHash, highHash)
}

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// MsgRequestRelayBlocksHashes is the maximum number of hashes that can
@@ -13,7 +13,7 @@ const MsgRequestRelayBlocksHashes = MaxInvPerMsg
// relay protocol.
type MsgRequestRelayBlocks struct {
baseMessage
Hashes []*externalapi.DomainHash
Hashes []*daghash.Hash
}
// 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 []*externalapi.DomainHash) *MsgRequestRelayBlocks {
func NewMsgRequestRelayBlocks(hashes []*daghash.Hash) *MsgRequestRelayBlocks {
return &MsgRequestRelayBlocks{
Hashes: hashes,
}

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// MaxInvPerRequestTransactionsMsg is the maximum number of hashes that can
@@ -13,7 +13,7 @@ const MaxInvPerRequestTransactionsMsg = MaxInvPerMsg
// transactions relay protocol.
type MsgRequestTransactions struct {
baseMessage
IDs []*externalapi.DomainTransactionID
IDs []*daghash.TxID
}
// 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 []*externalapi.DomainTransactionID) *MsgRequestTransactions {
func NewMsgRequestTransactions(ids []*daghash.TxID) *MsgRequestTransactions {
return &MsgRequestTransactions{
IDs: ids,
}

View File

@@ -1,7 +1,7 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// MsgSelectedTip implements the Message interface and represents a kaspa
@@ -10,7 +10,7 @@ import (
type MsgSelectedTip struct {
baseMessage
// The selected tip hash of the generator of the message.
SelectedTipHash *externalapi.DomainHash
SelectedTipHash *daghash.Hash
}
// Command returns the protocol command string for the message. This is part
@@ -21,7 +21,7 @@ func (msg *MsgSelectedTip) Command() MessageCommand {
// NewMsgSelectedTip returns a new kaspa selectedtip message that conforms to the
// Message interface.
func NewMsgSelectedTip(selectedTipHash *externalapi.DomainHash) *MsgSelectedTip {
func NewMsgSelectedTip(selectedTipHash *daghash.Hash) *MsgSelectedTip {
return &MsgSelectedTip{
SelectedTipHash: selectedTipHash,
}

View File

@@ -1,16 +1,16 @@
package appmessage
import (
"github.com/kaspanet/kaspad/util/daghash"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
// TestSelectedTip tests the MsgSelectedTip API.
func TestSelectedTip(t *testing.T) {
// Ensure the command is expected value.
wantCmd := MessageCommand(11)
msg := NewMsgSelectedTip(&externalapi.DomainHash{})
msg := NewMsgSelectedTip(&daghash.ZeroHash)
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgSelectedTip: wrong command - got %v want %v",
cmd, wantCmd)

View File

@@ -5,14 +5,14 @@
package appmessage
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// 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 *externalapi.DomainTransactionID
ID *daghash.TxID
}
// 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 *externalapi.DomainTransactionID) *MsgTransactionNotFound {
func NewMsgTransactionNotFound(id *daghash.TxID) *MsgTransactionNotFound {
return &MsgTransactionNotFound{
ID: id,
}

View File

@@ -6,16 +6,14 @@ package appmessage
import (
"encoding/binary"
"fmt"
"io"
"math"
"strconv"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/binaryserializer"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
const (
@@ -61,7 +59,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 + externalapi.DomainHashSize
minTxInPayload = 9 + daghash.HashSize
// maxTxInPerMessage is the maximum number of transactions inputs that
// a transaction which fits into a message could possibly have.
@@ -83,18 +81,102 @@ 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 externalapi.DomainTransactionID
TxID daghash.TxID
Index uint32
}
// NewOutpoint returns a new kaspa transaction outpoint point with the
// provided hash and index.
func NewOutpoint(txID *externalapi.DomainTransactionID, index uint32) *Outpoint {
func NewOutpoint(txID *daghash.TxID, index uint32) *Outpoint {
return &Outpoint{
TxID: *txID,
Index: index,
@@ -109,9 +191,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*externalapi.DomainHashSize+1, 2*externalapi.DomainHashSize+1+10)
buf := make([]byte, 2*daghash.HashSize+1, 2*daghash.HashSize+1+10)
copy(buf, o.TxID.String())
buf[2*externalapi.DomainHashSize] = ':'
buf[2*daghash.HashSize] = ':'
buf = strconv.AppendUint(buf, uint64(o.Index), 10)
return string(buf)
}
@@ -123,6 +205,27 @@ 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.
@@ -140,6 +243,14 @@ 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 {
@@ -161,9 +272,9 @@ type MsgTx struct {
TxIn []*TxIn
TxOut []*TxOut
LockTime uint64
SubnetworkID externalapi.DomainSubnetworkID
SubnetworkID subnetworkid.SubnetworkID
Gas uint64
PayloadHash *externalapi.DomainHash
PayloadHash *daghash.Hash
Payload []byte
}
@@ -184,17 +295,41 @@ 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 == subnetworks.SubnetworkIDCoinbase
return msg.SubnetworkID.IsEqual(subnetworkid.SubnetworkIDCoinbase)
}
// TxHash generates the Hash for the transaction.
func (msg *MsgTx) TxHash() *externalapi.DomainHash {
return consensusserialization.TransactionHash(MsgTxToDomainTransaction(msg))
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
}
// TxID generates the Hash for the transaction without the signature script, gas and payload fields.
func (msg *MsgTx) TxID() *externalapi.DomainTransactionID {
return consensusserialization.TransactionID(MsgTxToDomainTransaction(msg))
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
}
// Copy creates a deep copy of a transaction so that the original does not get
@@ -222,7 +357,7 @@ func (msg *MsgTx) Copy() *MsgTx {
// Deep copy the old previous outpoint.
oldOutpoint := oldTxIn.PreviousOutpoint
newOutpoint := Outpoint{}
newOutpoint.TxID = oldOutpoint.TxID
newOutpoint.TxID.SetBytes(oldOutpoint.TxID[:])
newOutpoint.Index = oldOutpoint.Index
// Deep copy the old signature script.
@@ -268,6 +403,368 @@ 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 {
@@ -280,14 +777,52 @@ 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 *externalapi.DomainSubnetworkID) bool {
func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *subnetworkid.SubnetworkID) bool {
return subnetworkID == nil ||
*subnetworkID == subnetworks.SubnetworkIDNative ||
*subnetworkID == msg.SubnetworkID
subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) ||
subnetworkID.IsEqual(&msg.SubnetworkID)
}
// newMsgTx returns a new tx message that conforms to the Message interface.
@@ -299,7 +834,7 @@ func (msg *MsgTx) IsSubnetworkCompatible(subnetworkID *externalapi.DomainSubnetw
// 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 *externalapi.DomainSubnetworkID,
func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetworkid.SubnetworkID,
gas uint64, payload []byte, lockTime uint64) *MsgTx {
if txIn == nil {
@@ -310,9 +845,9 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externa
txOut = make([]*TxOut, 0, defaultTxInOutAlloc)
}
var payloadHash *externalapi.DomainHash
if *subnetworkID != subnetworks.SubnetworkIDNative {
payloadHash = hashes.HashData(payload)
var payloadHash *daghash.Hash
if !subnetworkID.IsEqual(subnetworkid.SubnetworkIDNative) {
payloadHash = daghash.DoubleHashP(payload)
}
return &MsgTx{
@@ -329,11 +864,11 @@ func newMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *externa
// NewNativeMsgTx returns a new tx message in the native subnetwork
func NewNativeMsgTx(version int32, txIn []*TxIn, txOut []*TxOut) *MsgTx {
return newMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDNative, 0, nil, 0)
return newMsgTx(version, txIn, txOut, subnetworkid.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 *externalapi.DomainSubnetworkID,
func NewSubnetworkMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, subnetworkID *subnetworkid.SubnetworkID,
gas uint64, payload []byte) *MsgTx {
return newMsgTx(version, txIn, txOut, subnetworkID, gas, payload, 0)
@@ -343,7 +878,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, &subnetworks.SubnetworkIDNative, 0, nil, locktime)
return newMsgTx(version, txIn, txOut, subnetworkid.SubnetworkIDNative, 0, nil, locktime)
}
// NewRegistryMsgTx creates a new MsgTx that registers a new subnetwork
@@ -351,5 +886,119 @@ func NewRegistryMsgTx(version int32, txIn []*TxIn, txOut []*TxOut, gasLimit uint
payload := make([]byte, 8)
binary.LittleEndian.PutUint64(payload, gasLimit)
return NewSubnetworkMsgTx(version, txIn, txOut, &subnetworks.SubnetworkIDRegistry, 0, payload)
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)
}

View File

@@ -7,17 +7,16 @@ package appmessage
import (
"bytes"
"fmt"
"github.com/pkg/errors"
"io"
"math"
"reflect"
"testing"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionid"
"unsafe"
"github.com/davecgh/go-spew/spew"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// TestTx tests the MsgTx API.
@@ -25,7 +24,7 @@ func TestTx(t *testing.T) {
pver := ProtocolVersion
txIDStr := "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506"
txID, err := transactionid.FromString(txIDStr)
txID, err := daghash.NewTxIDFromStr(txIDStr)
if err != nil {
t.Errorf("NewTxIDFromStr: %v", err)
}
@@ -52,7 +51,7 @@ func TestTx(t *testing.T) {
// testing package functionality.
prevOutIndex := uint32(1)
prevOut := NewOutpoint(txID, prevOutIndex)
if prevOut.TxID != *txID {
if !prevOut.TxID.IsEqual(txID) {
t.Errorf("NewOutpoint: wrong ID - got %v, want %v",
spew.Sprint(&prevOut.TxID), spew.Sprint(txID))
}
@@ -131,8 +130,8 @@ func TestTx(t *testing.T) {
// TestTxHash tests the ability to generate the hash of a transaction accurately.
func TestTxHashAndID(t *testing.T) {
txID1Str := "a3d29c39bfb578235e4813cc8138a9ba10def63acad193a7a880159624840d7f"
wantTxID1, err := transactionid.FromString(txID1Str)
txID1Str := "edca872f27279674c7a52192b32fd68b8b8be714bfea52d98b2c3c86c30e85c6"
wantTxID1, err := daghash.NewTxIDFromStr(txID1Str)
if err != nil {
t.Errorf("NewTxIDFromStr: %v", err)
return
@@ -141,7 +140,7 @@ func TestTxHashAndID(t *testing.T) {
// A coinbase transaction
txIn := &TxIn{
PreviousOutpoint: Outpoint{
TxID: externalapi.DomainTransactionID{},
TxID: daghash.TxID{},
Index: math.MaxUint32,
},
SignatureScript: []byte{0x04, 0x31, 0xdc, 0x00, 0x1b, 0x01, 0x62},
@@ -163,31 +162,31 @@ func TestTxHashAndID(t *testing.T) {
0xac, // OP_CHECKSIG
},
}
tx1 := NewSubnetworkMsgTx(1, []*TxIn{txIn}, []*TxOut{txOut}, &subnetworks.SubnetworkIDCoinbase, 0, nil)
tx1 := NewSubnetworkMsgTx(1, []*TxIn{txIn}, []*TxOut{txOut}, subnetworkid.SubnetworkIDCoinbase, 0, nil)
// Ensure the hash produced is expected.
tx1Hash := tx1.TxHash()
if *tx1Hash != (externalapi.DomainHash)(*wantTxID1) {
if !tx1Hash.IsEqual((*daghash.Hash)(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 != *wantTxID1 {
if !tx1ID.IsEqual(wantTxID1) {
t.Errorf("TxID: wrong ID - got %v, want %v",
spew.Sprint(tx1ID), spew.Sprint(wantTxID1))
}
hash2Str := "c84f3009b337aaa3adeb2ffd41010d5f62dd773ca25b39c908a77da91f87b729"
wantHash2, err := hashes.FromString(hash2Str)
hash2Str := "b11924b7eeffea821522222576c53dc5b8ddd97602f81e5e124d2626646d74ca"
wantHash2, err := daghash.NewHashFromStr(hash2Str)
if err != nil {
t.Errorf("NewTxIDFromStr: %v", err)
return
}
id2Str := "7c919f676109743a1271a88beeb43849a6f9cc653f6082e59a7266f3df4802b9"
wantID2, err := transactionid.FromString(id2Str)
id2Str := "750499ae9e6d44961ef8bad8af27a44dd4bcbea166b71baf181e8d3997e1ff72"
wantID2, err := daghash.NewTxIDFromStr(id2Str)
if err != nil {
t.Errorf("NewTxIDFromStr: %v", err)
return
@@ -196,7 +195,7 @@ func TestTxHashAndID(t *testing.T) {
txIns := []*TxIn{{
PreviousOutpoint: Outpoint{
Index: 0,
TxID: externalapi.DomainTransactionID{1, 2, 3},
TxID: daghash.TxID{1, 2, 3},
},
SignatureScript: []byte{
0x49, 0x30, 0x46, 0x02, 0x21, 0x00, 0xDA, 0x0D, 0xC6, 0xAE, 0xCE, 0xFE, 0x1E, 0x06, 0xEF, 0xDF,
@@ -227,29 +226,717 @@ func TestTxHashAndID(t *testing.T) {
},
},
}
tx2 := NewSubnetworkMsgTx(1, txIns, txOuts, &externalapi.DomainSubnetworkID{1, 2, 3}, 0, payload)
tx2 := NewSubnetworkMsgTx(1, txIns, txOuts, &subnetworkid.SubnetworkID{1, 2, 3}, 0, payload)
// Ensure the hash produced is expected.
tx2Hash := tx2.TxHash()
if *tx2Hash != *wantHash2 {
if !tx2Hash.IsEqual(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 != *wantID2 {
if !tx2ID.IsEqual(wantID2) {
t.Errorf("TxID: wrong ID - got %v, want %v",
spew.Sprint(tx2ID), spew.Sprint(wantID2))
}
if *tx2ID == (externalapi.DomainTransactionID)(*tx2Hash) {
if tx2ID.IsEqual((*daghash.TxID)(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 != (externalapi.DomainTransactionID)(*newTx2Hash) {
if !tx2ID.IsEqual((*daghash.TxID)(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}

View File

@@ -6,13 +6,14 @@ package appmessage
import (
"fmt"
"strings"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/version"
"strings"
"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,13 +55,13 @@ type MsgVersion struct {
UserAgent string
// The selected tip hash of the generator of the version message.
SelectedTipHash *externalapi.DomainHash
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 *externalapi.DomainSubnetworkID
SubnetworkID *subnetworkid.SubnetworkID
}
// HasService returns whether the specified service is supported by the peer
@@ -85,7 +86,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 *externalapi.DomainHash, subnetworkID *externalapi.DomainSubnetworkID) *MsgVersion {
selectedTipHash *daghash.Hash, subnetworkID *subnetworkid.SubnetworkID) *MsgVersion {
// Limit the timestamp to one millisecond precision since the protocol
// doesn't support better.

View File

@@ -5,13 +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/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/id"
)
// TestVersion tests the MsgVersion API.
@@ -19,7 +18,7 @@ func TestVersion(t *testing.T) {
pver := ProtocolVersion
// Create version message data.
selectedTipHash := &externalapi.DomainHash{12, 34}
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()
@@ -45,7 +44,7 @@ func TestVersion(t *testing.T) {
t.Errorf("NewMsgVersion: wrong user agent - got %v, want %v",
msg.UserAgent, DefaultUserAgent)
}
if *msg.SelectedTipHash != *selectedTipHash {
if !msg.SelectedTipHash.IsEqual(selectedTipHash) {
t.Errorf("NewMsgVersion: wrong selected tip hash - got %s, want %s",
msg.SelectedTipHash, selectedTipHash)
}

View File

@@ -6,6 +6,8 @@ type GetBlockRequestMessage struct {
baseMessage
Hash string
SubnetworkID string
IncludeBlockHex bool
IncludeBlockVerboseData bool
IncludeTransactionVerboseData bool
}
@@ -15,10 +17,13 @@ func (msg *GetBlockRequestMessage) Command() MessageCommand {
}
// NewGetBlockRequestMessage returns a instance of the message
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeTransactionVerboseData bool) *GetBlockRequestMessage {
func NewGetBlockRequestMessage(hash string, subnetworkID string, includeBlockHex bool,
includeBlockVerboseData bool, includeTransactionVerboseData bool) *GetBlockRequestMessage {
return &GetBlockRequestMessage{
Hash: hash,
SubnetworkID: subnetworkID,
IncludeBlockHex: includeBlockHex,
IncludeBlockVerboseData: includeBlockVerboseData,
IncludeTransactionVerboseData: includeTransactionVerboseData,
}
}
@@ -27,6 +32,7 @@ func NewGetBlockRequestMessage(hash string, subnetworkID string, includeTransact
// its respective RPC message
type GetBlockResponseMessage struct {
baseMessage
BlockHex string
BlockVerboseData *BlockVerboseData
Error *RPCError
@@ -45,6 +51,10 @@ 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
@@ -58,13 +68,16 @@ type BlockVerboseData struct {
Difficulty float64
ParentHashes []string
SelectedParentHash string
ChildHashes []string
AcceptedBlockHashes []string
}
// TransactionVerboseData holds verbose data about a transaction
type TransactionVerboseData struct {
Hex string
TxID string
Hash string
Size uint64
Size int32
Version int32
LockTime uint64
SubnetworkID string
@@ -74,6 +87,8 @@ type TransactionVerboseData struct {
TransactionVerboseInputs []*TransactionVerboseInput
TransactionVerboseOutputs []*TransactionVerboseOutput
BlockHash string
AcceptedBy string
IsInMempool bool
Time uint64
BlockTime uint64
}

View File

@@ -20,12 +20,11 @@ func NewGetBlockDAGInfoRequestMessage() *GetBlockDAGInfoRequestMessage {
// its respective RPC message
type GetBlockDAGInfoResponseMessage struct {
baseMessage
NetworkName string
BlockCount uint64
TipHashes []string
VirtualParentHashes []string
Difficulty float64
PastMedianTime int64
NetworkName string
BlockCount uint64
TipHashes []string
Difficulty float64
PastMedianTime int64
Error *RPCError
}

View File

@@ -5,6 +5,7 @@ package appmessage
type GetBlockTemplateRequestMessage struct {
baseMessage
PayAddress string
LongPollID string
}
// Command returns the protocol command string for the message
@@ -13,9 +14,10 @@ func (msg *GetBlockTemplateRequestMessage) Command() MessageCommand {
}
// NewGetBlockTemplateRequestMessage returns a instance of the message
func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateRequestMessage {
func NewGetBlockTemplateRequestMessage(payAddress string, longPollID string) *GetBlockTemplateRequestMessage {
return &GetBlockTemplateRequestMessage{
PayAddress: payAddress,
LongPollID: longPollID,
}
}
@@ -23,7 +25,23 @@ func NewGetBlockTemplateRequestMessage(payAddress string) *GetBlockTemplateReque
// its respective RPC message
type GetBlockTemplateResponseMessage struct {
baseMessage
MsgBlock *MsgBlock
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
Error *RPCError
}
@@ -34,6 +52,27 @@ func (msg *GetBlockTemplateResponseMessage) Command() MessageCommand {
}
// NewGetBlockTemplateResponseMessage returns a instance of the message
func NewGetBlockTemplateResponseMessage(msgBlock *MsgBlock) *GetBlockTemplateResponseMessage {
return &GetBlockTemplateResponseMessage{MsgBlock: msgBlock}
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{}
}

View File

@@ -1,38 +0,0 @@
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,
}
}

View File

@@ -21,28 +21,15 @@ 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(fee uint64, transactionVerboseData *TransactionVerboseData) *GetMempoolEntryResponseMessage {
return &GetMempoolEntryResponseMessage{
Entry: &MempoolEntry{
Fee: fee,
TransactionVerboseData: transactionVerboseData,
},
}
func NewGetMempoolEntryResponseMessage() *GetMempoolEntryResponseMessage {
return &GetMempoolEntryResponseMessage{}
}

View File

@@ -1,72 +0,0 @@
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,
}
}

View File

@@ -1,37 +0,0 @@
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{}
}

View File

@@ -1,34 +0,0 @@
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{}
}

View File

@@ -4,7 +4,7 @@ package appmessage
// its respective RPC message
type SubmitBlockRequestMessage struct {
baseMessage
Block *MsgBlock
BlockHex string
}
// 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(block *MsgBlock) *SubmitBlockRequestMessage {
func NewSubmitBlockRequestMessage(blockHex string) *SubmitBlockRequestMessage {
return &SubmitBlockRequestMessage{
Block: block,
BlockHex: blockHex,
}
}

View File

@@ -4,7 +4,7 @@ package appmessage
// its respective RPC message
type SubmitTransactionRequestMessage struct {
baseMessage
Transaction *MsgTx
TransactionHex string
}
// 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(transaction *MsgTx) *SubmitTransactionRequestMessage {
func NewSubmitTransactionRequestMessage(transactionHex string) *SubmitTransactionRequestMessage {
return &SubmitTransactionRequestMessage{
Transaction: transaction,
TransactionHex: transactionHex,
}
}

View File

@@ -1,164 +0,0 @@
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)
}
err = a.addressManager.Stop()
if err != nil {
log.Errorf("Error stopping address manager: %s", 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(cfg, db)
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, 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)
})
}
}
// 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
}

View File

@@ -5,11 +5,11 @@
package blocklogger
import (
"github.com/kaspanet/kaspad/util/mstime"
"sync"
"time"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util"
)
var (
@@ -22,12 +22,12 @@ 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 *externalapi.DomainBlock) error {
func LogBlock(block *util.Block) error {
mtx.Lock()
defer mtx.Unlock()
receivedLogBlocks++
receivedLogTx += int64(len(block.Transactions))
receivedLogTx += int64(len(block.MsgBlock().Transactions))
now := mstime.Now()
duration := now.Sub(lastBlockLogTime)
@@ -48,9 +48,14 @@ func LogBlock(block *externalapi.DomainBlock) error {
txStr = "transaction"
}
log.Infof("Processed %d %s in the last %s (%d %s, %s)",
blueScore, err := block.BlueScore()
if err != nil {
return err
}
log.Infof("Processed %d %s in the last %s (%d %s, blue score %d, %s)",
receivedLogBlocks, blockStr, tDuration, receivedLogTx,
txStr, mstime.UnixMilliseconds(block.Header.TimeInMilliseconds))
txStr, blueScore, block.MsgBlock().Header.Timestamp)
receivedLogBlocks = 0
receivedLogTx = 0

View File

@@ -3,32 +3,29 @@ package flowcontext
import (
"sync/atomic"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"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 *externalapi.DomainBlock) error {
f.Domain().MiningManager().HandleNewBlockTransactions(block.Transactions)
func (f *FlowContext) OnNewBlock(block *util.Block) error {
transactionsAcceptedToMempool, err := f.txPool.HandleNewBlock(block)
if err != nil {
return err
}
if f.onBlockAddedToDAGHandler != nil {
err := f.onBlockAddedToDAGHandler(block)
if err != nil {
return err
}
f.onBlockAddedToDAGHandler(block)
}
return nil
return f.broadcastTransactionsAfterBlockAdded(block, transactionsAcceptedToMempool)
}
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
block *externalapi.DomainBlock, transactionsAcceptedToMempool []*externalapi.DomainTransaction) error {
func (f *FlowContext) broadcastTransactionsAfterBlockAdded(block *util.Block, transactionsAcceptedToMempool []*util.Tx) error {
f.updateTransactionsToRebroadcast(block)
// Don't relay transactions when in IBD.
@@ -36,14 +33,14 @@ func (f *FlowContext) broadcastTransactionsAfterBlockAdded(
return nil
}
var txIDsToRebroadcast []*externalapi.DomainTransactionID
var txIDsToRebroadcast []*daghash.TxID
if f.shouldRebroadcastTransactions() {
txIDsToRebroadcast = f.txIDsToRebroadcast()
}
txIDsToBroadcast := make([]*externalapi.DomainTransactionID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
txIDsToBroadcast := make([]*daghash.TxID, len(transactionsAcceptedToMempool)+len(txIDsToRebroadcast))
for i, tx := range transactionsAcceptedToMempool {
txIDsToBroadcast[i] = consensusserialization.TransactionID(tx)
txIDsToBroadcast[i] = tx.ID()
}
offset := len(transactionsAcceptedToMempool)
for i, txID := range txIDsToRebroadcast {
@@ -67,8 +64,8 @@ func (f *FlowContext) SharedRequestedBlocks() *blockrelay.SharedRequestedBlocks
}
// AddBlock adds the given block to the DAG and propagates it.
func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error {
err := f.Domain().Consensus().ValidateAndInsertBlock(block)
func (f *FlowContext) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
_, _, err := f.DAG().ProcessBlock(block, flags)
if err != nil {
return err
}
@@ -76,5 +73,5 @@ func (f *FlowContext) AddBlock(block *externalapi.DomainBlock) error {
if err != nil {
return err
}
return f.Broadcast(appmessage.NewMsgInvBlock(consensusserialization.BlockHash(block)))
return f.Broadcast(appmessage.NewMsgInvBlock(block.Hash()))
}

View File

@@ -0,0 +1,8 @@
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
}

View File

@@ -1,10 +0,0 @@
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
}

View File

@@ -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"
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 *externalapi.DomainBlock) error
type OnBlockAddedToDAGHandler func(block *util.Block)
// OnTransactionAddedToMempoolHandler is a handler function that's triggered
// when a transaction is added to the mempool
@@ -31,7 +31,8 @@ type OnTransactionAddedToMempoolHandler func()
type FlowContext struct {
cfg *config.Config
netAdapter *netadapter.NetAdapter
domain domain.Domain
txPool *mempool.TxPool
dag *blockdag.BlockDAG
addressManager *addressmanager.AddressManager
connectionManager *connmanager.ConnectionManager
@@ -39,7 +40,7 @@ type FlowContext struct {
onTransactionAddedToMempoolHandler OnTransactionAddedToMempoolHandler
transactionsToRebroadcastLock sync.Mutex
transactionsToRebroadcast map[externalapi.DomainTransactionID]*externalapi.DomainTransaction
transactionsToRebroadcast map[daghash.TxID]*util.Tx
lastRebroadcastTime time.Time
sharedRequestedTransactions *relaytransactions.SharedRequestedTransactions
@@ -54,19 +55,21 @@ type FlowContext struct {
}
// New returns a new instance of FlowContext.
func New(cfg *config.Config, domain domain.Domain, addressManager *addressmanager.AddressManager,
netAdapter *netadapter.NetAdapter, connectionManager *connmanager.ConnectionManager) *FlowContext {
func New(cfg *config.Config, dag *blockdag.BlockDAG, addressManager *addressmanager.AddressManager,
txPool *mempool.TxPool, netAdapter *netadapter.NetAdapter,
connectionManager *connmanager.ConnectionManager) *FlowContext {
return &FlowContext{
cfg: cfg,
netAdapter: netAdapter,
domain: domain,
dag: dag,
addressManager: addressManager,
connectionManager: connectionManager,
txPool: txPool,
sharedRequestedTransactions: relaytransactions.NewSharedRequestedTransactions(),
sharedRequestedBlocks: blockrelay.NewSharedRequestedBlocks(),
peers: make(map[id.ID]*peerpkg.Peer),
transactionsToRebroadcast: make(map[externalapi.DomainTransactionID]*externalapi.DomainTransaction),
transactionsToRebroadcast: make(map[daghash.TxID]*util.Tx),
}
}

View File

@@ -4,35 +4,29 @@ import (
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/util/mstime"
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() error {
func (f *FlowContext) StartIBDIfRequired() {
f.startIBDMutex.Lock()
defer f.startIBDMutex.Unlock()
if f.IsInIBD() {
return nil
return
}
peer, err := f.selectPeerForIBD()
if err != nil {
return err
}
peer := f.selectPeerForIBD(f.dag)
if peer == nil {
spawn("StartIBDIfRequired-requestSelectedTipsIfRequired", f.requestSelectedTipsIfRequired)
return nil
return
}
atomic.StoreUint32(&f.isInIBD, 1)
f.ibdPeer = peer
spawn("StartIBDIfRequired-peer.StartIBD", peer.StartIBD)
return nil
}
// IsInIBD is true if IBD is currently running
@@ -42,42 +36,29 @@ func (f *FlowContext) IsInIBD() bool {
// selectPeerForIBD returns the first peer whose selected tip
// hash is not in our DAG
func (f *FlowContext) selectPeerForIBD() (*peerpkg.Peer, error) {
func (f *FlowContext) selectPeerForIBD(dag *blockdag.BlockDAG) *peerpkg.Peer {
f.peersMutex.RLock()
defer f.peersMutex.RUnlock()
for _, peer := range f.peers {
peerSelectedTipHash := peer.SelectedTipHash()
blockInfo, err := f.domain.Consensus().GetBlockInfo(peerSelectedTipHash)
if err != nil {
return nil, err
}
if !blockInfo.Exists {
return peer, nil
if !dag.IsInDAG(peerSelectedTipHash) {
return peer
}
}
return nil, nil
return nil
}
func (f *FlowContext) requestSelectedTipsIfRequired() {
dagTimeCurrent, err := f.shouldRequestSelectedTips()
if err != nil {
panic(err)
}
if dagTimeCurrent {
if f.isDAGTimeCurrent() {
return
}
f.requestSelectedTips()
}
func (f *FlowContext) shouldRequestSelectedTips() (bool, error) {
func (f *FlowContext) isDAGTimeCurrent() bool {
const minDurationToRequestSelectedTips = time.Minute
virtualSelectedParent, err := f.domain.Consensus().GetVirtualSelectedParent()
if err != nil {
return false, err
}
virtualSelectedParentTime := mstime.UnixMilliseconds(virtualSelectedParent.Header.TimeInMilliseconds)
return mstime.Now().Sub(virtualSelectedParentTime) > minDurationToRequestSelectedTips, nil
return f.dag.Now().Sub(f.dag.SelectedTipHeader().Timestamp) > minDurationToRequestSelectedTips
}
func (f *FlowContext) requestSelectedTips() {
@@ -90,12 +71,12 @@ func (f *FlowContext) requestSelectedTips() {
}
// FinishIBD finishes the current IBD flow and starts a new one if required.
func (f *FlowContext) FinishIBD() error {
func (f *FlowContext) FinishIBD() {
f.ibdPeer = nil
atomic.StoreUint32(&f.isInIBD, 0)
return f.StartIBDIfRequired()
f.StartIBDIfRequired()
}
// IBDPeer returns the currently active IBD peer.

View File

@@ -1,38 +1,42 @@
package flowcontext
import (
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol/flows/relaytransactions"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/mempool"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
"time"
)
// AddTransaction adds transaction to the mempool and propagates it.
func (f *FlowContext) AddTransaction(tx *externalapi.DomainTransaction) error {
func (f *FlowContext) AddTransaction(tx *util.Tx) error {
f.transactionsToRebroadcastLock.Lock()
defer f.transactionsToRebroadcastLock.Unlock()
err := f.Domain().MiningManager().ValidateAndInsertTransaction(tx, false)
transactionsAcceptedToMempool, err := f.txPool.ProcessTransaction(tx, false)
if err != nil {
return err
}
transactionID := consensusserialization.TransactionID(tx)
f.transactionsToRebroadcast[*transactionID] = tx
inv := appmessage.NewMsgInvTransaction([]*externalapi.DomainTransactionID{transactionID})
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()})
return f.Broadcast(inv)
}
func (f *FlowContext) updateTransactionsToRebroadcast(block *externalapi.DomainBlock) {
func (f *FlowContext) updateTransactionsToRebroadcast(block *util.Block) {
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, *consensusserialization.TransactionID(tx))
for _, tx := range block.Transactions() {
delete(f.transactionsToRebroadcast, *tx.ID())
}
}
@@ -41,14 +45,14 @@ func (f *FlowContext) shouldRebroadcastTransactions() bool {
return time.Since(f.lastRebroadcastTime) > rebroadcastInterval
}
func (f *FlowContext) txIDsToRebroadcast() []*externalapi.DomainTransactionID {
func (f *FlowContext) txIDsToRebroadcast() []*daghash.TxID {
f.transactionsToRebroadcastLock.Lock()
defer f.transactionsToRebroadcastLock.Unlock()
txIDs := make([]*externalapi.DomainTransactionID, len(f.transactionsToRebroadcast))
txIDs := make([]*daghash.TxID, len(f.transactionsToRebroadcast))
i := 0
for _, tx := range f.transactionsToRebroadcast {
txIDs[i] = consensusserialization.TransactionID(tx)
txIDs[i] = tx.ID()
i++
}
return txIDs
@@ -60,6 +64,11 @@ func (f *FlowContext) SharedRequestedTransactions() *relaytransactions.SharedReq
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() {

View File

@@ -45,7 +45,7 @@ func ReceiveAddresses(context ReceiveAddressesContext, incomingRoute *router.Rou
return protocolerrors.Errorf(true, "got unexpected "+
"IncludeAllSubnetworks=true in [%s] command", msgAddresses.Command())
}
if msgAddresses.SubnetworkID != nil && *msgAddresses.SubnetworkID != *context.Config().SubnetworkID {
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)

View File

@@ -4,14 +4,14 @@ 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"
"github.com/kaspanet/kaspad/domain/blockdag"
"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 {
Domain() domain.Domain
DAG() *blockdag.BlockDAG
}
// HandleRelayBlockRequests listens to appmessage.MsgRequestRelayBlocks messages and sends
@@ -27,21 +27,26 @@ func HandleRelayBlockRequests(context RelayBlockRequestsContext, incomingRoute *
getRelayBlocksMessage := message.(*appmessage.MsgRequestRelayBlocks)
for _, hash := range getRelayBlocksMessage.Hashes {
// Fetch the block from the database.
blockInfo, err := context.Domain().Consensus().GetBlockInfo(hash)
if err != nil {
return err
}
if !blockInfo.Exists {
block, err := context.DAG().BlockByHash(hash)
if blockdag.IsNotInDAGErr(err) {
return protocolerrors.Errorf(true, "block %s not found", hash)
}
block, err := context.Domain().Consensus().GetBlock(hash)
if err != nil {
} else if err != nil {
return errors.Wrapf(err, "unable to fetch requested block hash %s", hash)
}
msgBlock := block.MsgBlock()
// TODO (Partial nodes): Convert block to partial block if needed
// 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()
err = outgoingRoute.Enqueue(appmessage.DomainBlockToMsgBlock(block))
isNodeFull := nodeSubnetworkID == nil
isPeerFull := peerSubnetworkID == nil
if isNodeFull && !isPeerFull {
msgBlock.ConvertToPartial(peerSubnetworkID)
}
err = outgoingRoute.Enqueue(msgBlock)
if err != nil {
return err
}

View File

@@ -6,24 +6,22 @@ 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/domain"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/blocks"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/blockdag"
"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"
)
// RelayInvsContext is the interface for the context needed for the HandleRelayInvs flow.
type RelayInvsContext interface {
Domain() domain.Domain
NetAdapter() *netadapter.NetAdapter
OnNewBlock(block *externalapi.DomainBlock) error
DAG() *blockdag.BlockDAG
OnNewBlock(block *util.Block) error
SharedRequestedBlocks() *SharedRequestedBlocks
StartIBDIfRequired() error
StartIBDIfRequired()
IsInIBD() bool
Broadcast(message appmessage.Message) error
}
@@ -59,22 +57,15 @@ func (flow *handleRelayInvsFlow) start() error {
log.Debugf("Got relay inv for block %s", inv.Hash)
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(inv.Hash)
if err != nil {
return err
}
if blockInfo.Exists {
if blockInfo.BlockStatus == externalapi.StatusInvalid {
if flow.DAG().IsKnownBlock(inv.Hash) {
if flow.DAG().IsKnownInvalid(inv.Hash) {
return protocolerrors.Errorf(true, "sent inv of an invalid block %s",
inv.Hash)
}
continue
}
err = flow.StartIBDIfRequired()
if err != nil {
return err
}
flow.StartIBDIfRequired()
if flow.IsInIBD() {
// Block relay is disabled during IBD
continue
@@ -117,8 +108,8 @@ func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) err
numHashesToRequest := mathUtil.MinInt(appmessage.MsgRequestRelayBlocksHashes, requestQueue.len())
hashesToRequest := requestQueue.dequeue(numHashesToRequest)
pendingBlocks := map[externalapi.DomainHash]struct{}{}
var filteredHashesToRequest []*externalapi.DomainHash
pendingBlocks := map[daghash.Hash]struct{}{}
var filteredHashesToRequest []*daghash.Hash
for _, hash := range hashesToRequest {
exists := flow.SharedRequestedBlocks().addIfNotExists(hash)
if exists {
@@ -126,11 +117,7 @@ func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) err
}
// The block can become known from another peer in the process of orphan resolution
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(hash)
if err != nil {
return err
}
if blockInfo.Exists {
if flow.DAG().IsKnownBlock(hash) {
continue
}
@@ -159,11 +146,11 @@ func (flow *handleRelayInvsFlow) requestBlocks(requestQueue *hashesQueueSet) err
return err
}
block := appmessage.MsgBlockToDomainBlock(msgBlock)
blockHash := consensusserialization.BlockHash(block)
block := util.NewBlock(msgBlock)
blockHash := block.Hash()
if _, ok := pendingBlocks[*blockHash]; !ok {
return protocolerrors.Errorf(true, "got unrequested block %s", blockHash)
return protocolerrors.Errorf(true, "got unrequested block %s", block.Hash())
}
err = flow.processAndRelayBlock(requestQueue, block)
@@ -200,46 +187,45 @@ func (flow *handleRelayInvsFlow) readMsgBlock() (
}
}
func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueSet, block *externalapi.DomainBlock) error {
blockHash := consensusserialization.BlockHash(block)
err := flow.Domain().Consensus().ValidateAndInsertBlock(block)
func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueSet, block *util.Block) error {
blockHash := block.Hash()
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
if err != nil {
if !errors.As(err, &ruleerrors.RuleError{}) {
if !errors.As(err, &blockdag.RuleError{}) {
return errors.Wrapf(err, "failed to process block %s", blockHash)
}
missingParentsError := &ruleerrors.ErrMissingParents{}
if errors.As(err, missingParentsError) {
blueScore, err := blocks.ExtractBlueScore(block)
if err != nil {
return protocolerrors.Errorf(true, "received an orphan "+
"block %s with malformed blue score", blockHash)
}
const maxOrphanBlueScoreDiff = 10000
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
if err != nil {
return err
}
selectedTipBlueScore, err := blocks.ExtractBlueScore(virtualSelectedParent)
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
}
// Request the parents for the orphan block from the peer that sent it.
for _, missingAncestor := range missingParentsError.MissingParentHashes {
requestQueue.enqueueIfNotExists(missingAncestor)
}
return nil
}
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)
}
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
}
// 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
}
err = blocklogger.LogBlock(block)
if err != nil {
return err
@@ -249,10 +235,7 @@ func (flow *handleRelayInvsFlow) processAndRelayBlock(requestQueue *hashesQueueS
return err
}
err = flow.StartIBDIfRequired()
if err != nil {
return err
}
flow.StartIBDIfRequired()
err = flow.OnNewBlock(block)
if err != nil {
return err

View File

@@ -1,15 +1,13 @@
package blockrelay
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
)
import "github.com/kaspanet/kaspad/util/daghash"
type hashesQueueSet struct {
queue []*externalapi.DomainHash
set map[externalapi.DomainHash]struct{}
queue []*daghash.Hash
set map[daghash.Hash]struct{}
}
func (r *hashesQueueSet) enqueueIfNotExists(hash *externalapi.DomainHash) {
func (r *hashesQueueSet) enqueueIfNotExists(hash *daghash.Hash) {
if _, ok := r.set[*hash]; ok {
return
}
@@ -17,8 +15,8 @@ func (r *hashesQueueSet) enqueueIfNotExists(hash *externalapi.DomainHash) {
r.set[*hash] = struct{}{}
}
func (r *hashesQueueSet) dequeue(numItems int) []*externalapi.DomainHash {
var hashes []*externalapi.DomainHash
func (r *hashesQueueSet) dequeue(numItems int) []*daghash.Hash {
var hashes []*daghash.Hash
hashes, r.queue = r.queue[:numItems], r.queue[numItems:]
for _, hash := range hashes {
delete(r.set, *hash)
@@ -32,6 +30,6 @@ func (r *hashesQueueSet) len() int {
func newHashesQueueSet() *hashesQueueSet {
return &hashesQueueSet{
set: make(map[externalapi.DomainHash]struct{}),
set: make(map[daghash.Hash]struct{}),
}
}

View File

@@ -3,23 +3,23 @@ package blockrelay
import (
"sync"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/util/daghash"
)
// 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[externalapi.DomainHash]struct{}
blocks map[daghash.Hash]struct{}
sync.Mutex
}
func (s *SharedRequestedBlocks) remove(hash *externalapi.DomainHash) {
func (s *SharedRequestedBlocks) remove(hash *daghash.Hash) {
s.Lock()
defer s.Unlock()
delete(s.blocks, *hash)
}
func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash]struct{}) {
func (s *SharedRequestedBlocks) removeSet(blockHashes map[daghash.Hash]struct{}) {
s.Lock()
defer s.Unlock()
for hash := range blockHashes {
@@ -27,7 +27,7 @@ func (s *SharedRequestedBlocks) removeSet(blockHashes map[externalapi.DomainHash
}
}
func (s *SharedRequestedBlocks) addIfNotExists(hash *externalapi.DomainHash) (exists bool) {
func (s *SharedRequestedBlocks) addIfNotExists(hash *daghash.Hash) (exists bool) {
s.Lock()
defer s.Unlock()
_, ok := s.blocks[*hash]
@@ -41,6 +41,6 @@ func (s *SharedRequestedBlocks) addIfNotExists(hash *externalapi.DomainHash) (ex
// NewSharedRequestedBlocks returns a new instance of SharedRequestedBlocks.
func NewSharedRequestedBlocks() *SharedRequestedBlocks {
return &SharedRequestedBlocks{
blocks: make(map[externalapi.DomainHash]struct{}),
blocks: make(map[daghash.Hash]struct{}),
}
}

View File

@@ -4,12 +4,11 @@ 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"
@@ -24,9 +23,9 @@ import (
type HandleHandshakeContext interface {
Config() *config.Config
NetAdapter() *netadapter.NetAdapter
Domain() domain.Domain
DAG() *blockdag.BlockDAG
AddressManager() *addressmanager.AddressManager
StartIBDIfRequired() error
StartIBDIfRequired()
AddToPeers(peer *peerpkg.Peer) error
HandleError(err error, flowName string, isStopping *uint32, errChan chan<- error)
}
@@ -91,10 +90,7 @@ func HandleHandshake(context HandleHandshakeContext, netConnection *netadapter.N
context.AddressManager().Good(peerAddress, subnetworkID)
}
err = context.StartIBDIfRequired()
if err != nil {
return nil, err
}
context.StartIBDIfRequired()
return peer, nil
}

View File

@@ -72,7 +72,7 @@ func (flow *receiveVersionFlow) start() (*appmessage.NetAddress, error) {
}
// Disconnect from partial nodes in networks that don't allow them
if !flow.Config().ActiveNetParams.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
if !flow.DAG().Params.EnableNonNativeSubnetworks && msgVersion.SubnetworkID != nil {
return nil, protocolerrors.New(true, "partial nodes are not allowed")
}
@@ -84,7 +84,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 != *localSubnetworkID) {
(!isLocalNodeFull && !isRemoteNodeFull && !msgVersion.SubnetworkID.IsEqual(localSubnetworkID)) {
return nil, protocolerrors.New(false, "incompatible subnetworks")
}

View File

@@ -4,7 +4,6 @@ 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/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/version"
)
@@ -29,7 +28,6 @@ var (
type sendVersionFlow struct {
HandleHandshakeContext
incomingRoute, outgoingRoute *router.Route
peer *peerpkg.Peer
}
@@ -48,11 +46,7 @@ func SendVersion(context HandleHandshakeContext, incomingRoute *router.Route,
}
func (flow *sendVersionFlow) start() error {
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
if err != nil {
return err
}
selectedTipHash := consensusserialization.BlockHash(virtualSelectedParent)
selectedTipHash := flow.DAG().SelectedTipHash()
subnetworkID := flow.Config().SubnetworkID
// Version message.
@@ -70,7 +64,7 @@ func (flow *sendVersionFlow) start() error {
// Advertise if inv messages for transactions are desired.
msg.DisableRelayTx = flow.Config().BlocksOnly
err = flow.outgoingRoute.Enqueue(msg)
err := flow.outgoingRoute.Enqueue(msg)
if err != nil {
return err
}

View File

@@ -3,14 +3,14 @@ package ibd
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/domain/blockdag"
"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 {
Domain() domain.Domain
DAG() *blockdag.BlockDAG
}
type handleRequestBlockLocatorFlow struct {
@@ -37,7 +37,7 @@ func (flow *handleRequestBlockLocatorFlow) start() error {
return err
}
locator, err := flow.Domain().Consensus().CreateBlockLocator(lowHash, highHash)
locator, err := flow.DAG().BlockLocatorFromHashes(highHash, lowHash)
if err != nil || len(locator) == 0 {
return protocolerrors.Errorf(true, "couldn't build a block "+
"locator between blocks %s and %s", lowHash, highHash)
@@ -50,8 +50,8 @@ func (flow *handleRequestBlockLocatorFlow) start() error {
}
}
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *externalapi.DomainHash,
highHash *externalapi.DomainHash, err error) {
func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *daghash.Hash,
highHash *daghash.Hash, err error) {
message, err := flow.incomingRoute.Dequeue()
if err != nil {
@@ -62,7 +62,7 @@ func (flow *handleRequestBlockLocatorFlow) receiveGetBlockLocator() (lowHash *ex
return msgGetBlockLocator.LowHash, msgGetBlockLocator.HighHash, nil
}
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator externalapi.BlockLocator) error {
func (flow *handleRequestBlockLocatorFlow) sendBlockLocator(locator blockdag.BlockLocator) error {
msgBlockLocator := appmessage.NewMsgBlockLocator(locator)
err := flow.outgoingRoute.Enqueue(msgBlockLocator)
if err != nil {

View File

@@ -1,19 +1,19 @@
package ibd
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/protocol/protocolerrors"
"github.com/kaspanet/kaspad/domain"
"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 {
Domain() domain.Domain
DAG() *blockdag.BlockDAG
}
type handleRequestBlocksFlow struct {
@@ -78,8 +78,8 @@ func (flow *handleRequestBlocksFlow) start() error {
}
}
func receiveRequestIBDBlocks(incomingRoute *router.Route) (lowHash *externalapi.DomainHash,
highHash *externalapi.DomainHash, err error) {
func receiveRequestIBDBlocks(incomingRoute *router.Route) (lowHash *daghash.Hash,
highHash *daghash.Hash, err error) {
message, err := incomingRoute.Dequeue()
if err != nil {
@@ -90,25 +90,26 @@ func receiveRequestIBDBlocks(incomingRoute *router.Route) (lowHash *externalapi.
return msgRequestIBDBlocks.LowHash, msgRequestIBDBlocks.HighHash, nil
}
func (flow *handleRequestBlocksFlow) buildMsgIBDBlocks(lowHash *externalapi.DomainHash,
highHash *externalapi.DomainHash) ([]*appmessage.MsgIBDBlock, error) {
func (flow *handleRequestBlocksFlow) buildMsgIBDBlocks(lowHash *daghash.Hash,
highHash *daghash.Hash) ([]*appmessage.MsgIBDBlock, error) {
blockHashes, err := flow.Domain().Consensus().GetHashesBetween(lowHash, highHash)
if err != nil {
return nil, err
}
const maxHashesInMsgIBDBlocks = appmessage.MaxInvPerMsg
if len(blockHashes) > maxHashesInMsgIBDBlocks {
blockHashes = blockHashes[:maxHashesInMsgIBDBlocks]
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.Domain().Consensus().GetBlock(blockHash)
block, err := flow.DAG().BlockByHash(blockHash)
if err != nil {
return nil, err
}
msgIBDBlocks[i] = appmessage.NewMsgIBDBlock(appmessage.DomainBlockToMsgBlock(block))
msgIBDBlocks[i] = appmessage.NewMsgIBDBlock(block.MsgBlock())
}
return msgIBDBlocks, nil

View File

@@ -6,21 +6,19 @@ 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/domain"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/infrastructure/config"
"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 {
Domain() domain.Domain
Config() *config.Config
OnNewBlock(block *externalapi.DomainBlock) error
FinishIBD() error
DAG() *blockdag.BlockDAG
OnNewBlock(block *util.Block) error
StartIBDIfRequired()
FinishIBD()
}
type handleIBDFlow struct {
@@ -63,14 +61,19 @@ func (flow *handleIBDFlow) runIBD() error {
}
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 *externalapi.DomainHash) (lowHash *externalapi.DomainHash,
func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *daghash.Hash) (lowHash *daghash.Hash,
err error) {
lowHash = flow.Config().ActiveNetParams.GenesisHash
lowHash = flow.DAG().Params.GenesisHash
highHash := peerSelectedTipHash
for {
@@ -88,28 +91,21 @@ func (flow *handleIBDFlow) findHighestSharedBlockHash(peerSelectedTipHash *exter
// 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 {
if flow.DAG().IsInDAG(locatorHighHash) {
return locatorHighHash, nil
}
highHash, lowHash, err = flow.Domain().Consensus().FindNextBlockLocatorBoundaries(blockLocatorHashes)
if err != nil {
return nil, err
}
highHash, lowHash = flow.DAG().FindNextLocatorBoundaries(blockLocatorHashes)
}
}
func (flow *handleIBDFlow) sendGetBlockLocator(lowHash *externalapi.DomainHash, highHash *externalapi.DomainHash) error {
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 []*externalapi.DomainHash, err error) {
func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*daghash.Hash, err error) {
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
if err != nil {
return nil, err
@@ -123,8 +119,8 @@ func (flow *handleIBDFlow) receiveBlockLocator() (blockLocatorHashes []*external
return msgBlockLocator.BlockLocatorHashes, nil
}
func (flow *handleIBDFlow) downloadBlocks(highestSharedBlockHash *externalapi.DomainHash,
peerSelectedTipHash *externalapi.DomainHash) error {
func (flow *handleIBDFlow) downloadBlocks(highestSharedBlockHash *daghash.Hash,
peerSelectedTipHash *daghash.Hash) error {
err := flow.sendGetBlocks(highestSharedBlockHash, peerSelectedTipHash)
if err != nil {
@@ -157,8 +153,8 @@ func (flow *handleIBDFlow) downloadBlocks(highestSharedBlockHash *externalapi.Do
}
}
func (flow *handleIBDFlow) sendGetBlocks(highestSharedBlockHash *externalapi.DomainHash,
peerSelectedTipHash *externalapi.DomainHash) error {
func (flow *handleIBDFlow) sendGetBlocks(highestSharedBlockHash *daghash.Hash,
peerSelectedTipHash *daghash.Hash) error {
msgGetBlockInvs := appmessage.NewMsgRequstIBDBlocks(highestSharedBlockHash, peerSelectedTipHash)
return flow.outgoingRoute.Enqueue(msgGetBlockInvs)
@@ -182,24 +178,27 @@ func (flow *handleIBDFlow) receiveIBDBlock() (msgIBDBlock *appmessage.MsgIBDBloc
}
func (flow *handleIBDFlow) processIBDBlock(msgIBDBlock *appmessage.MsgIBDBlock) error {
block := appmessage.MsgBlockToDomainBlock(msgIBDBlock.MsgBlock)
blockHash := consensusserialization.BlockHash(block)
blockInfo, err := flow.Domain().Consensus().GetBlockInfo(blockHash)
if err != nil {
return err
}
if blockInfo.Exists {
log.Debugf("IBD block %s is already in the DAG. Skipping...", blockHash)
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
}
err = flow.Domain().Consensus().ValidateAndInsertBlock(block)
isOrphan, isDelayed, err := flow.DAG().ProcessBlock(block, blockdag.BFNone)
if err != nil {
if !errors.As(err, &ruleerrors.RuleError{}) {
return errors.Wrapf(err, "failed to process block %s during IBD", blockHash)
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", blockHash, flow.peer, err)
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", blockHash)
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 {

View File

@@ -2,15 +2,14 @@ package selectedtip
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"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 {
Domain() domain.Domain
DAG() *blockdag.BlockDAG
}
type handleRequestSelectedTipFlow struct {
@@ -57,10 +56,6 @@ func (flow *handleRequestSelectedTipFlow) receiveGetSelectedTip() error {
}
func (flow *handleRequestSelectedTipFlow) sendSelectedTipHash() error {
virtualSelectedParent, err := flow.Domain().Consensus().GetVirtualSelectedParent()
if err != nil {
return err
}
msgSelectedTip := appmessage.NewMsgSelectedTip(consensusserialization.BlockHash(virtualSelectedParent))
msgSelectedTip := appmessage.NewMsgSelectedTip(flow.DAG().SelectedTipHash())
return flow.outgoingRoute.Enqueue(msgSelectedTip)
}

View File

@@ -4,15 +4,15 @@ 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"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"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 {
Domain() domain.Domain
StartIBDIfRequired() error
DAG() *blockdag.BlockDAG
StartIBDIfRequired()
}
type requestSelectedTipFlow struct {
@@ -59,7 +59,8 @@ func (flow *requestSelectedTipFlow) runSelectedTipRequest() error {
}
flow.peer.SetSelectedTipHash(peerSelectedTipHash)
return flow.StartIBDIfRequired()
flow.StartIBDIfRequired()
return nil
}
func (flow *requestSelectedTipFlow) requestSelectedTip() error {
@@ -67,7 +68,7 @@ func (flow *requestSelectedTipFlow) requestSelectedTip() error {
return flow.outgoingRoute.Enqueue(msgGetSelectedTip)
}
func (flow *requestSelectedTipFlow) receiveSelectedTip() (selectedTipHash *externalapi.DomainHash, err error) {
func (flow *requestSelectedTipFlow) receiveSelectedTip() (selectedTipHash *daghash.Hash, err error) {
message, err := flow.incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
if err != nil {
return nil, err

View File

@@ -4,12 +4,12 @@ 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"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/domain/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,8 +17,9 @@ import (
// HandleRelayedTransactions and HandleRequestedTransactions flows.
type TransactionsRelayContext interface {
NetAdapter() *netadapter.NetAdapter
Domain() domain.Domain
DAG() *blockdag.BlockDAG
SharedRequestedTransactions() *SharedRequestedTransactions
TxPool() *mempool.TxPool
Broadcast(message appmessage.Message) error
OnTransactionAddedToMempool()
}
@@ -61,9 +62,9 @@ func (flow *handleRelayedTransactionsFlow) start() error {
}
func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
inv *appmessage.MsgInvTransaction) (requestedIDs []*externalapi.DomainTransactionID, err error) {
inv *appmessage.MsgInvTransaction) (requestedIDs []*daghash.TxID, err error) {
idsToRequest := make([]*externalapi.DomainTransactionID, 0, len(inv.TxIDs))
idsToRequest := make([]*daghash.TxID, 0, len(inv.TxIDs))
for _, txID := range inv.TxIDs {
if flow.isKnownTransaction(txID) {
continue
@@ -88,13 +89,29 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
return idsToRequest, nil
}
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool {
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *daghash.TxID) bool {
// Ask the transaction memory pool if the transaction is known
// to it in any form (main pool or orphan).
if _, ok := flow.Domain().MiningManager().GetTransaction(txID); ok {
if flow.TxPool().HaveTransaction(txID) {
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
}
@@ -118,8 +135,12 @@ func (flow *handleRelayedTransactionsFlow) readInv() (*appmessage.MsgInvTransact
return inv, nil
}
func (flow *handleRelayedTransactionsFlow) broadcastAcceptedTransactions(acceptedTxIDs []*externalapi.DomainTransactionID) error {
inv := appmessage.NewMsgInvTransaction(acceptedTxIDs)
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)
return flow.Broadcast(inv)
}
@@ -149,7 +170,7 @@ func (flow *handleRelayedTransactionsFlow) readMsgTxOrNotFound() (
}
}
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*externalapi.DomainTransactionID) error {
func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransactions []*daghash.TxID) 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)
@@ -159,41 +180,42 @@ func (flow *handleRelayedTransactionsFlow) receiveTransactions(requestedTransact
return err
}
if msgTxNotFound != nil {
if msgTxNotFound.ID != expectedID {
if !msgTxNotFound.ID.IsEqual(expectedID) {
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
expectedID, msgTxNotFound.ID)
}
continue
}
tx := appmessage.MsgTxToDomainTransaction(msgTx)
txID := consensusserialization.TransactionID(tx)
if txID != expectedID {
tx := util.NewTx(msgTx)
if !tx.ID().IsEqual(expectedID) {
return protocolerrors.Errorf(true, "expected transaction %s, but got %s",
expectedID, txID)
expectedID, tx.ID())
}
err = flow.Domain().MiningManager().ValidateAndInsertTransaction(tx, true)
acceptedTxs, err := flow.TxPool().ProcessTransaction(tx, true)
if err != nil {
ruleErr := &mempool.RuleError{}
if !errors.As(err, ruleErr) {
return errors.Wrapf(err, "failed to process transaction %s", txID)
return errors.Wrapf(err, "failed to process transaction %s", tx.ID())
}
shouldBan := true
shouldBan := false
if txRuleErr := (&mempool.TxRuleError{}); errors.As(ruleErr.Err, txRuleErr) {
if txRuleErr.RejectCode != mempool.RejectInvalid {
shouldBan = false
if txRuleErr.RejectCode == mempool.RejectInvalid {
shouldBan = true
}
} else if dagRuleErr := (&blockdag.RuleError{}); errors.As(ruleErr.Err, dagRuleErr) {
shouldBan = true
}
if !shouldBan {
continue
}
return protocolerrors.Errorf(true, "rejected transaction %s", txID)
return protocolerrors.Errorf(true, "rejected transaction %s", tx.ID())
}
err = flow.broadcastAcceptedTransactions([]*externalapi.DomainTransactionID{txID})
err = flow.broadcastAcceptedTransactions(acceptedTxs)
if err != nil {
return err
}

View File

@@ -30,7 +30,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
}
for _, transactionID := range msgRequestTransactions.IDs {
tx, ok := flow.Domain().MiningManager().GetTransaction(transactionID)
tx, ok := flow.TxPool().FetchTransaction(transactionID)
if !ok {
msgTransactionNotFound := appmessage.NewMsgTransactionNotFound(transactionID)
@@ -41,7 +41,7 @@ func (flow *handleRequestedTransactionsFlow) start() error {
continue
}
err := flow.outgoingRoute.Enqueue(appmessage.DomainTransactionToMsgTx(tx))
err := flow.outgoingRoute.Enqueue(tx.MsgTx())
if err != nil {
return err
}

View File

@@ -1,25 +1,24 @@
package relaytransactions
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[externalapi.DomainTransactionID]struct{}
transactions map[daghash.TxID]struct{}
sync.Mutex
}
func (s *SharedRequestedTransactions) remove(txID *externalapi.DomainTransactionID) {
func (s *SharedRequestedTransactions) remove(txID *daghash.TxID) {
s.Lock()
defer s.Unlock()
delete(s.transactions, *txID)
}
func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTransactionID) {
func (s *SharedRequestedTransactions) removeMany(txIDs []*daghash.TxID) {
s.Lock()
defer s.Unlock()
for _, txID := range txIDs {
@@ -27,7 +26,7 @@ func (s *SharedRequestedTransactions) removeMany(txIDs []*externalapi.DomainTran
}
}
func (s *SharedRequestedTransactions) addIfNotExists(txID *externalapi.DomainTransactionID) (exists bool) {
func (s *SharedRequestedTransactions) addIfNotExists(txID *daghash.TxID) (exists bool) {
s.Lock()
defer s.Unlock()
_, ok := s.transactions[*txID]
@@ -41,6 +40,6 @@ func (s *SharedRequestedTransactions) addIfNotExists(txID *externalapi.DomainTra
// NewSharedRequestedTransactions returns a new instance of SharedRequestedTransactions.
func NewSharedRequestedTransactions() *SharedRequestedTransactions {
return &SharedRequestedTransactions{
transactions: make(map[externalapi.DomainTransactionID]struct{}),
transactions: make(map[daghash.TxID]struct{}),
}
}

View File

@@ -3,16 +3,15 @@ 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
@@ -21,13 +20,13 @@ type Manager struct {
}
// NewManager creates a new instance of the p2p protocol manager
func NewManager(cfg *config.Config, domain domain.Domain, netAdapter *netadapter.NetAdapter, addressManager *addressmanager.AddressManager,
func NewManager(cfg *config.Config, dag *blockdag.BlockDAG, netAdapter *netadapter.NetAdapter,
addressManager *addressmanager.AddressManager, txPool *mempool.TxPool,
connectionManager *connmanager.ConnectionManager) (*Manager, error) {
manager := Manager{
context: flowcontext.New(cfg, domain, addressManager, netAdapter, connectionManager),
context: flowcontext.New(cfg, dag, addressManager, txPool, netAdapter, connectionManager),
}
netAdapter.SetP2PRouterInitializer(manager.routerInitializer)
return &manager, nil
}
@@ -44,13 +43,13 @@ func (m *Manager) IBDPeer() *peerpkg.Peer {
}
// AddTransaction adds transaction to the mempool and propagates it.
func (m *Manager) AddTransaction(tx *externalapi.DomainTransaction) error {
func (m *Manager) AddTransaction(tx *util.Tx) error {
return m.context.AddTransaction(tx)
}
// AddBlock adds the given block to the DAG and propagates it.
func (m *Manager) AddBlock(block *externalapi.DomainBlock) error {
return m.context.AddBlock(block)
func (m *Manager) AddBlock(block *util.Block, flags blockdag.BehaviorFlags) error {
return m.context.AddBlock(block, flags)
}
func (m *Manager) runFlows(flows []*flow, peer *peerpkg.Peer, errChan <-chan error) error {

View File

@@ -1,17 +1,17 @@
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"
)
// Peer holds data about a peer.
@@ -19,14 +19,14 @@ type Peer struct {
connection *netadapter.NetConnection
selectedTipHashMtx sync.RWMutex
selectedTipHash *externalapi.DomainHash
selectedTipHash *daghash.Hash
userAgent string
services appmessage.ServiceFlag
advertisedProtocolVerion uint32 // protocol version advertised by remote
protocolVersion uint32 // negotiated protocol version
disableRelayTx bool
subnetworkID *externalapi.DomainSubnetworkID
subnetworkID *subnetworkid.SubnetworkID
timeOffset time.Duration
connectionStarted time.Time
@@ -59,14 +59,14 @@ func (p *Peer) Connection() *netadapter.NetConnection {
}
// SelectedTipHash returns the selected tip of the peer.
func (p *Peer) SelectedTipHash() *externalapi.DomainHash {
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 *externalapi.DomainHash) {
func (p *Peer) SetSelectedTipHash(hash *daghash.Hash) {
p.selectedTipHashMtx.Lock()
defer p.selectedTipHashMtx.Unlock()
p.selectedTipHash = hash
@@ -74,7 +74,7 @@ func (p *Peer) SetSelectedTipHash(hash *externalapi.DomainHash) {
// SubnetworkID returns the subnetwork the peer is associated with.
// It is nil in full nodes.
func (p *Peer) SubnetworkID() *externalapi.DomainSubnetworkID {
func (p *Peer) SubnetworkID() *subnetworkid.SubnetworkID {
return p.subnetworkID
}

View File

@@ -4,12 +4,16 @@ 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"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"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/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
@@ -20,22 +24,26 @@ 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,
shutDownChan chan<- struct{}) *Manager {
acceptanceIndex *indexers.AcceptanceIndex) *Manager {
manager := Manager{
context: rpccontext.NewContext(
cfg,
domain,
netAdapter,
dag,
protocolManager,
connectionManager,
blockTemplateGenerator,
mempool,
addressManager,
shutDownChan,
acceptanceIndex,
),
}
netAdapter.SetRPCRouterInitializer(manager.routerInitializer)
@@ -44,19 +52,29 @@ func NewManager(
}
// NotifyBlockAddedToDAG notifies the manager that a block has been added to the DAG
func (m *Manager) NotifyBlockAddedToDAG(block *externalapi.DomainBlock) error {
notification := appmessage.NewBlockAddedNotificationMessage(appmessage.DomainBlockToMsgBlock(block))
return m.context.NotificationManager.NotifyBlockAdded(notification)
func (m *Manager) NotifyBlockAddedToDAG(block *util.Block) {
m.context.BlockTemplateState.NotifyBlockAdded(block)
notification := appmessage.NewBlockAddedNotificationMessage(block.MsgBlock())
m.context.NotificationManager.NotifyBlockAdded(notification)
}
// 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)
// 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
}
// 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)
// NotifyTransactionAddedToMempool notifies the manager that a transaction has been added to the mempool
func (m *Manager) NotifyTransactionAddedToMempool() {
m.context.BlockTemplateState.NotifyMempoolTx()
}

View File

@@ -12,28 +12,24 @@ 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.CmdResolveFinalityConflictRequestMessage: rpchandlers.HandleResolveFinalityConflict,
appmessage.CmdNotifyFinalityConflictsRequestMessage: rpchandlers.HandleNotifyFinalityConflicts,
appmessage.CmdGetMempoolEntriesRequestMessage: rpchandlers.HandleGetMempoolEntries,
appmessage.CmdShutDownRequestMessage: rpchandlers.HandleGetMempoolEntries,
appmessage.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
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.CmdGetHeadersRequestMessage: rpchandlers.HandleGetHeaders,
}
func (m *Manager) routerInitializer(router *router.Router, netConnection *netadapter.NetConnection) {

View File

@@ -0,0 +1,476 @@
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
}

View File

@@ -0,0 +1,40 @@
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
}

View File

@@ -2,7 +2,10 @@ package rpccontext
import (
"github.com/kaspanet/kaspad/app/protocol"
"github.com/kaspanet/kaspad/domain"
"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/infrastructure/config"
"github.com/kaspanet/kaspad/infrastructure/network/addressmanager"
"github.com/kaspanet/kaspad/infrastructure/network/connmanager"
@@ -11,35 +14,43 @@ import (
// Context represents the RPC context
type Context struct {
Config *config.Config
NetAdapter *netadapter.NetAdapter
Domain domain.Domain
ProtocolManager *protocol.Manager
ConnectionManager *connmanager.ConnectionManager
AddressManager *addressmanager.AddressManager
ShutDownChan chan<- 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
BlockTemplateState *BlockTemplateState
NotificationManager *NotificationManager
}
// NewContext creates a new RPC context
func NewContext(cfg *config.Config,
domain domain.Domain,
func NewContext(
cfg *config.Config,
netAdapter *netadapter.NetAdapter,
dag *blockdag.BlockDAG,
protocolManager *protocol.Manager,
connectionManager *connmanager.ConnectionManager,
blockTemplateGenerator *mining.BlkTmplGenerator,
mempool *mempool.TxPool,
addressManager *addressmanager.AddressManager,
shutDownChan chan<- struct{}) *Context {
acceptanceIndex *indexers.AcceptanceIndex) *Context {
context := &Context{
Config: cfg,
NetAdapter: netAdapter,
Domain: domain,
ProtocolManager: protocolManager,
ConnectionManager: connectionManager,
AddressManager: addressManager,
ShutDownChan: shutDownChan,
Config: cfg,
NetAdapter: netAdapter,
DAG: dag,
ProtocolManager: protocolManager,
ConnectionManager: connectionManager,
BlockTemplateGenerator: blockTemplateGenerator,
Mempool: mempool,
AddressManager: addressManager,
AcceptanceIndex: acceptanceIndex,
}
context.BlockTemplateState = NewBlockTemplateState(context)
context.NotificationManager = NewNotificationManager()
return context
}

View File

@@ -15,10 +15,8 @@ type NotificationManager struct {
// NotificationListener represents a registered RPC notification listener
type NotificationListener struct {
propagateBlockAddedNotifications bool
propagateChainChangedNotifications bool
propagateFinalityConflictNotifications bool
propagateFinalityConflictResolvedNotifications bool
propagateBlockAddedNotifications bool
propagateChainChangedNotifications bool
}
// NewNotificationManager creates a new NotificationManager
@@ -89,44 +87,10 @@ func (nm *NotificationManager) NotifyChainChanged(notification *appmessage.Chain
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{
propagateBlockAddedNotifications: false,
propagateChainChangedNotifications: false,
propagateFinalityConflictNotifications: false,
propagateFinalityConflictResolvedNotifications: false,
propagateBlockAddedNotifications: false,
propagateChainChangedNotifications: false,
}
}
@@ -141,15 +105,3 @@ func (nl *NotificationListener) PropagateBlockAddedNotifications() {
func (nl *NotificationListener) PropagateChainChangedNotifications() {
nl.propagateChainChangedNotifications = true
}
// PropagateFinalityConflictNotifications instructs the listener to send finality conflict notifications
// to the remote listener
func (nl *NotificationListener) PropagateFinalityConflictNotifications() {
nl.propagateFinalityConflictNotifications = true
}
// PropagateFinalityConflictResolvedNotifications instructs the listener to send finality conflict resolved notifications
// to the remote listener
func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications() {
nl.propagateFinalityConflictResolvedNotifications = true
}

View File

@@ -1,29 +1,60 @@
package rpccontext
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/domain/txscript"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/pointers"
"math/big"
"strconv"
"github.com/kaspanet/kaspad/domain/consensus/utils/estimatedsize"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/pointers"
)
// BuildBlockVerboseData builds a BlockVerboseData from the given block.
// This method must be called with the DAG lock held for reads
func (ctx *Context) BuildBlockVerboseData(block *externalapi.DomainBlock, includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
hash := consensusserialization.BlockHash(block)
blockHeader := block.Header
func (ctx *Context) BuildBlockVerboseData(block *util.Block, includeTransactionVerboseData bool) (*appmessage.BlockVerboseData, error) {
hash := block.Hash()
params := ctx.DAG.Params
blockHeader := block.MsgBlock().Header
blockBlueScore, err := ctx.DAG.BlueScoreByBlockHash(hash)
if err != nil {
return nil, err
}
// Get the hashes for the next blocks unless there are none.
childHashes, err := ctx.DAG.ChildHashesByHash(hash)
if err != nil {
return nil, err
}
blockConfirmations, err := ctx.DAG.BlockConfirmationsByHashNoLock(hash)
if err != nil {
return nil, err
}
selectedParentHash, err := ctx.DAG.SelectedParentHash(hash)
if err != nil {
return nil, err
}
selectedParentHashStr := ""
if selectedParentHash != nil {
selectedParentHashStr = selectedParentHash.String()
}
isChainBlock, err := ctx.DAG.IsInSelectedParentChain(hash)
if err != nil {
return nil, err
}
acceptedBlockHashes, err := ctx.DAG.BluesByBlockHash(hash)
if err != nil {
return nil, err
}
result := &appmessage.BlockVerboseData{
Hash: hash.String(),
@@ -32,24 +63,33 @@ func (ctx *Context) BuildBlockVerboseData(block *externalapi.DomainBlock, includ
HashMerkleRoot: blockHeader.HashMerkleRoot.String(),
AcceptedIDMerkleRoot: blockHeader.AcceptedIDMerkleRoot.String(),
UTXOCommitment: blockHeader.UTXOCommitment.String(),
ParentHashes: externalapi.DomainHashesToStrings(blockHeader.ParentHashes),
ParentHashes: daghash.Strings(blockHeader.ParentHashes),
SelectedParentHash: selectedParentHashStr,
Nonce: blockHeader.Nonce,
Time: blockHeader.TimeInMilliseconds,
Time: blockHeader.Timestamp.UnixMilliseconds(),
Confirmations: blockConfirmations,
BlueScore: blockBlueScore,
IsChainBlock: isChainBlock,
Size: int32(block.MsgBlock().SerializeSize()),
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
Difficulty: ctx.GetDifficultyRatio(blockHeader.Bits, ctx.Config.ActiveNetParams),
Difficulty: ctx.GetDifficultyRatio(blockHeader.Bits, params),
ChildHashes: daghash.Strings(childHashes),
AcceptedBlockHashes: daghash.Strings(acceptedBlockHashes),
}
txIDs := make([]string, len(block.Transactions))
for i, tx := range block.Transactions {
txIDs[i] = consensusserialization.TransactionID(tx).String()
transactions := block.Transactions()
txIDs := make([]string, len(transactions))
for i, tx := range transactions {
txIDs[i] = tx.ID().String()
}
result.TxIDs = txIDs
if includeTransactionVerboseData {
transactionVerboseData := make([]*appmessage.TransactionVerboseData, len(block.Transactions))
for i, tx := range block.Transactions {
txID := consensusserialization.TransactionID(tx).String()
data, err := ctx.BuildTransactionVerboseData(tx, txID, blockHeader, hash.String())
transactions := block.Transactions()
transactionVerboseData := make([]*appmessage.TransactionVerboseData, len(transactions))
for i, tx := range transactions {
data, err := ctx.buildTransactionVerboseData(tx.MsgTx(), tx.ID().String(),
&blockHeader, hash.String(), nil, false)
if err != nil {
return nil, err
}
@@ -80,55 +120,76 @@ func (ctx *Context) GetDifficultyRatio(bits uint32, params *dagconfig.Params) fl
return diff
}
// BuildTransactionVerboseData builds a TransactionVerboseData from
// the given parameters
func (ctx *Context) BuildTransactionVerboseData(tx *externalapi.DomainTransaction, txID string,
blockHeader *externalapi.DomainBlockHeader, blockHash string) (
*appmessage.TransactionVerboseData, error) {
func (ctx *Context) buildTransactionVerboseData(mtx *appmessage.MsgTx,
txID string, blockHeader *appmessage.BlockHeader, blockHash string,
acceptingBlock *daghash.Hash, isInMempool bool) (*appmessage.TransactionVerboseData, error) {
mtxHex, err := msgTxToHex(mtx)
if err != nil {
return nil, err
}
var payloadHash string
if tx.SubnetworkID != subnetworks.SubnetworkIDNative {
payloadHash = tx.PayloadHash.String()
if mtx.PayloadHash != nil {
payloadHash = mtx.PayloadHash.String()
}
txReply := &appmessage.TransactionVerboseData{
Hex: mtxHex,
TxID: txID,
Hash: consensusserialization.TransactionHash(tx).String(),
Size: estimatedsize.TransactionEstimatedSerializedSize(tx),
TransactionVerboseInputs: ctx.buildTransactionVerboseInputs(tx),
TransactionVerboseOutputs: ctx.buildTransactionVerboseOutputs(tx, nil),
Version: tx.Version,
LockTime: tx.LockTime,
SubnetworkID: tx.SubnetworkID.String(),
Gas: tx.Gas,
Hash: mtx.TxHash().String(),
Size: int32(mtx.SerializeSize()),
TransactionVerboseInputs: ctx.buildTransactionVerboseInputs(mtx),
TransactionVerboseOutputs: ctx.buildTransactionVerboseOutputs(mtx, nil),
Version: mtx.Version,
LockTime: mtx.LockTime,
SubnetworkID: mtx.SubnetworkID.String(),
Gas: mtx.Gas,
PayloadHash: payloadHash,
Payload: hex.EncodeToString(tx.Payload),
Payload: hex.EncodeToString(mtx.Payload),
}
if blockHeader != nil {
txReply.Time = uint64(blockHeader.TimeInMilliseconds)
txReply.BlockTime = uint64(blockHeader.TimeInMilliseconds)
txReply.Time = uint64(blockHeader.Timestamp.UnixMilliseconds())
txReply.BlockTime = uint64(blockHeader.Timestamp.UnixMilliseconds())
txReply.BlockHash = blockHash
}
txReply.IsInMempool = isInMempool
if acceptingBlock != nil {
txReply.AcceptedBy = acceptingBlock.String()
}
return txReply, nil
}
func (ctx *Context) buildTransactionVerboseInputs(tx *externalapi.DomainTransaction) []*appmessage.TransactionVerboseInput {
inputs := make([]*appmessage.TransactionVerboseInput, len(tx.Inputs))
for i, transactionInput := range tx.Inputs {
// msgTxToHex serializes a transaction using the latest protocol version and
// returns a hex-encoded string of the result.
func msgTxToHex(msgTx *appmessage.MsgTx) (string, error) {
var buf bytes.Buffer
err := msgTx.KaspaEncode(&buf, 0)
if err != nil {
return "", err
}
return hex.EncodeToString(buf.Bytes()), nil
}
func (ctx *Context) buildTransactionVerboseInputs(mtx *appmessage.MsgTx) []*appmessage.TransactionVerboseInput {
inputs := make([]*appmessage.TransactionVerboseInput, len(mtx.TxIn))
for i, txIn := range mtx.TxIn {
// The disassembled string will contain [error] inline
// if the script doesn't fully parse, so ignore the
// error here.
disbuf, _ := txscript.DisasmString(transactionInput.SignatureScript)
disbuf, _ := txscript.DisasmString(txIn.SignatureScript)
input := &appmessage.TransactionVerboseInput{}
input.TxID = transactionInput.PreviousOutpoint.TransactionID.String()
input.OutputIndex = transactionInput.PreviousOutpoint.Index
input.Sequence = transactionInput.Sequence
input.TxID = txIn.PreviousOutpoint.TxID.String()
input.OutputIndex = txIn.PreviousOutpoint.Index
input.Sequence = txIn.Sequence
input.ScriptSig = &appmessage.ScriptSig{
Asm: disbuf,
Hex: hex.EncodeToString(transactionInput.SignatureScript),
Hex: hex.EncodeToString(txIn.SignatureScript),
}
inputs[i] = input
}
@@ -138,18 +199,18 @@ func (ctx *Context) buildTransactionVerboseInputs(tx *externalapi.DomainTransact
// buildTransactionVerboseOutputs returns a slice of JSON objects for the outputs of the passed
// transaction.
func (ctx *Context) buildTransactionVerboseOutputs(tx *externalapi.DomainTransaction, filterAddrMap map[string]struct{}) []*appmessage.TransactionVerboseOutput {
outputs := make([]*appmessage.TransactionVerboseOutput, len(tx.Outputs))
for i, transactionOutput := range tx.Outputs {
func (ctx *Context) buildTransactionVerboseOutputs(mtx *appmessage.MsgTx, filterAddrMap map[string]struct{}) []*appmessage.TransactionVerboseOutput {
outputs := make([]*appmessage.TransactionVerboseOutput, len(mtx.TxOut))
for i, v := range mtx.TxOut {
// The disassembled string will contain [error] inline if the
// script doesn't fully parse, so ignore the error here.
disbuf, _ := txscript.DisasmString(transactionOutput.ScriptPublicKey)
disbuf, _ := txscript.DisasmString(v.ScriptPubKey)
// Ignore the error here since an error means the script
// couldn't parse and there is no additional information about
// it anyways.
scriptClass, addr, _ := txscript.ExtractScriptPubKeyAddress(
transactionOutput.ScriptPublicKey, ctx.Config.ActiveNetParams)
v.ScriptPubKey, ctx.DAG.Params)
// Encode the addresses while checking if the address passes the
// filter when needed.
@@ -171,11 +232,11 @@ func (ctx *Context) buildTransactionVerboseOutputs(tx *externalapi.DomainTransac
output := &appmessage.TransactionVerboseOutput{}
output.Index = uint32(i)
output.Value = transactionOutput.Value
output.Value = v.Value
output.ScriptPubKey = &appmessage.ScriptPubKeyResult{
Address: encodedAddr,
Asm: disbuf,
Hex: hex.EncodeToString(transactionOutput.ScriptPublicKey),
Hex: hex.EncodeToString(v.ScriptPubKey),
Type: scriptClass.String(),
}
outputs[i] = output

View File

@@ -10,7 +10,7 @@ import (
// HandleAddPeer handles the respectively named RPC command
func HandleAddPeer(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
AddPeerRequest := request.(*appmessage.AddPeerRequestMessage)
address, err := network.NormalizeAddress(AddPeerRequest.Address, context.Config.ActiveNetParams.DefaultPort)
address, err := network.NormalizeAddress(AddPeerRequest.Address, context.DAG.Params.DefaultPort)
if err != nil {
errorMessage := &appmessage.AddPeerResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse address: %s", err)

View File

@@ -1,10 +1,14 @@
package rpchandlers
import (
"bufio"
"bytes"
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// HandleGetBlock handles the respectively named RPC command
@@ -12,27 +16,83 @@ func HandleGetBlock(context *rpccontext.Context, _ *router.Router, request appme
getBlockRequest := request.(*appmessage.GetBlockRequestMessage)
// Load the raw block bytes from the database.
hash, err := hashes.FromString(getBlockRequest.Hash)
hash, err := daghash.NewHashFromStr(getBlockRequest.Hash)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Hash could not be parsed: %s", err)
return errorMessage, nil
}
block, err := context.Domain.Consensus().GetBlock(hash)
context.DAG.RLock()
defer context.DAG.RUnlock()
if context.DAG.IsKnownInvalid(hash) {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s is known to be invalid", hash)
return errorMessage, nil
}
if context.DAG.IsKnownOrphan(hash) {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s is an orphan", hash)
return errorMessage, nil
}
block, err := context.DAG.BlockByHash(hash)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found", hash)
return errorMessage, nil
}
response := appmessage.NewGetBlockResponseMessage()
blockVerboseData, err := context.BuildBlockVerboseData(block, getBlockRequest.IncludeTransactionVerboseData)
blockBytes, err := block.Bytes()
if err != nil {
return nil, err
}
response.BlockVerboseData = blockVerboseData
// Handle partial blocks
if getBlockRequest.SubnetworkID != "" {
requestSubnetworkID, err := subnetworkid.NewFromStr(getBlockRequest.SubnetworkID)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("SubnetworkID could not be parsed: %s", err)
return errorMessage, nil
}
nodeSubnetworkID := context.Config.SubnetworkID
if requestSubnetworkID != nil {
if nodeSubnetworkID != nil {
if !nodeSubnetworkID.IsEqual(requestSubnetworkID) {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("subnetwork %s does not match this partial node",
getBlockRequest.SubnetworkID)
return errorMessage, nil
}
// nothing to do - partial node stores partial blocks
} else {
// Deserialize the block.
msgBlock := block.MsgBlock()
msgBlock.ConvertToPartial(requestSubnetworkID)
var b bytes.Buffer
err := msgBlock.Serialize(bufio.NewWriter(&b))
if err != nil {
return nil, err
}
blockBytes = b.Bytes()
}
}
}
response := appmessage.NewGetBlockResponseMessage()
if getBlockRequest.IncludeBlockHex {
response.BlockHex = hex.EncodeToString(blockBytes)
}
if getBlockRequest.IncludeBlockVerboseData {
blockVerboseData, err := context.BuildBlockVerboseData(block, getBlockRequest.IncludeTransactionVerboseData)
if err != nil {
return nil, err
}
response.BlockVerboseData = blockVerboseData
}
return response, nil
}

View File

@@ -8,6 +8,6 @@ import (
// HandleGetBlockCount handles the respectively named RPC command
func HandleGetBlockCount(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
response := appmessage.NewGetBlockCountResponseMessage(0) // TODO
response := appmessage.NewGetBlockCountResponseMessage(context.DAG.BlockCount())
return response, nil
}

View File

@@ -4,14 +4,19 @@ import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleGetBlockDAGInfo handles the respectively named RPC command
func HandleGetBlockDAGInfo(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
params := context.Config.ActiveNetParams
dag := context.DAG
params := dag.Params
response := appmessage.NewGetBlockDAGInfoResponseMessage()
response.NetworkName = params.Name
response.BlockCount = dag.BlockCount()
response.TipHashes = daghash.Strings(dag.TipHashes())
response.Difficulty = context.GetDifficultyRatio(dag.CurrentBits(), params)
response.PastMedianTime = dag.CalcPastMedianTime().UnixMilliseconds()
return response, nil
}

View File

@@ -3,8 +3,6 @@ package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
@@ -13,21 +11,76 @@ import (
func HandleGetBlockTemplate(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlockTemplateRequest := request.(*appmessage.GetBlockTemplateRequestMessage)
payAddress, err := util.DecodeAddress(getBlockTemplateRequest.PayAddress, context.Config.ActiveNetParams.Prefix)
payAddress, err := util.DecodeAddress(getBlockTemplateRequest.PayAddress, context.DAG.Params.Prefix)
if err != nil {
errorMessage := &appmessage.GetBlockResponseMessage{}
errorMessage := &appmessage.GetBlockTemplateResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not decode address: %s", err)
return errorMessage, nil
}
scriptPublicKey, err := txscript.PayToAddrScript(payAddress)
// When a long poll ID was provided, this is a long poll request by the
// client to be notified when block template referenced by the ID should
// be replaced with a new one.
if getBlockTemplateRequest.LongPollID != "" {
return handleGetBlockTemplateLongPoll(context, getBlockTemplateRequest.LongPollID, payAddress)
}
// Protect concurrent access when updating block templates.
context.BlockTemplateState.Lock()
defer context.BlockTemplateState.Unlock()
// Get and return a block template. A new block template will be
// generated when the current best block has changed or the transactions
// in the memory pool have been updated and it has been at least five
// seconds since the last template was generated. Otherwise, the
// timestamp for the existing block template is updated (and possibly
// the difficulty on testnet per the consesus rules).
err = context.BlockTemplateState.Update(payAddress)
if err != nil {
return nil, err
}
return context.BlockTemplateState.Response()
}
// handleGetBlockTemplateLongPoll is a helper for handleGetBlockTemplateRequest
// which deals with handling long polling for block templates. When a caller
// sends a request with a long poll ID that was previously returned, a response
// is not sent until the caller should stop working on the previous block
// template in favor of the new one. In particular, this is the case when the
// old block template is no longer valid due to a solution already being found
// and added to the block DAG, or new transactions have shown up and some time
// has passed without finding a solution.
func handleGetBlockTemplateLongPoll(context *rpccontext.Context, longPollID string,
payAddress util.Address) (*appmessage.GetBlockTemplateResponseMessage, error) {
state := context.BlockTemplateState
result, longPollChan, err := state.BlockTemplateOrLongPollChan(longPollID, payAddress)
if err != nil {
return nil, err
}
coinbaseData := &externalapi.DomainCoinbaseData{ScriptPublicKey: scriptPublicKey}
if result != nil {
return result, nil
}
templateBlock := context.Domain.MiningManager().GetBlockTemplate(coinbaseData)
// Wait until signal received to send the reply.
<-longPollChan
return appmessage.DomainBlockToMsgBlock(templateBlock), nil
// Get the lastest block template
state.Lock()
defer state.Unlock()
if err := state.Update(payAddress); err != nil {
return nil, err
}
// 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 = state.Response()
if err != nil {
return nil, err
}
return result, nil
}

View File

@@ -1,19 +1,118 @@
package rpchandlers
import (
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
)
const (
// maxBlocksInGetBlocksResponse is the max amount of blocks that are
// allowed in a GetBlocksResult.
maxBlocksInGetBlocksResponse = 100
maxBlocksInGetBlocksResponse = 1000
)
// HandleGetBlocks handles the respectively named RPC command
func HandleGetBlocks(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
getBlocksRequest := request.(*appmessage.GetBlocksRequestMessage)
return nil, nil
var lowHash *daghash.Hash
if getBlocksRequest.LowHash != "" {
lowHash = &daghash.Hash{}
err := daghash.Decode(lowHash, getBlocksRequest.LowHash)
if err != nil {
errorMessage := &appmessage.GetBlocksResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse lowHash: %s", err)
return errorMessage, nil
}
}
context.DAG.RLock()
defer context.DAG.RUnlock()
// If lowHash is not in the DAG, there's nothing to do; return an error.
if lowHash != nil && !context.DAG.IsKnownBlock(lowHash) {
errorMessage := &appmessage.GetBlocksResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found in DAG", lowHash)
return errorMessage, nil
}
// Retrieve the block hashes.
blockHashes, err := context.DAG.BlockHashesFrom(lowHash, maxBlocksInGetBlocksResponse)
if err != nil {
return nil, err
}
// Convert the hashes to strings
hashes := make([]string, len(blockHashes))
for i, blockHash := range blockHashes {
hashes[i] = blockHash.String()
}
// Include more data if requested
var blockHexes []string
var blockVerboseData []*appmessage.BlockVerboseData
if getBlocksRequest.IncludeBlockHexes || getBlocksRequest.IncludeBlockVerboseData {
blockBytesSlice, err := hashesToBlockBytes(context, blockHashes)
if err != nil {
return nil, err
}
if getBlocksRequest.IncludeBlockHexes {
blockHexes = blockBytesToStrings(blockBytesSlice)
}
if getBlocksRequest.IncludeBlockVerboseData {
data, err := blockBytesToBlockVerboseResults(context, blockBytesSlice, getBlocksRequest.IncludeBlockVerboseData)
if err != nil {
return nil, err
}
blockVerboseData = data
}
}
response := appmessage.NewGetBlocksResponseMessage(hashes, blockHexes, blockVerboseData)
return response, nil
}
func hashesToBlockBytes(context *rpccontext.Context, hashes []*daghash.Hash) ([][]byte, error) {
blocks := make([][]byte, len(hashes))
for i, hash := range hashes {
block, err := context.DAG.BlockByHash(hash)
if err != nil {
return nil, err
}
blockBytes, err := block.Bytes()
if err != nil {
return nil, err
}
blocks[i] = blockBytes
}
return blocks, nil
}
func blockBytesToStrings(blockBytesSlice [][]byte) []string {
rawBlocks := make([]string, len(blockBytesSlice))
for i, blockBytes := range blockBytesSlice {
rawBlocks[i] = hex.EncodeToString(blockBytes)
}
return rawBlocks
}
func blockBytesToBlockVerboseResults(context *rpccontext.Context, blockBytesSlice [][]byte,
includeTransactionVerboseData bool) ([]*appmessage.BlockVerboseData, error) {
verboseBlocks := make([]*appmessage.BlockVerboseData, len(blockBytesSlice))
for i, blockBytes := range blockBytesSlice {
block, err := util.NewBlockFromBytes(blockBytes)
if err != nil {
return nil, err
}
getBlockVerboseResult, err := context.BuildBlockVerboseData(block, includeTransactionVerboseData)
if err != nil {
return nil, err
}
verboseBlocks[i] = getBlockVerboseResult
}
return verboseBlocks, nil
}

View File

@@ -4,6 +4,8 @@ import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
const (
@@ -14,5 +16,91 @@ const (
// HandleGetChainFromBlock handles the respectively named RPC command
func HandleGetChainFromBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
return nil, nil
getChainFromBlockRequest := request.(*appmessage.GetChainFromBlockRequestMessage)
if context.AcceptanceIndex == nil {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("The acceptance index must be " +
"enabled to get the selected parent chain " +
"(specify --acceptanceindex)")
return errorMessage, nil
}
var startHash *daghash.Hash
if getChainFromBlockRequest.StartHash != "" {
startHash = &daghash.Hash{}
err := daghash.Decode(startHash, getChainFromBlockRequest.StartHash)
if err != nil {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse startHash: %s", err)
return errorMessage, nil
}
}
context.DAG.RLock()
defer context.DAG.RUnlock()
// If startHash is not in the selected parent chain, there's nothing
// to do; return an error.
if startHash != nil && !context.DAG.IsInDAG(startHash) {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block %s not found in the DAG", startHash)
return errorMessage, nil
}
// Retrieve the selected parent chain.
removedChainHashes, addedChainHashes, err := context.DAG.SelectedParentChain(startHash)
if err != nil {
return nil, err
}
// Limit the amount of blocks in the response
if len(addedChainHashes) > maxBlocksInGetChainFromBlockResponse {
addedChainHashes = addedChainHashes[:maxBlocksInGetChainFromBlockResponse]
}
// Collect addedChainBlocks.
addedChainBlocks, err := context.CollectChainBlocks(addedChainHashes)
if err != nil {
errorMessage := &appmessage.GetChainFromBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not collect chain blocks: %s", err)
return errorMessage, nil
}
// Collect removedHashes.
removedHashes := make([]string, len(removedChainHashes))
for i, hash := range removedChainHashes {
removedHashes[i] = hash.String()
}
// If the user specified to include the blocks, collect them as well.
var blockVerboseData []*appmessage.BlockVerboseData
if getChainFromBlockRequest.IncludeBlockVerboseData {
data, err := hashesToBlockVerboseData(context, addedChainHashes)
if err != nil {
return nil, err
}
blockVerboseData = data
}
response := appmessage.NewGetChainFromBlockResponseMessage(removedHashes, addedChainBlocks, blockVerboseData)
return response, nil
}
// hashesToBlockVerboseData takes block hashes and returns their
// correspondent block verbose.
func hashesToBlockVerboseData(context *rpccontext.Context, hashes []*daghash.Hash) ([]*appmessage.BlockVerboseData, error) {
getBlockVerboseResults := make([]*appmessage.BlockVerboseData, 0, len(hashes))
for _, blockHash := range hashes {
block, err := context.DAG.BlockByHash(blockHash)
if err != nil {
return nil, errors.Errorf("could not retrieve block %s.", blockHash)
}
getBlockVerboseResult, err := context.BuildBlockVerboseData(block, false)
if err != nil {
return nil, errors.Wrapf(err, "could not build getBlockVerboseResult for block %s", blockHash)
}
getBlockVerboseResults = append(getBlockVerboseResults, getBlockVerboseResult)
}
return getBlockVerboseResults, nil
}

View File

@@ -8,6 +8,6 @@ import (
// HandleGetCurrentNetwork handles the respectively named RPC command
func HandleGetCurrentNetwork(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
response := appmessage.NewGetCurrentNetworkResponseMessage(context.Config.ActiveNetParams.Net.String())
response := appmessage.NewGetCurrentNetworkResponseMessage(context.DAG.Params.Net.String())
return response, nil
}

View File

@@ -1,12 +1,52 @@
package rpchandlers
import (
"bytes"
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleGetHeaders handles the respectively named RPC command
func HandleGetHeaders(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
return nil, nil
getHeadersRequest := request.(*appmessage.GetHeadersRequestMessage)
dag := context.DAG
var startHash *daghash.Hash
if getHeadersRequest.StartHash != "" {
var err error
startHash, err = daghash.NewHashFromStr(getHeadersRequest.StartHash)
if err != nil {
errorMessage := &appmessage.GetHeadersResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Start hash could not be parsed: %s", err)
return errorMessage, nil
}
}
const getHeadersDefaultLimit uint64 = 2000
limit := getHeadersDefaultLimit
if getHeadersRequest.Limit != 0 {
limit = getHeadersRequest.Limit
}
headers, err := dag.GetHeaders(startHash, limit, getHeadersRequest.IsAscending)
if err != nil {
errorMessage := &appmessage.GetHeadersResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Error getting the headers: %s", err)
return errorMessage, nil
}
headersHex := make([]string, len(headers))
var buf bytes.Buffer
for i, header := range headers {
err := header.Serialize(&buf)
if err != nil {
return nil, err
}
headersHex[i] = hex.EncodeToString(buf.Bytes())
buf.Reset()
}
return appmessage.NewGetHeadersResponseMessage(headersHex), nil
}

View File

@@ -1,12 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetMempoolEntries handles the respectively named RPC command
func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
return nil, nil
}

View File

@@ -4,9 +4,26 @@ import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
)
// HandleGetMempoolEntry handles the respectively named RPC command
func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
return nil, nil
getMempoolEntryRequest := request.(*appmessage.GetMempoolEntryRequestMessage)
txID, err := daghash.NewTxIDFromStr(getMempoolEntryRequest.TxID)
if err != nil {
errorMessage := &appmessage.GetMempoolEntryResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse txId: %s", err)
return errorMessage, nil
}
_, ok := context.Mempool.FetchTxDesc(txID)
if !ok {
errorMessage := &appmessage.GetMempoolEntryResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("transaction is not in the pool")
return errorMessage, nil
}
response := appmessage.NewGetMempoolEntryResponseMessage()
return response, nil
}

View File

@@ -3,19 +3,11 @@ package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleGetSelectedTipHash handles the respectively named RPC command
func HandleGetSelectedTipHash(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
selectedTip, err := context.Domain.Consensus().GetVirtualSelectedParent()
if err != nil {
return nil, err
}
response := appmessage.NewGetSelectedTipHashResponseMessage(
consensusserialization.BlockHash(selectedTip).String())
response := appmessage.NewGetSelectedTipHashResponseMessage(context.DAG.SelectedTipHash().String())
return response, nil
}

View File

@@ -4,9 +4,31 @@ import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util/subnetworkid"
)
// HandleGetSubnetwork handles the respectively named RPC command
func HandleGetSubnetwork(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
return nil, nil
getSubnetworkRequest := request.(*appmessage.GetSubnetworkRequestMessage)
subnetworkID, err := subnetworkid.NewFromStr(getSubnetworkRequest.SubnetworkID)
if err != nil {
errorMessage := &appmessage.GetSubnetworkResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Could not parse subnetworkID: %s", err)
return errorMessage, nil
}
var gasLimit uint64
if !subnetworkID.IsBuiltInOrNative() {
limit, err := context.DAG.GasLimit(subnetworkID)
if err != nil {
errorMessage := &appmessage.GetSubnetworkResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Subnetwork %s not found.", subnetworkID)
return errorMessage, nil
}
gasLimit = limit
}
response := appmessage.NewGetSubnetworkResponseMessage(gasLimit)
return response, nil
}

View File

@@ -2,8 +2,6 @@ package rpchandlers
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"github.com/kaspanet/kaspad/util/panics"
)
var log, _ = logger.Get(logger.SubsystemTags.RPCS)
var spawn = panics.GoroutineWrapperFunc(log)

View File

@@ -8,6 +8,12 @@ import (
// HandleNotifyChainChanged handles the respectively named RPC command
func HandleNotifyChainChanged(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
if context.AcceptanceIndex == nil {
errorMessage := appmessage.NewNotifyChainChangedResponseMessage()
errorMessage.Error = appmessage.RPCErrorf("Acceptance index is not available")
return errorMessage, nil
}
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err

View File

@@ -1,20 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleNotifyFinalityConflicts handles the respectively named RPC command
func HandleNotifyFinalityConflicts(context *rpccontext.Context, router *router.Router, _ appmessage.Message) (appmessage.Message, error) {
listener, err := context.NotificationManager.Listener(router)
if err != nil {
return nil, err
}
listener.PropagateFinalityConflictNotifications()
listener.PropagateFinalityConflictResolvedNotifications()
response := appmessage.NewNotifyFinalityConflictsResponseMessage()
return response, nil
}

View File

@@ -1,12 +0,0 @@
package rpchandlers
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
// HandleResolveFinalityConflict handles the respectively named RPC command
func HandleResolveFinalityConflict(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
return nil, nil
}

View File

@@ -1,25 +0,0 @@
package rpchandlers
import (
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
)
const pauseBeforeShutDown = time.Second
// HandleShutDown handles the respectively named RPC command
func HandleShutDown(context *rpccontext.Context, _ *router.Router, _ appmessage.Message) (appmessage.Message, error) {
log.Warn("ShutDown RPC called.")
// Wait a second before shutting down, to allow time to return the response to the caller
spawn("HandleShutDown-pauseAndShutDown", func() {
<-time.After(pauseBeforeShutDown)
close(context.ShutDownChan)
})
response := appmessage.NewShutDownResponseMessage()
return response, nil
}

View File

@@ -1,27 +1,40 @@
package rpchandlers
import (
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
)
// HandleSubmitBlock handles the respectively named RPC command
func HandleSubmitBlock(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitBlockRequest := request.(*appmessage.SubmitBlockRequestMessage)
msgBlock := submitBlockRequest.Block
domainBlock := appmessage.MsgBlockToDomainBlock(msgBlock)
// Deserialize the submitted block.
serializedBlock, err := hex.DecodeString(submitBlockRequest.BlockHex)
if err != nil {
errorMessage := &appmessage.SubmitBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block hex could not be parsed: %s", err)
return errorMessage, nil
}
block, err := util.NewBlockFromBytes(serializedBlock)
if err != nil {
errorMessage := &appmessage.SubmitBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block decode failed: %s", err)
return errorMessage, nil
}
err := context.ProtocolManager.AddBlock(domainBlock)
err = context.ProtocolManager.AddBlock(block, blockdag.BFDisallowDelay|blockdag.BFDisallowOrphans)
if err != nil {
errorMessage := &appmessage.SubmitBlockResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Block rejected. Reason: %s", err)
return errorMessage, nil
}
log.Infof("Accepted domainBlock %s via submitBlock", consensusserialization.BlockHash(domainBlock))
log.Infof("Accepted block %s via submitBlock", block.Hash())
response := appmessage.NewSubmitBlockResponseMessage()
return response, nil

View File

@@ -1,11 +1,13 @@
package rpchandlers
import (
"bytes"
"encoding/hex"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
"github.com/kaspanet/kaspad/domain/mempool"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
@@ -13,20 +15,33 @@ import (
func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, request appmessage.Message) (appmessage.Message, error) {
submitTransactionRequest := request.(*appmessage.SubmitTransactionRequestMessage)
domainTransaction := appmessage.MsgTxToDomainTransaction(submitTransactionRequest.Transaction)
transactionID := consensusserialization.TransactionID(domainTransaction)
err := context.ProtocolManager.AddTransaction(domainTransaction)
serializedTx, err := hex.DecodeString(submitTransactionRequest.TransactionHex)
if err != nil {
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Transaction hex could not be parsed: %s", err)
return errorMessage, nil
}
var msgTx appmessage.MsgTx
err = msgTx.Deserialize(bytes.NewReader(serializedTx))
if err != nil {
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Transaction decode failed: %s", err)
return errorMessage, nil
}
tx := util.NewTx(&msgTx)
err = context.ProtocolManager.AddTransaction(tx)
if err != nil {
if !errors.As(err, &mempool.RuleError{}) {
return nil, err
}
log.Debugf("Rejected transaction %s: %s", transactionID, err)
log.Debugf("Rejected transaction %s: %s", tx.ID(), err)
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", transactionID, err)
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", tx.ID(), err)
return errorMessage, nil
}
response := appmessage.NewSubmitTransactionResponseMessage(transactionID.String())
response := appmessage.NewSubmitTransactionResponseMessage(tx.ID().String())
return response, nil
}

88
cmd/addblock/addblock.go Normal file
View 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 main
import (
"github.com/kaspanet/kaspad/infrastructure/logger"
"os"
"runtime"
"github.com/kaspanet/kaspad/infrastructure/os/limits"
"github.com/kaspanet/kaspad/util/panics"
)
const (
// blockDBNamePrefix is the prefix for the kaspad block database.
blockDBNamePrefix = "blocks"
)
var (
cfg *ConfigFlags
log *logger.Logger
spawn func(string, func())
)
// realMain is the real main function for the utility. It is necessary to work
// around the fact that deferred functions do not run when os.Exit() is called.
func realMain() error {
// Load configuration and parse command line.
tcfg, _, err := loadConfig()
if err != nil {
return err
}
cfg = tcfg
// Setup logging.
backendLogger := logger.NewBackend()
defer os.Stdout.Sync()
log = backendLogger.Logger("MAIN")
spawn = panics.GoroutineWrapperFunc(log)
fi, err := os.Open(cfg.InFile)
if err != nil {
log.Errorf("Failed to open file %s: %s", cfg.InFile, err)
return err
}
defer fi.Close()
// Create a block importer for the database and input file and start it.
// The done channel returned from start will contain an error if
// anything went wrong.
importer, err := newBlockImporter(fi)
if err != nil {
log.Errorf("Failed create block importer: %s", err)
return err
}
// Perform the import asynchronously. This allows blocks to be
// processed and read in parallel. The results channel returned from
// Import contains the statistics about the import including an error
// if something went wrong.
log.Info("Starting import")
resultsChan := importer.Import()
results := <-resultsChan
if results.err != nil {
log.Errorf("%s", results.err)
return results.err
}
log.Infof("Processed a total of %d blocks (%d imported, %d already "+
"known)", results.blocksProcessed, results.blocksImported,
results.blocksProcessed-results.blocksImported)
return nil
}
func main() {
// Use all processor cores and up some limits.
runtime.GOMAXPROCS(runtime.NumCPU())
if err := limits.SetLimits(); err != nil {
os.Exit(1)
}
// Work around defer not working after os.Exit()
if err := realMain(); err != nil {
os.Exit(1)
}
}

97
cmd/addblock/config.go Normal file
View File

@@ -0,0 +1,97 @@
// 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 main
import (
"fmt"
flags "github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
"os"
"path/filepath"
)
const (
defaultDataFile = "bootstrap.dat"
defaultProgress = 10
)
var (
kaspadHomeDir = util.AppDataDir("kaspad", false)
defaultDataDir = filepath.Join(kaspadHomeDir, "data")
activeConfig *ConfigFlags
)
// ActiveConfig returns the active configuration struct
func ActiveConfig() *ConfigFlags {
return activeConfig
}
// ConfigFlags defines the configuration options for addblock.
//
// See loadConfig for details on the configuration load process.
type ConfigFlags struct {
DataDir string `short:"b" long:"datadir" description:"Location of the kaspad data directory"`
InFile string `short:"i" long:"infile" description:"File containing the block(s)"`
Progress int `short:"p" long:"progress" description:"Show a progress message each time this number of seconds have passed -- Use 0 to disable progress announcements"`
AcceptanceIndex bool `long:"acceptanceindex" description:"Maintain a full hash-based acceptance index which makes the getChainFromBlock RPC available"`
config.NetworkFlags
}
// filesExists reports whether the named file or directory exists.
func fileExists(name string) bool {
if _, err := os.Stat(name); err != nil {
if os.IsNotExist(err) {
return false
}
}
return true
}
// loadConfig initializes and parses the config using command line options.
func loadConfig() (*ConfigFlags, []string, error) {
// Default config.
activeConfig = &ConfigFlags{
DataDir: defaultDataDir,
InFile: defaultDataFile,
Progress: defaultProgress,
}
// Parse command line options.
parser := flags.NewParser(&activeConfig, flags.Default)
remainingArgs, err := parser.Parse()
if err != nil {
var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); !ok || flagsErr.Type != flags.ErrHelp {
parser.WriteHelp(os.Stderr)
}
return nil, nil, err
}
err = activeConfig.ResolveNetwork(parser)
if err != nil {
return nil, nil, err
}
// Append the network type to the data directory so it is "namespaced"
// per network. In addition to the block database, there are other
// pieces of data that are saved to disk such as address manager state.
// All data is specific to a network, so namespacing the data directory
// means each individual piece of serialized data does not have to
// worry about changing names per network and such.
cfg.DataDir = filepath.Join(cfg.DataDir, ActiveConfig().NetParams().Name)
// Ensure the specified block file exists.
if !fileExists(cfg.InFile) {
str := "%s: The specified block file [%s] does not exist"
err := errors.Errorf(str, "loadConfig", cfg.InFile)
fmt.Fprintln(os.Stderr, err)
parser.WriteHelp(os.Stderr)
return nil, nil, err
}
return cfg, remainingArgs, nil
}

321
cmd/addblock/import.go Normal file
View File

@@ -0,0 +1,321 @@
// 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 main
import (
"encoding/binary"
"github.com/kaspanet/kaspad/domain/blockdag/indexers"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/pkg/errors"
"io"
"sync"
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/blockdag"
"github.com/kaspanet/kaspad/util"
)
// importResults houses the stats and result as an import operation.
type importResults struct {
blocksProcessed int64
blocksImported int64
err error
}
// blockImporter houses information about an ongoing import from a block data
// file to the block database.
type blockImporter struct {
dag *blockdag.BlockDAG
r io.ReadSeeker
processQueue chan []byte
doneChan chan bool
errChan chan error
quit chan struct{}
wg sync.WaitGroup
blocksProcessed int64
blocksImported int64
receivedLogBlocks int64
receivedLogTx int64
lastHeight int64
lastBlockTime mstime.Time
lastLogTime mstime.Time
}
// readBlock reads the next block from the input file.
func (bi *blockImporter) readBlock() ([]byte, error) {
// The block file format is:
// <network> <block length> <serialized block>
var net uint32
err := binary.Read(bi.r, binary.LittleEndian, &net)
if err != nil {
if err != io.EOF {
return nil, err
}
// No block and no error means there are no more blocks to read.
return nil, nil
}
if net != uint32(ActiveConfig().NetParams().Net) {
return nil, errors.Errorf("network mismatch -- got %x, want %x",
net, uint32(ActiveConfig().NetParams().Net))
}
// Read the block length and ensure it is sane.
var blockLen uint32
if err := binary.Read(bi.r, binary.LittleEndian, &blockLen); err != nil {
return nil, err
}
if blockLen > appmessage.MaxMessagePayload {
return nil, errors.Errorf("block payload of %d bytes is larger "+
"than the max allowed %d bytes", blockLen,
appmessage.MaxMessagePayload)
}
serializedBlock := make([]byte, blockLen)
if _, err := io.ReadFull(bi.r, serializedBlock); err != nil {
return nil, err
}
return serializedBlock, nil
}
// processBlock potentially imports the block into the database. It first
// deserializes the raw block while checking for errors. Already known blocks
// are skipped and orphan blocks are considered errors. Finally, it runs the
// block through the DAG rules to ensure it follows all rules.
// Returns whether the block was imported along with any potential errors.
func (bi *blockImporter) processBlock(serializedBlock []byte) (bool, error) {
// Deserialize the block which includes checks for malformed blocks.
block, err := util.NewBlockFromBytes(serializedBlock)
if err != nil {
return false, err
}
// update progress statistics
bi.lastBlockTime = block.MsgBlock().Header.Timestamp
bi.receivedLogTx += int64(len(block.MsgBlock().Transactions))
// Skip blocks that already exist.
blockHash := block.Hash()
if bi.dag.IsKnownBlock(blockHash) {
return false, nil
}
// Don't bother trying to process orphans.
parentHashes := block.MsgBlock().Header.ParentHashes
if len(parentHashes) > 0 {
if !bi.dag.AreKnownBlocks(parentHashes) {
return false, errors.Errorf("import file contains block "+
"%v which does not link to the available "+
"block DAG", parentHashes)
}
}
// Ensure the blocks follows all of the DAG rules.
isOrphan, isDelayed, err := bi.dag.ProcessBlock(block,
blockdag.BFFastAdd)
if err != nil {
return false, err
}
if isDelayed {
return false, errors.Errorf("import file contains a block that is too far in the future")
}
if isOrphan {
return false, errors.Errorf("import file contains an orphan "+
"block: %s", blockHash)
}
return true, nil
}
// readHandler is the main handler for reading blocks from the import file.
// This allows block processing to take place in parallel with block reads.
// It must be run as a goroutine.
func (bi *blockImporter) readHandler() {
out:
for {
// Read the next block from the file and if anything goes wrong
// notify the status handler with the error and bail.
serializedBlock, err := bi.readBlock()
if err != nil {
bi.errChan <- errors.Errorf("Error reading from input "+
"file: %s", err.Error())
break out
}
// A nil block with no error means we're done.
if serializedBlock == nil {
break out
}
// Send the block or quit if we've been signalled to exit by
// the status handler due to an error elsewhere.
select {
case bi.processQueue <- serializedBlock:
case <-bi.quit:
break out
}
}
// Close the processing channel to signal no more blocks are coming.
close(bi.processQueue)
bi.wg.Done()
}
// logProgress logs block progress as an information message. In order to
// prevent spam, it limits logging to one message every cfg.Progress seconds
// with duration and totals included.
func (bi *blockImporter) logProgress() {
bi.receivedLogBlocks++
now := mstime.Now()
duration := now.Sub(bi.lastLogTime)
if duration < time.Second*time.Duration(cfg.Progress) {
return
}
// Truncate the duration to 10s of milliseconds.
durationMillis := int64(duration / time.Millisecond)
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
// Log information about new block height.
blockStr := "blocks"
if bi.receivedLogBlocks == 1 {
blockStr = "block"
}
txStr := "transactions"
if bi.receivedLogTx == 1 {
txStr = "transaction"
}
log.Infof("Processed %d %s in the last %s (%d %s, height %d, %s)",
bi.receivedLogBlocks, blockStr, tDuration, bi.receivedLogTx,
txStr, bi.lastHeight, bi.lastBlockTime)
bi.receivedLogBlocks = 0
bi.receivedLogTx = 0
bi.lastLogTime = now
}
// processHandler is the main handler for processing blocks. This allows block
// processing to take place in parallel with block reads from the import file.
// It must be run as a goroutine.
func (bi *blockImporter) processHandler() {
out:
for {
select {
case serializedBlock, ok := <-bi.processQueue:
// We're done when the channel is closed.
if !ok {
break out
}
bi.blocksProcessed++
bi.lastHeight++
imported, err := bi.processBlock(serializedBlock)
if err != nil {
bi.errChan <- err
break out
}
if imported {
bi.blocksImported++
}
bi.logProgress()
case <-bi.quit:
break out
}
}
bi.wg.Done()
}
// statusHandler waits for updates from the import operation and notifies
// the passed doneChan with the results of the import. It also causes all
// goroutines to exit if an error is reported from any of them.
func (bi *blockImporter) statusHandler(resultsChan chan *importResults) {
select {
// An error from either of the goroutines means we're done so signal
// caller with the error and signal all goroutines to quit.
case err := <-bi.errChan:
resultsChan <- &importResults{
blocksProcessed: bi.blocksProcessed,
blocksImported: bi.blocksImported,
err: err,
}
close(bi.quit)
// The import finished normally.
case <-bi.doneChan:
resultsChan <- &importResults{
blocksProcessed: bi.blocksProcessed,
blocksImported: bi.blocksImported,
err: nil,
}
}
}
// Import is the core function which handles importing the blocks from the file
// associated with the block importer to the database. It returns a channel
// on which the results will be returned when the operation has completed.
func (bi *blockImporter) Import() chan *importResults {
// Start up the read and process handling goroutines. This setup allows
// blocks to be read from disk in parallel while being processed.
bi.wg.Add(2)
spawn("blockImporter.readHandler", bi.readHandler)
spawn("blockImporter.processHandler", bi.processHandler)
// Wait for the import to finish in a separate goroutine and signal
// the status handler when done.
spawn("blockImporter.sendToDoneChan", func() {
bi.wg.Wait()
bi.doneChan <- true
})
// Start the status handler and return the result channel that it will
// send the results on when the import is done.
resultChan := make(chan *importResults)
spawn("blockImporter.statusHandler", func() {
bi.statusHandler(resultChan)
})
return resultChan
}
// newBlockImporter returns a new importer for the provided file reader seeker
// and database.
func newBlockImporter(r io.ReadSeeker) (*blockImporter, error) {
// Create the acceptance index if needed.
var indexes []indexers.Indexer
if cfg.AcceptanceIndex {
log.Info("Acceptance index is enabled")
indexes = append(indexes, indexers.NewAcceptanceIndex())
}
// Create an index manager if any of the optional indexes are enabled.
var indexManager blockdag.IndexManager
if len(indexes) > 0 {
indexManager = indexers.NewManager(indexes)
}
dag, err := blockdag.New(&blockdag.Config{
DAGParams: ActiveConfig().NetParams(),
TimeSource: blockdag.NewTimeSource(),
IndexManager: indexManager,
})
if err != nil {
return nil, err
}
return &blockImporter{
r: r,
processQueue: make(chan []byte, 2),
doneChan: make(chan bool),
errChan: make(chan error),
quit: make(chan struct{}),
dag: dag,
lastLogTime: mstime.Now(),
}, nil
}

View File

@@ -1,19 +1,18 @@
package main
import (
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"github.com/jessevdk/go-flags"
)
var (
defaultRPCServer = "localhost"
defaultTimeout uint64 = 30
defaultRPCServer = "localhost"
)
type configFlags struct {
RPCServer string `short:"s" long:"rpcserver" description:"RPC server to connect to"`
Timeout uint64 `short:"t" long:"timeout" description:"Timeout for the request (in seconds)"`
RequestJSON string `description:"The request in JSON format"`
config.NetworkFlags
}
@@ -21,7 +20,6 @@ type configFlags struct {
func parseConfig() (*configFlags, error) {
cfg := &configFlags{
RPCServer: defaultRPCServer,
Timeout: defaultTimeout,
}
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
args, err := parser.Parse()

View File

@@ -4,7 +4,6 @@ import (
"fmt"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient/grpcclient"
"os"
"time"
)
func main() {
@@ -13,36 +12,19 @@ func main() {
printErrorAndExit(fmt.Sprintf("error parsing command-line arguments: %s", err))
}
rpcAddress, err := cfg.NetParams().NormalizeRPCServerAddress(cfg.RPCServer)
if err != nil {
printErrorAndExit(fmt.Sprintf("error parsing RPC server address: %s", err))
}
client, err := grpcclient.Connect(rpcAddress)
client, err := grpcclient.Connect(cfg.RPCServer)
if err != nil {
printErrorAndExit(fmt.Sprintf("error connecting to the RPC server: %s", err))
}
defer client.Disconnect()
var responseString string
done := make(chan struct{})
go func() {
requestString := cfg.RequestJSON
var err error
responseString, err = client.PostJSON(requestString)
if err != nil {
printErrorAndExit(fmt.Sprintf("error posting the request to the RPC server: %s", err))
}
done <- struct{}{}
}()
timeout := time.Duration(cfg.Timeout) * time.Second
select {
case <-done:
fmt.Println(responseString)
case <-time.After(timeout):
printErrorAndExit(fmt.Sprintf("timeout of %s has been exceeded", timeout))
requestString := cfg.RequestJSON
responseString, err := client.PostJSON(requestString)
if err != nil {
printErrorAndExit(fmt.Sprintf("error posting the request to the RPC server: %s", err))
}
fmt.Println(responseString)
}
func printErrorAndExit(message string) {

View File

@@ -2,21 +2,16 @@ package main
import (
nativeerrors "errors"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/domain/mining"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"math/rand"
"sync"
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensusserialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/pkg/errors"
)
@@ -36,7 +31,7 @@ func mineLoop(client *minerClient, numberOfBlocks uint64, blockDelay uint64, min
spawn("mineLoop-internalLoop", func() {
wg := sync.WaitGroup{}
for i := uint64(0); numberOfBlocks == 0 || i < numberOfBlocks; i++ {
foundBlock := make(chan *externalapi.DomainBlock)
foundBlock := make(chan *util.Block)
mineNextBlock(client, miningAddr, foundBlock, mineWhenNotSynced, templateStopChan, errChan)
block := <-foundBlock
templateStopChan <- struct{}{}
@@ -82,7 +77,7 @@ func logHashRate() {
})
}
func mineNextBlock(client *minerClient, miningAddr util.Address, foundBlock chan *externalapi.DomainBlock, mineWhenNotSynced bool,
func mineNextBlock(client *minerClient, miningAddr util.Address, foundBlock chan *util.Block, mineWhenNotSynced bool,
templateStopChan chan struct{}, errChan chan error) {
newTemplateChan := make(chan *appmessage.GetBlockTemplateResponseMessage)
@@ -94,29 +89,29 @@ func mineNextBlock(client *minerClient, miningAddr util.Address, foundBlock chan
})
}
func handleFoundBlock(client *minerClient, block *externalapi.DomainBlock) error {
blockHash := consensusserialization.BlockHash(block)
log.Infof("Found block %s with parents %s. Submitting to %s", blockHash, block.Header.ParentHashes, client.Address())
func handleFoundBlock(client *minerClient, block *util.Block) error {
log.Infof("Found block %s with parents %s. Submitting to %s", block.Hash(), block.MsgBlock().Header.ParentHashes, client.Address())
err := client.SubmitBlock(block)
if err != nil {
return errors.Errorf("Error submitting block %s to %s: %s", blockHash, client.Address(), err)
return errors.Errorf("Error submitting block %s to %s: %s", block.Hash(), client.Address(), err)
}
return nil
}
func solveBlock(block *externalapi.DomainBlock, stopChan chan struct{}, foundBlock chan *externalapi.DomainBlock) {
targetDifficulty := util.CompactToBig(block.Header.Bits)
func solveBlock(block *util.Block, stopChan chan struct{}, foundBlock chan *util.Block) {
msgBlock := block.MsgBlock()
targetDifficulty := util.CompactToBig(msgBlock.Header.Bits)
initialNonce := random.Uint64()
for i := initialNonce; i != initialNonce-1; i++ {
select {
case <-stopChan:
return
default:
block.Header.Nonce = i
hash := consensusserialization.BlockHash(block)
msgBlock.Header.Nonce = i
hash := msgBlock.BlockHash()
atomic.AddUint64(&hashesTried, 1)
if hashes.ToBig(hash).Cmp(targetDifficulty) <= 0 {
if daghash.HashToBig(hash).Cmp(targetDifficulty) <= 0 {
foundBlock <- block
return
}
@@ -127,32 +122,46 @@ func solveBlock(block *externalapi.DomainBlock, stopChan chan struct{}, foundBlo
func templatesLoop(client *minerClient, miningAddr util.Address,
newTemplateChan chan *appmessage.GetBlockTemplateResponseMessage, errChan chan error, stopChan chan struct{}) {
getBlockTemplate := func() {
template, err := client.GetBlockTemplate(miningAddr.String())
longPollID := ""
getBlockTemplateLongPoll := func() {
if longPollID != "" {
log.Infof("Requesting template with longPollID '%s' from %s", longPollID, client.Address())
} else {
log.Infof("Requesting template without longPollID from %s", client.Address())
}
template, err := client.GetBlockTemplate(miningAddr.String(), longPollID)
if nativeerrors.Is(err, router.ErrTimeout) {
log.Infof("Got timeout while requesting block template from %s", client.Address())
log.Infof("Got timeout while requesting template '%s' from %s", longPollID, client.Address())
return
} else if err != nil {
errChan <- errors.Errorf("Error getting block template from %s: %s", client.Address(), err)
return
}
newTemplateChan <- template
if !template.IsConnected {
errChan <- errors.Errorf("Kaspad is not connected for %s", client.Address())
return
}
if template.LongPollID != longPollID {
log.Infof("Got new long poll template: %s", template.LongPollID)
longPollID = template.LongPollID
newTemplateChan <- template
}
}
getBlockTemplate()
getBlockTemplateLongPoll()
for {
select {
case <-stopChan:
close(newTemplateChan)
return
case <-client.blockAddedNotificationChan:
getBlockTemplate()
getBlockTemplateLongPoll()
case <-time.Tick(500 * time.Millisecond):
getBlockTemplate()
getBlockTemplateLongPoll()
}
}
}
func solveLoop(newTemplateChan chan *appmessage.GetBlockTemplateResponseMessage, foundBlock chan *externalapi.DomainBlock,
func solveLoop(newTemplateChan chan *appmessage.GetBlockTemplateResponseMessage, foundBlock chan *util.Block,
mineWhenNotSynced bool, errChan chan error) {
var stopOldTemplateSolving chan struct{}
@@ -161,8 +170,20 @@ func solveLoop(newTemplateChan chan *appmessage.GetBlockTemplateResponseMessage,
close(stopOldTemplateSolving)
}
if !template.IsSynced {
if !mineWhenNotSynced {
errChan <- errors.Errorf("got template with isSynced=false")
return
}
log.Warnf("Got template with isSynced=false")
}
stopOldTemplateSolving = make(chan struct{})
block := appmessage.MsgBlockToDomainBlock(template.MsgBlock)
block, err := mining.ConvertGetBlockTemplateResultToBlock(template)
if err != nil {
errChan <- errors.Errorf("Error parsing block: %s", err)
return
}
spawn("solveBlock", func() {
solveBlock(block, stopOldTemplateSolving, foundBlock)

36
cmd/txsigner/config.go Normal file
View File

@@ -0,0 +1,36 @@
package main
import (
"github.com/jessevdk/go-flags"
"github.com/kaspanet/kaspad/infrastructure/config"
)
var activeConfig *ConfigFlags
// ActiveConfig returns the active configuration struct
func ActiveConfig() *ConfigFlags {
return activeConfig
}
// ConfigFlags holds the configurations set by the command line argument
type ConfigFlags struct {
Transaction string `long:"transaction" short:"t" description:"Unsigned transaction in HEX format" required:"true"`
PrivateKey string `long:"private-key" short:"p" description:"Private key" required:"true"`
config.NetworkFlags
}
func parseCommandLine() (*ConfigFlags, error) {
activeConfig = &ConfigFlags{}
parser := flags.NewParser(activeConfig, flags.PrintErrors|flags.HelpFlag)
_, err := parser.Parse()
if err != nil {
return nil, err
}
err = activeConfig.ResolveNetwork(parser)
if err != nil {
return nil, err
}
return activeConfig, nil
}

Some files were not shown because too many files have changed in this diff Show More