apply max fee constrains

This commit is contained in:
Michael Sutton 2024-09-15 12:15:22 +03:00
parent 0beb9edf12
commit c0415eaaec
3 changed files with 48 additions and 31 deletions

View File

@ -61,7 +61,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum
mass := s.txMassCalculator.CalculateTransactionOverallMass(domainTx)
feeRate := float64(entry.Entry.Fee) / float64(mass)
newFeeRate, err := s.calculateFeeRate(request.FeePolicy)
newFeeRate, maxFee, err := s.calculateFeeLimits(request.FeePolicy)
if err != nil {
return nil, err
}
@ -87,7 +87,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum
for outpoint := range outpointsToInputs {
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 {
return nil, err
}
@ -128,7 +128,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum
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 {
return nil, err
}

View File

@ -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)
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) (
*pb.CreateUnsignedTransactionsResponse, error,
) {
@ -34,23 +37,36 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
}
func (s *server) calculateFeeRate(requestFeePolicy *pb.FeePolicy) (float64, error) {
var feeRate float64
func (s *server) calculateFeeLimits(requestFeePolicy *pb.FeePolicy) (feeRate float64, maxFee uint64, err error) {
feeRate = minFeeRate
maxFee = math.MaxUint64
switch requestFeePolicy := requestFeePolicy.FeePolicy.(type) {
case *pb.FeePolicy_ExactFeeRate:
feeRate = requestFeePolicy.ExactFeeRate
feeRate = math.Max(requestFeePolicy.ExactFeeRate, minFeeRate)
case *pb.FeePolicy_MaxFeeRate:
estimate, err := s.rpcClient.GetFeeEstimate()
if err != nil {
return 0, err
return 0, 0, err
}
feeRate = math.Min(estimate.Estimate.NormalBuckets[0].Feerate, requestFeePolicy.MaxFeeRate)
case *pb.FeePolicy_MaxFee:
// TODO
panic("todo")
estimate, err := s.rpcClient.GetFeeEstimate()
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) {
@ -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())
}
feeRate, err := s.calculateFeeRate(requestFeePolicy)
feeRate, maxFee, err := s.calculateFeeLimits(requestFeePolicy)
if err != nil {
return nil, err
}
@ -84,7 +100,7 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
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 {
return nil, err
}
@ -110,19 +126,19 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
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 {
return nil, err
}
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) {
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) {
preSelectedSet := make(map[externalapi.DomainOutpoint]struct{})
@ -168,7 +184,7 @@ func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allo
totalValue += utxo.UTXOEntry.Amount()
// 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 {
return false, err
}
@ -227,7 +243,7 @@ func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allo
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{}
fakeAddr, err := util.NewAddressPublicKey(fakePubKey[:], s.params.Prefix)
if err != nil {
@ -273,7 +289,7 @@ func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float
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) {

View File

@ -22,8 +22,8 @@ import (
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
// paying to the original transaction's payee.
func (s *server) maybeAutoCompoundTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64) ([][]byte, error) {
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate)
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64, maxFee uint64) ([][]byte, error) {
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
if err != nil {
return nil, err
}
@ -44,6 +44,7 @@ func (s *server) mergeTransaction(
changeAddress util.Address,
changeWalletAddress *walletAddress,
feeRate float64,
maxFee uint64,
) (*serialization.PartiallySignedTransaction, error) {
numOutputs := len(originalTransaction.Tx.Outputs)
if numOutputs > 2 || numOutputs == 0 {
@ -70,7 +71,7 @@ func (s *server) mergeTransaction(
totalValue += output.Value
}
// 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 {
return nil, err
}
@ -104,7 +105,7 @@ func (s *server) mergeTransaction(
}
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)
if err != nil {
@ -115,7 +116,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
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 {
return nil, err
}
@ -125,19 +126,19 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
startIndex := i * inputCountPerSplit
endIndex := startIndex + inputCountPerSplit
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 {
return nil, err
}
}
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 {
return nil, err
}
// 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 {
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.
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,
// 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,
// 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 {
return 0, 0, err
}
@ -188,7 +189,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
}
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)
totalSompi := uint64(0)
@ -206,7 +207,7 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
}
if len(selectedUTXOs) != 0 {
fee, err := s.estimateFee(selectedUTXOs, feeRate, false)
fee, err := s.estimateFee(selectedUTXOs, feeRate, maxFee, false)
if err != nil {
return nil, err
}