mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-11-24 14:35:53 +00:00
Add fee estimation to wallet
This commit is contained in:
parent
48a142e12f
commit
78ca616b1f
@ -163,6 +163,8 @@ const (
|
|||||||
CmdGetMempoolEntriesByAddressesResponseMessage
|
CmdGetMempoolEntriesByAddressesResponseMessage
|
||||||
CmdGetCoinSupplyRequestMessage
|
CmdGetCoinSupplyRequestMessage
|
||||||
CmdGetCoinSupplyResponseMessage
|
CmdGetCoinSupplyResponseMessage
|
||||||
|
CmdGetFeeEstimateRequestMessage
|
||||||
|
CmdGetFeeEstimateResponseMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
||||||
@ -300,6 +302,8 @@ var RPCMessageCommandToString = map[MessageCommand]string{
|
|||||||
CmdGetMempoolEntriesByAddressesResponseMessage: "GetMempoolEntriesByAddressesResponse",
|
CmdGetMempoolEntriesByAddressesResponseMessage: "GetMempoolEntriesByAddressesResponse",
|
||||||
CmdGetCoinSupplyRequestMessage: "GetCoinSupplyRequest",
|
CmdGetCoinSupplyRequestMessage: "GetCoinSupplyRequest",
|
||||||
CmdGetCoinSupplyResponseMessage: "GetCoinSupplyResponse",
|
CmdGetCoinSupplyResponseMessage: "GetCoinSupplyResponse",
|
||||||
|
CmdGetFeeEstimateRequestMessage: "GetFeeEstimateRequest",
|
||||||
|
CmdGetFeeEstimateResponseMessage: "GetFeeEstimateResponse",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message is an interface that describes a kaspa message. A type that
|
// Message is an interface that describes a kaspa message. A type that
|
||||||
|
|||||||
47
app/appmessage/rpc_fee_estimate.go
Normal file
47
app/appmessage/rpc_fee_estimate.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package appmessage
|
||||||
|
|
||||||
|
// GetFeeEstimateRequestMessage is an appmessage corresponding to
|
||||||
|
// its respective RPC message
|
||||||
|
type GetFeeEstimateRequestMessage struct {
|
||||||
|
baseMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message
|
||||||
|
func (msg *GetFeeEstimateRequestMessage) Command() MessageCommand {
|
||||||
|
return CmdGetFeeEstimateRequestMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetFeeEstimateRequestMessage returns a instance of the message
|
||||||
|
func NewGetFeeEstimateRequestMessage() *GetFeeEstimateRequestMessage {
|
||||||
|
return &GetFeeEstimateRequestMessage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCFeeRateBucket struct {
|
||||||
|
Feerate float64
|
||||||
|
EstimatedSeconds float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCFeeEstimate struct {
|
||||||
|
PriorityBucket RPCFeeRateBucket
|
||||||
|
NormalBuckets []RPCFeeRateBucket
|
||||||
|
LowBuckets []RPCFeeRateBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCoinSupplyResponseMessage is an appmessage corresponding to
|
||||||
|
// its respective RPC message
|
||||||
|
type GetFeeEstimateResponseMessage struct {
|
||||||
|
baseMessage
|
||||||
|
Estimate RPCFeeEstimate
|
||||||
|
|
||||||
|
Error *RPCError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message
|
||||||
|
func (msg *GetFeeEstimateResponseMessage) Command() MessageCommand {
|
||||||
|
return CmdGetFeeEstimateResponseMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetFeeEstimateResponseMessage returns a instance of the message
|
||||||
|
func NewGetFeeEstimateResponseMessage() *GetFeeEstimateResponseMessage {
|
||||||
|
return &GetFeeEstimateResponseMessage{}
|
||||||
|
}
|
||||||
@ -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"
|
||||||
)
|
)
|
||||||
@ -62,6 +63,8 @@ type sendConfig struct {
|
|||||||
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||||
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount). If --from-address was used, will send all only from the specified addresses."`
|
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount). If --from-address was used, will send all only from the specified addresses."`
|
||||||
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
||||||
|
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the maximum between the fee estimate from the connected node and this value."`
|
||||||
|
FeeRate float64 `long:"fee-rate" short:"r" description:"Fee rate in Sompi/gram to use for the transaction. This option will override any fee estimate from the connected node."`
|
||||||
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
|
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
|
||||||
config.NetworkFlags
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
@ -79,6 +82,8 @@ type createUnsignedTransactionConfig struct {
|
|||||||
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||||
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
||||||
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
||||||
|
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the maximum between the fee estimate from the connected node and this value."`
|
||||||
|
FeeRate float64 `long:"fee-rate" short:"r" description:"Fee rate in Sompi/gram to use for the transaction. This option will override any fee estimate from the connected node."`
|
||||||
config.NetworkFlags
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +321,19 @@ func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig
|
|||||||
|
|
||||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.MaxFeeRate < 0 {
|
||||||
|
return errors.New("--max-fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.FeeRate < 0 {
|
||||||
|
return errors.New("--fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.MaxFeeRate > 0 && conf.FeeRate > 0 {
|
||||||
|
return errors.New("at most one of '--max-fee-rate' or '--fee-rate' can be specified")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -325,6 +343,19 @@ func validateSendConfig(conf *sendConfig) error {
|
|||||||
|
|
||||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.MaxFeeRate < 0 {
|
||||||
|
return errors.New("--max-fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.FeeRate < 0 {
|
||||||
|
return errors.New("--fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.MaxFeeRate > 0 && conf.FeeRate > 0 {
|
||||||
|
return errors.New("at most one of '--max-fee-rate' or '--fee-rate' can be specified")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
@ -26,12 +27,22 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feeRate := &pb.FeeRate{
|
||||||
|
FeeRate: &pb.FeeRate_Max{Max: math.MaxFloat64},
|
||||||
|
}
|
||||||
|
if conf.FeeRate > 0 {
|
||||||
|
feeRate.FeeRate = &pb.FeeRate_Exact{Exact: conf.FeeRate}
|
||||||
|
} else if conf.MaxFeeRate > 0 {
|
||||||
|
feeRate.FeeRate = &pb.FeeRate_Max{Max: conf.MaxFeeRate}
|
||||||
|
}
|
||||||
|
|
||||||
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||||
From: conf.FromAddresses,
|
From: conf.FromAddresses,
|
||||||
Address: conf.ToAddress,
|
Address: conf.ToAddress,
|
||||||
Amount: sendAmountSompi,
|
Amount: sendAmountSompi,
|
||||||
IsSendAll: conf.IsSendAll,
|
IsSendAll: conf.IsSendAll,
|
||||||
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
||||||
|
FeeRate: feeRate,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -4,22 +4,25 @@ option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb";
|
|||||||
package kaspawalletd;
|
package kaspawalletd;
|
||||||
|
|
||||||
service kaspawalletd {
|
service kaspawalletd {
|
||||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
|
rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse) {}
|
||||||
rpc GetExternalSpendableUTXOs (GetExternalSpendableUTXOsRequest) returns (GetExternalSpendableUTXOsResponse) {}
|
rpc GetExternalSpendableUTXOs(GetExternalSpendableUTXOsRequest)
|
||||||
rpc CreateUnsignedTransactions (CreateUnsignedTransactionsRequest) returns (CreateUnsignedTransactionsResponse) {}
|
returns (GetExternalSpendableUTXOsResponse) {}
|
||||||
rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {}
|
rpc CreateUnsignedTransactions(CreateUnsignedTransactionsRequest)
|
||||||
rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
|
returns (CreateUnsignedTransactionsResponse) {}
|
||||||
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
|
rpc ShowAddresses(ShowAddressesRequest) returns (ShowAddressesResponse) {}
|
||||||
rpc Broadcast (BroadcastRequest) returns (BroadcastResponse) {}
|
rpc NewAddress(NewAddressRequest) returns (NewAddressResponse) {}
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
rpc Shutdown(ShutdownRequest) returns (ShutdownResponse) {}
|
||||||
|
rpc Broadcast(BroadcastRequest) returns (BroadcastResponse) {}
|
||||||
|
// Since SendRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
rpc Send(SendRequest) returns (SendResponse) {}
|
rpc Send(SendRequest) returns (SendResponse) {}
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
rpc Sign(SignRequest) returns (SignResponse) {}
|
rpc Sign(SignRequest) returns (SignResponse) {}
|
||||||
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
|
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetBalanceRequest {
|
message GetBalanceRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetBalanceResponse {
|
message GetBalanceResponse {
|
||||||
uint64 available = 1;
|
uint64 available = 1;
|
||||||
@ -33,46 +36,44 @@ message AddressBalances {
|
|||||||
uint64 pending = 3;
|
uint64 pending = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FeeRate {
|
||||||
|
oneof feeRate {
|
||||||
|
double max = 6;
|
||||||
|
double exact = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message CreateUnsignedTransactionsRequest {
|
message CreateUnsignedTransactionsRequest {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
uint64 amount = 2;
|
uint64 amount = 2;
|
||||||
repeated string from = 3;
|
repeated string from = 3;
|
||||||
bool useExistingChangeAddress = 4;
|
bool useExistingChangeAddress = 4;
|
||||||
bool isSendAll = 5;
|
bool isSendAll = 5;
|
||||||
|
FeeRate feeRate = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateUnsignedTransactionsResponse {
|
message CreateUnsignedTransactionsResponse {
|
||||||
repeated bytes unsignedTransactions = 1;
|
repeated bytes unsignedTransactions = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ShowAddressesRequest {
|
message ShowAddressesRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message ShowAddressesResponse {
|
message ShowAddressesResponse { repeated string address = 1; }
|
||||||
repeated string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NewAddressRequest {
|
message NewAddressRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message NewAddressResponse {
|
message NewAddressResponse { string address = 1; }
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BroadcastRequest {
|
message BroadcastRequest {
|
||||||
bool isDomain = 1;
|
bool isDomain = 1;
|
||||||
repeated bytes transactions = 2;
|
repeated bytes transactions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BroadcastResponse {
|
message BroadcastResponse { repeated string txIDs = 1; }
|
||||||
repeated string txIDs = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ShutdownRequest {
|
message ShutdownRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message ShutdownResponse {
|
message ShutdownResponse {}
|
||||||
}
|
|
||||||
|
|
||||||
message Outpoint {
|
message Outpoint {
|
||||||
string transactionId = 1;
|
string transactionId = 1;
|
||||||
@ -97,41 +98,37 @@ message UtxoEntry {
|
|||||||
bool isCoinbase = 4;
|
bool isCoinbase = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetExternalSpendableUTXOsRequest{
|
message GetExternalSpendableUTXOsRequest { string address = 1; }
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetExternalSpendableUTXOsResponse{
|
message GetExternalSpendableUTXOsResponse {
|
||||||
repeated UtxosByAddressesEntry Entries = 1;
|
repeated UtxosByAddressesEntry Entries = 1;
|
||||||
}
|
}
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SendRequest contains a password - this command should only be used on a
|
||||||
message SendRequest{
|
// trusted or secure connection
|
||||||
|
message SendRequest {
|
||||||
string toAddress = 1;
|
string toAddress = 1;
|
||||||
uint64 amount = 2;
|
uint64 amount = 2;
|
||||||
string password = 3;
|
string password = 3;
|
||||||
repeated string from = 4;
|
repeated string from = 4;
|
||||||
bool useExistingChangeAddress = 5;
|
bool useExistingChangeAddress = 5;
|
||||||
bool isSendAll = 6;
|
bool isSendAll = 6;
|
||||||
|
FeeRate feeRate = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SendResponse{
|
message SendResponse {
|
||||||
repeated string txIDs = 1;
|
repeated string txIDs = 1;
|
||||||
repeated bytes signedTransactions = 2;
|
repeated bytes signedTransactions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on a
|
||||||
message SignRequest{
|
// trusted or secure connection
|
||||||
|
message SignRequest {
|
||||||
repeated bytes unsignedTransactions = 1;
|
repeated bytes unsignedTransactions = 1;
|
||||||
string password = 2;
|
string password = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SignResponse{
|
message SignResponse { repeated bytes signedTransactions = 1; }
|
||||||
repeated bytes signedTransactions = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetVersionRequest{
|
message GetVersionRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetVersionResponse{
|
message GetVersionResponse { string version = 1; }
|
||||||
string version = 1;
|
|
||||||
}
|
|
||||||
@ -29,9 +29,11 @@ type KaspawalletdClient interface {
|
|||||||
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)
|
||||||
Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
|
Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SendRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error)
|
Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error)
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
|
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
|
||||||
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
|
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
|
||||||
}
|
}
|
||||||
@ -145,9 +147,11 @@ type KaspawalletdServer interface {
|
|||||||
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
|
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
|
||||||
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
||||||
Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
|
Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SendRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
Send(context.Context, *SendRequest) (*SendResponse, error)
|
Send(context.Context, *SendRequest) (*SendResponse, error)
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
Sign(context.Context, *SignRequest) (*SignResponse, error)
|
Sign(context.Context, *SignRequest) (*SignResponse, error)
|
||||||
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
|
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
|
||||||
mustEmbedUnimplementedKaspawalletdServer()
|
mustEmbedUnimplementedKaspawalletdServer()
|
||||||
|
|||||||
@ -3,17 +3,17 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||||
"github.com/kaspanet/kaspad/util"
|
"github.com/kaspanet/kaspad/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Implement a better fee estimation mechanism
|
|
||||||
const feePerInput = 10000
|
|
||||||
|
|
||||||
// The minimal change amount to target in order to avoid large storage mass (see KIP9 for more details).
|
// The minimal change amount to target in order to avoid large storage mass (see KIP9 for more details).
|
||||||
// By having at least 0.2KAS in the change output we make sure that every transaction with send value >= 0.2KAS
|
// By having at least 0.2KAS in the change output we make sure that every transaction with send value >= 0.2KAS
|
||||||
// should succeed (at most 50K storage mass for each output, thus overall lower than standard mass upper bound which is 100K gram)
|
// should succeed (at most 50K storage mass for each output, thus overall lower than standard mass upper bound which is 100K gram)
|
||||||
@ -26,7 +26,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
|
|||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.IsSendAll,
|
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.IsSendAll,
|
||||||
request.From, request.UseExistingChangeAddress)
|
request.From, request.UseExistingChangeAddress, request.FeeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -34,10 +34,23 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
|
|||||||
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool) ([][]byte, error) {
|
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool, feeRateOneOf *pb.FeeRate) ([][]byte, error) {
|
||||||
if !s.isSynced() {
|
if !s.isSynced() {
|
||||||
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var feeRate float64
|
||||||
|
switch requestFeeRate := feeRateOneOf.FeeRate.(type) {
|
||||||
|
case *pb.FeeRate_Exact:
|
||||||
|
feeRate = requestFeeRate.Exact
|
||||||
|
case *pb.FeeRate_Max:
|
||||||
|
estimate, err := s.rpcClient.GetFeeEstimate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
feeRate = math.Min(estimate.Estimate.NormalBuckets[0].Feerate, requestFeeRate.Max)
|
||||||
|
}
|
||||||
|
|
||||||
// make sure address string is correct before proceeding to a
|
// make sure address string is correct before proceeding to a
|
||||||
// potentially long UTXO refreshment operation
|
// potentially long UTXO refreshment operation
|
||||||
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
|
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
|
||||||
@ -54,7 +67,12 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
fromAddresses = append(fromAddresses, fromAddress)
|
fromAddresses = append(fromAddresses, fromAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feePerInput, fromAddresses)
|
changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress, fromAddresses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feeRate, fromAddresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -63,11 +81,6 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, errors.Errorf("couldn't find funds to spend")
|
return nil, errors.Errorf("couldn't find funds to spend")
|
||||||
}
|
}
|
||||||
|
|
||||||
changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress, fromAddresses)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payments := []*libkaspawallet.Payment{{
|
payments := []*libkaspawallet.Payment{{
|
||||||
Address: toAddress,
|
Address: toAddress,
|
||||||
Amount: spendValue,
|
Amount: spendValue,
|
||||||
@ -85,14 +98,14 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress)
|
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return unsignedTransactions, nil
|
return unsignedTransactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uint64, fromAddresses []*walletAddress) (
|
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feeRate float64, fromAddresses []*walletAddress) (
|
||||||
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
|
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
|
||||||
|
|
||||||
selectedUTXOs = []*libkaspawallet.UTXO{}
|
selectedUTXOs = []*libkaspawallet.UTXO{}
|
||||||
@ -103,6 +116,7 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
return nil, 0, 0, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fee uint64
|
||||||
for _, utxo := range s.utxosSortedByAmount {
|
for _, utxo := range s.utxosSortedByAmount {
|
||||||
if (fromAddresses != nil && !walletAddressesContain(fromAddresses, utxo.address)) ||
|
if (fromAddresses != nil && !walletAddressesContain(fromAddresses, utxo.address)) ||
|
||||||
!s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
|
!s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
|
||||||
@ -125,7 +139,11 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
|
|
||||||
totalValue += utxo.UTXOEntry.Amount()
|
totalValue += utxo.UTXOEntry.Amount()
|
||||||
|
|
||||||
fee := feePerInput * uint64(len(selectedUTXOs))
|
fee, err = s.estimateFee(selectedUTXOs, feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
totalSpend := spendAmount + fee
|
totalSpend := spendAmount + fee
|
||||||
// Two break cases (if not send all):
|
// Two break cases (if not send all):
|
||||||
// 1. totalValue == totalSpend, so there's no change needed -> number of outputs = 1, so a single input is sufficient
|
// 1. totalValue == totalSpend, so there's no change needed -> number of outputs = 1, so a single input is sufficient
|
||||||
@ -137,7 +155,6 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fee := feePerInput * uint64(len(selectedUTXOs))
|
|
||||||
var totalSpend uint64
|
var totalSpend uint64
|
||||||
if isSendAll {
|
if isSendAll {
|
||||||
totalSpend = totalValue
|
totalSpend = totalValue
|
||||||
@ -154,6 +171,80 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float64) (uint64, error) {
|
||||||
|
fakePubKey := [util.PublicKeySize]byte{}
|
||||||
|
fakeAddr, err := util.NewAddressPublicKey(fakePubKey[:], s.params.Prefix)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mockPayments := []*libkaspawallet.Payment{
|
||||||
|
{
|
||||||
|
Address: fakeAddr,
|
||||||
|
Amount: 1,
|
||||||
|
},
|
||||||
|
{ // We're overestimating a bit by assuming that any transaction will have a change output
|
||||||
|
Address: fakeAddr,
|
||||||
|
Amount: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
mockTx, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||||
|
s.keysFile.MinimumSignatures,
|
||||||
|
mockPayments, selectedUTXOs)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mass, err := s.estimateMassAfterSignatures(mockTx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return uint64(float64(mass) * feeRate), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) estimateFeePerInput(feeRate float64) (uint64, error) {
|
||||||
|
mockUTXO := &libkaspawallet.UTXO{
|
||||||
|
Outpoint: &externalapi.DomainOutpoint{
|
||||||
|
TransactionID: externalapi.DomainTransactionID{},
|
||||||
|
Index: 0,
|
||||||
|
},
|
||||||
|
UTXOEntry: utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{
|
||||||
|
Script: nil,
|
||||||
|
Version: 0,
|
||||||
|
}, false, 0),
|
||||||
|
DerivationPath: "m",
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTx, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||||
|
s.keysFile.MinimumSignatures,
|
||||||
|
nil, []*libkaspawallet.UTXO{mockUTXO})
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mass, err := s.estimateMassAfterSignatures(mockTx)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
mockTxWithoutUTXO, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||||
|
s.keysFile.MinimumSignatures,
|
||||||
|
nil, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
massWithoutUTXO, err := s.estimateMassAfterSignatures(mockTxWithoutUTXO)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
inputMass := mass - massWithoutUTXO
|
||||||
|
|
||||||
|
return uint64(float64(inputMass) * feeRate), nil
|
||||||
|
}
|
||||||
|
|
||||||
func walletAddressesContain(addresses []*walletAddress, contain *walletAddress) bool {
|
func walletAddressesContain(addresses []*walletAddress, contain *walletAddress) bool {
|
||||||
for _, address := range addresses {
|
for _, address := range addresses {
|
||||||
if *address == *contain {
|
if *address == *contain {
|
||||||
|
|||||||
@ -21,7 +21,15 @@ func (s *server) GetExternalSpendableUTXOs(_ context.Context, request *pb.GetExt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
selectedUTXOs, err := s.selectExternalSpendableUTXOs(externalUTXOs, request.Address)
|
|
||||||
|
estimate, err := s.rpcClient.GetFeeEstimate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
feeRate := estimate.Estimate.NormalBuckets[0].Feerate
|
||||||
|
|
||||||
|
selectedUTXOs, err := s.selectExternalSpendableUTXOs(externalUTXOs, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -30,7 +38,7 @@ func (s *server) GetExternalSpendableUTXOs(_ context.Context, request *pb.GetExt
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOsByAddressesResponseMessage, address string) ([]*pb.UtxosByAddressesEntry, error) {
|
func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOsByAddressesResponseMessage, feeRate float64) ([]*pb.UtxosByAddressesEntry, error) {
|
||||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -42,8 +50,13 @@ func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOs
|
|||||||
//we do not make because we do not know size, because of unspendable utxos
|
//we do not make because we do not know size, because of unspendable utxos
|
||||||
var selectedExternalUtxos []*pb.UtxosByAddressesEntry
|
var selectedExternalUtxos []*pb.UtxosByAddressesEntry
|
||||||
|
|
||||||
|
feePerInput, err := s.estimateFeePerInput(feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, entry := range externalUTXOs.Entries {
|
for _, entry := range externalUTXOs.Entries {
|
||||||
if !isExternalUTXOSpendable(entry, daaScore, maturity) {
|
if !isExternalUTXOSpendable(entry, daaScore, maturity, feePerInput) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
selectedExternalUtxos = append(selectedExternalUtxos, libkaspawallet.AppMessageUTXOToKaspawalletdUTXO(entry))
|
selectedExternalUtxos = append(selectedExternalUtxos, libkaspawallet.AppMessageUTXOToKaspawalletdUTXO(entry))
|
||||||
@ -52,7 +65,7 @@ func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOs
|
|||||||
return selectedExternalUtxos, nil
|
return selectedExternalUtxos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isExternalUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
|
func isExternalUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64, feePerInput uint64) bool {
|
||||||
if !entry.UTXOEntry.IsCoinbase {
|
if !entry.UTXOEntry.IsCoinbase {
|
||||||
return true
|
return true
|
||||||
} else if entry.UTXOEntry.Amount <= feePerInput {
|
} else if entry.UTXOEntry.Amount <= feePerInput {
|
||||||
|
|||||||
@ -11,7 +11,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
|
|||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.IsSendAll,
|
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.IsSendAll,
|
||||||
request.From, request.UseExistingChangeAddress)
|
request.From, request.UseExistingChangeAddress, request.FeeRate)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@ -20,14 +20,9 @@ import (
|
|||||||
// into a change address.
|
// into a change address.
|
||||||
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
|
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
|
||||||
// paying to the original transaction's payee.
|
// paying to the original transaction's payee.
|
||||||
func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte, toAddress util.Address,
|
func (s *server) maybeAutoCompoundTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
||||||
changeAddress util.Address, changeWalletAddress *walletAddress) ([][]byte, error) {
|
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64) ([][]byte, error) {
|
||||||
transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes)
|
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -47,6 +42,7 @@ func (s *server) mergeTransaction(
|
|||||||
toAddress util.Address,
|
toAddress util.Address,
|
||||||
changeAddress util.Address,
|
changeAddress util.Address,
|
||||||
changeWalletAddress *walletAddress,
|
changeWalletAddress *walletAddress,
|
||||||
|
feeRate float64,
|
||||||
) (*serialization.PartiallySignedTransaction, error) {
|
) (*serialization.PartiallySignedTransaction, error) {
|
||||||
numOutputs := len(originalTransaction.Tx.Outputs)
|
numOutputs := len(originalTransaction.Tx.Outputs)
|
||||||
if numOutputs > 2 || numOutputs == 0 {
|
if numOutputs > 2 || numOutputs == 0 {
|
||||||
@ -71,13 +67,18 @@ func (s *server) mergeTransaction(
|
|||||||
DerivationPath: s.walletAddressPath(changeWalletAddress),
|
DerivationPath: s.walletAddressPath(changeWalletAddress),
|
||||||
}
|
}
|
||||||
totalValue += output.Value
|
totalValue += output.Value
|
||||||
totalValue -= feePerInput
|
|
||||||
}
|
}
|
||||||
|
fee, err := s.estimateFee(utxos, feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
totalValue -= fee
|
||||||
|
|
||||||
if totalValue < sentValue {
|
if totalValue < sentValue {
|
||||||
// sometimes the fees from compound transactions make the total output higher than what's available from selected
|
// sometimes the fees from compound transactions make the total output higher than what's available from selected
|
||||||
// utxos, in such cases - find one more UTXO and use it.
|
// utxos, in such cases - find one more UTXO and use it.
|
||||||
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue)
|
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -96,17 +97,12 @@ func (s *server) mergeTransaction(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
return libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||||
s.keysFile.MinimumSignatures, payments, utxos)
|
s.keysFile.MinimumSignatures, payments, utxos)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
||||||
changeAddress util.Address, changeWalletAddress *walletAddress) ([]*serialization.PartiallySignedTransaction, error) {
|
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64) ([]*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
transactionMass, err := s.estimateMassAfterSignatures(transaction)
|
transactionMass, err := s.estimateMassAfterSignatures(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -117,7 +113,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress)
|
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -127,19 +123,19 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
startIndex := i * inputCountPerSplit
|
startIndex := i * inputCountPerSplit
|
||||||
endIndex := startIndex + inputCountPerSplit
|
endIndex := startIndex + inputCountPerSplit
|
||||||
var err error
|
var err error
|
||||||
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex)
|
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(splitTransactions) > 1 {
|
if len(splitTransactions) > 1 {
|
||||||
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
|
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
|
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
|
||||||
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress)
|
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -152,7 +148,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
|
|
||||||
// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
|
// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
|
||||||
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
|
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
|
||||||
changeAddress util.Address) (splitCount, inputsPerSplitCount int, err error) {
|
changeAddress util.Address, feeRate float64) (splitCount, inputsPerSplitCount int, err error) {
|
||||||
|
|
||||||
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
|
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
|
||||||
// to calculate how much mass do all the inputs have
|
// to calculate how much mass do all the inputs have
|
||||||
@ -172,7 +168,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
|
|||||||
|
|
||||||
// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
|
// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
|
||||||
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
|
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
|
||||||
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0)
|
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
@ -190,7 +186,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
||||||
changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) {
|
changeAddress util.Address, startIndex int, endIndex int, feeRate float64) (*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
||||||
totalSompi := uint64(0)
|
totalSompi := uint64(0)
|
||||||
@ -206,19 +202,19 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign
|
|||||||
})
|
})
|
||||||
|
|
||||||
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
|
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
|
||||||
totalSompi -= feePerInput
|
|
||||||
}
|
}
|
||||||
unsignedTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
fee, err := s.estimateFee(selectedUTXOs, feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
totalSompi -= fee
|
||||||
|
return libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
||||||
s.keysFile.MinimumSignatures,
|
s.keysFile.MinimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: changeAddress,
|
Address: changeAddress,
|
||||||
Amount: totalSompi,
|
Amount: totalSompi,
|
||||||
}}, selectedUTXOs)
|
}}, selectedUTXOs)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
|
func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
|
||||||
@ -248,7 +244,7 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall
|
|||||||
return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil
|
return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (
|
func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64, feeRate float64) (
|
||||||
additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) {
|
additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) {
|
||||||
|
|
||||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||||
@ -260,6 +256,11 @@ func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawa
|
|||||||
alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
|
alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feePerInput, err := s.estimateFeePerInput(feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, utxo := range s.utxosSortedByAmount {
|
for _, utxo := range s.utxosSortedByAmount {
|
||||||
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
|
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
|
||||||
continue
|
continue
|
||||||
|
|||||||
@ -22,7 +22,7 @@ import (
|
|||||||
|
|
||||||
func TestEstimateMassAfterSignatures(t *testing.T) {
|
func TestEstimateMassAfterSignatures(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
unsignedTransactionBytes, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
|
unsignedTransaction, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
|
||||||
defer teardown(false)
|
defer teardown(false)
|
||||||
|
|
||||||
serverInstance := &server{
|
serverInstance := &server{
|
||||||
@ -33,16 +33,16 @@ func TestEstimateMassAfterSignatures(t *testing.T) {
|
|||||||
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
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)
|
estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error from estimateMassAfterSignatures: %s", err)
|
t.Fatalf("Error from estimateMassAfterSignatures: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unsignedTransactionBytes, err := serialization.SerializePartiallySignedTransaction(unsignedTransaction)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error deserializing unsignedTransaction: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false)
|
signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Sign: %+v", err)
|
t.Fatalf("Sign: %+v", err)
|
||||||
@ -68,7 +68,7 @@ func TestEstimateMassAfterSignatures(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) (
|
func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) (
|
||||||
[]byte, []string, *dagconfig.Params, func(keepDataDir bool)) {
|
*serialization.PartiallySignedTransaction, []string, *dagconfig.Params, func(keepDataDir bool)) {
|
||||||
|
|
||||||
consensusConfig.BlockCoinbaseMaturity = 0
|
consensusConfig.BlockCoinbaseMaturity = 0
|
||||||
params := &consensusConfig.Params
|
params := &consensusConfig.Params
|
||||||
|
|||||||
@ -31,15 +31,10 @@ func CreateUnsignedTransaction(
|
|||||||
extendedPublicKeys []string,
|
extendedPublicKeys []string,
|
||||||
minimumSignatures uint32,
|
minimumSignatures uint32,
|
||||||
payments []*Payment,
|
payments []*Payment,
|
||||||
selectedUTXOs []*UTXO) ([]byte, error) {
|
selectedUTXOs []*UTXO) (*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
sortPublicKeys(extendedPublicKeys)
|
sortPublicKeys(extendedPublicKeys)
|
||||||
unsignedTransaction, err := createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
return createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiSigRedeemScript(extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) ([]byte, error) {
|
func multiSigRedeemScript(extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) ([]byte, error) {
|
||||||
|
|||||||
@ -2,6 +2,7 @@ package libkaspawallet_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -26,6 +27,20 @@ func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUnsignedTransactionSerialized(
|
||||||
|
extendedPublicKeys []string,
|
||||||
|
minimumSignatures uint32,
|
||||||
|
payments []*libkaspawallet.Payment,
|
||||||
|
selectedUTXOs []*libkaspawallet.UTXO) ([]byte, error) {
|
||||||
|
|
||||||
|
tx, err := libkaspawallet.CreateUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialization.SerializePartiallySignedTransaction(tx)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMultisig(t *testing.T) {
|
func TestMultisig(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
params := &consensusConfig.Params
|
params := &consensusConfig.Params
|
||||||
@ -102,7 +117,7 @@ func TestMultisig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTransaction, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
@ -263,7 +278,7 @@ func TestP2PK(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTransaction, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
@ -425,7 +440,7 @@ func TestMaxSompi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTxWithLargeInputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTxWithLargeInputAmount, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
@ -476,7 +491,7 @@ func TestMaxSompi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTxWithLargeInputAndOutputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTxWithLargeInputAndOutputAmount, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 22e6 * constants.SompiPerKaspa,
|
Amount: 22e6 * constants.SompiPerKaspa,
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -42,6 +43,15 @@ func send(conf *sendConfig) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feeRate := &pb.FeeRate{
|
||||||
|
FeeRate: &pb.FeeRate_Max{Max: math.MaxFloat64},
|
||||||
|
}
|
||||||
|
if conf.FeeRate > 0 {
|
||||||
|
feeRate.FeeRate = &pb.FeeRate_Exact{Exact: conf.FeeRate}
|
||||||
|
} else if conf.MaxFeeRate > 0 {
|
||||||
|
feeRate.FeeRate = &pb.FeeRate_Max{Max: conf.MaxFeeRate}
|
||||||
|
}
|
||||||
|
|
||||||
createUnsignedTransactionsResponse, err :=
|
createUnsignedTransactionsResponse, err :=
|
||||||
daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||||
From: conf.FromAddresses,
|
From: conf.FromAddresses,
|
||||||
@ -49,6 +59,7 @@ func send(conf *sendConfig) error {
|
|||||||
Amount: sendAmountSompi,
|
Amount: sendAmountSompi,
|
||||||
IsSendAll: conf.IsSendAll,
|
IsSendAll: conf.IsSendAll,
|
||||||
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
||||||
|
FeeRate: feeRate,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -139,6 +139,28 @@ message KaspadMessage {
|
|||||||
GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085;
|
GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085;
|
||||||
GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086;
|
GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086;
|
||||||
GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087;
|
GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087;
|
||||||
|
PingRequestMessage pingRequest = 1088;
|
||||||
|
GetMetricsRequestMessage getMetricsRequest = 1090;
|
||||||
|
GetServerInfoRequestMessage getServerInfoRequest = 1092;
|
||||||
|
GetSyncStatusRequestMessage getSyncStatusRequest = 1094;
|
||||||
|
GetDaaScoreTimestampEstimateRequestMessage getDaaScoreTimestampEstimateRequest = 1096;
|
||||||
|
SubmitTransactionReplacementRequestMessage submitTransactionReplacementRequest = 1100;
|
||||||
|
GetConnectionsRequestMessage getConnectionsRequest = 1102;
|
||||||
|
GetSystemInfoRequestMessage getSystemInfoRequest = 1104;
|
||||||
|
GetFeeEstimateRequestMessage getFeeEstimateRequest = 1106;
|
||||||
|
GetFeeEstimateExperimentalRequestMessage getFeeEstimateExperimentalRequest = 1108;
|
||||||
|
GetCurrentBlockColorRequestMessage getCurrentBlockColorRequest = 1110;
|
||||||
|
PingResponseMessage pingResponse= 1089;
|
||||||
|
GetMetricsResponseMessage getMetricsResponse= 1091;
|
||||||
|
GetServerInfoResponseMessage getServerInfoResponse = 1093;
|
||||||
|
GetSyncStatusResponseMessage getSyncStatusResponse = 1095;
|
||||||
|
GetDaaScoreTimestampEstimateResponseMessage getDaaScoreTimestampEstimateResponse = 1097;
|
||||||
|
SubmitTransactionReplacementResponseMessage submitTransactionReplacementResponse = 1101;
|
||||||
|
GetConnectionsResponseMessage getConnectionsResponse= 1103;
|
||||||
|
GetSystemInfoResponseMessage getSystemInfoResponse= 1105;
|
||||||
|
GetFeeEstimateResponseMessage getFeeEstimateResponse = 1107;
|
||||||
|
GetFeeEstimateExperimentalResponseMessage getFeeEstimateExperimentalResponse = 1109;
|
||||||
|
GetCurrentBlockColorResponseMessage getCurrentBlockColorResponse = 1111;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.2.0
|
// - protoc-gen-go-grpc v1.2.0
|
||||||
// - protoc v3.17.2
|
// - protoc v3.12.3
|
||||||
// source: messages.proto
|
// source: messages.proto
|
||||||
|
|
||||||
package protowire
|
package protowire
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// protoc-gen-go v1.26.0
|
// protoc-gen-go v1.27.1
|
||||||
// protoc v3.17.2
|
// protoc v3.12.3
|
||||||
// source: p2p.proto
|
// source: p2p.proto
|
||||||
|
|
||||||
package protowire
|
package protowire
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@ -721,3 +721,221 @@ message GetCoinSupplyResponseMessage{
|
|||||||
|
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message PingRequestMessage{
|
||||||
|
}
|
||||||
|
|
||||||
|
message PingResponseMessage { RPCError error = 1000; }
|
||||||
|
|
||||||
|
message ProcessMetrics {
|
||||||
|
uint64 residentSetSize = 1;
|
||||||
|
uint64 virtualMemorySize = 2;
|
||||||
|
uint32 coreNum = 3;
|
||||||
|
float cpuUsage = 4;
|
||||||
|
uint32 fdNum = 5;
|
||||||
|
uint64 diskIoReadBytes = 6;
|
||||||
|
uint64 diskIoWriteBytes = 7;
|
||||||
|
float diskIoReadPerSec = 8;
|
||||||
|
float diskIoWritePerSec = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConnectionMetrics {
|
||||||
|
uint32 borshLiveConnections = 31;
|
||||||
|
uint64 borshConnectionAttempts = 32;
|
||||||
|
uint64 borshHandshakeFailures = 33;
|
||||||
|
|
||||||
|
uint32 jsonLiveConnections = 41;
|
||||||
|
uint64 jsonConnectionAttempts = 42;
|
||||||
|
uint64 jsonHandshakeFailures = 43;
|
||||||
|
|
||||||
|
uint32 activePeers = 51;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BandwidthMetrics {
|
||||||
|
uint64 borshBytesTx = 61;
|
||||||
|
uint64 borshBytesRx = 62;
|
||||||
|
uint64 jsonBytesTx = 63;
|
||||||
|
uint64 jsonBytesRx = 64;
|
||||||
|
uint64 grpcP2pBytesTx = 65;
|
||||||
|
uint64 grpcP2pBytesRx = 66;
|
||||||
|
uint64 grpcUserBytesTx = 67;
|
||||||
|
uint64 grpcUserBytesRx = 68;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ConsensusMetrics {
|
||||||
|
uint64 blocksSubmitted = 1;
|
||||||
|
uint64 headerCounts = 2;
|
||||||
|
uint64 depCounts = 3;
|
||||||
|
uint64 bodyCounts = 4;
|
||||||
|
uint64 txsCounts = 5;
|
||||||
|
uint64 chainBlockCounts = 6;
|
||||||
|
uint64 massCounts = 7;
|
||||||
|
|
||||||
|
uint64 blockCount = 11;
|
||||||
|
uint64 headerCount = 12;
|
||||||
|
uint64 mempoolSize = 13;
|
||||||
|
uint32 tipHashesCount = 14;
|
||||||
|
double difficulty = 15;
|
||||||
|
uint64 pastMedianTime = 16;
|
||||||
|
uint32 virtualParentHashesCount = 17;
|
||||||
|
uint64 virtualDaaScore = 18;
|
||||||
|
}
|
||||||
|
|
||||||
|
message StorageMetrics { uint64 storageSizeBytes = 1; }
|
||||||
|
|
||||||
|
message GetConnectionsRequestMessage { bool includeProfileData = 1; }
|
||||||
|
|
||||||
|
message ConnectionsProfileData {
|
||||||
|
double cpuUsage = 1;
|
||||||
|
uint64 memoryUsage = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetConnectionsResponseMessage {
|
||||||
|
uint32 clients = 1;
|
||||||
|
uint32 peers = 2;
|
||||||
|
ConnectionsProfileData profileData = 3;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetSystemInfoRequestMessage {}
|
||||||
|
|
||||||
|
message GetSystemInfoResponseMessage {
|
||||||
|
string version = 1;
|
||||||
|
string systemId = 2;
|
||||||
|
string gitHash = 3;
|
||||||
|
uint32 coreNum = 4;
|
||||||
|
uint64 totalMemory = 5;
|
||||||
|
uint32 fdLimit = 6;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMetricsRequestMessage {
|
||||||
|
bool processMetrics = 1;
|
||||||
|
bool connectionMetrics = 2;
|
||||||
|
bool bandwidthMetrics = 3;
|
||||||
|
bool consensusMetrics = 4;
|
||||||
|
bool storageMetrics = 5;
|
||||||
|
bool customMetrics = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetMetricsResponseMessage {
|
||||||
|
uint64 serverTime = 1;
|
||||||
|
ProcessMetrics processMetrics = 11;
|
||||||
|
ConnectionMetrics connectionMetrics = 12;
|
||||||
|
BandwidthMetrics bandwidthMetrics = 13;
|
||||||
|
ConsensusMetrics consensusMetrics = 14;
|
||||||
|
StorageMetrics storageMetrics = 15;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetServerInfoRequestMessage {}
|
||||||
|
|
||||||
|
message GetServerInfoResponseMessage {
|
||||||
|
uint32 rpcApiVersion = 1;
|
||||||
|
uint32 rpcApiRevision = 2;
|
||||||
|
string serverVersion = 3;
|
||||||
|
string networkId = 4;
|
||||||
|
bool hasUtxoIndex = 5;
|
||||||
|
bool isSynced = 6;
|
||||||
|
uint64 virtualDaaScore = 7;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetSyncStatusRequestMessage {}
|
||||||
|
|
||||||
|
message GetSyncStatusResponseMessage {
|
||||||
|
bool isSynced = 1;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDaaScoreTimestampEstimateRequestMessage {
|
||||||
|
repeated uint64 daa_scores = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetDaaScoreTimestampEstimateResponseMessage {
|
||||||
|
repeated uint64 timestamps = 1;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RpcFeerateBucket {
|
||||||
|
// Fee/mass of a transaction in `sompi/gram` units
|
||||||
|
double feerate = 1;
|
||||||
|
double estimated_seconds = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Data required for making fee estimates.
|
||||||
|
//
|
||||||
|
// Feerate values represent fee/mass of a transaction in `sompi/gram` units.
|
||||||
|
// Given a feerate value recommendation, calculate the required fee by
|
||||||
|
// taking the transaction mass and multiplying it by feerate: `fee = feerate *
|
||||||
|
// mass(tx)`
|
||||||
|
message RpcFeeEstimate {
|
||||||
|
// Top-priority feerate bucket. Provides an estimation of the feerate required
|
||||||
|
// for sub-second DAG inclusion.
|
||||||
|
RpcFeerateBucket priority_bucket = 1;
|
||||||
|
|
||||||
|
// A vector of *normal* priority feerate values. The first value of this
|
||||||
|
// vector is guaranteed to exist and provide an estimation for sub-*minute*
|
||||||
|
// DAG inclusion. All other values will have shorter estimation times than all
|
||||||
|
// `low_bucket` values. Therefor by chaining `[priority] | normal | low` and
|
||||||
|
// interpolating between them, one can compose a complete feerate function on
|
||||||
|
// the client side. The API makes an effort to sample enough "interesting"
|
||||||
|
// points on the feerate-to-time curve, so that the interpolation is
|
||||||
|
// meaningful.
|
||||||
|
repeated RpcFeerateBucket normal_buckets = 2;
|
||||||
|
|
||||||
|
// A vector of *low* priority feerate values. The first value of this vector
|
||||||
|
// is guaranteed to exist and provide an estimation for sub-*hour* DAG
|
||||||
|
// inclusion.
|
||||||
|
repeated RpcFeerateBucket low_buckets = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RpcFeeEstimateVerboseExperimentalData {
|
||||||
|
uint64 mempool_ready_transactions_count = 1;
|
||||||
|
uint64 mempool_ready_transactions_total_mass = 2;
|
||||||
|
uint64 network_mass_per_second = 3;
|
||||||
|
|
||||||
|
double next_block_template_feerate_min = 11;
|
||||||
|
double next_block_template_feerate_median = 12;
|
||||||
|
double next_block_template_feerate_max = 13;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetFeeEstimateRequestMessage {}
|
||||||
|
|
||||||
|
message GetFeeEstimateResponseMessage {
|
||||||
|
RpcFeeEstimate estimate = 1;
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetFeeEstimateExperimentalRequestMessage { bool verbose = 1; }
|
||||||
|
|
||||||
|
message GetFeeEstimateExperimentalResponseMessage {
|
||||||
|
RpcFeeEstimate estimate = 1;
|
||||||
|
RpcFeeEstimateVerboseExperimentalData verbose = 2;
|
||||||
|
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
message GetCurrentBlockColorRequestMessage { string hash = 1; }
|
||||||
|
|
||||||
|
message GetCurrentBlockColorResponseMessage {
|
||||||
|
bool blue = 1;
|
||||||
|
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitTransactionReplacementRequestMessage submits a transaction to the
|
||||||
|
// mempool, applying a mandatory Replace by Fee policy
|
||||||
|
message SubmitTransactionReplacementRequestMessage {
|
||||||
|
RpcTransaction transaction = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
message SubmitTransactionReplacementResponseMessage {
|
||||||
|
// The transaction ID of the submitted transaction
|
||||||
|
string transactionId = 1;
|
||||||
|
|
||||||
|
// The previous transaction replaced in the mempool by the newly submitted one
|
||||||
|
RpcTransaction replacedTransaction = 2;
|
||||||
|
|
||||||
|
RPCError error = 1000;
|
||||||
|
}
|
||||||
@ -17,7 +17,7 @@ func (x *KaspadMessage_GetCurrentNetworkResponse) toAppMessage() (appmessage.Mes
|
|||||||
if x == nil {
|
if x == nil {
|
||||||
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetCurrentNetworkResponse is nil")
|
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetCurrentNetworkResponse is nil")
|
||||||
}
|
}
|
||||||
return x.toAppMessage()
|
return x.GetCurrentNetworkResponse.toAppMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *KaspadMessage_GetCurrentNetworkResponse) fromAppMessage(message *appmessage.GetCurrentNetworkResponseMessage) error {
|
func (x *KaspadMessage_GetCurrentNetworkResponse) fromAppMessage(message *appmessage.GetCurrentNetworkResponseMessage) error {
|
||||||
|
|||||||
@ -0,0 +1,67 @@
|
|||||||
|
package protowire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/app/appmessage"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x *KaspadMessage_GetFeeEstimateRequest) toAppMessage() (appmessage.Message, error) {
|
||||||
|
return &appmessage.GetFeeEstimateRequestMessage{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KaspadMessage_GetFeeEstimateRequest) fromAppMessage(_ *appmessage.GetFeeEstimateRequestMessage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KaspadMessage_GetFeeEstimateResponse) toAppMessage() (appmessage.Message, error) {
|
||||||
|
if x == nil {
|
||||||
|
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetFeeEstimateResponse is nil")
|
||||||
|
}
|
||||||
|
return x.GetFeeEstimateResponse.toAppMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetFeeEstimateResponseMessage) toAppMessage() (appmessage.Message, error) {
|
||||||
|
if x == nil {
|
||||||
|
return nil, errors.Wrapf(errorNil, "GetFeeEstimateResponseMessage is nil")
|
||||||
|
}
|
||||||
|
rpcErr, err := x.Error.toAppMessage()
|
||||||
|
// Error is an optional field
|
||||||
|
if err != nil && !errors.Is(err, errorNil) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
estimate, err := x.Estimate.toAppMessage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &appmessage.GetFeeEstimateResponseMessage{
|
||||||
|
Error: rpcErr,
|
||||||
|
Estimate: estimate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RpcFeeEstimate) toAppMessage() (appmessage.RPCFeeEstimate, error) {
|
||||||
|
if x == nil {
|
||||||
|
return appmessage.RPCFeeEstimate{}, errors.Wrapf(errorNil, "RpcFeeEstimate is nil")
|
||||||
|
}
|
||||||
|
return appmessage.RPCFeeEstimate{
|
||||||
|
PriorityBucket: appmessage.RPCFeeRateBucket{
|
||||||
|
Feerate: x.PriorityBucket.Feerate,
|
||||||
|
EstimatedSeconds: x.PriorityBucket.EstimatedSeconds,
|
||||||
|
},
|
||||||
|
NormalBuckets: feeRateBucketsToAppMessage(x.NormalBuckets),
|
||||||
|
LowBuckets: feeRateBucketsToAppMessage(x.LowBuckets),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func feeRateBucketsToAppMessage(protoBuckets []*RpcFeerateBucket) []appmessage.RPCFeeRateBucket {
|
||||||
|
appMsgBuckets := make([]appmessage.RPCFeeRateBucket, len(protoBuckets))
|
||||||
|
for i, bucket := range protoBuckets {
|
||||||
|
appMsgBuckets[i] = appmessage.RPCFeeRateBucket{
|
||||||
|
Feerate: bucket.Feerate,
|
||||||
|
EstimatedSeconds: bucket.EstimatedSeconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return appMsgBuckets
|
||||||
|
}
|
||||||
@ -968,6 +968,13 @@ func toRPCPayload(message appmessage.Message) (isKaspadMessage_Payload, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return payload, nil
|
return payload, nil
|
||||||
|
case *appmessage.GetFeeEstimateRequestMessage:
|
||||||
|
payload := new(KaspadMessage_GetFeeEstimateRequest)
|
||||||
|
err := payload.fromAppMessage(message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
default:
|
default:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|||||||
20
infrastructure/network/rpcclient/rpc_get_fee_estimate.go
Normal file
20
infrastructure/network/rpcclient/rpc_get_fee_estimate.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package rpcclient
|
||||||
|
|
||||||
|
import "github.com/kaspanet/kaspad/app/appmessage"
|
||||||
|
|
||||||
|
// GetFeeEstimate sends an RPC request respective to the function's name and returns the RPC server's response
|
||||||
|
func (c *RPCClient) GetFeeEstimate() (*appmessage.GetFeeEstimateResponseMessage, error) {
|
||||||
|
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewGetFeeEstimateRequestMessage())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response, err := c.route(appmessage.CmdGetFeeEstimateResponseMessage).DequeueWithTimeout(c.timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := response.(*appmessage.GetFeeEstimateResponseMessage)
|
||||||
|
if resp.Error != nil {
|
||||||
|
return nil, c.convertRPCError(resp.Error)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user