mirror of
https://github.com/kaspanet/kaspad.git
synced 2026-02-24 12:23:22 +00:00
Compare commits
18 Commits
anti-spam-
...
min-fee-pa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
01c81143ab | ||
|
|
86b89065cf | ||
|
|
f41dc7fa0b | ||
|
|
6b38bf7069 | ||
|
|
d2453f8e7b | ||
|
|
629faa8436 | ||
|
|
91e6c6b74b | ||
|
|
0819244ba1 | ||
|
|
a0149cd8d0 | ||
|
|
5a3b8a0066 | ||
|
|
8e71f79f98 | ||
|
|
346341a709 | ||
|
|
8c881aea39 | ||
|
|
40ec440dcf | ||
|
|
88bdcb43bc | ||
|
|
9d1e44673f | ||
|
|
387fade044 | ||
|
|
c417c8b525 |
2
.github/workflows/deploy.yaml
vendored
2
.github/workflows/deploy.yaml
vendored
@@ -23,7 +23,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19
|
||||
go-version: 1.21
|
||||
|
||||
- name: Build on Linux
|
||||
if: runner.os == 'Linux'
|
||||
|
||||
@@ -218,7 +218,7 @@ func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externa
|
||||
Outputs: outputs,
|
||||
LockTime: rpcTransaction.LockTime,
|
||||
SubnetworkID: *subnetworkID,
|
||||
Gas: rpcTransaction.LockTime,
|
||||
Gas: rpcTransaction.Gas,
|
||||
Payload: payload,
|
||||
}, nil
|
||||
}
|
||||
@@ -286,7 +286,7 @@ func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransactio
|
||||
Outputs: outputs,
|
||||
LockTime: transaction.LockTime,
|
||||
SubnetworkID: subnetworkID,
|
||||
Gas: transaction.LockTime,
|
||||
Gas: transaction.Gas,
|
||||
Payload: payload,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,8 @@ func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, requ
|
||||
}
|
||||
|
||||
log.Debugf("Rejected transaction %s: %s", transactionID, err)
|
||||
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
|
||||
// Return the ID also in the case of error, so that clients can match the response to the correct transaction submit request
|
||||
errorMessage := appmessage.NewSubmitTransactionResponseMessage(transactionID.String())
|
||||
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", transactionID, err)
|
||||
return errorMessage, nil
|
||||
}
|
||||
|
||||
@@ -1,3 +1,30 @@
|
||||
Kaspad v0.12.17 - 2024-02-19
|
||||
===========================
|
||||
|
||||
* Wallet-related improvements and fixes (#2253, #2257, #2258, #2262)
|
||||
|
||||
Kaspad v0.12.16 - 2023-12-25
|
||||
===========================
|
||||
|
||||
* Adapt wallet UTXO selection to dust patch (#2254)
|
||||
|
||||
Kaspad v0.12.15 - 2023-12-16
|
||||
===========================
|
||||
|
||||
* Update ECDSA address test to use a valid public key (#2202)
|
||||
* Fix off by small amounts in sent amount kaspa (#2220)
|
||||
* Use removeRedeemers correctly by (#2235)
|
||||
* Fix windows asset building by increasing go version (#2245)
|
||||
* Added a mainnet dnsseeder (#2247)
|
||||
* Fix extract atomic swap data pushes (#2203)
|
||||
* Adapt kaspawallet to support testnet 11 (#2211)
|
||||
* Fix type detection in RemoveInvalidTransactions (#2252)
|
||||
|
||||
Kaspad v0.12.14 - 2023-09-26
|
||||
===========================
|
||||
|
||||
* Anti-spam measurements against dust attack (#2223)
|
||||
|
||||
Kaspad v0.12.13 - 2023-03-06
|
||||
===========================
|
||||
|
||||
|
||||
@@ -22,6 +22,8 @@ const (
|
||||
newAddressSubCmd = "new-address"
|
||||
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
|
||||
startDaemonSubCmd = "start-daemon"
|
||||
versionSubCmd = "version"
|
||||
getDaemonVersionSubCmd = "get-daemon-version"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -30,6 +32,7 @@ const (
|
||||
)
|
||||
|
||||
type configFlags struct {
|
||||
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
@@ -56,9 +59,9 @@ type sendConfig struct {
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
||||
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
|
||||
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
|
||||
SendAmount float64 `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)"`
|
||||
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Repeat multiple times (adding -a before each) to accept several addresses" required:"false"`
|
||||
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."`
|
||||
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)"`
|
||||
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
|
||||
config.NetworkFlags
|
||||
@@ -74,7 +77,7 @@ type createUnsignedTransactionConfig struct {
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
||||
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
|
||||
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
|
||||
SendAmount float64 `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)"`
|
||||
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)"`
|
||||
config.NetworkFlags
|
||||
@@ -113,12 +116,13 @@ type newAddressConfig struct {
|
||||
}
|
||||
|
||||
type startDaemonConfig struct {
|
||||
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
|
||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
|
||||
Listen string `long:"listen" short:"l" description:"Address to listen on (default: 0.0.0.0:8082)"`
|
||||
Timeout uint32 `long:"wait-timeout" short:"w" description:"Waiting timeout for RPC calls, seconds (default: 30 s)"`
|
||||
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
||||
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"`
|
||||
RPCServer string `long:"rpcserver" short:"s" description:"RPC server to connect to"`
|
||||
Listen string `long:"listen" short:"l" description:"Address to listen on (default: 0.0.0.0:8082)"`
|
||||
Timeout uint32 `long:"wait-timeout" short:"w" description:"Waiting timeout for RPC calls, seconds (default: 30 s)"`
|
||||
Profile string `long:"profile" description:"Enable HTTP profiling on given port -- NOTE port must be between 1024 and 65536"`
|
||||
MinFeePerTx uint64 `long:"min-fee-per-tx" description:"Minimum fee per transaction (in sompis) (default: 0)"`
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
@@ -129,6 +133,13 @@ type dumpUnencryptedDataConfig struct {
|
||||
config.NetworkFlags
|
||||
}
|
||||
|
||||
type versionConfig struct {
|
||||
}
|
||||
|
||||
type getDaemonVersionConfig struct {
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
||||
}
|
||||
|
||||
func parseCommandLine() (subCommand string, config interface{}) {
|
||||
cfg := &configFlags{}
|
||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||
@@ -185,6 +196,9 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
Listen: defaultListen,
|
||||
}
|
||||
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
|
||||
parser.AddCommand(versionSubCmd, "Get the wallet version", "Get the wallet version", &versionConfig{})
|
||||
getDaemonVersionConf := &getDaemonVersionConfig{DaemonAddress: defaultListen}
|
||||
parser.AddCommand(getDaemonVersionSubCmd, "Get the wallet daemon version", "Get the wallet daemon version", getDaemonVersionConf)
|
||||
|
||||
_, err := parser.Parse()
|
||||
if err != nil {
|
||||
@@ -290,14 +304,17 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
printErrorAndExit(err)
|
||||
}
|
||||
config = startDaemonConf
|
||||
case versionSubCmd:
|
||||
case getDaemonVersionSubCmd:
|
||||
config = getDaemonVersionConf
|
||||
}
|
||||
|
||||
return parser.Command.Active.Name, config
|
||||
}
|
||||
|
||||
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
|
||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
||||
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||
(conf.IsSendAll && conf.SendAmount != "") {
|
||||
|
||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||
}
|
||||
@@ -305,8 +322,8 @@ func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig
|
||||
}
|
||||
|
||||
func validateSendConfig(conf *sendConfig) error {
|
||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
||||
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||
(conf.IsSendAll && conf.SendAmount != "") {
|
||||
|
||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
)
|
||||
|
||||
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
@@ -20,7 +20,12 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
||||
sendAmountSompi, err := utils.KasToSompi(conf.SendAmount)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||
From: conf.FromAddresses,
|
||||
Address: conf.ToAddress,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.26.0
|
||||
// protoc v3.21.12
|
||||
// protoc-gen-go v1.27.1
|
||||
// protoc v3.12.3
|
||||
// source: kaspawalletd.proto
|
||||
|
||||
package pb
|
||||
@@ -1242,6 +1242,91 @@ func (x *SignResponse) GetSignedTransactions() [][]byte {
|
||||
return nil
|
||||
}
|
||||
|
||||
type GetVersionRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
}
|
||||
|
||||
func (x *GetVersionRequest) Reset() {
|
||||
*x = GetVersionRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[23]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetVersionRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetVersionRequest) ProtoMessage() {}
|
||||
|
||||
func (x *GetVersionRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[23]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetVersionRequest.ProtoReflect.Descriptor instead.
|
||||
func (*GetVersionRequest) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{23}
|
||||
}
|
||||
|
||||
type GetVersionResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Version string `protobuf:"bytes,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||
}
|
||||
|
||||
func (x *GetVersionResponse) Reset() {
|
||||
*x = GetVersionResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[24]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *GetVersionResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*GetVersionResponse) ProtoMessage() {}
|
||||
|
||||
func (x *GetVersionResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_kaspawalletd_proto_msgTypes[24]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use GetVersionResponse.ProtoReflect.Descriptor instead.
|
||||
func (*GetVersionResponse) Descriptor() ([]byte, []int) {
|
||||
return file_kaspawalletd_proto_rawDescGZIP(), []int{24}
|
||||
}
|
||||
|
||||
func (x *GetVersionResponse) GetVersion() string {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_kaspawalletd_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_kaspawalletd_proto_rawDesc = []byte{
|
||||
@@ -1371,62 +1456,72 @@ var file_kaspawalletd_proto_rawDesc = []byte{
|
||||
0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72,
|
||||
0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0c,
|
||||
0x52, 0x12, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x32, 0xb3, 0x06, 0x0a, 0x0c, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
|
||||
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61,
|
||||
0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65,
|
||||
0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71,
|
||||
0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c,
|
||||
0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a, 0x19, 0x47, 0x65, 0x74, 0x45,
|
||||
0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65,
|
||||
0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||
0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c,
|
||||
0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01, 0x0a, 0x1a, 0x43, 0x72, 0x65,
|
||||
0x69, 0x6f, 0x6e, 0x73, 0x22, 0x13, 0x0a, 0x11, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69,
|
||||
0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2e, 0x0a, 0x12, 0x47, 0x65, 0x74,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12,
|
||||
0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09,
|
||||
0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x32, 0x86, 0x07, 0x0a, 0x0c, 0x6b, 0x61,
|
||||
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x12, 0x51, 0x0a, 0x0a, 0x47, 0x65,
|
||||
0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e, 0x63, 0x65, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61,
|
||||
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61, 0x6e,
|
||||
0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70,
|
||||
0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x61, 0x6c, 0x61,
|
||||
0x6e, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x7e, 0x0a,
|
||||
0x19, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e,
|
||||
0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54, 0x58, 0x4f, 0x73, 0x12, 0x2e, 0x2e, 0x6b, 0x61, 0x73,
|
||||
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54,
|
||||
0x58, 0x4f, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x2f, 0x2e, 0x6b, 0x61, 0x73,
|
||||
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x45, 0x78, 0x74,
|
||||
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x53, 0x70, 0x65, 0x6e, 0x64, 0x61, 0x62, 0x6c, 0x65, 0x55, 0x54,
|
||||
0x58, 0x4f, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x81, 0x01,
|
||||
0x0a, 0x1a, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64,
|
||||
0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x2e, 0x6b,
|
||||
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61,
|
||||
0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61,
|
||||
0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e,
|
||||
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65,
|
||||
0x61, 0x74, 0x65, 0x55, 0x6e, 0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73,
|
||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x2f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
|
||||
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e, 0x73,
|
||||
0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e,
|
||||
0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x30, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61,
|
||||
0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x55, 0x6e,
|
||||
0x73, 0x69, 0x67, 0x6e, 0x65, 0x64, 0x54, 0x72, 0x61, 0x6e, 0x73, 0x61, 0x63, 0x74, 0x69, 0x6f,
|
||||
0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x5a, 0x0a, 0x0d,
|
||||
0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x12, 0x22, 0x2e,
|
||||
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f,
|
||||
0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
|
||||
0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a, 0x0a, 0x4e, 0x65, 0x77, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
|
||||
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
|
||||
0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
|
||||
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x53,
|
||||
0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77,
|
||||
0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
|
||||
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a, 0x09, 0x42, 0x72, 0x6f, 0x61,
|
||||
0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65,
|
||||
0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x52, 0x65,
|
||||
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x65, 0x6e, 0x64,
|
||||
0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e,
|
||||
0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61,
|
||||
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52,
|
||||
0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a, 0x04, 0x53, 0x69, 0x67,
|
||||
0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
|
||||
0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x6b,
|
||||
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e,
|
||||
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69,
|
||||
0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65,
|
||||
0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64, 0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73,
|
||||
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f,
|
||||
0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22,
|
||||
0x00, 0x12, 0x5a, 0x0a, 0x0d, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73,
|
||||
0x65, 0x73, 0x12, 0x22, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
|
||||
0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x65, 0x73, 0x52,
|
||||
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
|
||||
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x6f, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73,
|
||||
0x73, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x51, 0x0a,
|
||||
0x0a, 0x4e, 0x65, 0x77, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1f, 0x2e, 0x6b, 0x61,
|
||||
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41, 0x64,
|
||||
0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20, 0x2e, 0x6b,
|
||||
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x4e, 0x65, 0x77, 0x41,
|
||||
0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00,
|
||||
0x12, 0x4b, 0x0a, 0x08, 0x53, 0x68, 0x75, 0x74, 0x64, 0x6f, 0x77, 0x6e, 0x12, 0x1d, 0x2e, 0x6b,
|
||||
0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74,
|
||||
0x64, 0x6f, 0x77, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, 0x6b, 0x61,
|
||||
0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x68, 0x75, 0x74, 0x64,
|
||||
0x6f, 0x77, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x4e, 0x0a,
|
||||
0x09, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63, 0x61, 0x73, 0x74, 0x12, 0x1e, 0x2e, 0x6b, 0x61, 0x73,
|
||||
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
|
||||
0x61, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x6b, 0x61, 0x73,
|
||||
0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x42, 0x72, 0x6f, 0x61, 0x64, 0x63,
|
||||
0x61, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f, 0x0a,
|
||||
0x04, 0x53, 0x65, 0x6e, 0x64, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c,
|
||||
0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
|
||||
0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e,
|
||||
0x53, 0x65, 0x6e, 0x64, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x3f,
|
||||
0x0a, 0x04, 0x53, 0x69, 0x67, 0x6e, 0x12, 0x19, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61,
|
||||
0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1a, 0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64,
|
||||
0x2e, 0x53, 0x69, 0x67, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12,
|
||||
0x51, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x1f, 0x2e,
|
||||
0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65, 0x74,
|
||||
0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x20,
|
||||
0x2e, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74, 0x64, 0x2e, 0x47, 0x65,
|
||||
0x74, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
|
||||
0x22, 0x00, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d,
|
||||
0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x6e, 0x65, 0x74, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x64,
|
||||
0x2f, 0x63, 0x6d, 0x64, 0x2f, 0x6b, 0x61, 0x73, 0x70, 0x61, 0x77, 0x61, 0x6c, 0x6c, 0x65, 0x74,
|
||||
0x2f, 0x64, 0x61, 0x65, 0x6d, 0x6f, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74,
|
||||
0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -1441,7 +1536,7 @@ func file_kaspawalletd_proto_rawDescGZIP() []byte {
|
||||
return file_kaspawalletd_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 23)
|
||||
var file_kaspawalletd_proto_msgTypes = make([]protoimpl.MessageInfo, 25)
|
||||
var file_kaspawalletd_proto_goTypes = []interface{}{
|
||||
(*GetBalanceRequest)(nil), // 0: kaspawalletd.GetBalanceRequest
|
||||
(*GetBalanceResponse)(nil), // 1: kaspawalletd.GetBalanceResponse
|
||||
@@ -1466,6 +1561,8 @@ var file_kaspawalletd_proto_goTypes = []interface{}{
|
||||
(*SendResponse)(nil), // 20: kaspawalletd.SendResponse
|
||||
(*SignRequest)(nil), // 21: kaspawalletd.SignRequest
|
||||
(*SignResponse)(nil), // 22: kaspawalletd.SignResponse
|
||||
(*GetVersionRequest)(nil), // 23: kaspawalletd.GetVersionRequest
|
||||
(*GetVersionResponse)(nil), // 24: kaspawalletd.GetVersionResponse
|
||||
}
|
||||
var file_kaspawalletd_proto_depIdxs = []int32{
|
||||
2, // 0: kaspawalletd.GetBalanceResponse.addressBalances:type_name -> kaspawalletd.AddressBalances
|
||||
@@ -1482,17 +1579,19 @@ var file_kaspawalletd_proto_depIdxs = []int32{
|
||||
9, // 11: kaspawalletd.kaspawalletd.Broadcast:input_type -> kaspawalletd.BroadcastRequest
|
||||
19, // 12: kaspawalletd.kaspawalletd.Send:input_type -> kaspawalletd.SendRequest
|
||||
21, // 13: kaspawalletd.kaspawalletd.Sign:input_type -> kaspawalletd.SignRequest
|
||||
1, // 14: kaspawalletd.kaspawalletd.GetBalance:output_type -> kaspawalletd.GetBalanceResponse
|
||||
18, // 15: kaspawalletd.kaspawalletd.GetExternalSpendableUTXOs:output_type -> kaspawalletd.GetExternalSpendableUTXOsResponse
|
||||
4, // 16: kaspawalletd.kaspawalletd.CreateUnsignedTransactions:output_type -> kaspawalletd.CreateUnsignedTransactionsResponse
|
||||
6, // 17: kaspawalletd.kaspawalletd.ShowAddresses:output_type -> kaspawalletd.ShowAddressesResponse
|
||||
8, // 18: kaspawalletd.kaspawalletd.NewAddress:output_type -> kaspawalletd.NewAddressResponse
|
||||
12, // 19: kaspawalletd.kaspawalletd.Shutdown:output_type -> kaspawalletd.ShutdownResponse
|
||||
10, // 20: kaspawalletd.kaspawalletd.Broadcast:output_type -> kaspawalletd.BroadcastResponse
|
||||
20, // 21: kaspawalletd.kaspawalletd.Send:output_type -> kaspawalletd.SendResponse
|
||||
22, // 22: kaspawalletd.kaspawalletd.Sign:output_type -> kaspawalletd.SignResponse
|
||||
14, // [14:23] is the sub-list for method output_type
|
||||
5, // [5:14] is the sub-list for method input_type
|
||||
23, // 14: kaspawalletd.kaspawalletd.GetVersion:input_type -> kaspawalletd.GetVersionRequest
|
||||
1, // 15: kaspawalletd.kaspawalletd.GetBalance:output_type -> kaspawalletd.GetBalanceResponse
|
||||
18, // 16: kaspawalletd.kaspawalletd.GetExternalSpendableUTXOs:output_type -> kaspawalletd.GetExternalSpendableUTXOsResponse
|
||||
4, // 17: kaspawalletd.kaspawalletd.CreateUnsignedTransactions:output_type -> kaspawalletd.CreateUnsignedTransactionsResponse
|
||||
6, // 18: kaspawalletd.kaspawalletd.ShowAddresses:output_type -> kaspawalletd.ShowAddressesResponse
|
||||
8, // 19: kaspawalletd.kaspawalletd.NewAddress:output_type -> kaspawalletd.NewAddressResponse
|
||||
12, // 20: kaspawalletd.kaspawalletd.Shutdown:output_type -> kaspawalletd.ShutdownResponse
|
||||
10, // 21: kaspawalletd.kaspawalletd.Broadcast:output_type -> kaspawalletd.BroadcastResponse
|
||||
20, // 22: kaspawalletd.kaspawalletd.Send:output_type -> kaspawalletd.SendResponse
|
||||
22, // 23: kaspawalletd.kaspawalletd.Sign:output_type -> kaspawalletd.SignResponse
|
||||
24, // 24: kaspawalletd.kaspawalletd.GetVersion:output_type -> kaspawalletd.GetVersionResponse
|
||||
15, // [15:25] is the sub-list for method output_type
|
||||
5, // [5:15] is the sub-list for method input_type
|
||||
5, // [5:5] is the sub-list for extension type_name
|
||||
5, // [5:5] is the sub-list for extension extendee
|
||||
0, // [0:5] is the sub-list for field type_name
|
||||
@@ -1780,6 +1879,30 @@ func file_kaspawalletd_proto_init() {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[23].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetVersionRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_kaspawalletd_proto_msgTypes[24].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*GetVersionResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
@@ -1787,7 +1910,7 @@ func file_kaspawalletd_proto_init() {
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_kaspawalletd_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 23,
|
||||
NumMessages: 25,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
|
||||
@@ -15,6 +15,7 @@ service kaspawalletd {
|
||||
rpc Send(SendRequest) returns (SendResponse) {}
|
||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
||||
rpc Sign(SignRequest) returns (SignResponse) {}
|
||||
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
|
||||
}
|
||||
|
||||
message GetBalanceRequest {
|
||||
@@ -127,3 +128,10 @@ message SignRequest{
|
||||
message SignResponse{
|
||||
repeated bytes signedTransactions = 1;
|
||||
}
|
||||
|
||||
message GetVersionRequest{
|
||||
}
|
||||
|
||||
message GetVersionResponse{
|
||||
string version = 1;
|
||||
}
|
||||
@@ -1,4 +1,8 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.12.3
|
||||
// source: kaspawalletd.proto
|
||||
|
||||
package pb
|
||||
|
||||
@@ -29,6 +33,7 @@ type KaspawalletdClient interface {
|
||||
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
|
||||
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
|
||||
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
|
||||
}
|
||||
|
||||
type kaspawalletdClient struct {
|
||||
@@ -120,6 +125,15 @@ func (c *kaspawalletdClient) Sign(ctx context.Context, in *SignRequest, opts ...
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *kaspawalletdClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) {
|
||||
out := new(GetVersionResponse)
|
||||
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/GetVersion", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// KaspawalletdServer is the server API for Kaspawalletd service.
|
||||
// All implementations must embed UnimplementedKaspawalletdServer
|
||||
// for forward compatibility
|
||||
@@ -135,6 +149,7 @@ type KaspawalletdServer interface {
|
||||
Send(context.Context, *SendRequest) (*SendResponse, error)
|
||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
||||
Sign(context.Context, *SignRequest) (*SignResponse, error)
|
||||
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
|
||||
mustEmbedUnimplementedKaspawalletdServer()
|
||||
}
|
||||
|
||||
@@ -169,6 +184,9 @@ func (UnimplementedKaspawalletdServer) Send(context.Context, *SendRequest) (*Sen
|
||||
func (UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*SignResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented")
|
||||
}
|
||||
func (UnimplementedKaspawalletdServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
|
||||
}
|
||||
func (UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {}
|
||||
|
||||
// UnsafeKaspawalletdServer may be embedded to opt out of forward compatibility for this service.
|
||||
@@ -344,6 +362,24 @@ func _Kaspawalletd_Sign_Handler(srv interface{}, ctx context.Context, dec func(i
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _Kaspawalletd_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(GetVersionRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(KaspawalletdServer).GetVersion(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/kaspawalletd.kaspawalletd/GetVersion",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(KaspawalletdServer).GetVersion(ctx, req.(*GetVersionRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Kaspawalletd_ServiceDesc is the grpc.ServiceDesc for Kaspawalletd service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
@@ -387,6 +423,10 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "Sign",
|
||||
Handler: _Kaspawalletd_Sign_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "GetVersion",
|
||||
Handler: _Kaspawalletd_GetVersion_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "kaspawalletd.proto",
|
||||
|
||||
@@ -2,6 +2,7 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
@@ -14,13 +15,15 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
if !s.isSynced() {
|
||||
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
||||
}
|
||||
|
||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
daaScore := dagInfo.VirtualDAAScore
|
||||
maturity := s.params.BlockCoinbaseMaturity
|
||||
|
||||
balancesMap := make(balancesMapType, 0)
|
||||
for _, entry := range s.utxosSortedByAmount {
|
||||
amount := entry.UTXOEntry.Amount()
|
||||
@@ -30,7 +33,7 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
||||
balances = new(balancesType)
|
||||
balancesMap[address] = balances
|
||||
}
|
||||
if isUTXOSpendable(entry, daaScore, maturity) {
|
||||
if s.isUTXOSpendable(entry, daaScore) {
|
||||
balances.available += amount
|
||||
} else {
|
||||
balances.pending += amount
|
||||
@@ -55,6 +58,8 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
||||
pending += balances.pending
|
||||
}
|
||||
|
||||
log.Infof("GetBalance request scanned %d UTXOs overall over %d addresses", len(s.utxosSortedByAmount), len(balancesMap))
|
||||
|
||||
return &pb.GetBalanceResponse{
|
||||
Available: available,
|
||||
Pending: pending,
|
||||
@@ -62,9 +67,9 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
||||
}, nil
|
||||
}
|
||||
|
||||
func isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
|
||||
func (s *server) isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64) bool {
|
||||
if !entry.UTXOEntry.IsCoinbase() {
|
||||
return true
|
||||
}
|
||||
return entry.UTXOEntry.BlockDAAScore()+coinbaseMaturity < virtualDAAScore
|
||||
return entry.UTXOEntry.BlockDAAScore()+s.coinbaseMaturity < virtualDAAScore
|
||||
}
|
||||
|
||||
@@ -2,14 +2,16 @@ package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"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/infrastructure/network/rpcclient"
|
||||
"github.com/pkg/errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
|
||||
@@ -54,16 +56,12 @@ func (s *server) broadcast(transactions [][]byte, isDomain bool) ([]string, erro
|
||||
}
|
||||
}
|
||||
|
||||
err = s.refreshUTXOs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s.forceSync()
|
||||
return txIDs, nil
|
||||
}
|
||||
|
||||
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
|
||||
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), false)
|
||||
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), consensushashing.TransactionID(tx).String(), false)
|
||||
if err != nil {
|
||||
return "", errors.Wrapf(err, "error submitting transaction")
|
||||
}
|
||||
|
||||
@@ -3,19 +3,22 @@ package server
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/util"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/exp/slices"
|
||||
)
|
||||
|
||||
// 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).
|
||||
// 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)
|
||||
const minChangeTarget = constants.SompiPerKaspa / 5
|
||||
|
||||
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
|
||||
*pb.CreateUnsignedTransactionsResponse, error,
|
||||
) {
|
||||
@@ -35,7 +38,6 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
||||
if !s.isSynced() {
|
||||
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
||||
}
|
||||
|
||||
// make sure address string is correct before proceeding to a
|
||||
// potentially long UTXO refreshment operation
|
||||
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
|
||||
@@ -43,16 +45,11 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = s.refreshUTXOs()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var fromAddresses []*walletAddress
|
||||
for _, from := range fromAddressesString {
|
||||
fromAddress, exists := s.addressSet[from]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("Specified from address %s does not exists", from)
|
||||
return nil, fmt.Errorf("specified from address %s does not exists", from)
|
||||
}
|
||||
fromAddresses = append(fromAddresses, fromAddress)
|
||||
}
|
||||
@@ -107,13 +104,13 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
||||
}
|
||||
|
||||
for _, utxo := range s.utxosSortedByAmount {
|
||||
if (fromAddresses != nil && !slices.Contains(fromAddresses, utxo.address)) ||
|
||||
!isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
||||
if (fromAddresses != nil && !walletAddressesContain(fromAddresses, utxo.address)) ||
|
||||
!s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
|
||||
continue
|
||||
}
|
||||
|
||||
if broadcastTime, ok := s.usedOutpoints[*utxo.Outpoint]; ok {
|
||||
if time.Since(broadcastTime) > time.Minute {
|
||||
if s.usedOutpointHasExpired(broadcastTime) {
|
||||
delete(s.usedOutpoints, *utxo.Outpoint)
|
||||
} else {
|
||||
continue
|
||||
@@ -129,13 +126,26 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
||||
totalValue += utxo.UTXOEntry.Amount()
|
||||
|
||||
fee := feePerInput * uint64(len(selectedUTXOs))
|
||||
if fee < s.minFeePerTx {
|
||||
fee = s.minFeePerTx
|
||||
}
|
||||
|
||||
totalSpend := spendAmount + fee
|
||||
if !isSendAll && totalValue >= totalSpend {
|
||||
// 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
|
||||
// 2. totalValue > totalSpend, so there will be change and 2 outputs, therefor in order to not struggle with --
|
||||
// 2.1 go-nodes dust patch we try and find at least 2 inputs (even though the next one is not necessary in terms of spend value)
|
||||
// 2.2 KIP9 we try and make sure that the change amount is not too small
|
||||
if !isSendAll && (totalValue == totalSpend || (totalValue >= totalSpend+minChangeTarget && len(selectedUTXOs) > 1)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
fee := feePerInput * uint64(len(selectedUTXOs))
|
||||
if fee < s.minFeePerTx {
|
||||
fee = s.minFeePerTx
|
||||
}
|
||||
|
||||
var totalSpend uint64
|
||||
if isSendAll {
|
||||
totalSpend = totalValue
|
||||
@@ -151,3 +161,13 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
||||
|
||||
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
||||
}
|
||||
|
||||
func walletAddressesContain(addresses []*walletAddress, contain *walletAddress) bool {
|
||||
for _, address := range addresses {
|
||||
if *address == *contain {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
"github.com/kaspanet/kaspad/util/txmass"
|
||||
@@ -27,17 +30,23 @@ import (
|
||||
type server struct {
|
||||
pb.UnimplementedKaspawalletdServer
|
||||
|
||||
rpcClient *rpcclient.RPCClient
|
||||
params *dagconfig.Params
|
||||
rpcClient *rpcclient.RPCClient // RPC client for ongoing user requests
|
||||
backgroundRPCClient *rpcclient.RPCClient // RPC client dedicated for address and UTXO background fetching
|
||||
params *dagconfig.Params
|
||||
coinbaseMaturity uint64 // Is different from default if we use testnet-11
|
||||
minFeePerTx uint64
|
||||
|
||||
lock sync.RWMutex
|
||||
utxosSortedByAmount []*walletUTXO
|
||||
nextSyncStartIndex uint32
|
||||
keysFile *keys.File
|
||||
shutdown chan struct{}
|
||||
addressSet walletAddressSet
|
||||
txMassCalculator *txmass.Calculator
|
||||
usedOutpoints map[externalapi.DomainOutpoint]time.Time
|
||||
lock sync.RWMutex
|
||||
utxosSortedByAmount []*walletUTXO
|
||||
nextSyncStartIndex uint32
|
||||
keysFile *keys.File
|
||||
shutdown chan struct{}
|
||||
forceSyncChan chan struct{}
|
||||
startTimeOfLastCompletedRefresh time.Time
|
||||
addressSet walletAddressSet
|
||||
txMassCalculator *txmass.Calculator
|
||||
usedOutpoints map[externalapi.DomainOutpoint]time.Time
|
||||
firstSyncDone atomic.Bool
|
||||
|
||||
isLogFinalProgressLineShown bool
|
||||
maxUsedAddressesForLog uint32
|
||||
@@ -49,7 +58,7 @@ type server struct {
|
||||
const MaxDaemonSendMsgSize = 100_000_000
|
||||
|
||||
// Start starts the kaspawalletd server
|
||||
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32) error {
|
||||
func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath string, profile string, timeout uint32, minFeePerTx uint64) error {
|
||||
initLog(defaultLogFile, defaultErrLogFile)
|
||||
|
||||
defer panics.HandlePanic(log, "MAIN", nil)
|
||||
@@ -59,6 +68,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
||||
profiling.Start(profile, log)
|
||||
}
|
||||
|
||||
log.Infof("Version %s", version.Version())
|
||||
listener, err := net.Listen("tcp", listen)
|
||||
if err != nil {
|
||||
return (errors.Wrapf(err, "Error listening to TCP on %s", listen))
|
||||
@@ -70,6 +80,10 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
||||
if err != nil {
|
||||
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
|
||||
}
|
||||
backgroundRPCClient, err := connectToRPC(params, rpcServer, timeout)
|
||||
if err != nil {
|
||||
return (errors.Wrapf(err, "Error making a second connection to RPC server %s", rpcServer))
|
||||
}
|
||||
|
||||
log.Infof("Connected, reading keys file %s...", keysFilePath)
|
||||
keysFile, err := keys.ReadKeysFile(params, keysFilePath)
|
||||
@@ -82,13 +96,27 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
||||
return err
|
||||
}
|
||||
|
||||
dagInfo, err := rpcClient.GetBlockDAGInfo()
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
coinbaseMaturity := params.BlockCoinbaseMaturity
|
||||
if dagInfo.NetworkName == "kaspa-testnet-11" {
|
||||
coinbaseMaturity = 1000
|
||||
}
|
||||
|
||||
serverInstance := &server{
|
||||
rpcClient: rpcClient,
|
||||
backgroundRPCClient: backgroundRPCClient,
|
||||
params: params,
|
||||
coinbaseMaturity: coinbaseMaturity,
|
||||
minFeePerTx: minFeePerTx,
|
||||
utxosSortedByAmount: []*walletUTXO{},
|
||||
nextSyncStartIndex: 0,
|
||||
keysFile: keysFile,
|
||||
shutdown: make(chan struct{}),
|
||||
forceSyncChan: make(chan struct{}),
|
||||
addressSet: make(walletAddressSet),
|
||||
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
||||
usedOutpoints: map[externalapi.DomainOutpoint]time.Time{},
|
||||
@@ -98,8 +126,8 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
||||
}
|
||||
|
||||
log.Infof("Read, syncing the wallet...")
|
||||
spawn("serverInstance.sync", func() {
|
||||
err := serverInstance.sync()
|
||||
spawn("serverInstance.syncLoop", func() {
|
||||
err := serverInstance.syncLoop()
|
||||
if err != nil {
|
||||
printErrorAndExit(errors.Wrap(err, "error syncing the wallet"))
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawa
|
||||
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
|
||||
continue
|
||||
}
|
||||
if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
||||
if !s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
|
||||
continue
|
||||
}
|
||||
additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{
|
||||
|
||||
@@ -23,7 +23,7 @@ func (was walletAddressSet) strings() []string {
|
||||
return addresses
|
||||
}
|
||||
|
||||
func (s *server) sync() error {
|
||||
func (s *server) syncLoop() error {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
|
||||
@@ -32,29 +32,39 @@ func (s *server) sync() error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.refreshExistingUTXOsWithLock()
|
||||
err = s.refreshUTXOs()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for range ticker.C {
|
||||
err = s.collectFarAddresses()
|
||||
if err != nil {
|
||||
return err
|
||||
s.firstSyncDone.Store(true)
|
||||
log.Infof("Wallet is synced and ready for operation")
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-s.forceSyncChan:
|
||||
}
|
||||
|
||||
err = s.collectRecentAddresses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.refreshExistingUTXOsWithLock()
|
||||
err := s.sync()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
func (s *server) sync() error {
|
||||
err := s.collectFarAddresses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = s.collectRecentAddresses()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.refreshUTXOs()
|
||||
}
|
||||
|
||||
const (
|
||||
@@ -158,7 +168,7 @@ func (s *server) collectAddresses(start, end uint32) error {
|
||||
return err
|
||||
}
|
||||
|
||||
getBalancesByAddressesResponse, err := s.rpcClient.GetBalancesByAddresses(addressSet.strings())
|
||||
getBalancesByAddressesResponse, err := s.backgroundRPCClient.GetBalancesByAddresses(addressSet.strings())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -208,15 +218,17 @@ func (s *server) updateAddressesAndLastUsedIndexes(requestedAddressSet walletAdd
|
||||
return s.keysFile.SetLastUsedInternalIndex(lastUsedInternalIndex)
|
||||
}
|
||||
|
||||
func (s *server) refreshExistingUTXOsWithLock() error {
|
||||
s.lock.Lock()
|
||||
defer s.lock.Unlock()
|
||||
|
||||
return s.refreshUTXOs()
|
||||
func (s *server) usedOutpointHasExpired(outpointBroadcastTime time.Time) bool {
|
||||
// If the node returns a UTXO we previously attempted to spend and enough time has passed, we assume
|
||||
// that the network rejected or lost the previous transaction and allow a reuse. We set this time
|
||||
// interval to a minute.
|
||||
// We also verify that a full refresh UTXO operation started after this time point and has already
|
||||
// completed, in order to make sure that indeed this state reflects a state obtained following the required wait time.
|
||||
return s.startTimeOfLastCompletedRefresh.After(outpointBroadcastTime.Add(time.Minute))
|
||||
}
|
||||
|
||||
// updateUTXOSet clears the current UTXO set, and re-fills it with the given entries
|
||||
func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, mempoolEntries []*appmessage.MempoolEntryByAddress) error {
|
||||
func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, mempoolEntries []*appmessage.MempoolEntryByAddress, refreshStart time.Time) error {
|
||||
utxos := make([]*walletUTXO, 0, len(entries))
|
||||
|
||||
exclude := make(map[appmessage.RPCOutpoint]struct{})
|
||||
@@ -243,6 +255,7 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
|
||||
return err
|
||||
}
|
||||
|
||||
// No need to lock for reading since the only writer of this set is on `syncLoop` on the same goroutine.
|
||||
address, ok := s.addressSet[entry.Address]
|
||||
if !ok {
|
||||
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
|
||||
@@ -256,32 +269,56 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
|
||||
|
||||
sort.Slice(utxos, func(i, j int) bool { return utxos[i].UTXOEntry.Amount() > utxos[j].UTXOEntry.Amount() })
|
||||
|
||||
s.lock.Lock()
|
||||
s.startTimeOfLastCompletedRefresh = refreshStart
|
||||
s.utxosSortedByAmount = utxos
|
||||
|
||||
// Cleanup expired used outpoints to avoid a memory leak
|
||||
for outpoint, broadcastTime := range s.usedOutpoints {
|
||||
if s.usedOutpointHasExpired(broadcastTime) {
|
||||
delete(s.usedOutpoints, outpoint)
|
||||
}
|
||||
}
|
||||
s.lock.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *server) refreshUTXOs() error {
|
||||
refreshStart := time.Now()
|
||||
|
||||
// No need to lock for reading since the only writer of this set is on `syncLoop` on the same goroutine.
|
||||
addresses := s.addressSet.strings()
|
||||
// It's important to check the mempool before calling `GetUTXOsByAddresses`:
|
||||
// If we would do it the other way around an output can be spent in the mempool
|
||||
// and not in consensus, and between the calls its spending transaction will be
|
||||
// added to consensus and removed from the mempool, so `getUTXOsByAddressesResponse`
|
||||
// will include an obsolete output.
|
||||
mempoolEntriesByAddresses, err := s.rpcClient.GetMempoolEntriesByAddresses(s.addressSet.strings(), true, true)
|
||||
mempoolEntriesByAddresses, err := s.backgroundRPCClient.GetMempoolEntriesByAddresses(addresses, true, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
getUTXOsByAddressesResponse, err := s.rpcClient.GetUTXOsByAddresses(s.addressSet.strings())
|
||||
getUTXOsByAddressesResponse, err := s.backgroundRPCClient.GetUTXOsByAddresses(addresses)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.updateUTXOSet(getUTXOsByAddressesResponse.Entries, mempoolEntriesByAddresses.Entries)
|
||||
return s.updateUTXOSet(getUTXOsByAddressesResponse.Entries, mempoolEntriesByAddresses.Entries, refreshStart)
|
||||
}
|
||||
|
||||
func (s *server) forceSync() {
|
||||
// Technically if two callers check the `if` simultaneously they will both spawn a
|
||||
// goroutine, but we don't care about the small redundancy in such a rare case.
|
||||
if len(s.forceSyncChan) == 0 {
|
||||
go func() {
|
||||
s.forceSyncChan <- struct{}{}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (s *server) isSynced() bool {
|
||||
return s.nextSyncStartIndex > s.maxUsedIndex()
|
||||
return s.nextSyncStartIndex > s.maxUsedIndex() && s.firstSyncDone.Load()
|
||||
}
|
||||
|
||||
func (s *server) formatSyncStateReport() string {
|
||||
@@ -291,8 +328,11 @@ func (s *server) formatSyncStateReport() string {
|
||||
maxUsedIndex = s.nextSyncStartIndex
|
||||
}
|
||||
|
||||
return fmt.Sprintf("scanned %d out of %d addresses (%.2f%%)",
|
||||
s.nextSyncStartIndex, maxUsedIndex, float64(s.nextSyncStartIndex)*100.0/float64(maxUsedIndex))
|
||||
if s.nextSyncStartIndex < s.maxUsedIndex() {
|
||||
return fmt.Sprintf("scanned %d out of %d addresses (%.2f%%)",
|
||||
s.nextSyncStartIndex, maxUsedIndex, float64(s.nextSyncStartIndex)*100.0/float64(maxUsedIndex))
|
||||
}
|
||||
return "loading the wallet UTXO set"
|
||||
}
|
||||
|
||||
func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAddresses uint32) {
|
||||
@@ -311,7 +351,7 @@ func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAdd
|
||||
|
||||
if s.maxProcessedAddressesForLog >= s.maxUsedAddressesForLog {
|
||||
if !s.isLogFinalProgressLineShown {
|
||||
log.Infof("Wallet is synced, ready for queries")
|
||||
log.Infof("Finished scanning recent addresses")
|
||||
s.isLogFinalProgressLineShown = true
|
||||
}
|
||||
} else {
|
||||
|
||||
16
cmd/kaspawallet/daemon/server/version.go
Normal file
16
cmd/kaspawallet/daemon/server/version.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
)
|
||||
|
||||
func (s *server) GetVersion(_ context.Context, _ *pb.GetVersionRequest) (*pb.GetVersionResponse, error) {
|
||||
s.lock.RLock()
|
||||
defer s.lock.RUnlock()
|
||||
|
||||
return &pb.GetVersionResponse{
|
||||
Version: version.Version(),
|
||||
}, nil
|
||||
}
|
||||
26
cmd/kaspawallet/get_daemon_version.go
Normal file
26
cmd/kaspawallet/get_daemon_version.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
)
|
||||
|
||||
func getDaemonVersion(conf *getDaemonVersionConfig) error {
|
||||
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tearDown()
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
response, err := daemonClient.GetVersion(ctx, &pb.GetVersionRequest{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println(response.Version)
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,10 +1,11 @@
|
||||
package main
|
||||
|
||||
import "github.com/pkg/errors"
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func main() {
|
||||
subCmd, config := parseCommandLine()
|
||||
|
||||
var err error
|
||||
switch subCmd {
|
||||
case createSubCmd:
|
||||
@@ -31,6 +32,10 @@ func main() {
|
||||
err = startDaemon(config.(*startDaemonConfig))
|
||||
case sweepSubCmd:
|
||||
err = sweep(config.(*sweepConfig))
|
||||
case versionSubCmd:
|
||||
showVersion()
|
||||
case getDaemonVersionSubCmd:
|
||||
err = getDaemonVersion(config.(*getDaemonVersionConfig))
|
||||
default:
|
||||
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -35,7 +35,11 @@ func send(conf *sendConfig) error {
|
||||
|
||||
var sendAmountSompi uint64
|
||||
if !conf.IsSendAll {
|
||||
sendAmountSompi = uint64(conf.SendAmount * constants.SompiPerKaspa)
|
||||
sendAmountSompi, err = utils.KasToSompi(conf.SendAmount)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
createUnsignedTransactionsResponse, err :=
|
||||
@@ -71,23 +75,29 @@ func send(conf *sendConfig) error {
|
||||
signedTransactions[i] = signedTransaction
|
||||
}
|
||||
|
||||
if len(signedTransactions) > 1 {
|
||||
fmt.Printf("Broadcasting %d transactions\n", len(signedTransactions))
|
||||
}
|
||||
|
||||
fmt.Printf("Broadcasting %d transaction(s)\n", len(signedTransactions))
|
||||
// Since we waited for user input when getting the password, which could take unbound amount of time -
|
||||
// create a new context for broadcast, to reset the timeout.
|
||||
broadcastCtx, broadcastCancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer broadcastCancel()
|
||||
|
||||
response, err := daemonClient.Broadcast(broadcastCtx, &pb.BroadcastRequest{Transactions: signedTransactions})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Println("Transactions were sent successfully")
|
||||
fmt.Println("Transaction ID(s): ")
|
||||
for _, txID := range response.TxIDs {
|
||||
fmt.Printf("\t%s\n", txID)
|
||||
const chunkSize = 100 // To avoid sending a message bigger than the gRPC max message size, we split it to chunks
|
||||
for offset := 0; offset < len(signedTransactions); offset += chunkSize {
|
||||
end := len(signedTransactions)
|
||||
if offset+chunkSize <= len(signedTransactions) {
|
||||
end = offset + chunkSize
|
||||
}
|
||||
|
||||
chunk := signedTransactions[offset:end]
|
||||
response, err := daemonClient.Broadcast(broadcastCtx, &pb.BroadcastRequest{Transactions: chunk})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("Broadcasted %d transaction(s) (broadcasted %.2f%% of the transactions so far)\n", len(chunk), 100*float64(end)/float64(len(signedTransactions)))
|
||||
fmt.Println("Broadcasted Transaction ID(s): ")
|
||||
for _, txID := range response.TxIDs {
|
||||
fmt.Printf("\t%s\n", txID)
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Verbose {
|
||||
|
||||
@@ -3,5 +3,5 @@ package main
|
||||
import "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
||||
|
||||
func startDaemon(conf *startDaemonConfig) error {
|
||||
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout)
|
||||
return server.Start(conf.NetParams(), conf.Listen, conf.RPCServer, conf.KeysFile, conf.Profile, conf.Timeout, conf.MinFeePerTx)
|
||||
}
|
||||
|
||||
@@ -2,8 +2,13 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// FormatKas takes the amount of sompis as uint64, and returns amount of KAS with 8 decimal places
|
||||
@@ -14,3 +19,50 @@ func FormatKas(amount uint64) string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// KasToSompi takes in a string representation of the Kas value to convert to Sompi
|
||||
func KasToSompi(amount string) (uint64, error) {
|
||||
err := validateKASAmountFormat(amount)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// after validation, amount can only be either an int OR
|
||||
// a float with an int component and decimal places
|
||||
parts := strings.Split(amount, ".")
|
||||
amountStr := ""
|
||||
|
||||
if constants.SompiPerKaspa%10 != 0 {
|
||||
return 0, errors.Errorf("Unable to convert to sompi when SompiPerKaspa is not a multiple of 10")
|
||||
}
|
||||
|
||||
decimalPlaces := int(math.Log10(constants.SompiPerKaspa))
|
||||
decimalStr := ""
|
||||
|
||||
if len(parts) == 2 {
|
||||
decimalStr = parts[1]
|
||||
}
|
||||
|
||||
amountStr = fmt.Sprintf("%s%-*s", parts[0], decimalPlaces, decimalStr) // Padded with spaces at the end to fill for missing decimals: Sample "0.01234 "
|
||||
amountStr = strings.ReplaceAll(amountStr, " ", "0") // Make the spaces be 0s. Sample "0.012340000"
|
||||
|
||||
convertedAmount, err := strconv.ParseUint(amountStr, 10, 64)
|
||||
|
||||
return convertedAmount, err
|
||||
}
|
||||
|
||||
func validateKASAmountFormat(amount string) error {
|
||||
// Check whether it's an integer, or a float with max 8 digits
|
||||
match, err := regexp.MatchString("^([1-9]\\d{0,11}|0)(\\.\\d{0,8})?$", amount)
|
||||
|
||||
if !match {
|
||||
return errors.Errorf("Invalid amount")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
90
cmd/kaspawallet/utils/format_kas_test.go
Normal file
90
cmd/kaspawallet/utils/format_kas_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
// Takes in a string representation of the Kas value to convert to Sompi
|
||||
func TestKasToSompi(t *testing.T) {
|
||||
type testVector struct {
|
||||
originalAmount string
|
||||
convertedAmount uint64
|
||||
}
|
||||
|
||||
validCases := []testVector{
|
||||
{originalAmount: "0", convertedAmount: 0},
|
||||
{originalAmount: "1", convertedAmount: 100000000},
|
||||
{originalAmount: "33184.1489732", convertedAmount: 3318414897320},
|
||||
{originalAmount: "21.35808032", convertedAmount: 2135808032},
|
||||
{originalAmount: "184467440737.09551615", convertedAmount: 18446744073709551615},
|
||||
}
|
||||
|
||||
for _, currentTestVector := range validCases {
|
||||
convertedAmount, err := KasToSompi(currentTestVector.originalAmount)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if convertedAmount != currentTestVector.convertedAmount {
|
||||
t.Errorf("Expected %s, to convert to %d. Got: %d", currentTestVector.originalAmount, currentTestVector.convertedAmount, convertedAmount)
|
||||
}
|
||||
}
|
||||
|
||||
invalidCases := []string{
|
||||
"184467440737.09551616", // Bigger than max uint64
|
||||
"-1",
|
||||
"a",
|
||||
"",
|
||||
}
|
||||
|
||||
for _, currentTestVector := range invalidCases {
|
||||
_, err := KasToSompi(currentTestVector)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error but succeeded validation for test case %s", currentTestVector)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateAmountFormat(t *testing.T) {
|
||||
validCases := []string{
|
||||
"0",
|
||||
"1",
|
||||
"1.0",
|
||||
"0.1",
|
||||
"0.12345678",
|
||||
"111111111111.11111111", // 12 digits to the left of decimal, 8 digits to the right
|
||||
"184467440737.09551615", // Maximum input that can be represented in sompi later
|
||||
"184467440737.09551616", // Cannot be represented in sompi, but we'll acccept for "correct format"
|
||||
"999999999999.99999999", // Cannot be represented in sompi, but we'll acccept for "correct format"
|
||||
}
|
||||
|
||||
for _, testCase := range validCases {
|
||||
err := validateKASAmountFormat(testCase)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
invalidCases := []string{
|
||||
"",
|
||||
"a",
|
||||
"-1",
|
||||
"0.123456789", // 9 decimal digits
|
||||
".1", // decimal but no integer component
|
||||
"0a", // Extra character
|
||||
"0000000000000", // 13 zeros
|
||||
"012", // Int padded with zero
|
||||
"00.1", // Decimal padded with zeros
|
||||
"111111111111111111111", // all digits
|
||||
"111111111111A11111111", // non-period/non-digit where decimal would be
|
||||
"000000000000.00000000", // all zeros
|
||||
"kaspa", // all text
|
||||
}
|
||||
|
||||
for _, testCase := range invalidCases {
|
||||
err := validateKASAmountFormat(testCase)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error but succeeded validation for test case %s", testCase)
|
||||
}
|
||||
}
|
||||
}
|
||||
15
cmd/kaspawallet/version.go
Normal file
15
cmd/kaspawallet/version.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/version"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func showVersion() {
|
||||
appName := filepath.Base(os.Args[0])
|
||||
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||
fmt.Println(appName, "version", version.Version())
|
||||
}
|
||||
@@ -43,6 +43,9 @@ type TestConsensus interface {
|
||||
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
|
||||
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||
|
||||
AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
|
||||
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||
|
||||
AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||
|
||||
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
||||
|
||||
@@ -131,11 +131,12 @@ func (bb *blockBuilder) validateTransactions(stagingArea *model.StagingArea,
|
||||
for _, transaction := range transactions {
|
||||
err := bb.validateTransaction(stagingArea, transaction)
|
||||
if err != nil {
|
||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
||||
ruleError := ruleerrors.RuleError{}
|
||||
if !errors.As(err, &ruleError) {
|
||||
return err
|
||||
}
|
||||
invalidTransactions = append(invalidTransactions,
|
||||
ruleerrors.InvalidTransaction{Transaction: transaction, Error: err})
|
||||
ruleerrors.InvalidTransaction{Transaction: transaction, Error: &ruleError})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -315,7 +315,7 @@ func NewErrMissingParents(missingParentHashes []*externalapi.DomainHash) error {
|
||||
// InvalidTransaction is a struct containing an invalid transaction, and the error explaining why it's invalid.
|
||||
type InvalidTransaction struct {
|
||||
Transaction *externalapi.DomainTransaction
|
||||
Error error
|
||||
Error *RuleError
|
||||
}
|
||||
|
||||
func (invalid InvalidTransaction) String() string {
|
||||
|
||||
@@ -3,9 +3,10 @@ package ruleerrors
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"testing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
)
|
||||
|
||||
@@ -49,7 +50,7 @@ func TestNewErrMissingTxOut(t *testing.T) {
|
||||
func TestNewErrInvalidTransactionsInNewBlock(t *testing.T) {
|
||||
tx := &externalapi.DomainTransaction{Fee: 1337}
|
||||
txID := consensushashing.TransactionID(tx)
|
||||
outer := NewErrInvalidTransactionsInNewBlock([]InvalidTransaction{{tx, ErrNoTxInputs}})
|
||||
outer := NewErrInvalidTransactionsInNewBlock([]InvalidTransaction{{tx, &ErrNoTxInputs}})
|
||||
//TODO: Implement Stringer for `DomainTransaction`
|
||||
expectedOuterErr := fmt.Sprintf("ErrInvalidTransactionsInNewBlock: [(%s: ErrNoTxInputs)]", txID)
|
||||
inner := &ErrInvalidTransactionsInNewBlock{}
|
||||
@@ -60,7 +61,7 @@ func TestNewErrInvalidTransactionsInNewBlock(t *testing.T) {
|
||||
if len(inner.InvalidTransactions) != 1 {
|
||||
t.Fatalf("TestNewErrInvalidTransactionsInNewBlock: Expected len(inner.MissingOutpoints) 1, found: %d", len(inner.InvalidTransactions))
|
||||
}
|
||||
if inner.InvalidTransactions[0].Error != ErrNoTxInputs {
|
||||
if *inner.InvalidTransactions[0].Error != ErrNoTxInputs {
|
||||
t.Fatalf("TestNewErrInvalidTransactionsInNewBlock: Expected ErrNoTxInputs. found: %v", inner.InvalidTransactions[0].Error)
|
||||
}
|
||||
if inner.InvalidTransactions[0].Transaction.Fee != 1337 {
|
||||
|
||||
@@ -69,6 +69,17 @@ func (tc *testConsensus) AddBlock(parentHashes []*externalapi.DomainHash, coinba
|
||||
return consensushashing.BlockHash(block), virtualChangeSet, nil
|
||||
}
|
||||
|
||||
func (tc *testConsensus) AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
|
||||
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error) {
|
||||
|
||||
tips, err := tc.Tips()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return tc.AddBlock(tips, coinbaseData, transactions)
|
||||
}
|
||||
|
||||
func (tc *testConsensus) AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
||||
*externalapi.VirtualChangeSet, error) {
|
||||
|
||||
|
||||
@@ -387,7 +387,7 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(pops) != 20 {
|
||||
if len(pops) != 19 {
|
||||
return nil, nil
|
||||
}
|
||||
isAtomicSwap := pops[0].opcode.value == OpIf &&
|
||||
@@ -403,13 +403,12 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
||||
pops[10].opcode.value == OpElse &&
|
||||
canonicalPush(pops[11]) &&
|
||||
pops[12].opcode.value == OpCheckLockTimeVerify &&
|
||||
pops[13].opcode.value == OpDrop &&
|
||||
pops[14].opcode.value == OpDup &&
|
||||
pops[15].opcode.value == OpBlake2b &&
|
||||
pops[16].opcode.value == OpData32 &&
|
||||
pops[17].opcode.value == OpEndIf &&
|
||||
pops[18].opcode.value == OpEqualVerify &&
|
||||
pops[19].opcode.value == OpCheckSig
|
||||
pops[13].opcode.value == OpDup &&
|
||||
pops[14].opcode.value == OpBlake2b &&
|
||||
pops[15].opcode.value == OpData32 &&
|
||||
pops[16].opcode.value == OpEndIf &&
|
||||
pops[17].opcode.value == OpEqualVerify &&
|
||||
pops[18].opcode.value == OpCheckSig
|
||||
if !isAtomicSwap {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -417,9 +416,9 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
||||
pushes := new(AtomicSwapDataPushes)
|
||||
copy(pushes.SecretHash[:], pops[5].data)
|
||||
copy(pushes.RecipientBlake2b[:], pops[9].data)
|
||||
copy(pushes.RefundBlake2b[:], pops[16].data)
|
||||
copy(pushes.RefundBlake2b[:], pops[15].data)
|
||||
if pops[2].data != nil {
|
||||
locktime, err := makeScriptNum(pops[2].data, 5)
|
||||
locktime, err := makeScriptNum(pops[2].data, 8)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
@@ -430,7 +429,7 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
||||
return nil, nil
|
||||
}
|
||||
if pops[11].data != nil {
|
||||
locktime, err := makeScriptNum(pops[11].data, 5)
|
||||
locktime, err := makeScriptNum(pops[11].data, 8)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
@@ -232,6 +232,8 @@ var MainnetParams = Params{
|
||||
"seeder4.kaspad.net",
|
||||
// This DNS seeder is run by Tim
|
||||
"kaspadns.kaspacalc.net",
|
||||
// This DNS seeder is run by supertypo
|
||||
"n-mainnet.kaspa.ws",
|
||||
},
|
||||
|
||||
// DAG parameters
|
||||
|
||||
@@ -147,16 +147,12 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate(
|
||||
invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
|
||||
if errors.As(err, &invalidTxsErr) {
|
||||
log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate")
|
||||
invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions))
|
||||
for _, tx := range invalidTxsErr.InvalidTransactions {
|
||||
invalidTxs = append(invalidTxs, tx.Transaction)
|
||||
}
|
||||
err = btb.mempool.RemoveTransactions(invalidTxs, true)
|
||||
err = btb.mempool.RemoveInvalidTransactions(&invalidTxsErr)
|
||||
if err != nil {
|
||||
// mempool.RemoveTransactions might return errors in situations that are perfectly fine in this context.
|
||||
// mempool.RemoveInvalidTransactions might return errors in situations that are perfectly fine in this context.
|
||||
// TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`:
|
||||
// https://github.com/kaspanet/kaspad/issues/1553
|
||||
log.Criticalf("Error from mempool.RemoveTransactions: %+v", err)
|
||||
log.Criticalf("Error from mempool.RemoveInvalidTransactions: %+v", err)
|
||||
}
|
||||
// We can call this recursively without worry because this should almost never happen
|
||||
return btb.BuildBlockTemplate(coinbaseData)
|
||||
|
||||
@@ -51,6 +51,7 @@ const (
|
||||
RejectDifficulty RejectCode = 0x44
|
||||
RejectImmatureSpend RejectCode = 0x45
|
||||
RejectBadOrphan RejectCode = 0x64
|
||||
RejectSpamTx RejectCode = 0x65
|
||||
)
|
||||
|
||||
// Map of reject codes back strings for pretty printing.
|
||||
|
||||
@@ -3,6 +3,11 @@ package mempool
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensusreference"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
@@ -141,7 +146,57 @@ func (mp *mempool) BlockCandidateTransactions() []*externalapi.DomainTransaction
|
||||
mp.mtx.RLock()
|
||||
defer mp.mtx.RUnlock()
|
||||
|
||||
return mp.transactionsPool.allReadyTransactions()
|
||||
readyTxs := mp.transactionsPool.allReadyTransactions()
|
||||
var candidateTxs []*externalapi.DomainTransaction
|
||||
var spamTx *externalapi.DomainTransaction
|
||||
var spamTxNewestUTXODaaScore uint64
|
||||
for _, tx := range readyTxs {
|
||||
if len(tx.Outputs) > len(tx.Inputs) {
|
||||
hasCoinbaseInput := false
|
||||
for _, input := range tx.Inputs {
|
||||
if input.UTXOEntry.IsCoinbase() {
|
||||
hasCoinbaseInput = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
numExtraOuts := len(tx.Outputs) - len(tx.Inputs)
|
||||
if !hasCoinbaseInput && numExtraOuts > 2 && tx.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa {
|
||||
log.Debugf("Filtered spam tx %s", consensushashing.TransactionID(tx))
|
||||
continue
|
||||
}
|
||||
|
||||
if hasCoinbaseInput || tx.Fee > uint64(numExtraOuts)*constants.SompiPerKaspa {
|
||||
candidateTxs = append(candidateTxs, tx)
|
||||
} else {
|
||||
txNewestUTXODaaScore := tx.Inputs[0].UTXOEntry.BlockDAAScore()
|
||||
for _, input := range tx.Inputs {
|
||||
if input.UTXOEntry.BlockDAAScore() > txNewestUTXODaaScore {
|
||||
txNewestUTXODaaScore = input.UTXOEntry.BlockDAAScore()
|
||||
}
|
||||
}
|
||||
|
||||
if spamTx != nil {
|
||||
if txNewestUTXODaaScore < spamTxNewestUTXODaaScore {
|
||||
spamTx = tx
|
||||
spamTxNewestUTXODaaScore = txNewestUTXODaaScore
|
||||
}
|
||||
} else {
|
||||
spamTx = tx
|
||||
spamTxNewestUTXODaaScore = txNewestUTXODaaScore
|
||||
}
|
||||
}
|
||||
} else {
|
||||
candidateTxs = append(candidateTxs, tx)
|
||||
}
|
||||
}
|
||||
|
||||
if spamTx != nil {
|
||||
log.Debugf("Adding spam tx candidate %s", consensushashing.TransactionID(spamTx))
|
||||
candidateTxs = append(candidateTxs, spamTx)
|
||||
}
|
||||
|
||||
return candidateTxs
|
||||
}
|
||||
|
||||
func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) {
|
||||
@@ -151,11 +206,19 @@ func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*ex
|
||||
return mp.revalidateHighPriorityTransactions()
|
||||
}
|
||||
|
||||
func (mp *mempool) RemoveTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
|
||||
func (mp *mempool) RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error {
|
||||
mp.mtx.Lock()
|
||||
defer mp.mtx.Unlock()
|
||||
|
||||
return mp.removeTransactions(transactions, removeRedeemers)
|
||||
for _, tx := range err.InvalidTransactions {
|
||||
removeRedeemers := !errors.As(tx.Error, &ruleerrors.ErrMissingTxOut{})
|
||||
err := mp.removeTransaction(consensushashing.TransactionID(tx.Transaction), removeRedeemers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
||||
|
||||
@@ -2,20 +2,9 @@ package mempool
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
||||
)
|
||||
|
||||
func (mp *mempool) removeTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
|
||||
for _, transaction := range transactions {
|
||||
err := mp.removeTransaction(consensushashing.TransactionID(transaction), removeRedeemers)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
||||
if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok {
|
||||
return mp.orphansPool.removeOrphan(transactionID, true)
|
||||
|
||||
@@ -7,20 +7,85 @@ import (
|
||||
)
|
||||
|
||||
func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTransaction, error) {
|
||||
type txNode struct {
|
||||
children map[externalapi.DomainTransactionID]struct{}
|
||||
nonVisitedParents int
|
||||
tx *model.MempoolTransaction
|
||||
visited bool
|
||||
}
|
||||
|
||||
onEnd := logger.LogAndMeasureExecutionTime(log, "revalidateHighPriorityTransactions")
|
||||
defer onEnd()
|
||||
|
||||
// We revalidate transactions in topological order in case there are dependencies between them
|
||||
|
||||
// Naturally transactions point to their dependencies, but since we want to start processing the dependencies
|
||||
// first, we build the opposite DAG. We initially fill `queue` with transactions with no dependencies.
|
||||
txDAG := make(map[externalapi.DomainTransactionID]*txNode)
|
||||
|
||||
maybeAddNode := func(txID externalapi.DomainTransactionID) *txNode {
|
||||
if node, ok := txDAG[txID]; ok {
|
||||
return node
|
||||
}
|
||||
|
||||
node := &txNode{
|
||||
children: make(map[externalapi.DomainTransactionID]struct{}),
|
||||
nonVisitedParents: 0,
|
||||
tx: mp.transactionsPool.highPriorityTransactions[txID],
|
||||
}
|
||||
txDAG[txID] = node
|
||||
return node
|
||||
}
|
||||
|
||||
queue := make([]*txNode, 0, len(mp.transactionsPool.highPriorityTransactions))
|
||||
for id, transaction := range mp.transactionsPool.highPriorityTransactions {
|
||||
node := maybeAddNode(id)
|
||||
|
||||
parents := make(map[externalapi.DomainTransactionID]struct{})
|
||||
for _, input := range transaction.Transaction().Inputs {
|
||||
if _, ok := mp.transactionsPool.highPriorityTransactions[input.PreviousOutpoint.TransactionID]; !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
parents[input.PreviousOutpoint.TransactionID] = struct{}{} // To avoid duplicate parents, we first add it to a set and then count it
|
||||
maybeAddNode(input.PreviousOutpoint.TransactionID).children[id] = struct{}{}
|
||||
}
|
||||
node.nonVisitedParents = len(parents)
|
||||
|
||||
if node.nonVisitedParents == 0 {
|
||||
queue = append(queue, node)
|
||||
}
|
||||
}
|
||||
|
||||
validTransactions := []*externalapi.DomainTransaction{}
|
||||
for _, transaction := range mp.transactionsPool.highPriorityTransactions {
|
||||
|
||||
// Now we iterate the DAG in topological order using BFS
|
||||
for len(queue) > 0 {
|
||||
var node *txNode
|
||||
node, queue = queue[0], queue[1:]
|
||||
|
||||
if node.visited {
|
||||
continue
|
||||
}
|
||||
node.visited = true
|
||||
|
||||
transaction := node.tx
|
||||
isValid, err := mp.revalidateTransaction(transaction)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !isValid {
|
||||
continue
|
||||
|
||||
for child := range node.children {
|
||||
childNode := txDAG[child]
|
||||
childNode.nonVisitedParents--
|
||||
if childNode.nonVisitedParents == 0 {
|
||||
queue = append(queue, txDAG[child])
|
||||
}
|
||||
}
|
||||
|
||||
validTransactions = append(validTransactions, transaction.Transaction().Clone())
|
||||
if isValid {
|
||||
validTransactions = append(validTransactions, transaction.Transaction().Clone())
|
||||
}
|
||||
}
|
||||
|
||||
return validTransactions, nil
|
||||
@@ -35,7 +100,7 @@ func (mp *mempool) revalidateTransaction(transaction *model.MempoolTransaction)
|
||||
}
|
||||
if len(missingParents) > 0 {
|
||||
log.Debugf("Removing transaction %s, it failed revalidation", transaction.TransactionID())
|
||||
err := mp.removeTransaction(transaction.TransactionID(), true)
|
||||
err := mp.removeTransaction(transaction.TransactionID(), false)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
|
||||
@@ -2,6 +2,7 @@ package mempool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||
@@ -44,6 +45,20 @@ func (mp *mempool) validateTransactionInIsolation(transaction *externalapi.Domai
|
||||
}
|
||||
|
||||
func (mp *mempool) validateTransactionInContext(transaction *externalapi.DomainTransaction) error {
|
||||
hasCoinbaseInput := false
|
||||
for _, input := range transaction.Inputs {
|
||||
if input.UTXOEntry.IsCoinbase() {
|
||||
hasCoinbaseInput = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
numExtraOuts := len(transaction.Outputs) - len(transaction.Inputs)
|
||||
if !hasCoinbaseInput && numExtraOuts > 2 && transaction.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa {
|
||||
log.Warnf("Rejected spam tx %s from mempool (%d outputs)", consensushashing.TransactionID(transaction), len(transaction.Outputs))
|
||||
return transactionRuleError(RejectSpamTx, fmt.Sprintf("Rejected spam tx %s from mempool", consensushashing.TransactionID(transaction)))
|
||||
}
|
||||
|
||||
if !mp.config.AcceptNonStandard {
|
||||
err := mp.checkTransactionStandardInContext(transaction)
|
||||
if err != nil {
|
||||
|
||||
@@ -577,6 +577,72 @@ func TestRevalidateHighPriorityTransactions(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestRevalidateHighPriorityTransactionsWithChain(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
consensusConfig.BlockCoinbaseMaturity = 0
|
||||
factory := consensus.NewFactory()
|
||||
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestRevalidateHighPriorityTransactions")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed setting up TestConsensus: %+v", err)
|
||||
}
|
||||
defer teardown(false)
|
||||
|
||||
miningFactory := miningmanager.NewFactory()
|
||||
mempoolConfig := mempool.DefaultConfig(&consensusConfig.Params)
|
||||
tcAsConsensus := tc.(externalapi.Consensus)
|
||||
tcAsConsensusPointer := &tcAsConsensus
|
||||
consensusReference := consensusreference.NewConsensusReference(&tcAsConsensusPointer)
|
||||
miningManager := miningFactory.NewMiningManager(consensusReference, &consensusConfig.Params, mempoolConfig)
|
||||
|
||||
const chainSize = 10
|
||||
chain, err := createTxChain(tc, chainSize)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = miningManager.ValidateAndInsertTransaction(chain[0], true, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
blockHash, _, err := tc.AddBlockOnTips(nil, []*externalapi.DomainTransaction{chain[0].Clone()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
block, _, err := tc.GetBlock(blockHash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
_, err = miningManager.HandleNewBlockTransactions(block.Transactions)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, transaction := range chain[1:] {
|
||||
_, err = miningManager.ValidateAndInsertTransaction(transaction, true, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
_, _, err = tc.AddBlockOnTips(nil, []*externalapi.DomainTransaction{chain[1].Clone()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
revalidated, err := miningManager.RevalidateHighPriorityTransactions()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(revalidated) != chainSize-2 {
|
||||
t.Fatalf("expected %d transactions to revalidate but instead only %d revalidated", chainSize-2, len(revalidated))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// TestModifyBlockTemplate verifies that modifying a block template changes coinbase data correctly.
|
||||
func TestModifyBlockTemplate(t *testing.T) {
|
||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||
@@ -904,40 +970,58 @@ func createArraysOfParentAndChildrenTransactions(tc testapi.TestConsensus) ([]*e
|
||||
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (txParent *externalapi.DomainTransaction,
|
||||
txChild *externalapi.DomainTransaction, err error) {
|
||||
|
||||
chain, err := createTxChain(tc, 2)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
return chain[0], chain[1], nil
|
||||
}
|
||||
|
||||
func createTxChain(tc testapi.TestConsensus, numTxs int) ([]*externalapi.DomainTransaction, error) {
|
||||
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
|
||||
tips, err := tc.Tips()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _, err = tc.AddBlock(tips, nil, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrapf(err, "AddBlock: %v", err)
|
||||
return nil, errors.Wrapf(err, "AddBlock: %v", err)
|
||||
}
|
||||
|
||||
tips, err = tc.Tips()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
|
||||
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "AddBlock: ")
|
||||
return nil, errors.Wrap(err, "AddBlock: ")
|
||||
}
|
||||
fundingBlockForParent, _, err := tc.GetBlock(fundingBlockHashForParent)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "GetBlock: ")
|
||||
return nil, errors.Wrap(err, "GetBlock: ")
|
||||
}
|
||||
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||
txParent, err = testutils.CreateTransaction(fundingTransactionForParent, 1000)
|
||||
|
||||
transactions := make([]*externalapi.DomainTransaction, numTxs)
|
||||
transactions[0], err = testutils.CreateTransaction(fundingTransactionForParent, 1000)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
return nil, err
|
||||
}
|
||||
txChild, err = testutils.CreateTransaction(txParent, 1000)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
||||
txParent := transactions[0]
|
||||
for i := 1; i < numTxs; i++ {
|
||||
transactions[i], err = testutils.CreateTransaction(txParent, 1000)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
txParent = transactions[i]
|
||||
}
|
||||
return txParent, txChild, nil
|
||||
|
||||
return transactions, nil
|
||||
}
|
||||
|
||||
func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
||||
|
||||
@@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||
)
|
||||
|
||||
// Mempool maintains a set of known transactions that
|
||||
@@ -11,7 +12,7 @@ type Mempool interface {
|
||||
BlockCandidateTransactions() []*externalapi.DomainTransaction
|
||||
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
|
||||
acceptedTransactions []*externalapi.DomainTransaction, err error)
|
||||
RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error
|
||||
RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error
|
||||
GetTransaction(
|
||||
transactionID *externalapi.DomainTransactionID,
|
||||
includeTransactionPool bool,
|
||||
|
||||
@@ -52,6 +52,8 @@ func (ui *UTXOIndex) Reset() error {
|
||||
ui.mutex.Lock()
|
||||
defer ui.mutex.Unlock()
|
||||
|
||||
log.Infof("Starting UTXO index reset")
|
||||
|
||||
err := ui.store.deleteAll()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -88,7 +90,13 @@ func (ui *UTXOIndex) Reset() error {
|
||||
}
|
||||
|
||||
// This has to be done last to mark that the reset went smoothly and no reset has to be called next time.
|
||||
return ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
|
||||
err = ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
log.Infof("Finished UTXO index reset")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ui *UTXOIndex) isSynced() (bool, error) {
|
||||
|
||||
@@ -1,23 +1,44 @@
|
||||
package rpcclient
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/app/appmessage"
|
||||
)
|
||||
|
||||
// SubmitTransaction sends an RPC request respective to the function's name and returns the RPC server's response
|
||||
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction, allowOrphan bool) (*appmessage.SubmitTransactionResponseMessage, error) {
|
||||
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction, transactionID string, allowOrphan bool) (*appmessage.SubmitTransactionResponseMessage, error) {
|
||||
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionRequestMessage(transaction, allowOrphan))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
response, err := c.route(appmessage.CmdSubmitTransactionResponseMessage).DequeueWithTimeout(c.timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
submitTransactionResponse := response.(*appmessage.SubmitTransactionResponseMessage)
|
||||
if submitTransactionResponse.Error != nil {
|
||||
return nil, c.convertRPCError(submitTransactionResponse.Error)
|
||||
}
|
||||
for {
|
||||
response, err := c.route(appmessage.CmdSubmitTransactionResponseMessage).DequeueWithTimeout(c.timeout)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
submitTransactionResponse := response.(*appmessage.SubmitTransactionResponseMessage)
|
||||
// Match the response to the expected ID. If they are different it means we got an old response which we
|
||||
// previously timed-out on, so we log and continue waiting for the correct current response.
|
||||
if submitTransactionResponse.TransactionID != transactionID {
|
||||
if submitTransactionResponse.Error != nil {
|
||||
// A non-updated Kaspad might return an empty ID in the case of error, so in
|
||||
// such a case we fallback to checking if the error contains the expected ID
|
||||
if submitTransactionResponse.TransactionID != "" || !strings.Contains(submitTransactionResponse.Error.Message, transactionID) {
|
||||
log.Warnf("SubmitTransaction: received an error response for previous request: %s", submitTransactionResponse.Error)
|
||||
continue
|
||||
}
|
||||
|
||||
return submitTransactionResponse, nil
|
||||
} else {
|
||||
log.Warnf("SubmitTransaction: received a successful response for previous request with ID %s",
|
||||
submitTransactionResponse.TransactionID)
|
||||
continue
|
||||
}
|
||||
}
|
||||
if submitTransactionResponse.Error != nil {
|
||||
return nil, c.convertRPCError(submitTransactionResponse.Error)
|
||||
}
|
||||
|
||||
return submitTransactionResponse, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -86,7 +86,7 @@ func submitAnAmountOfTransactionsToTheMempool(t *testing.T, rpcClient *rpcclient
|
||||
|
||||
for i, transaction := range transactions {
|
||||
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
|
||||
_, err := rpcClient.SubmitTransaction(rpcTransaction, false)
|
||||
_, err := rpcClient.SubmitTransaction(rpcTransaction, consensushashing.TransactionID(transaction).String(), false)
|
||||
if err != nil {
|
||||
if ignoreOrphanRejects && strings.Contains(err.Error(), "orphan") {
|
||||
continue
|
||||
|
||||
@@ -53,7 +53,7 @@ func TestTxRelay(t *testing.T) {
|
||||
msgTx := generateTx(t, secondBlock.Transactions[transactionhelper.CoinbaseTransactionIndex], payer, payee)
|
||||
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
||||
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
|
||||
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction, false)
|
||||
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction, consensushashing.TransactionID(domainTransaction).String(), false)
|
||||
if err != nil {
|
||||
t.Fatalf("Error submitting transaction: %+v", err)
|
||||
}
|
||||
|
||||
@@ -88,8 +88,8 @@ func TestUTXOIndex(t *testing.T) {
|
||||
// Submit a few transactions that spends some UTXOs
|
||||
const transactionAmountToSpend = 5
|
||||
for i := 0; i < transactionAmountToSpend; i++ {
|
||||
rpcTransaction := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
|
||||
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction, false)
|
||||
rpcTransaction, transactionID := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
|
||||
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction, transactionID, false)
|
||||
if err != nil {
|
||||
t.Fatalf("Error submitting transaction: %s", err)
|
||||
}
|
||||
@@ -171,7 +171,7 @@ func TestUTXOIndex(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) *appmessage.RPCTransaction {
|
||||
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) (*appmessage.RPCTransaction, string) {
|
||||
transactionIDBytes, err := hex.DecodeString(entry.Outpoint.TransactionID)
|
||||
if err != nil {
|
||||
t.Fatalf("Error decoding transaction ID: %s", err)
|
||||
@@ -224,5 +224,5 @@ func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAdd
|
||||
msgTx.TxIn[0].SignatureScript = signatureScript
|
||||
|
||||
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
||||
return appmessage.DomainTransactionToRPCTransaction(domainTransaction)
|
||||
return appmessage.DomainTransactionToRPCTransaction(domainTransaction), consensushashing.TransactionID(domainTransaction).String()
|
||||
}
|
||||
|
||||
@@ -105,23 +105,18 @@ func TestAddresses(t *testing.T) {
|
||||
// ECDSA P2PK tests.
|
||||
{
|
||||
name: "mainnet ecdsa p2pk",
|
||||
addr: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
||||
encoded: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
||||
addr: "kaspa:qyp0r5mcq4rd5grj3652ra09u5dcgwqq9ntuswp247nama5quyj40eq03sc2dkx",
|
||||
encoded: "kaspa:qyp0r5mcq4rd5grj3652ra09u5dcgwqq9ntuswp247nama5quyj40eq03sc2dkx",
|
||||
valid: true,
|
||||
result: util.TstAddressPubKeyECDSA(
|
||||
util.Bech32PrefixKaspa,
|
||||
[util.PublicKeySizeECDSA]byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c, 0xaa,
|
||||
0x02, 0xf1, 0xd3, 0x78, 0x05, 0x46, 0xda, 0x20, 0x72, 0x8e, 0xa8, 0xa1, 0xf5, 0xe5, 0xe5, 0x1b, 0x84, 0x38, 0x00, 0x2c, 0xd7, 0xc8, 0x38, 0x2a, 0xaf, 0xa7, 0xdd, 0xf6, 0x80, 0xe1, 0x25, 0x57, 0xe4,
|
||||
}),
|
||||
f: func() (util.Address, error) {
|
||||
publicKey := []byte{
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
||||
0xe3, 0x4c, 0xaa}
|
||||
0x02, 0xf1, 0xd3, 0x78, 0x05, 0x46, 0xda, 0x20, 0x72, 0x8e, 0xa8, 0xa1, 0xf5, 0xe5, 0xe5, 0x1b, 0x84, 0x38, 0x00, 0x2c, 0xd7, 0xc8, 0x38, 0x2a, 0xaf, 0xa7, 0xdd, 0xf6, 0x80, 0xe1, 0x25, 0x57, 0xe4,
|
||||
}
|
||||
return util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
||||
},
|
||||
passedPrefix: util.Bech32PrefixUnknown,
|
||||
|
||||
@@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
|
||||
const (
|
||||
appMajor uint = 0
|
||||
appMinor uint = 12
|
||||
appPatch uint = 13
|
||||
appPatch uint = 17
|
||||
)
|
||||
|
||||
// appBuild is defined as a variable so it can be overridden during the build
|
||||
|
||||
Reference in New Issue
Block a user