From bcdf72b698b4726704bdcc923ddef8e8b659ed11 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 9 Jan 2022 09:44:57 +0200 Subject: [PATCH 01/40] Add GetUTXOsByBalances command to rpc --- app/appmessage/rpc_get_balance_by_address.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/appmessage/rpc_get_balance_by_address.go b/app/appmessage/rpc_get_balance_by_address.go index 3bd81b9dc1..c8cb258ad3 100644 --- a/app/appmessage/rpc_get_balance_by_address.go +++ b/app/appmessage/rpc_get_balance_by_address.go @@ -9,7 +9,7 @@ type GetBalanceByAddressRequestMessage struct { // Command returns the protocol command string for the message func (msg *GetBalanceByAddressRequestMessage) Command() MessageCommand { - return CmdGetBalanceByAddressRequestMessage + return CmdGetBalancesByAddressesRequestMessage } // NewGetBalanceByAddressRequest returns a instance of the message @@ -30,7 +30,7 @@ type GetBalanceByAddressResponseMessage struct { // Command returns the protocol command string for the message func (msg *GetBalanceByAddressResponseMessage) Command() MessageCommand { - return CmdGetBalanceByAddressResponseMessage + return CmdGetBalancesByAddressesResponseMessage } // NewGetBalanceByAddressResponse returns an instance of the message From 8444a9a274066269067dab84f3243b17e7549672 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 11 Jan 2022 15:34:32 +0200 Subject: [PATCH 02/40] Fix wrong commands in GetBalanceByAddress --- app/appmessage/rpc_get_balance_by_address.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/appmessage/rpc_get_balance_by_address.go b/app/appmessage/rpc_get_balance_by_address.go index c8cb258ad3..3bd81b9dc1 100644 --- a/app/appmessage/rpc_get_balance_by_address.go +++ b/app/appmessage/rpc_get_balance_by_address.go @@ -9,7 +9,7 @@ type GetBalanceByAddressRequestMessage struct { // Command returns the protocol command string for the message func (msg *GetBalanceByAddressRequestMessage) Command() MessageCommand { - return CmdGetBalancesByAddressesRequestMessage + return CmdGetBalanceByAddressRequestMessage } // NewGetBalanceByAddressRequest returns a instance of the message @@ -30,7 +30,7 @@ type GetBalanceByAddressResponseMessage struct { // Command returns the protocol command string for the message func (msg *GetBalanceByAddressResponseMessage) Command() MessageCommand { - return CmdGetBalancesByAddressesResponseMessage + return CmdGetBalanceByAddressResponseMessage } // NewGetBalanceByAddressResponse returns an instance of the message From bccca21ee829ee1ec527a8da9ecc87cbfd6a5a5f Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 11 Jan 2022 16:44:52 +0200 Subject: [PATCH 03/40] Moved calculation of TransactionMass out of TransactionValidator, so t that it can be used in kaspawallet --- domain/consensus/consensus.go | 4 +- domain/consensus/factory.go | 19 +-- .../blockvalidator/blockvalidator.go | 8 ++ .../processes/transactionvalidator/mass.go | 91 +-------------- .../transactionvalidator.go | 15 +-- .../mempool/check_transaction_standard.go | 4 +- util/txmass/calculator.go | 110 ++++++++++++++++++ 7 files changed, 141 insertions(+), 110 deletions(-) create mode 100644 util/txmass/calculator.go diff --git a/domain/consensus/consensus.go b/domain/consensus/consensus.go index 38154197c7..6061fe4098 100644 --- a/domain/consensus/consensus.go +++ b/domain/consensus/consensus.go @@ -1,8 +1,6 @@ package consensus import ( - "github.com/kaspanet/kaspad/infrastructure/logger" - "github.com/kaspanet/kaspad/util/staging" "math/big" "sync" @@ -10,6 +8,8 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/infrastructure/logger" + "github.com/kaspanet/kaspad/util/staging" "github.com/pkg/errors" ) diff --git a/domain/consensus/factory.go b/domain/consensus/factory.go index d41ed0cbd6..b344ddaa61 100644 --- a/domain/consensus/factory.go +++ b/domain/consensus/factory.go @@ -1,15 +1,17 @@ package consensus import ( + "io/ioutil" + "os" + "sync" + "github.com/kaspanet/kaspad/domain/consensus/datastructures/daawindowstore" "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/processes/blockparentbuilder" parentssanager "github.com/kaspanet/kaspad/domain/consensus/processes/parentsmanager" "github.com/kaspanet/kaspad/domain/consensus/processes/pruningproofmanager" "github.com/kaspanet/kaspad/domain/consensus/utils/constants" - "io/ioutil" - "os" - "sync" + "github.com/kaspanet/kaspad/util/txmass" "github.com/kaspanet/kaspad/domain/prefixmanager/prefix" @@ -169,6 +171,9 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas config.GenesisHash, ) + + txMassCalculator := txmass.NewCalculator(config.MassPerTxByte, config.MassPerScriptPubKeyByte, config.MassPerSigOp) + pastMedianTimeManager := f.pastMedianTimeConsructor( config.TimestampDeviationTolerance, dbManager, @@ -178,14 +183,12 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas config.GenesisHash) transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity, config.EnableNonNativeSubnetworks, - config.MassPerTxByte, - config.MassPerScriptPubKeyByte, - config.MassPerSigOp, config.MaxCoinbasePayloadLength, dbManager, pastMedianTimeManager, ghostdagDataStore, - daaBlocksStore) + daaBlocksStore, + txMassCalculator) difficultyManager := f.difficultyConstructor( dbManager, ghostdagManager, @@ -328,6 +331,8 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas reachabilityDataStore, consensusStateStore, daaBlocksStore, + + txMassCalculator, ) syncManager := syncmanager.New( diff --git a/domain/consensus/processes/blockvalidator/blockvalidator.go b/domain/consensus/processes/blockvalidator/blockvalidator.go index f34861ef4b..9ef5e6b11a 100644 --- a/domain/consensus/processes/blockvalidator/blockvalidator.go +++ b/domain/consensus/processes/blockvalidator/blockvalidator.go @@ -4,6 +4,8 @@ import ( "math/big" "time" + "github.com/kaspanet/kaspad/util/txmass" + "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/util/difficulty" @@ -47,6 +49,8 @@ type blockValidator struct { reachabilityStore model.ReachabilityDataStore consensusStateStore model.ConsensusStateStore daaBlocksStore model.DAABlocksStore + + txMassCalculator *txmass.Calculator } // New instantiates a new BlockValidator @@ -85,6 +89,8 @@ func New(powMax *big.Int, reachabilityStore model.ReachabilityDataStore, consensusStateStore model.ConsensusStateStore, daaBlocksStore model.DAABlocksStore, + + txMassCalculator *txmass.Calculator, ) model.BlockValidator { return &blockValidator{ @@ -123,5 +129,7 @@ func New(powMax *big.Int, reachabilityStore: reachabilityStore, consensusStateStore: consensusStateStore, daaBlocksStore: daaBlocksStore, + + txMassCalculator: txMassCalculator, } } diff --git a/domain/consensus/processes/transactionvalidator/mass.go b/domain/consensus/processes/transactionvalidator/mass.go index c4e4a7aafb..d994104705 100644 --- a/domain/consensus/processes/transactionvalidator/mass.go +++ b/domain/consensus/processes/transactionvalidator/mass.go @@ -2,99 +2,12 @@ package transactionvalidator import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" - "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" ) +// PopulateMass calculates and populates the mass of the given transaction func (v *transactionValidator) PopulateMass(transaction *externalapi.DomainTransaction) { if transaction.Mass != 0 { return } - transaction.Mass = v.transactionMass(transaction) -} - -func (v *transactionValidator) transactionMass(transaction *externalapi.DomainTransaction) uint64 { - if transactionhelper.IsCoinBase(transaction) { - return 0 - } - - // calculate mass for size - size := transactionEstimatedSerializedSize(transaction) - massForSize := size * v.massPerTxByte - - // calculate mass for scriptPubKey - totalScriptPubKeySize := uint64(0) - for _, output := range transaction.Outputs { - totalScriptPubKeySize += 2 //output.ScriptPublicKey.Version (uint16) - totalScriptPubKeySize += uint64(len(output.ScriptPublicKey.Script)) - } - massForScriptPubKey := totalScriptPubKeySize * v.massPerScriptPubKeyByte - - // calculate mass for SigOps - totalSigOpCount := uint64(0) - for _, input := range transaction.Inputs { - totalSigOpCount += uint64(input.SigOpCount) - } - massForSigOps := totalSigOpCount * v.massPerSigOp - - // Sum all components of mass - return massForSize + massForScriptPubKey + massForSigOps -} - -// transactionEstimatedSerializedSize is the estimated size of a transaction in some -// serialization. This has to be deterministic, but not necessarily accurate, since -// it's only used as the size component in the transaction and block mass limit -// calculation. -func transactionEstimatedSerializedSize(tx *externalapi.DomainTransaction) uint64 { - if transactionhelper.IsCoinBase(tx) { - return 0 - } - size := uint64(0) - size += 2 // Txn Version - size += 8 // number of inputs (uint64) - for _, input := range tx.Inputs { - size += transactionInputEstimatedSerializedSize(input) - } - - size += 8 // number of outputs (uint64) - for _, output := range tx.Outputs { - size += TransactionOutputEstimatedSerializedSize(output) - } - - size += 8 // lock time (uint64) - size += externalapi.DomainSubnetworkIDSize - size += 8 // gas (uint64) - size += externalapi.DomainHashSize // payload hash - - size += 8 // length of the payload (uint64) - size += uint64(len(tx.Payload)) - - return size -} - -func transactionInputEstimatedSerializedSize(input *externalapi.DomainTransactionInput) uint64 { - size := uint64(0) - size += outpointEstimatedSerializedSize() - - size += 8 // length of signature script (uint64) - size += uint64(len(input.SignatureScript)) - - size += 8 // sequence (uint64) - return size -} - -func outpointEstimatedSerializedSize() uint64 { - size := uint64(0) - size += externalapi.DomainHashSize // ID - size += 4 // index (uint32) - return size -} - -// TransactionOutputEstimatedSerializedSize is the same as transactionEstimatedSerializedSize but for outputs only -func TransactionOutputEstimatedSerializedSize(output *externalapi.DomainTransactionOutput) uint64 { - size := uint64(0) - size += 8 // value (uint64) - size += 2 // output.ScriptPublicKey.Version (uint 16) - size += 8 // length of script public key (uint64) - size += uint64(len(output.ScriptPublicKey.Script)) - return size + transaction.Mass = v.txMassCalculator.CalculateTransactionMass(transaction) } diff --git a/domain/consensus/processes/transactionvalidator/transactionvalidator.go b/domain/consensus/processes/transactionvalidator/transactionvalidator.go index 19f73188f5..73621e7560 100644 --- a/domain/consensus/processes/transactionvalidator/transactionvalidator.go +++ b/domain/consensus/processes/transactionvalidator/transactionvalidator.go @@ -3,6 +3,7 @@ package transactionvalidator import ( "github.com/kaspanet/kaspad/domain/consensus/model" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" + "github.com/kaspanet/kaspad/util/txmass" ) const sigCacheSize = 10_000 @@ -16,32 +17,25 @@ type transactionValidator struct { ghostdagDataStore model.GHOSTDAGDataStore daaBlocksStore model.DAABlocksStore enableNonNativeSubnetworks bool - massPerTxByte uint64 - massPerScriptPubKeyByte uint64 - massPerSigOp uint64 maxCoinbasePayloadLength uint64 sigCache *txscript.SigCache sigCacheECDSA *txscript.SigCacheECDSA + txMassCalculator *txmass.Calculator } // New instantiates a new TransactionValidator func New(blockCoinbaseMaturity uint64, enableNonNativeSubnetworks bool, - massPerTxByte uint64, - massPerScriptPubKeyByte uint64, - massPerSigOp uint64, maxCoinbasePayloadLength uint64, databaseContext model.DBReader, pastMedianTimeManager model.PastMedianTimeManager, ghostdagDataStore model.GHOSTDAGDataStore, - daaBlocksStore model.DAABlocksStore) model.TransactionValidator { + daaBlocksStore model.DAABlocksStore, + txMassCalculator *txmass.Calculator) model.TransactionValidator { return &transactionValidator{ blockCoinbaseMaturity: blockCoinbaseMaturity, enableNonNativeSubnetworks: enableNonNativeSubnetworks, - massPerTxByte: massPerTxByte, - massPerScriptPubKeyByte: massPerScriptPubKeyByte, - massPerSigOp: massPerSigOp, maxCoinbasePayloadLength: maxCoinbasePayloadLength, databaseContext: databaseContext, pastMedianTimeManager: pastMedianTimeManager, @@ -49,5 +43,6 @@ func New(blockCoinbaseMaturity uint64, daaBlocksStore: daaBlocksStore, sigCache: txscript.NewSigCache(sigCacheSize), sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize), + txMassCalculator: txMassCalculator, } } diff --git a/domain/miningmanager/mempool/check_transaction_standard.go b/domain/miningmanager/mempool/check_transaction_standard.go index 39df3429d4..abc62a7b60 100644 --- a/domain/miningmanager/mempool/check_transaction_standard.go +++ b/domain/miningmanager/mempool/check_transaction_standard.go @@ -3,7 +3,7 @@ package mempool import ( "fmt" - "github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator" + "github.com/kaspanet/kaspad/util/txmass" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" @@ -127,7 +127,7 @@ func (mp *mempool) IsTransactionOutputDust(output *externalapi.DomainTransaction // The most common scripts are pay-to-pubkey, and as per the above // breakdown, the minimum size of a p2pk input script is 148 bytes. So // that figure is used. - totalSerializedSize := transactionvalidator.TransactionOutputEstimatedSerializedSize(output) + 148 + totalSerializedSize := txmass.TransactionOutputEstimatedSerializedSize(output) + 148 // The output is considered dust if the cost to the network to spend the // coins is more than 1/3 of the minimum free transaction relay fee. diff --git a/util/txmass/calculator.go b/util/txmass/calculator.go new file mode 100644 index 0000000000..037c663158 --- /dev/null +++ b/util/txmass/calculator.go @@ -0,0 +1,110 @@ +package txmass + +import ( + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper" +) + +// Calculator exposes methods to calculate the mass of a transaction +type Calculator struct { + massPerTxByte uint64 + massPerScriptPubKeyByte uint64 + massPerSigOp uint64 +} + +// NewCalculator creates a new instance of Calculator +func NewCalculator(massPerTxByte, massPerScriptPubKeyByte, massPerSigOp uint64) *Calculator { + return &Calculator{ + massPerTxByte: massPerTxByte, + massPerScriptPubKeyByte: massPerScriptPubKeyByte, + massPerSigOp: massPerSigOp, + } +} + +// CalculateTransactionMass calculates the mass of the given transaction +func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTransaction) uint64 { + if transactionhelper.IsCoinBase(transaction) { + return 0 + } + + // calculate mass for size + size := transactionEstimatedSerializedSize(transaction) + massForSize := size * c.massPerTxByte + + // calculate mass for scriptPubKey + totalScriptPubKeySize := uint64(0) + for _, output := range transaction.Outputs { + totalScriptPubKeySize += 2 //output.ScriptPublicKey.Version (uint16) + totalScriptPubKeySize += uint64(len(output.ScriptPublicKey.Script)) + } + massForScriptPubKey := totalScriptPubKeySize * c.massPerScriptPubKeyByte + + // calculate mass for SigOps + totalSigOpCount := uint64(0) + for _, input := range transaction.Inputs { + totalSigOpCount += uint64(input.SigOpCount) + } + massForSigOps := totalSigOpCount * c.massPerSigOp + + // Sum all components of mass + return massForSize + massForScriptPubKey + massForSigOps +} + +// transactionEstimatedSerializedSize is the estimated size of a transaction in some +// serialization. This has to be deterministic, but not necessarily accurate, since +// it's only used as the size component in the transaction and block mass limit +// calculation. +func transactionEstimatedSerializedSize(tx *externalapi.DomainTransaction) uint64 { + if transactionhelper.IsCoinBase(tx) { + return 0 + } + size := uint64(0) + size += 2 // Txn Version + size += 8 // number of inputs (uint64) + for _, input := range tx.Inputs { + size += transactionInputEstimatedSerializedSize(input) + } + + size += 8 // number of outputs (uint64) + for _, output := range tx.Outputs { + size += TransactionOutputEstimatedSerializedSize(output) + } + + size += 8 // lock time (uint64) + size += externalapi.DomainSubnetworkIDSize + size += 8 // gas (uint64) + size += externalapi.DomainHashSize // payload hash + + size += 8 // length of the payload (uint64) + size += uint64(len(tx.Payload)) + + return size +} + +func transactionInputEstimatedSerializedSize(input *externalapi.DomainTransactionInput) uint64 { + size := uint64(0) + size += outpointEstimatedSerializedSize() + + size += 8 // length of signature script (uint64) + size += uint64(len(input.SignatureScript)) + + size += 8 // sequence (uint64) + return size +} + +func outpointEstimatedSerializedSize() uint64 { + size := uint64(0) + size += externalapi.DomainHashSize // ID + size += 4 // index (uint32) + return size +} + +// TransactionOutputEstimatedSerializedSize is the same as transactionEstimatedSerializedSize but for outputs only +func TransactionOutputEstimatedSerializedSize(output *externalapi.DomainTransactionOutput) uint64 { + size := uint64(0) + size += 8 // value (uint64) + size += 2 // output.ScriptPublicKey.Version (uint 16) + size += 8 // length of script public key (uint64) + size += uint64(len(output.ScriptPublicKey.Script)) + return size +} From 504309a0e0e6796bf9d5bb41c2d57d9cd12b7fde Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sat, 29 Jan 2022 09:43:21 +0200 Subject: [PATCH 04/40] Allow CreateUnsignedTransaction to return multiple transactions --- cmd/kaspawallet/create_unsigned_tx.go | 7 +- cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go | 193 +++++++++--------- cmd/kaspawallet/daemon/pb/kaspawalletd.proto | 8 +- .../daemon/pb/kaspawalletd_grpc.pb.go | 28 +-- .../server/create_unsigned_transaction.go | 5 +- .../libkaspawallet/transaction_test.go | 9 +- cmd/kaspawallet/send.go | 20 +- 7 files changed, 141 insertions(+), 129 deletions(-) diff --git a/cmd/kaspawallet/create_unsigned_tx.go b/cmd/kaspawallet/create_unsigned_tx.go index 22eac3ec6a..30c7c58384 100644 --- a/cmd/kaspawallet/create_unsigned_tx.go +++ b/cmd/kaspawallet/create_unsigned_tx.go @@ -4,6 +4,7 @@ import ( "context" "encoding/hex" "fmt" + "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/kaspanet/kaspad/domain/consensus/utils/constants" @@ -20,7 +21,7 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error { defer cancel() sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa) - response, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{ + response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{ Address: conf.ToAddress, Amount: sendAmountSompi, }) @@ -29,6 +30,8 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error { } fmt.Println("Created unsigned transaction") - fmt.Println(hex.EncodeToString(response.UnsignedTransaction)) + for _, unsignedTransaction := range response.UnsignedTransactions { + fmt.Println(hex.EncodeToString(unsignedTransaction)) + } return nil } diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go b/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go index a72f259e8b..c43b947abb 100644 --- a/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go +++ b/cmd/kaspawallet/daemon/pb/kaspawalletd.pb.go @@ -1,6 +1,6 @@ // Code generated by protoc-gen-go. DO NOT EDIT. // versions: -// protoc-gen-go v1.27.1 +// protoc-gen-go v1.26.0 // protoc v3.12.3 // source: kaspawalletd.proto @@ -184,7 +184,7 @@ func (x *AddressBalances) GetPending() uint64 { return 0 } -type CreateUnsignedTransactionRequest struct { +type CreateUnsignedTransactionsRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields @@ -193,8 +193,8 @@ type CreateUnsignedTransactionRequest struct { Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"` } -func (x *CreateUnsignedTransactionRequest) Reset() { - *x = CreateUnsignedTransactionRequest{} +func (x *CreateUnsignedTransactionsRequest) Reset() { + *x = CreateUnsignedTransactionsRequest{} if protoimpl.UnsafeEnabled { mi := &file_kaspawalletd_proto_msgTypes[3] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -202,13 +202,13 @@ func (x *CreateUnsignedTransactionRequest) Reset() { } } -func (x *CreateUnsignedTransactionRequest) String() string { +func (x *CreateUnsignedTransactionsRequest) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateUnsignedTransactionRequest) ProtoMessage() {} +func (*CreateUnsignedTransactionsRequest) ProtoMessage() {} -func (x *CreateUnsignedTransactionRequest) ProtoReflect() protoreflect.Message { +func (x *CreateUnsignedTransactionsRequest) ProtoReflect() protoreflect.Message { mi := &file_kaspawalletd_proto_msgTypes[3] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -220,35 +220,35 @@ func (x *CreateUnsignedTransactionRequest) ProtoReflect() protoreflect.Message { return mi.MessageOf(x) } -// Deprecated: Use CreateUnsignedTransactionRequest.ProtoReflect.Descriptor instead. -func (*CreateUnsignedTransactionRequest) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateUnsignedTransactionsRequest.ProtoReflect.Descriptor instead. +func (*CreateUnsignedTransactionsRequest) Descriptor() ([]byte, []int) { return file_kaspawalletd_proto_rawDescGZIP(), []int{3} } -func (x *CreateUnsignedTransactionRequest) GetAddress() string { +func (x *CreateUnsignedTransactionsRequest) GetAddress() string { if x != nil { return x.Address } return "" } -func (x *CreateUnsignedTransactionRequest) GetAmount() uint64 { +func (x *CreateUnsignedTransactionsRequest) GetAmount() uint64 { if x != nil { return x.Amount } return 0 } -type CreateUnsignedTransactionResponse struct { +type CreateUnsignedTransactionsResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - UnsignedTransaction []byte `protobuf:"bytes,1,opt,name=unsignedTransaction,proto3" json:"unsignedTransaction,omitempty"` + UnsignedTransactions [][]byte `protobuf:"bytes,1,rep,name=unsignedTransactions,proto3" json:"unsignedTransactions,omitempty"` } -func (x *CreateUnsignedTransactionResponse) Reset() { - *x = CreateUnsignedTransactionResponse{} +func (x *CreateUnsignedTransactionsResponse) Reset() { + *x = CreateUnsignedTransactionsResponse{} if protoimpl.UnsafeEnabled { mi := &file_kaspawalletd_proto_msgTypes[4] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -256,13 +256,13 @@ func (x *CreateUnsignedTransactionResponse) Reset() { } } -func (x *CreateUnsignedTransactionResponse) String() string { +func (x *CreateUnsignedTransactionsResponse) String() string { return protoimpl.X.MessageStringOf(x) } -func (*CreateUnsignedTransactionResponse) ProtoMessage() {} +func (*CreateUnsignedTransactionsResponse) ProtoMessage() {} -func (x *CreateUnsignedTransactionResponse) ProtoReflect() protoreflect.Message { +func (x *CreateUnsignedTransactionsResponse) ProtoReflect() protoreflect.Message { mi := &file_kaspawalletd_proto_msgTypes[4] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) @@ -274,14 +274,14 @@ func (x *CreateUnsignedTransactionResponse) ProtoReflect() protoreflect.Message return mi.MessageOf(x) } -// Deprecated: Use CreateUnsignedTransactionResponse.ProtoReflect.Descriptor instead. -func (*CreateUnsignedTransactionResponse) Descriptor() ([]byte, []int) { +// Deprecated: Use CreateUnsignedTransactionsResponse.ProtoReflect.Descriptor instead. +func (*CreateUnsignedTransactionsResponse) Descriptor() ([]byte, []int) { return file_kaspawalletd_proto_rawDescGZIP(), []int{4} } -func (x *CreateUnsignedTransactionResponse) GetUnsignedTransaction() []byte { +func (x *CreateUnsignedTransactionsResponse) GetUnsignedTransactions() [][]byte { if x != nil { - return x.UnsignedTransaction + return x.UnsignedTransactions } return nil } @@ -646,64 +646,65 @@ var file_kaspawalletd_proto_rawDesc = []byte{ 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x61, 0x76, 0x61, 0x69, 0x6c, 0x61, 0x62, 0x6c, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x04, - 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x54, 0x0a, 0x20, 0x43, 0x72, 0x65, + 0x52, 0x07, 0x70, 0x65, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x22, 0x55, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, - 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, - 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22, - 0x55, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, - 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, - 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, - 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, - 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31, - 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, - 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, - 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, - 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61, - 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, - 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x72, - 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x27, 0x0a, 0x11, - 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, - 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74, - 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x91, 0x03, 0x0a, - 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x37, 0x0a, - 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x47, 0x65, - 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, - 0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, - 0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, - 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, - 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, - 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, - 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x15, 0x2e, - 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, - 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, - 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x2e, 0x4e, - 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, - 0x1a, 0x13, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, - 0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, - 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x09, 0x42, 0x72, - 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, - 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x42, 0x72, 0x6f, - 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, - 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, - 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, - 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64, - 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, + 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, + 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, + 0x22, 0x58, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, + 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, + 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, + 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x22, 0x31, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, + 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, + 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, + 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, + 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72, + 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, + 0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x0c, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x22, 0x27, 0x0a, 0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, + 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x32, 0x94, 0x03, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, + 0x64, 0x12, 0x37, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, + 0x12, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, 0x0a, 0x1a, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, + 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x43, + 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, + 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x12, 0x15, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, + 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x53, 0x68, + 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x12, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, + 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75, + 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53, + 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, + 0x00, 0x12, 0x34, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11, + 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x12, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, + 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, + 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -720,30 +721,30 @@ func file_kaspawalletd_proto_rawDescGZIP() []byte { var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 13) var file_kaspawalletd_proto_goTypes = []interface{}{ - (*GetBalanceRequest)(nil), // 0: GetBalanceRequest - (*GetBalanceResponse)(nil), // 1: GetBalanceResponse - (*AddressBalances)(nil), // 2: AddressBalances - (*CreateUnsignedTransactionRequest)(nil), // 3: CreateUnsignedTransactionRequest - (*CreateUnsignedTransactionResponse)(nil), // 4: CreateUnsignedTransactionResponse - (*ShowAddressesRequest)(nil), // 5: ShowAddressesRequest - (*ShowAddressesResponse)(nil), // 6: ShowAddressesResponse - (*NewAddressRequest)(nil), // 7: NewAddressRequest - (*NewAddressResponse)(nil), // 8: NewAddressResponse - (*BroadcastRequest)(nil), // 9: BroadcastRequest - (*BroadcastResponse)(nil), // 10: BroadcastResponse - (*ShutdownRequest)(nil), // 11: ShutdownRequest - (*ShutdownResponse)(nil), // 12: ShutdownResponse + (*GetBalanceRequest)(nil), // 0: GetBalanceRequest + (*GetBalanceResponse)(nil), // 1: GetBalanceResponse + (*AddressBalances)(nil), // 2: AddressBalances + (*CreateUnsignedTransactionsRequest)(nil), // 3: CreateUnsignedTransactionsRequest + (*CreateUnsignedTransactionsResponse)(nil), // 4: CreateUnsignedTransactionsResponse + (*ShowAddressesRequest)(nil), // 5: ShowAddressesRequest + (*ShowAddressesResponse)(nil), // 6: ShowAddressesResponse + (*NewAddressRequest)(nil), // 7: NewAddressRequest + (*NewAddressResponse)(nil), // 8: NewAddressResponse + (*BroadcastRequest)(nil), // 9: BroadcastRequest + (*BroadcastResponse)(nil), // 10: BroadcastResponse + (*ShutdownRequest)(nil), // 11: ShutdownRequest + (*ShutdownResponse)(nil), // 12: ShutdownResponse } var file_kaspawalletd_proto_depIdxs = []int32{ 2, // 0: GetBalanceResponse.addressBalances:type_name -> AddressBalances 0, // 1: kaspawalletd.GetBalance:input_type -> GetBalanceRequest - 3, // 2: kaspawalletd.CreateUnsignedTransaction:input_type -> CreateUnsignedTransactionRequest + 3, // 2: kaspawalletd.CreateUnsignedTransactions:input_type -> CreateUnsignedTransactionsRequest 5, // 3: kaspawalletd.ShowAddresses:input_type -> ShowAddressesRequest 7, // 4: kaspawalletd.NewAddress:input_type -> NewAddressRequest 11, // 5: kaspawalletd.Shutdown:input_type -> ShutdownRequest 9, // 6: kaspawalletd.Broadcast:input_type -> BroadcastRequest 1, // 7: kaspawalletd.GetBalance:output_type -> GetBalanceResponse - 4, // 8: kaspawalletd.CreateUnsignedTransaction:output_type -> CreateUnsignedTransactionResponse + 4, // 8: kaspawalletd.CreateUnsignedTransactions:output_type -> CreateUnsignedTransactionsResponse 6, // 9: kaspawalletd.ShowAddresses:output_type -> ShowAddressesResponse 8, // 10: kaspawalletd.NewAddress:output_type -> NewAddressResponse 12, // 11: kaspawalletd.Shutdown:output_type -> ShutdownResponse @@ -798,7 +799,7 @@ func file_kaspawalletd_proto_init() { } } file_kaspawalletd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateUnsignedTransactionRequest); i { + switch v := v.(*CreateUnsignedTransactionsRequest); i { case 0: return &v.state case 1: @@ -810,7 +811,7 @@ func file_kaspawalletd_proto_init() { } } file_kaspawalletd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { - switch v := v.(*CreateUnsignedTransactionResponse); i { + switch v := v.(*CreateUnsignedTransactionsResponse); i { case 0: return &v.state case 1: diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd.proto b/cmd/kaspawallet/daemon/pb/kaspawalletd.proto index 3c34b42e89..1992d5cd57 100644 --- a/cmd/kaspawallet/daemon/pb/kaspawalletd.proto +++ b/cmd/kaspawallet/daemon/pb/kaspawalletd.proto @@ -4,7 +4,7 @@ option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"; service kaspawalletd { rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {} - rpc CreateUnsignedTransaction (CreateUnsignedTransactionRequest) returns (CreateUnsignedTransactionResponse) {} + rpc CreateUnsignedTransactions (CreateUnsignedTransactionsRequest) returns (CreateUnsignedTransactionsResponse) {} rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {} rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {} rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {} @@ -26,13 +26,13 @@ message AddressBalances { uint64 pending = 3; } -message CreateUnsignedTransactionRequest { +message CreateUnsignedTransactionsRequest { string address = 1; uint64 amount = 2; } -message CreateUnsignedTransactionResponse { - bytes unsignedTransaction = 1; +message CreateUnsignedTransactionsResponse { + repeated bytes unsignedTransactions = 1; } message ShowAddressesRequest { diff --git a/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go b/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go index 04112b6fff..f23b245d1b 100644 --- a/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go +++ b/cmd/kaspawallet/daemon/pb/kaspawalletd_grpc.pb.go @@ -19,7 +19,7 @@ const _ = grpc.SupportPackageIsVersion7 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type KaspawalletdClient interface { GetBalance(ctx context.Context, in *GetBalanceRequest, opts ...grpc.CallOption) (*GetBalanceResponse, error) - CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error) + CreateUnsignedTransactions(ctx context.Context, in *CreateUnsignedTransactionsRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionsResponse, error) ShowAddresses(ctx context.Context, in *ShowAddressesRequest, opts ...grpc.CallOption) (*ShowAddressesResponse, error) NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error) Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error) @@ -43,9 +43,9 @@ func (c *kaspawalletdClient) GetBalance(ctx context.Context, in *GetBalanceReque return out, nil } -func (c *kaspawalletdClient) CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error) { - out := new(CreateUnsignedTransactionResponse) - err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransaction", in, out, opts...) +func (c *kaspawalletdClient) CreateUnsignedTransactions(ctx context.Context, in *CreateUnsignedTransactionsRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionsResponse, error) { + out := new(CreateUnsignedTransactionsResponse) + err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransactions", in, out, opts...) if err != nil { return nil, err } @@ -93,7 +93,7 @@ func (c *kaspawalletdClient) Broadcast(ctx context.Context, in *BroadcastRequest // for forward compatibility type KaspawalletdServer interface { GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) - CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error) + CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error) Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error) @@ -108,8 +108,8 @@ type UnimplementedKaspawalletdServer struct { func (UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetBalance not implemented") } -func (UnimplementedKaspawalletdServer) CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransaction not implemented") +func (UnimplementedKaspawalletdServer) CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransactions not implemented") } func (UnimplementedKaspawalletdServer) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ShowAddresses not implemented") @@ -154,20 +154,20 @@ func _Kaspawalletd_GetBalance_Handler(srv interface{}, ctx context.Context, dec return interceptor(ctx, in, info, handler) } -func _Kaspawalletd_CreateUnsignedTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { - in := new(CreateUnsignedTransactionRequest) +func _Kaspawalletd_CreateUnsignedTransactions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(CreateUnsignedTransactionsRequest) if err := dec(in); err != nil { return nil, err } if interceptor == nil { - return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, in) + return srv.(KaspawalletdServer).CreateUnsignedTransactions(ctx, in) } info := &grpc.UnaryServerInfo{ Server: srv, - FullMethod: "/kaspawalletd/CreateUnsignedTransaction", + FullMethod: "/kaspawalletd/CreateUnsignedTransactions", } handler := func(ctx context.Context, req interface{}) (interface{}, error) { - return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, req.(*CreateUnsignedTransactionRequest)) + return srv.(KaspawalletdServer).CreateUnsignedTransactions(ctx, req.(*CreateUnsignedTransactionsRequest)) } return interceptor(ctx, in, info, handler) } @@ -256,8 +256,8 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{ Handler: _Kaspawalletd_GetBalance_Handler, }, { - MethodName: "CreateUnsignedTransaction", - Handler: _Kaspawalletd_CreateUnsignedTransaction_Handler, + MethodName: "CreateUnsignedTransactions", + Handler: _Kaspawalletd_CreateUnsignedTransactions_Handler, }, { MethodName: "ShowAddresses", diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 68e6364ef6..01df2f1a73 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -10,7 +10,8 @@ import ( "github.com/pkg/errors" ) -func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.CreateUnsignedTransactionRequest) (*pb.CreateUnsignedTransactionResponse, error) { +func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) ( + *pb.CreateUnsignedTransactionsResponse, error) { s.lock.Lock() defer s.lock.Unlock() @@ -53,7 +54,7 @@ func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.Create return nil, err } - return &pb.CreateUnsignedTransactionResponse{UnsignedTransaction: unsignedTransaction}, nil + return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: [][]byte{unsignedTransaction}}, nil } func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) ( diff --git a/cmd/kaspawallet/libkaspawallet/transaction_test.go b/cmd/kaspawallet/libkaspawallet/transaction_test.go index d7c7a74216..1b705e739b 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction_test.go +++ b/cmd/kaspawallet/libkaspawallet/transaction_test.go @@ -2,6 +2,9 @@ package libkaspawallet_test import ( "fmt" + "strings" + "testing" + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/kaspanet/kaspad/domain/consensus" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -10,8 +13,6 @@ import ( "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "github.com/kaspanet/kaspad/util" - "strings" - "testing" ) func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) { @@ -106,7 +107,7 @@ func TestMultisig(t *testing.T) { Amount: 10, }}, selectedUTXOs) if err != nil { - t.Fatalf("CreateUnsignedTransaction: %+v", err) + t.Fatalf("CreateUnsignedTransactions: %+v", err) } isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction) @@ -267,7 +268,7 @@ func TestP2PK(t *testing.T) { Amount: 10, }}, selectedUTXOs) if err != nil { - t.Fatalf("CreateUnsignedTransaction: %+v", err) + t.Fatalf("CreateUnsignedTransactions: %+v", err) } isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction) diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go index e101c54404..92deee4479 100644 --- a/cmd/kaspawallet/send.go +++ b/cmd/kaspawallet/send.go @@ -3,6 +3,7 @@ package main import ( "context" "fmt" + "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/kaspanet/kaspad/cmd/kaspawallet/keys" @@ -31,10 +32,11 @@ func send(conf *sendConfig) error { defer cancel() sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa) - createUnsignedTransactionResponse, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{ - Address: conf.ToAddress, - Amount: sendAmountSompi, - }) + createUnsignedTransactionsResponse, err := + daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{ + Address: conf.ToAddress, + Amount: sendAmountSompi, + }) if err != nil { return err } @@ -44,9 +46,13 @@ func send(conf *sendConfig) error { return err } - signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, createUnsignedTransactionResponse.UnsignedTransaction, keysFile.ECDSA) - if err != nil { - return err + signedTransactions := make([][]byte, len(createUnsignedTransactionsResponse.UnsignedTransactions)) + for i, unsignedTransaction := range createUnsignedTransactionsResponse.UnsignedTransactions { + signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, unsignedTransaction, keysFile.ECDSA) + if err != nil { + return err + } + signedTransactions[i] = signedTransaction } ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout) From 3a91e219010541744ff1a8d94ecaad4e7519b644 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 1 Feb 2022 14:43:03 +0200 Subject: [PATCH 05/40] Start working on split --- .../server/create_unsigned_transaction.go | 7 ++- cmd/kaspawallet/daemon/server/server.go | 4 ++ .../daemon/server/split_transaction.go | 48 +++++++++++++++++++ cmd/kaspawallet/libkaspawallet/transaction.go | 1 + .../mempool/check_transaction_standard.go | 8 ++-- .../check_transaction_standard_test.go | 9 ++-- util/txmass/calculator.go | 9 ++++ 7 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 cmd/kaspawallet/daemon/server/split_transaction.go diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 01df2f1a73..9256bf8351 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -54,7 +54,12 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return nil, err } - return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: [][]byte{unsignedTransaction}}, nil + unsignedTransactions, err := s.maybeSplitTransaction(unsignedTransaction) + if err != nil { + return nil, err + } + + return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil } func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) ( diff --git a/cmd/kaspawallet/daemon/server/server.go b/cmd/kaspawallet/daemon/server/server.go index d9fe254624..1335aa3a59 100644 --- a/cmd/kaspawallet/daemon/server/server.go +++ b/cmd/kaspawallet/daemon/server/server.go @@ -7,6 +7,8 @@ import ( "sync" "time" + "github.com/kaspanet/kaspad/util/txmass" + "github.com/kaspanet/kaspad/util/profiling" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" @@ -32,6 +34,7 @@ type server struct { keysFile *keys.File shutdown chan struct{} addressSet walletAddressSet + txMassCalculator *txmass.Calculator } // Start starts the kaspawalletd server @@ -69,6 +72,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri keysFile: keysFile, shutdown: make(chan struct{}), addressSet: make(walletAddressSet), + txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp), } spawn("serverInstance.sync", func() { diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go new file mode 100644 index 0000000000..6a76dcbd9c --- /dev/null +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -0,0 +1,48 @@ +package server + +import ( + "github.com/kaspanet/go-secp256k1" + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" +) + +func (s *server) maybeSplitTransaction(partiallySignedTransactionBytes []byte) ([][]byte, error) { + partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(partiallySignedTransactionBytes) + if err != nil { + return nil, err + } + + partiallySignedTransactions := s.maybeSplitTransactionInner(partiallySignedTransaction) + if len(partiallySignedTransactions) > 1 { + partiallySignedTransactions = append(partiallySignedTransactions, mergeTransaction(partiallySignedTransactions)) + } + + partiallySignedTransactionsBytes := make([][]byte, len(partiallySignedTransactions)) + for i, partiallySignedTransaction := range partiallySignedTransactions { + partiallySignedTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(partiallySignedTransaction) + if err != nil { + return nil, err + } + } + return partiallySignedTransactionsBytes, nil +} + +func mergeTransaction(transactions []*serialization.PartiallySignedTransaction) *serialization.PartiallySignedTransaction { + // TODO +} + +func (s *server) maybeSplitTransactionInner(partiallySignedTransaction *serialization.PartiallySignedTransaction) []*serialization.PartiallySignedTransaction { + transactionMass := s.txMassCalculator.CalculateTransactionMass(partiallySignedTransaction.Tx) + transactionMass += s.estimateMassIncreaseForSignatures(partiallySignedTransaction.Tx) +} + +func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.DomainTransaction) uint64 { + var signatureSize uint64 + if s.keysFile.ECDSA { + signatureSize = secp256k1.SerializedECDSASignatureSize + } else { + signatureSize = secp256k1.SerializedSchnorrSignatureSize + } + + return uint64(s.keysFile.MinimumSignatures) * signatureSize * s.txMassCalculator.MassPerTxByte() +} diff --git a/cmd/kaspawallet/libkaspawallet/transaction.go b/cmd/kaspawallet/libkaspawallet/transaction.go index a1b2e549f2..37f122d94f 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction.go +++ b/cmd/kaspawallet/libkaspawallet/transaction.go @@ -159,6 +159,7 @@ func createUnsignedTransaction( Tx: domainTransaction, PartiallySignedInputs: partiallySignedInputs, }, nil + } // IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast. diff --git a/domain/miningmanager/mempool/check_transaction_standard.go b/domain/miningmanager/mempool/check_transaction_standard.go index abc62a7b60..9a66e2b677 100644 --- a/domain/miningmanager/mempool/check_transaction_standard.go +++ b/domain/miningmanager/mempool/check_transaction_standard.go @@ -36,9 +36,9 @@ const ( // (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650 maximumStandardSignatureScriptSize = 1650 - // maximumStandardTransactionMass is the maximum mass allowed for transactions that + // MaximumStandardTransactionMass is the maximum mass allowed for transactions that // are considered standard and will therefore be relayed and considered for mining. - maximumStandardTransactionMass = 100000 + MaximumStandardTransactionMass = 100_000 ) func (mp *mempool) checkTransactionStandardInIsolation(transaction *externalapi.DomainTransaction) error { @@ -58,9 +58,9 @@ func (mp *mempool) checkTransactionStandardInIsolation(transaction *externalapi. // almost as much to process as the sender fees, limit the maximum // size of a transaction. This also helps mitigate CPU exhaustion // attacks. - if transaction.Mass > maximumStandardTransactionMass { + if transaction.Mass > MaximumStandardTransactionMass { str := fmt.Sprintf("transaction mass of %d is larger than max allowed size of %d", - transaction.Mass, maximumStandardTransactionMass) + transaction.Mass, MaximumStandardTransactionMass) return transactionRuleError(RejectNonstandard, str) } diff --git a/domain/miningmanager/mempool/check_transaction_standard_test.go b/domain/miningmanager/mempool/check_transaction_standard_test.go index 04e6686749..9366e5a534 100644 --- a/domain/miningmanager/mempool/check_transaction_standard_test.go +++ b/domain/miningmanager/mempool/check_transaction_standard_test.go @@ -6,10 +6,11 @@ package mempool import ( "bytes" - "github.com/kaspanet/kaspad/domain/consensusreference" "math" "testing" + "github.com/kaspanet/kaspad/domain/consensusreference" + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" "github.com/kaspanet/kaspad/domain/consensus" @@ -45,13 +46,13 @@ func TestCalcMinRequiredTxRelayFee(t *testing.T) { }, { "max standard tx size with default minimum relay fee", - maximumStandardTransactionMass, + MaximumStandardTransactionMass, defaultMinimumRelayTransactionFee, 100000, }, { "max standard tx size with max sompi relay fee", - maximumStandardTransactionMass, + MaximumStandardTransactionMass, constants.MaxSompi, constants.MaxSompi, }, @@ -249,7 +250,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) { name: "Transaction size is too large", tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ Value: 0, - ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, maximumStandardTransactionMass+1), 0}, + ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, MaximumStandardTransactionMass+1), 0}, }}}, height: 300000, isStandard: false, diff --git a/util/txmass/calculator.go b/util/txmass/calculator.go index 037c663158..5206f7fe57 100644 --- a/util/txmass/calculator.go +++ b/util/txmass/calculator.go @@ -21,6 +21,15 @@ func NewCalculator(massPerTxByte, massPerScriptPubKeyByte, massPerSigOp uint64) } } +// MassPerTxByte returns the mass per transaction byte configured for this Calculator +func (c *Calculator) MassPerTxByte() uint64 { return c.massPerTxByte } + +// MassPerScriptPubKeyByte returns the mass per ScriptPublicKey byte configured for this Calculator +func (c *Calculator) MassPerScriptPubKeyByte() uint64 { return c.massPerScriptPubKeyByte } + +// MassPerSigOp returns the mass per SigOp byte configured for this Calculator +func (c *Calculator) MassPerSigOp() uint64 { return c.massPerSigOp } + // CalculateTransactionMass calculates the mass of the given transaction func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTransaction) uint64 { if transactionhelper.IsCoinBase(transaction) { From d03d9189c655a362316d9ca83f7d7fdab19d1e73 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 9 Feb 2022 10:42:39 +0200 Subject: [PATCH 06/40] Implement maybeSplitTransactionInner --- .../daemon/server/split_transaction.go | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 6a76dcbd9c..7bc5f9fa0a 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -4,6 +4,7 @@ import ( "github.com/kaspanet/go-secp256k1" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" ) func (s *server) maybeSplitTransaction(partiallySignedTransactionBytes []byte) ([][]byte, error) { @@ -31,9 +32,36 @@ func mergeTransaction(transactions []*serialization.PartiallySignedTransaction) // TODO } -func (s *server) maybeSplitTransactionInner(partiallySignedTransaction *serialization.PartiallySignedTransaction) []*serialization.PartiallySignedTransaction { - transactionMass := s.txMassCalculator.CalculateTransactionMass(partiallySignedTransaction.Tx) - transactionMass += s.estimateMassIncreaseForSignatures(partiallySignedTransaction.Tx) +func (s *server) maybeSplitTransactionInner( + transaction *serialization.PartiallySignedTransaction) []*serialization.PartiallySignedTransaction { + + transactionMass := s.txMassCalculator.CalculateTransactionMass(transaction.Tx) + transactionMass += s.estimateMassIncreaseForSignatures(transaction.Tx) + + if transactionMass < mempool.MaximumStandardTransactionMass { + return []*serialization.PartiallySignedTransaction{transaction} + } + + splitCount := int(transactionMass / mempool.MaximumStandardTransactionMass) + if transactionMass%mempool.MaximumStandardTransactionMass > 0 { + splitCount++ + } + inputCountPerSplit := len(transaction.Tx.Inputs) / splitCount + + splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount) + for i := 0; i < splitCount; i++ { + startIndex := i * inputCountPerSplit + endIndex := startIndex + inputCountPerSplit + splitTransactions[i] = s.createSplitTransaction(transaction, startIndex, endIndex) + } + + return splitTransactions +} + +func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction, + startIndex int, endIndex int) *serialization.PartiallySignedTransaction { + + // TODO } func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.DomainTransaction) uint64 { @@ -45,4 +73,5 @@ func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.Doma } return uint64(s.keysFile.MinimumSignatures) * signatureSize * s.txMassCalculator.MassPerTxByte() + // TODO: Add increase per sigop after https://github.com/kaspanet/kaspad/issues/1874 is handled } From 2be2632803f5cf20852a430e88668aa167edddc1 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 9 Feb 2022 10:50:12 +0200 Subject: [PATCH 07/40] estimateMassIncreaseForSignatures should multiply by the number of inputs --- cmd/kaspawallet/daemon/server/split_transaction.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 7bc5f9fa0a..3f7314bae7 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -72,6 +72,9 @@ func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.Doma signatureSize = secp256k1.SerializedSchnorrSignatureSize } - return uint64(s.keysFile.MinimumSignatures) * signatureSize * s.txMassCalculator.MassPerTxByte() + return uint64(len(transaction.Inputs)) * + uint64(s.keysFile.MinimumSignatures) * + signatureSize * + s.txMassCalculator.MassPerTxByte() // TODO: Add increase per sigop after https://github.com/kaspanet/kaspad/issues/1874 is handled } From 6218cdbbe4baff3b9bf55fe84df2bcd42872a9fd Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 9 Feb 2022 11:05:09 +0200 Subject: [PATCH 08/40] Implement createSplitTransaction --- .../server/create_unsigned_transaction.go | 5 +- .../daemon/server/split_transaction.go | 52 ++++++++++++++++--- 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 9256bf8351..920fee6c30 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -10,6 +10,9 @@ import ( "github.com/pkg/errors" ) +// TODO: Implement a better fee estimation mechanism +const feePerInput = 10000 + func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) ( *pb.CreateUnsignedTransactionsResponse, error) { s.lock.Lock() @@ -29,8 +32,6 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return nil, err } - // TODO: Implement a better fee estimation mechanism - const feePerInput = 10000 selectedUTXOs, changeSompi, err := s.selectUTXOs(request.Amount, feePerInput) if err != nil { return nil, err diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 3f7314bae7..baa450bc8e 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -2,9 +2,11 @@ package server import ( "github.com/kaspanet/go-secp256k1" + "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/miningmanager/mempool" + "github.com/kaspanet/kaspad/util" ) func (s *server) maybeSplitTransaction(partiallySignedTransactionBytes []byte) ([][]byte, error) { @@ -13,7 +15,10 @@ func (s *server) maybeSplitTransaction(partiallySignedTransactionBytes []byte) ( return nil, err } - partiallySignedTransactions := s.maybeSplitTransactionInner(partiallySignedTransaction) + partiallySignedTransactions, err := s.maybeSplitTransactionInner(partiallySignedTransaction) + if err != nil { + return nil, err + } if len(partiallySignedTransactions) > 1 { partiallySignedTransactions = append(partiallySignedTransactions, mergeTransaction(partiallySignedTransactions)) } @@ -32,14 +37,14 @@ func mergeTransaction(transactions []*serialization.PartiallySignedTransaction) // TODO } -func (s *server) maybeSplitTransactionInner( - transaction *serialization.PartiallySignedTransaction) []*serialization.PartiallySignedTransaction { +func (s *server) maybeSplitTransactionInner(transaction *serialization.PartiallySignedTransaction) ( + []*serialization.PartiallySignedTransaction, error) { transactionMass := s.txMassCalculator.CalculateTransactionMass(transaction.Tx) transactionMass += s.estimateMassIncreaseForSignatures(transaction.Tx) if transactionMass < mempool.MaximumStandardTransactionMass { - return []*serialization.PartiallySignedTransaction{transaction} + return []*serialization.PartiallySignedTransaction{transaction}, nil } splitCount := int(transactionMass / mempool.MaximumStandardTransactionMass) @@ -48,20 +53,51 @@ func (s *server) maybeSplitTransactionInner( } inputCountPerSplit := len(transaction.Tx.Inputs) / splitCount + changeAddress, err := s.changeAddress() + if err != nil { + return nil, err + } + splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount) for i := 0; i < splitCount; i++ { startIndex := i * inputCountPerSplit endIndex := startIndex + inputCountPerSplit - splitTransactions[i] = s.createSplitTransaction(transaction, startIndex, endIndex) + splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex) + if err != nil { + return nil, err + } } - return splitTransactions + return splitTransactions, nil } func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction, - startIndex int, endIndex int) *serialization.PartiallySignedTransaction { + changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) { - // TODO + selectedUTXOs := make([]*libkaspawallet.UTXO, endIndex-startIndex) + totalSompi := uint64(0) + + for i := startIndex; i < endIndex; i++ { + selectedUTXOs[i-startIndex] = &libkaspawallet.UTXO{ + Outpoint: &transaction.Tx.Inputs[i].PreviousOutpoint, + UTXOEntry: transaction.Tx.Inputs[i].UTXOEntry, + DerivationPath: transaction.PartiallySignedInputs[i].DerivationPath, + } + + totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount() + totalSompi -= feePerInput + } + unsignedTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys, + s.keysFile.MinimumSignatures, + []*libkaspawallet.Payment{{ + Address: changeAddress, + Amount: totalSompi, + }}, selectedUTXOs) + if err != nil { + return nil, err + } + + return serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes) } func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.DomainTransaction) uint64 { From c7d026705449f6a5e94520bad9a5bc60a7f4c41c Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 9 Feb 2022 11:57:55 +0200 Subject: [PATCH 09/40] Implement mergeTransactions --- cmd/kaspawallet/daemon/server/address.go | 12 ++- .../server/create_unsigned_transaction.go | 2 +- .../daemon/server/split_transaction.go | 85 ++++++++++++++----- domain/consensus/utils/constants/constants.go | 4 + .../fill_inputs_and_get_missing_parents.go | 3 +- .../miningmanager/mempool/mempool_utxo_set.go | 4 +- .../miningmanager/mempool/model/constants.go | 6 -- domain/miningmanager/mempool/orphan_pool.go | 4 +- 8 files changed, 87 insertions(+), 33 deletions(-) delete mode 100644 domain/miningmanager/mempool/model/constants.go diff --git a/cmd/kaspawallet/daemon/server/address.go b/cmd/kaspawallet/daemon/server/address.go index 621c442328..188ba896bb 100644 --- a/cmd/kaspawallet/daemon/server/address.go +++ b/cmd/kaspawallet/daemon/server/address.go @@ -10,15 +10,15 @@ import ( "github.com/pkg/errors" ) -func (s *server) changeAddress() (util.Address, error) { +func (s *server) changeAddress() (util.Address, *walletAddress, error) { err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1) if err != nil { - return nil, err + return nil, nil, err } err = s.keysFile.Save() if err != nil { - return nil, err + return nil, nil, err } walletAddr := &walletAddress{ @@ -27,7 +27,11 @@ func (s *server) changeAddress() (util.Address, error) { keyChain: libkaspawallet.InternalKeychain, } path := s.walletAddressPath(walletAddr) - return libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA) + address, err := libkaspawallet.Address(s.params, s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, path, s.keysFile.ECDSA) + if err != nil { + return nil, nil, err + } + return address, walletAddr, nil } func (s *server) ShowAddresses(_ context.Context, request *pb.ShowAddressesRequest) (*pb.ShowAddressesResponse, error) { diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 920fee6c30..ff9c854b12 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -37,7 +37,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return nil, err } - changeAddress, err := s.changeAddress() + changeAddress, _, err := s.changeAddress() if err != nil { return nil, err } diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index baa450bc8e..3fa4f58a8b 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -5,40 +5,91 @@ import ( "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/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/util" ) -func (s *server) maybeSplitTransaction(partiallySignedTransactionBytes []byte) ([][]byte, error) { - partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(partiallySignedTransactionBytes) +func (s *server) maybeSplitTransaction(transactionBytes []byte) ([][]byte, error) { + transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes) if err != nil { return nil, err } - partiallySignedTransactions, err := s.maybeSplitTransactionInner(partiallySignedTransaction) + splitAddress, splitWalletAddress, err := s.changeAddress() if err != nil { return nil, err } - if len(partiallySignedTransactions) > 1 { - partiallySignedTransactions = append(partiallySignedTransactions, mergeTransaction(partiallySignedTransactions)) + + splitTransactions, err := s.maybeSplitTransactionInner(transaction, splitAddress) + if err != nil { + return nil, err + } + if len(splitTransactions) > 1 { + mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, splitAddress, splitWalletAddress) + if err != nil { + return nil, err + } + splitTransactions = append(splitTransactions, mergeTransaction) } - partiallySignedTransactionsBytes := make([][]byte, len(partiallySignedTransactions)) - for i, partiallySignedTransaction := range partiallySignedTransactions { - partiallySignedTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(partiallySignedTransaction) + splitTransactionsBytes := make([][]byte, len(splitTransactions)) + for i, splitTransaction := range splitTransactions { + splitTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(splitTransaction) if err != nil { return nil, err } } - return partiallySignedTransactionsBytes, nil + return splitTransactionsBytes, nil } -func mergeTransaction(transactions []*serialization.PartiallySignedTransaction) *serialization.PartiallySignedTransaction { - // TODO +func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySignedTransaction, + originalTransaction *serialization.PartiallySignedTransaction, splitAddress util.Address, + splitWalletAddress *walletAddress) (*serialization.PartiallySignedTransaction, error) { + + targetAddress, err := util.NewAddressScriptHash(originalTransaction.Tx.Outputs[0].ScriptPublicKey.Script, s.params.Prefix) + if err != nil { + return nil, err + } + changeAddress, err := util.NewAddressScriptHash(originalTransaction.Tx.Outputs[1].ScriptPublicKey.Script, s.params.Prefix) + if err != nil { + return nil, err + } + + totalValue := uint64(0) + sentValue := originalTransaction.Tx.Outputs[0].Value + utxos := make([]*libkaspawallet.UTXO, len(splitTransactions)) + for i, splitTransaction := range splitTransactions { + output := splitTransaction.Tx.Outputs[0] + utxos[i] = &libkaspawallet.UTXO{ + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(splitTransaction.Tx), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, constants.UnacceptedDAAScore), + DerivationPath: s.walletAddressPath(splitWalletAddress), + } + totalValue += output.Value + totalValue -= feePerInput + } + + mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys, + s.keysFile.MinimumSignatures, + []*libkaspawallet.Payment{{ + Address: targetAddress, + Amount: sentValue, + }, { + Address: changeAddress, + Amount: totalValue - sentValue, + }}, utxos) + + return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes) } -func (s *server) maybeSplitTransactionInner(transaction *serialization.PartiallySignedTransaction) ( - []*serialization.PartiallySignedTransaction, error) { +func (s *server) maybeSplitTransactionInner(transaction *serialization.PartiallySignedTransaction, + splitAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) { transactionMass := s.txMassCalculator.CalculateTransactionMass(transaction.Tx) transactionMass += s.estimateMassIncreaseForSignatures(transaction.Tx) @@ -53,16 +104,12 @@ func (s *server) maybeSplitTransactionInner(transaction *serialization.Partially } inputCountPerSplit := len(transaction.Tx.Inputs) / splitCount - changeAddress, err := s.changeAddress() - if err != nil { - return nil, err - } - splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount) for i := 0; i < splitCount; i++ { startIndex := i * inputCountPerSplit endIndex := startIndex + inputCountPerSplit - splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex) + var err error + splitTransactions[i], err = s.createSplitTransaction(transaction, splitAddress, startIndex, endIndex) if err != nil { return nil, err } diff --git a/domain/consensus/utils/constants/constants.go b/domain/consensus/utils/constants/constants.go index 219df23a4e..55d06c7b18 100644 --- a/domain/consensus/utils/constants/constants.go +++ b/domain/consensus/utils/constants/constants.go @@ -40,4 +40,8 @@ const ( // This is technically 255, but we clamped it at 256 - block level of mainnet genesis // This means that any block that has a level lower or equal to genesis will be level 0. MaxBlockLevel = 225 + + // UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool, or otherwise + // not-yet-accepted transactions. + UnacceptedDAAScore = math.MaxUint64 ) diff --git a/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go b/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go index e269a079ae..ad9cfc6075 100644 --- a/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go +++ b/domain/miningmanager/mempool/fill_inputs_and_get_missing_parents.go @@ -3,6 +3,7 @@ package mempool import ( "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" "github.com/kaspanet/kaspad/domain/miningmanager/mempool/model" "github.com/pkg/errors" @@ -42,6 +43,6 @@ func fillInputs(transaction *externalapi.DomainTransaction, parentsInPool model. } relevantOutput := parent.Transaction().Outputs[input.PreviousOutpoint.Index] input.UTXOEntry = utxo.NewUTXOEntry(relevantOutput.Value, relevantOutput.ScriptPublicKey, - false, model.UnacceptedDAAScore) + false, constants.UnacceptedDAAScore) } } diff --git a/domain/miningmanager/mempool/mempool_utxo_set.go b/domain/miningmanager/mempool/mempool_utxo_set.go index efb1dd9b84..aa20d05d5d 100644 --- a/domain/miningmanager/mempool/mempool_utxo_set.go +++ b/domain/miningmanager/mempool/mempool_utxo_set.go @@ -3,6 +3,8 @@ package mempool import ( "fmt" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -42,7 +44,7 @@ func (mpus *mempoolUTXOSet) addTransaction(transaction *model.MempoolTransaction outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID(), Index: uint32(i)} mpus.poolUnspentOutputs[outpoint] = - utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, model.UnacceptedDAAScore) + utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, constants.UnacceptedDAAScore) } } diff --git a/domain/miningmanager/mempool/model/constants.go b/domain/miningmanager/mempool/model/constants.go deleted file mode 100644 index d2d743bdc2..0000000000 --- a/domain/miningmanager/mempool/model/constants.go +++ /dev/null @@ -1,6 +0,0 @@ -package model - -import "math" - -// UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool. -const UnacceptedDAAScore = math.MaxUint64 diff --git a/domain/miningmanager/mempool/orphan_pool.go b/domain/miningmanager/mempool/orphan_pool.go index df54903fd7..ce7d77774c 100644 --- a/domain/miningmanager/mempool/orphan_pool.go +++ b/domain/miningmanager/mempool/orphan_pool.go @@ -3,6 +3,8 @@ package mempool import ( "fmt" + "github.com/kaspanet/kaspad/domain/consensus/utils/constants" + "github.com/kaspanet/kaspad/domain/consensus/ruleerrors" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" @@ -153,7 +155,7 @@ func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransactio for _, input := range orphan.Transaction().Inputs { if input.PreviousOutpoint.Equal(&outpoint) && input.UTXOEntry == nil { input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, - model.UnacceptedDAAScore) + constants.UnacceptedDAAScore) break } } From 4abd3f1a701da4fa038b7911a9b0cf27beb25210 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Thu, 10 Feb 2022 15:24:52 +0200 Subject: [PATCH 10/40] Broadcast all transaction, not only 1 --- cmd/kaspawallet/send.go | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go index 92deee4479..aaadcf33a7 100644 --- a/cmd/kaspawallet/send.go +++ b/cmd/kaspawallet/send.go @@ -55,17 +55,25 @@ func send(conf *sendConfig) error { signedTransactions[i] = signedTransaction } - ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout) - defer cancel2() - broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{ - Transaction: signedTransaction, - }) - if err != nil { - return err + if len(signedTransactions) > 1 { + fmt.Printf("Broadcasting %d transactions", len(signedTransactions)) } + for _, signedTransaction := range signedTransactions { + return func() error { // surround with func so that defer runs separately per transaction + ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout) + defer cancel2() + broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{ + Transaction: signedTransaction, + }) + if err != nil { + return err + } - fmt.Println("Transaction was sent successfully") - fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID) + fmt.Println("Transaction was sent successfully") + fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID) + return nil + }() + } return nil } From 78b19d9db06cd73da999b5097a2e360dc7e431b5 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 16 Feb 2022 15:32:32 +0200 Subject: [PATCH 11/40] workaround missing UTXOEntry in partially signed transaction --- cmd/kaspawallet/daemon/server/split_transaction.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 3fa4f58a8b..6b6283df8a 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -125,10 +125,13 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign totalSompi := uint64(0) for i := startIndex; i < endIndex; i++ { + partiallySignedInput := transaction.PartiallySignedInputs[i] selectedUTXOs[i-startIndex] = &libkaspawallet.UTXO{ - Outpoint: &transaction.Tx.Inputs[i].PreviousOutpoint, - UTXOEntry: transaction.Tx.Inputs[i].UTXOEntry, - DerivationPath: transaction.PartiallySignedInputs[i].DerivationPath, + Outpoint: &transaction.Tx.Inputs[i].PreviousOutpoint, + UTXOEntry: utxo.NewUTXOEntry( + partiallySignedInput.PrevOutput.Value, partiallySignedInput.PrevOutput.ScriptPublicKey, + false, constants.UnacceptedDAAScore), + DerivationPath: partiallySignedInput.DerivationPath, } totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount() From 529a0271e8495033785d924c30582e2182e77936 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 16 Feb 2022 15:43:55 +0200 Subject: [PATCH 12/40] Bugfix in broadcast loop --- cmd/kaspawallet/send.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go index aaadcf33a7..c3fa1e8000 100644 --- a/cmd/kaspawallet/send.go +++ b/cmd/kaspawallet/send.go @@ -56,10 +56,10 @@ func send(conf *sendConfig) error { } if len(signedTransactions) > 1 { - fmt.Printf("Broadcasting %d transactions", len(signedTransactions)) + fmt.Printf("Broadcasting %d transactions\n", len(signedTransactions)) } for _, signedTransaction := range signedTransactions { - return func() error { // surround with func so that defer runs separately per transaction + err := func() error { // surround with func so that defer runs separately per transaction ctx2, cancel2 := context.WithTimeout(context.Background(), daemonTimeout) defer cancel2() broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{ @@ -73,6 +73,9 @@ func send(conf *sendConfig) error { fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID) return nil }() + if err != nil { + return err + } } return nil From 612726e5a6d0f223cad8d2cc3e50b3c12a772d13 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Thu, 17 Feb 2022 10:14:38 +0200 Subject: [PATCH 13/40] Add underscores in some constants --- infrastructure/config/config.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/infrastructure/config/config.go b/infrastructure/config/config.go index b334276775..c886671827 100644 --- a/infrastructure/config/config.go +++ b/infrastructure/config/config.go @@ -42,16 +42,16 @@ const ( defaultMaxRPCClients = 10 defaultMaxRPCWebsockets = 25 defaultMaxRPCConcurrentReqs = 20 - defaultBlockMaxMass = 10000000 + defaultBlockMaxMass = 10_000_000 blockMaxMassMin = 1000 - blockMaxMassMax = 10000000 + blockMaxMassMax = 10_000_000 defaultMinRelayTxFee = 1e-5 // 1 sompi per byte defaultMaxOrphanTransactions = 100 //DefaultMaxOrphanTxSize is the default maximum size for an orphan transaction - DefaultMaxOrphanTxSize = 100000 - defaultSigCacheMaxSize = 100000 + DefaultMaxOrphanTxSize = 100_000 + defaultSigCacheMaxSize = 100_000 sampleConfigFilename = "sample-kaspad.conf" - defaultMaxUTXOCacheSize = 5000000000 + defaultMaxUTXOCacheSize = 5_000_000_000 defaultProtocolVersion = 4 ) From 15369e27e1083928d0f76f3b13d0018f0b36ce6d Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Thu, 17 Feb 2022 10:29:41 +0200 Subject: [PATCH 14/40] Make all nets RelayNonStdTxs: false --- domain/dagconfig/params.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/domain/dagconfig/params.go b/domain/dagconfig/params.go index 99ba74911e..3ae530a430 100644 --- a/domain/dagconfig/params.go +++ b/domain/dagconfig/params.go @@ -311,7 +311,7 @@ var TestnetParams = Params{ MinerConfirmationWindow: 2016, // Mempool parameters - RelayNonStdTxs: true, + RelayNonStdTxs: false, // AcceptUnroutable specifies whether this network accepts unroutable // IP addresses, such as 10.0.0.0/8 @@ -377,7 +377,7 @@ var SimnetParams = Params{ MinerConfirmationWindow: 100, // Mempool parameters - RelayNonStdTxs: true, + RelayNonStdTxs: false, // AcceptUnroutable specifies whether this network accepts unroutable // IP addresses, such as 10.0.0.0/8 @@ -434,7 +434,7 @@ var DevnetParams = Params{ MinerConfirmationWindow: 2016, // Mempool parameters - RelayNonStdTxs: true, + RelayNonStdTxs: false, // AcceptUnroutable specifies whether this network accepts unroutable // IP addresses, such as 10.0.0.0/8 From 135c08bee0a2ca7b532395741205c527cc083bdc Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Thu, 17 Feb 2022 11:19:33 +0200 Subject: [PATCH 15/40] Change estimateMassIncreaseForSignatures to estimateMassAfterSignatures --- .../daemon/server/split_transaction.go | 31 +++- .../daemon/server/split_transaction_test.go | 149 ++++++++++++++++++ .../serialization/serialization.go | 33 ++++ cmd/kaspawallet/libkaspawallet/sign.go | 1 - cmd/kaspawallet/libkaspawallet/transaction.go | 4 +- 5 files changed, 207 insertions(+), 11 deletions(-) create mode 100644 cmd/kaspawallet/daemon/server/split_transaction_test.go diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 6b6283df8a..d721ed68e2 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -2,6 +2,7 @@ package server import ( "github.com/kaspanet/go-secp256k1" + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -91,8 +92,10 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi func (s *server) maybeSplitTransactionInner(transaction *serialization.PartiallySignedTransaction, splitAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) { - transactionMass := s.txMassCalculator.CalculateTransactionMass(transaction.Tx) - transactionMass += s.estimateMassIncreaseForSignatures(transaction.Tx) + transactionMass, err := s.estimateMassAfterSignatures(transaction) + if err != nil { + return nil, err + } if transactionMass < mempool.MaximumStandardTransactionMass { return []*serialization.PartiallySignedTransaction{transaction}, nil @@ -150,7 +153,8 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign return serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes) } -func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.DomainTransaction) uint64 { +func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) { + transaction = transaction.Clone() var signatureSize uint64 if s.keysFile.ECDSA { signatureSize = secp256k1.SerializedECDSASignatureSize @@ -158,9 +162,20 @@ func (s *server) estimateMassIncreaseForSignatures(transaction *externalapi.Doma signatureSize = secp256k1.SerializedSchnorrSignatureSize } - return uint64(len(transaction.Inputs)) * - uint64(s.keysFile.MinimumSignatures) * - signatureSize * - s.txMassCalculator.MassPerTxByte() - // TODO: Add increase per sigop after https://github.com/kaspanet/kaspad/issues/1874 is handled + for i, input := range transaction.PartiallySignedInputs { + for j, pubKeyPair := range input.PubKeySignaturePairs { + if uint32(j) >= s.keysFile.MinimumSignatures { + break + } + pubKeyPair.Signature = make([]byte, signatureSize+1) // +1 for SigHashType + } + transaction.Tx.Inputs[i].SigOpCount = byte(len(input.PubKeySignaturePairs)) + } + + transactionWithEverything, err := libkaspawallet.ExtractTransactionDeserialized(transaction, s.keysFile.ECDSA) + if err != nil { + return 0, err + } + + return s.txMassCalculator.CalculateTransactionMass(transactionWithEverything), nil } diff --git a/cmd/kaspawallet/daemon/server/split_transaction_test.go b/cmd/kaspawallet/daemon/server/split_transaction_test.go new file mode 100644 index 0000000000..1c9ad69eaa --- /dev/null +++ b/cmd/kaspawallet/daemon/server/split_transaction_test.go @@ -0,0 +1,149 @@ +package server + +import ( + "testing" + + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" + + "github.com/kaspanet/kaspad/cmd/kaspawallet/keys" + "github.com/kaspanet/kaspad/util/txmass" + + "github.com/kaspanet/kaspad/domain/dagconfig" + + "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" + "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" + "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" + "github.com/kaspanet/kaspad/domain/consensus/utils/utxo" + + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" + "github.com/kaspanet/kaspad/domain/consensus" + "github.com/kaspanet/kaspad/domain/consensus/utils/testutils" +) + +func TestEstimateMassAfterSignatures(t *testing.T) { + testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) { + unsignedTransactionBytes, 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), + } + + unsignedTransaction, err := serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes) + if err != nil { + t.Fatalf("Error deserializing unsignedTransaction: %s", err) + } + + estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction) + + 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 testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) ( + []byte, []string, *dagconfig.Params, func(keepDataDir bool)) { + + consensusConfig.BlockCoinbaseMaturity = 0 + params := &consensusConfig.Params + + tc, teardown, err := consensus.NewFactory().NewTestConsensus(consensusConfig, "TestMultisig") + if err != nil { + t.Fatalf("Error setting up tc: %+v", err) + } + + const numKeys = 3 + mnemonics := make([]string, numKeys) + publicKeys := make([]string, numKeys) + for i := 0; i < numKeys; i++ { + var err error + mnemonics[i], err = libkaspawallet.CreateMnemonic() + if err != nil { + t.Fatalf("CreateMnemonic: %+v", err) + } + + publicKeys[i], err = libkaspawallet.MasterPublicKeyFromMnemonic(&consensusConfig.Params, mnemonics[i], true) + if err != nil { + t.Fatalf("MasterPublicKeyFromMnemonic: %+v", err) + } + } + + const minimumSignatures = 2 + path := "m/1/2/3" + address, err := libkaspawallet.Address(params, publicKeys, minimumSignatures, path, false) + if err != nil { + t.Fatalf("Address: %+v", err) + } + + scriptPublicKey, err := txscript.PayToAddrScript(address) + if err != nil { + t.Fatalf("PayToAddrScript: %+v", err) + } + + coinbaseData := &externalapi.DomainCoinbaseData{ + ScriptPublicKey: scriptPublicKey, + ExtraData: nil, + } + + fundingBlockHash, _, err := tc.AddBlock([]*externalapi.DomainHash{consensusConfig.GenesisHash}, coinbaseData, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + block1Hash, _, err := tc.AddBlock([]*externalapi.DomainHash{fundingBlockHash}, nil, nil) + if err != nil { + t.Fatalf("AddBlock: %+v", err) + } + + block1, err := tc.GetBlock(block1Hash) + if err != nil { + t.Fatalf("GetBlock: %+v", err) + } + + block1Tx := block1.Transactions[0] + block1TxOut := block1Tx.Outputs[0] + selectedUTXOs := []*libkaspawallet.UTXO{ + { + Outpoint: &externalapi.DomainOutpoint{ + TransactionID: *consensushashing.TransactionID(block1.Transactions[0]), + Index: 0, + }, + UTXOEntry: utxo.NewUTXOEntry(block1TxOut.Value, block1TxOut.ScriptPublicKey, true, 0), + DerivationPath: path, + }, + } + + unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures, + []*libkaspawallet.Payment{{ + Address: address, + Amount: 10, + }}, selectedUTXOs) + if err != nil { + t.Fatalf("CreateUnsignedTransactions: %+v", err) + } + + return unsignedTransaction, mnemonics, params, teardown +} diff --git a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go index 3abdaef4a8..a2537f5501 100644 --- a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go +++ b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go @@ -34,6 +34,39 @@ type PubKeySignaturePair struct { Signature []byte } +func (pst *PartiallySignedTransaction) Clone() *PartiallySignedTransaction { + clone := &PartiallySignedTransaction{ + Tx: pst.Tx.Clone(), + PartiallySignedInputs: make([]*PartiallySignedInput, len(pst.PartiallySignedInputs)), + } + for i, partiallySignedInput := range pst.PartiallySignedInputs { + clone.PartiallySignedInputs[i] = partiallySignedInput.Clone() + } + return clone +} + +func (psi PartiallySignedInput) Clone() *PartiallySignedInput { + clone := &PartiallySignedInput{ + PrevOutput: psi.PrevOutput.Clone(), + MinimumSignatures: psi.MinimumSignatures, + PubKeySignaturePairs: make([]*PubKeySignaturePair, len(psi.PubKeySignaturePairs)), + DerivationPath: psi.DerivationPath, + } + for i, pubKeySignaturePair := range psi.PubKeySignaturePairs { + clone.PubKeySignaturePairs[i] = pubKeySignaturePair.Clone() + } + return clone +} + +func (psp PubKeySignaturePair) Clone() *PubKeySignaturePair { + clone := &PubKeySignaturePair{ + ExtendedPublicKey: psp.ExtendedPublicKey, + Signature: make([]byte, len(psp.Signature)), + } + copy(clone.Signature, psp.Signature) + return clone +} + // DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction. func DeserializePartiallySignedTransaction(serializedPartiallySignedTransaction []byte) (*PartiallySignedTransaction, error) { protoPartiallySignedTransaction := &protoserialization.PartiallySignedTransaction{} diff --git a/cmd/kaspawallet/libkaspawallet/sign.go b/cmd/kaspawallet/libkaspawallet/sign.go index 5bef3bf084..98057ddb05 100644 --- a/cmd/kaspawallet/libkaspawallet/sign.go +++ b/cmd/kaspawallet/libkaspawallet/sign.go @@ -40,7 +40,6 @@ func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, e return nil, err } } - return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction) } diff --git a/cmd/kaspawallet/libkaspawallet/transaction.go b/cmd/kaspawallet/libkaspawallet/transaction.go index 37f122d94f..2f3499373b 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction.go +++ b/cmd/kaspawallet/libkaspawallet/transaction.go @@ -195,10 +195,10 @@ func ExtractTransaction(partiallySignedTransactionBytes []byte, ecdsa bool) (*ex return nil, err } - return extractTransaction(partiallySignedTransaction, ecdsa) + return ExtractTransactionDeserialized(partiallySignedTransaction, ecdsa) } -func extractTransaction(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) (*externalapi.DomainTransaction, error) { +func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) (*externalapi.DomainTransaction, error) { for i, input := range partiallySignedTransaction.PartiallySignedInputs { isMultisig := len(input.PubKeySignaturePairs) > 1 scriptBuilder := txscript.NewScriptBuilder() From 7239bc8c6262eb9037111cbf587ba5568277499a Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Thu, 17 Feb 2022 15:52:31 +0200 Subject: [PATCH 16/40] Allow situations where merge transaction doesn't have enough funds to pay fees --- .../daemon/server/split_transaction.go | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index d721ed68e2..c81698dcd4 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -76,15 +76,25 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi totalValue -= feePerInput } - mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys, - s.keysFile.MinimumSignatures, - []*libkaspawallet.Payment{{ + var payments []*libkaspawallet.Payment + if totalValue >= sentValue { + payments = []*libkaspawallet.Payment{{ Address: targetAddress, Amount: sentValue, }, { Address: changeAddress, Amount: totalValue - sentValue, - }}, utxos) + }} + } else { + // sometimes the fees from compound transactions make the total output higher than what's available from selected + // utxos, in such cases, the remaining fee will be deduced from the resulting amount + payments = []*libkaspawallet.Payment{{ + Address: targetAddress, + Amount: totalValue, + }} + } + mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys, + s.keysFile.MinimumSignatures, payments, utxos) return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes) } From dbec7e718d542b9aa39d71a4d6aeb97a9bb1e846 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 20 Feb 2022 13:01:33 +0200 Subject: [PATCH 17/40] Add comments --- .../libkaspawallet/serialization/serialization.go | 3 +++ cmd/kaspawallet/libkaspawallet/transaction.go | 5 ++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go index a2537f5501..7ec37a9dec 100644 --- a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go +++ b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go @@ -34,6 +34,7 @@ type PubKeySignaturePair struct { Signature []byte } +// Clone creates a deep-clone of this PartiallySignedTransaction func (pst *PartiallySignedTransaction) Clone() *PartiallySignedTransaction { clone := &PartiallySignedTransaction{ Tx: pst.Tx.Clone(), @@ -45,6 +46,7 @@ func (pst *PartiallySignedTransaction) Clone() *PartiallySignedTransaction { return clone } +// Clone creates a deep-clone of this PartiallySignedInput func (psi PartiallySignedInput) Clone() *PartiallySignedInput { clone := &PartiallySignedInput{ PrevOutput: psi.PrevOutput.Clone(), @@ -58,6 +60,7 @@ func (psi PartiallySignedInput) Clone() *PartiallySignedInput { return clone } +// Clone creates a deep-clone of this PubKeySignaturePair func (psp PubKeySignaturePair) Clone() *PubKeySignaturePair { clone := &PubKeySignaturePair{ ExtendedPublicKey: psp.ExtendedPublicKey, diff --git a/cmd/kaspawallet/libkaspawallet/transaction.go b/cmd/kaspawallet/libkaspawallet/transaction.go index 2f3499373b..5682221cc3 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction.go +++ b/cmd/kaspawallet/libkaspawallet/transaction.go @@ -198,7 +198,10 @@ func ExtractTransaction(partiallySignedTransactionBytes []byte, ecdsa bool) (*ex return ExtractTransactionDeserialized(partiallySignedTransaction, ecdsa) } -func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) (*externalapi.DomainTransaction, error) { +// ExtractTransactionDeserialized does the same thing ExtractTransaction does, only receives the PartiallySignedTransaction +// in an already deserialized format +func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) ( + *externalapi.DomainTransaction, error) { for i, input := range partiallySignedTransaction.PartiallySignedInputs { isMultisig := len(input.PubKeySignaturePairs) > 1 scriptBuilder := txscript.NewScriptBuilder() From ee60333fa35605b6337333a1a42ed95ac89c37b9 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 20 Feb 2022 13:13:05 +0200 Subject: [PATCH 18/40] A few of renames --- .../daemon/server/create_unsigned_transaction.go | 2 +- cmd/kaspawallet/daemon/server/split_transaction.go | 10 +++++----- cmd/kaspawallet/libkaspawallet/transaction.go | 1 + 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index ff9c854b12..0495c98ce2 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -55,7 +55,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return nil, err } - unsignedTransactions, err := s.maybeSplitTransaction(unsignedTransaction) + unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction) if err != nil { return nil, err } diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index c81698dcd4..c64089f49c 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -13,7 +13,7 @@ import ( "github.com/kaspanet/kaspad/util" ) -func (s *server) maybeSplitTransaction(transactionBytes []byte) ([][]byte, error) { +func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte) ([][]byte, error) { transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes) if err != nil { return nil, err @@ -24,7 +24,7 @@ func (s *server) maybeSplitTransaction(transactionBytes []byte) ([][]byte, error return nil, err } - splitTransactions, err := s.maybeSplitTransactionInner(transaction, splitAddress) + splitTransactions, err := s.maybeSplitTransaction(transaction, splitAddress) if err != nil { return nil, err } @@ -99,7 +99,7 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes) } -func (s *server) maybeSplitTransactionInner(transaction *serialization.PartiallySignedTransaction, +func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySignedTransaction, splitAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) { transactionMass, err := s.estimateMassAfterSignatures(transaction) @@ -182,10 +182,10 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall transaction.Tx.Inputs[i].SigOpCount = byte(len(input.PubKeySignaturePairs)) } - transactionWithEverything, err := libkaspawallet.ExtractTransactionDeserialized(transaction, s.keysFile.ECDSA) + transactionWithSignatures, err := libkaspawallet.ExtractTransactionDeserialized(transaction, s.keysFile.ECDSA) if err != nil { return 0, err } - return s.txMassCalculator.CalculateTransactionMass(transactionWithEverything), nil + return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil } diff --git a/cmd/kaspawallet/libkaspawallet/transaction.go b/cmd/kaspawallet/libkaspawallet/transaction.go index 5682221cc3..62664b66bd 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction.go +++ b/cmd/kaspawallet/libkaspawallet/transaction.go @@ -202,6 +202,7 @@ func ExtractTransaction(partiallySignedTransactionBytes []byte, ecdsa bool) (*ex // in an already deserialized format func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.PartiallySignedTransaction, ecdsa bool) ( *externalapi.DomainTransaction, error) { + for i, input := range partiallySignedTransaction.PartiallySignedInputs { isMultisig := len(input.PubKeySignaturePairs) > 1 scriptBuilder := txscript.NewScriptBuilder() From 3fbf8bc3772deb975bb8e92a195999b31bac6b19 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 20 Feb 2022 13:21:18 +0200 Subject: [PATCH 19/40] Handle missed errors --- cmd/kaspawallet/daemon/server/split_transaction.go | 9 ++++++--- cmd/kaspawallet/daemon/server/split_transaction_test.go | 3 +++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index c64089f49c..dc90b0739f 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -29,7 +29,7 @@ func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte) ([][]byte return nil, err } if len(splitTransactions) > 1 { - mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, splitAddress, splitWalletAddress) + mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, splitWalletAddress) if err != nil { return nil, err } @@ -47,8 +47,8 @@ func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte) ([][]byte } func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySignedTransaction, - originalTransaction *serialization.PartiallySignedTransaction, splitAddress util.Address, - splitWalletAddress *walletAddress) (*serialization.PartiallySignedTransaction, error) { + originalTransaction *serialization.PartiallySignedTransaction, splitWalletAddress *walletAddress) ( + *serialization.PartiallySignedTransaction, error) { targetAddress, err := util.NewAddressScriptHash(originalTransaction.Tx.Outputs[0].ScriptPublicKey.Script, s.params.Prefix) if err != nil { @@ -95,6 +95,9 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi } mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, payments, utxos) + if err != nil { + return nil, err + } return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes) } diff --git a/cmd/kaspawallet/daemon/server/split_transaction_test.go b/cmd/kaspawallet/daemon/server/split_transaction_test.go index 1c9ad69eaa..0f521bb62f 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction_test.go +++ b/cmd/kaspawallet/daemon/server/split_transaction_test.go @@ -39,6 +39,9 @@ func TestEstimateMassAfterSignatures(t *testing.T) { } estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction) + if err != nil { + t.Fatalf("Error from estimateMassAfterSignatures: %s", err) + } signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false) if err != nil { From f21e1be325b73bf06cf266462cced8fa1c10ffd4 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 20 Feb 2022 13:56:01 +0200 Subject: [PATCH 20/40] Fix clone of PubKeySignaturePair to properly clone nil signatures --- .../libkaspawallet/serialization/serialization.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go index 7ec37a9dec..4abc6b5d97 100644 --- a/cmd/kaspawallet/libkaspawallet/serialization/serialization.go +++ b/cmd/kaspawallet/libkaspawallet/serialization/serialization.go @@ -64,9 +64,11 @@ func (psi PartiallySignedInput) Clone() *PartiallySignedInput { func (psp PubKeySignaturePair) Clone() *PubKeySignaturePair { clone := &PubKeySignaturePair{ ExtendedPublicKey: psp.ExtendedPublicKey, - Signature: make([]byte, len(psp.Signature)), } - copy(clone.Signature, psp.Signature) + if psp.Signature != nil { + clone.Signature = make([]byte, len(psp.Signature)) + copy(clone.Signature, psp.Signature) + } return clone } From e90a374295877c4f46be5415af9fd38b8c1672a0 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 21 Feb 2022 16:56:58 +0200 Subject: [PATCH 21/40] Add sanity check to make sure originalTransaction has exactly two outputs --- cmd/kaspawallet/daemon/server/split_transaction.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index dc90b0739f..2a555c8895 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -2,6 +2,7 @@ package server import ( "github.com/kaspanet/go-secp256k1" + "github.com/pkg/errors" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" @@ -50,6 +51,11 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi originalTransaction *serialization.PartiallySignedTransaction, splitWalletAddress *walletAddress) ( *serialization.PartiallySignedTransaction, error) { + if len(originalTransaction.Tx.Outputs) != 2 { + return nil, errors.Errorf("original transaction has %d outputs, while 2 are expected", + len(originalTransaction.Tx.Outputs)) + } + targetAddress, err := util.NewAddressScriptHash(originalTransaction.Tx.Outputs[0].ScriptPublicKey.Script, s.params.Prefix) if err != nil { return nil, err From 84f3ecd1a3238f0aeb8148ec7db020b9fb144bd7 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 22 Feb 2022 14:44:04 +0200 Subject: [PATCH 22/40] Re-use change address for splitAddress --- .../server/create_unsigned_transaction.go | 4 +- .../daemon/server/split_transaction.go | 47 ++++++++----------- 2 files changed, 21 insertions(+), 30 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 0495c98ce2..ac12711ba9 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -37,7 +37,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return nil, err } - changeAddress, _, err := s.changeAddress() + changeAddress, changeWalletAddress, err := s.changeAddress() if err != nil { return nil, err } @@ -55,7 +55,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat return nil, err } - unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction) + unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress) if err != nil { return nil, err } diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 2a555c8895..51f1b8a83e 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -14,23 +14,19 @@ import ( "github.com/kaspanet/kaspad/util" ) -func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte) ([][]byte, error) { +func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte, toAddress util.Address, + changeAddress util.Address, changeWalletAddress *walletAddress) ([][]byte, error) { transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes) if err != nil { return nil, err } - splitAddress, splitWalletAddress, err := s.changeAddress() - if err != nil { - return nil, err - } - - splitTransactions, err := s.maybeSplitTransaction(transaction, splitAddress) + splitTransactions, err := s.maybeSplitTransaction(transaction, changeAddress) if err != nil { return nil, err } if len(splitTransactions) > 1 { - mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, splitWalletAddress) + mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress) if err != nil { return nil, err } @@ -47,24 +43,19 @@ func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte) ([][]byte return splitTransactionsBytes, nil } -func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySignedTransaction, - originalTransaction *serialization.PartiallySignedTransaction, splitWalletAddress *walletAddress) ( - *serialization.PartiallySignedTransaction, error) { - - if len(originalTransaction.Tx.Outputs) != 2 { - return nil, errors.Errorf("original transaction has %d outputs, while 2 are expected", +func (s *server) mergeTransaction( + splitTransactions []*serialization.PartiallySignedTransaction, + originalTransaction *serialization.PartiallySignedTransaction, + toAddress util.Address, + changeAddress util.Address, + changeWalletAddress *walletAddress, +) (*serialization.PartiallySignedTransaction, error) { + numOutputs := len(originalTransaction.Tx.Outputs) + if numOutputs > 2 || numOutputs == 0 { + return nil, errors.Errorf("original transaction has %d outputs, while 1 or 2 are expected", len(originalTransaction.Tx.Outputs)) } - targetAddress, err := util.NewAddressScriptHash(originalTransaction.Tx.Outputs[0].ScriptPublicKey.Script, s.params.Prefix) - if err != nil { - return nil, err - } - changeAddress, err := util.NewAddressScriptHash(originalTransaction.Tx.Outputs[1].ScriptPublicKey.Script, s.params.Prefix) - if err != nil { - return nil, err - } - totalValue := uint64(0) sentValue := originalTransaction.Tx.Outputs[0].Value utxos := make([]*libkaspawallet.UTXO, len(splitTransactions)) @@ -76,7 +67,7 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi Index: 0, }, UTXOEntry: utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, constants.UnacceptedDAAScore), - DerivationPath: s.walletAddressPath(splitWalletAddress), + DerivationPath: s.walletAddressPath(changeWalletAddress), } totalValue += output.Value totalValue -= feePerInput @@ -85,7 +76,7 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi var payments []*libkaspawallet.Payment if totalValue >= sentValue { payments = []*libkaspawallet.Payment{{ - Address: targetAddress, + Address: toAddress, Amount: sentValue, }, { Address: changeAddress, @@ -95,7 +86,7 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi // sometimes the fees from compound transactions make the total output higher than what's available from selected // utxos, in such cases, the remaining fee will be deduced from the resulting amount payments = []*libkaspawallet.Payment{{ - Address: targetAddress, + Address: toAddress, Amount: totalValue, }} } @@ -109,7 +100,7 @@ func (s *server) mergeTransaction(splitTransactions []*serialization.PartiallySi } func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySignedTransaction, - splitAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) { + changeAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) { transactionMass, err := s.estimateMassAfterSignatures(transaction) if err != nil { @@ -131,7 +122,7 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne startIndex := i * inputCountPerSplit endIndex := startIndex + inputCountPerSplit var err error - splitTransactions[i], err = s.createSplitTransaction(transaction, splitAddress, startIndex, endIndex) + splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex) if err != nil { return nil, err } From 761d461c5991b1dbc22053ae490b395bd626aff9 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 22 Feb 2022 15:06:01 +0200 Subject: [PATCH 23/40] Add one more utxo if the total amount is smaller then what we need to send due to fees --- .../server/create_unsigned_transaction.go | 29 ++++++++++++++++ .../daemon/server/split_transaction.go | 33 +++++++++++-------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index ac12711ba9..7b9f919840 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -102,3 +102,32 @@ func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) ( return selectedUTXOs, totalValue - totalSpend, nil } + +func (s *server) oneMoreUTXOForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (*libkaspawallet.UTXO, error) { + dagInfo, err := s.rpcClient.GetBlockDAGInfo() + if err != nil { + return nil, err + } + + var utxo *walletUTXO + for _, utxo = range s.utxosSortedByAmount { + if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) { + continue + } + if utxo.UTXOEntry.Amount() < requiredAmount+feePerInput { + return nil, errors.Errorf("Insufficient funds for merge transaction") + } + for _, alreadySelectedUTXO := range alreadySelectedUTXOs { + if alreadySelectedUTXO.Outpoint.Equal(utxo.Outpoint) { + // comparing outpoints should be sufficient as they are unique per utxo entry + continue + } + } + break + } + return &libkaspawallet.UTXO{ + Outpoint: utxo.Outpoint, + UTXOEntry: utxo.UTXOEntry, + DerivationPath: s.walletAddressPath(utxo.address), + }, nil +} diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 51f1b8a83e..097763092f 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -73,23 +73,28 @@ func (s *server) mergeTransaction( totalValue -= feePerInput } - var payments []*libkaspawallet.Payment - if totalValue >= sentValue { - payments = []*libkaspawallet.Payment{{ - Address: toAddress, - Amount: sentValue, - }, { + if totalValue < sentValue { + // sometimes the fees from compound transactions make the total output higher than what's available from selected + // utxos, in such cases - find one more UTXO and use it. + oneMoreUTXO, err := s.oneMoreUTXOForMergeTransaction(utxos, sentValue-totalValue) + if err != nil { + return nil, err + } + utxos = append(utxos, oneMoreUTXO) + totalValue += oneMoreUTXO.UTXOEntry.Amount() + } + + payments := []*libkaspawallet.Payment{{ + Address: toAddress, + Amount: sentValue, + }} + if totalValue > sentValue { + payments = append(payments, &libkaspawallet.Payment{ Address: changeAddress, Amount: totalValue - sentValue, - }} - } else { - // sometimes the fees from compound transactions make the total output higher than what's available from selected - // utxos, in such cases, the remaining fee will be deduced from the resulting amount - payments = []*libkaspawallet.Payment{{ - Address: toAddress, - Amount: totalValue, - }} + }) } + mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys, s.keysFile.MinimumSignatures, payments, utxos) if err != nil { From bbb7323fcbc1aa127211d53ff8ceae7d9f87fe81 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 22 Feb 2022 16:08:02 +0200 Subject: [PATCH 24/40] Fix off-by-1 error in splitTrasnaction --- cmd/kaspawallet/daemon/server/split_transaction.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 097763092f..d62df39adc 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -121,6 +121,9 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne splitCount++ } inputCountPerSplit := len(transaction.Tx.Inputs) / splitCount + if len(transaction.Tx.Inputs)%splitCount > 0 { + splitCount++ + } splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount) for i := 0; i < splitCount; i++ { @@ -139,18 +142,18 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction, changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) { - selectedUTXOs := make([]*libkaspawallet.UTXO, endIndex-startIndex) + selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex) totalSompi := uint64(0) - for i := startIndex; i < endIndex; i++ { + for i := startIndex; i < endIndex && i < len(transaction.PartiallySignedInputs); i++ { partiallySignedInput := transaction.PartiallySignedInputs[i] - selectedUTXOs[i-startIndex] = &libkaspawallet.UTXO{ + selectedUTXOs = append(selectedUTXOs, &libkaspawallet.UTXO{ Outpoint: &transaction.Tx.Inputs[i].PreviousOutpoint, UTXOEntry: utxo.NewUTXOEntry( partiallySignedInput.PrevOutput.Value, partiallySignedInput.PrevOutput.ScriptPublicKey, false, constants.UnacceptedDAAScore), DerivationPath: partiallySignedInput.DerivationPath, - } + }) totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount() totalSompi -= feePerInput From 75f71905bfffcfa2201229246a297569403dd7dd Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 28 Feb 2022 19:50:18 +0200 Subject: [PATCH 25/40] Add a comment to maybeAutoCompoundTransaction --- cmd/kaspawallet/daemon/server/split_transaction.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index d62df39adc..a498093e27 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -14,6 +14,12 @@ import ( "github.com/kaspanet/kaspad/util" ) +// maybeAutoCompoundTransaction checks if a transaction's mass is higher that what is allowed for a standard +// transaction. +// If it is - the transaction is split into multiple transactions, each with a portion of the inputs and a single output +// into a change address. +// 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(transactionBytes []byte, toAddress util.Address, changeAddress util.Address, changeWalletAddress *walletAddress) ([][]byte, error) { transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes) From cb0d618e0962afcf3f03f051cd231b1a4b304825 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 28 Feb 2022 19:58:35 +0200 Subject: [PATCH 26/40] Add comment on why we are increasing inputCountPerSplit --- cmd/kaspawallet/daemon/server/split_transaction.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index a498093e27..b2463a0f9a 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -128,6 +128,8 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne } inputCountPerSplit := len(transaction.Tx.Inputs) / splitCount if len(transaction.Tx.Inputs)%splitCount > 0 { + // note we are incrementing splitCount, and not inputCountPerSplit, since incrementing inputCountPerSplit + // might make the transaction mass too high splitCount++ } From 7a09ed25f8af496c28e5229f6ad94ffda9851ed6 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 28 Feb 2022 20:06:35 +0200 Subject: [PATCH 27/40] Add comment explaining while originalTransaction has 1 or 2 outputs --- cmd/kaspawallet/daemon/server/split_transaction.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index b2463a0f9a..64bf1737d3 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -58,6 +58,9 @@ func (s *server) mergeTransaction( ) (*serialization.PartiallySignedTransaction, error) { numOutputs := len(originalTransaction.Tx.Outputs) if numOutputs > 2 || numOutputs == 0 { + // This is a sanity check to make sure originalTransaction has either 1 or 2 outputs: + // 1. For the payment itself + // 2. (optional) for change return nil, errors.Errorf("original transaction has %d outputs, while 1 or 2 are expected", len(originalTransaction.Tx.Outputs)) } From 608bf6931f16cfaffaabfd030707015d3cd74e63 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 28 Feb 2022 20:09:08 +0200 Subject: [PATCH 28/40] Move oneMoreUTXOForMergeTransaction to split_transaction.go --- .../server/create_unsigned_transaction.go | 29 ------------------- .../daemon/server/split_transaction.go | 29 +++++++++++++++++++ 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go index 7b9f919840..ac12711ba9 100644 --- a/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go +++ b/cmd/kaspawallet/daemon/server/create_unsigned_transaction.go @@ -102,32 +102,3 @@ func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) ( return selectedUTXOs, totalValue - totalSpend, nil } - -func (s *server) oneMoreUTXOForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (*libkaspawallet.UTXO, error) { - dagInfo, err := s.rpcClient.GetBlockDAGInfo() - if err != nil { - return nil, err - } - - var utxo *walletUTXO - for _, utxo = range s.utxosSortedByAmount { - if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) { - continue - } - if utxo.UTXOEntry.Amount() < requiredAmount+feePerInput { - return nil, errors.Errorf("Insufficient funds for merge transaction") - } - for _, alreadySelectedUTXO := range alreadySelectedUTXOs { - if alreadySelectedUTXO.Outpoint.Equal(utxo.Outpoint) { - // comparing outpoints should be sufficient as they are unique per utxo entry - continue - } - } - break - } - return &libkaspawallet.UTXO{ - Outpoint: utxo.Outpoint, - UTXOEntry: utxo.UTXOEntry, - DerivationPath: s.walletAddressPath(utxo.address), - }, nil -} diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 64bf1737d3..129e1f7d2e 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -208,3 +208,32 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil } + +func (s *server) oneMoreUTXOForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (*libkaspawallet.UTXO, error) { + dagInfo, err := s.rpcClient.GetBlockDAGInfo() + if err != nil { + return nil, err + } + + var utxo *walletUTXO + for _, utxo = range s.utxosSortedByAmount { + if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) { + continue + } + if utxo.UTXOEntry.Amount() < requiredAmount+feePerInput { + return nil, errors.Errorf("Insufficient funds for merge transaction") + } + for _, alreadySelectedUTXO := range alreadySelectedUTXOs { + if alreadySelectedUTXO.Outpoint.Equal(utxo.Outpoint) { + // comparing outpoints should be sufficient as they are unique per utxo entry + continue + } + } + break + } + return &libkaspawallet.UTXO{ + Outpoint: utxo.Outpoint, + UTXOEntry: utxo.UTXOEntry, + DerivationPath: s.walletAddressPath(utxo.address), + }, nil +} From f39bac4fd0c4d185a9ad97776a372fe321a427ed Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 28 Feb 2022 20:26:34 +0200 Subject: [PATCH 29/40] Allow to add multiple utxos to pay fee for mergeTransactions, if needed --- .../daemon/server/split_transaction.go | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 129e1f7d2e..79641f735c 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -85,12 +85,12 @@ func (s *server) mergeTransaction( if totalValue < sentValue { // sometimes the fees from compound transactions make the total output higher than what's available from selected // utxos, in such cases - find one more UTXO and use it. - oneMoreUTXO, err := s.oneMoreUTXOForMergeTransaction(utxos, sentValue-totalValue) + additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue) if err != nil { return nil, err } - utxos = append(utxos, oneMoreUTXO) - totalValue += oneMoreUTXO.UTXOEntry.Amount() + utxos = append(utxos, additionalUTXOs...) + totalValue += totalValueAdded } payments := []*libkaspawallet.Payment{{ @@ -209,31 +209,36 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil } -func (s *server) oneMoreUTXOForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (*libkaspawallet.UTXO, error) { +func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) ( + additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) { + dagInfo, err := s.rpcClient.GetBlockDAGInfo() if err != nil { - return nil, err + return nil, 0, err } - var utxo *walletUTXO - for _, utxo = range s.utxosSortedByAmount { + for _, utxo := range s.utxosSortedByAmount { if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) { continue } - if utxo.UTXOEntry.Amount() < requiredAmount+feePerInput { - return nil, errors.Errorf("Insufficient funds for merge transaction") - } for _, alreadySelectedUTXO := range alreadySelectedUTXOs { if alreadySelectedUTXO.Outpoint.Equal(utxo.Outpoint) { // comparing outpoints should be sufficient as they are unique per utxo entry continue } } - break + additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{ + Outpoint: utxo.Outpoint, + UTXOEntry: utxo.UTXOEntry, + DerivationPath: s.walletAddressPath(utxo.address)}) + totalValueAdded += utxo.UTXOEntry.Amount() - feePerInput + if totalValueAdded >= requiredAmount { + break + } + } + if totalValueAdded < requiredAmount { + return nil, 0, errors.Errorf("Insufficient funds for merge transaction") } - return &libkaspawallet.UTXO{ - Outpoint: utxo.Outpoint, - UTXOEntry: utxo.UTXOEntry, - DerivationPath: s.walletAddressPath(utxo.address), - }, nil + + return additionalUTXOs, totalValueAdded, nil } From 85acccaf486720d8417f9fe5bb05fbdbce8379da Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Tue, 1 Mar 2022 11:47:53 +0200 Subject: [PATCH 30/40] calculate split input counts and sizes properly --- .../daemon/server/split_transaction.go | 34 +++++++++++++------ 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 79641f735c..b3a3c7b384 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -125,16 +125,7 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne return []*serialization.PartiallySignedTransaction{transaction}, nil } - splitCount := int(transactionMass / mempool.MaximumStandardTransactionMass) - if transactionMass%mempool.MaximumStandardTransactionMass > 0 { - splitCount++ - } - inputCountPerSplit := len(transaction.Tx.Inputs) / splitCount - if len(transaction.Tx.Inputs)%splitCount > 0 { - // note we are incrementing splitCount, and not inputCountPerSplit, since incrementing inputCountPerSplit - // might make the transaction mass too high - splitCount++ - } + splitCount, inputCountPerSplit := s.splitAndInputPerSplitCounts(transaction, transactionMass) splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount) for i := 0; i < splitCount; i++ { @@ -150,6 +141,29 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne return splitTransactions, nil } +func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64) ( + splitCount, inputPerSplitCount int) { + transactionWithoutInputs := transaction.Tx.Clone() + transactionWithoutInputs.Inputs = []*externalapi.DomainTransactionInput{} + massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs) + + massForInputs := transactionMass - massWithoutInputs + + inputCount := len(transaction.Tx.Inputs) + massPerInput := massForInputs / uint64(inputCount) + if massForInputs%uint64(inputCount) > 0 { + massPerInput++ + } + + inputPerSplitCount = int((mempool.MaximumStandardTransactionMass - massWithoutInputs) / massPerInput) + splitCount = inputCount / inputPerSplitCount + if inputCount%inputPerSplitCount > 0 { + splitCount++ + } + + return splitCount, inputPerSplitCount +} + func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction, changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) { From c6523bb4709a51b282aaec6d81e97def18e3699b Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 2 Mar 2022 13:23:39 +0200 Subject: [PATCH 31/40] Allow multiple transactions inside the create-unsigned-transaction -> sign -> broadcast workflow --- cmd/kaspawallet/broadcast.go | 25 ++++---- cmd/kaspawallet/config.go | 7 ++- cmd/kaspawallet/create_unsigned_tx.go | 5 +- cmd/kaspawallet/sign.go | 60 ++++++++++++-------- cmd/kaspawallet/transactions_hex_encoding.go | 33 +++++++++++ 5 files changed, 86 insertions(+), 44 deletions(-) create mode 100644 cmd/kaspawallet/transactions_hex_encoding.go diff --git a/cmd/kaspawallet/broadcast.go b/cmd/kaspawallet/broadcast.go index 9b4c2e2d3e..ce43b15661 100644 --- a/cmd/kaspawallet/broadcast.go +++ b/cmd/kaspawallet/broadcast.go @@ -2,13 +2,13 @@ package main import ( "context" - "encoding/hex" "fmt" + "io/ioutil" + "strings" + "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb" "github.com/pkg/errors" - "io/ioutil" - "strings" ) func broadcast(conf *broadcastConfig) error { @@ -28,27 +28,28 @@ func broadcast(conf *broadcastConfig) error { return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time") } - transactionHex := conf.Transaction + transactionsHex := conf.Transaction if conf.TransactionFile != "" { transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile) if err != nil { return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile) } - transactionHex = strings.TrimSpace(string(transactionHexBytes)) + transactionsHex = strings.TrimSpace(string(transactionHexBytes)) } - transaction, err := hex.DecodeString(transactionHex) + transactions, err := decodeTransactionsFromHex(transactionsHex) if err != nil { return err } - response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction}) - if err != nil { - return err + for _, transaction := range transactions { + response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction}) + if err != nil { + return err + } + fmt.Println("Transaction was sent successfully") + fmt.Printf("Transaction ID: \t%s\n", response.TxID) } - fmt.Println("Transaction was sent successfully") - fmt.Printf("Transaction ID: \t%s\n", response.TxID) - return nil } diff --git a/cmd/kaspawallet/config.go b/cmd/kaspawallet/config.go index b02a9446b7..4aeacee2ff 100644 --- a/cmd/kaspawallet/config.go +++ b/cmd/kaspawallet/config.go @@ -1,9 +1,10 @@ package main import ( + "os" + "github.com/kaspanet/kaspad/infrastructure/config" "github.com/pkg/errors" - "os" "github.com/jessevdk/go-flags" ) @@ -68,8 +69,8 @@ type createUnsignedTransactionConfig struct { type signConfig 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"` - Transaction string `long:"transaction" short:"t" description:"The unsigned transaction to sign on (encoded in hex)"` - TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"` + Transaction string `long:"transaction" short:"t" description:"The unsigned transaction(s) to sign on (encoded in hex)"` + TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction(s) to sign on (encoded in hex)"` config.NetworkFlags } diff --git a/cmd/kaspawallet/create_unsigned_tx.go b/cmd/kaspawallet/create_unsigned_tx.go index 30c7c58384..5d67493251 100644 --- a/cmd/kaspawallet/create_unsigned_tx.go +++ b/cmd/kaspawallet/create_unsigned_tx.go @@ -2,7 +2,6 @@ package main import ( "context" - "encoding/hex" "fmt" "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client" @@ -30,8 +29,6 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error { } fmt.Println("Created unsigned transaction") - for _, unsignedTransaction := range response.UnsignedTransactions { - fmt.Println(hex.EncodeToString(unsignedTransaction)) - } + fmt.Println(encodeTransactionsToHex(response.UnsignedTransactions)) return nil } diff --git a/cmd/kaspawallet/sign.go b/cmd/kaspawallet/sign.go index 6ee60c8bf5..4ea60f7873 100644 --- a/cmd/kaspawallet/sign.go +++ b/cmd/kaspawallet/sign.go @@ -1,21 +1,16 @@ package main import ( - "encoding/hex" "fmt" + "io/ioutil" + "strings" + "github.com/kaspanet/kaspad/cmd/kaspawallet/keys" "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet" "github.com/pkg/errors" - "io/ioutil" - "strings" ) func sign(conf *signConfig) error { - keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile) - if err != nil { - return err - } - if conf.Transaction == "" && conf.TransactionFile == "" { return errors.Errorf("Either --transaction or --transaction-file is required") } @@ -23,41 +18,56 @@ func sign(conf *signConfig) error { return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time") } - transactionHex := conf.Transaction - if conf.TransactionFile != "" { - transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile) - if err != nil { - return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile) - } - transactionHex = strings.TrimSpace(string(transactionHexBytes)) - } - - partiallySignedTransaction, err := hex.DecodeString(transactionHex) + keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile) if err != nil { return err } - privateKeys, err := keysFile.DecryptMnemonics(conf.Password) if err != nil { return err } - updatedPartiallySignedTransaction, err := libkaspawallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA) + transactionsHex := conf.Transaction + if conf.TransactionFile != "" { + transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile) + if err != nil { + return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile) + } + transactionsHex = strings.TrimSpace(string(transactionHexBytes)) + } + partiallySignedTransactions, err := decodeTransactionsFromHex(transactionsHex) if err != nil { return err } - isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPartiallySignedTransaction) - if err != nil { - return err + updatedPartiallySignedTransactions := make([][]byte, len(partiallySignedTransactions)) + for i, partiallySignedTransaction := range partiallySignedTransactions { + updatedPartiallySignedTransactions[i], err = + libkaspawallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA) + if err != nil { + return err + } + } + + areAllTransactionsFullySigned := true + for _, updatedPartiallySignedTransaction := range updatedPartiallySignedTransactions { + // This is somewhat redundant to check all transactions, but we do that just-in-case + isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPartiallySignedTransaction) + if err != nil { + return err + } + if !isFullySigned { + areAllTransactionsFullySigned = false + } } - if isFullySigned { + if areAllTransactionsFullySigned { fmt.Println("The transaction is signed and ready to broadcast") } else { fmt.Println("Successfully signed transaction") } - fmt.Printf("Transaction: %x\n", updatedPartiallySignedTransaction) + fmt.Println("Transaction: ") + fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions)) return nil } diff --git a/cmd/kaspawallet/transactions_hex_encoding.go b/cmd/kaspawallet/transactions_hex_encoding.go new file mode 100644 index 0000000000..9399e2713a --- /dev/null +++ b/cmd/kaspawallet/transactions_hex_encoding.go @@ -0,0 +1,33 @@ +package main + +import ( + "encoding/hex" + "strings" +) + +// hexTransactionsSeparator is used to mark the end of one transaction and the beggining of the next one. +// We use a separator that is not in the hex alphabet, but which will not split selection with a double click +const hexTransactionsSeparator = "_" + +func encodeTransactionsToHex(transactions [][]byte) string { + transactionsInHex := make([]string, len(transactions)) + for i, transaction := range transactions { + transactionsInHex[i] = hex.EncodeToString(transaction) + } + return strings.Join(transactionsInHex, hexTransactionsSeparator) +} + +func decodeTransactionsFromHex(transactionsHex string) ([][]byte, error) { + splitTransactionsHexes := strings.Split(transactionsHex, hexTransactionsSeparator) + transactions := make([][]byte, len(splitTransactionsHexes)) + + var err error + for i, transactionHex := range splitTransactionsHexes { + transactions[i], err = hex.DecodeString(transactionHex) + if err != nil { + return nil, err + } + } + + return transactions, nil +} From e3bac53eaa4c6f9264a05850a7c42a925c4cc3da Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 16 Mar 2022 16:53:37 +0200 Subject: [PATCH 32/40] Print the number of transaction which was sent, in case there were multiple --- cmd/kaspawallet/broadcast.go | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/kaspawallet/broadcast.go b/cmd/kaspawallet/broadcast.go index ce43b15661..dcb6b45cda 100644 --- a/cmd/kaspawallet/broadcast.go +++ b/cmd/kaspawallet/broadcast.go @@ -42,12 +42,17 @@ func broadcast(conf *broadcastConfig) error { return err } - for _, transaction := range transactions { + transactionsCount := len(transactions) + for i, transaction := range transactions { response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction}) if err != nil { return err } - fmt.Println("Transaction was sent successfully") + if transactionsCount == 1 { + fmt.Println("Transaction was sent successfully") + } else { + fmt.Printf("Transaction %d (out of %d) was sent successfully\n", i+1, transactionsCount) + } fmt.Printf("Transaction ID: \t%s\n", response.TxID) } From c60c3394cdd7650ca3063b8dcf54e46f587e3155 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 16 Mar 2022 16:59:04 +0200 Subject: [PATCH 33/40] Rename broadcastConfig.Transaction(File) to Transactions(File) --- app/appmessage/p2p_msgblock_test.go | 9 +++++---- app/protocol/flowcontext/transactions.go | 2 +- app/rpc/rpchandlers/get_mempool_entry.go | 4 ++-- cmd/kaspawallet/broadcast.go | 18 +++++++++--------- cmd/kaspawallet/config.go | 6 +++--- .../daemon/server/split_transaction.go | 2 +- .../libkaspawallet/transaction_test.go | 10 +++++----- cmd/kaspawallet/parse.go | 7 ++++--- cmd/kaspawallet/send.go | 4 ++-- cmd/kaspawallet/sign.go | 2 +- .../calculate_past_utxo.go | 6 +++--- .../blocktemplatebuilder.go | 5 +++-- .../mempool/check_transaction_standard_test.go | 6 +++--- .../mempool/validate_and_insert_transaction.go | 2 +- domain/miningmanager/miningmanager_test.go | 5 +++-- 15 files changed, 46 insertions(+), 42 deletions(-) diff --git a/app/appmessage/p2p_msgblock_test.go b/app/appmessage/p2p_msgblock_test.go index 4e11a42d07..1697aa8cb3 100644 --- a/app/appmessage/p2p_msgblock_test.go +++ b/app/appmessage/p2p_msgblock_test.go @@ -5,12 +5,13 @@ package appmessage import ( - "github.com/davecgh/go-spew/spew" - "github.com/kaspanet/kaspad/util/mstime" "math" "reflect" "testing" + "github.com/davecgh/go-spew/spew" + "github.com/kaspanet/kaspad/util/mstime" + "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -220,7 +221,7 @@ var blockOneBytes = []byte{ 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script (coinbase) 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence 0x01, // Varint for number of transaction outputs - 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount + 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transactions amount 0x43, // Varint for length of scriptPubKey 0x41, // OP_DATA_65 0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c, @@ -239,7 +240,7 @@ var blockOneBytes = []byte{ 0x00, 0x00, 0x00, 0x00, // SubnetworkID } -// Transaction location information for block one transactions. +// Transactions location information for block one transactions. var blockOneTxLocs = []TxLoc{ {TxStart: 186, TxLen: 162}, } diff --git a/app/protocol/flowcontext/transactions.go b/app/protocol/flowcontext/transactions.go index 644c72d93a..88f2e10490 100644 --- a/app/protocol/flowcontext/transactions.go +++ b/app/protocol/flowcontext/transactions.go @@ -64,7 +64,7 @@ func (f *FlowContext) maybePropagateTransactions() error { if len(transactionIDsToBroadcast) > appmessage.MaxInvPerTxInvMsg { transactionIDsToBroadcast = f.transactionIDsToPropagate[:len(transactionIDsToBroadcast)] } - log.Debugf("Transaction propagation: broadcasting %d transactions", len(transactionIDsToBroadcast)) + log.Debugf("Transactions propagation: broadcasting %d transactions", len(transactionIDsToBroadcast)) inv := appmessage.NewMsgInvTransaction(transactionIDsToBroadcast) err := f.Broadcast(inv) diff --git a/app/rpc/rpchandlers/get_mempool_entry.go b/app/rpc/rpchandlers/get_mempool_entry.go index 115e0b7f0f..48b37834b5 100644 --- a/app/rpc/rpchandlers/get_mempool_entry.go +++ b/app/rpc/rpchandlers/get_mempool_entry.go @@ -14,14 +14,14 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques transactionID, err := transactionid.FromString(getMempoolEntryRequest.TxID) if err != nil { errorMessage := &appmessage.GetMempoolEntryResponseMessage{} - errorMessage.Error = appmessage.RPCErrorf("Transaction ID could not be parsed: %s", err) + errorMessage.Error = appmessage.RPCErrorf("Transactions ID could not be parsed: %s", err) return errorMessage, nil } transaction, ok := context.Domain.MiningManager().GetTransaction(transactionID) if !ok { errorMessage := &appmessage.GetMempoolEntryResponseMessage{} - errorMessage.Error = appmessage.RPCErrorf("Transaction %s was not found", transactionID) + errorMessage.Error = appmessage.RPCErrorf("Transactions %s was not found", transactionID) return errorMessage, nil } rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) diff --git a/cmd/kaspawallet/broadcast.go b/cmd/kaspawallet/broadcast.go index dcb6b45cda..d4427ceb4b 100644 --- a/cmd/kaspawallet/broadcast.go +++ b/cmd/kaspawallet/broadcast.go @@ -21,18 +21,18 @@ func broadcast(conf *broadcastConfig) error { ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout) defer cancel() - if conf.Transaction == "" && conf.TransactionFile == "" { + if conf.Transactions == "" && conf.TransactionsFile == "" { return errors.Errorf("Either --transaction or --transaction-file is required") } - if conf.Transaction != "" && conf.TransactionFile != "" { + if conf.Transactions != "" && conf.TransactionsFile != "" { return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time") } - transactionsHex := conf.Transaction - if conf.TransactionFile != "" { - transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile) + transactionsHex := conf.Transactions + if conf.TransactionsFile != "" { + transactionHexBytes, err := ioutil.ReadFile(conf.TransactionsFile) if err != nil { - return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionFile) + return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionsFile) } transactionsHex = strings.TrimSpace(string(transactionHexBytes)) } @@ -49,11 +49,11 @@ func broadcast(conf *broadcastConfig) error { return err } if transactionsCount == 1 { - fmt.Println("Transaction was sent successfully") + fmt.Println("Transactions was sent successfully") } else { - fmt.Printf("Transaction %d (out of %d) was sent successfully\n", i+1, transactionsCount) + fmt.Printf("Transactions %d (out of %d) was sent successfully\n", i+1, transactionsCount) } - fmt.Printf("Transaction ID: \t%s\n", response.TxID) + fmt.Printf("Transactions ID: \t%s\n", response.TxID) } return nil diff --git a/cmd/kaspawallet/config.go b/cmd/kaspawallet/config.go index 4aeacee2ff..2ef7196695 100644 --- a/cmd/kaspawallet/config.go +++ b/cmd/kaspawallet/config.go @@ -75,9 +75,9 @@ type signConfig struct { } type broadcastConfig struct { - DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"` - Transaction string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)"` - TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"` + DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"` + Transactions string `long:"transaction" short:"t" description:"The signed transaction to broadcast (encoded in hex)"` + TransactionsFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"` config.NetworkFlags } diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index b3a3c7b384..00a8025813 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -84,7 +84,7 @@ func (s *server) mergeTransaction( if totalValue < sentValue { // sometimes the fees from compound transactions make the total output higher than what's available from selected - // utxos, in such cases - find one more UTXO and use it. + // utxos, in such cases - find more UTXO(s) and use them. additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue) if err != nil { return nil, err diff --git a/cmd/kaspawallet/libkaspawallet/transaction_test.go b/cmd/kaspawallet/libkaspawallet/transaction_test.go index 1b705e739b..5280f2d92b 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction_test.go +++ b/cmd/kaspawallet/libkaspawallet/transaction_test.go @@ -116,7 +116,7 @@ func TestMultisig(t *testing.T) { } if isFullySigned { - t.Fatalf("Transaction is not expected to be signed") + t.Fatalf("Transactions is not expected to be signed") } _, err = libkaspawallet.ExtractTransaction(unsignedTransaction, ecdsa) @@ -135,7 +135,7 @@ func TestMultisig(t *testing.T) { } if isFullySigned { - t.Fatalf("Transaction is not expected to be fully signed") + t.Fatalf("Transactions is not expected to be fully signed") } signedTxStep2, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1, ecdsa) @@ -174,7 +174,7 @@ func TestMultisig(t *testing.T) { Index: 0, } if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { - t.Fatalf("Transaction wasn't accepted in the DAG") + t.Fatalf("Transactions wasn't accepted in the DAG") } }) }) @@ -277,7 +277,7 @@ func TestP2PK(t *testing.T) { } if isFullySigned { - t.Fatalf("Transaction is not expected to be signed") + t.Fatalf("Transactions is not expected to be signed") } _, err = libkaspawallet.ExtractTransaction(unsignedTransaction, ecdsa) @@ -305,7 +305,7 @@ func TestP2PK(t *testing.T) { Index: 0, } if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { - t.Fatalf("Transaction wasn't accepted in the DAG") + t.Fatalf("Transactions wasn't accepted in the DAG") } }) }) diff --git a/cmd/kaspawallet/parse.go b/cmd/kaspawallet/parse.go index 37ce00c497..2d58c9ebc0 100644 --- a/cmd/kaspawallet/parse.go +++ b/cmd/kaspawallet/parse.go @@ -3,13 +3,14 @@ package main import ( "encoding/hex" "fmt" + "io/ioutil" + "strings" + "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" "github.com/pkg/errors" - "io/ioutil" - "strings" ) func parse(conf *parseConfig) error { @@ -39,7 +40,7 @@ func parse(conf *parseConfig) error { return err } - fmt.Printf("Transaction ID: \t%s\n", consensushashing.TransactionID(partiallySignedTransaction.Tx)) + fmt.Printf("Transactions ID: \t%s\n", consensushashing.TransactionID(partiallySignedTransaction.Tx)) fmt.Println() allInputSompi := uint64(0) diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go index c3fa1e8000..cabfa06ca2 100644 --- a/cmd/kaspawallet/send.go +++ b/cmd/kaspawallet/send.go @@ -69,8 +69,8 @@ func send(conf *sendConfig) error { return err } - fmt.Println("Transaction was sent successfully") - fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID) + fmt.Println("Transactions was sent successfully") + fmt.Printf("Transactions ID: \t%s\n", broadcastResponse.TxID) return nil }() if err != nil { diff --git a/cmd/kaspawallet/sign.go b/cmd/kaspawallet/sign.go index 4ea60f7873..eafed1f133 100644 --- a/cmd/kaspawallet/sign.go +++ b/cmd/kaspawallet/sign.go @@ -67,7 +67,7 @@ func sign(conf *signConfig) error { fmt.Println("Successfully signed transaction") } - fmt.Println("Transaction: ") + fmt.Println("Transactions: ") fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions)) return nil } diff --git a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go index 793ebd4f34..3881f57a6a 100644 --- a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go @@ -189,7 +189,7 @@ func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.Staging if err != nil { return nil, nil, err } - log.Tracef("Transaction %s in block %s isAccepted: %t, fee: %d", + log.Tracef("Transactions %s in block %s isAccepted: %t, fee: %d", transactionID, mergeSetBlockHash, isAccepted, transaction.Fee) var transactionInputUTXOEntries []externalapi.UTXOEntry @@ -235,12 +235,12 @@ func (csm *consensusStateManager) maybeAcceptTransaction(stagingArea *model.Stag // Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain. if transactionhelper.IsCoinBase(transaction) { if !isSelectedParent { - log.Tracef("Transaction %s is the coinbase of block %s "+ + log.Tracef("Transactions %s is the coinbase of block %s "+ "but said block is not in the selected parent chain. "+ "As such, it is not accepted", transactionID, blockHash) return false, accumulatedMassBefore, nil } - log.Tracef("Transaction %s is the coinbase of block %s", transactionID, blockHash) + log.Tracef("Transactions %s is the coinbase of block %s", transactionID, blockHash) } else { log.Tracef("Validating transaction %s in block %s", transactionID, blockHash) err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee( diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go index 7cfde37f28..2d8ef96e94 100644 --- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go +++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go @@ -1,10 +1,11 @@ package blocktemplatebuilder import ( - "github.com/kaspanet/kaspad/domain/consensusreference" "math" "sort" + "github.com/kaspanet/kaspad/domain/consensusreference" + "github.com/kaspanet/kaspad/util/difficulty" consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -86,7 +87,7 @@ func New(consensusReference consensusreference.ConsensusReference, mempool minin // Given the above, a block generated by this function is of the following form: // // ----------------------------------- -- -- -// | Coinbase Transaction | | | +// | Coinbase Transactions | | | // |-----------------------------------| | | // | | | | ----- policy.BlockPrioritySize // | High-priority Transactions | | | diff --git a/domain/miningmanager/mempool/check_transaction_standard_test.go b/domain/miningmanager/mempool/check_transaction_standard_test.go index 9366e5a534..dd71e522b1 100644 --- a/domain/miningmanager/mempool/check_transaction_standard_test.go +++ b/domain/miningmanager/mempool/check_transaction_standard_test.go @@ -26,7 +26,7 @@ import ( func TestCalcMinRequiredTxRelayFee(t *testing.T) { tests := []struct { name string // test description. - size uint64 // Transaction size in bytes. + size uint64 // Transactions size in bytes. minimumRelayTransactionFee util.Amount // minimum relay transaction fee. want uint64 // Expected fee. }{ @@ -239,7 +239,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) { isStandard: true, }, { - name: "Transaction version too high", + name: "Transactions version too high", tx: &externalapi.DomainTransaction{Version: constants.MaxTransactionVersion + 1, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{&dummyTxOut}}, height: 300000, isStandard: false, @@ -247,7 +247,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) { }, { - name: "Transaction size is too large", + name: "Transactions size is too large", tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ Value: 0, ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, MaximumStandardTransactionMass+1), 0}, diff --git a/domain/miningmanager/mempool/validate_and_insert_transaction.go b/domain/miningmanager/mempool/validate_and_insert_transaction.go index 3ccedc9bb1..ffdd364e16 100644 --- a/domain/miningmanager/mempool/validate_and_insert_transaction.go +++ b/domain/miningmanager/mempool/validate_and_insert_transaction.go @@ -31,7 +31,7 @@ func (mp *mempool) validateAndInsertTransaction(transaction *externalapi.DomainT if len(missingOutpoints) > 0 { if !allowOrphan { - str := fmt.Sprintf("Transaction %s is an orphan, where allowOrphan = false", + str := fmt.Sprintf("Transactions %s is an orphan, where allowOrphan = false", consensushashing.TransactionID(transaction)) return nil, transactionRuleError(RejectBadOrphan, str) } diff --git a/domain/miningmanager/miningmanager_test.go b/domain/miningmanager/miningmanager_test.go index 81ed476996..66a16d26c5 100644 --- a/domain/miningmanager/miningmanager_test.go +++ b/domain/miningmanager/miningmanager_test.go @@ -1,11 +1,12 @@ package miningmanager_test import ( - "github.com/kaspanet/kaspad/domain/consensusreference" "reflect" "strings" "testing" + "github.com/kaspanet/kaspad/domain/consensusreference" + "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/domain/consensus" @@ -367,7 +368,7 @@ func TestOrphanTransactions(t *testing.T) { } } if !isContained { - t.Fatalf("Error: Unknown Transaction %s in a block.", consensushashing.TransactionID(transactionFromBlock)) + t.Fatalf("Error: Unknown Transactions %s in a block.", consensushashing.TransactionID(transactionFromBlock)) } } }) From 8cc61872d40b3982e29907e4c20e935de6ab1e8c Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 16 Mar 2022 17:08:48 +0200 Subject: [PATCH 34/40] Convert alreadySelectedUTXOs to a map --- cmd/kaspawallet/daemon/server/split_transaction.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 00a8025813..1680ab19f2 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -230,16 +230,17 @@ func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawa if err != nil { return nil, 0, err } + alreadySelectedUTXOsMap := make(map[externalapi.DomainOutpoint]struct{}, len(alreadySelectedUTXOs)) + for _, alreadySelectedUTXO := range alreadySelectedUTXOs { + alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{} + } for _, utxo := range s.utxosSortedByAmount { - if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) { + if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok { continue } - for _, alreadySelectedUTXO := range alreadySelectedUTXOs { - if alreadySelectedUTXO.Outpoint.Equal(utxo.Outpoint) { - // comparing outpoints should be sufficient as they are unique per utxo entry - continue - } + if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) { + continue } additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{ Outpoint: utxo.Outpoint, From 1cdc3cabb7bab7a257f0259eee1546f180acd0c5 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Wed, 16 Mar 2022 17:37:59 +0200 Subject: [PATCH 35/40] Fix a typo --- cmd/kaspawallet/transactions_hex_encoding.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/kaspawallet/transactions_hex_encoding.go b/cmd/kaspawallet/transactions_hex_encoding.go index 9399e2713a..e7e857bf11 100644 --- a/cmd/kaspawallet/transactions_hex_encoding.go +++ b/cmd/kaspawallet/transactions_hex_encoding.go @@ -5,7 +5,7 @@ import ( "strings" ) -// hexTransactionsSeparator is used to mark the end of one transaction and the beggining of the next one. +// hexTransactionsSeparator is used to mark the end of one transaction and the beginning of the next one. // We use a separator that is not in the hex alphabet, but which will not split selection with a double click const hexTransactionsSeparator = "_" From ecf6b57b8384520260b9d2490bca4d1ea700d65c Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 21 Mar 2022 12:01:29 +0200 Subject: [PATCH 36/40] Add comment explaining that we assume all inputs are the same --- cmd/kaspawallet/daemon/server/split_transaction.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 1680ab19f2..3e8fc6a6b1 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -149,6 +149,8 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall massForInputs := transactionMass - massWithoutInputs + // Since the transaction was generated by kaspawallet, we assume all inputs have the same number of signatures, and + // thus - the same mass. inputCount := len(transaction.Tx.Inputs) massPerInput := massForInputs / uint64(inputCount) if massForInputs%uint64(inputCount) > 0 { From 73bfb6111e930b8db49b61a5ab808642a8c17c30 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 21 Mar 2022 12:09:02 +0200 Subject: [PATCH 37/40] Revert over-refactor of rename of config.Transaction -> config.Transactions --- app/appmessage/p2p_msgblock_test.go | 9 ++++----- app/protocol/flowcontext/transactions.go | 2 +- app/rpc/rpchandlers/get_mempool_entry.go | 4 ++-- cmd/kaspawallet/broadcast.go | 6 +++--- cmd/kaspawallet/daemon/server/split_transaction.go | 2 +- cmd/kaspawallet/libkaspawallet/transaction_test.go | 10 +++++----- cmd/kaspawallet/parse.go | 7 +++---- cmd/kaspawallet/send.go | 4 ++-- cmd/kaspawallet/sign.go | 2 +- .../consensusstatemanager/calculate_past_utxo.go | 6 +++--- .../blocktemplatebuilder/blocktemplatebuilder.go | 5 ++--- .../mempool/check_transaction_standard_test.go | 6 +++--- .../mempool/validate_and_insert_transaction.go | 2 +- domain/miningmanager/miningmanager_test.go | 5 ++--- 14 files changed, 33 insertions(+), 37 deletions(-) diff --git a/app/appmessage/p2p_msgblock_test.go b/app/appmessage/p2p_msgblock_test.go index 1697aa8cb3..4e11a42d07 100644 --- a/app/appmessage/p2p_msgblock_test.go +++ b/app/appmessage/p2p_msgblock_test.go @@ -5,13 +5,12 @@ package appmessage import ( + "github.com/davecgh/go-spew/spew" + "github.com/kaspanet/kaspad/util/mstime" "math" "reflect" "testing" - "github.com/davecgh/go-spew/spew" - "github.com/kaspanet/kaspad/util/mstime" - "github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks" "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -221,7 +220,7 @@ var blockOneBytes = []byte{ 0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, // Signature script (coinbase) 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Sequence 0x01, // Varint for number of transaction outputs - 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transactions amount + 0x00, 0xf2, 0x05, 0x2a, 0x01, 0x00, 0x00, 0x00, // Transaction amount 0x43, // Varint for length of scriptPubKey 0x41, // OP_DATA_65 0x04, 0x96, 0xb5, 0x38, 0xe8, 0x53, 0x51, 0x9c, @@ -240,7 +239,7 @@ var blockOneBytes = []byte{ 0x00, 0x00, 0x00, 0x00, // SubnetworkID } -// Transactions location information for block one transactions. +// Transaction location information for block one transactions. var blockOneTxLocs = []TxLoc{ {TxStart: 186, TxLen: 162}, } diff --git a/app/protocol/flowcontext/transactions.go b/app/protocol/flowcontext/transactions.go index 88f2e10490..644c72d93a 100644 --- a/app/protocol/flowcontext/transactions.go +++ b/app/protocol/flowcontext/transactions.go @@ -64,7 +64,7 @@ func (f *FlowContext) maybePropagateTransactions() error { if len(transactionIDsToBroadcast) > appmessage.MaxInvPerTxInvMsg { transactionIDsToBroadcast = f.transactionIDsToPropagate[:len(transactionIDsToBroadcast)] } - log.Debugf("Transactions propagation: broadcasting %d transactions", len(transactionIDsToBroadcast)) + log.Debugf("Transaction propagation: broadcasting %d transactions", len(transactionIDsToBroadcast)) inv := appmessage.NewMsgInvTransaction(transactionIDsToBroadcast) err := f.Broadcast(inv) diff --git a/app/rpc/rpchandlers/get_mempool_entry.go b/app/rpc/rpchandlers/get_mempool_entry.go index 48b37834b5..115e0b7f0f 100644 --- a/app/rpc/rpchandlers/get_mempool_entry.go +++ b/app/rpc/rpchandlers/get_mempool_entry.go @@ -14,14 +14,14 @@ func HandleGetMempoolEntry(context *rpccontext.Context, _ *router.Router, reques transactionID, err := transactionid.FromString(getMempoolEntryRequest.TxID) if err != nil { errorMessage := &appmessage.GetMempoolEntryResponseMessage{} - errorMessage.Error = appmessage.RPCErrorf("Transactions ID could not be parsed: %s", err) + errorMessage.Error = appmessage.RPCErrorf("Transaction ID could not be parsed: %s", err) return errorMessage, nil } transaction, ok := context.Domain.MiningManager().GetTransaction(transactionID) if !ok { errorMessage := &appmessage.GetMempoolEntryResponseMessage{} - errorMessage.Error = appmessage.RPCErrorf("Transactions %s was not found", transactionID) + errorMessage.Error = appmessage.RPCErrorf("Transaction %s was not found", transactionID) return errorMessage, nil } rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction) diff --git a/cmd/kaspawallet/broadcast.go b/cmd/kaspawallet/broadcast.go index d4427ceb4b..a6f84e7fe2 100644 --- a/cmd/kaspawallet/broadcast.go +++ b/cmd/kaspawallet/broadcast.go @@ -49,11 +49,11 @@ func broadcast(conf *broadcastConfig) error { return err } if transactionsCount == 1 { - fmt.Println("Transactions was sent successfully") + fmt.Println("Transaction was sent successfully") } else { - fmt.Printf("Transactions %d (out of %d) was sent successfully\n", i+1, transactionsCount) + fmt.Printf("Transaction %d (out of %d) was sent successfully\n", i+1, transactionsCount) } - fmt.Printf("Transactions ID: \t%s\n", response.TxID) + fmt.Printf("Transaction ID: \t%s\n", response.TxID) } return nil diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 3e8fc6a6b1..9963035d9d 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -84,7 +84,7 @@ func (s *server) mergeTransaction( if totalValue < sentValue { // sometimes the fees from compound transactions make the total output higher than what's available from selected - // utxos, in such cases - find more UTXO(s) and use them. + // utxos, in such cases - find one more UTXO and use it. additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue) if err != nil { return nil, err diff --git a/cmd/kaspawallet/libkaspawallet/transaction_test.go b/cmd/kaspawallet/libkaspawallet/transaction_test.go index 5280f2d92b..1b705e739b 100644 --- a/cmd/kaspawallet/libkaspawallet/transaction_test.go +++ b/cmd/kaspawallet/libkaspawallet/transaction_test.go @@ -116,7 +116,7 @@ func TestMultisig(t *testing.T) { } if isFullySigned { - t.Fatalf("Transactions is not expected to be signed") + t.Fatalf("Transaction is not expected to be signed") } _, err = libkaspawallet.ExtractTransaction(unsignedTransaction, ecdsa) @@ -135,7 +135,7 @@ func TestMultisig(t *testing.T) { } if isFullySigned { - t.Fatalf("Transactions is not expected to be fully signed") + t.Fatalf("Transaction is not expected to be fully signed") } signedTxStep2, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1, ecdsa) @@ -174,7 +174,7 @@ func TestMultisig(t *testing.T) { Index: 0, } if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { - t.Fatalf("Transactions wasn't accepted in the DAG") + t.Fatalf("Transaction wasn't accepted in the DAG") } }) }) @@ -277,7 +277,7 @@ func TestP2PK(t *testing.T) { } if isFullySigned { - t.Fatalf("Transactions is not expected to be signed") + t.Fatalf("Transaction is not expected to be signed") } _, err = libkaspawallet.ExtractTransaction(unsignedTransaction, ecdsa) @@ -305,7 +305,7 @@ func TestP2PK(t *testing.T) { Index: 0, } if !virtualChangeSet.VirtualUTXODiff.ToAdd().Contains(addedUTXO) { - t.Fatalf("Transactions wasn't accepted in the DAG") + t.Fatalf("Transaction wasn't accepted in the DAG") } }) }) diff --git a/cmd/kaspawallet/parse.go b/cmd/kaspawallet/parse.go index 2d58c9ebc0..37ce00c497 100644 --- a/cmd/kaspawallet/parse.go +++ b/cmd/kaspawallet/parse.go @@ -3,14 +3,13 @@ package main import ( "encoding/hex" "fmt" - "io/ioutil" - "strings" - "github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization" "github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing" "github.com/kaspanet/kaspad/domain/consensus/utils/constants" "github.com/kaspanet/kaspad/domain/consensus/utils/txscript" "github.com/pkg/errors" + "io/ioutil" + "strings" ) func parse(conf *parseConfig) error { @@ -40,7 +39,7 @@ func parse(conf *parseConfig) error { return err } - fmt.Printf("Transactions ID: \t%s\n", consensushashing.TransactionID(partiallySignedTransaction.Tx)) + fmt.Printf("Transaction ID: \t%s\n", consensushashing.TransactionID(partiallySignedTransaction.Tx)) fmt.Println() allInputSompi := uint64(0) diff --git a/cmd/kaspawallet/send.go b/cmd/kaspawallet/send.go index cabfa06ca2..c3fa1e8000 100644 --- a/cmd/kaspawallet/send.go +++ b/cmd/kaspawallet/send.go @@ -69,8 +69,8 @@ func send(conf *sendConfig) error { return err } - fmt.Println("Transactions was sent successfully") - fmt.Printf("Transactions ID: \t%s\n", broadcastResponse.TxID) + fmt.Println("Transaction was sent successfully") + fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID) return nil }() if err != nil { diff --git a/cmd/kaspawallet/sign.go b/cmd/kaspawallet/sign.go index eafed1f133..4ea60f7873 100644 --- a/cmd/kaspawallet/sign.go +++ b/cmd/kaspawallet/sign.go @@ -67,7 +67,7 @@ func sign(conf *signConfig) error { fmt.Println("Successfully signed transaction") } - fmt.Println("Transactions: ") + fmt.Println("Transaction: ") fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions)) return nil } diff --git a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go index 3881f57a6a..793ebd4f34 100644 --- a/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go +++ b/domain/consensus/processes/consensusstatemanager/calculate_past_utxo.go @@ -189,7 +189,7 @@ func (csm *consensusStateManager) applyMergeSetBlocks(stagingArea *model.Staging if err != nil { return nil, nil, err } - log.Tracef("Transactions %s in block %s isAccepted: %t, fee: %d", + log.Tracef("Transaction %s in block %s isAccepted: %t, fee: %d", transactionID, mergeSetBlockHash, isAccepted, transaction.Fee) var transactionInputUTXOEntries []externalapi.UTXOEntry @@ -235,12 +235,12 @@ func (csm *consensusStateManager) maybeAcceptTransaction(stagingArea *model.Stag // Coinbase transaction outputs are added to the UTXO-set only if they are in the selected parent chain. if transactionhelper.IsCoinBase(transaction) { if !isSelectedParent { - log.Tracef("Transactions %s is the coinbase of block %s "+ + log.Tracef("Transaction %s is the coinbase of block %s "+ "but said block is not in the selected parent chain. "+ "As such, it is not accepted", transactionID, blockHash) return false, accumulatedMassBefore, nil } - log.Tracef("Transactions %s is the coinbase of block %s", transactionID, blockHash) + log.Tracef("Transaction %s is the coinbase of block %s", transactionID, blockHash) } else { log.Tracef("Validating transaction %s in block %s", transactionID, blockHash) err = csm.transactionValidator.ValidateTransactionInContextAndPopulateFee( diff --git a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go index 2d8ef96e94..7cfde37f28 100644 --- a/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go +++ b/domain/miningmanager/blocktemplatebuilder/blocktemplatebuilder.go @@ -1,11 +1,10 @@ package blocktemplatebuilder import ( + "github.com/kaspanet/kaspad/domain/consensusreference" "math" "sort" - "github.com/kaspanet/kaspad/domain/consensusreference" - "github.com/kaspanet/kaspad/util/difficulty" consensusexternalapi "github.com/kaspanet/kaspad/domain/consensus/model/externalapi" @@ -87,7 +86,7 @@ func New(consensusReference consensusreference.ConsensusReference, mempool minin // Given the above, a block generated by this function is of the following form: // // ----------------------------------- -- -- -// | Coinbase Transactions | | | +// | Coinbase Transaction | | | // |-----------------------------------| | | // | | | | ----- policy.BlockPrioritySize // | High-priority Transactions | | | diff --git a/domain/miningmanager/mempool/check_transaction_standard_test.go b/domain/miningmanager/mempool/check_transaction_standard_test.go index dd71e522b1..9366e5a534 100644 --- a/domain/miningmanager/mempool/check_transaction_standard_test.go +++ b/domain/miningmanager/mempool/check_transaction_standard_test.go @@ -26,7 +26,7 @@ import ( func TestCalcMinRequiredTxRelayFee(t *testing.T) { tests := []struct { name string // test description. - size uint64 // Transactions size in bytes. + size uint64 // Transaction size in bytes. minimumRelayTransactionFee util.Amount // minimum relay transaction fee. want uint64 // Expected fee. }{ @@ -239,7 +239,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) { isStandard: true, }, { - name: "Transactions version too high", + name: "Transaction version too high", tx: &externalapi.DomainTransaction{Version: constants.MaxTransactionVersion + 1, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{&dummyTxOut}}, height: 300000, isStandard: false, @@ -247,7 +247,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) { }, { - name: "Transactions size is too large", + name: "Transaction size is too large", tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{ Value: 0, ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, MaximumStandardTransactionMass+1), 0}, diff --git a/domain/miningmanager/mempool/validate_and_insert_transaction.go b/domain/miningmanager/mempool/validate_and_insert_transaction.go index ffdd364e16..3ccedc9bb1 100644 --- a/domain/miningmanager/mempool/validate_and_insert_transaction.go +++ b/domain/miningmanager/mempool/validate_and_insert_transaction.go @@ -31,7 +31,7 @@ func (mp *mempool) validateAndInsertTransaction(transaction *externalapi.DomainT if len(missingOutpoints) > 0 { if !allowOrphan { - str := fmt.Sprintf("Transactions %s is an orphan, where allowOrphan = false", + str := fmt.Sprintf("Transaction %s is an orphan, where allowOrphan = false", consensushashing.TransactionID(transaction)) return nil, transactionRuleError(RejectBadOrphan, str) } diff --git a/domain/miningmanager/miningmanager_test.go b/domain/miningmanager/miningmanager_test.go index 66a16d26c5..81ed476996 100644 --- a/domain/miningmanager/miningmanager_test.go +++ b/domain/miningmanager/miningmanager_test.go @@ -1,12 +1,11 @@ package miningmanager_test import ( + "github.com/kaspanet/kaspad/domain/consensusreference" "reflect" "strings" "testing" - "github.com/kaspanet/kaspad/domain/consensusreference" - "github.com/kaspanet/kaspad/domain/miningmanager/mempool" "github.com/kaspanet/kaspad/domain/consensus" @@ -368,7 +367,7 @@ func TestOrphanTransactions(t *testing.T) { } } if !isContained { - t.Fatalf("Error: Unknown Transactions %s in a block.", consensushashing.TransactionID(transactionFromBlock)) + t.Fatalf("Error: Unknown Transaction %s in a block.", consensushashing.TransactionID(transactionFromBlock)) } } }) From 517063eed6abddf3274de33251457f2ad5c5de74 Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 21 Mar 2022 14:37:58 +0200 Subject: [PATCH 38/40] Rename: inputPerSplitCount -> inputsPerSplitCount --- cmd/kaspawallet/daemon/server/split_transaction.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 9963035d9d..9875890926 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -142,7 +142,7 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne } func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64) ( - splitCount, inputPerSplitCount int) { + splitCount, inputsPerSplitCount int) { transactionWithoutInputs := transaction.Tx.Clone() transactionWithoutInputs.Inputs = []*externalapi.DomainTransactionInput{} massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs) @@ -157,13 +157,13 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall massPerInput++ } - inputPerSplitCount = int((mempool.MaximumStandardTransactionMass - massWithoutInputs) / massPerInput) - splitCount = inputCount / inputPerSplitCount - if inputCount%inputPerSplitCount > 0 { + inputsPerSplitCount = int((mempool.MaximumStandardTransactionMass - massWithoutInputs) / massPerInput) + splitCount = inputCount / inputsPerSplitCount + if inputCount%inputsPerSplitCount > 0 { splitCount++ } - return splitCount, inputPerSplitCount + return splitCount, inputsPerSplitCount } func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction, From 3ffacb0cc913fd7971f1c71e87424d130f343e3a Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Mon, 21 Mar 2022 14:45:36 +0200 Subject: [PATCH 39/40] Add comment for splitAndInputPerSplitCounts --- cmd/kaspawallet/daemon/server/split_transaction.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 9875890926..5729c9921d 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -141,8 +141,10 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne return splitTransactions, nil } +// 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) ( splitCount, inputsPerSplitCount int) { + transactionWithoutInputs := transaction.Tx.Clone() transactionWithoutInputs.Inputs = []*externalapi.DomainTransactionInput{} massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs) From 88857c83ff5327cfcb976cafa95f46e8f08073ec Mon Sep 17 00:00:00 2001 From: Mike Zak Date: Sun, 27 Mar 2022 13:44:40 +0300 Subject: [PATCH 40/40] Use createSplitTransaction to calculate the upper bound of mass for split transactions --- .../daemon/server/split_transaction.go | 31 ++++++++++++++----- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/cmd/kaspawallet/daemon/server/split_transaction.go b/cmd/kaspawallet/daemon/server/split_transaction.go index 5729c9921d..fd672b019d 100644 --- a/cmd/kaspawallet/daemon/server/split_transaction.go +++ b/cmd/kaspawallet/daemon/server/split_transaction.go @@ -125,7 +125,10 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne return []*serialization.PartiallySignedTransaction{transaction}, nil } - splitCount, inputCountPerSplit := s.splitAndInputPerSplitCounts(transaction, transactionMass) + splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress) + if err != nil { + return nil, err + } splitTransactions := make([]*serialization.PartiallySignedTransaction, splitCount) for i := 0; i < splitCount; i++ { @@ -142,30 +145,42 @@ func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySigne } // 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) ( - splitCount, inputsPerSplitCount int) { +func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64, + changeAddress util.Address) (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 transactionWithoutInputs := transaction.Tx.Clone() transactionWithoutInputs.Inputs = []*externalapi.DomainTransactionInput{} massWithoutInputs := s.txMassCalculator.CalculateTransactionMass(transactionWithoutInputs) - massForInputs := transactionMass - massWithoutInputs + massOfAllInputs := transactionMass - massWithoutInputs // Since the transaction was generated by kaspawallet, we assume all inputs have the same number of signatures, and // thus - the same mass. inputCount := len(transaction.Tx.Inputs) - massPerInput := massForInputs / uint64(inputCount) - if massForInputs%uint64(inputCount) > 0 { + massPerInput := massOfAllInputs / uint64(inputCount) + if massOfAllInputs%uint64(inputCount) > 0 { massPerInput++ } - inputsPerSplitCount = int((mempool.MaximumStandardTransactionMass - massWithoutInputs) / massPerInput) + // 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) + if err != nil { + return 0, 0, err + } + massForEverythingExceptInputsInSplitTransaction := + s.txMassCalculator.CalculateTransactionMass(splitTransactionWithoutInputs.Tx) + massForInputsInSplitTransaction := mempool.MaximumStandardTransactionMass - massForEverythingExceptInputsInSplitTransaction + + inputsPerSplitCount = int(massForInputsInSplitTransaction / massPerInput) splitCount = inputCount / inputsPerSplitCount if inputCount%inputsPerSplitCount > 0 { splitCount++ } - return splitCount, inputsPerSplitCount + return splitCount, inputsPerSplitCount, nil } func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,