mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-13 13:00:10 +00:00

* Add fee estimation to wallet * Add fee rate to kaspawallet parse * Update go version * Get rid of golint * Add RBF support to wallet * Fix bump_fee UTXO lookup and fix wrong change address * impl storage mass as per KIP9 * Use CalculateTransactionOverallMass where needed * Some fixes * Minor typos * Fix test * update version * BroadcastRBF -> BroadcastReplacement * rc3 * align proto files to only use camel case (fixed on RK as well) * Rename to FeePolicy and add MaxFee option + todo * apply max fee constrains * increase minChangeTarget to 10kas * fmt * Some fixes * fix description: maximum -> minimum * put min feerate check in the correct location * Fix calculateFeeLimits nil handling * Add validations to CLI flags * Change to rc6 * Add checkTransactionFeeRate * Add failed broadcast transactions on send error` * Fix estimateFee change value * Estimate fee correctly for --send-all * On estimateFee always assume that the recipient has ECDSA address * remove patch version --------- Co-authored-by: Michael Sutton <msutton@cs.huji.ac.il>
157 lines
4.5 KiB
Go
157 lines
4.5 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
|
|
"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/domain/consensus/model/externalapi"
|
|
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.BumpFeeResponse, error) {
|
|
s.lock.Lock()
|
|
defer s.lock.Unlock()
|
|
|
|
entry, err := s.rpcClient.GetMempoolEntry(request.TxID, false, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
domainTx, err := appmessage.RPCTransactionToDomainTransaction(entry.Entry.Transaction)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
outpointsToInputs := make(map[externalapi.DomainOutpoint]*externalapi.DomainTransactionInput)
|
|
var maxUTXO *walletUTXO
|
|
for _, input := range domainTx.Inputs {
|
|
outpointsToInputs[input.PreviousOutpoint] = input
|
|
utxo, ok := s.mempoolExcludedUTXOs[input.PreviousOutpoint]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
input.UTXOEntry = utxo.UTXOEntry
|
|
if maxUTXO == nil || utxo.UTXOEntry.Amount() > maxUTXO.UTXOEntry.Amount() {
|
|
maxUTXO = utxo
|
|
}
|
|
}
|
|
|
|
if maxUTXO == nil {
|
|
// If we got here it means for some reason s.mempoolExcludedUTXOs is not up to date and we need to search for the UTXOs in s.utxosSortedByAmount
|
|
for _, utxo := range s.utxosSortedByAmount {
|
|
input, ok := outpointsToInputs[*utxo.Outpoint]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
input.UTXOEntry = utxo.UTXOEntry
|
|
if maxUTXO == nil || utxo.UTXOEntry.Amount() > maxUTXO.UTXOEntry.Amount() {
|
|
maxUTXO = utxo
|
|
}
|
|
}
|
|
}
|
|
|
|
if maxUTXO == nil {
|
|
return nil, errors.Errorf("no UTXOs were found for transaction %s. This probably means the transaction is already accepted", request.TxID)
|
|
}
|
|
|
|
mass := s.txMassCalculator.CalculateTransactionOverallMass(domainTx)
|
|
feeRate := float64(entry.Entry.Fee) / float64(mass)
|
|
newFeeRate, maxFee, err := s.calculateFeeLimits(request.FeePolicy)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if feeRate >= newFeeRate {
|
|
return nil, errors.Errorf("new fee rate (%f) is not higher than the current fee rate (%f)", newFeeRate, feeRate)
|
|
}
|
|
|
|
if len(domainTx.Outputs) == 0 || len(domainTx.Outputs) > 2 {
|
|
return nil, errors.Errorf("kaspawallet supports only transactions with 1 or 2 outputs in transaction %s, but this transaction got %d", request.TxID, len(domainTx.Outputs))
|
|
}
|
|
|
|
var fromAddresses []*walletAddress
|
|
for _, from := range request.From {
|
|
fromAddress, exists := s.addressSet[from]
|
|
if !exists {
|
|
return nil, errors.Errorf("specified from address %s does not exists", from)
|
|
}
|
|
fromAddresses = append(fromAddresses, fromAddress)
|
|
}
|
|
|
|
allowUsed := make(map[externalapi.DomainOutpoint]struct{})
|
|
for outpoint := range outpointsToInputs {
|
|
allowUsed[outpoint] = struct{}{}
|
|
}
|
|
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOsWithPreselected([]*walletUTXO{maxUTXO}, allowUsed, domainTx.Outputs[0].Value, false, newFeeRate, maxFee, fromAddresses)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
_, toAddress, err := txscript.ExtractScriptPubKeyAddress(domainTx.Outputs[0].ScriptPublicKey, s.params)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
changeAddress, changeWalletAddress, err := s.changeAddress(request.UseExistingChangeAddress, fromAddresses)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if len(selectedUTXOs) == 0 {
|
|
return nil, errors.Errorf("couldn't find funds to spend")
|
|
}
|
|
|
|
payments := []*libkaspawallet.Payment{{
|
|
Address: toAddress,
|
|
Amount: spendValue,
|
|
}}
|
|
if changeSompi > 0 {
|
|
changeAddress, _, err := s.changeAddress(request.UseExistingChangeAddress, fromAddresses)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
payments = append(payments, &libkaspawallet.Payment{
|
|
Address: changeAddress,
|
|
Amount: changeSompi,
|
|
})
|
|
}
|
|
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
|
s.keysFile.MinimumSignatures,
|
|
payments, selectedUTXOs)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, newFeeRate, maxFee)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if request.Password == "" {
|
|
return &pb.BumpFeeResponse{
|
|
Transactions: unsignedTransactions,
|
|
}, nil
|
|
}
|
|
|
|
signedTransactions, err := s.signTransactions(unsignedTransactions, request.Password)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
txIDs, err := s.broadcastReplacement(signedTransactions, false)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return &pb.BumpFeeResponse{
|
|
TxIDs: txIDs,
|
|
Transactions: signedTransactions,
|
|
}, nil
|
|
}
|