From 7a61c637b0641002abc30ecf2e7e5729ab3cab20 Mon Sep 17 00:00:00 2001 From: Aleoami Date: Thu, 21 Jul 2022 13:55:12 +0300 Subject: [PATCH 1/2] Add RPC timeout parameter to wallet daemon (#2104) Co-authored-by: Ori Newman --- cmd/kaspawallet/config.go | 4 ++-- .../daemon/server/create_unsigned_transaction.go | 16 ++++++++++------ cmd/kaspawallet/daemon/server/rpc.go | 15 +++++++++++++-- cmd/kaspawallet/daemon/server/server.go | 4 ++-- cmd/kaspawallet/start_daemon.go | 2 +- infrastructure/network/rpcclient/rpcclient.go | 7 ++++--- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/cmd/kaspawallet/config.go b/cmd/kaspawallet/config.go index 07df82465..06daff64a 100644 --- a/cmd/kaspawallet/config.go +++ b/cmd/kaspawallet/config.go @@ -111,7 +111,8 @@ type startDaemonConfig struct { KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"` Password string `long:"password" short:"p" description:"Wallet password"` RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"` - Listen string `short:"l" long:"listen" description:"Address to listen on (default: 0.0.0.0:8082)"` + Listen string `long:"listen" short:"l" description:"Address to listen on (default: 0.0.0.0:8082)"` + Timeout uint32 `long:"wait-timeout" short:"w" description:"Waiting timeout for RPC calls, seconds (default: 30 s)"` Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"` config.NetworkFlags } @@ -181,7 +182,6 @@ func parseCommandLine() (subCommand string, config interface{}) { parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf) _, err := parser.Parse() - if err != nil { var flagsErr *flags.Error if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp { diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 265780c4a..dca075570 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -3,20 +3,22 @@ package server import ( "context" "fmt" + "time" + "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/util" "github.com/pkg/errors" "golang.org/x/exp/slices" - "time" ) // TODO: Implement a better fee estimation mechanism const feePerInput = 10000 func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) ( - *pb.CreateUnsignedTransactionsResponse, error) { + *pb.CreateUnsignedTransactionsResponse, error, +) { s.lock.Lock() defer s.lock.Unlock() @@ -33,12 +35,14 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, fromA return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport()) } - err := s.refreshUTXOs() + // make sure address string is correct before proceeding to a + // potentially long UTXO refreshment operation + toAddress, err := util.DecodeAddress(address, s.params.Prefix) if err != nil { return nil, err } - toAddress, err := util.DecodeAddress(address, s.params.Prefix) + err = s.refreshUTXOs() if err != nil { return nil, err } @@ -87,8 +91,8 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, fromA } func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64, fromAddresses []*walletAddress) ( - selectedUTXOs []*libkaspawallet.UTXO, changeSompi uint64, err error) { - + selectedUTXOs []*libkaspawallet.UTXO, changeSompi uint64, err error, +) { selectedUTXOs = []*libkaspawallet.UTXO{} totalValue := uint64(0) diff --git a/cmd/kaspawallet/daemon/server/rpc.go b/cmd/kaspawallet/daemon/server/rpc.go index b37e8f66a..a80fe4452 100644 --- a/cmd/kaspawallet/daemon/server/rpc.go +++ b/cmd/kaspawallet/daemon/server/rpc.go @@ -1,15 +1,26 @@ package server import ( + "time" + "github.com/kaspanet/kaspad/domain/dagconfig" "github.com/kaspanet/kaspad/infrastructure/network/rpcclient" ) -func connectToRPC(params *dagconfig.Params, rpcServer string) (*rpcclient.RPCClient, error) { +func connectToRPC(params *dagconfig.Params, rpcServer string, timeout uint32) (*rpcclient.RPCClient, error) { rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer) if err != nil { return nil, err } - return rpcclient.NewRPCClient(rpcAddress) + rpcClient, err := rpcclient.NewRPCClient(rpcAddress) + if err != nil { + return nil, err + } + + if timeout != 0 { + rpcClient.SetTimeout(time.Duration(timeout) * time.Second) + } + + return rpcClient, err } diff --git a/cmd/kaspawallet/daemon/server/server.go b/cmd/kaspawallet/daemon/server/server.go index d66ff8d65..afa140319 100644 --- a/cmd/kaspawallet/daemon/server/server.go +++ b/cmd/kaspawallet/daemon/server/server.go @@ -45,7 +45,7 @@ type server struct { } // Start starts the kaspawalletd server -func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string) error { +func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32) error { initLog(defaultLogFile, defaultErrLogFile) defer panics.HandlePanic(log, "MAIN", nil) @@ -62,7 +62,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri log.Infof("Listening to TCP on %s", listen) log.Infof("Connecting to a node at %s...", rpcServer) - rpcClient, err := connectToRPC(params, rpcServer) + rpcClient, err := connectToRPC(params, rpcServer, timeout) if err != nil { return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer)) } diff --git a/cmd/kaspawallet/start_daemon.go b/cmd/kaspawallet/start_daemon.go index bf1bbdfaf..db819aa2f 100644 --- a/cmd/kaspawallet/start_daemon.go +++ b/cmd/kaspawallet/start_daemon.go @@ -3,5 +3,5 @@ package main import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server" func startDaemon(conf *startDaemonConfig) error { - return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile) + return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout) } diff --git a/infrastructure/network/rpcclient/rpcclient.go b/infrastructure/network/rpcclient/rpcclient.go index d047fd8e6..7256f6c82 100644 --- a/infrastructure/network/rpcclient/rpcclient.go +++ b/infrastructure/network/rpcclient/rpcclient.go @@ -1,6 +1,9 @@ package rpcclient import ( + "sync/atomic" + "time" + "github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/infrastructure/logger" routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" @@ -8,8 +11,6 @@ import ( "github.com/kaspanet/kaspad/util/panics" "github.com/kaspanet/kaspad/version" "github.com/pkg/errors" - "sync/atomic" - "time" ) const defaultTimeout = 30 * time.Second @@ -28,7 +29,7 @@ type RPCClient struct { timeout time.Duration } -// NewRPCClient creates a new RPC client +// NewRPCClient сreates a new RPC client with a default call timeout value func NewRPCClient(rpcAddress string) (*RPCClient, error) { rpcClient := &RPCClient{ rpcAddress: rpcAddress, From eb693c4a862b0a002bebbafc0704745a21fe95c1 Mon Sep 17 00:00:00 2001 From: D-Stacks <78099568+D-Stacks@users.noreply.github.com> Date: Tue, 26 Jul 2022 22:06:08 +0200 Subject: [PATCH 2/2] Mempool: Retrive stable state of the mempool. optimze get mempool entries by addresses (#2111) * fix mempool accessing, rewrite get_mempool_entries_by_addresses * fix counter, add verbose * fmt * addresses as string * Define error in case utxoEntry is missing. * fix error variable to string * stop tests from failing (see in code comment) * access both pools in the same state via parameters * get rid of todo message * fmt - very important! * perf: scriptpublickey in mempool, no txscript. * address reveiw * fmt fix * mixed up isorphan bool, pass tests now * do map preallocation in mempoolbyaddresses * no proallocation for orphanpool sending. Co-authored-by: Ori Newman --- .../handle_relayed_transactions.go | 2 +- .../handle_requested_transactions.go | 3 +- app/rpc/rpccontext/notificationmanager.go | 3 +- app/rpc/rpccontext/utxos_by_addresses.go | 18 +- app/rpc/rpchandlers/get_info.go | 2 +- app/rpc/rpchandlers/get_mempool_entries.go | 70 +++---- .../get_mempool_entries_by_addresses.go | 185 ++++++++---------- app/rpc/rpchandlers/get_mempool_entry.go | 11 +- .../model/externalapi/transaction.go | 18 ++ domain/miningmanager/mempool/mempool.go | 79 ++++++-- .../miningmanager/mempool/mempool_utxo_set.go | 2 +- .../miningmanager/mempool/model/map_types.go | 3 + domain/miningmanager/mempool/orphan_pool.go | 40 +++- .../revalidate_high_priority_transactions.go | 3 +- .../mempool/transactions_pool.go | 38 +++- .../validate_and_insert_transaction.go | 2 +- domain/miningmanager/miningmanager.go | 54 +++-- domain/miningmanager/miningmanager_test.go | 36 ++-- .../miningmanager/model/interface_mempool.go | 30 ++- domain/utxoindex/model.go | 20 -- domain/utxoindex/store.go | 8 +- 21 files changed, 350 insertions(+), 277 deletions(-) diff --git a/app/protocol/flows/v5/transactionrelay/handle_relayed_transactions.go b/app/protocol/flows/v5/transactionrelay/handle_relayed_transactions.go index 21702986f..401ac46fd 100644 --- a/app/protocol/flows/v5/transactionrelay/handle_relayed_transactions.go +++ b/app/protocol/flows/v5/transactionrelay/handle_relayed_transactions.go @@ -102,7 +102,7 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions( func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool { // Ask the transaction memory pool if the transaction is known // to it in any form (main pool or orphan). - if _, ok := flow.Domain().MiningManager().GetTransaction(txID); ok { + if _, _, ok := flow.Domain().MiningManager().GetTransaction(txID, true, true); ok { return true } diff --git a/app/protocol/flows/v5/transactionrelay/handle_requested_transactions.go b/app/protocol/flows/v5/transactionrelay/handle_requested_transactions.go index 3dd8b294b..00fd23ae2 100644 --- a/app/protocol/flows/v5/transactionrelay/handle_requested_transactions.go +++ b/app/protocol/flows/v5/transactionrelay/handle_requested_transactions.go @@ -30,7 +30,7 @@ func (flow *handleRequestedTransactionsFlow) start() error { } for _, transactionID := range msgRequestTransactions.IDs { - tx, ok := flow.Domain().MiningManager().GetTransaction(transactionID) + tx, _, ok := flow.Domain().MiningManager().GetTransaction(transactionID, true, false) if !ok { msgTransactionNotFound := appmessage.NewMsgTransactionNotFound(transactionID) @@ -40,7 +40,6 @@ func (flow *handleRequestedTransactionsFlow) start() error { } continue } - err := flow.outgoingRoute.Enqueue(appmessage.DomainTransactionToMsgTx(tx)) if err != nil { return err diff --git a/app/rpc/rpccontext/notificationmanager.go b/app/rpc/rpccontext/notificationmanager.go index e9b3ef98c..0ba132f17 100644 --- a/app/rpc/rpccontext/notificationmanager.go +++ b/app/rpc/rpccontext/notificationmanager.go @@ -5,6 +5,7 @@ import ( "github.com/kaspanet/kaspad/domain/dagconfig" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" "github.com/kaspanet/kaspad/app/appmessage" @@ -421,7 +422,7 @@ func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification( } func (nl *NotificationListener) scriptPubKeyStringToAddressString(scriptPublicKeyString utxoindex.ScriptPublicKeyString) (string, error) { - scriptPubKey := utxoindex.ConvertStringToScriptPublicKey(scriptPublicKeyString) + scriptPubKey := externalapi.NewScriptPublicKeyFromString(string(scriptPublicKeyString)) // ignore error because it is often returned when the script is of unknown type scriptType, address, err := txscript.ExtractScriptPubKeyAddress(scriptPubKey, nl.params) diff --git a/app/rpc/rpccontext/utxos_by_addresses.go b/app/rpc/rpccontext/utxos_by_addresses.go index d9ca04aed..00251589c 100644 --- a/app/rpc/rpccontext/utxos_by_addresses.go +++ b/app/rpc/rpccontext/utxos_by_addresses.go @@ -32,22 +32,6 @@ func ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(address string, pair return utxosByAddressesEntries } -// convertUTXOOutpointsToUTXOsByAddressesEntries converts -// UTXOOutpoints to a slice of UTXOsByAddressesEntry -func convertUTXOOutpointsToUTXOsByAddressesEntries(address string, outpoints utxoindex.UTXOOutpoints) []*appmessage.UTXOsByAddressesEntry { - utxosByAddressesEntries := make([]*appmessage.UTXOsByAddressesEntry, 0, len(outpoints)) - for outpoint := range outpoints { - utxosByAddressesEntries = append(utxosByAddressesEntries, &appmessage.UTXOsByAddressesEntry{ - Address: address, - Outpoint: &appmessage.RPCOutpoint{ - TransactionID: outpoint.TransactionID.String(), - Index: outpoint.Index, - }, - }) - } - return utxosByAddressesEntries -} - // ConvertAddressStringsToUTXOsChangedNotificationAddresses converts address strings // to UTXOsChangedNotificationAddresses func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses( @@ -63,7 +47,7 @@ func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses( if err != nil { return nil, errors.Errorf("Could not create a scriptPublicKey for address '%s': %s", addressString, err) } - scriptPublicKeyString := utxoindex.ConvertScriptPublicKeyToString(scriptPublicKey) + scriptPublicKeyString := utxoindex.ScriptPublicKeyString(scriptPublicKey.String()) addresses[i] = &UTXOsChangedNotificationAddress{ Address: addressString, ScriptPublicKeyString: scriptPublicKeyString, diff --git a/app/rpc/rpchandlers/get_info.go b/app/rpc/rpchandlers/get_info.go index 2b72d7428..308e47615 100644 --- a/app/rpc/rpchandlers/get_info.go +++ b/app/rpc/rpchandlers/get_info.go @@ -16,7 +16,7 @@ func HandleGetInfo(context *rpccontext.Context, _ *router.Router, _ appmessage.M response := appmessage.NewGetInfoResponseMessage( context.NetAdapter.ID().String(), - uint64(context.Domain.MiningManager().TransactionCount()), + uint64(context.Domain.MiningManager().TransactionCount(true, false)), version.Version(), context.Config.UTXOIndex, context.ProtocolManager.Context().HasPeers() && isNearlySynced, diff --git a/app/rpc/rpchandlers/get_mempool_entries.go b/app/rpc/rpchandlers/get_mempool_entries.go index e37d84922..4f5ec8c81 100644 --- a/app/rpc/rpchandlers/get_mempool_entries.go +++ b/app/rpc/rpchandlers/get_mempool_entries.go @@ -12,58 +12,36 @@ func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, requ entries := make([]*appmessage.MempoolEntry, 0) + transactionPoolTransactions, orphanPoolTransactions := context.Domain.MiningManager().AllTransactions(!getMempoolEntriesRequest.FilterTransactionPool, getMempoolEntriesRequest.IncludeOrphanPool) + if !getMempoolEntriesRequest.FilterTransactionPool { - transactionPoolEntries, err := getTransactionPoolMempoolEntries(context) - if err != nil { - return nil, err + for _, transaction := range transactionPoolTransactions { + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) + if err != nil { + return nil, err + } + entries = append(entries, &appmessage.MempoolEntry{ + Fee: transaction.Fee, + Transaction: rpcTransaction, + IsOrphan: false, + }) } - - entries = append(entries, transactionPoolEntries...) } - if getMempoolEntriesRequest.IncludeOrphanPool { - orphanPoolEntries, err := getOrphanPoolMempoolEntries(context) - if err != nil { - return nil, err + for _, transaction := range orphanPoolTransactions { + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) + if err != nil { + return nil, err + } + entries = append(entries, &appmessage.MempoolEntry{ + Fee: transaction.Fee, + Transaction: rpcTransaction, + IsOrphan: true, + }) } - entries = append(entries, orphanPoolEntries...) } return appmessage.NewGetMempoolEntriesResponseMessage(entries), nil } - -func getTransactionPoolMempoolEntries(context *rpccontext.Context) ([]*appmessage.MempoolEntry, error) { - transactions := context.Domain.MiningManager().AllTransactions() - entries := make([]*appmessage.MempoolEntry, 0, len(transactions)) - for _, transaction := range transactions { - rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) - err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) - if err != nil { - return nil, err - } - entries = append(entries, &appmessage.MempoolEntry{ - Fee: transaction.Fee, - Transaction: rpcTransaction, - IsOrphan: false, - }) - } - return entries, nil -} - -func getOrphanPoolMempoolEntries(context *rpccontext.Context) ([]*appmessage.MempoolEntry, error) { - orphanTransactions := context.Domain.MiningManager().AllOrphanTransactions() - entries := make([]*appmessage.MempoolEntry, 0, len(orphanTransactions)) - for _, orphanTransaction := range orphanTransactions { - rpcTransaction := appmessage.DomainTransactionToRPCTransaction(orphanTransaction) - err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) - if err != nil { - return nil, err - } - entries = append(entries, &appmessage.MempoolEntry{ - Fee: orphanTransaction.Fee, - Transaction: rpcTransaction, - IsOrphan: true, - }) - } - return entries, nil -} diff --git a/app/rpc/rpchandlers/get_mempool_entries_by_addresses.go b/app/rpc/rpchandlers/get_mempool_entries_by_addresses.go index 04e8ac30b..0e9bf65f3 100644 --- a/app/rpc/rpchandlers/get_mempool_entries_by_addresses.go +++ b/app/rpc/rpchandlers/get_mempool_entries_by_addresses.go @@ -1,14 +1,10 @@ package rpchandlers import ( - "errors" - "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/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" + "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" "github.com/kaspanet/kaspad/util" ) @@ -20,132 +16,107 @@ func HandleGetMempoolEntriesByAddresses(context *rpccontext.Context, _ *router.R mempoolEntriesByAddresses := make([]*appmessage.MempoolEntryByAddress, 0) - if !getMempoolEntriesByAddressesRequest.FilterTransactionPool { - transactionPoolTransactions := context.Domain.MiningManager().AllTransactions() - transactionPoolEntriesByAddresses, err := extractMempoolEntriesByAddressesFromTransactions( - context, - getMempoolEntriesByAddressesRequest.Addresses, - transactionPoolTransactions, - false, - ) - if err != nil { - rpcError := &appmessage.RPCError{} - if !errors.As(err, &rpcError) { - return nil, err - } - errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{} - errorMessage.Error = rpcError - return errorMessage, nil - } - mempoolEntriesByAddresses = append(mempoolEntriesByAddresses, transactionPoolEntriesByAddresses...) + sendingInTransactionPool, receivingInTransactionPool, sendingInOrphanPool, receivingInOrphanPool, err := context.Domain.MiningManager().GetTransactionsByAddresses(!getMempoolEntriesByAddressesRequest.FilterTransactionPool, getMempoolEntriesByAddressesRequest.IncludeOrphanPool) + if err != nil { + return nil, err } - if getMempoolEntriesByAddressesRequest.IncludeOrphanPool { + for _, addressString := range getMempoolEntriesByAddressesRequest.Addresses { - orphanPoolTransactions := context.Domain.MiningManager().AllOrphanTransactions() - orphanPoolEntriesByAddress, err := extractMempoolEntriesByAddressesFromTransactions( - context, - getMempoolEntriesByAddressesRequest.Addresses, - orphanPoolTransactions, - true, - ) + address, err := util.DecodeAddress(addressString, context.Config.NetParams().Prefix) if err != nil { - rpcError := &appmessage.RPCError{} - if !errors.As(err, &rpcError) { - return nil, err - } - errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{} - errorMessage.Error = rpcError + errorMessage := &appmessage.GetMempoolEntriesByAddressesResponseMessage{} + errorMessage.Error = appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err) return errorMessage, nil } - mempoolEntriesByAddresses = append(mempoolEntriesByAddresses, orphanPoolEntriesByAddress...) - } - - return appmessage.NewGetMempoolEntriesByAddressesResponseMessage(mempoolEntriesByAddresses), nil -} - -//TO DO: optimize extractMempoolEntriesByAddressesFromTransactions -func extractMempoolEntriesByAddressesFromTransactions(context *rpccontext.Context, addresses []string, transactions []*externalapi.DomainTransaction, areOrphans bool) ([]*appmessage.MempoolEntryByAddress, error) { - mempoolEntriesByAddresses := make([]*appmessage.MempoolEntryByAddress, 0) - for _, addressString := range addresses { - _, err := util.DecodeAddress(addressString, context.Config.ActiveNetParams.Prefix) - if err != nil { - return nil, appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err) - } - sending := make([]*appmessage.MempoolEntry, 0) receiving := make([]*appmessage.MempoolEntry, 0) - for _, transaction := range transactions { + scriptPublicKey, err := txscript.PayToAddrScript(address) + if err != nil { + errorMessage := &appmessage.GetMempoolEntriesByAddressesResponseMessage{} + errorMessage.Error = appmessage.RPCErrorf("Could not extract scriptPublicKey from address '%s': %s", addressString, err) + return errorMessage, nil + } - for i, input := range transaction.Inputs { - if input.UTXOEntry == nil { - if !areOrphans { // Orphans can legitimately have `input.UTXOEntry == nil` - // TODO: Fix the underlying cause of the bug for non-orphan entries - log.Debugf( - "Couldn't find UTXO entry for input %d in mempool transaction %s. This is a bug and should be fixed.", - i, consensushashing.TransactionID(transaction)) - } - continue - } + if !getMempoolEntriesByAddressesRequest.FilterTransactionPool { - _, transactionSendingAddress, err := txscript.ExtractScriptPubKeyAddress( - input.UTXOEntry.ScriptPublicKey(), - context.Config.ActiveNetParams) + if transaction, found := sendingInTransactionPool[scriptPublicKey.String()]; found { + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) if err != nil { return nil, err } - if addressString == transactionSendingAddress.String() { - rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) - sending = append( - sending, - &appmessage.MempoolEntry{ - Fee: transaction.Fee, - Transaction: rpcTransaction, - IsOrphan: areOrphans, - }, - ) - break //one input is enough - } - } - for _, output := range transaction.Outputs { - _, transactionReceivingAddress, err := txscript.ExtractScriptPubKeyAddress( - output.ScriptPublicKey, - context.Config.ActiveNetParams, + sending = append(sending, &appmessage.MempoolEntry{ + Fee: transaction.Fee, + Transaction: rpcTransaction, + IsOrphan: false, + }, ) + } + + if transaction, found := receivingInTransactionPool[scriptPublicKey.String()]; found { + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) if err != nil { return nil, err } - if addressString == transactionReceivingAddress.String() { - rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) - receiving = append( - receiving, - &appmessage.MempoolEntry{ - Fee: transaction.Fee, - Transaction: rpcTransaction, - IsOrphan: areOrphans, - }, - ) - break //one output is enough - } - } - //Only append mempoolEntriesByAddress, if at least 1 mempoolEntry for the address is found. - //This mimics the behaviour of GetUtxosByAddresses RPC call. - if len(sending) > 0 || len(receiving) > 0 { - mempoolEntriesByAddresses = append( - mempoolEntriesByAddresses, - &appmessage.MempoolEntryByAddress{ - Address: addressString, - Sending: sending, - Receiving: receiving, - }, + receiving = append(receiving, &appmessage.MempoolEntry{ + Fee: transaction.Fee, + Transaction: rpcTransaction, + IsOrphan: false, + }, ) } } + if getMempoolEntriesByAddressesRequest.IncludeOrphanPool { + if transaction, found := sendingInOrphanPool[scriptPublicKey.String()]; found { + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) + if err != nil { + return nil, err + } + + sending = append(sending, &appmessage.MempoolEntry{ + Fee: transaction.Fee, + Transaction: rpcTransaction, + IsOrphan: true, + }, + ) + } + + if transaction, found := receivingInOrphanPool[scriptPublicKey.String()]; found { + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) + if err != nil { + return nil, err + } + + receiving = append(receiving, &appmessage.MempoolEntry{ + Fee: transaction.Fee, + Transaction: rpcTransaction, + IsOrphan: true, + }, + ) + } + + } + + if len(sending) > 0 || len(receiving) > 0 { + mempoolEntriesByAddresses = append( + mempoolEntriesByAddresses, + &appmessage.MempoolEntryByAddress{ + Address: address.String(), + Sending: sending, + Receiving: receiving, + }, + ) + } } - return mempoolEntriesByAddresses, nil + + return appmessage.NewGetMempoolEntriesByAddressesResponseMessage(mempoolEntriesByAddresses), nil } diff --git a/app/rpc/rpchandlers/get_mempool_entry.go b/app/rpc/rpchandlers/get_mempool_entry.go index 5bc8938ef..e2200ed5c 100644 --- a/app/rpc/rpchandlers/get_mempool_entry.go +++ b/app/rpc/rpchandlers/get_mempool_entry.go @@ -24,14 +24,7 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques return errorMessage, nil } - if !getMempoolEntryRequest.FilterTransactionPool { - transaction, found = context.Domain.MiningManager().GetTransaction(transactionID) - } - - if getMempoolEntryRequest.IncludeOrphanPool && !found { - transaction, found = context.Domain.MiningManager().GetOrphanTransaction(transactionID) - isOrphan = true - } + mempoolTransaction, isOrphan, found := context.Domain.MiningManager().GetTransaction(transactionID, !getMempoolEntryRequest.FilterTransactionPool, getMempoolEntryRequest.IncludeOrphanPool) if !found { errorMessage := &appmessage.GetMempoolEntryResponseMessage{} @@ -39,7 +32,7 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques return errorMessage, nil } - rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) + rpcTransaction := appmessage.DomainTransactionToRPCTransaction(mempoolTransaction) err = context.PopulateTransactionWithVerboseData(rpcTransaction, nil) if err != nil { return nil, err diff --git a/domain/consensus/model/externalapi/transaction.go b/domain/consensus/model/externalapi/transaction.go index a64d3181a..b12baec4a 100644 --- a/domain/consensus/model/externalapi/transaction.go +++ b/domain/consensus/model/externalapi/transaction.go @@ -2,6 +2,7 @@ package externalapi import ( "bytes" + "encoding/binary" "fmt" "github.com/pkg/errors" @@ -242,6 +243,23 @@ func (spk *ScriptPublicKey) Equal(other *ScriptPublicKey) bool { return bytes.Equal(spk.Script, other.Script) } +// String stringifies a ScriptPublicKey. +func (spk *ScriptPublicKey) String() string { + var versionBytes = make([]byte, 2) // uint16 + binary.LittleEndian.PutUint16(versionBytes, spk.Version) + versionString := string(versionBytes) + scriptString := string(spk.Script) + return versionString + scriptString +} + +// NewScriptPublicKeyFromString converts the given string to a scriptPublicKey +func NewScriptPublicKeyFromString(ScriptPublicKeyString string) *ScriptPublicKey { + bytes := []byte(ScriptPublicKeyString) + version := binary.LittleEndian.Uint16(bytes[:2]) + script := bytes[2:] + return &ScriptPublicKey{Script: script, Version: version} +} + // DomainTransactionOutput represents a Kaspad transaction output type DomainTransactionOutput struct { Value uint64 diff --git a/domain/miningmanager/mempool/mempool.go b/domain/miningmanager/mempool/mempool.go index c6eb41fe4..f4d6ef7bf 100644 --- a/domain/miningmanager/mempool/mempool.go +++ b/domain/miningmanager/mempool/mempool.go @@ -1,9 +1,10 @@ package mempool import ( - "github.com/kaspanet/kaspad/domain/consensusreference" "sync" + "github.com/kaspanet/kaspad/domain/consensusreference" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model" ) @@ -42,39 +43,89 @@ func (mp *mempool) ValidateAndInsertTransaction(transaction *externalapi.DomainT return mp.validateAndInsertTransaction(transaction, isHighPriority, allowOrphan) } -func (mp *mempool) GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { +func (mp *mempool) GetTransaction(transactionID *externalapi.DomainTransactionID, + includeTransactionPool bool, + includeOrphanPool bool) ( + transaction *externalapi.DomainTransaction, + isOrphan bool, + found bool) { + mp.mtx.RLock() defer mp.mtx.RUnlock() - return mp.transactionsPool.getTransaction(transactionID) + var transactionfound bool + isOrphan = false + + if includeTransactionPool { + transaction, transactionfound = mp.transactionsPool.getTransaction(transactionID, true) + isOrphan = false + } + if !transactionfound && includeOrphanPool { + transaction, transactionfound = mp.orphansPool.getOrphanTransaction(transactionID) + isOrphan = true + } + + return transaction, isOrphan, transactionfound } -func (mp *mempool) AllTransactions() []*externalapi.DomainTransaction { +func (mp *mempool) GetTransactionsByAddresses(includeTransactionPool bool, includeOrphanPool bool) ( + sendingInTransactionPool map[string]*externalapi.DomainTransaction, + receivingInTransactionPool map[string]*externalapi.DomainTransaction, + sendingInOrphanPool map[string]*externalapi.DomainTransaction, + receivingInOrphanPool map[string]*externalapi.DomainTransaction, + err error) { mp.mtx.RLock() defer mp.mtx.RUnlock() - return mp.transactionsPool.getAllTransactions() + if includeTransactionPool { + sendingInTransactionPool, receivingInTransactionPool, err = mp.transactionsPool.getTransactionsByAddresses() + if err != nil { + return nil, nil, nil, nil, err + } + } + + if includeOrphanPool { + sendingInTransactionPool, receivingInOrphanPool, err = mp.orphansPool.getOrphanTransactionsByAddresses() + if err != nil { + return nil, nil, nil, nil, err + } + } + + return sendingInTransactionPool, receivingInTransactionPool, sendingInTransactionPool, receivingInOrphanPool, nil } -func (mp *mempool) GetOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { +func (mp *mempool) AllTransactions(includeTransactionPool bool, includeOrphanPool bool) ( + transactionPoolTransactions []*externalapi.DomainTransaction, + orphanPoolTransactions []*externalapi.DomainTransaction) { + mp.mtx.RLock() defer mp.mtx.RUnlock() - return mp.orphansPool.getOrphanTransaction(transactionID) + if includeTransactionPool { + transactionPoolTransactions = mp.transactionsPool.getAllTransactions() + } + + if includeOrphanPool { + orphanPoolTransactions = mp.orphansPool.getAllOrphanTransactions() + } + + return transactionPoolTransactions, orphanPoolTransactions } -func (mp *mempool) AllOrphanTransactions() []*externalapi.DomainTransaction { +func (mp *mempool) TransactionCount(includeTransactionPool bool, includeOrphanPool bool) int { mp.mtx.RLock() defer mp.mtx.RUnlock() - return mp.orphansPool.getAllOrphanTransactions() -} + transactionCount := 0 -func (mp *mempool) TransactionCount() int { - mp.mtx.RLock() - defer mp.mtx.RUnlock() + if includeOrphanPool { + transactionCount += mp.orphansPool.orphanTransactionCount() + } + if includeTransactionPool { + transactionCount += mp.transactionsPool.transactionCount() + } - return mp.transactionsPool.transactionCount() + return transactionCount } func (mp *mempool) HandleNewBlockTransactions(transactions []*externalapi.DomainTransaction) ( diff --git a/domain/miningmanager/mempool/mempool_utxo_set.go b/domain/miningmanager/mempool/mempool_utxo_set.go index aa20d05d5..bbe555258 100644 --- a/domain/miningmanager/mempool/mempool_utxo_set.go +++ b/domain/miningmanager/mempool/mempool_utxo_set.go @@ -51,7 +51,7 @@ func (mpus *mempoolUTXOSet) addTransaction(transaction *model.MempoolTransaction func (mpus *mempoolUTXOSet) removeTransaction(transaction *model.MempoolTransaction) { for _, input := range transaction.Transaction().Inputs { // If the transaction creating the output spent by this input is in the mempool - restore it's UTXO - if _, ok := mpus.mempool.transactionsPool.getTransaction(&input.PreviousOutpoint.TransactionID); ok { + if _, ok := mpus.mempool.transactionsPool.getTransaction(&input.PreviousOutpoint.TransactionID, false); ok { mpus.poolUnspentOutputs[input.PreviousOutpoint] = input.UTXOEntry } delete(mpus.transactionByPreviousOutpoint, input.PreviousOutpoint) diff --git a/domain/miningmanager/mempool/model/map_types.go b/domain/miningmanager/mempool/model/map_types.go index d6ca685de..ca364287b 100644 --- a/domain/miningmanager/mempool/model/map_types.go +++ b/domain/miningmanager/mempool/model/map_types.go @@ -15,3 +15,6 @@ type OutpointToUTXOEntryMap map[externalapi.DomainOutpoint]externalapi.UTXOEntry // OutpointToTransactionMap maps an outpoint to a MempoolTransaction type OutpointToTransactionMap map[externalapi.DomainOutpoint]*MempoolTransaction + +// ScriptPublicKeyStringToDomainTransaction maps an outpoint to a DomainTransaction +type ScriptPublicKeyStringToDomainTransaction map[string]*externalapi.DomainTransaction diff --git a/domain/miningmanager/mempool/orphan_pool.go b/domain/miningmanager/mempool/orphan_pool.go index d053ecd28..59767d501 100644 --- a/domain/miningmanager/mempool/orphan_pool.go +++ b/domain/miningmanager/mempool/orphan_pool.go @@ -169,7 +169,7 @@ func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransactio } return nil, err } - acceptedOrphans = append(acceptedOrphans, orphan.Transaction()) + acceptedOrphans = append(acceptedOrphans, orphan.Transaction().Clone()) //these pointers leave the mempool, hence the clone } } } @@ -331,15 +331,45 @@ func (op *orphansPool) randomNonHighPriorityOrphan() *model.OrphanTransaction { func (op *orphansPool) getOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { if orphanTransaction, ok := op.allOrphans[*transactionID]; ok { - return orphanTransaction.Transaction(), true + return orphanTransaction.Transaction().Clone(), true //this pointer leaves the mempool, hence we clone. } return nil, false } -func (op *orphansPool) getAllOrphanTransactions() []*externalapi.DomainTransaction { - allOrphanTransactions := make([]*externalapi.DomainTransaction, 0, len(op.allOrphans)) +func (op *orphansPool) getOrphanTransactionsByAddresses() ( + sending model.ScriptPublicKeyStringToDomainTransaction, + receiving model.ScriptPublicKeyStringToDomainTransaction, + err error) { + sending = make(model.ScriptPublicKeyStringToDomainTransaction) + receiving = make(model.ScriptPublicKeyStringToDomainTransaction, op.orphanTransactionCount()) + var transaction *externalapi.DomainTransaction for _, mempoolTransaction := range op.allOrphans { - allOrphanTransactions = append(allOrphanTransactions, mempoolTransaction.Transaction()) + transaction = mempoolTransaction.Transaction().Clone() //these pointers leave the mempool, hence we clone. + for _, input := range transaction.Inputs { + if input.UTXOEntry == nil { //this is not a bug, but a valid state of orphan transactions with missing outpoints. + continue + } + + sending[input.UTXOEntry.ScriptPublicKey().String()] = transaction + } + for _, output := range transaction.Outputs { + receiving[output.ScriptPublicKey.String()] = transaction + + } + } + return sending, receiving, nil +} + +func (op *orphansPool) getAllOrphanTransactions() []*externalapi.DomainTransaction { + allOrphanTransactions := make([]*externalapi.DomainTransaction, len(op.allOrphans)) + i := 0 + for _, mempoolTransaction := range op.allOrphans { + allOrphanTransactions[i] = mempoolTransaction.Transaction().Clone() //these pointers leave the mempool, hence we clone. + i++ } return allOrphanTransactions } + +func (op *orphansPool) orphanTransactionCount() int { + return len(op.allOrphans) +} diff --git a/domain/miningmanager/mempool/revalidate_high_priority_transactions.go b/domain/miningmanager/mempool/revalidate_high_priority_transactions.go index 6cb876875..12f76403f 100644 --- a/domain/miningmanager/mempool/revalidate_high_priority_transactions.go +++ b/domain/miningmanager/mempool/revalidate_high_priority_transactions.go @@ -11,7 +11,6 @@ func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTr defer onEnd() validTransactions := []*externalapi.DomainTransaction{} - for _, transaction := range mp.transactionsPool.highPriorityTransactions { isValid, err := mp.revalidateTransaction(transaction) if err != nil { @@ -21,7 +20,7 @@ func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTr continue } - validTransactions = append(validTransactions, transaction.Transaction()) + validTransactions = append(validTransactions, transaction.Transaction().Clone()) } return validTransactions, nil diff --git a/domain/miningmanager/mempool/transactions_pool.go b/domain/miningmanager/mempool/transactions_pool.go index bad4e7f7c..63908ee77 100644 --- a/domain/miningmanager/mempool/transactions_pool.go +++ b/domain/miningmanager/mempool/transactions_pool.go @@ -6,6 +6,7 @@ import ( "github.com/pkg/errors" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" ) @@ -135,7 +136,7 @@ func (tp *transactionsPool) allReadyTransactions() []*externalapi.DomainTransact for _, mempoolTransaction := range tp.allTransactions { if len(mempoolTransaction.ParentTransactionsInPool()) == 0 { - result = append(result, mempoolTransaction.Transaction()) + result = append(result, mempoolTransaction.Transaction().Clone()) //this pointer leaves the mempool, and gets its utxo set to nil, hence we clone. } } @@ -204,17 +205,44 @@ func (tp *transactionsPool) limitTransactionCount() error { return nil } -func (tp *transactionsPool) getTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { +func (tp *transactionsPool) getTransaction(transactionID *externalapi.DomainTransactionID, clone bool) (*externalapi.DomainTransaction, bool) { if mempoolTransaction, ok := tp.allTransactions[*transactionID]; ok { + if clone { + return mempoolTransaction.Transaction().Clone(), true //this pointer leaves the mempool, hence we clone. + } return mempoolTransaction.Transaction(), true } return nil, false } -func (tp *transactionsPool) getAllTransactions() []*externalapi.DomainTransaction { - allTransactions := make([]*externalapi.DomainTransaction, 0, len(tp.allTransactions)) +func (tp *transactionsPool) getTransactionsByAddresses() ( + sending model.ScriptPublicKeyStringToDomainTransaction, + receiving model.ScriptPublicKeyStringToDomainTransaction, + err error) { + sending = make(model.ScriptPublicKeyStringToDomainTransaction, tp.transactionCount()) + receiving = make(model.ScriptPublicKeyStringToDomainTransaction, tp.transactionCount()) + var transaction *externalapi.DomainTransaction for _, mempoolTransaction := range tp.allTransactions { - allTransactions = append(allTransactions, mempoolTransaction.Transaction()) + transaction = mempoolTransaction.Transaction().Clone() //this pointer leaves the mempool, hence we clone. + for _, input := range transaction.Inputs { + if input.UTXOEntry == nil { + return nil, nil, errors.Errorf("Mempool transaction %s is missing an UTXOEntry. This should be fixed, and not happen", consensushashing.TransactionID(transaction).String()) + } + sending[input.UTXOEntry.ScriptPublicKey().String()] = transaction + } + for _, output := range transaction.Outputs { + receiving[output.ScriptPublicKey.String()] = transaction + } + } + return sending, receiving, nil +} + +func (tp *transactionsPool) getAllTransactions() []*externalapi.DomainTransaction { + allTransactions := make([]*externalapi.DomainTransaction, len(tp.allTransactions)) + i := 0 + for _, mempoolTransaction := range tp.allTransactions { + allTransactions[i] = mempoolTransaction.Transaction().Clone() //this pointer leaves the mempool, hence we clone. + i++ } return allTransactions } diff --git a/domain/miningmanager/mempool/validate_and_insert_transaction.go b/domain/miningmanager/mempool/validate_and_insert_transaction.go index 3ccedc9bb..d06a19cea 100644 --- a/domain/miningmanager/mempool/validate_and_insert_transaction.go +++ b/domain/miningmanager/mempool/validate_and_insert_transaction.go @@ -54,7 +54,7 @@ func (mp *mempool) validateAndInsertTransaction(transaction *externalapi.DomainT return nil, err } - acceptedTransactions = append([]*externalapi.DomainTransaction{transaction}, acceptedOrphans...) + acceptedTransactions = append([]*externalapi.DomainTransaction{transaction.Clone()}, acceptedOrphans...) //these pointer leave the mempool, hence we clone. err = mp.transactionsPool.limitTransactionCount() if err != nil { diff --git a/domain/miningmanager/miningmanager.go b/domain/miningmanager/miningmanager.go index 08e576f13..10bf6d0dc 100644 --- a/domain/miningmanager/miningmanager.go +++ b/domain/miningmanager/miningmanager.go @@ -15,11 +15,20 @@ type MiningManager interface { GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (block *externalapi.DomainBlock, isNearlySynced bool, err error) ClearBlockTemplate() GetBlockTemplateBuilder() miningmanagermodel.BlockTemplateBuilder - GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) - AllTransactions() []*externalapi.DomainTransaction - GetOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) - AllOrphanTransactions() []*externalapi.DomainTransaction - TransactionCount() int + GetTransaction(transactionID *externalapi.DomainTransactionID, includeTransactionPool bool, includeOrphanPool bool) ( + transactionPoolTransaction *externalapi.DomainTransaction, + isOrphan bool, + found bool) + GetTransactionsByAddresses(includeTransactionPool bool, includeOrphanPool bool) ( + sendingInTransactionPool map[string]*externalapi.DomainTransaction, + receivingInTransactionPool map[string]*externalapi.DomainTransaction, + sendingInOrphanPool map[string]*externalapi.DomainTransaction, + receivingInOrphanPool map[string]*externalapi.DomainTransaction, + err error) + AllTransactions(includeTransactionPool bool, includeOrphanPool bool) ( + transactionPoolTransactions []*externalapi.DomainTransaction, + orphanPoolTransactions []*externalapi.DomainTransaction) + TransactionCount(includeTransactionPool bool, includeOrphanPool bool) int HandleNewBlockTransactions(txs []*externalapi.DomainTransaction) ([]*externalapi.DomainTransaction, error) ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( acceptedTransactions []*externalapi.DomainTransaction, err error) @@ -109,28 +118,35 @@ func (mm *miningManager) ValidateAndInsertTransaction(transaction *externalapi.D } func (mm *miningManager) GetTransaction( - transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { + transactionID *externalapi.DomainTransactionID, + includeTransactionPool bool, + includeOrphanPool bool) ( + transactionPoolTransaction *externalapi.DomainTransaction, + isOrphan bool, + found bool) { - return mm.mempool.GetTransaction(transactionID) + return mm.mempool.GetTransaction(transactionID, includeTransactionPool, includeOrphanPool) } -func (mm *miningManager) AllTransactions() []*externalapi.DomainTransaction { - return mm.mempool.AllTransactions() +func (mm *miningManager) AllTransactions(includeTransactionPool bool, includeOrphanPool bool) ( + transactionPoolTransactions []*externalapi.DomainTransaction, + orphanPoolTransactions []*externalapi.DomainTransaction) { + + return mm.mempool.AllTransactions(includeTransactionPool, includeOrphanPool) } -func (mm *miningManager) GetOrphanTransaction( - transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { +func (mm *miningManager) GetTransactionsByAddresses(includeTransactionPool bool, includeOrphanPool bool) ( + sendingInTransactionPool map[string]*externalapi.DomainTransaction, + receivingInTransactionPool map[string]*externalapi.DomainTransaction, + sendingInOrphanPool map[string]*externalapi.DomainTransaction, + receivingInOrphanPool map[string]*externalapi.DomainTransaction, + err error) { - return mm.mempool.GetOrphanTransaction(transactionID) + return mm.mempool.GetTransactionsByAddresses(includeTransactionPool, includeOrphanPool) } -func (mm *miningManager) AllOrphanTransactions() []*externalapi.DomainTransaction { - - return mm.mempool.AllOrphanTransactions() -} - -func (mm *miningManager) TransactionCount() int { - return mm.mempool.TransactionCount() +func (mm *miningManager) TransactionCount(includeTransactionPool bool, includeOrphanPool bool) int { + return mm.mempool.TransactionCount(includeTransactionPool, includeOrphanPool) } func (mm *miningManager) RevalidateHighPriorityTransactions() ( diff --git a/domain/miningmanager/miningmanager_test.go b/domain/miningmanager/miningmanager_test.go index d8a31aeda..d98950b11 100644 --- a/domain/miningmanager/miningmanager_test.go +++ b/domain/miningmanager/miningmanager_test.go @@ -52,7 +52,7 @@ func TestValidateAndInsertTransaction(t *testing.T) { } // The UTXOEntry was filled manually for those transactions, so the transactions won't be considered orphans. // Therefore, all the transactions expected to be contained in the mempool. - transactionsFromMempool := miningManager.AllTransactions() + transactionsFromMempool, _ := miningManager.AllTransactions(true, false) if len(transactionsToInsert) != len(transactionsFromMempool) { t.Fatalf("Wrong number of transactions in mempool: expected: %d, got: %d", len(transactionsToInsert), len(transactionsFromMempool)) } @@ -72,7 +72,7 @@ func TestValidateAndInsertTransaction(t *testing.T) { if err != nil { t.Fatalf("ValidateAndInsertTransaction: %v", err) } - transactionsFromMempool = miningManager.AllTransactions() + transactionsFromMempool, _ = miningManager.AllTransactions(true, false) if !contains(transactionNotAnOrphan, transactionsFromMempool) { t.Fatalf("Missing transaction %s in the mempool", consensushashing.TransactionID(transactionNotAnOrphan)) } @@ -99,7 +99,7 @@ func TestImmatureSpend(t *testing.T) { if !errors.As(err, txRuleError) || txRuleError.RejectCode != mempool.RejectImmatureSpend { t.Fatalf("Unexpected error %+v", err) } - transactionsFromMempool := miningManager.AllTransactions() + transactionsFromMempool, _ := miningManager.AllTransactions(true, false) if contains(tx, transactionsFromMempool) { t.Fatalf("Mempool contains a transaction with immature coinbase") } @@ -205,7 +205,7 @@ func TestHandleNewBlockTransactions(t *testing.T) { if err != nil { t.Fatalf("HandleNewBlockTransactions: %v", err) } - mempoolTransactions := miningManager.AllTransactions() + mempoolTransactions, _ := miningManager.AllTransactions(true, false) for _, removedTransaction := range blockWithFirstPartOfTheTransactions { if contains(removedTransaction, mempoolTransactions) { t.Fatalf("This transaction shouldnt be in mempool: %s", consensushashing.TransactionID(removedTransaction)) @@ -214,7 +214,7 @@ func TestHandleNewBlockTransactions(t *testing.T) { // There are no chained/double-spends transactions, and hence it is expected that all the other // transactions, will still be included in the mempool. - mempoolTransactions = miningManager.AllTransactions() + mempoolTransactions, _ = miningManager.AllTransactions(true, false) for _, transaction := range blockWithRestOfTheTransactions[transactionhelper.CoinbaseTransactionIndex+1:] { if !contains(transaction, mempoolTransactions) { t.Fatalf("This transaction %s should be in mempool.", consensushashing.TransactionID(transaction)) @@ -225,8 +225,9 @@ func TestHandleNewBlockTransactions(t *testing.T) { if err != nil { t.Fatalf("HandleNewBlockTransactions: %v", err) } - if len(miningManager.AllTransactions()) != 0 { - blockIDs := domainBlocksToBlockIds(miningManager.AllTransactions()) + mempoolTransactions, _ = miningManager.AllTransactions(true, false) + if len(mempoolTransactions) != 0 { + blockIDs := domainBlocksToBlockIds(mempoolTransactions) t.Fatalf("The mempool contains unexpected transactions: %s", blockIDs) } }) @@ -269,7 +270,8 @@ func TestDoubleSpendWithBlock(t *testing.T) { if err != nil { t.Fatalf("HandleNewBlockTransactions: %v", err) } - if contains(transactionInTheMempool, miningManager.AllTransactions()) { + mempoolTransactions, _ := miningManager.AllTransactions(true, false) + if contains(transactionInTheMempool, mempoolTransactions) { t.Fatalf("The transaction %s, shouldn't be in the mempool, since at least one "+ "output was already spent.", consensushashing.TransactionID(transactionInTheMempool)) } @@ -303,7 +305,7 @@ func TestOrphanTransactions(t *testing.T) { t.Fatalf("ValidateAndInsertTransaction: %v", err) } } - transactionsMempool := miningManager.AllTransactions() + transactionsMempool, _ := miningManager.AllTransactions(true, false) for _, transaction := range transactionsMempool { if contains(transaction, childTransactions) { t.Fatalf("Error: an orphan transaction is exist in the mempool") @@ -345,7 +347,7 @@ func TestOrphanTransactions(t *testing.T) { if err != nil { t.Fatalf("HandleNewBlockTransactions: %+v", err) } - transactionsMempool = miningManager.AllTransactions() + transactionsMempool, _ = miningManager.AllTransactions(true, false) if len(transactionsMempool) != len(childTransactions) { t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool)) } @@ -553,8 +555,8 @@ func TestRevalidateHighPriorityTransactions(t *testing.T) { } // Make sure spendingTransaction is still in mempool - allTransactions := miningManager.AllTransactions() - if len(allTransactions) != 1 || !allTransactions[0].Equal(spendingTransaction) { + mempoolTransactions, _ := miningManager.AllTransactions(true, false) + if len(mempoolTransactions) != 1 || !mempoolTransactions[0].Equal(spendingTransaction) { t.Fatalf("Expected to have spendingTransaction as only validTransaction returned from "+ "RevalidateHighPriorityTransactions, but got %v instead", validTransactions) } @@ -568,9 +570,9 @@ func TestRevalidateHighPriorityTransactions(t *testing.T) { t.Fatalf("Expected to have empty validTransactions, but got %v instead", validTransactions) } // And also AllTransactions should be empty as well - allTransactions = miningManager.AllTransactions() - if len(allTransactions) != 0 { - t.Fatalf("Expected to have empty allTransactions, but got %v instead", allTransactions) + mempoolTransactions, _ = miningManager.AllTransactions(true, false) + if len(mempoolTransactions) != 0 { + t.Fatalf("Expected to have empty allTransactions, but got %v instead", mempoolTransactions) } }) } @@ -605,7 +607,7 @@ func TestModifyBlockTemplate(t *testing.T) { t.Fatalf("ValidateAndInsertTransaction: %v", err) } } - transactionsMempool := miningManager.AllTransactions() + transactionsMempool, _ := miningManager.AllTransactions(true, false) for _, transaction := range transactionsMempool { if contains(transaction, childTransactions) { t.Fatalf("Error: an orphan transaction is exist in the mempool") @@ -654,7 +656,7 @@ func TestModifyBlockTemplate(t *testing.T) { if err != nil { t.Fatalf("HandleNewBlockTransactions: %+v", err) } - transactionsMempool = miningManager.AllTransactions() + transactionsMempool, _ = miningManager.AllTransactions(true, false) if len(transactionsMempool) != len(childTransactions) { t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool)) } diff --git a/domain/miningmanager/model/interface_mempool.go b/domain/miningmanager/model/interface_mempool.go index caf6b5968..fe9be9b5c 100644 --- a/domain/miningmanager/model/interface_mempool.go +++ b/domain/miningmanager/model/interface_mempool.go @@ -12,11 +12,31 @@ type Mempool interface { ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( acceptedTransactions []*externalapi.DomainTransaction, err error) RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error - GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) - AllTransactions() []*externalapi.DomainTransaction - GetOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) - AllOrphanTransactions() []*externalapi.DomainTransaction - TransactionCount() int + GetTransaction( + transactionID *externalapi.DomainTransactionID, + includeTransactionPool bool, + includeOrphanPool bool, + ) ( + transactionPoolTransaction *externalapi.DomainTransaction, + isOrphan bool, + found bool) + GetTransactionsByAddresses( + includeTransactionPool bool, + includeOrphanPool bool) ( + sendingInTransactionPool map[string]*externalapi.DomainTransaction, + receivingInTransactionPool map[string]*externalapi.DomainTransaction, + sendingInOrphanPool map[string]*externalapi.DomainTransaction, + receivingInOrphanPool map[string]*externalapi.DomainTransaction, + err error) + AllTransactions( + includeTransactionPool bool, + includeOrphanPool bool, + ) ( + transactionPoolTransactions []*externalapi.DomainTransaction, + orphanPoolTransactions []*externalapi.DomainTransaction) + TransactionCount( + includeTransactionPool bool, + includeOrphanPool bool) int RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) IsTransactionOutputDust(output *externalapi.DomainTransactionOutput) bool } diff --git a/domain/utxoindex/model.go b/domain/utxoindex/model.go index 2ceb65607..a6ffefc1d 100644 --- a/domain/utxoindex/model.go +++ b/domain/utxoindex/model.go @@ -1,7 +1,6 @@ package utxoindex import ( - "encoding/binary" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" ) @@ -22,22 +21,3 @@ type UTXOChanges struct { Added map[ScriptPublicKeyString]UTXOOutpointEntryPairs Removed map[ScriptPublicKeyString]UTXOOutpointEntryPairs } - -// ConvertScriptPublicKeyToString converts the given scriptPublicKey to a string -func ConvertScriptPublicKeyToString(scriptPublicKey *externalapi.ScriptPublicKey) ScriptPublicKeyString { - var versionBytes = make([]byte, 2) // uint16 - binary.LittleEndian.PutUint16(versionBytes, scriptPublicKey.Version) - versionString := ScriptPublicKeyString(versionBytes) - scriptString := ScriptPublicKeyString(scriptPublicKey.Script) - return versionString + scriptString - -} - -// ConvertStringToScriptPublicKey converts the given string to a scriptPublicKey -func ConvertStringToScriptPublicKey(string ScriptPublicKeyString) *externalapi.ScriptPublicKey { - bytes := []byte(string) - version := binary.LittleEndian.Uint16(bytes[:2]) - script := bytes[2:] - return &externalapi.ScriptPublicKey{Script: script, Version: version} - -} diff --git a/domain/utxoindex/store.go b/domain/utxoindex/store.go index dbca42b7b..d42f4ac52 100644 --- a/domain/utxoindex/store.go +++ b/domain/utxoindex/store.go @@ -32,7 +32,7 @@ func newUTXOIndexStore(database database.Database) *utxoIndexStore { func (uis *utxoIndexStore) add(scriptPublicKey *externalapi.ScriptPublicKey, outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry) error { - key := ConvertScriptPublicKeyToString(scriptPublicKey) + key := ScriptPublicKeyString(scriptPublicKey.String()) log.Tracef("Adding outpoint %s:%d to scriptPublicKey %s", outpoint.TransactionID, outpoint.Index, key) @@ -66,7 +66,7 @@ func (uis *utxoIndexStore) add(scriptPublicKey *externalapi.ScriptPublicKey, out } func (uis *utxoIndexStore) remove(scriptPublicKey *externalapi.ScriptPublicKey, outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry) error { - key := ConvertScriptPublicKeyToString(scriptPublicKey) + key := ScriptPublicKeyString(scriptPublicKey.String()) log.Tracef("Removing outpoint %s:%d from scriptPublicKey %s", outpoint.TransactionID, outpoint.Index, key) @@ -122,7 +122,7 @@ func (uis *utxoIndexStore) commit() error { toRemoveSompiSupply := uint64(0) for scriptPublicKeyString, toRemoveUTXOOutpointEntryPairs := range uis.toRemove { - scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString) + scriptPublicKey := externalapi.NewScriptPublicKeyFromString(string(scriptPublicKeyString)) bucket := uis.bucketForScriptPublicKey(scriptPublicKey) for outpointToRemove, utxoEntryToRemove := range toRemoveUTXOOutpointEntryPairs { key, err := uis.convertOutpointToKey(bucket, &outpointToRemove) @@ -140,7 +140,7 @@ func (uis *utxoIndexStore) commit() error { toAddSompiSupply := uint64(0) for scriptPublicKeyString, toAddUTXOOutpointEntryPairs := range uis.toAdd { - scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString) + scriptPublicKey := externalapi.NewScriptPublicKeyFromString(string(scriptPublicKeyString)) bucket := uis.bucketForScriptPublicKey(scriptPublicKey) for outpointToAdd, utxoEntryToAdd := range toAddUTXOOutpointEntryPairs { key, err := uis.convertOutpointToKey(bucket, &outpointToAdd)