Compare commits

..

13 Commits

Author SHA1 Message Date
Ori Newman
387fbf0451 Add comment 2023-12-27 14:17:31 +02:00
Ori Newman
265d76a393 small fixes 2023-12-27 14:15:40 +02:00
Ori Newman
057cc85c8c Compare wallet addresses by value 2023-12-27 14:02:41 +02:00
Michael Sutton
0534413213 one more rename 2023-12-27 00:03:25 +02:00
Michael Sutton
0428502c44 rename to follow conventions 2023-12-26 23:51:09 +02:00
Michael Sutton
c3ef43795b add an rpc client dedicated for background ops 2023-12-26 23:24:48 +02:00
Michael Sutton
9e3b89c357 fix small memory leak 2023-12-26 20:57:03 +02:00
Michael Sutton
f62a174985 lock address reading 2023-12-26 20:40:45 +02:00
Michael Sutton
0d66fa8cd8 fix expire condition 2023-12-26 20:33:05 +02:00
Ori Newman
eabbdad738 Better policy for used outpoints and wait for first sync when creating an unsigned tx 2023-12-26 19:03:14 +02:00
Ori Newman
c28eaa6998 Don't push to forceSyncChan if it has an element 2023-12-26 18:10:52 +02:00
Ori Newman
ee2a848412 Make a more granular lock for refreshUTXOs 2023-12-26 17:52:56 +02:00
Ori Newman
524a654886 Lazy wallet utxo sync after broadcasting a tx 2023-12-26 17:30:43 +02:00
12 changed files with 34 additions and 81 deletions

View File

@@ -28,8 +28,7 @@ func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, requ
}
log.Debugf("Rejected transaction %s: %s", transactionID, err)
// Return the ID also in the case of error, so that clients can match the response to the correct transaction submit request
errorMessage := appmessage.NewSubmitTransactionResponseMessage(transactionID.String())
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", transactionID, err)
return errorMessage, nil
}

View File

@@ -1,8 +1,3 @@
Kaspad v0.12.17 - 2024-02-19
===========================
* Wallet-related improvements and fixes (#2253, #2257, #2258, #2262)
Kaspad v0.12.16 - 2023-12-25
===========================

View File

@@ -1,10 +1,9 @@
package main
import (
"os"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"os"
"github.com/jessevdk/go-flags"
)
@@ -59,9 +58,9 @@ type sendConfig struct {
Password string `long:"password" short:"p" description:"Wallet password"`
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Repeat multiple times (adding -a before each) 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 string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount). If --from-address was used, will send all only from the specified addresses."`
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
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)"`
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
config.NetworkFlags
@@ -116,13 +115,12 @@ type newAddressConfig struct {
}
type startDaemonConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Listen string `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"`
MinFeePerTx uint64 `long:"min-fee-per-tx" description:"Minimum fee per transaction (in sompis) (default: 0)"`
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Password string `long:"password" short:"p" description:"Wallet password"`
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
Listen string `long:"listen" short:"l" description:"Address to listen on (default: 0.0.0.0:8082)"`
Timeout uint32 `long:"wait-timeout" short:"w" description:"Waiting timeout for RPC calls, seconds (default: 30 s)"`
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
config.NetworkFlags
}

View File

@@ -2,16 +2,14 @@ package server
import (
"context"
"time"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/infrastructure/network/rpcclient"
"github.com/pkg/errors"
"time"
)
func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
@@ -61,7 +59,7 @@ func (s *server) broadcast(transactions [][]byte, isDomain bool) ([]string, erro
}
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), consensushashing.TransactionID(tx).String(), false)
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), false)
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction")
}

View File

@@ -14,11 +14,6 @@ import (
// TODO: Implement a better fee estimation mechanism
const feePerInput = 10000
// The minimal change amount to target in order to avoid large storage mass (see KIP9 for more details).
// By having at least 0.2KAS in the change output we make sure that every transaction with send value >= 0.2KAS
// should succeed (at most 50K storage mass for each output, thus overall lower than standard mass upper bound which is 100K gram)
const minChangeTarget = constants.SompiPerKaspa / 5
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
*pb.CreateUnsignedTransactionsResponse, error,
) {
@@ -126,26 +121,17 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
totalValue += utxo.UTXOEntry.Amount()
fee := feePerInput * uint64(len(selectedUTXOs))
if fee < s.minFeePerTx {
fee = s.minFeePerTx
}
totalSpend := spendAmount + fee
// Two break cases (if not send all):
// 1. totalValue == totalSpend, so there's no change needed -> number of outputs = 1, so a single input is sufficient
// 2. totalValue > totalSpend, so there will be change and 2 outputs, therefor in order to not struggle with --
// 2.1 go-nodes dust patch we try and find at least 2 inputs (even though the next one is not necessary in terms of spend value)
// 2.2 KIP9 we try and make sure that the change amount is not too small
if !isSendAll && (totalValue == totalSpend || (totalValue >= totalSpend+minChangeTarget && len(selectedUTXOs) > 1)) {
// 2. totalValue > totalSpend, so there will be change and 2 outputs, therefor in order to not struggle with new dust
// rules we try and find at least 2 inputs (even though the next one is not necessary in terms of spend value)
if !isSendAll && (totalValue == totalSpend || (totalValue > totalSpend && len(selectedUTXOs) > 1)) {
break
}
}
fee := feePerInput * uint64(len(selectedUTXOs))
if fee < s.minFeePerTx {
fee = s.minFeePerTx
}
var totalSpend uint64
if isSendAll {
totalSpend = totalValue

View File

@@ -34,7 +34,6 @@ type server struct {
backgroundRPCClient *rpcclient.RPCClient // RPC client dedicated for address and UTXO background fetching
params *dagconfig.Params
coinbaseMaturity uint64 // Is different from default if we use testnet-11
minFeePerTx uint64
lock sync.RWMutex
utxosSortedByAmount []*walletUTXO
@@ -58,7 +57,7 @@ type server struct {
const MaxDaemonSendMsgSize = 100_000_000
// Start starts the kaspawalletd server
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32, minFeePerTx uint64) error {
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32) error {
initLog(defaultLogFile, defaultErrLogFile)
defer panics.HandlePanic(log, "MAIN", nil)
@@ -111,7 +110,6 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
backgroundRPCClient: backgroundRPCClient,
params: params,
coinbaseMaturity: coinbaseMaturity,
minFeePerTx: minFeePerTx,
utxosSortedByAmount: []*walletUTXO{},
nextSyncStartIndex: 0,
keysFile: keysFile,

View File

@@ -3,5 +3,5 @@ package main
import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
func startDaemon(conf *startDaemonConfig) error {
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout, conf.MinFeePerTx)
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout)
}

View File

@@ -1,44 +1,23 @@
package rpcclient
import (
"strings"
"github.com/kaspanet/kaspad/app/appmessage"
)
// SubmitTransaction sends an RPC request respective to the function's name and returns the RPC server's response
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction, transactionID string, allowOrphan bool) (*appmessage.SubmitTransactionResponseMessage, error) {
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction, allowOrphan bool) (*appmessage.SubmitTransactionResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionRequestMessage(transaction, allowOrphan))
if err != nil {
return nil, err
}
for {
response, err := c.route(appmessage.CmdSubmitTransactionResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return nil, err
}
submitTransactionResponse := response.(*appmessage.SubmitTransactionResponseMessage)
// Match the response to the expected ID. If they are different it means we got an old response which we
// previously timed-out on, so we log and continue waiting for the correct current response.
if submitTransactionResponse.TransactionID != transactionID {
if submitTransactionResponse.Error != nil {
// A non-updated Kaspad might return an empty ID in the case of error, so in
// such a case we fallback to checking if the error contains the expected ID
if submitTransactionResponse.TransactionID != "" || !strings.Contains(submitTransactionResponse.Error.Message, transactionID) {
log.Warnf("SubmitTransaction: received an error response for previous request: %s", submitTransactionResponse.Error)
continue
}
} else {
log.Warnf("SubmitTransaction: received a successful response for previous request with ID %s",
submitTransactionResponse.TransactionID)
continue
}
}
if submitTransactionResponse.Error != nil {
return nil, c.convertRPCError(submitTransactionResponse.Error)
}
return submitTransactionResponse, nil
response, err := c.route(appmessage.CmdSubmitTransactionResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return nil, err
}
submitTransactionResponse := response.(*appmessage.SubmitTransactionResponseMessage)
if submitTransactionResponse.Error != nil {
return nil, c.convertRPCError(submitTransactionResponse.Error)
}
return submitTransactionResponse, nil
}

View File

@@ -86,7 +86,7 @@ func submitAnAmountOfTransactionsToTheMempool(t *testing.T, rpcClient *rpcclient
for i, transaction := range transactions {
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
_, err := rpcClient.SubmitTransaction(rpcTransaction, consensushashing.TransactionID(transaction).String(), false)
_, err := rpcClient.SubmitTransaction(rpcTransaction, false)
if err != nil {
if ignoreOrphanRejects && strings.Contains(err.Error(), "orphan") {
continue

View File

@@ -53,7 +53,7 @@ func TestTxRelay(t *testing.T) {
msgTx := generateTx(t, secondBlock.Transactions[transactionhelper.CoinbaseTransactionIndex], payer, payee)
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction, consensushashing.TransactionID(domainTransaction).String(), false)
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction, false)
if err != nil {
t.Fatalf("Error submitting transaction: %+v", err)
}

View File

@@ -88,8 +88,8 @@ func TestUTXOIndex(t *testing.T) {
// Submit a few transactions that spends some UTXOs
const transactionAmountToSpend = 5
for i := 0; i < transactionAmountToSpend; i++ {
rpcTransaction, transactionID := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction, transactionID, false)
rpcTransaction := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction, false)
if err != nil {
t.Fatalf("Error submitting transaction: %s", err)
}
@@ -171,7 +171,7 @@ func TestUTXOIndex(t *testing.T) {
}
}
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) (*appmessage.RPCTransaction, string) {
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) *appmessage.RPCTransaction {
transactionIDBytes, err := hex.DecodeString(entry.Outpoint.TransactionID)
if err != nil {
t.Fatalf("Error decoding transaction ID: %s", err)
@@ -224,5 +224,5 @@ func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAdd
msgTx.TxIn[0].SignatureScript = signatureScript
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
return appmessage.DomainTransactionToRPCTransaction(domainTransaction), consensushashing.TransactionID(domainTransaction).String()
return appmessage.DomainTransactionToRPCTransaction(domainTransaction)
}

View File

@@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
const (
appMajor uint = 0
appMinor uint = 12
appPatch uint = 17
appPatch uint = 16
)
// appBuild is defined as a variable so it can be overridden during the build