mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-11-29 08:28:50 +00:00
apply max fee constrains
This commit is contained in:
parent
0beb9edf12
commit
c0415eaaec
@ -61,7 +61,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum
|
|||||||
|
|
||||||
mass := s.txMassCalculator.CalculateTransactionOverallMass(domainTx)
|
mass := s.txMassCalculator.CalculateTransactionOverallMass(domainTx)
|
||||||
feeRate := float64(entry.Entry.Fee) / float64(mass)
|
feeRate := float64(entry.Entry.Fee) / float64(mass)
|
||||||
newFeeRate, err := s.calculateFeeRate(request.FeePolicy)
|
newFeeRate, maxFee, err := s.calculateFeeLimits(request.FeePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -87,7 +87,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum
|
|||||||
for outpoint := range outpointsToInputs {
|
for outpoint := range outpointsToInputs {
|
||||||
allowUsed[outpoint] = struct{}{}
|
allowUsed[outpoint] = struct{}{}
|
||||||
}
|
}
|
||||||
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOsWithPreselected([]*walletUTXO{maxUTXO}, allowUsed, domainTx.Outputs[0].Value, false, newFeeRate, fromAddresses)
|
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOsWithPreselected([]*walletUTXO{maxUTXO}, allowUsed, domainTx.Outputs[0].Value, false, newFeeRate, maxFee, fromAddresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -128,7 +128,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, newFeeRate)
|
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, newFeeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,6 +19,9 @@ import (
|
|||||||
// should succeed (at most 50K storage mass for each output, thus overall lower than standard mass upper bound which is 100K gram)
|
// 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
|
const minChangeTarget = constants.SompiPerKaspa / 5
|
||||||
|
|
||||||
|
// The current minimal fee rate according to mempool standards
|
||||||
|
const minFeeRate = 1.0
|
||||||
|
|
||||||
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
|
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
|
||||||
*pb.CreateUnsignedTransactionsResponse, error,
|
*pb.CreateUnsignedTransactionsResponse, error,
|
||||||
) {
|
) {
|
||||||
@ -34,23 +37,36 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
|
|||||||
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) calculateFeeRate(requestFeePolicy *pb.FeePolicy) (float64, error) {
|
func (s *server) calculateFeeLimits(requestFeePolicy *pb.FeePolicy) (feeRate float64, maxFee uint64, err error) {
|
||||||
var feeRate float64
|
feeRate = minFeeRate
|
||||||
|
maxFee = math.MaxUint64
|
||||||
switch requestFeePolicy := requestFeePolicy.FeePolicy.(type) {
|
switch requestFeePolicy := requestFeePolicy.FeePolicy.(type) {
|
||||||
case *pb.FeePolicy_ExactFeeRate:
|
case *pb.FeePolicy_ExactFeeRate:
|
||||||
feeRate = requestFeePolicy.ExactFeeRate
|
feeRate = math.Max(requestFeePolicy.ExactFeeRate, minFeeRate)
|
||||||
case *pb.FeePolicy_MaxFeeRate:
|
case *pb.FeePolicy_MaxFeeRate:
|
||||||
estimate, err := s.rpcClient.GetFeeEstimate()
|
estimate, err := s.rpcClient.GetFeeEstimate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
feeRate = math.Min(estimate.Estimate.NormalBuckets[0].Feerate, requestFeePolicy.MaxFeeRate)
|
feeRate = math.Min(estimate.Estimate.NormalBuckets[0].Feerate, requestFeePolicy.MaxFeeRate)
|
||||||
case *pb.FeePolicy_MaxFee:
|
case *pb.FeePolicy_MaxFee:
|
||||||
// TODO
|
estimate, err := s.rpcClient.GetFeeEstimate()
|
||||||
panic("todo")
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
feeRate = estimate.Estimate.NormalBuckets[0].Feerate
|
||||||
|
maxFee = requestFeePolicy.MaxFee
|
||||||
|
case nil:
|
||||||
|
estimate, err := s.rpcClient.GetFeeEstimate()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
feeRate = estimate.Estimate.NormalBuckets[0].Feerate
|
||||||
|
// Default to a bound of max 1 KAS as fee
|
||||||
|
maxFee = constants.SompiPerKaspa
|
||||||
}
|
}
|
||||||
|
|
||||||
return feeRate, nil
|
return feeRate, maxFee, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool, requestFeePolicy *pb.FeePolicy) ([][]byte, error) {
|
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool, requestFeePolicy *pb.FeePolicy) ([][]byte, error) {
|
||||||
@ -58,7 +74,7 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
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())
|
||||||
}
|
}
|
||||||
|
|
||||||
feeRate, err := s.calculateFeeRate(requestFeePolicy)
|
feeRate, maxFee, err := s.calculateFeeLimits(requestFeePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -84,7 +100,7 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feeRate, fromAddresses)
|
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feeRate, maxFee, fromAddresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -110,19 +126,19 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return unsignedTransactions, nil
|
return unsignedTransactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feeRate float64, fromAddresses []*walletAddress) (
|
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feeRate float64, maxFee uint64, fromAddresses []*walletAddress) (
|
||||||
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
|
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
|
||||||
return s.selectUTXOsWithPreselected(nil, map[externalapi.DomainOutpoint]struct{}{}, spendAmount, isSendAll, feeRate, fromAddresses)
|
return s.selectUTXOsWithPreselected(nil, map[externalapi.DomainOutpoint]struct{}{}, spendAmount, isSendAll, feeRate, maxFee, fromAddresses)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allowUsed map[externalapi.DomainOutpoint]struct{}, spendAmount uint64, isSendAll bool, feeRate float64, fromAddresses []*walletAddress) (
|
func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allowUsed map[externalapi.DomainOutpoint]struct{}, spendAmount uint64, isSendAll bool, feeRate float64, maxFee uint64, fromAddresses []*walletAddress) (
|
||||||
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
|
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
|
||||||
|
|
||||||
preSelectedSet := make(map[externalapi.DomainOutpoint]struct{})
|
preSelectedSet := make(map[externalapi.DomainOutpoint]struct{})
|
||||||
@ -168,7 +184,7 @@ func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allo
|
|||||||
totalValue += utxo.UTXOEntry.Amount()
|
totalValue += utxo.UTXOEntry.Amount()
|
||||||
|
|
||||||
// We're overestimating a bit by assuming that any transaction will have a change output
|
// We're overestimating a bit by assuming that any transaction will have a change output
|
||||||
fee, err = s.estimateFee(selectedUTXOs, feeRate, true)
|
fee, err = s.estimateFee(selectedUTXOs, feeRate, maxFee, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
@ -227,7 +243,7 @@ func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allo
|
|||||||
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float64, assumeChange bool) (uint64, error) {
|
func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float64, maxFee uint64, assumeChange bool) (uint64, error) {
|
||||||
fakePubKey := [util.PublicKeySize]byte{}
|
fakePubKey := [util.PublicKeySize]byte{}
|
||||||
fakeAddr, err := util.NewAddressPublicKey(fakePubKey[:], s.params.Prefix)
|
fakeAddr, err := util.NewAddressPublicKey(fakePubKey[:], s.params.Prefix)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -273,7 +289,7 @@ func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float
|
|||||||
return 0, err
|
return 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return uint64(float64(mass) * feeRate), nil
|
return min(uint64(math.Ceil(float64(mass)*feeRate)), maxFee), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) estimateFeePerInput(feeRate float64) (uint64, error) {
|
func (s *server) estimateFeePerInput(feeRate float64) (uint64, error) {
|
||||||
|
|||||||
@ -22,8 +22,8 @@ import (
|
|||||||
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
|
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
|
||||||
// paying to the original transaction's payee.
|
// paying to the original transaction's payee.
|
||||||
func (s *server) maybeAutoCompoundTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
func (s *server) maybeAutoCompoundTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
||||||
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64) ([][]byte, error) {
|
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64, maxFee uint64) ([][]byte, error) {
|
||||||
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -44,6 +44,7 @@ func (s *server) mergeTransaction(
|
|||||||
changeAddress util.Address,
|
changeAddress util.Address,
|
||||||
changeWalletAddress *walletAddress,
|
changeWalletAddress *walletAddress,
|
||||||
feeRate float64,
|
feeRate float64,
|
||||||
|
maxFee uint64,
|
||||||
) (*serialization.PartiallySignedTransaction, error) {
|
) (*serialization.PartiallySignedTransaction, error) {
|
||||||
numOutputs := len(originalTransaction.Tx.Outputs)
|
numOutputs := len(originalTransaction.Tx.Outputs)
|
||||||
if numOutputs > 2 || numOutputs == 0 {
|
if numOutputs > 2 || numOutputs == 0 {
|
||||||
@ -70,7 +71,7 @@ func (s *server) mergeTransaction(
|
|||||||
totalValue += output.Value
|
totalValue += output.Value
|
||||||
}
|
}
|
||||||
// We're overestimating a bit by assuming that any transaction will have a change output
|
// We're overestimating a bit by assuming that any transaction will have a change output
|
||||||
fee, err := s.estimateFee(utxos, feeRate, true)
|
fee, err := s.estimateFee(utxos, feeRate, maxFee, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -104,7 +105,7 @@ func (s *server) mergeTransaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
||||||
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64) ([]*serialization.PartiallySignedTransaction, error) {
|
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64, maxFee uint64) ([]*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
transactionMass, err := s.estimateComputeMassAfterSignatures(transaction)
|
transactionMass, err := s.estimateComputeMassAfterSignatures(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -115,7 +116,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress, feeRate)
|
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -125,19 +126,19 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
startIndex := i * inputCountPerSplit
|
startIndex := i * inputCountPerSplit
|
||||||
endIndex := startIndex + inputCountPerSplit
|
endIndex := startIndex + inputCountPerSplit
|
||||||
var err error
|
var err error
|
||||||
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex, feeRate)
|
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(splitTransactions) > 1 {
|
if len(splitTransactions) > 1 {
|
||||||
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
|
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
|
||||||
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -150,7 +151,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
|
|
||||||
// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
|
// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
|
||||||
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
|
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
|
||||||
changeAddress util.Address, feeRate float64) (splitCount, inputsPerSplitCount int, err error) {
|
changeAddress util.Address, feeRate float64, maxFee uint64) (splitCount, inputsPerSplitCount int, err error) {
|
||||||
|
|
||||||
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
|
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
|
||||||
// to calculate how much mass do all the inputs have
|
// to calculate how much mass do all the inputs have
|
||||||
@ -170,7 +171,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
|
|||||||
|
|
||||||
// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
|
// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
|
||||||
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
|
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
|
||||||
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0, feeRate)
|
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
@ -188,7 +189,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
||||||
changeAddress util.Address, startIndex int, endIndex int, feeRate float64) (*serialization.PartiallySignedTransaction, error) {
|
changeAddress util.Address, startIndex int, endIndex int, feeRate float64, maxFee uint64) (*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
||||||
totalSompi := uint64(0)
|
totalSompi := uint64(0)
|
||||||
@ -206,7 +207,7 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign
|
|||||||
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
|
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
|
||||||
}
|
}
|
||||||
if len(selectedUTXOs) != 0 {
|
if len(selectedUTXOs) != 0 {
|
||||||
fee, err := s.estimateFee(selectedUTXOs, feeRate, false)
|
fee, err := s.estimateFee(selectedUTXOs, feeRate, maxFee, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user