From 7a61c637b0641002abc30ecf2e7e5729ab3cab20 Mon Sep 17 00:00:00 2001 From: Aleoami Date: Thu, 21 Jul 2022 13:55:12 +0300 Subject: [PATCH 01/11] 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 02/11] 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) From 715cb3b1ac765937faed119386350f697261a60f Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Tue, 2 Aug 2022 10:33:39 +0300 Subject: [PATCH 03/11] Fix a subtle lock sync issue in consensus insert block (#2121) * Manage the locks more tightly and fix a slight and rare sync issue * Extract virtualResolveChunk constant --- domain/consensus/consensus.go | 41 +++++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 8177389b7..20d02be9c 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -64,6 +64,12 @@ type consensus struct { virtualNotUpdated bool } +// In order to prevent a situation that the consensus lock is held for too much time, we +// release the lock each time we resolve 100 blocks. +// Note: `virtualResolveChunk` should be smaller than `params.FinalityDuration` in order to avoid a situation +// where UpdatePruningPointByVirtual skips a pruning point. +const virtualResolveChunk = 100 + func (s *consensus) ValidateAndInsertBlockWithTrustedData(block *externalapi.BlockWithTrustedData, validateUTXO bool) error { s.lock.Lock() defer s.lock.Unlock() @@ -198,15 +204,32 @@ func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, updat if updateVirtual { s.lock.Lock() if s.virtualNotUpdated { - s.lock.Unlock() - err := s.ResolveVirtual(nil) - if err != nil { - return err + // We enter the loop in locked state + for { + _, isCompletelyResolved, err := s.resolveVirtualChunkNoLock(virtualResolveChunk) + if err != nil { + s.lock.Unlock() + return err + } + if isCompletelyResolved { + // Make sure we enter the block insertion function w/o releasing the lock. + // Otherwise, we might actually enter it in `s.virtualNotUpdated == true` state + _, err = s.validateAndInsertBlockNoLock(block, updateVirtual) + // Finally, unlock for the last iteration and return + s.lock.Unlock() + if err != nil { + return err + } + return nil + } + // Unlock to allow other threads to enter consensus + s.lock.Unlock() + // Lock for the next iteration + s.lock.Lock() } - return s.validateAndInsertBlockWithLock(block, updateVirtual) } - defer s.lock.Unlock() _, err := s.validateAndInsertBlockNoLock(block, updateVirtual) + s.lock.Unlock() if err != nil { return err } @@ -912,11 +935,7 @@ func (s *consensus) ResolveVirtual(progressReportCallback func(uint64, uint64)) progressReportCallback(virtualDAAScoreStart, virtualDAAScore) } - // In order to prevent a situation that the consensus lock is held for too much time, we - // release the lock each time we resolve 100 blocks. - // Note: maxBlocksToResolve should be smaller than `params.FinalityDuration` in order to avoid a situation - // where UpdatePruningPointByVirtual skips a pruning point. - _, isCompletelyResolved, err := s.resolveVirtualChunkWithLock(100) + _, isCompletelyResolved, err := s.resolveVirtualChunkWithLock(virtualResolveChunk) if err != nil { return err } From 9ee409afaa6955b67e8c464af94c46aea9b77179 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Tue, 9 Aug 2022 16:30:24 +0300 Subject: [PATCH 04/11] Fix RPC client memory/goroutine leak (#2122) * Showcase the RPC client memory leak * Fixes an RPC client goroutine leak by properly closing the underlying connection --- .../rpcclient/grpcclient/grpcclient.go | 8 ++++- infrastructure/network/rpcclient/rpcclient.go | 5 ++- testing/integration/rpc_test.go | 32 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/infrastructure/network/rpcclient/grpcclient/grpcclient.go b/infrastructure/network/rpcclient/grpcclient/grpcclient.go index e15731aa3..4042c6267 100644 --- a/infrastructure/network/rpcclient/grpcclient/grpcclient.go +++ b/infrastructure/network/rpcclient/grpcclient/grpcclient.go @@ -22,6 +22,7 @@ type OnDisconnectedHandler func() // GRPCClient is a gRPC-based RPC client type GRPCClient struct { stream protowire.RPC_MessageStreamClient + connection *grpc.ClientConn onErrorHandler OnErrorHandler onDisconnectedHandler OnDisconnectedHandler } @@ -43,7 +44,12 @@ func Connect(address string) (*GRPCClient, error) { if err != nil { return nil, errors.Wrapf(err, "error getting client stream for %s", address) } - return &GRPCClient{stream: stream}, nil + return &GRPCClient{stream: stream, connection: gRPCConnection}, nil +} + +// Close closes the underlying grpc connection +func (c *GRPCClient) Close() error { + return c.connection.Close() } // Disconnect disconnects from the RPC server diff --git a/infrastructure/network/rpcclient/rpcclient.go b/infrastructure/network/rpcclient/rpcclient.go index 7256f6c82..e4671c028 100644 --- a/infrastructure/network/rpcclient/rpcclient.go +++ b/infrastructure/network/rpcclient/rpcclient.go @@ -143,6 +143,9 @@ func (c *RPCClient) handleClientDisconnected() { } func (c *RPCClient) handleClientError(err error) { + if atomic.LoadUint32(&c.isClosed) == 1 { + return + } log.Warnf("Received error from client: %s", err) c.handleClientDisconnected() } @@ -159,7 +162,7 @@ func (c *RPCClient) Close() error { return errors.Errorf("Cannot close a client that had already been closed") } c.rpcRouter.router.Close() - return nil + return c.GRPCClient.Close() } // Address returns the address the RPC client connected to diff --git a/testing/integration/rpc_test.go b/testing/integration/rpc_test.go index 44fb1e40d..76caf1ff5 100644 --- a/testing/integration/rpc_test.go +++ b/testing/integration/rpc_test.go @@ -2,6 +2,7 @@ package integration import ( "github.com/kaspanet/kaspad/infrastructure/config" + "runtime" "testing" "time" @@ -26,6 +27,37 @@ func newTestRPCClient(rpcAddress string) (*testRPCClient, error) { }, nil } +func connectAndClose(rpcAddress string) error { + client, err := rpcclient.NewRPCClient(rpcAddress) + if err != nil { + return err + } + defer client.Close() + return nil +} + +func TestRPCClientGoroutineLeak(t *testing.T) { + _, teardown := setupHarness(t, &harnessParams{ + p2pAddress: p2pAddress1, + rpcAddress: rpcAddress1, + miningAddress: miningAddress1, + miningAddressPrivateKey: miningAddress1PrivateKey, + }) + defer teardown() + numGoroutinesBefore := runtime.NumGoroutine() + for i := 1; i < 100; i++ { + err := connectAndClose(rpcAddress1) + if err != nil { + t.Fatalf("Failed to set up an RPC client: %s", err) + } + time.Sleep(10 * time.Millisecond) + if runtime.NumGoroutine() > numGoroutinesBefore+10 { + t.Fatalf("Number of goroutines is increasing for each RPC client open (%d -> %d), which indicates a memory leak", + numGoroutinesBefore, runtime.NumGoroutine()) + } + } +} + func TestRPCMaxInboundConnections(t *testing.T) { harness, teardown := setupHarness(t, &harnessParams{ p2pAddress: p2pAddress1, From 266ec6c270342869f51adaaa117280ddbe6e0265 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Wed, 17 Aug 2022 15:56:53 +0300 Subject: [PATCH 05/11] Calculate pruning point utxo set from acceptance data (#2123) * Calc new pruning point utxo diff through chain acceptance data * Fix daa score to chain block --- .../pruningmanager/pruningmanager.go | 97 ++++++------------- 1 file changed, 30 insertions(+), 67 deletions(-) diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go index dd5798afd..e0b7ca4c3 100644 --- a/domain/consensus/processes/pruningmanager/pruningmanager.go +++ b/domain/consensus/processes/pruningmanager/pruningmanager.go @@ -772,78 +772,41 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st if err != nil { return nil, err } - currentPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningHash, false) + + utxoDiff := utxo.NewMutableUTXODiff() + + iterator, err := pm.dagTraversalManager.SelectedChildIterator(stagingArea, currentPruningHash, previousPruningHash, false) if err != nil { return nil, err } - previousPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningHash, false) - if err != nil { - return nil, err + defer iterator.Close() + + for ok := iterator.First(); ok; ok = iterator.Next() { + child, err := iterator.Get() + if err != nil { + return nil, err + } + chainBlockAcceptanceData, err := pm.acceptanceDataStore.Get(pm.databaseContext, stagingArea, child) + if err != nil { + return nil, err + } + chainBlockHeader, err := pm.blockHeaderStore.BlockHeader(pm.databaseContext, stagingArea, child) + if err != nil { + return nil, err + } + for _, blockAcceptanceData := range chainBlockAcceptanceData { + for _, transactionAcceptanceData := range blockAcceptanceData.TransactionAcceptanceData { + if transactionAcceptanceData.IsAccepted { + err = utxoDiff.AddTransaction(transactionAcceptanceData.Transaction, chainBlockHeader.DAAScore()) + if err != nil { + return nil, err + } + } + } + } } - currentPruningCurrentDiffChild := currentPruningHash - previousPruningCurrentDiffChild := previousPruningHash - // We need to use BlueWork because it's the only thing that's monotonic in the whole DAG - // We use the BlueWork to know which point is currently lower on the DAG so we can keep climbing its children, - // that way we keep climbing on the lowest point until they both reach the exact same descendant - currentPruningCurrentDiffChildBlueWork := currentPruningGhostDAG.BlueWork() - previousPruningCurrentDiffChildBlueWork := previousPruningGhostDAG.BlueWork() - - var diffHashesFromPrevious []*externalapi.DomainHash - var diffHashesFromCurrent []*externalapi.DomainHash - for { - // if currentPruningCurrentDiffChildBlueWork > previousPruningCurrentDiffChildBlueWork - if currentPruningCurrentDiffChildBlueWork.Cmp(previousPruningCurrentDiffChildBlueWork) == 1 { - diffHashesFromPrevious = append(diffHashesFromPrevious, previousPruningCurrentDiffChild) - previousPruningCurrentDiffChild, err = pm.utxoDiffStore.UTXODiffChild(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild) - if err != nil { - return nil, err - } - diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningCurrentDiffChild, false) - if err != nil { - return nil, err - } - previousPruningCurrentDiffChildBlueWork = diffChildGhostDag.BlueWork() - } else if currentPruningCurrentDiffChild.Equal(previousPruningCurrentDiffChild) { - break - } else { - diffHashesFromCurrent = append(diffHashesFromCurrent, currentPruningCurrentDiffChild) - currentPruningCurrentDiffChild, err = pm.utxoDiffStore.UTXODiffChild(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild) - if err != nil { - return nil, err - } - diffChildGhostDag, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, currentPruningCurrentDiffChild, false) - if err != nil { - return nil, err - } - currentPruningCurrentDiffChildBlueWork = diffChildGhostDag.BlueWork() - } - } - // The order in which we apply the diffs should be from top to bottom, but we traversed from bottom to top - // so we apply the diffs in reverse order. - oldDiff := utxo.NewMutableUTXODiff() - for i := len(diffHashesFromPrevious) - 1; i >= 0; i-- { - utxoDiff, err := pm.utxoDiffStore.UTXODiff(pm.databaseContext, stagingArea, diffHashesFromPrevious[i]) - if err != nil { - return nil, err - } - err = oldDiff.WithDiffInPlace(utxoDiff) - if err != nil { - return nil, err - } - } - newDiff := utxo.NewMutableUTXODiff() - for i := len(diffHashesFromCurrent) - 1; i >= 0; i-- { - utxoDiff, err := pm.utxoDiffStore.UTXODiff(pm.databaseContext, stagingArea, diffHashesFromCurrent[i]) - if err != nil { - return nil, err - } - err = newDiff.WithDiffInPlace(utxoDiff) - if err != nil { - return nil, err - } - } - return oldDiff.DiffFrom(newDiff.ToImmutable()) + return utxoDiff.ToImmutable(), err } // finalityScore is the number of finality intervals passed since From 3f80638c86d334379b43ffc69bbfad6ba6d0fe09 Mon Sep 17 00:00:00 2001 From: Michael Sutton Date: Sun, 21 Aug 2022 01:26:03 +0300 Subject: [PATCH 06/11] Add missing locks to notification listener modifications (#2124) --- app/rpc/manager.go | 13 +------ app/rpc/rpccontext/notificationmanager.go | 37 +++++++++++++++---- app/rpc/rpchandlers/notify_utxos_changed.go | 2 +- .../stop_notifying_utxos_changed.go | 2 +- 4 files changed, 33 insertions(+), 21 deletions(-) diff --git a/app/rpc/manager.go b/app/rpc/manager.go index 137fd6555..c9479666b 100644 --- a/app/rpc/manager.go +++ b/app/rpc/manager.go @@ -223,18 +223,9 @@ func (m *Manager) notifyVirtualSelectedParentChainChanged(virtualChangeSet *exte onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualSelectedParentChainChanged") defer onEnd() - listenersThatPropagateSelectedParentChanged := - m.context.NotificationManager.AllListenersThatPropagateVirtualSelectedParentChainChanged() - if len(listenersThatPropagateSelectedParentChanged) > 0 { - // Generating acceptedTransactionIDs is a heavy operation, so we check if it's needed by any listener. - includeAcceptedTransactionIDs := false - for _, listener := range listenersThatPropagateSelectedParentChanged { - if listener.IncludeAcceptedTransactionIDsInVirtualSelectedParentChainChangedNotifications() { - includeAcceptedTransactionIDs = true - break - } - } + hasListeners, includeAcceptedTransactionIDs := m.context.NotificationManager.HasListenersThatPropagateVirtualSelectedParentChainChanged() + if hasListeners { notification, err := m.context.ConvertVirtualSelectedParentChainChangesToChainChangedNotificationMessage( virtualChangeSet.VirtualSelectedParentChainChanges, includeAcceptedTransactionIDs) if err != nil { diff --git a/app/rpc/rpccontext/notificationmanager.go b/app/rpc/rpccontext/notificationmanager.go index 0ba132f17..bcd2db4d7 100644 --- a/app/rpc/rpccontext/notificationmanager.go +++ b/app/rpc/rpccontext/notificationmanager.go @@ -142,16 +142,29 @@ func (nm *NotificationManager) NotifyVirtualSelectedParentChainChanged( return nil } -// AllListenersThatPropagateVirtualSelectedParentChainChanged returns true if there's any listener that is -// subscribed to VirtualSelectedParentChainChanged notifications. -func (nm *NotificationManager) AllListenersThatPropagateVirtualSelectedParentChainChanged() []*NotificationListener { - var listenersThatPropagate []*NotificationListener +// HasListenersThatPropagateVirtualSelectedParentChainChanged returns whether there's any listener that is +// subscribed to VirtualSelectedParentChainChanged notifications as well as checks if any such listener requested +// to include AcceptedTransactionIDs. +func (nm *NotificationManager) HasListenersThatPropagateVirtualSelectedParentChainChanged() (hasListeners, hasListenersThatRequireAcceptedTransactionIDs bool) { + + nm.RLock() + defer nm.RUnlock() + + hasListeners = false + hasListenersThatRequireAcceptedTransactionIDs = false + for _, listener := range nm.listeners { if listener.propagateVirtualSelectedParentChainChangedNotifications { - listenersThatPropagate = append(listenersThatPropagate, listener) + hasListeners = true + // Generating acceptedTransactionIDs is a heavy operation, so we check if it's needed by any listener. + if listener.includeAcceptedTransactionIDsInVirtualSelectedParentChainChangedNotifications { + hasListenersThatRequireAcceptedTransactionIDs = true + break + } } } - return listenersThatPropagate + + return hasListeners, hasListenersThatRequireAcceptedTransactionIDs } // NotifyFinalityConflict notifies the notification manager that there's a finality conflict in the DAG @@ -338,7 +351,11 @@ func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications() // to the remote listener for the given addresses. Subsequent calls instruct the listener to // send UTXOs changed notifications for those addresses along with the old ones. Duplicate addresses // are ignored. -func (nl *NotificationListener) PropagateUTXOsChangedNotifications(addresses []*UTXOsChangedNotificationAddress) { +func (nm *NotificationManager) PropagateUTXOsChangedNotifications(nl *NotificationListener, addresses []*UTXOsChangedNotificationAddress) { + // Apply a write-lock since the internal listener address map is modified + nm.Lock() + defer nm.Unlock() + if !nl.propagateUTXOsChangedNotifications { nl.propagateUTXOsChangedNotifications = true nl.propagateUTXOsChangedNotificationAddresses = @@ -353,7 +370,11 @@ func (nl *NotificationListener) PropagateUTXOsChangedNotifications(addresses []* // StopPropagatingUTXOsChangedNotifications instructs the listener to stop sending UTXOs // changed notifications to the remote listener for the given addresses. Addresses for which // notifications are not currently sent are ignored. -func (nl *NotificationListener) StopPropagatingUTXOsChangedNotifications(addresses []*UTXOsChangedNotificationAddress) { +func (nm *NotificationManager) StopPropagatingUTXOsChangedNotifications(nl *NotificationListener, addresses []*UTXOsChangedNotificationAddress) { + // Apply a write-lock since the internal listener address map is modified + nm.Lock() + defer nm.Unlock() + if !nl.propagateUTXOsChangedNotifications { return } diff --git a/app/rpc/rpchandlers/notify_utxos_changed.go b/app/rpc/rpchandlers/notify_utxos_changed.go index 41ffe0dd3..e43f10204 100644 --- a/app/rpc/rpchandlers/notify_utxos_changed.go +++ b/app/rpc/rpchandlers/notify_utxos_changed.go @@ -26,7 +26,7 @@ func HandleNotifyUTXOsChanged(context *rpccontext.Context, router *router.Router if err != nil { return nil, err } - listener.PropagateUTXOsChangedNotifications(addresses) + context.NotificationManager.PropagateUTXOsChangedNotifications(listener, addresses) response := appmessage.NewNotifyUTXOsChangedResponseMessage() return response, nil diff --git a/app/rpc/rpchandlers/stop_notifying_utxos_changed.go b/app/rpc/rpchandlers/stop_notifying_utxos_changed.go index 0da89a9bf..2ef376397 100644 --- a/app/rpc/rpchandlers/stop_notifying_utxos_changed.go +++ b/app/rpc/rpchandlers/stop_notifying_utxos_changed.go @@ -26,7 +26,7 @@ func HandleStopNotifyingUTXOsChanged(context *rpccontext.Context, router *router if err != nil { return nil, err } - listener.StopPropagatingUTXOsChangedNotifications(addresses) + context.NotificationManager.StopPropagatingUTXOsChangedNotifications(listener, addresses) response := appmessage.NewStopNotifyingUTXOsChangedResponseMessage() return response, nil From d941c73701fc3eacd77dd9344cf0e471d74d1f3b Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 21 Aug 2022 02:32:40 +0300 Subject: [PATCH 07/11] Change testnet dnsseeder (#2126) Co-authored-by: Michael Sutton --- domain/dagconfig/params.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/domain/dagconfig/params.go b/domain/dagconfig/params.go index 52b3cfb6e..7ef89bd31 100644 --- a/domain/dagconfig/params.go +++ b/domain/dagconfig/params.go @@ -296,7 +296,7 @@ var TestnetParams = Params{ Net: appmessage.Testnet, RPCPort: "16210", DefaultPort: "16211", - DNSSeeds: []string{"testnet-9-dnsseed.daglabs-dev.com"}, + DNSSeeds: []string{"testnet-10-dnsseed.kas.pa"}, // DAG parameters GenesisBlock: &testnetGenesisBlock, From 10f1e7e3f43ff728f236b15a87dbc30f638ad57e Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 21 Aug 2022 03:10:59 +0300 Subject: [PATCH 08/11] Replace daglabs's dnsseeder with Wolfie's (#2119) Co-authored-by: Michael Sutton --- domain/dagconfig/params.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain/dagconfig/params.go b/domain/dagconfig/params.go index 7ef89bd31..67e20c16f 100644 --- a/domain/dagconfig/params.go +++ b/domain/dagconfig/params.go @@ -214,7 +214,8 @@ var MainnetParams = Params{ RPCPort: "16110", DefaultPort: "16111", DNSSeeds: []string{ - "mainnet-dnsseed.daglabs-dev.com", + // This DNS seeder is run by Wolfie + "mainnet-dnsseed.kas.pa", // This DNS seeder is run by Denis Mashkevich "mainnet-dnsseed-1.kaspanet.org", // This DNS seeder is run by Denis Mashkevich From 20b7ab89f94e2e97190bedb063abb6ecf2fca778 Mon Sep 17 00:00:00 2001 From: Elichai Turkel Date: Sun, 21 Aug 2022 09:08:20 +0300 Subject: [PATCH 09/11] Add tests for hash writers (#2120) Co-authored-by: Michael Sutton --- domain/consensus/utils/hashes/writers_test.go | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/domain/consensus/utils/hashes/writers_test.go b/domain/consensus/utils/hashes/writers_test.go index ae02f0ab1..c1a76bd78 100644 --- a/domain/consensus/utils/hashes/writers_test.go +++ b/domain/consensus/utils/hashes/writers_test.go @@ -1,10 +1,80 @@ package hashes import ( + "fmt" "math/rand" "testing" ) +func TestNewBlockHash(t *testing.T) { + datas := [][]byte{ + {}, + {1}, + {5, 199, 126, 44, 71, 32, 82, 139, 122, 217, 43, 48, 52, 112, 40, 209, 180, 83, 139, 231, 72, 48, 136, 48, 168, 226, 133, 7, 60, 4, 160, 205}, + {42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42, 42}, + {0, 0, 0, 0, 0, 0, 0, 0}, + } + tests := []struct { + hasher HashWriter + expected []string + }{ + {NewTransactionHashWriter(), []string{ + "50272a9e37c728026f93d0eda6ab4467f627338b879076483c88d291193cb3bf", + "f9bf7e04c712621a0f4bb75d763f9ef5f73af6c438fd15b80744393bc96398ad", + "8e791f3edcc92b71b8de2778efbc4666ee5bd146acbe8723a55bca26b022b0e0", + "a6dab1a3088548c62d13a082fa28e870fdbbe51adcd8c364e2ea37e473c04d81", + "3b79b78b967233843ad30f707b165eb3d6a91af8338076be8755c46a963c3d1d", + }}, + {NewTransactionIDWriter(), []string{ + "e5f65efda0894d2b0590c2e9e46e9acc03032f505a1522f5e8c78c5ec70b1d9c", + "aea52cf5e5a13da13a52dd69abd636eb1b0f86e58bc1dda6b17886b94593415a", + "a50a2f87bdce075740189e9e23907ae22b5addbd875ccb70c116811b1fa5fb18", + "0db7a485f7013a346a8f7f5caf73d52ca3c3b5ee101ad8753adedd4235b7236b", + "2afc9c855854b0a6e94a722c3451d0cdfc8c11748b78ef65b9786f87b48d0d07", + }}, + {NewTransactionSigningHashWriter(), []string{ + "34c75037ad62740d4b3228f88f844f7901c07bfacd55a045be518eabc15e52ce", + "8523b0471bcbea04575ccaa635eef9f9114f2890bda54367e5ff8caa3878bf82", + "a51c49d9eb3d13f9de16e1aa8d1ff17668d55633ce00f36a643ac714b0fb137f", + "487f199ef74c3e893e85bd37770e6334575a2d4d113b2e10474593c49807de93", + "6392adc33a8e24e9a0a0c4c5f07f9c1cc958ad40c16d7a9a276e374cebb4e32b", + }}, + {NewTransactionSigningHashECDSAWriter(), []string{ + "b31ad1fbbe41b0e2a90e07c84708b38ba581f0c0e9185416913a04fb6d342027", + "c43e1f75ea9df6379b56a95074c2b6289ed8c5a01fff2d49d9d44ad5575c164b", + "49085f99fa0084b5436663f757a5916b1e4290c3321707fb76921ed4e47844ec", + "3f887e866428de813c1d0463b14eef3ca1363c8187e917dda1eee0ec5996490b", + "56de89a8c75f0fee2de61b11ab05d0d42e29ed50879467cf128dd80800a52ada", + }}, + {NewBlockHashWriter(), []string{ + "a80b6aa20f20b15ebabe2b1949527f78a257594a732e774de637d85e6973a768", + "5643023add641f9421187b8c9aa3c6c73227d5ec34131c61a08d35b43e7e4b65", + "4dc3bf72045431e46f8839a7d390898f27c887fddd8637149bfb70f732f04334", + "15d7648e69023dca65c949a61ea166192049f449c604523494813873b19918a7", + "3ac41af8385ea5d902ce6d47f509b7accc9c631f1d57a719d777874467f6d877", + }}, + {NewMerkleBranchHashWriter(), []string{ + "4de3617db456d01248173f17ec58196e92fbd994b636476db4b875ed2ec84054", + "5737cd8b6fca5a30c19a491323a14e6b7021641cb3f8875f10c7a2eafd3cf43f", + "a49eeda61cc75e0a8e5915829752fe0ad97620d6d32de7c9883595b0810ca33e", + "28f33681dcff1313674e07dacc2d74c3089f6d8cea7a4f8792a71fd870988ee5", + "2d53a43a42020a5091c125230bcd8a4cf0eeb188333e68325d4bce58a1c75ca3", + }}, + } + + for _, testVector := range tests { + hasher := testVector.hasher + for i, data := range datas { + hasher.InfallibleWrite(data) + res := hasher.Finalize().String() + if res != testVector.expected[i] { + panic(fmt.Sprintf("expected: %s, got: %s", testVector.expected[i], res)) + } + } + } + +} + func BenchmarkNewBlockHashWriterSmall(b *testing.B) { r := rand.New(rand.NewSource(0)) var someBytes [32]byte From aabbc741d78eb2a1624214c6124f7d2c6e6e816e Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Sun, 21 Aug 2022 17:12:05 +0300 Subject: [PATCH 10/11] Add UseExistingChangeAddress option to the wallet (#2127) --- cmd/kaspawallet/config.go | 22 +- cmd/kaspawallet/create_unsigned_tx.go | 7 +- cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go | 344 ++++++++++-------- cmd/kaspawallet/daemon/pb/kaspawalletd.proto | 6 +- .../daemon/pb/kaspawalletd_grpc.pb.go | 43 +-- cmd/kaspawallet/daemon/server/address.go | 23 +- .../server/create_unsigned_transaction.go | 6 +- cmd/kaspawallet/daemon/server/send.go | 2 +- cmd/kaspawallet/send.go | 7 +- 9 files changed, 242 insertions(+), 218 deletions(-) diff --git a/cmd/kaspawallet/config.go b/cmd/kaspawallet/config.go index 06daff64a..518ef351b 100644 --- a/cmd/kaspawallet/config.go +++ b/cmd/kaspawallet/config.go @@ -52,12 +52,13 @@ type balanceConfig struct { } type sendConfig 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"` - DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"` - ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"` - FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"` - SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"` + 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"` + DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"` + ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"` + FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"` + SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"` + UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"` config.NetworkFlags } @@ -68,10 +69,11 @@ type sweepConfig struct { } type createUnsignedTransactionConfig struct { - DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"` - ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"` - FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"` - SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"` + DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"` + ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"` + FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"` + SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)" required:"true"` + UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"` config.NetworkFlags } diff --git a/cmd/kaspawallet/create_unsigned_tx.go b/cmd/kaspawallet/create_unsigned_tx.go index e53f31c3e..4a03e6801 100644 --- a/cmd/kaspawallet/create_unsigned_tx.go +++ b/cmd/kaspawallet/create_unsigned_tx.go @@ -22,9 +22,10 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error { sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa) response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{ - From: conf.FromAddresses, - Address: conf.ToAddress, - Amount: sendAmountSompi, + From: conf.FromAddresses, + Address: conf.ToAddress, + Amount: sendAmountSompi, + UseExistingChangeAddress: conf.UseExistingChangeAddress, }) if err != nil { return err diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go b/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go index 5ee1f1fd7..032d37e8e 100644 --- a/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go +++ b/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go @@ -1,12 +1,13 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.28.0 -// protoc v3.17.2 +// protoc-gen-go v1.25.0 +// protoc v3.12.3 // source: kaspawalletd.proto package pb import ( + proto "github.com/golang/protobuf/proto" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" reflect "reflect" @@ -20,6 +21,10 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// This is a compile-time assertion that a sufficiently up-to-date version +// of the legacy proto package is being used. +const _ = proto.ProtoPackageIsVersion4 + type GetBalanceRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -189,9 +194,10 @@ type CreateUnsignedTransactionsRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` - Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` - From []string `protobuf:"bytes,3,rep,name=from,proto3" json:"from,omitempty"` + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + From []string `protobuf:"bytes,3,rep,name=from,proto3" json:"from,omitempty"` + UseExistingChangeAddress bool `protobuf:"varint,4,opt,name=useExistingChangeAddress,proto3" json:"useExistingChangeAddress,omitempty"` } func (x *CreateUnsignedTransactionsRequest) Reset() { @@ -247,6 +253,13 @@ func (x *CreateUnsignedTransactionsRequest) GetFrom() []string { return nil } +func (x *CreateUnsignedTransactionsRequest) GetUseExistingChangeAddress() bool { + if x != nil { + return x.UseExistingChangeAddress + } + return false +} + type CreateUnsignedTransactionsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -986,10 +999,11 @@ type SendRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - ToAddress string `protobuf:"bytes,1,opt,name=toAddress,proto3" json:"toAddress,omitempty"` - Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` - Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` - From []string `protobuf:"bytes,4,rep,name=from,proto3" json:"from,omitempty"` + ToAddress string `protobuf:"bytes,1,opt,name=toAddress,proto3" json:"toAddress,omitempty"` + Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` + Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"` + From []string `protobuf:"bytes,4,rep,name=from,proto3" json:"from,omitempty"` + UseExistingChangeAddress bool `protobuf:"varint,5,opt,name=useExistingChangeAddress,proto3" json:"useExistingChangeAddress,omitempty"` } func (x *SendRequest) Reset() { @@ -1052,6 +1066,13 @@ func (x *SendRequest) GetFrom() []string { return nil } +func (x *SendRequest) GetUseExistingChangeAddress() bool { + if x != nil { + return x.UseExistingChangeAddress + } + return false +} + type SendResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1224,155 +1245,162 @@ var file_kaspawalletd_proto_rawDesc = []byte{ 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, 0x52, 0x07, 0x70, 0x65, 0x6e, - 0x64, 0x69, 0x6e, 0x67, 0x22, 0x69, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, - 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, - 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22, - 0x58, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, - 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, - 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x22, 0x31, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, - 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x10, 0x42, 0x72, 0x6f, - 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, - 0x08, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, - 0x08, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x72, 0x61, - 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, - 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x29, 0x0a, - 0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, - 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, - 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, - 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, - 0x46, 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, - 0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, - 0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x55, 0x74, 0x78, 0x6f, - 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, - 0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6f, - 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, - 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4f, 0x75, 0x74, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, - 0x35, 0x0a, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, - 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, - 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, - 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb2, 0x01, - 0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, - 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, - 0x75, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, - 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, - 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x63, 0x72, 0x69, - 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x72, - 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, - 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, - 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, - 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, - 0x73, 0x65, 0x22, 0x3c, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x22, 0x62, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, - 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, - 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x45, 0x6e, 0x74, - 0x72, 0x69, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, - 0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, - 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x65, 0x6e, - 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, - 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, - 0x5d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, - 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, - 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, - 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e, - 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, - 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, - 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xb3, - 0x06, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, - 0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, - 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, - 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, - 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, - 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, - 0x2e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, - 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, - 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, - 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, - 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, - 0x73, 0x12, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, - 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, - 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, - 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, - 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, - 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, - 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, - 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, - 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, - 0x6e, 0x12, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, - 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, - 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, - 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, - 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, - 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, - 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, - 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61, - 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, - 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, - 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, - 0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, - 0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x33, + 0x64, 0x69, 0x6e, 0x67, 0x22, 0xa5, 0x01, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, + 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, + 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, + 0x12, 0x3a, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43, + 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x58, 0x0a, 0x22, + 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, + 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31, + 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, + 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, + 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73, + 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x74, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x29, 0x0a, 0x11, 0x42, 0x72, + 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, + 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, + 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x08, + 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14, + 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69, + 0x6e, 0x64, 0x65, 0x78, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, + 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70, + 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6b, 0x61, 0x73, + 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, + 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x09, + 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x55, + 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, + 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, + 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb2, 0x01, 0x0a, 0x09, 0x55, + 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x12, 0x47, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, + 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70, + 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, + 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, + 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x6c, 0x6f, + 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, + 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12, + 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x22, + 0x3c, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, + 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x62, 0x0a, + 0x21, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, + 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, + 0x74, 0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, + 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, + 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, + 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, + 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x03, 0x28, + 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x3a, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, + 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, + 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, + 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x5d, 0x0a, 0x0b, 0x53, 0x69, 0x67, + 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, + 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, + 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, + 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xb3, 0x06, 0x0a, 0x0c, 0x6b, 0x61, 0x73, + 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, + 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, + 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, + 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x19, + 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, + 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2e, 0x2e, 0x6b, 0x61, 0x73, 0x70, + 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, + 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, + 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, + 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, + 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, + 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, + 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x2e, 0x6b, 0x61, + 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6b, + 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, + 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, + 0x73, 0x12, 0x22, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, + 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0a, + 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, + 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, + 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, + 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x6b, 0x61, + 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, + 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6b, 0x61, 0x73, + 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, + 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, + 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, + 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, + 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, + 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, + 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, + 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, + 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, + 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, + 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, + 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, + 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x36, + 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, + 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, + 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, + 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd.proto b/cmd/kaspawallet/daemon/pb/kaspawalletd.proto index defbe51d3..120a0ebe3 100644 --- a/cmd/kaspawallet/daemon/pb/kaspawalletd.proto +++ b/cmd/kaspawallet/daemon/pb/kaspawalletd.proto @@ -36,6 +36,7 @@ message CreateUnsignedTransactionsRequest { string address = 1; uint64 amount = 2; repeated string from = 3; + bool useExistingChangeAddress = 4; } message CreateUnsignedTransactionsResponse { @@ -57,8 +58,8 @@ message NewAddressResponse { } message BroadcastRequest { - bool isDomain = 1; - repeated bytes transactions = 2; + bool isDomain = 1; + repeated bytes transactions = 2; } message BroadcastResponse { @@ -107,6 +108,7 @@ message SendRequest{ uint64 amount = 2; string password = 3; repeated string from = 4; + bool useExistingChangeAddress = 5; } message SendResponse{ diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go b/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go index ec5b02f01..726cd3cf6 100644 --- a/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go +++ b/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go @@ -1,8 +1,4 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. -// versions: -// - protoc-gen-go-grpc v1.2.0 -// - protoc v3.17.2 -// source: kaspawalletd.proto package pb @@ -15,8 +11,7 @@ import ( // This is a compile-time assertion to ensure that this generated file // is compatible with the grpc package it is being compiled against. -// Requires gRPC-Go v1.32.0 or later. -const _ = grpc.SupportPackageIsVersion7 +const _ = grpc.SupportPackageIsVersion6 // KaspawalletdClient is the client API for Kaspawalletd service. // @@ -146,44 +141,37 @@ type KaspawalletdServer interface { type UnimplementedKaspawalletdServer struct { } -func (UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) { +func (*UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetBalance not implemented") } -func (UnimplementedKaspawalletdServer) GetExternalSpendableUTXOs(context.Context, *GetExternalSpendableUTXOsRequest) (*GetExternalSpendableUTXOsResponse, error) { +func (*UnimplementedKaspawalletdServer) GetExternalSpendableUTXOs(context.Context, *GetExternalSpendableUTXOsRequest) (*GetExternalSpendableUTXOsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetExternalSpendableUTXOs not implemented") } -func (UnimplementedKaspawalletdServer) CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error) { +func (*UnimplementedKaspawalletdServer) CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransactions not implemented") } -func (UnimplementedKaspawalletdServer) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) { +func (*UnimplementedKaspawalletdServer) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ShowAddresses not implemented") } -func (UnimplementedKaspawalletdServer) NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error) { +func (*UnimplementedKaspawalletdServer) NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method NewAddress not implemented") } -func (UnimplementedKaspawalletdServer) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) { +func (*UnimplementedKaspawalletdServer) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Shutdown not implemented") } -func (UnimplementedKaspawalletdServer) Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error) { +func (*UnimplementedKaspawalletdServer) Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Broadcast not implemented") } -func (UnimplementedKaspawalletdServer) Send(context.Context, *SendRequest) (*SendResponse, error) { +func (*UnimplementedKaspawalletdServer) Send(context.Context, *SendRequest) (*SendResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Send not implemented") } -func (UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*SignResponse, error) { +func (*UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*SignResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented") } -func (UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {} +func (*UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {} -// UnsafeKaspawalletdServer may be embedded to opt out of forward compatibility for this service. -// Use of this interface is not recommended, as added methods to KaspawalletdServer will -// result in compilation errors. -type UnsafeKaspawalletdServer interface { - mustEmbedUnimplementedKaspawalletdServer() -} - -func RegisterKaspawalletdServer(s grpc.ServiceRegistrar, srv KaspawalletdServer) { - s.RegisterService(&Kaspawalletd_ServiceDesc, srv) +func RegisterKaspawalletdServer(s *grpc.Server, srv KaspawalletdServer) { + s.RegisterService(&_Kaspawalletd_serviceDesc, srv) } func _Kaspawalletd_GetBalance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { @@ -348,10 +336,7 @@ func _Kaspawalletd_Sign_Handler(srv interface{}, ctx context.Context, dec func(i return interceptor(ctx, in, info, handler) } -// Kaspawalletd_ServiceDesc is the grpc.ServiceDesc for Kaspawalletd service. -// It's only intended for direct use with grpc.RegisterService, -// and not to be introspected or modified (even as a copy) -var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{ +var _Kaspawalletd_serviceDesc = grpc.ServiceDesc{ ServiceName: "kaspawalletd.kaspawalletd", HandlerType: (*KaspawalletdServer)(nil), Methods: []grpc.MethodDesc{ diff --git a/cmd/kaspawallet/daemon/server/address.go b/cmd/kaspawallet/daemon/server/address.go index fa49149b2..d26ca3107 100644 --- a/cmd/kaspawallet/daemon/server/address.go +++ b/cmd/kaspawallet/daemon/server/address.go @@ -10,19 +10,24 @@ import ( "github.com/pkg/errors" ) -func (s *server) changeAddress() (util.Address, *walletAddress, error) { - err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1) - if err != nil { - return nil, nil, err - } +func (s *server) changeAddress(useFirst bool) (util.Address, *walletAddress, error) { + internalIndex := uint32(0) + if !useFirst { + err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1) + if err != nil { + return nil, nil, err + } - err = s.keysFile.Save() - if err != nil { - return nil, nil, err + err = s.keysFile.Save() + if err != nil { + return nil, nil, err + } + + internalIndex = s.keysFile.LastUsedInternalIndex() } walletAddr := &walletAddress{ - index: s.keysFile.LastUsedInternalIndex(), + index: internalIndex, cosignerIndex: s.keysFile.CosignerIndex, keyChain: libkaspawallet.InternalKeychain, } diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index dca075570..212812b4d 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -22,7 +22,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat s.lock.Lock() defer s.lock.Unlock() - unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.From) + unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.From, request.UseExistingChangeAddress) if err != nil { return nil, err } @@ -30,7 +30,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil } -func (s *server) createUnsignedTransactions(address string, amount uint64, fromAddressesString []string) ([][]byte, error) { +func (s *server) createUnsignedTransactions(address string, amount uint64, fromAddressesString []string, useExistingChangeAddress bool) ([][]byte, error) { if !s.isSynced() { return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport()) } @@ -61,7 +61,7 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, fromA return nil, err } - changeAddress, changeWalletAddress, err := s.changeAddress() + changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress) if err != nil { return nil, err } diff --git a/cmd/kaspawallet/daemon/server/send.go b/cmd/kaspawallet/daemon/server/send.go index 5260714de..a52e42005 100644 --- a/cmd/kaspawallet/daemon/server/send.go +++ b/cmd/kaspawallet/daemon/server/send.go @@ -10,7 +10,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo s.lock.Lock() defer s.lock.Unlock() - unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.From) + unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.From, request.UseExistingChangeAddress) if err != nil { return nil, err } diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go index 79f24c69a..9f4616c65 100644 --- a/cmd/kaspawallet/send.go +++ b/cmd/kaspawallet/send.go @@ -35,9 +35,10 @@ func send(conf *sendConfig) error { createUnsignedTransactionsResponse, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{ - From: conf.FromAddresses, - Address: conf.ToAddress, - Amount: sendAmountSompi, + From: conf.FromAddresses, + Address: conf.ToAddress, + Amount: sendAmountSompi, + UseExistingChangeAddress: conf.UseExistingChangeAddress, }) if err != nil { return err From 3286a7d0105be5e99e3b7d3ca0044dd518532c6b Mon Sep 17 00:00:00 2001 From: Ori Newman Date: Wed, 24 Aug 2022 13:32:39 +0300 Subject: [PATCH 11/11] Call update pruning point if required on resolve virtual (#2129) * Call UpdatePruningPointIfRequired when resolving virtual * Don't sanity check pruning point UTXO set if it's genesis * Add UpdatePruningPointIfRequired on init --- domain/consensus/consensus.go | 5 +++++ domain/consensus/factory.go | 7 +++++++ .../pruningmanager/pruningmanager.go | 2 +- infrastructure/network/rpcclient/rpc_ban.go | 20 +++++++++++++++++++ infrastructure/network/rpcclient/rpc_unban.go | 20 +++++++++++++++++++ 5 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 infrastructure/network/rpcclient/rpc_ban.go create mode 100644 infrastructure/network/rpcclient/rpc_unban.go diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 20d02be9c..a615869f6 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -972,6 +972,11 @@ func (s *consensus) resolveVirtualChunkNoLock(maxBlocksToResolve uint64) (*exter return nil, false, err } + err = s.pruningManager.UpdatePruningPointIfRequired() + if err != nil { + return nil, false, err + } + err = s.sendVirtualChangedEvent(virtualChangeSet, true) if err != nil { return nil, false, err diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index 3b92ecb93..d53e49bb4 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -540,6 +540,8 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas return nil, false, err } + // If the virtual moved before shutdown but the pruning point hasn't, we + // move it if needed. stagingArea := model.NewStagingArea() err = pruningManager.UpdatePruningPointByVirtual(stagingArea) if err != nil { @@ -551,6 +553,11 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas return nil, false, err } + err = pruningManager.UpdatePruningPointIfRequired() + if err != nil { + return nil, false, err + } + return c, false, nil } diff --git a/domain/consensus/processes/pruningmanager/pruningmanager.go b/domain/consensus/processes/pruningmanager/pruningmanager.go index e0b7ca4c3..0d81dcfcc 100644 --- a/domain/consensus/processes/pruningmanager/pruningmanager.go +++ b/domain/consensus/processes/pruningmanager/pruningmanager.go @@ -900,7 +900,7 @@ func (pm *pruningManager) updatePruningPoint() error { if err != nil { return err } - if pm.shouldSanityCheckPruningUTXOSet { + if pm.shouldSanityCheckPruningUTXOSet && !pruningPoint.Equal(pm.genesisHash) { err = pm.validateUTXOSetFitsCommitment(stagingArea, pruningPoint) if err != nil { return err diff --git a/infrastructure/network/rpcclient/rpc_ban.go b/infrastructure/network/rpcclient/rpc_ban.go new file mode 100644 index 000000000..66499a9e7 --- /dev/null +++ b/infrastructure/network/rpcclient/rpc_ban.go @@ -0,0 +1,20 @@ +package rpcclient + +import "github.com/kaspanet/kaspad/app/appmessage" + +// Ban sends an RPC request respective to the function's name and returns the RPC server's response +func (c *RPCClient) Ban(ip string) (*appmessage.BanResponseMessage, error) { + err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewBanRequestMessage(ip)) + if err != nil { + return nil, err + } + response, err := c.route(appmessage.CmdBanRequestMessage).DequeueWithTimeout(c.timeout) + if err != nil { + return nil, err + } + banResponse := response.(*appmessage.BanResponseMessage) + if banResponse.Error != nil { + return nil, c.convertRPCError(banResponse.Error) + } + return banResponse, nil +} diff --git a/infrastructure/network/rpcclient/rpc_unban.go b/infrastructure/network/rpcclient/rpc_unban.go new file mode 100644 index 000000000..6e82ebe65 --- /dev/null +++ b/infrastructure/network/rpcclient/rpc_unban.go @@ -0,0 +1,20 @@ +package rpcclient + +import "github.com/kaspanet/kaspad/app/appmessage" + +// Unban sends an RPC request respective to the function's name and returns the RPC server's response +func (c *RPCClient) Unban(ip string) (*appmessage.UnbanResponseMessage, error) { + err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewUnbanRequestMessage(ip)) + if err != nil { + return nil, err + } + response, err := c.route(appmessage.CmdUnbanRequestMessage).DequeueWithTimeout(c.timeout) + if err != nil { + return nil, err + } + unbanResponse := response.(*appmessage.UnbanResponseMessage) + if unbanResponse.Error != nil { + return nil, c.convertRPCError(unbanResponse.Error) + } + return unbanResponse, nil +}