diff --git a/cmd/kaspawallet/daemon/server/bump_fee.go b/cmd/kaspawallet/daemon/server/bump_fee.go index 08357ed4a..a0c8810c1 100644 --- a/cmd/kaspawallet/daemon/server/bump_fee.go +++ b/cmd/kaspawallet/daemon/server/bump_fee.go @@ -25,7 +25,7 @@ func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.Bum return nil, err } - mass := s.txMassCalculator.CalculateTransactionMass(domainTx) // TODO: Does GetMempoolEntry already provide the mass? + mass := s.txMassCalculator.CalculateTransactionOverallMass(domainTx) feeRate := float64(entry.Entry.Fee) / float64(mass) newFeeRate, err := s.calculateFeeRate(request.FeeRate) if err != nil { diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 4d369c105..a1f88be8e 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -275,7 +275,9 @@ func (s *server) estimateFeePerInput(feeRate float64) (uint64, error) { return 0, err } - mass, err := s.estimateMassAfterSignatures(mockTx) + // Here we use compute mass to avoid dividing by zero. This is ok since `s.estimateFeePerInput` is only used + // in the case of compound transactions that have a compute mass higher than its storage mass. + mass, err := s.estimateComputeMassAfterSignatures(mockTx) if err != nil { return 0, err } @@ -287,7 +289,7 @@ func (s *server) estimateFeePerInput(feeRate float64) (uint64, error) { return 0, err } - massWithoutUTXO, err := s.estimateMassAfterSignatures(mockTxWithoutUTXO) + massWithoutUTXO, err := s.estimateComputeMassAfterSignatures(mockTxWithoutUTXO) if err != nil { return 0, err } diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 8e2d09aa4..8f08830ee 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -105,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) { - transactionMass, err := s.estimateMassAfterSignatures(transaction) + transactionMass, err := s.estimateComputeMassAfterSignatures(transaction) if err != nil { return nil, err } @@ -222,7 +222,11 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall return EstimateMassAfterSignatures(transaction, s.keysFile.ECDSA, s.keysFile.MinimumSignatures, s.txMassCalculator) } -func EstimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (uint64, error) { +func (s *server) estimateComputeMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) { + return estimateComputeMassAfterSignatures(transaction, s.keysFile.ECDSA, s.keysFile.MinimumSignatures, s.txMassCalculator) +} + +func createTransactionWithJunkFieldsForMassCalculation(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (*externalapi.DomainTransaction, error) { transaction = transaction.Clone() var signatureSize uint64 if ecdsa { @@ -241,7 +245,11 @@ func EstimateMassAfterSignatures(transaction *serialization.PartiallySignedTrans transaction.Tx.Inputs[i].SigOpCount = byte(len(input.PubKeySignaturePairs)) } - transactionWithSignatures, err := libkaspawallet.ExtractTransactionDeserialized(transaction, ecdsa) + return libkaspawallet.ExtractTransactionDeserialized(transaction, ecdsa) +} + +func estimateComputeMassAfterSignatures(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (uint64, error) { + transactionWithSignatures, err := createTransactionWithJunkFieldsForMassCalculation(transaction, ecdsa, minimumSignatures, txMassCalculator) if err != nil { return 0, err } @@ -249,6 +257,15 @@ func EstimateMassAfterSignatures(transaction *serialization.PartiallySignedTrans return txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil } +func EstimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (uint64, error) { + transactionWithSignatures, err := createTransactionWithJunkFieldsForMassCalculation(transaction, ecdsa, minimumSignatures, txMassCalculator) + if err != nil { + return 0, err + } + + return txMassCalculator.CalculateTransactionOverallMass(transactionWithSignatures), nil +} + func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64, feeRate float64) ( additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) { diff --git a/cmd/kaspawallet/daemon/server/split_transaction_test.go b/cmd/kaspawallet/daemon/server/split_transaction_test.go index 4706f4977..54d8f8e75 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction_test.go +++ b/cmd/kaspawallet/daemon/server/split_transaction_test.go @@ -20,11 +20,62 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" ) +func TestEstimateComputeMassAfterSignatures(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + unsignedTransaction, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig) + defer teardown(false) + + serverInstance := &server{ + params: params, + keysFile: &keys.File{MinimumSignatures: 2}, + shutdown: make(chan struct{}), + addressSet: make(walletAddressSet), + txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp), + } + + estimatedMassAfterSignatures, err := serverInstance.estimateComputeMassAfterSignatures(unsignedTransaction) + if err != nil { + t.Fatalf("Error from estimateMassAfterSignatures: %s", err) + } + + unsignedTransactionBytes, err := serialization.SerializePartiallySignedTransaction(unsignedTransaction) + if err != nil { + t.Fatalf("Error deserializing unsignedTransaction: %s", err) + } + + signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false) + if err != nil { + t.Fatalf("Sign: %+v", err) + } + + signedTxStep2Bytes, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1Bytes, false) + if err != nil { + t.Fatalf("Sign: %+v", err) + } + + extractedSignedTx, err := libkaspawallet.ExtractTransaction(signedTxStep2Bytes, false) + if err != nil { + t.Fatalf("ExtractTransaction: %+v", err) + } + + actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx) + + if estimatedMassAfterSignatures != actualMassAfterSignatures { + t.Errorf("Estimated mass after signatures: %d but actually got %d", + estimatedMassAfterSignatures, actualMassAfterSignatures) + } + }) +} + func TestEstimateMassAfterSignatures(t *testing.T) { testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { unsignedTransaction, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig) defer teardown(false) + for i := range unsignedTransaction.Tx.Inputs { + unsignedTransaction.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{}, false, 0) + } + serverInstance := &server{ params: params, keysFile: &keys.File{MinimumSignatures: 2}, @@ -58,7 +109,11 @@ func TestEstimateMassAfterSignatures(t *testing.T) { t.Fatalf("ExtractTransaction: %+v", err) } - actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx) + for i := range extractedSignedTx.Inputs { + extractedSignedTx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{}, false, 0) + } + + actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionOverallMass(extractedSignedTx) if estimatedMassAfterSignatures != actualMassAfterSignatures { t.Errorf("Estimated mass after signatures: %d but actually got %d",