Merge branch 'dev' into broadcast-timeout

This commit is contained in:
Michael Sutton 2022-08-28 12:20:26 +03:00 committed by GitHub
commit 9040223f77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 886 additions and 615 deletions

View File

@ -102,7 +102,7 @@ func (flow *handleRelayedTransactionsFlow) requestInvTransactions(
func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool { func (flow *handleRelayedTransactionsFlow) isKnownTransaction(txID *externalapi.DomainTransactionID) bool {
// Ask the transaction memory pool if the transaction is known // Ask the transaction memory pool if the transaction is known
// to it in any form (main pool or orphan). // 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 return true
} }

View File

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

View File

@ -223,18 +223,9 @@ func (m *Manager) notifyVirtualSelectedParentChainChanged(virtualChangeSet *exte
onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualSelectedParentChainChanged") onEnd := logger.LogAndMeasureExecutionTime(log, "RPCManager.NotifyVirtualSelectedParentChainChanged")
defer onEnd() defer onEnd()
listenersThatPropagateSelectedParentChanged := hasListeners, includeAcceptedTransactionIDs := m.context.NotificationManager.HasListenersThatPropagateVirtualSelectedParentChainChanged()
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
}
}
if hasListeners {
notification, err := m.context.ConvertVirtualSelectedParentChainChangesToChainChangedNotificationMessage( notification, err := m.context.ConvertVirtualSelectedParentChainChangesToChainChangedNotificationMessage(
virtualChangeSet.VirtualSelectedParentChainChanges, includeAcceptedTransactionIDs) virtualChangeSet.VirtualSelectedParentChainChanges, includeAcceptedTransactionIDs)
if err != nil { if err != nil {

View File

@ -5,6 +5,7 @@ import (
"github.com/kaspanet/kaspad/domain/dagconfig" "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/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/appmessage"
@ -141,16 +142,29 @@ func (nm *NotificationManager) NotifyVirtualSelectedParentChainChanged(
return nil return nil
} }
// AllListenersThatPropagateVirtualSelectedParentChainChanged returns true if there's any listener that is // HasListenersThatPropagateVirtualSelectedParentChainChanged returns whether there's any listener that is
// subscribed to VirtualSelectedParentChainChanged notifications. // subscribed to VirtualSelectedParentChainChanged notifications as well as checks if any such listener requested
func (nm *NotificationManager) AllListenersThatPropagateVirtualSelectedParentChainChanged() []*NotificationListener { // to include AcceptedTransactionIDs.
var listenersThatPropagate []*NotificationListener func (nm *NotificationManager) HasListenersThatPropagateVirtualSelectedParentChainChanged() (hasListeners, hasListenersThatRequireAcceptedTransactionIDs bool) {
nm.RLock()
defer nm.RUnlock()
hasListeners = false
hasListenersThatRequireAcceptedTransactionIDs = false
for _, listener := range nm.listeners { for _, listener := range nm.listeners {
if listener.propagateVirtualSelectedParentChainChangedNotifications { 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 // NotifyFinalityConflict notifies the notification manager that there's a finality conflict in the DAG
@ -337,7 +351,11 @@ func (nl *NotificationListener) PropagateFinalityConflictResolvedNotifications()
// to the remote listener for the given addresses. Subsequent calls instruct the listener to // 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 // send UTXOs changed notifications for those addresses along with the old ones. Duplicate addresses
// are ignored. // 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 { if !nl.propagateUTXOsChangedNotifications {
nl.propagateUTXOsChangedNotifications = true nl.propagateUTXOsChangedNotifications = true
nl.propagateUTXOsChangedNotificationAddresses = nl.propagateUTXOsChangedNotificationAddresses =
@ -352,7 +370,11 @@ func (nl *NotificationListener) PropagateUTXOsChangedNotifications(addresses []*
// StopPropagatingUTXOsChangedNotifications instructs the listener to stop sending UTXOs // StopPropagatingUTXOsChangedNotifications instructs the listener to stop sending UTXOs
// changed notifications to the remote listener for the given addresses. Addresses for which // changed notifications to the remote listener for the given addresses. Addresses for which
// notifications are not currently sent are ignored. // 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 { if !nl.propagateUTXOsChangedNotifications {
return return
} }
@ -421,7 +443,7 @@ func (nl *NotificationListener) convertUTXOChangesToUTXOsChangedNotification(
} }
func (nl *NotificationListener) scriptPubKeyStringToAddressString(scriptPublicKeyString utxoindex.ScriptPublicKeyString) (string, error) { 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 // ignore error because it is often returned when the script is of unknown type
scriptType, address, err := txscript.ExtractScriptPubKeyAddress(scriptPubKey, nl.params) scriptType, address, err := txscript.ExtractScriptPubKeyAddress(scriptPubKey, nl.params)

View File

@ -32,22 +32,6 @@ func ConvertUTXOOutpointEntryPairsToUTXOsByAddressesEntries(address string, pair
return utxosByAddressesEntries 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 // ConvertAddressStringsToUTXOsChangedNotificationAddresses converts address strings
// to UTXOsChangedNotificationAddresses // to UTXOsChangedNotificationAddresses
func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses( func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses(
@ -63,7 +47,7 @@ func (ctx *Context) ConvertAddressStringsToUTXOsChangedNotificationAddresses(
if err != nil { if err != nil {
return nil, errors.Errorf("Could not create a scriptPublicKey for address '%s': %s", addressString, err) 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{ addresses[i] = &UTXOsChangedNotificationAddress{
Address: addressString, Address: addressString,
ScriptPublicKeyString: scriptPublicKeyString, ScriptPublicKeyString: scriptPublicKeyString,

View File

@ -16,7 +16,7 @@ func HandleGetInfo(context *rpccontext.Context, _ *router.Router, _ appmessage.M
response := appmessage.NewGetInfoResponseMessage( response := appmessage.NewGetInfoResponseMessage(
context.NetAdapter.ID().String(), context.NetAdapter.ID().String(),
uint64(context.Domain.MiningManager().TransactionCount()), uint64(context.Domain.MiningManager().TransactionCount(true, false)),
version.Version(), version.Version(),
context.Config.UTXOIndex, context.Config.UTXOIndex,
context.ProtocolManager.Context().HasPeers() && isNearlySynced, context.ProtocolManager.Context().HasPeers() && isNearlySynced,

View File

@ -12,30 +12,10 @@ func HandleGetMempoolEntries(context *rpccontext.Context, _ *router.Router, requ
entries := make([]*appmessage.MempoolEntry, 0) entries := make([]*appmessage.MempoolEntry, 0)
transactionPoolTransactions, orphanPoolTransactions := context.Domain.MiningManager().AllTransactions(!getMempoolEntriesRequest.FilterTransactionPool, getMempoolEntriesRequest.IncludeOrphanPool)
if !getMempoolEntriesRequest.FilterTransactionPool { if !getMempoolEntriesRequest.FilterTransactionPool {
transactionPoolEntries, err := getTransactionPoolMempoolEntries(context) for _, transaction := range transactionPoolTransactions {
if err != nil {
return nil, err
}
entries = append(entries, transactionPoolEntries...)
}
if getMempoolEntriesRequest.IncludeOrphanPool {
orphanPoolEntries, err := getOrphanPoolMempoolEntries(context)
if err != nil {
return nil, err
}
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) rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil { if err != nil {
@ -47,23 +27,21 @@ func getTransactionPoolMempoolEntries(context *rpccontext.Context) ([]*appmessag
IsOrphan: false, IsOrphan: false,
}) })
} }
return entries, nil
} }
if getMempoolEntriesRequest.IncludeOrphanPool {
func getOrphanPoolMempoolEntries(context *rpccontext.Context) ([]*appmessage.MempoolEntry, error) { for _, transaction := range orphanPoolTransactions {
orphanTransactions := context.Domain.MiningManager().AllOrphanTransactions() rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
entries := make([]*appmessage.MempoolEntry, 0, len(orphanTransactions))
for _, orphanTransaction := range orphanTransactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(orphanTransaction)
err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil) err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
entries = append(entries, &appmessage.MempoolEntry{ entries = append(entries, &appmessage.MempoolEntry{
Fee: orphanTransaction.Fee, Fee: transaction.Fee,
Transaction: rpcTransaction, Transaction: rpcTransaction,
IsOrphan: true, IsOrphan: true,
}) })
} }
return entries, nil }
return appmessage.NewGetMempoolEntriesResponseMessage(entries), nil
} }

View File

@ -1,14 +1,10 @@
package rpchandlers package rpchandlers
import ( import (
"errors"
"github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/app/rpc/rpccontext" "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/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
"github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util"
) )
@ -20,125 +16,101 @@ func HandleGetMempoolEntriesByAddresses(context *rpccontext.Context, _ *router.R
mempoolEntriesByAddresses := make([]*appmessage.MempoolEntryByAddress, 0) mempoolEntriesByAddresses := make([]*appmessage.MempoolEntryByAddress, 0)
if !getMempoolEntriesByAddressesRequest.FilterTransactionPool { sendingInTransactionPool, receivingInTransactionPool, sendingInOrphanPool, receivingInOrphanPool, err := context.Domain.MiningManager().GetTransactionsByAddresses(!getMempoolEntriesByAddressesRequest.FilterTransactionPool, getMempoolEntriesByAddressesRequest.IncludeOrphanPool)
transactionPoolTransactions := context.Domain.MiningManager().AllTransactions()
transactionPoolEntriesByAddresses, err := extractMempoolEntriesByAddressesFromTransactions(
context,
getMempoolEntriesByAddressesRequest.Addresses,
transactionPoolTransactions,
false,
)
if err != nil { if err != nil {
rpcError := &appmessage.RPCError{}
if !errors.As(err, &rpcError) {
return nil, err return nil, err
} }
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = rpcError
return errorMessage, nil
}
mempoolEntriesByAddresses = append(mempoolEntriesByAddresses, transactionPoolEntriesByAddresses...)
}
if getMempoolEntriesByAddressesRequest.IncludeOrphanPool { for _, addressString := range getMempoolEntriesByAddressesRequest.Addresses {
orphanPoolTransactions := context.Domain.MiningManager().AllOrphanTransactions() address, err := util.DecodeAddress(addressString, context.Config.NetParams().Prefix)
orphanPoolEntriesByAddress, err := extractMempoolEntriesByAddressesFromTransactions(
context,
getMempoolEntriesByAddressesRequest.Addresses,
orphanPoolTransactions,
true,
)
if err != nil { if err != nil {
rpcError := &appmessage.RPCError{} errorMessage := &appmessage.GetMempoolEntriesByAddressesResponseMessage{}
if !errors.As(err, &rpcError) { errorMessage.Error = appmessage.RPCErrorf("Could not decode address '%s': %s", addressString, err)
return nil, err
}
errorMessage := &appmessage.GetUTXOsByAddressesResponseMessage{}
errorMessage.Error = rpcError
return errorMessage, nil 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) sending := make([]*appmessage.MempoolEntry, 0)
receiving := make([]*appmessage.MempoolEntry, 0) receiving := make([]*appmessage.MempoolEntry, 0)
for _, transaction := range transactions { scriptPublicKey, err := txscript.PayToAddrScript(address)
if err != nil {
for i, input := range transaction.Inputs { errorMessage := &appmessage.GetMempoolEntriesByAddressesResponseMessage{}
if input.UTXOEntry == nil { errorMessage.Error = appmessage.RPCErrorf("Could not extract scriptPublicKey from address '%s': %s", addressString, err)
if !areOrphans { // Orphans can legitimately have `input.UTXOEntry == nil` return errorMessage, 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
} }
_, transactionSendingAddress, err := txscript.ExtractScriptPubKeyAddress( if !getMempoolEntriesByAddressesRequest.FilterTransactionPool {
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 { if err != nil {
return nil, err return nil, err
} }
if addressString == transactionSendingAddress.String() {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) sending = append(sending, &appmessage.MempoolEntry{
sending = append(
sending,
&appmessage.MempoolEntry{
Fee: transaction.Fee, Fee: transaction.Fee,
Transaction: rpcTransaction, Transaction: rpcTransaction,
IsOrphan: areOrphans, IsOrphan: false,
}, },
) )
break //one input is enough
}
} }
for _, output := range transaction.Outputs { if transaction, found := receivingInTransactionPool[scriptPublicKey.String()]; found {
_, transactionReceivingAddress, err := txscript.ExtractScriptPubKeyAddress( rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
output.ScriptPublicKey, err := context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
context.Config.ActiveNetParams,
)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if addressString == transactionReceivingAddress.String() {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) receiving = append(receiving, &appmessage.MempoolEntry{
receiving = append(
receiving,
&appmessage.MempoolEntry{
Fee: transaction.Fee, Fee: transaction.Fee,
Transaction: rpcTransaction, Transaction: rpcTransaction,
IsOrphan: areOrphans, IsOrphan: false,
}, },
) )
break //one output is enough
} }
} }
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,
},
)
}
}
//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 { if len(sending) > 0 || len(receiving) > 0 {
mempoolEntriesByAddresses = append( mempoolEntriesByAddresses = append(
mempoolEntriesByAddresses, mempoolEntriesByAddresses,
&appmessage.MempoolEntryByAddress{ &appmessage.MempoolEntryByAddress{
Address: addressString, Address: address.String(),
Sending: sending, Sending: sending,
Receiving: receiving, Receiving: receiving,
}, },
@ -146,6 +118,5 @@ func extractMempoolEntriesByAddressesFromTransactions(context *rpccontext.Contex
} }
} }
} return appmessage.NewGetMempoolEntriesByAddressesResponseMessage(mempoolEntriesByAddresses), nil
return mempoolEntriesByAddresses, nil
} }

View File

@ -24,14 +24,7 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques
return errorMessage, nil return errorMessage, nil
} }
if !getMempoolEntryRequest.FilterTransactionPool { mempoolTransaction, isOrphan, found := context.Domain.MiningManager().GetTransaction(transactionID, !getMempoolEntryRequest.FilterTransactionPool, getMempoolEntryRequest.IncludeOrphanPool)
transaction, found = context.Domain.MiningManager().GetTransaction(transactionID)
}
if getMempoolEntryRequest.IncludeOrphanPool && !found {
transaction, found = context.Domain.MiningManager().GetOrphanTransaction(transactionID)
isOrphan = true
}
if !found { if !found {
errorMessage := &appmessage.GetMempoolEntryResponseMessage{} errorMessage := &appmessage.GetMempoolEntryResponseMessage{}
@ -39,7 +32,7 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques
return errorMessage, nil return errorMessage, nil
} }
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) rpcTransaction := appmessage.DomainTransactionToRPCTransaction(mempoolTransaction)
err = context.PopulateTransactionWithVerboseData(rpcTransaction, nil) err = context.PopulateTransactionWithVerboseData(rpcTransaction, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -26,7 +26,7 @@ func HandleNotifyUTXOsChanged(context *rpccontext.Context, router *router.Router
if err != nil { if err != nil {
return nil, err return nil, err
} }
listener.PropagateUTXOsChangedNotifications(addresses) context.NotificationManager.PropagateUTXOsChangedNotifications(listener, addresses)
response := appmessage.NewNotifyUTXOsChangedResponseMessage() response := appmessage.NewNotifyUTXOsChangedResponseMessage()
return response, nil return response, nil

View File

@ -26,7 +26,7 @@ func HandleStopNotifyingUTXOsChanged(context *rpccontext.Context, router *router
if err != nil { if err != nil {
return nil, err return nil, err
} }
listener.StopPropagatingUTXOsChangedNotifications(addresses) context.NotificationManager.StopPropagatingUTXOsChangedNotifications(listener, addresses)
response := appmessage.NewStopNotifyingUTXOsChangedResponseMessage() response := appmessage.NewStopNotifyingUTXOsChangedResponseMessage()
return response, nil return response, nil

View File

@ -58,6 +58,7 @@ type sendConfig struct {
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"` 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"` 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"` 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 config.NetworkFlags
} }
@ -72,6 +73,7 @@ type createUnsignedTransactionConfig struct {
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"` 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"` 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"` 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 config.NetworkFlags
} }
@ -111,7 +113,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))"` 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"` Password string `long:"password" short:"p" description:"Wallet password"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"` 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"` Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
config.NetworkFlags config.NetworkFlags
} }
@ -181,7 +184,6 @@ func parseCommandLine() (subCommand string, config interface{}) {
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf) parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
_, err := parser.Parse() _, err := parser.Parse()
if err != nil { if err != nil {
var flagsErr *flags.Error var flagsErr *flags.Error
if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp { if ok := errors.As(err, &flagsErr); ok && flagsErr.Type == flags.ErrHelp {

View File

@ -25,6 +25,7 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
From: conf.FromAddresses, From: conf.FromAddresses,
Address: conf.ToAddress, Address: conf.ToAddress,
Amount: sendAmountSompi, Amount: sendAmountSompi,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -1,12 +1,13 @@
// Code generated by protoc-gen-go. DO NOT EDIT. // Code generated by protoc-gen-go. DO NOT EDIT.
// versions: // versions:
// protoc-gen-go v1.28.0 // protoc-gen-go v1.25.0
// protoc v3.17.2 // protoc v3.12.3
// source: kaspawalletd.proto // source: kaspawalletd.proto
package pb package pb
import ( import (
proto "github.com/golang/protobuf/proto"
protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl" protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect" reflect "reflect"
@ -20,6 +21,10 @@ const (
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) _ = 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 { type GetBalanceRequest struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -192,6 +197,7 @@ type CreateUnsignedTransactionsRequest struct {
Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,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"` Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
From []string `protobuf:"bytes,3,rep,name=from,proto3" json:"from,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() { func (x *CreateUnsignedTransactionsRequest) Reset() {
@ -247,6 +253,13 @@ func (x *CreateUnsignedTransactionsRequest) GetFrom() []string {
return nil return nil
} }
func (x *CreateUnsignedTransactionsRequest) GetUseExistingChangeAddress() bool {
if x != nil {
return x.UseExistingChangeAddress
}
return false
}
type CreateUnsignedTransactionsResponse struct { type CreateUnsignedTransactionsResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
@ -990,6 +1003,7 @@ type SendRequest struct {
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,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"` Password string `protobuf:"bytes,3,opt,name=password,proto3" json:"password,omitempty"`
From []string `protobuf:"bytes,4,rep,name=from,proto3" json:"from,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() { func (x *SendRequest) Reset() {
@ -1052,6 +1066,13 @@ func (x *SendRequest) GetFrom() []string {
return nil return nil
} }
func (x *SendRequest) GetUseExistingChangeAddress() bool {
if x != nil {
return x.UseExistingChangeAddress
}
return false
}
type SendResponse struct { type SendResponse struct {
state protoimpl.MessageState state protoimpl.MessageState
sizeCache protoimpl.SizeCache 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, 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, 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, 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, 0x64, 0x69, 0x6e, 0x67, 0x22, 0xa5, 0x01, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55,
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64,
0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64,
0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02,
0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x12, 0x0a, 0x04,
0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d,
0x58, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x12, 0x3a, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43,
0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x04, 0x20, 0x01,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x28, 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43,
0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x58, 0x0a, 0x22,
0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
0x74, 0x22, 0x31, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c,
0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64,
0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31,
0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52,
0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52,
0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
0x08, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,
0x08, 0x69, 0x73, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x52, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x29, 0x0a, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x69, 0x73,
0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x69, 0x73,
0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x44, 0x6f, 0x6d, 0x61, 0x69, 0x6e, 0x12, 0x22, 0x0a, 0x0c, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x0c, 0x74, 0x72,
0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x29, 0x0a, 0x11, 0x42, 0x72,
0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
0x46, 0x0a, 0x08, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77,
0x28, 0x09, 0x52, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74,
0x64, 0x12, 0x14, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x46, 0x0a, 0x08,
0x52, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x55, 0x74, 0x78, 0x6f, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x24, 0x0a, 0x0d, 0x74, 0x72, 0x61, 0x6e,
0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x79, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x0d, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x64, 0x12, 0x14,
0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6f, 0x0a, 0x05, 0x69, 0x6e, 0x64, 0x65, 0x78, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x05, 0x69,
0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6e, 0x64, 0x65, 0x78, 0x22, 0x9c, 0x01, 0x0a, 0x15, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4f, 0x75, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x18,
0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
0x35, 0x0a, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x32, 0x0a, 0x08, 0x6f, 0x75, 0x74, 0x70,
0x28, 0x0b, 0x32, 0x17, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x6f, 0x69, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x6b, 0x61, 0x73,
0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4f, 0x75, 0x74, 0x70, 0x6f, 0x69,
0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x6e, 0x74, 0x52, 0x08, 0x6f, 0x75, 0x74, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x35, 0x0a, 0x09,
0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32,
0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x17, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x55,
0x69, 0x6f, 0x6e, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x09, 0x75, 0x74, 0x78, 0x6f, 0x45, 0x6e,
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x74, 0x72, 0x79, 0x22, 0x55, 0x0a, 0x0f, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62,
0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb2, 0x01, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f,
0x0a, 0x09, 0x55, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e,
0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x12, 0x28, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x75, 0x6e, 0x74, 0x12, 0x47, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70,
0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x22, 0xb2, 0x01, 0x0a, 0x09, 0x55,
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x74, 0x78, 0x6f, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75,
0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x6e, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x12, 0x47, 0x0a, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63,
0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x4b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70,
0x01, 0x28, 0x04, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x63, 0x72, 0x69, 0x70, 0x74, 0x50,
0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x52, 0x0f, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74,
0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x50, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x4b, 0x65, 0x79, 0x12, 0x24, 0x0a, 0x0d, 0x62, 0x6c, 0x6f,
0x73, 0x65, 0x22, 0x3c, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04,
0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x52, 0x0d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x44, 0x61, 0x61, 0x53, 0x63, 0x6f, 0x72, 0x65, 0x12,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x1e, 0x0a, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x18, 0x04, 0x20,
0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, 0x73, 0x43, 0x6f, 0x69, 0x6e, 0x62, 0x61, 0x73, 0x65, 0x22,
0x22, 0x62, 0x0a, 0x21, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x3c, 0x0a, 0x20, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70,
0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01,
0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x62, 0x0a,
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x21, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e,
0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x45, 0x6e, 0x74, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
0x72, 0x69, 0x65, 0x73, 0x22, 0x73, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65, 0x73, 0x18, 0x01, 0x20,
0x65, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x03, 0x28, 0x0b, 0x32, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65,
0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x74, 0x64, 0x2e, 0x55, 0x74, 0x78, 0x6f, 0x73, 0x42, 0x79, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x73, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x07, 0x45, 0x6e, 0x74, 0x72, 0x69, 0x65,
0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x22, 0xaf, 0x01, 0x0a, 0x0b, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01,
0x73, 0x77, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x74, 0x6f, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12,
0x03, 0x28, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52,
0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x44, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x6f, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77,
0x5d, 0x0a, 0x0b, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x6f, 0x72, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x18, 0x04, 0x20, 0x03, 0x28,
0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x09, 0x52, 0x04, 0x66, 0x72, 0x6f, 0x6d, 0x12, 0x3a, 0x0a, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78,
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72,
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x65, 0x73, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x18, 0x75, 0x73, 0x65, 0x45, 0x78,
0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x69, 0x73, 0x74, 0x69, 0x6e, 0x67, 0x43, 0x68, 0x61, 0x6e, 0x67, 0x65, 0x41, 0x64, 0x64, 0x72,
0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e, 0x65, 0x73, 0x73, 0x22, 0x24, 0x0a, 0x0c, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x18, 0x01, 0x20, 0x03,
0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x28, 0x09, 0x52, 0x05, 0x74, 0x78, 0x49, 0x44, 0x73, 0x22, 0x5d, 0x0a, 0x0b, 0x53, 0x69, 0x67,
0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69,
0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xb3, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73,
0x06, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1a, 0x0a, 0x08,
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08,
0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x22, 0x3e, 0x0a, 0x0c, 0x53, 0x69, 0x67, 0x6e,
0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e,
0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
0x22, 0x00, 0x12, 0x7e, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x20, 0x03, 0x28, 0x0c, 0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e,
0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x32, 0xb3, 0x06, 0x0a, 0x0c, 0x6b, 0x61, 0x73,
0x2e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74,
0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63,
0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61,
0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e,
0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x19,
0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64,
0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2e, 0x2e, 0x6b, 0x61, 0x73, 0x70,
0x73, 0x12, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65,
0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58,
0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70,
0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65,
0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58,
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a,
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54,
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x22, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x2e, 0x6b, 0x61,
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6b,
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61,
0x22, 0x00, 0x12, 0x51, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65,
0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x73, 0x12, 0x22, 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, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
0x6e, 0x12, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0a,
0x1a, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73,
0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61,
0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64,
0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x6b, 0x61,
0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64,
0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 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, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f,
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09,
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70,
0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61,
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61,
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04,
0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c,
0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53,
0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a,
0x6f, 0x74, 0x6f, 0x33, 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 ( var (

View File

@ -36,6 +36,7 @@ message CreateUnsignedTransactionsRequest {
string address = 1; string address = 1;
uint64 amount = 2; uint64 amount = 2;
repeated string from = 3; repeated string from = 3;
bool useExistingChangeAddress = 4;
} }
message CreateUnsignedTransactionsResponse { message CreateUnsignedTransactionsResponse {
@ -107,6 +108,7 @@ message SendRequest{
uint64 amount = 2; uint64 amount = 2;
string password = 3; string password = 3;
repeated string from = 4; repeated string from = 4;
bool useExistingChangeAddress = 5;
} }
message SendResponse{ message SendResponse{

View File

@ -1,8 +1,4 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT. // 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 package pb
@ -15,8 +11,7 @@ import (
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against. // is compatible with the grpc package it is being compiled against.
// Requires gRPC-Go v1.32.0 or later. const _ = grpc.SupportPackageIsVersion6
const _ = grpc.SupportPackageIsVersion7
// KaspawalletdClient is the client API for Kaspawalletd service. // KaspawalletdClient is the client API for Kaspawalletd service.
// //
@ -146,44 +141,37 @@ type KaspawalletdServer interface {
type UnimplementedKaspawalletdServer struct { 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") 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") 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") 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") 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") 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") 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") 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") 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") 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. func RegisterKaspawalletdServer(s *grpc.Server, srv KaspawalletdServer) {
// Use of this interface is not recommended, as added methods to KaspawalletdServer will s.RegisterService(&_Kaspawalletd_serviceDesc, srv)
// result in compilation errors.
type UnsafeKaspawalletdServer interface {
mustEmbedUnimplementedKaspawalletdServer()
}
func RegisterKaspawalletdServer(s grpc.ServiceRegistrar, 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) { 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) return interceptor(ctx, in, info, handler)
} }
// Kaspawalletd_ServiceDesc is the grpc.ServiceDesc for Kaspawalletd service. var _Kaspawalletd_serviceDesc = grpc.ServiceDesc{
// 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{
ServiceName: "kaspawalletd.kaspawalletd", ServiceName: "kaspawalletd.kaspawalletd",
HandlerType: (*KaspawalletdServer)(nil), HandlerType: (*KaspawalletdServer)(nil),
Methods: []grpc.MethodDesc{ Methods: []grpc.MethodDesc{

View File

@ -10,7 +10,9 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
) )
func (s *server) changeAddress() (util.Address, *walletAddress, error) { func (s *server) changeAddress(useFirst bool) (util.Address, *walletAddress, error) {
internalIndex := uint32(0)
if !useFirst {
err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1) err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -21,8 +23,11 @@ func (s *server) changeAddress() (util.Address, *walletAddress, error) {
return nil, nil, err return nil, nil, err
} }
internalIndex = s.keysFile.LastUsedInternalIndex()
}
walletAddr := &walletAddress{ walletAddr := &walletAddress{
index: s.keysFile.LastUsedInternalIndex(), index: internalIndex,
cosignerIndex: s.keysFile.CosignerIndex, cosignerIndex: s.keysFile.CosignerIndex,
keyChain: libkaspawallet.InternalKeychain, keyChain: libkaspawallet.InternalKeychain,
} }

View File

@ -3,24 +3,26 @@ package server
import ( import (
"context" "context"
"fmt" "fmt"
"time"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/util" "github.com/kaspanet/kaspad/util"
"github.com/pkg/errors" "github.com/pkg/errors"
"golang.org/x/exp/slices" "golang.org/x/exp/slices"
"time"
) )
// TODO: Implement a better fee estimation mechanism // TODO: Implement a better fee estimation mechanism
const feePerInput = 10000 const feePerInput = 10000
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) ( func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
*pb.CreateUnsignedTransactionsResponse, error) { *pb.CreateUnsignedTransactionsResponse, error,
) {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() 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 { if err != nil {
return nil, err return nil, err
} }
@ -28,17 +30,19 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil 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() { if !s.isSynced() {
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport()) 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 { if err != nil {
return nil, err return nil, err
} }
toAddress, err := util.DecodeAddress(address, s.params.Prefix) err = s.refreshUTXOs()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -57,7 +61,7 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, fromA
return nil, err return nil, err
} }
changeAddress, changeWalletAddress, err := s.changeAddress() changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress)
if err != nil { if err != nil {
return nil, err 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) ( 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{} selectedUTXOs = []*libkaspawallet.UTXO{}
totalValue := uint64(0) totalValue := uint64(0)

View File

@ -1,15 +1,26 @@
package server package server
import ( import (
"time"
"github.com/kaspanet/kaspad/domain/dagconfig" "github.com/kaspanet/kaspad/domain/dagconfig"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient" "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) rpcAddress, err := params.NormalizeRPCServerAddress(rpcServer)
if err != nil { if err != nil {
return nil, err 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
} }

View File

@ -10,7 +10,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() 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 { if err != nil {
return nil, err return nil, err
} }

View File

@ -45,7 +45,7 @@ type server struct {
} }
// Start starts the kaspawalletd server // 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) initLog(defaultLogFile, defaultErrLogFile)
defer panics.HandlePanic(log, "MAIN", nil) 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("Listening to TCP on %s", listen)
log.Infof("Connecting to a node at %s...", rpcServer) log.Infof("Connecting to a node at %s...", rpcServer)
rpcClient, err := connectToRPC(params, rpcServer) rpcClient, err := connectToRPC(params, rpcServer, timeout)
if err != nil { if err != nil {
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer)) return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
} }

View File

@ -38,6 +38,7 @@ func send(conf *sendConfig) error {
From: conf.FromAddresses, From: conf.FromAddresses,
Address: conf.ToAddress, Address: conf.ToAddress,
Amount: sendAmountSompi, Amount: sendAmountSompi,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
}) })
if err != nil { if err != nil {
return err return err

View File

@ -3,5 +3,5 @@ package main
import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server" import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
func startDaemon(conf *startDaemonConfig) error { 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)
} }

View File

@ -64,6 +64,12 @@ type consensus struct {
virtualNotUpdated bool 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 { func (s *consensus) ValidateAndInsertBlockWithTrustedData(block *externalapi.BlockWithTrustedData, validateUTXO bool) error {
s.lock.Lock() s.lock.Lock()
defer s.lock.Unlock() defer s.lock.Unlock()
@ -198,15 +204,32 @@ func (s *consensus) ValidateAndInsertBlock(block *externalapi.DomainBlock, updat
if updateVirtual { if updateVirtual {
s.lock.Lock() s.lock.Lock()
if s.virtualNotUpdated { if s.virtualNotUpdated {
// 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() s.lock.Unlock()
err := s.ResolveVirtual(nil)
if err != nil { if err != nil {
return err return err
} }
return s.validateAndInsertBlockWithLock(block, updateVirtual) return nil
}
// Unlock to allow other threads to enter consensus
s.lock.Unlock()
// Lock for the next iteration
s.lock.Lock()
}
} }
defer s.lock.Unlock()
_, err := s.validateAndInsertBlockNoLock(block, updateVirtual) _, err := s.validateAndInsertBlockNoLock(block, updateVirtual)
s.lock.Unlock()
if err != nil { if err != nil {
return err return err
} }
@ -912,11 +935,7 @@ func (s *consensus) ResolveVirtual(progressReportCallback func(uint64, uint64))
progressReportCallback(virtualDAAScoreStart, virtualDAAScore) progressReportCallback(virtualDAAScoreStart, virtualDAAScore)
} }
// In order to prevent a situation that the consensus lock is held for too much time, we _, isCompletelyResolved, err := s.resolveVirtualChunkWithLock(virtualResolveChunk)
// 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)
if err != nil { if err != nil {
return err return err
} }
@ -953,6 +972,11 @@ func (s *consensus) resolveVirtualChunkNoLock(maxBlocksToResolve uint64) (*exter
return nil, false, err return nil, false, err
} }
err = s.pruningManager.UpdatePruningPointIfRequired()
if err != nil {
return nil, false, err
}
err = s.sendVirtualChangedEvent(virtualChangeSet, true) err = s.sendVirtualChangedEvent(virtualChangeSet, true)
if err != nil { if err != nil {
return nil, false, err return nil, false, err

View File

@ -540,6 +540,8 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
return nil, false, err return nil, false, err
} }
// If the virtual moved before shutdown but the pruning point hasn't, we
// move it if needed.
stagingArea := model.NewStagingArea() stagingArea := model.NewStagingArea()
err = pruningManager.UpdatePruningPointByVirtual(stagingArea) err = pruningManager.UpdatePruningPointByVirtual(stagingArea)
if err != nil { if err != nil {
@ -551,6 +553,11 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
return nil, false, err return nil, false, err
} }
err = pruningManager.UpdatePruningPointIfRequired()
if err != nil {
return nil, false, err
}
return c, false, nil return c, false, nil
} }

View File

@ -2,6 +2,7 @@ package externalapi
import ( import (
"bytes" "bytes"
"encoding/binary"
"fmt" "fmt"
"github.com/pkg/errors" "github.com/pkg/errors"
@ -242,6 +243,23 @@ func (spk *ScriptPublicKey) Equal(other *ScriptPublicKey) bool {
return bytes.Equal(spk.Script, other.Script) 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 // DomainTransactionOutput represents a Kaspad transaction output
type DomainTransactionOutput struct { type DomainTransactionOutput struct {
Value uint64 Value uint64

View File

@ -772,78 +772,41 @@ func (pm *pruningManager) calculateDiffBetweenPreviousAndCurrentPruningPoints(st
if err != nil { if err != nil {
return nil, err 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 { if err != nil {
return nil, err return nil, err
} }
previousPruningGhostDAG, err := pm.ghostdagDataStore.Get(pm.databaseContext, stagingArea, previousPruningHash, false) defer iterator.Close()
for ok := iterator.First(); ok; ok = iterator.Next() {
child, err := iterator.Get()
if err != nil { if err != nil {
return nil, err 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 return utxoDiff.ToImmutable(), err
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())
} }
// finalityScore is the number of finality intervals passed since // finalityScore is the number of finality intervals passed since
@ -937,7 +900,7 @@ func (pm *pruningManager) updatePruningPoint() error {
if err != nil { if err != nil {
return err return err
} }
if pm.shouldSanityCheckPruningUTXOSet { if pm.shouldSanityCheckPruningUTXOSet && !pruningPoint.Equal(pm.genesisHash) {
err = pm.validateUTXOSetFitsCommitment(stagingArea, pruningPoint) err = pm.validateUTXOSetFitsCommitment(stagingArea, pruningPoint)
if err != nil { if err != nil {
return err return err

View File

@ -1,10 +1,80 @@
package hashes package hashes
import ( import (
"fmt"
"math/rand" "math/rand"
"testing" "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) { func BenchmarkNewBlockHashWriterSmall(b *testing.B) {
r := rand.New(rand.NewSource(0)) r := rand.New(rand.NewSource(0))
var someBytes [32]byte var someBytes [32]byte

View File

@ -214,7 +214,8 @@ var MainnetParams = Params{
RPCPort: "16110", RPCPort: "16110",
DefaultPort: "16111", DefaultPort: "16111",
DNSSeeds: []string{ 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 // This DNS seeder is run by Denis Mashkevich
"mainnet-dnsseed-1.kaspanet.org", "mainnet-dnsseed-1.kaspanet.org",
// This DNS seeder is run by Denis Mashkevich // This DNS seeder is run by Denis Mashkevich
@ -296,7 +297,7 @@ var TestnetParams = Params{
Net: appmessage.Testnet, Net: appmessage.Testnet,
RPCPort: "16210", RPCPort: "16210",
DefaultPort: "16211", DefaultPort: "16211",
DNSSeeds: []string{"testnet-9-dnsseed.daglabs-dev.com"}, DNSSeeds: []string{"testnet-10-dnsseed.kas.pa"},
// DAG parameters // DAG parameters
GenesisBlock: &testnetGenesisBlock, GenesisBlock: &testnetGenesisBlock,

View File

@ -1,9 +1,10 @@
package mempool package mempool
import ( import (
"github.com/kaspanet/kaspad/domain/consensusreference"
"sync" "sync"
"github.com/kaspanet/kaspad/domain/consensusreference"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
miningmanagermodel "github.com/kaspanet/kaspad/domain/miningmanager/model" 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) 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() mp.mtx.RLock()
defer mp.mtx.RUnlock() 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
} }
func (mp *mempool) AllTransactions() []*externalapi.DomainTransaction { return transaction, isOrphan, transactionfound
mp.mtx.RLock()
defer mp.mtx.RUnlock()
return mp.transactionsPool.getAllTransactions()
} }
func (mp *mempool) GetOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) { 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() mp.mtx.RLock()
defer mp.mtx.RUnlock() defer mp.mtx.RUnlock()
return mp.orphansPool.getOrphanTransaction(transactionID) if includeTransactionPool {
sendingInTransactionPool, receivingInTransactionPool, err = mp.transactionsPool.getTransactionsByAddresses()
if err != nil {
return nil, nil, nil, nil, err
}
} }
func (mp *mempool) AllOrphanTransactions() []*externalapi.DomainTransaction { if includeOrphanPool {
mp.mtx.RLock() sendingInTransactionPool, receivingInOrphanPool, err = mp.orphansPool.getOrphanTransactionsByAddresses()
defer mp.mtx.RUnlock() if err != nil {
return nil, nil, nil, nil, err
return mp.orphansPool.getAllOrphanTransactions() }
} }
func (mp *mempool) TransactionCount() int { return sendingInTransactionPool, receivingInTransactionPool, sendingInTransactionPool, receivingInOrphanPool, nil
}
func (mp *mempool) AllTransactions(includeTransactionPool bool, includeOrphanPool bool) (
transactionPoolTransactions []*externalapi.DomainTransaction,
orphanPoolTransactions []*externalapi.DomainTransaction) {
mp.mtx.RLock() mp.mtx.RLock()
defer mp.mtx.RUnlock() defer mp.mtx.RUnlock()
return mp.transactionsPool.transactionCount() if includeTransactionPool {
transactionPoolTransactions = mp.transactionsPool.getAllTransactions()
}
if includeOrphanPool {
orphanPoolTransactions = mp.orphansPool.getAllOrphanTransactions()
}
return transactionPoolTransactions, orphanPoolTransactions
}
func (mp *mempool) TransactionCount(includeTransactionPool bool, includeOrphanPool bool) int {
mp.mtx.RLock()
defer mp.mtx.RUnlock()
transactionCount := 0
if includeOrphanPool {
transactionCount += mp.orphansPool.orphanTransactionCount()
}
if includeTransactionPool {
transactionCount += mp.transactionsPool.transactionCount()
}
return transactionCount
} }
func (mp *mempool) HandleNewBlockTransactions(transactions []*externalapi.DomainTransaction) ( func (mp *mempool) HandleNewBlockTransactions(transactions []*externalapi.DomainTransaction) (

View File

@ -51,7 +51,7 @@ func (mpus *mempoolUTXOSet) addTransaction(transaction *model.MempoolTransaction
func (mpus *mempoolUTXOSet) removeTransaction(transaction *model.MempoolTransaction) { func (mpus *mempoolUTXOSet) removeTransaction(transaction *model.MempoolTransaction) {
for _, input := range transaction.Transaction().Inputs { 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 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 mpus.poolUnspentOutputs[input.PreviousOutpoint] = input.UTXOEntry
} }
delete(mpus.transactionByPreviousOutpoint, input.PreviousOutpoint) delete(mpus.transactionByPreviousOutpoint, input.PreviousOutpoint)

View File

@ -15,3 +15,6 @@ type OutpointToUTXOEntryMap map[externalapi.DomainOutpoint]externalapi.UTXOEntry
// OutpointToTransactionMap maps an outpoint to a MempoolTransaction // OutpointToTransactionMap maps an outpoint to a MempoolTransaction
type OutpointToTransactionMap map[externalapi.DomainOutpoint]*MempoolTransaction type OutpointToTransactionMap map[externalapi.DomainOutpoint]*MempoolTransaction
// ScriptPublicKeyStringToDomainTransaction maps an outpoint to a DomainTransaction
type ScriptPublicKeyStringToDomainTransaction map[string]*externalapi.DomainTransaction

View File

@ -169,7 +169,7 @@ func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransactio
} }
return nil, err 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) { func (op *orphansPool) getOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) {
if orphanTransaction, ok := op.allOrphans[*transactionID]; ok { 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 return nil, false
} }
func (op *orphansPool) getAllOrphanTransactions() []*externalapi.DomainTransaction { func (op *orphansPool) getOrphanTransactionsByAddresses() (
allOrphanTransactions := make([]*externalapi.DomainTransaction, 0, len(op.allOrphans)) 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 { 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 return allOrphanTransactions
} }
func (op *orphansPool) orphanTransactionCount() int {
return len(op.allOrphans)
}

View File

@ -11,7 +11,6 @@ func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTr
defer onEnd() defer onEnd()
validTransactions := []*externalapi.DomainTransaction{} validTransactions := []*externalapi.DomainTransaction{}
for _, transaction := range mp.transactionsPool.highPriorityTransactions { for _, transaction := range mp.transactionsPool.highPriorityTransactions {
isValid, err := mp.revalidateTransaction(transaction) isValid, err := mp.revalidateTransaction(transaction)
if err != nil { if err != nil {
@ -21,7 +20,7 @@ func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTr
continue continue
} }
validTransactions = append(validTransactions, transaction.Transaction()) validTransactions = append(validTransactions, transaction.Transaction().Clone())
} }
return validTransactions, nil return validTransactions, nil

View File

@ -6,6 +6,7 @@ import (
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
) )
@ -135,7 +136,7 @@ func (tp *transactionsPool) allReadyTransactions() []*externalapi.DomainTransact
for _, mempoolTransaction := range tp.allTransactions { for _, mempoolTransaction := range tp.allTransactions {
if len(mempoolTransaction.ParentTransactionsInPool()) == 0 { 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 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 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 mempoolTransaction.Transaction(), true
} }
return nil, false return nil, false
} }
func (tp *transactionsPool) getAllTransactions() []*externalapi.DomainTransaction { func (tp *transactionsPool) getTransactionsByAddresses() (
allTransactions := make([]*externalapi.DomainTransaction, 0, len(tp.allTransactions)) 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 { 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 return allTransactions
} }

View File

@ -54,7 +54,7 @@ func (mp *mempool) validateAndInsertTransaction(transaction *externalapi.DomainT
return nil, err 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() err = mp.transactionsPool.limitTransactionCount()
if err != nil { if err != nil {

View File

@ -15,11 +15,20 @@ type MiningManager interface {
GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (block *externalapi.DomainBlock, isNearlySynced bool, err error) GetBlockTemplate(coinbaseData *externalapi.DomainCoinbaseData) (block *externalapi.DomainBlock, isNearlySynced bool, err error)
ClearBlockTemplate() ClearBlockTemplate()
GetBlockTemplateBuilder() miningmanagermodel.BlockTemplateBuilder GetBlockTemplateBuilder() miningmanagermodel.BlockTemplateBuilder
GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) GetTransaction(transactionID *externalapi.DomainTransactionID, includeTransactionPool bool, includeOrphanPool bool) (
AllTransactions() []*externalapi.DomainTransaction transactionPoolTransaction *externalapi.DomainTransaction,
GetOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) isOrphan bool,
AllOrphanTransactions() []*externalapi.DomainTransaction found bool)
TransactionCount() int 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) HandleNewBlockTransactions(txs []*externalapi.DomainTransaction) ([]*externalapi.DomainTransaction, error)
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
acceptedTransactions []*externalapi.DomainTransaction, err error) acceptedTransactions []*externalapi.DomainTransaction, err error)
@ -109,28 +118,35 @@ func (mm *miningManager) ValidateAndInsertTransaction(transaction *externalapi.D
} }
func (mm *miningManager) GetTransaction( 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 { func (mm *miningManager) AllTransactions(includeTransactionPool bool, includeOrphanPool bool) (
return mm.mempool.AllTransactions() transactionPoolTransactions []*externalapi.DomainTransaction,
orphanPoolTransactions []*externalapi.DomainTransaction) {
return mm.mempool.AllTransactions(includeTransactionPool, includeOrphanPool)
} }
func (mm *miningManager) GetOrphanTransaction( func (mm *miningManager) GetTransactionsByAddresses(includeTransactionPool bool, includeOrphanPool bool) (
transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, 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 { func (mm *miningManager) TransactionCount(includeTransactionPool bool, includeOrphanPool bool) int {
return mm.mempool.TransactionCount(includeTransactionPool, includeOrphanPool)
return mm.mempool.AllOrphanTransactions()
}
func (mm *miningManager) TransactionCount() int {
return mm.mempool.TransactionCount()
} }
func (mm *miningManager) RevalidateHighPriorityTransactions() ( func (mm *miningManager) RevalidateHighPriorityTransactions() (

View File

@ -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. // 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. // Therefore, all the transactions expected to be contained in the mempool.
transactionsFromMempool := miningManager.AllTransactions() transactionsFromMempool, _ := miningManager.AllTransactions(true, false)
if len(transactionsToInsert) != len(transactionsFromMempool) { if len(transactionsToInsert) != len(transactionsFromMempool) {
t.Fatalf("Wrong number of transactions in mempool: expected: %d, got: %d", 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 { if err != nil {
t.Fatalf("ValidateAndInsertTransaction: %v", err) t.Fatalf("ValidateAndInsertTransaction: %v", err)
} }
transactionsFromMempool = miningManager.AllTransactions() transactionsFromMempool, _ = miningManager.AllTransactions(true, false)
if !contains(transactionNotAnOrphan, transactionsFromMempool) { if !contains(transactionNotAnOrphan, transactionsFromMempool) {
t.Fatalf("Missing transaction %s in the mempool", consensushashing.TransactionID(transactionNotAnOrphan)) 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 { if !errors.As(err, txRuleError) || txRuleError.RejectCode != mempool.RejectImmatureSpend {
t.Fatalf("Unexpected error %+v", err) t.Fatalf("Unexpected error %+v", err)
} }
transactionsFromMempool := miningManager.AllTransactions() transactionsFromMempool, _ := miningManager.AllTransactions(true, false)
if contains(tx, transactionsFromMempool) { if contains(tx, transactionsFromMempool) {
t.Fatalf("Mempool contains a transaction with immature coinbase") t.Fatalf("Mempool contains a transaction with immature coinbase")
} }
@ -205,7 +205,7 @@ func TestHandleNewBlockTransactions(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err) t.Fatalf("HandleNewBlockTransactions: %v", err)
} }
mempoolTransactions := miningManager.AllTransactions() mempoolTransactions, _ := miningManager.AllTransactions(true, false)
for _, removedTransaction := range blockWithFirstPartOfTheTransactions { for _, removedTransaction := range blockWithFirstPartOfTheTransactions {
if contains(removedTransaction, mempoolTransactions) { if contains(removedTransaction, mempoolTransactions) {
t.Fatalf("This transaction shouldnt be in mempool: %s", consensushashing.TransactionID(removedTransaction)) 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 // There are no chained/double-spends transactions, and hence it is expected that all the other
// transactions, will still be included in the mempool. // transactions, will still be included in the mempool.
mempoolTransactions = miningManager.AllTransactions() mempoolTransactions, _ = miningManager.AllTransactions(true, false)
for _, transaction := range blockWithRestOfTheTransactions[transactionhelper.CoinbaseTransactionIndex+1:] { for _, transaction := range blockWithRestOfTheTransactions[transactionhelper.CoinbaseTransactionIndex+1:] {
if !contains(transaction, mempoolTransactions) { if !contains(transaction, mempoolTransactions) {
t.Fatalf("This transaction %s should be in mempool.", consensushashing.TransactionID(transaction)) t.Fatalf("This transaction %s should be in mempool.", consensushashing.TransactionID(transaction))
@ -225,8 +225,9 @@ func TestHandleNewBlockTransactions(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err) t.Fatalf("HandleNewBlockTransactions: %v", err)
} }
if len(miningManager.AllTransactions()) != 0 { mempoolTransactions, _ = miningManager.AllTransactions(true, false)
blockIDs := domainBlocksToBlockIds(miningManager.AllTransactions()) if len(mempoolTransactions) != 0 {
blockIDs := domainBlocksToBlockIds(mempoolTransactions)
t.Fatalf("The mempool contains unexpected transactions: %s", blockIDs) t.Fatalf("The mempool contains unexpected transactions: %s", blockIDs)
} }
}) })
@ -269,7 +270,8 @@ func TestDoubleSpendWithBlock(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("HandleNewBlockTransactions: %v", err) 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 "+ t.Fatalf("The transaction %s, shouldn't be in the mempool, since at least one "+
"output was already spent.", consensushashing.TransactionID(transactionInTheMempool)) "output was already spent.", consensushashing.TransactionID(transactionInTheMempool))
} }
@ -303,7 +305,7 @@ func TestOrphanTransactions(t *testing.T) {
t.Fatalf("ValidateAndInsertTransaction: %v", err) t.Fatalf("ValidateAndInsertTransaction: %v", err)
} }
} }
transactionsMempool := miningManager.AllTransactions() transactionsMempool, _ := miningManager.AllTransactions(true, false)
for _, transaction := range transactionsMempool { for _, transaction := range transactionsMempool {
if contains(transaction, childTransactions) { if contains(transaction, childTransactions) {
t.Fatalf("Error: an orphan transaction is exist in the mempool") t.Fatalf("Error: an orphan transaction is exist in the mempool")
@ -345,7 +347,7 @@ func TestOrphanTransactions(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("HandleNewBlockTransactions: %+v", err) t.Fatalf("HandleNewBlockTransactions: %+v", err)
} }
transactionsMempool = miningManager.AllTransactions() transactionsMempool, _ = miningManager.AllTransactions(true, false)
if len(transactionsMempool) != len(childTransactions) { if len(transactionsMempool) != len(childTransactions) {
t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool)) 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 // Make sure spendingTransaction is still in mempool
allTransactions := miningManager.AllTransactions() mempoolTransactions, _ := miningManager.AllTransactions(true, false)
if len(allTransactions) != 1 || !allTransactions[0].Equal(spendingTransaction) { if len(mempoolTransactions) != 1 || !mempoolTransactions[0].Equal(spendingTransaction) {
t.Fatalf("Expected to have spendingTransaction as only validTransaction returned from "+ t.Fatalf("Expected to have spendingTransaction as only validTransaction returned from "+
"RevalidateHighPriorityTransactions, but got %v instead", validTransactions) "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) t.Fatalf("Expected to have empty validTransactions, but got %v instead", validTransactions)
} }
// And also AllTransactions should be empty as well // And also AllTransactions should be empty as well
allTransactions = miningManager.AllTransactions() mempoolTransactions, _ = miningManager.AllTransactions(true, false)
if len(allTransactions) != 0 { if len(mempoolTransactions) != 0 {
t.Fatalf("Expected to have empty allTransactions, but got %v instead", allTransactions) 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) t.Fatalf("ValidateAndInsertTransaction: %v", err)
} }
} }
transactionsMempool := miningManager.AllTransactions() transactionsMempool, _ := miningManager.AllTransactions(true, false)
for _, transaction := range transactionsMempool { for _, transaction := range transactionsMempool {
if contains(transaction, childTransactions) { if contains(transaction, childTransactions) {
t.Fatalf("Error: an orphan transaction is exist in the mempool") t.Fatalf("Error: an orphan transaction is exist in the mempool")
@ -654,7 +656,7 @@ func TestModifyBlockTemplate(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("HandleNewBlockTransactions: %+v", err) t.Fatalf("HandleNewBlockTransactions: %+v", err)
} }
transactionsMempool = miningManager.AllTransactions() transactionsMempool, _ = miningManager.AllTransactions(true, false)
if len(transactionsMempool) != len(childTransactions) { if len(transactionsMempool) != len(childTransactions) {
t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool)) t.Fatalf("Expected %d transactions in the mempool but got %d", len(childTransactions), len(transactionsMempool))
} }

View File

@ -12,11 +12,31 @@ type Mempool interface {
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) ( ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
acceptedTransactions []*externalapi.DomainTransaction, err error) acceptedTransactions []*externalapi.DomainTransaction, err error)
RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error
GetTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) GetTransaction(
AllTransactions() []*externalapi.DomainTransaction transactionID *externalapi.DomainTransactionID,
GetOrphanTransaction(transactionID *externalapi.DomainTransactionID) (*externalapi.DomainTransaction, bool) includeTransactionPool bool,
AllOrphanTransactions() []*externalapi.DomainTransaction includeOrphanPool bool,
TransactionCount() int ) (
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) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error)
IsTransactionOutputDust(output *externalapi.DomainTransactionOutput) bool IsTransactionOutputDust(output *externalapi.DomainTransactionOutput) bool
} }

View File

@ -1,7 +1,6 @@
package utxoindex package utxoindex
import ( import (
"encoding/binary"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
) )
@ -22,22 +21,3 @@ type UTXOChanges struct {
Added map[ScriptPublicKeyString]UTXOOutpointEntryPairs Added map[ScriptPublicKeyString]UTXOOutpointEntryPairs
Removed 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}
}

View File

@ -32,7 +32,7 @@ func newUTXOIndexStore(database database.Database) *utxoIndexStore {
func (uis *utxoIndexStore) add(scriptPublicKey *externalapi.ScriptPublicKey, outpoint *externalapi.DomainOutpoint, utxoEntry externalapi.UTXOEntry) error { 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", log.Tracef("Adding outpoint %s:%d to scriptPublicKey %s",
outpoint.TransactionID, outpoint.Index, key) 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 { 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", log.Tracef("Removing outpoint %s:%d from scriptPublicKey %s",
outpoint.TransactionID, outpoint.Index, key) outpoint.TransactionID, outpoint.Index, key)
@ -122,7 +122,7 @@ func (uis *utxoIndexStore) commit() error {
toRemoveSompiSupply := uint64(0) toRemoveSompiSupply := uint64(0)
for scriptPublicKeyString, toRemoveUTXOOutpointEntryPairs := range uis.toRemove { for scriptPublicKeyString, toRemoveUTXOOutpointEntryPairs := range uis.toRemove {
scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString) scriptPublicKey := externalapi.NewScriptPublicKeyFromString(string(scriptPublicKeyString))
bucket := uis.bucketForScriptPublicKey(scriptPublicKey) bucket := uis.bucketForScriptPublicKey(scriptPublicKey)
for outpointToRemove, utxoEntryToRemove := range toRemoveUTXOOutpointEntryPairs { for outpointToRemove, utxoEntryToRemove := range toRemoveUTXOOutpointEntryPairs {
key, err := uis.convertOutpointToKey(bucket, &outpointToRemove) key, err := uis.convertOutpointToKey(bucket, &outpointToRemove)
@ -140,7 +140,7 @@ func (uis *utxoIndexStore) commit() error {
toAddSompiSupply := uint64(0) toAddSompiSupply := uint64(0)
for scriptPublicKeyString, toAddUTXOOutpointEntryPairs := range uis.toAdd { for scriptPublicKeyString, toAddUTXOOutpointEntryPairs := range uis.toAdd {
scriptPublicKey := ConvertStringToScriptPublicKey(scriptPublicKeyString) scriptPublicKey := externalapi.NewScriptPublicKeyFromString(string(scriptPublicKeyString))
bucket := uis.bucketForScriptPublicKey(scriptPublicKey) bucket := uis.bucketForScriptPublicKey(scriptPublicKey)
for outpointToAdd, utxoEntryToAdd := range toAddUTXOOutpointEntryPairs { for outpointToAdd, utxoEntryToAdd := range toAddUTXOOutpointEntryPairs {
key, err := uis.convertOutpointToKey(bucket, &outpointToAdd) key, err := uis.convertOutpointToKey(bucket, &outpointToAdd)

View File

@ -22,6 +22,7 @@ type OnDisconnectedHandler func()
// GRPCClient is a gRPC-based RPC client // GRPCClient is a gRPC-based RPC client
type GRPCClient struct { type GRPCClient struct {
stream protowire.RPC_MessageStreamClient stream protowire.RPC_MessageStreamClient
connection *grpc.ClientConn
onErrorHandler OnErrorHandler onErrorHandler OnErrorHandler
onDisconnectedHandler OnDisconnectedHandler onDisconnectedHandler OnDisconnectedHandler
} }
@ -43,7 +44,12 @@ func Connect(address string) (*GRPCClient, error) {
if err != nil { if err != nil {
return nil, errors.Wrapf(err, "error getting client stream for %s", address) 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 // Disconnect disconnects from the RPC server

View File

@ -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
}

View File

@ -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
}

View File

@ -1,6 +1,9 @@
package rpcclient package rpcclient
import ( import (
"sync/atomic"
"time"
"github.com/kaspanet/kaspad/app/appmessage" "github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/infrastructure/logger" "github.com/kaspanet/kaspad/infrastructure/logger"
routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router" routerpkg "github.com/kaspanet/kaspad/infrastructure/network/netadapter/router"
@ -8,8 +11,6 @@ import (
"github.com/kaspanet/kaspad/util/panics" "github.com/kaspanet/kaspad/util/panics"
"github.com/kaspanet/kaspad/version" "github.com/kaspanet/kaspad/version"
"github.com/pkg/errors" "github.com/pkg/errors"
"sync/atomic"
"time"
) )
const defaultTimeout = 30 * time.Second const defaultTimeout = 30 * time.Second
@ -28,7 +29,7 @@ type RPCClient struct {
timeout time.Duration 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) { func NewRPCClient(rpcAddress string) (*RPCClient, error) {
rpcClient := &RPCClient{ rpcClient := &RPCClient{
rpcAddress: rpcAddress, rpcAddress: rpcAddress,
@ -142,6 +143,9 @@ func (c *RPCClient) handleClientDisconnected() {
} }
func (c *RPCClient) handleClientError(err error) { func (c *RPCClient) handleClientError(err error) {
if atomic.LoadUint32(&c.isClosed) == 1 {
return
}
log.Warnf("Received error from client: %s", err) log.Warnf("Received error from client: %s", err)
c.handleClientDisconnected() c.handleClientDisconnected()
} }
@ -158,7 +162,7 @@ func (c *RPCClient) Close() error {
return errors.Errorf("Cannot close a client that had already been closed") return errors.Errorf("Cannot close a client that had already been closed")
} }
c.rpcRouter.router.Close() c.rpcRouter.router.Close()
return nil return c.GRPCClient.Close()
} }
// Address returns the address the RPC client connected to // Address returns the address the RPC client connected to

View File

@ -2,6 +2,7 @@ package integration
import ( import (
"github.com/kaspanet/kaspad/infrastructure/config" "github.com/kaspanet/kaspad/infrastructure/config"
"runtime"
"testing" "testing"
"time" "time"
@ -26,6 +27,37 @@ func newTestRPCClient(rpcAddress string) (*testRPCClient, error) {
}, nil }, 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) { func TestRPCMaxInboundConnections(t *testing.T) {
harness, teardown := setupHarness(t, &harnessParams{ harness, teardown := setupHarness(t, &harnessParams{
p2pAddress: p2pAddress1, p2pAddress: p2pAddress1,