mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-05 13:46:42 +00:00
Add support for auto-compound in kaspawallet send
(#1951)
* Add GetUTXOsByBalances command to rpc * Fix wrong commands in GetBalanceByAddress * Moved calculation of TransactionMass out of TransactionValidator, so t that it can be used in kaspawallet * Allow CreateUnsignedTransaction to return multiple transactions * Start working on split * Implement maybeSplitTransactionInner * estimateMassIncreaseForSignatures should multiply by the number of inputs * Implement createSplitTransaction * Implement mergeTransactions * Broadcast all transaction, not only 1 * workaround missing UTXOEntry in partially signed transaction * Bugfix in broadcast loop * Add underscores in some constants * Make all nets RelayNonStdTxs: false * Change estimateMassIncreaseForSignatures to estimateMassAfterSignatures * Allow situations where merge transaction doesn't have enough funds to pay fees * Add comments * A few of renames * Handle missed errors * Fix clone of PubKeySignaturePair to properly clone nil signatures * Add sanity check to make sure originalTransaction has exactly two outputs * Re-use change address for splitAddress * Add one more utxo if the total amount is smaller then what we need to send due to fees * Fix off-by-1 error in splitTrasnaction * Add a comment to maybeAutoCompoundTransaction * Add comment on why we are increasing inputCountPerSplit * Add comment explaining while originalTransaction has 1 or 2 outputs * Move oneMoreUTXOForMergeTransaction to split_transaction.go * Allow to add multiple utxos to pay fee for mergeTransactions, if needed * calculate split input counts and sizes properly * Allow multiple transactions inside the create-unsigned-transaction -> sign -> broadcast workflow * Print the number of transaction which was sent, in case there were multiple * Rename broadcastConfig.Transaction(File) to Transactions(File) * Convert alreadySelectedUTXOs to a map * Fix a typo * Add comment explaining that we assume all inputs are the same * Revert over-refactor of rename of config.Transaction -> config.Transactions * Rename: inputPerSplitCount -> inputsPerSplitCount * Add comment for splitAndInputPerSplitCounts * Use createSplitTransaction to calculate the upper bound of mass for split transactions
This commit is contained in:
parent
9fa08442cf
commit
639183ba0e
@ -2,13 +2,13 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func broadcast(conf *broadcastConfig) error {
|
func broadcast(conf *broadcastConfig) error {
|
||||||
@ -21,34 +21,40 @@ func broadcast(conf *broadcastConfig) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
if conf.Transaction == "" && conf.TransactionFile == "" {
|
if conf.Transactions == "" && conf.TransactionsFile == "" {
|
||||||
return errors.Errorf("Either --transaction or --transaction-file is required")
|
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")
|
return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionHex := conf.Transaction
|
transactionsHex := conf.Transactions
|
||||||
if conf.TransactionFile != "" {
|
if conf.TransactionsFile != "" {
|
||||||
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
|
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionsFile)
|
||||||
if err != nil {
|
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)
|
||||||
}
|
}
|
||||||
transactionHex = strings.TrimSpace(string(transactionHexBytes))
|
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
transaction, err := hex.DecodeString(transactionHex)
|
transactions, err := decodeTransactionsFromHex(transactionsHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction})
|
transactionsCount := len(transactions)
|
||||||
if err != nil {
|
for i, transaction := range transactions {
|
||||||
return err
|
response, err := daemonClient.Broadcast(ctx, &pb.BroadcastRequest{Transaction: transaction})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Transaction was sent successfully")
|
|
||||||
fmt.Printf("Transaction ID: \t%s\n", response.TxID)
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/config"
|
"github.com/kaspanet/kaspad/infrastructure/config"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/jessevdk/go-flags"
|
"github.com/jessevdk/go-flags"
|
||||||
)
|
)
|
||||||
@ -68,15 +69,15 @@ type createUnsignedTransactionConfig struct {
|
|||||||
type signConfig 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))"`
|
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"`
|
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)"`
|
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 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
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
type broadcastConfig struct {
|
type broadcastConfig struct {
|
||||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to (default: localhost:8082)"`
|
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)"`
|
Transactions 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)"`
|
TransactionsFile string `long:"transaction-file" short:"F" description:"The file containing the unsigned transaction to sign on (encoded in hex)"`
|
||||||
config.NetworkFlags
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,8 +2,8 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
@ -20,7 +20,7 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
||||||
response, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{
|
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||||
Address: conf.ToAddress,
|
Address: conf.ToAddress,
|
||||||
Amount: sendAmountSompi,
|
Amount: sendAmountSompi,
|
||||||
})
|
})
|
||||||
@ -29,6 +29,6 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Created unsigned transaction")
|
fmt.Println("Created unsigned transaction")
|
||||||
fmt.Println(hex.EncodeToString(response.UnsignedTransaction))
|
fmt.Println(encodeTransactionsToHex(response.UnsignedTransactions))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.27.1
|
// protoc-gen-go v1.26.0
|
||||||
// protoc v3.12.3
|
// protoc v3.12.3
|
||||||
// source: kaspawalletd.proto
|
// source: kaspawalletd.proto
|
||||||
|
|
||||||
@ -184,7 +184,7 @@ func (x *AddressBalances) GetPending() uint64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUnsignedTransactionRequest struct {
|
type CreateUnsignedTransactionsRequest struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
unknownFields protoimpl.UnknownFields
|
||||||
@ -193,8 +193,8 @@ type CreateUnsignedTransactionRequest struct {
|
|||||||
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
|
Amount uint64 `protobuf:"varint,2,opt,name=amount,proto3" json:"amount,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateUnsignedTransactionRequest) Reset() {
|
func (x *CreateUnsignedTransactionsRequest) Reset() {
|
||||||
*x = CreateUnsignedTransactionRequest{}
|
*x = CreateUnsignedTransactionsRequest{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_kaspawalletd_proto_msgTypes[3]
|
mi := &file_kaspawalletd_proto_msgTypes[3]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
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)
|
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]
|
mi := &file_kaspawalletd_proto_msgTypes[3]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@ -220,35 +220,35 @@ func (x *CreateUnsignedTransactionRequest) ProtoReflect() protoreflect.Message {
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use CreateUnsignedTransactionRequest.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateUnsignedTransactionsRequest.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateUnsignedTransactionRequest) Descriptor() ([]byte, []int) {
|
func (*CreateUnsignedTransactionsRequest) Descriptor() ([]byte, []int) {
|
||||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{3}
|
return file_kaspawalletd_proto_rawDescGZIP(), []int{3}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateUnsignedTransactionRequest) GetAddress() string {
|
func (x *CreateUnsignedTransactionsRequest) GetAddress() string {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Address
|
return x.Address
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateUnsignedTransactionRequest) GetAmount() uint64 {
|
func (x *CreateUnsignedTransactionsRequest) GetAmount() uint64 {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.Amount
|
return x.Amount
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
type CreateUnsignedTransactionResponse struct {
|
type CreateUnsignedTransactionsResponse struct {
|
||||||
state protoimpl.MessageState
|
state protoimpl.MessageState
|
||||||
sizeCache protoimpl.SizeCache
|
sizeCache protoimpl.SizeCache
|
||||||
unknownFields protoimpl.UnknownFields
|
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() {
|
func (x *CreateUnsignedTransactionsResponse) Reset() {
|
||||||
*x = CreateUnsignedTransactionResponse{}
|
*x = CreateUnsignedTransactionsResponse{}
|
||||||
if protoimpl.UnsafeEnabled {
|
if protoimpl.UnsafeEnabled {
|
||||||
mi := &file_kaspawalletd_proto_msgTypes[4]
|
mi := &file_kaspawalletd_proto_msgTypes[4]
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
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)
|
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]
|
mi := &file_kaspawalletd_proto_msgTypes[4]
|
||||||
if protoimpl.UnsafeEnabled && x != nil {
|
if protoimpl.UnsafeEnabled && x != nil {
|
||||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||||
@ -274,14 +274,14 @@ func (x *CreateUnsignedTransactionResponse) ProtoReflect() protoreflect.Message
|
|||||||
return mi.MessageOf(x)
|
return mi.MessageOf(x)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Deprecated: Use CreateUnsignedTransactionResponse.ProtoReflect.Descriptor instead.
|
// Deprecated: Use CreateUnsignedTransactionsResponse.ProtoReflect.Descriptor instead.
|
||||||
func (*CreateUnsignedTransactionResponse) Descriptor() ([]byte, []int) {
|
func (*CreateUnsignedTransactionsResponse) Descriptor() ([]byte, []int) {
|
||||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{4}
|
return file_kaspawalletd_proto_rawDescGZIP(), []int{4}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *CreateUnsignedTransactionResponse) GetUnsignedTransaction() []byte {
|
func (x *CreateUnsignedTransactionsResponse) GetUnsignedTransactions() [][]byte {
|
||||||
if x != nil {
|
if x != nil {
|
||||||
return x.UnsignedTransaction
|
return x.UnsignedTransactions
|
||||||
}
|
}
|
||||||
return nil
|
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,
|
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,
|
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,
|
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, 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,
|
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18,
|
||||||
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07,
|
0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
||||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e,
|
0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x6d, 0x6f, 0x75,
|
||||||
0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74, 0x22,
|
0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x04, 0x52, 0x06, 0x61, 0x6d, 0x6f, 0x75, 0x6e, 0x74,
|
||||||
0x55, 0x0a, 0x21, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65,
|
0x22, 0x58, 0x0a, 0x22, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e,
|
||||||
0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70,
|
0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x30, 0x0a, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
|
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, 0x0a, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e,
|
||||||
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28,
|
0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01,
|
||||||
0x0c, 0x52, 0x13, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
0x20, 0x03, 0x28, 0x0c, 0x52, 0x14, 0x75, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
|
||||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64,
|
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x16, 0x0a, 0x14, 0x53, 0x68,
|
||||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x31,
|
0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65,
|
||||||
0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52,
|
0x73, 0x74, 0x22, 0x31, 0x0a, 0x15, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65,
|
0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x61,
|
||||||
0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73,
|
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x07, 0x61, 0x64,
|
||||||
0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52,
|
0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
|
0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x4e, 0x65,
|
||||||
0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07,
|
0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x61,
|
0x12, 0x18, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28,
|
||||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
|
0x09, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x22, 0x34, 0x0a, 0x10, 0x42, 0x72,
|
||||||
0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20, 0x0a, 0x0b, 0x74, 0x72,
|
0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x20,
|
||||||
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52,
|
0x0a, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20,
|
||||||
0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x27, 0x0a, 0x11,
|
0x01, 0x28, 0x0c, 0x52, 0x0b, 0x74, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||||
0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
0x22, 0x27, 0x0a, 0x11, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73,
|
||||||
0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x78, 0x49, 0x44, 0x18, 0x01, 0x20,
|
||||||
0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77,
|
0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x78, 0x49, 0x44, 0x22, 0x11, 0x0a, 0x0f, 0x53, 0x68, 0x75,
|
||||||
0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10, 0x53, 0x68, 0x75, 0x74,
|
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x12, 0x0a, 0x10,
|
||||||
0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x91, 0x03, 0x0a,
|
0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||||
0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x37, 0x0a,
|
0x32, 0x94, 0x03, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
|
||||||
0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x12, 0x2e, 0x47, 0x65,
|
0x64, 0x12, 0x37, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12,
|
||||||
0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
|
0x12, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75,
|
||||||
0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70,
|
0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65,
|
||||||
0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x64, 0x0a, 0x19, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65,
|
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x67, 0x0a, 0x1a, 0x43, 0x72,
|
||||||
0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
|
0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e,
|
||||||
0x69, 0x6f, 0x6e, 0x12, 0x21, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69,
|
0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74,
|
||||||
0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52,
|
0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63,
|
||||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55,
|
0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x43,
|
||||||
0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69,
|
0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61,
|
||||||
0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d,
|
0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
|
||||||
0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x15, 0x2e,
|
0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||||
0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71,
|
0x73, 0x73, 0x65, 0x73, 0x12, 0x15, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65,
|
||||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 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,
|
||||||
0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37,
|
0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f,
|
||||||
0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x12, 0x2e, 0x4e,
|
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x37, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72,
|
||||||
0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
0x65, 0x73, 0x73, 0x12, 0x12, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
|
||||||
0x1a, 0x13, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73,
|
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64,
|
||||||
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64,
|
0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x31,
|
||||||
0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x10, 0x2e, 0x53, 0x68, 0x75,
|
||||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e,
|
0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x53,
|
||||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x34, 0x0a, 0x09, 0x42, 0x72,
|
0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||||
0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
|
0x00, 0x12, 0x34, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x11,
|
||||||
0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x42, 0x72, 0x6f,
|
0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||||
0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
0x74, 0x1a, 0x12, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65, 0x73,
|
||||||
0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b,
|
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75,
|
||||||
0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63,
|
0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b,
|
||||||
0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64,
|
0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
|
||||||
0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62,
|
||||||
|
0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -720,30 +721,30 @@ func file_kaspawalletd_proto_rawDescGZIP() []byte {
|
|||||||
|
|
||||||
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 13)
|
||||||
var file_kaspawalletd_proto_goTypes = []interface{}{
|
var file_kaspawalletd_proto_goTypes = []interface{}{
|
||||||
(*GetBalanceRequest)(nil), // 0: GetBalanceRequest
|
(*GetBalanceRequest)(nil), // 0: GetBalanceRequest
|
||||||
(*GetBalanceResponse)(nil), // 1: GetBalanceResponse
|
(*GetBalanceResponse)(nil), // 1: GetBalanceResponse
|
||||||
(*AddressBalances)(nil), // 2: AddressBalances
|
(*AddressBalances)(nil), // 2: AddressBalances
|
||||||
(*CreateUnsignedTransactionRequest)(nil), // 3: CreateUnsignedTransactionRequest
|
(*CreateUnsignedTransactionsRequest)(nil), // 3: CreateUnsignedTransactionsRequest
|
||||||
(*CreateUnsignedTransactionResponse)(nil), // 4: CreateUnsignedTransactionResponse
|
(*CreateUnsignedTransactionsResponse)(nil), // 4: CreateUnsignedTransactionsResponse
|
||||||
(*ShowAddressesRequest)(nil), // 5: ShowAddressesRequest
|
(*ShowAddressesRequest)(nil), // 5: ShowAddressesRequest
|
||||||
(*ShowAddressesResponse)(nil), // 6: ShowAddressesResponse
|
(*ShowAddressesResponse)(nil), // 6: ShowAddressesResponse
|
||||||
(*NewAddressRequest)(nil), // 7: NewAddressRequest
|
(*NewAddressRequest)(nil), // 7: NewAddressRequest
|
||||||
(*NewAddressResponse)(nil), // 8: NewAddressResponse
|
(*NewAddressResponse)(nil), // 8: NewAddressResponse
|
||||||
(*BroadcastRequest)(nil), // 9: BroadcastRequest
|
(*BroadcastRequest)(nil), // 9: BroadcastRequest
|
||||||
(*BroadcastResponse)(nil), // 10: BroadcastResponse
|
(*BroadcastResponse)(nil), // 10: BroadcastResponse
|
||||||
(*ShutdownRequest)(nil), // 11: ShutdownRequest
|
(*ShutdownRequest)(nil), // 11: ShutdownRequest
|
||||||
(*ShutdownResponse)(nil), // 12: ShutdownResponse
|
(*ShutdownResponse)(nil), // 12: ShutdownResponse
|
||||||
}
|
}
|
||||||
var file_kaspawalletd_proto_depIdxs = []int32{
|
var file_kaspawalletd_proto_depIdxs = []int32{
|
||||||
2, // 0: GetBalanceResponse.addressBalances:type_name -> AddressBalances
|
2, // 0: GetBalanceResponse.addressBalances:type_name -> AddressBalances
|
||||||
0, // 1: kaspawalletd.GetBalance:input_type -> GetBalanceRequest
|
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
|
5, // 3: kaspawalletd.ShowAddresses:input_type -> ShowAddressesRequest
|
||||||
7, // 4: kaspawalletd.NewAddress:input_type -> NewAddressRequest
|
7, // 4: kaspawalletd.NewAddress:input_type -> NewAddressRequest
|
||||||
11, // 5: kaspawalletd.Shutdown:input_type -> ShutdownRequest
|
11, // 5: kaspawalletd.Shutdown:input_type -> ShutdownRequest
|
||||||
9, // 6: kaspawalletd.Broadcast:input_type -> BroadcastRequest
|
9, // 6: kaspawalletd.Broadcast:input_type -> BroadcastRequest
|
||||||
1, // 7: kaspawalletd.GetBalance:output_type -> GetBalanceResponse
|
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
|
6, // 9: kaspawalletd.ShowAddresses:output_type -> ShowAddressesResponse
|
||||||
8, // 10: kaspawalletd.NewAddress:output_type -> NewAddressResponse
|
8, // 10: kaspawalletd.NewAddress:output_type -> NewAddressResponse
|
||||||
12, // 11: kaspawalletd.Shutdown:output_type -> ShutdownResponse
|
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{} {
|
file_kaspawalletd_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*CreateUnsignedTransactionRequest); i {
|
switch v := v.(*CreateUnsignedTransactionsRequest); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
@ -810,7 +811,7 @@ func file_kaspawalletd_proto_init() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_kaspawalletd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
file_kaspawalletd_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
|
||||||
switch v := v.(*CreateUnsignedTransactionResponse); i {
|
switch v := v.(*CreateUnsignedTransactionsResponse); i {
|
||||||
case 0:
|
case 0:
|
||||||
return &v.state
|
return &v.state
|
||||||
case 1:
|
case 1:
|
||||||
|
@ -4,7 +4,7 @@ option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb";
|
|||||||
|
|
||||||
service kaspawalletd {
|
service kaspawalletd {
|
||||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
|
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
|
||||||
rpc CreateUnsignedTransaction (CreateUnsignedTransactionRequest) returns (CreateUnsignedTransactionResponse) {}
|
rpc CreateUnsignedTransactions (CreateUnsignedTransactionsRequest) returns (CreateUnsignedTransactionsResponse) {}
|
||||||
rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {}
|
rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {}
|
||||||
rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
|
rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
|
||||||
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
|
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
|
||||||
@ -26,13 +26,13 @@ message AddressBalances {
|
|||||||
uint64 pending = 3;
|
uint64 pending = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateUnsignedTransactionRequest {
|
message CreateUnsignedTransactionsRequest {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
uint64 amount = 2;
|
uint64 amount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateUnsignedTransactionResponse {
|
message CreateUnsignedTransactionsResponse {
|
||||||
bytes unsignedTransaction = 1;
|
repeated bytes unsignedTransactions = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ShowAddressesRequest {
|
message ShowAddressesRequest {
|
||||||
|
@ -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.
|
// 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 {
|
type KaspawalletdClient interface {
|
||||||
GetBalance(ctx context.Context, in *GetBalanceRequest, opts ...grpc.CallOption) (*GetBalanceResponse, error)
|
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)
|
ShowAddresses(ctx context.Context, in *ShowAddressesRequest, opts ...grpc.CallOption) (*ShowAddressesResponse, error)
|
||||||
NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
|
NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
|
||||||
Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, 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
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *kaspawalletdClient) CreateUnsignedTransaction(ctx context.Context, in *CreateUnsignedTransactionRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionResponse, error) {
|
func (c *kaspawalletdClient) CreateUnsignedTransactions(ctx context.Context, in *CreateUnsignedTransactionsRequest, opts ...grpc.CallOption) (*CreateUnsignedTransactionsResponse, error) {
|
||||||
out := new(CreateUnsignedTransactionResponse)
|
out := new(CreateUnsignedTransactionsResponse)
|
||||||
err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransaction", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/kaspawalletd/CreateUnsignedTransactions", in, out, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -93,7 +93,7 @@ func (c *kaspawalletdClient) Broadcast(ctx context.Context, in *BroadcastRequest
|
|||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
type KaspawalletdServer interface {
|
type KaspawalletdServer interface {
|
||||||
GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error)
|
GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error)
|
||||||
CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error)
|
CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error)
|
||||||
ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error)
|
ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error)
|
||||||
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
|
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
|
||||||
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
||||||
@ -108,8 +108,8 @@ type UnimplementedKaspawalletdServer struct {
|
|||||||
func (UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) {
|
func (UnimplementedKaspawalletdServer) GetBalance(context.Context, *GetBalanceRequest) (*GetBalanceResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method GetBalance not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method GetBalance not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedKaspawalletdServer) CreateUnsignedTransaction(context.Context, *CreateUnsignedTransactionRequest) (*CreateUnsignedTransactionResponse, error) {
|
func (UnimplementedKaspawalletdServer) CreateUnsignedTransactions(context.Context, *CreateUnsignedTransactionsRequest) (*CreateUnsignedTransactionsResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransaction not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method CreateUnsignedTransactions not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedKaspawalletdServer) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) {
|
func (UnimplementedKaspawalletdServer) ShowAddresses(context.Context, *ShowAddressesRequest) (*ShowAddressesResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method ShowAddresses not implemented")
|
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)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
func _Kaspawalletd_CreateUnsignedTransaction_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
func _Kaspawalletd_CreateUnsignedTransactions_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(CreateUnsignedTransactionRequest)
|
in := new(CreateUnsignedTransactionsRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if interceptor == nil {
|
if interceptor == nil {
|
||||||
return srv.(KaspawalletdServer).CreateUnsignedTransaction(ctx, in)
|
return srv.(KaspawalletdServer).CreateUnsignedTransactions(ctx, in)
|
||||||
}
|
}
|
||||||
info := &grpc.UnaryServerInfo{
|
info := &grpc.UnaryServerInfo{
|
||||||
Server: srv,
|
Server: srv,
|
||||||
FullMethod: "/kaspawalletd/CreateUnsignedTransaction",
|
FullMethod: "/kaspawalletd/CreateUnsignedTransactions",
|
||||||
}
|
}
|
||||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
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)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
@ -256,8 +256,8 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
|
|||||||
Handler: _Kaspawalletd_GetBalance_Handler,
|
Handler: _Kaspawalletd_GetBalance_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "CreateUnsignedTransaction",
|
MethodName: "CreateUnsignedTransactions",
|
||||||
Handler: _Kaspawalletd_CreateUnsignedTransaction_Handler,
|
Handler: _Kaspawalletd_CreateUnsignedTransactions_Handler,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
MethodName: "ShowAddresses",
|
MethodName: "ShowAddresses",
|
||||||
|
@ -10,15 +10,15 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"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)
|
err := s.keysFile.SetLastUsedInternalIndex(s.keysFile.LastUsedInternalIndex() + 1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.keysFile.Save()
|
err = s.keysFile.Save()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
walletAddr := &walletAddress{
|
walletAddr := &walletAddress{
|
||||||
@ -27,7 +27,11 @@ func (s *server) changeAddress() (util.Address, error) {
|
|||||||
keyChain: libkaspawallet.InternalKeychain,
|
keyChain: libkaspawallet.InternalKeychain,
|
||||||
}
|
}
|
||||||
path := s.walletAddressPath(walletAddr)
|
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) {
|
func (s *server) ShowAddresses(_ context.Context, request *pb.ShowAddressesRequest) (*pb.ShowAddressesResponse, error) {
|
||||||
|
@ -10,7 +10,11 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.CreateUnsignedTransactionRequest) (*pb.CreateUnsignedTransactionResponse, error) {
|
// 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()
|
s.lock.Lock()
|
||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
@ -28,14 +32,12 @@ func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.Create
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement a better fee estimation mechanism
|
|
||||||
const feePerInput = 10000
|
|
||||||
selectedUTXOs, changeSompi, err := s.selectUTXOs(request.Amount, feePerInput)
|
selectedUTXOs, changeSompi, err := s.selectUTXOs(request.Amount, feePerInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
changeAddress, err := s.changeAddress()
|
changeAddress, changeWalletAddress, err := s.changeAddress()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -53,7 +55,12 @@ func (s *server) CreateUnsignedTransaction(_ context.Context, request *pb.Create
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &pb.CreateUnsignedTransactionResponse{UnsignedTransaction: unsignedTransaction}, nil
|
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) (
|
func (s *server) selectUTXOs(spendAmount uint64, feePerInput uint64) (
|
||||||
|
@ -7,6 +7,8 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/profiling"
|
"github.com/kaspanet/kaspad/util/profiling"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
@ -32,6 +34,7 @@ type server struct {
|
|||||||
keysFile *keys.File
|
keysFile *keys.File
|
||||||
shutdown chan struct{}
|
shutdown chan struct{}
|
||||||
addressSet walletAddressSet
|
addressSet walletAddressSet
|
||||||
|
txMassCalculator *txmass.Calculator
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start starts the kaspawalletd server
|
// Start starts the kaspawalletd server
|
||||||
@ -69,6 +72,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
|||||||
keysFile: keysFile,
|
keysFile: keysFile,
|
||||||
shutdown: make(chan struct{}),
|
shutdown: make(chan struct{}),
|
||||||
addressSet: make(walletAddressSet),
|
addressSet: make(walletAddressSet),
|
||||||
|
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
||||||
}
|
}
|
||||||
|
|
||||||
spawn("serverInstance.sync", func() {
|
spawn("serverInstance.sync", func() {
|
||||||
|
278
cmd/kaspawallet/daemon/server/split_transaction.go
Normal file
278
cmd/kaspawallet/daemon/server/split_transaction.go
Normal file
@ -0,0 +1,278 @@
|
|||||||
|
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"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
splitTransactions, err := s.maybeSplitTransaction(transaction, changeAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if len(splitTransactions) > 1 {
|
||||||
|
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
splitTransactions = append(splitTransactions, mergeTransaction)
|
||||||
|
}
|
||||||
|
|
||||||
|
splitTransactionsBytes := make([][]byte, len(splitTransactions))
|
||||||
|
for i, splitTransaction := range splitTransactions {
|
||||||
|
splitTransactionsBytes[i], err = serialization.SerializePartiallySignedTransaction(splitTransaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return splitTransactionsBytes, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
// 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
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(changeWalletAddress),
|
||||||
|
}
|
||||||
|
totalValue += output.Value
|
||||||
|
totalValue -= feePerInput
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
utxos = append(utxos, additionalUTXOs...)
|
||||||
|
totalValue += totalValueAdded
|
||||||
|
}
|
||||||
|
|
||||||
|
payments := []*libkaspawallet.Payment{{
|
||||||
|
Address: toAddress,
|
||||||
|
Amount: sentValue,
|
||||||
|
}}
|
||||||
|
if totalValue > sentValue {
|
||||||
|
payments = append(payments, &libkaspawallet.Payment{
|
||||||
|
Address: changeAddress,
|
||||||
|
Amount: totalValue - sentValue,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||||
|
s.keysFile.MinimumSignatures, payments, utxos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) maybeSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
||||||
|
changeAddress util.Address) ([]*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
|
transactionMass, err := s.estimateMassAfterSignatures(transaction)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if transactionMass < mempool.MaximumStandardTransactionMass {
|
||||||
|
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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++ {
|
||||||
|
startIndex := i * inputCountPerSplit
|
||||||
|
endIndex := startIndex + inputCountPerSplit
|
||||||
|
var err error
|
||||||
|
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
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)
|
||||||
|
|
||||||
|
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 := massOfAllInputs / uint64(inputCount)
|
||||||
|
if massOfAllInputs%uint64(inputCount) > 0 {
|
||||||
|
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, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
||||||
|
changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
|
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
||||||
|
totalSompi := uint64(0)
|
||||||
|
|
||||||
|
for i := startIndex; i < endIndex && i < len(transaction.PartiallySignedInputs); i++ {
|
||||||
|
partiallySignedInput := transaction.PartiallySignedInputs[i]
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
|
||||||
|
transaction = transaction.Clone()
|
||||||
|
var signatureSize uint64
|
||||||
|
if s.keysFile.ECDSA {
|
||||||
|
signatureSize = secp256k1.SerializedECDSASignatureSize
|
||||||
|
} else {
|
||||||
|
signatureSize = secp256k1.SerializedSchnorrSignatureSize
|
||||||
|
}
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
|
||||||
|
transactionWithSignatures, err := libkaspawallet.ExtractTransactionDeserialized(transaction, s.keysFile.ECDSA)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 0, err
|
||||||
|
}
|
||||||
|
alreadySelectedUTXOsMap := make(map[externalapi.DomainOutpoint]struct{}, len(alreadySelectedUTXOs))
|
||||||
|
for _, alreadySelectedUTXO := range alreadySelectedUTXOs {
|
||||||
|
alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, utxo := range s.utxosSortedByAmount {
|
||||||
|
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
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 additionalUTXOs, totalValueAdded, nil
|
||||||
|
}
|
152
cmd/kaspawallet/daemon/server/split_transaction_test.go
Normal file
152
cmd/kaspawallet/daemon/server/split_transaction_test.go
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error from estimateMassAfterSignatures: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
signedTxStep2Bytes, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1Bytes, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Sign: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
extractedSignedTx, err := libkaspawallet.ExtractTransaction(signedTxStep2Bytes, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ExtractTransaction: %+v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionMass(extractedSignedTx)
|
||||||
|
|
||||||
|
if estimatedMassAfterSignatures != actualMassAfterSignatures {
|
||||||
|
t.Errorf("Estimated mass after signatures: %d but actually got %d",
|
||||||
|
estimatedMassAfterSignatures, actualMassAfterSignatures)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func 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
|
||||||
|
}
|
@ -34,6 +34,44 @@ type PubKeySignaturePair struct {
|
|||||||
Signature []byte
|
Signature []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clone creates a deep-clone of this PartiallySignedTransaction
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a deep-clone of this PartiallySignedInput
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clone creates a deep-clone of this PubKeySignaturePair
|
||||||
|
func (psp PubKeySignaturePair) Clone() *PubKeySignaturePair {
|
||||||
|
clone := &PubKeySignaturePair{
|
||||||
|
ExtendedPublicKey: psp.ExtendedPublicKey,
|
||||||
|
}
|
||||||
|
if psp.Signature != nil {
|
||||||
|
clone.Signature = make([]byte, len(psp.Signature))
|
||||||
|
copy(clone.Signature, psp.Signature)
|
||||||
|
}
|
||||||
|
return clone
|
||||||
|
}
|
||||||
|
|
||||||
// DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction.
|
// DeserializePartiallySignedTransaction deserializes a byte slice into PartiallySignedTransaction.
|
||||||
func DeserializePartiallySignedTransaction(serializedPartiallySignedTransaction []byte) (*PartiallySignedTransaction, error) {
|
func DeserializePartiallySignedTransaction(serializedPartiallySignedTransaction []byte) (*PartiallySignedTransaction, error) {
|
||||||
protoPartiallySignedTransaction := &protoserialization.PartiallySignedTransaction{}
|
protoPartiallySignedTransaction := &protoserialization.PartiallySignedTransaction{}
|
||||||
|
@ -40,7 +40,6 @@ func Sign(params *dagconfig.Params, mnemonics []string, serializedPSTx []byte, e
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
|
return serialization.SerializePartiallySignedTransaction(partiallySignedTransaction)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -159,6 +159,7 @@ func createUnsignedTransaction(
|
|||||||
Tx: domainTransaction,
|
Tx: domainTransaction,
|
||||||
PartiallySignedInputs: partiallySignedInputs,
|
PartiallySignedInputs: partiallySignedInputs,
|
||||||
}, nil
|
}, nil
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
|
// IsTransactionFullySigned returns whether the transaction is fully signed and ready to broadcast.
|
||||||
@ -194,10 +195,14 @@ func ExtractTransaction(partiallySignedTransactionBytes []byte, ecdsa bool) (*ex
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return extractTransaction(partiallySignedTransaction, ecdsa)
|
return ExtractTransactionDeserialized(partiallySignedTransaction, ecdsa)
|
||||||
}
|
}
|
||||||
|
|
||||||
func extractTransaction(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 {
|
for i, input := range partiallySignedTransaction.PartiallySignedInputs {
|
||||||
isMultisig := len(input.PubKeySignaturePairs) > 1
|
isMultisig := len(input.PubKeySignaturePairs) > 1
|
||||||
scriptBuilder := txscript.NewScriptBuilder()
|
scriptBuilder := txscript.NewScriptBuilder()
|
||||||
|
@ -2,6 +2,9 @@ package libkaspawallet_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus"
|
"github.com/kaspanet/kaspad/domain/consensus"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"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/txscript"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||||
"github.com/kaspanet/kaspad/util"
|
"github.com/kaspanet/kaspad/util"
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
|
func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
|
||||||
@ -106,7 +107,7 @@ func TestMultisig(t *testing.T) {
|
|||||||
Amount: 10,
|
Amount: 10,
|
||||||
}}, selectedUTXOs)
|
}}, selectedUTXOs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateUnsignedTransaction: %+v", err)
|
t.Fatalf("CreateUnsignedTransactions: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
||||||
@ -267,7 +268,7 @@ func TestP2PK(t *testing.T) {
|
|||||||
Amount: 10,
|
Amount: 10,
|
||||||
}}, selectedUTXOs)
|
}}, selectedUTXOs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("CreateUnsignedTransaction: %+v", err)
|
t.Fatalf("CreateUnsignedTransactions: %+v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(unsignedTransaction)
|
||||||
|
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||||
@ -31,10 +32,11 @@ func send(conf *sendConfig) error {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
||||||
createUnsignedTransactionResponse, err := daemonClient.CreateUnsignedTransaction(ctx, &pb.CreateUnsignedTransactionRequest{
|
createUnsignedTransactionsResponse, err :=
|
||||||
Address: conf.ToAddress,
|
daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||||
Amount: sendAmountSompi,
|
Address: conf.ToAddress,
|
||||||
})
|
Amount: sendAmountSompi,
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -44,22 +46,37 @@ func send(conf *sendConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, createUnsignedTransactionResponse.UnsignedTransaction, keysFile.ECDSA)
|
signedTransactions := make([][]byte, len(createUnsignedTransactionsResponse.UnsignedTransactions))
|
||||||
if err != nil {
|
for i, unsignedTransaction := range createUnsignedTransactionsResponse.UnsignedTransactions {
|
||||||
return err
|
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)
|
if len(signedTransactions) > 1 {
|
||||||
defer cancel2()
|
fmt.Printf("Broadcasting %d transactions\n", len(signedTransactions))
|
||||||
broadcastResponse, err := daemonClient.Broadcast(ctx2, &pb.BroadcastRequest{
|
|
||||||
Transaction: signedTransaction,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
for _, signedTransaction := range signedTransactions {
|
||||||
|
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{
|
||||||
|
Transaction: signedTransaction,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
fmt.Println("Transaction was sent successfully")
|
fmt.Println("Transaction was sent successfully")
|
||||||
fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID)
|
fmt.Printf("Transaction ID: \t%s\n", broadcastResponse.TxID)
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,21 +1,16 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func sign(conf *signConfig) error {
|
func sign(conf *signConfig) error {
|
||||||
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if conf.Transaction == "" && conf.TransactionFile == "" {
|
if conf.Transaction == "" && conf.TransactionFile == "" {
|
||||||
return errors.Errorf("Either --transaction or --transaction-file is required")
|
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")
|
return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionHex := conf.Transaction
|
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
privateKeys, err := keysFile.DecryptMnemonics(conf.Password)
|
privateKeys, err := keysFile.DecryptMnemonics(conf.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
isFullySigned, err := libkaspawallet.IsTransactionFullySigned(updatedPartiallySignedTransaction)
|
updatedPartiallySignedTransactions := make([][]byte, len(partiallySignedTransactions))
|
||||||
if err != nil {
|
for i, partiallySignedTransaction := range partiallySignedTransactions {
|
||||||
return err
|
updatedPartiallySignedTransactions[i], err =
|
||||||
|
libkaspawallet.Sign(conf.NetParams(), privateKeys, partiallySignedTransaction, keysFile.ECDSA)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if isFullySigned {
|
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 areAllTransactionsFullySigned {
|
||||||
fmt.Println("The transaction is signed and ready to broadcast")
|
fmt.Println("The transaction is signed and ready to broadcast")
|
||||||
} else {
|
} else {
|
||||||
fmt.Println("Successfully signed transaction")
|
fmt.Println("Successfully signed transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Transaction: %x\n", updatedPartiallySignedTransaction)
|
fmt.Println("Transaction: ")
|
||||||
|
fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
33
cmd/kaspawallet/transactions_hex_encoding.go
Normal file
33
cmd/kaspawallet/transactions_hex_encoding.go
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 = "_"
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
@ -1,8 +1,6 @@
|
|||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
|
||||||
"github.com/kaspanet/kaspad/util/staging"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
@ -10,6 +8,8 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
"github.com/kaspanet/kaspad/util/staging"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package consensus
|
package consensus
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"sync"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockwindowheapslicestore"
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/blockwindowheapslicestore"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/datastructures/daawindowstore"
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/daawindowstore"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/blockparentbuilder"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/blockparentbuilder"
|
||||||
parentssanager "github.com/kaspanet/kaspad/domain/consensus/processes/parentsmanager"
|
parentssanager "github.com/kaspanet/kaspad/domain/consensus/processes/parentsmanager"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningproofmanager"
|
"github.com/kaspanet/kaspad/domain/consensus/processes/pruningproofmanager"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/prefixmanager/prefix"
|
"github.com/kaspanet/kaspad/domain/prefixmanager/prefix"
|
||||||
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
|
|
||||||
consensusdatabase "github.com/kaspanet/kaspad/domain/consensus/database"
|
consensusdatabase "github.com/kaspanet/kaspad/domain/consensus/database"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/datastructures/acceptancedatastore"
|
"github.com/kaspanet/kaspad/domain/consensus/datastructures/acceptancedatastore"
|
||||||
@ -171,6 +172,9 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
config.GenesisHash,
|
config.GenesisHash,
|
||||||
config.MaxBlockLevel,
|
config.MaxBlockLevel,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
txMassCalculator := txmass.NewCalculator(config.MassPerTxByte, config.MassPerScriptPubKeyByte, config.MassPerSigOp)
|
||||||
|
|
||||||
pastMedianTimeManager := f.pastMedianTimeConsructor(
|
pastMedianTimeManager := f.pastMedianTimeConsructor(
|
||||||
config.TimestampDeviationTolerance,
|
config.TimestampDeviationTolerance,
|
||||||
dbManager,
|
dbManager,
|
||||||
@ -180,14 +184,12 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
config.GenesisHash)
|
config.GenesisHash)
|
||||||
transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity,
|
transactionValidator := transactionvalidator.New(config.BlockCoinbaseMaturity,
|
||||||
config.EnableNonNativeSubnetworks,
|
config.EnableNonNativeSubnetworks,
|
||||||
config.MassPerTxByte,
|
|
||||||
config.MassPerScriptPubKeyByte,
|
|
||||||
config.MassPerSigOp,
|
|
||||||
config.MaxCoinbasePayloadLength,
|
config.MaxCoinbasePayloadLength,
|
||||||
dbManager,
|
dbManager,
|
||||||
pastMedianTimeManager,
|
pastMedianTimeManager,
|
||||||
ghostdagDataStore,
|
ghostdagDataStore,
|
||||||
daaBlocksStore)
|
daaBlocksStore,
|
||||||
|
txMassCalculator)
|
||||||
difficultyManager := f.difficultyConstructor(
|
difficultyManager := f.difficultyConstructor(
|
||||||
dbManager,
|
dbManager,
|
||||||
ghostdagManager,
|
ghostdagManager,
|
||||||
@ -331,6 +333,8 @@ func (f *factory) NewConsensus(config *Config, db infrastructuredatabase.Databas
|
|||||||
reachabilityDataStore,
|
reachabilityDataStore,
|
||||||
consensusStateStore,
|
consensusStateStore,
|
||||||
daaBlocksStore,
|
daaBlocksStore,
|
||||||
|
|
||||||
|
txMassCalculator,
|
||||||
)
|
)
|
||||||
|
|
||||||
syncManager := syncmanager.New(
|
syncManager := syncmanager.New(
|
||||||
|
@ -4,6 +4,8 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/util/difficulty"
|
"github.com/kaspanet/kaspad/util/difficulty"
|
||||||
@ -48,6 +50,8 @@ type blockValidator struct {
|
|||||||
reachabilityStore model.ReachabilityDataStore
|
reachabilityStore model.ReachabilityDataStore
|
||||||
consensusStateStore model.ConsensusStateStore
|
consensusStateStore model.ConsensusStateStore
|
||||||
daaBlocksStore model.DAABlocksStore
|
daaBlocksStore model.DAABlocksStore
|
||||||
|
|
||||||
|
txMassCalculator *txmass.Calculator
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instantiates a new BlockValidator
|
// New instantiates a new BlockValidator
|
||||||
@ -87,6 +91,8 @@ func New(powMax *big.Int,
|
|||||||
reachabilityStore model.ReachabilityDataStore,
|
reachabilityStore model.ReachabilityDataStore,
|
||||||
consensusStateStore model.ConsensusStateStore,
|
consensusStateStore model.ConsensusStateStore,
|
||||||
daaBlocksStore model.DAABlocksStore,
|
daaBlocksStore model.DAABlocksStore,
|
||||||
|
|
||||||
|
txMassCalculator *txmass.Calculator,
|
||||||
) model.BlockValidator {
|
) model.BlockValidator {
|
||||||
|
|
||||||
return &blockValidator{
|
return &blockValidator{
|
||||||
@ -126,5 +132,7 @@ func New(powMax *big.Int,
|
|||||||
reachabilityStore: reachabilityStore,
|
reachabilityStore: reachabilityStore,
|
||||||
consensusStateStore: consensusStateStore,
|
consensusStateStore: consensusStateStore,
|
||||||
daaBlocksStore: daaBlocksStore,
|
daaBlocksStore: daaBlocksStore,
|
||||||
|
|
||||||
|
txMassCalculator: txMassCalculator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,99 +2,12 @@ package transactionvalidator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"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) {
|
func (v *transactionValidator) PopulateMass(transaction *externalapi.DomainTransaction) {
|
||||||
if transaction.Mass != 0 {
|
if transaction.Mass != 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
transaction.Mass = v.transactionMass(transaction)
|
transaction.Mass = v.txMassCalculator.CalculateTransactionMass(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
|
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package transactionvalidator
|
|||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model"
|
"github.com/kaspanet/kaspad/domain/consensus/model"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||||
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sigCacheSize = 10_000
|
const sigCacheSize = 10_000
|
||||||
@ -16,32 +17,25 @@ type transactionValidator struct {
|
|||||||
ghostdagDataStore model.GHOSTDAGDataStore
|
ghostdagDataStore model.GHOSTDAGDataStore
|
||||||
daaBlocksStore model.DAABlocksStore
|
daaBlocksStore model.DAABlocksStore
|
||||||
enableNonNativeSubnetworks bool
|
enableNonNativeSubnetworks bool
|
||||||
massPerTxByte uint64
|
|
||||||
massPerScriptPubKeyByte uint64
|
|
||||||
massPerSigOp uint64
|
|
||||||
maxCoinbasePayloadLength uint64
|
maxCoinbasePayloadLength uint64
|
||||||
sigCache *txscript.SigCache
|
sigCache *txscript.SigCache
|
||||||
sigCacheECDSA *txscript.SigCacheECDSA
|
sigCacheECDSA *txscript.SigCacheECDSA
|
||||||
|
txMassCalculator *txmass.Calculator
|
||||||
}
|
}
|
||||||
|
|
||||||
// New instantiates a new TransactionValidator
|
// New instantiates a new TransactionValidator
|
||||||
func New(blockCoinbaseMaturity uint64,
|
func New(blockCoinbaseMaturity uint64,
|
||||||
enableNonNativeSubnetworks bool,
|
enableNonNativeSubnetworks bool,
|
||||||
massPerTxByte uint64,
|
|
||||||
massPerScriptPubKeyByte uint64,
|
|
||||||
massPerSigOp uint64,
|
|
||||||
maxCoinbasePayloadLength uint64,
|
maxCoinbasePayloadLength uint64,
|
||||||
databaseContext model.DBReader,
|
databaseContext model.DBReader,
|
||||||
pastMedianTimeManager model.PastMedianTimeManager,
|
pastMedianTimeManager model.PastMedianTimeManager,
|
||||||
ghostdagDataStore model.GHOSTDAGDataStore,
|
ghostdagDataStore model.GHOSTDAGDataStore,
|
||||||
daaBlocksStore model.DAABlocksStore) model.TransactionValidator {
|
daaBlocksStore model.DAABlocksStore,
|
||||||
|
txMassCalculator *txmass.Calculator) model.TransactionValidator {
|
||||||
|
|
||||||
return &transactionValidator{
|
return &transactionValidator{
|
||||||
blockCoinbaseMaturity: blockCoinbaseMaturity,
|
blockCoinbaseMaturity: blockCoinbaseMaturity,
|
||||||
enableNonNativeSubnetworks: enableNonNativeSubnetworks,
|
enableNonNativeSubnetworks: enableNonNativeSubnetworks,
|
||||||
massPerTxByte: massPerTxByte,
|
|
||||||
massPerScriptPubKeyByte: massPerScriptPubKeyByte,
|
|
||||||
massPerSigOp: massPerSigOp,
|
|
||||||
maxCoinbasePayloadLength: maxCoinbasePayloadLength,
|
maxCoinbasePayloadLength: maxCoinbasePayloadLength,
|
||||||
databaseContext: databaseContext,
|
databaseContext: databaseContext,
|
||||||
pastMedianTimeManager: pastMedianTimeManager,
|
pastMedianTimeManager: pastMedianTimeManager,
|
||||||
@ -49,5 +43,6 @@ func New(blockCoinbaseMaturity uint64,
|
|||||||
daaBlocksStore: daaBlocksStore,
|
daaBlocksStore: daaBlocksStore,
|
||||||
sigCache: txscript.NewSigCache(sigCacheSize),
|
sigCache: txscript.NewSigCache(sigCacheSize),
|
||||||
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
sigCacheECDSA: txscript.NewSigCacheECDSA(sigCacheSize),
|
||||||
|
txMassCalculator: txMassCalculator,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,8 @@ const (
|
|||||||
// LockTimeThreshold is the number below which a lock time is
|
// LockTimeThreshold is the number below which a lock time is
|
||||||
// interpreted to be a DAA score.
|
// interpreted to be a DAA score.
|
||||||
LockTimeThreshold = 5e11 // Tue Nov 5 00:53:20 1985 UTC
|
LockTimeThreshold = 5e11 // Tue Nov 5 00:53:20 1985 UTC
|
||||||
|
|
||||||
|
// UnacceptedDAAScore is used to for UTXOEntries that were created by transactions in the mempool, or otherwise
|
||||||
|
// not-yet-accepted transactions.
|
||||||
|
UnacceptedDAAScore = math.MaxUint64
|
||||||
)
|
)
|
||||||
|
@ -318,7 +318,7 @@ var TestnetParams = Params{
|
|||||||
MinerConfirmationWindow: 2016,
|
MinerConfirmationWindow: 2016,
|
||||||
|
|
||||||
// Mempool parameters
|
// Mempool parameters
|
||||||
RelayNonStdTxs: true,
|
RelayNonStdTxs: false,
|
||||||
|
|
||||||
// AcceptUnroutable specifies whether this network accepts unroutable
|
// AcceptUnroutable specifies whether this network accepts unroutable
|
||||||
// IP addresses, such as 10.0.0.0/8
|
// IP addresses, such as 10.0.0.0/8
|
||||||
@ -386,7 +386,7 @@ var SimnetParams = Params{
|
|||||||
MinerConfirmationWindow: 100,
|
MinerConfirmationWindow: 100,
|
||||||
|
|
||||||
// Mempool parameters
|
// Mempool parameters
|
||||||
RelayNonStdTxs: true,
|
RelayNonStdTxs: false,
|
||||||
|
|
||||||
// AcceptUnroutable specifies whether this network accepts unroutable
|
// AcceptUnroutable specifies whether this network accepts unroutable
|
||||||
// IP addresses, such as 10.0.0.0/8
|
// IP addresses, such as 10.0.0.0/8
|
||||||
@ -445,7 +445,7 @@ var DevnetParams = Params{
|
|||||||
MinerConfirmationWindow: 2016,
|
MinerConfirmationWindow: 2016,
|
||||||
|
|
||||||
// Mempool parameters
|
// Mempool parameters
|
||||||
RelayNonStdTxs: true,
|
RelayNonStdTxs: false,
|
||||||
|
|
||||||
// AcceptUnroutable specifies whether this network accepts unroutable
|
// AcceptUnroutable specifies whether this network accepts unroutable
|
||||||
// IP addresses, such as 10.0.0.0/8
|
// IP addresses, such as 10.0.0.0/8
|
||||||
|
@ -3,7 +3,7 @@ package mempool
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/processes/transactionvalidator"
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
|
|
||||||
@ -36,9 +36,9 @@ const (
|
|||||||
// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
|
// (1 + 15*74 + 3) + (15*34 + 3) + 23 = 1650
|
||||||
maximumStandardSignatureScriptSize = 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.
|
// 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 {
|
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
|
// almost as much to process as the sender fees, limit the maximum
|
||||||
// size of a transaction. This also helps mitigate CPU exhaustion
|
// size of a transaction. This also helps mitigate CPU exhaustion
|
||||||
// attacks.
|
// attacks.
|
||||||
if transaction.Mass > maximumStandardTransactionMass {
|
if transaction.Mass > MaximumStandardTransactionMass {
|
||||||
str := fmt.Sprintf("transaction mass of %d is larger than max allowed size of %d",
|
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)
|
return transactionRuleError(RejectNonstandard, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,7 +127,7 @@ func (mp *mempool) IsTransactionOutputDust(output *externalapi.DomainTransaction
|
|||||||
// The most common scripts are pay-to-pubkey, and as per the above
|
// 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
|
// breakdown, the minimum size of a p2pk input script is 148 bytes. So
|
||||||
// that figure is used.
|
// 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
|
// 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.
|
// coins is more than 1/3 of the minimum free transaction relay fee.
|
||||||
|
@ -6,10 +6,11 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"github.com/kaspanet/kaspad/domain/consensusreference"
|
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensusreference"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus"
|
"github.com/kaspanet/kaspad/domain/consensus"
|
||||||
@ -45,13 +46,13 @@ func TestCalcMinRequiredTxRelayFee(t *testing.T) {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"max standard tx size with default minimum relay fee",
|
"max standard tx size with default minimum relay fee",
|
||||||
maximumStandardTransactionMass,
|
MaximumStandardTransactionMass,
|
||||||
defaultMinimumRelayTransactionFee,
|
defaultMinimumRelayTransactionFee,
|
||||||
100000,
|
100000,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"max standard tx size with max sompi relay fee",
|
"max standard tx size with max sompi relay fee",
|
||||||
maximumStandardTransactionMass,
|
MaximumStandardTransactionMass,
|
||||||
constants.MaxSompi,
|
constants.MaxSompi,
|
||||||
constants.MaxSompi,
|
constants.MaxSompi,
|
||||||
},
|
},
|
||||||
@ -249,7 +250,7 @@ func TestCheckTransactionStandardInIsolation(t *testing.T) {
|
|||||||
name: "Transaction size is too large",
|
name: "Transaction size is too large",
|
||||||
tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{
|
tx: &externalapi.DomainTransaction{Version: 0, Inputs: []*externalapi.DomainTransactionInput{&dummyTxIn}, Outputs: []*externalapi.DomainTransactionOutput{{
|
||||||
Value: 0,
|
Value: 0,
|
||||||
ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, maximumStandardTransactionMass+1), 0},
|
ScriptPublicKey: &externalapi.ScriptPublicKey{bytes.Repeat([]byte{0x00}, MaximumStandardTransactionMass+1), 0},
|
||||||
}}},
|
}}},
|
||||||
height: 300000,
|
height: 300000,
|
||||||
isStandard: false,
|
isStandard: false,
|
||||||
|
@ -3,6 +3,7 @@ package mempool
|
|||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
"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/consensus/utils/utxo"
|
||||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -42,6 +43,6 @@ func fillInputs(transaction *externalapi.DomainTransaction, parentsInPool model.
|
|||||||
}
|
}
|
||||||
relevantOutput := parent.Transaction().Outputs[input.PreviousOutpoint.Index]
|
relevantOutput := parent.Transaction().Outputs[input.PreviousOutpoint.Index]
|
||||||
input.UTXOEntry = utxo.NewUTXOEntry(relevantOutput.Value, relevantOutput.ScriptPublicKey,
|
input.UTXOEntry = utxo.NewUTXOEntry(relevantOutput.Value, relevantOutput.ScriptPublicKey,
|
||||||
false, model.UnacceptedDAAScore)
|
false, constants.UnacceptedDAAScore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,8 @@ package mempool
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"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)}
|
outpoint := externalapi.DomainOutpoint{TransactionID: *transaction.TransactionID(), Index: uint32(i)}
|
||||||
|
|
||||||
mpus.poolUnspentOutputs[outpoint] =
|
mpus.poolUnspentOutputs[outpoint] =
|
||||||
utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, model.UnacceptedDAAScore)
|
utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false, constants.UnacceptedDAAScore)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
|
@ -3,6 +3,8 @@ package mempool
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
@ -153,7 +155,7 @@ func (op *orphansPool) processOrphansAfterAcceptedTransaction(acceptedTransactio
|
|||||||
for _, input := range orphan.Transaction().Inputs {
|
for _, input := range orphan.Transaction().Inputs {
|
||||||
if input.PreviousOutpoint.Equal(&outpoint) && input.UTXOEntry == nil {
|
if input.PreviousOutpoint.Equal(&outpoint) && input.UTXOEntry == nil {
|
||||||
input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false,
|
input.UTXOEntry = utxo.NewUTXOEntry(output.Value, output.ScriptPublicKey, false,
|
||||||
model.UnacceptedDAAScore)
|
constants.UnacceptedDAAScore)
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,16 +42,16 @@ const (
|
|||||||
defaultMaxRPCClients = 10
|
defaultMaxRPCClients = 10
|
||||||
defaultMaxRPCWebsockets = 25
|
defaultMaxRPCWebsockets = 25
|
||||||
defaultMaxRPCConcurrentReqs = 20
|
defaultMaxRPCConcurrentReqs = 20
|
||||||
defaultBlockMaxMass = 10000000
|
defaultBlockMaxMass = 10_000_000
|
||||||
blockMaxMassMin = 1000
|
blockMaxMassMin = 1000
|
||||||
blockMaxMassMax = 10000000
|
blockMaxMassMax = 10_000_000
|
||||||
defaultMinRelayTxFee = 1e-5 // 1 sompi per byte
|
defaultMinRelayTxFee = 1e-5 // 1 sompi per byte
|
||||||
defaultMaxOrphanTransactions = 100
|
defaultMaxOrphanTransactions = 100
|
||||||
//DefaultMaxOrphanTxSize is the default maximum size for an orphan transaction
|
//DefaultMaxOrphanTxSize is the default maximum size for an orphan transaction
|
||||||
DefaultMaxOrphanTxSize = 100000
|
DefaultMaxOrphanTxSize = 100_000
|
||||||
defaultSigCacheMaxSize = 100000
|
defaultSigCacheMaxSize = 100_000
|
||||||
sampleConfigFilename = "sample-kaspad.conf"
|
sampleConfigFilename = "sample-kaspad.conf"
|
||||||
defaultMaxUTXOCacheSize = 5000000000
|
defaultMaxUTXOCacheSize = 5_000_000_000
|
||||||
defaultProtocolVersion = 5
|
defaultProtocolVersion = 5
|
||||||
)
|
)
|
||||||
|
|
||||||
|
119
util/txmass/calculator.go
Normal file
119
util/txmass/calculator.go
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
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
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user