Compare commits

...

9 Commits

Author SHA1 Message Date
Ori Newman
03cc7dfc19 Update version to v0.12.20 2025-03-18 11:06:56 +02:00
Toni Lukkaroinen
ed745a9acb
Fix block verbosedata population… (#2275)
* Fix block verbosedata population from skipping transaction verbosedata population when BlockInfo errorenously reports block as Header only, but domainBlock is still fount with GetBlockEvenIfHeaderOnly.

* Update app/rpc/rpccontext/verbosedata.go

---------

Co-authored-by: Toni Lukkaroinen <toni.lukkaroinen@hoosat.fi>
Co-authored-by: Michael Sutton <mikisiton2@gmail.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
2025-03-18 11:03:47 +02:00
Ori Newman
c23c1d141c Update gh actions 2025-03-18 10:59:21 +02:00
Ori Newman
352d261fd6
Fix serializeTransaction (#2302) 2025-02-23 15:03:16 +02:00
Veronica
43b9523919
Adding support for mass. (#2301)
* Adding support for mass.

* update grpc v1.69.2

* Upgrade protos using latest protoc

* Use MassCommitment instead of Mass

* Fix DomainTransaction clone, equal and tests

---------

Co-authored-by: veronica <jimjouny@gmail.com>
Co-authored-by: Ori Newman <orinewman1@gmail.com>
2025-01-21 10:57:28 +02:00
Michael Sutton
6085d1fc84
bump version to 0.12.19 (#2295) 2024-10-22 13:09:31 +03:00
Ori Newman
1e9ddc42d0
Add fee estimation to wallet (#2291)
* Add fee estimation to wallet

* Add fee rate to kaspawallet parse

* Update go version

* Get rid of golint

* Add RBF support to wallet

* Fix bump_fee UTXO lookup and fix wrong change address

* impl storage mass as per KIP9

* Use CalculateTransactionOverallMass where needed

* Some fixes

* Minor typos

* Fix test

* update version

* BroadcastRBF -> BroadcastReplacement

* rc3

* align proto files to only use camel case (fixed on RK as well)

* Rename to FeePolicy and add MaxFee option + todo

* apply max fee constrains

* increase minChangeTarget to 10kas

* fmt

* Some fixes

* fix description: maximum -> minimum

* put min feerate check in the correct location

* Fix calculateFeeLimits nil handling

* Add validations to CLI flags

* Change to rc6

* Add checkTransactionFeeRate

* Add failed broadcast transactions on send error`

* Fix estimateFee change value

* Estimate fee correctly for --send-all

* On estimateFee always assume that the recipient has ECDSA address

* remove patch version

---------

Co-authored-by: Michael Sutton <msutton@cs.huji.ac.il>
2024-10-22 12:34:54 +03:00
Ori Newman
48a142e12f
Add deprecated message (#2282)
* Add deprecated message

* fix readme
2024-08-15 08:15:30 +03:00
Michael Sutton
86b89065cf
KIP9 basic wallet compatibility (#2276)
* introduce min change target

* clarify wallet help messages for from-address and send-all
2024-05-09 18:53:21 +03:00
68 changed files with 9765 additions and 6923 deletions

View File

@ -1,7 +1,7 @@
name: Build and upload assets
on:
release:
types: [ published ]
types: [published]
jobs:
build:
@ -9,7 +9,7 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, windows-latest, macos-latest ]
os: [ubuntu-latest, windows-latest, macos-latest]
name: Building, ${{ matrix.os }}
steps:
- name: Fix CRLF on Windows
@ -17,11 +17,10 @@ jobs:
run: git config --global core.autocrlf false
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.21
@ -31,7 +30,7 @@ jobs:
# `-tags netgo,osusergo` means use pure go replacements for "os/user" and "net"
# `-s -w` strips the binary to produce smaller size binaries
run: |
go build -v -ldflags="-s -w -extldflags=-static" -tags netgo,osusergo -o ./bin/ . ./cmd/...
go build -v -ldflags="-s -w -extldflags=-static" -tags netgo,osusergo -o ./bin/ ./cmd/...
archive="bin/kaspad-${{ github.event.release.tag_name }}-linux.zip"
asset_name="kaspad-${{ github.event.release.tag_name }}-linux.zip"
zip -r "${archive}" ./bin/*
@ -42,7 +41,7 @@ jobs:
if: runner.os == 'Windows'
shell: bash
run: |
go build -v -ldflags="-s -w" -o bin/ . ./cmd/...
go build -v -ldflags="-s -w" -o bin/ ./cmd/...
archive="bin/kaspad-${{ github.event.release.tag_name }}-win64.zip"
asset_name="kaspad-${{ github.event.release.tag_name }}-win64.zip"
powershell "Compress-Archive bin/* \"${archive}\""
@ -52,14 +51,13 @@ jobs:
- name: Build on MacOS
if: runner.os == 'macOS'
run: |
go build -v -ldflags="-s -w" -o ./bin/ . ./cmd/...
go build -v -ldflags="-s -w" -o ./bin/ ./cmd/...
archive="bin/kaspad-${{ github.event.release.tag_name }}-osx.zip"
asset_name="kaspad-${{ github.event.release.tag_name }}-osx.zip"
zip -r "${archive}" ./bin/*
echo "archive=${archive}" >> $GITHUB_ENV
echo "asset_name=${asset_name}" >> $GITHUB_ENV
- name: Upload release asset
uses: actions/upload-release-asset@v1
env:

View File

@ -11,18 +11,18 @@ jobs:
strategy:
fail-fast: false
matrix:
branch: [ master, latest ]
branch: [master, latest]
name: Race detection on ${{ matrix.branch }}
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: 1.23
- name: Set scheduled branch name
shell: bash

View File

@ -8,22 +8,20 @@ on:
types: [opened, synchronize, edited]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ ubuntu-latest, macos-latest ]
os: [ubuntu-latest, macos-latest]
name: Tests, ${{ matrix.os }}
steps:
- name: Fix CRLF on Windows
if: runner.os == 'Windows'
run: git config --global core.autocrlf false
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
# Increase the pagefile size on Windows to aviod running out of memory
- name: Increase pagefile size on Windows
@ -31,14 +29,13 @@ jobs:
run: powershell -command .github\workflows\SetPageFileSize.ps1
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: 1.23
# Source: https://github.com/actions/cache/blob/main/examples.md#go---modules
- name: Go Cache
uses: actions/cache@v2
uses: actions/cache@v4
with:
path: ~/go/pkg/mod
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
@ -49,19 +46,17 @@ jobs:
shell: bash
run: ./build_and_test.sh -v
stability-test-fast:
runs-on: ubuntu-latest
name: Fast stability tests, ${{ github.head_ref }}
steps:
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: 1.23
- name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v4
with:
fetch-depth: 0
@ -75,18 +70,17 @@ jobs:
working-directory: stability-tests
run: ./install_and_test.sh
coverage:
runs-on: ubuntu-latest
name: Produce code coverage
steps:
- name: Check out code into the Go module directory
uses: actions/checkout@v2
uses: actions/checkout@v4
- name: Setup Go
uses: actions/setup-go@v2
uses: actions/setup-go@v5
with:
go-version: 1.19
go-version: 1.23
- name: Delete the stability tests from coverage
run: rm -r stability-tests

1
.gitignore vendored
View File

@ -53,6 +53,7 @@ _testmain.go
debug
debug.test
__debug_bin
*__debug_*
# CI
version.txt

View File

@ -1,11 +1,15 @@
# DEPRECATED
Kaspad
====
The full node reference implementation was [rewritten in Rust](https://github.com/kaspanet/rusty-kaspa), as a result, the Go implementation is now deprecated.
PLEASE NOTE: Any pull requests or issues that will be opened in this repository will be closed without treatment, except for issues or pull requests related to the kaspawallet, which remains maintained. In any other case, please use the [Rust implementation](https://github.com/kaspanet/rusty-kaspa) instead.
# Kaspad
[![ISC License](http://img.shields.io/badge/license-ISC-blue.svg)](https://choosealicense.com/licenses/isc/)
[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](http://godoc.org/github.com/kaspanet/kaspad)
Kaspad is the reference full node Kaspa implementation written in Go (golang).
Kaspad was the reference full node Kaspa implementation written in Go (golang).
## What is kaspa
@ -13,7 +17,7 @@ Kaspa is an attempt at a proof-of-work cryptocurrency with instant confirmations
## Requirements
Go 1.18 or later.
Go 1.23 or later.
## Installation
@ -40,7 +44,6 @@ $ go install . ./cmd/...
not already add the bin directory to your system path during Go installation,
you are encouraged to do so now.
## Getting Started
Kaspad has several configuration options available to tweak how it runs, but all
@ -51,6 +54,7 @@ $ kaspad
```
## Discord
Join our discord server using the following link: https://discord.gg/YNYnNN5Pf2
## Issue Tracker

View File

@ -2,9 +2,10 @@ package appmessage
import (
"encoding/hex"
"github.com/pkg/errors"
"math/big"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
@ -213,13 +214,14 @@ func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externa
}
return &externalapi.DomainTransaction{
Version: rpcTransaction.Version,
Inputs: inputs,
Outputs: outputs,
LockTime: rpcTransaction.LockTime,
SubnetworkID: *subnetworkID,
Gas: rpcTransaction.Gas,
Payload: payload,
Version: rpcTransaction.Version,
Inputs: inputs,
Outputs: outputs,
LockTime: rpcTransaction.LockTime,
SubnetworkID: *subnetworkID,
Gas: rpcTransaction.Gas,
MassCommitment: rpcTransaction.Mass,
Payload: payload,
}, nil
}
@ -287,6 +289,7 @@ func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransactio
LockTime: transaction.LockTime,
SubnetworkID: subnetworkID,
Gas: transaction.Gas,
Mass: transaction.MassCommitment,
Payload: payload,
}
}

View File

@ -163,6 +163,10 @@ const (
CmdGetMempoolEntriesByAddressesResponseMessage
CmdGetCoinSupplyRequestMessage
CmdGetCoinSupplyResponseMessage
CmdGetFeeEstimateRequestMessage
CmdGetFeeEstimateResponseMessage
CmdSubmitTransactionReplacementRequestMessage
CmdSubmitTransactionReplacementResponseMessage
)
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
@ -300,6 +304,10 @@ var RPCMessageCommandToString = map[MessageCommand]string{
CmdGetMempoolEntriesByAddressesResponseMessage: "GetMempoolEntriesByAddressesResponse",
CmdGetCoinSupplyRequestMessage: "GetCoinSupplyRequest",
CmdGetCoinSupplyResponseMessage: "GetCoinSupplyResponse",
CmdGetFeeEstimateRequestMessage: "GetFeeEstimateRequest",
CmdGetFeeEstimateResponseMessage: "GetFeeEstimateResponse",
CmdSubmitTransactionReplacementRequestMessage: "SubmitTransactionReplacementRequest",
CmdSubmitTransactionReplacementResponseMessage: "SubmitTransactionReplacementResponse",
}
// Message is an interface that describes a kaspa message. A type that

View File

@ -0,0 +1,47 @@
package appmessage
// GetFeeEstimateRequestMessage is an appmessage corresponding to
// its respective RPC message
type GetFeeEstimateRequestMessage struct {
baseMessage
}
// Command returns the protocol command string for the message
func (msg *GetFeeEstimateRequestMessage) Command() MessageCommand {
return CmdGetFeeEstimateRequestMessage
}
// NewGetFeeEstimateRequestMessage returns a instance of the message
func NewGetFeeEstimateRequestMessage() *GetFeeEstimateRequestMessage {
return &GetFeeEstimateRequestMessage{}
}
type RPCFeeRateBucket struct {
Feerate float64
EstimatedSeconds float64
}
type RPCFeeEstimate struct {
PriorityBucket RPCFeeRateBucket
NormalBuckets []RPCFeeRateBucket
LowBuckets []RPCFeeRateBucket
}
// GetCoinSupplyResponseMessage is an appmessage corresponding to
// its respective RPC message
type GetFeeEstimateResponseMessage struct {
baseMessage
Estimate RPCFeeEstimate
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *GetFeeEstimateResponseMessage) Command() MessageCommand {
return CmdGetFeeEstimateResponseMessage
}
// NewGetFeeEstimateResponseMessage returns a instance of the message
func NewGetFeeEstimateResponseMessage() *GetFeeEstimateResponseMessage {
return &GetFeeEstimateResponseMessage{}
}

View File

@ -52,6 +52,7 @@ type RPCTransaction struct {
SubnetworkID string
Gas uint64
Payload string
Mass uint64
VerboseData *RPCTransactionVerboseData
}

View File

@ -0,0 +1,42 @@
package appmessage
// SubmitTransactionReplacementRequestMessage is an appmessage corresponding to
// its respective RPC message
type SubmitTransactionReplacementRequestMessage struct {
baseMessage
Transaction *RPCTransaction
}
// Command returns the protocol command string for the message
func (msg *SubmitTransactionReplacementRequestMessage) Command() MessageCommand {
return CmdSubmitTransactionReplacementRequestMessage
}
// NewSubmitTransactionReplacementRequestMessage returns a instance of the message
func NewSubmitTransactionReplacementRequestMessage(transaction *RPCTransaction) *SubmitTransactionReplacementRequestMessage {
return &SubmitTransactionReplacementRequestMessage{
Transaction: transaction,
}
}
// SubmitTransactionReplacementResponseMessage is an appmessage corresponding to
// its respective RPC message
type SubmitTransactionReplacementResponseMessage struct {
baseMessage
TransactionID string
ReplacedTransaction *RPCTransaction
Error *RPCError
}
// Command returns the protocol command string for the message
func (msg *SubmitTransactionReplacementResponseMessage) Command() MessageCommand {
return CmdSubmitTransactionReplacementResponseMessage
}
// NewSubmitTransactionReplacementResponseMessage returns a instance of the message
func NewSubmitTransactionReplacementResponseMessage(transactionID string) *SubmitTransactionReplacementResponseMessage {
return &SubmitTransactionReplacementResponseMessage{
TransactionID: transactionID,
}
}

View File

@ -81,10 +81,6 @@ func (ctx *Context) PopulateBlockWithVerboseData(block *appmessage.RPCBlock, dom
block.VerboseData.SelectedParentHash = blockInfo.SelectedParent.String()
}
if blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
return nil
}
// Get the block if we didn't receive it previously
if domainBlock == nil {
domainBlock, err = ctx.Domain.Consensus().GetBlockEvenIfHeaderOnly(blockHash)
@ -93,6 +89,10 @@ func (ctx *Context) PopulateBlockWithVerboseData(block *appmessage.RPCBlock, dom
}
}
if len(domainBlock.Transactions) == 0 {
return nil
}
transactionIDs := make([]string, len(domainBlock.Transactions))
for i, transaction := range domainBlock.Transactions {
transactionIDs[i] = consensushashing.TransactionID(transaction).String()

View File

@ -5,13 +5,10 @@ FLAGS=$@
go version
go get $FLAGS -t -d ./...
GO111MODULE=off go get $FLAGS golang.org/x/lint/golint
go install $FLAGS honnef.co/go/tools/cmd/staticcheck@latest
test -z "$(go fmt ./...)"
golint -set_exit_status ./...
staticcheck -checks SA4006,SA4008,SA4009,SA4010,SA5003,SA1004,SA1014,SA1021,SA1023,SA1024,SA1025,SA1026,SA1027,SA1028,SA2000,SA2001,SA2003,SA4000,SA4001,SA4003,SA4004,SA4011,SA4012,SA4013,SA4014,SA4015,SA4016,SA4017,SA4018,SA4019,SA4020,SA4021,SA4022,SA4023,SA5000,SA5002,SA5004,SA5005,SA5007,SA5008,SA5009,SA5010,SA5011,SA5012,SA6001,SA6002,SA9001,SA9002,SA9003,SA9004,SA9005,SA9006,ST1019 ./...
go build $FLAGS -o kaspad .

View File

@ -4,7 +4,7 @@ kaspactl is an RPC client for kaspad
## Requirements
Go 1.19 or later.
Go 1.23 or later.
## Installation
@ -50,4 +50,4 @@ For example:
$ kaspactl '{"getBlockDagInfoRequest":{}}'
```
For a list of all available requests check out the [RPC documentation](infrastructure/network/netadapter/server/grpcserver/protowire/rpc.md)
For a list of all available requests check out the [RPC documentation](infrastructure/network/netadapter/server/grpcserver/protowire/rpc.md)

View File

@ -1,5 +1,5 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.19-alpine AS build
FROM golang:1.23-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad

View File

@ -4,7 +4,7 @@ Kaspaminer is a CPU-based miner for kaspad
## Requirements
Go 1.19 or later.
Go 1.23 or later.
## Installation
@ -30,7 +30,7 @@ $ go install .
- Kapaminer should now be installed in `$(go env GOPATH)/bin`. If you did
not already add the bin directory to your system path during Go installation,
you are encouraged to do so now.
## Usage
The full kaspaminer configuration options can be seen with:
@ -40,6 +40,7 @@ $ kaspaminer --help
```
But the minimum configuration needed to run it is:
```bash
$ kaspaminer --miningaddr=<YOUR_MINING_ADDRESS>
```
```

View File

@ -1,5 +1,5 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.19-alpine AS build
FROM golang:1.23-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad

View File

@ -8,6 +8,7 @@ import (
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"github.com/pkg/errors"
)
@ -37,7 +38,7 @@ func broadcast(conf *broadcastConfig) error {
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
}
transactions, err := decodeTransactionsFromHex(transactionsHex)
transactions, err := server.DecodeTransactionsFromHex(transactionsHex)
if err != nil {
return err
}

View File

@ -0,0 +1,57 @@
package main
import (
"context"
"fmt"
"io/ioutil"
"strings"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"github.com/pkg/errors"
)
func broadcastReplacement(conf *broadcastConfig) error {
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
if err != nil {
return err
}
defer tearDown()
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
if conf.Transactions == "" && conf.TransactionsFile == "" {
return errors.Errorf("Either --transaction or --transaction-file is required")
}
if conf.Transactions != "" && conf.TransactionsFile != "" {
return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
}
transactionsHex := conf.Transactions
if conf.TransactionsFile != "" {
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionsFile)
if err != nil {
return errors.Wrapf(err, "Could not read hex from %s", conf.TransactionsFile)
}
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
}
transactions, err := server.DecodeTransactionsFromHex(transactionsHex)
if err != nil {
return err
}
response, err := daemonClient.BroadcastReplacement(ctx, &pb.BroadcastRequest{Transactions: transactions})
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)
}
return nil
}

117
cmd/kaspawallet/bump_fee.go Normal file
View File

@ -0,0 +1,117 @@
package main
import (
"context"
"fmt"
"os"
"strings"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/pkg/errors"
)
func bumpFee(conf *bumpFeeConfig) error {
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
if len(keysFile.ExtendedPublicKeys) > len(keysFile.EncryptedMnemonics) {
return errors.Errorf("Cannot use 'bump-fee' command for multisig wallet without all of the keys")
}
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
if err != nil {
return err
}
defer tearDown()
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
var feePolicy *pb.FeePolicy
if conf.FeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_ExactFeeRate{
ExactFeeRate: conf.FeeRate,
},
}
} else if conf.MaxFeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFeeRate{MaxFeeRate: conf.MaxFeeRate},
}
} else if conf.MaxFee > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFee{MaxFee: conf.MaxFee},
}
}
createUnsignedTransactionsResponse, err :=
daemonClient.BumpFee(ctx, &pb.BumpFeeRequest{
TxID: conf.TxID,
From: conf.FromAddresses,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
FeePolicy: feePolicy,
})
if err != nil {
return err
}
if len(conf.Password) == 0 {
conf.Password = keys.GetPassword("Password:")
}
mnemonics, err := keysFile.DecryptMnemonics(conf.Password)
if err != nil {
if strings.Contains(err.Error(), "message authentication failed") {
fmt.Fprintf(os.Stderr, "Password decryption failed. Sometimes this is a result of not "+
"specifying the same keys file used by the wallet daemon process.\n")
}
return err
}
signedTransactions := make([][]byte, len(createUnsignedTransactionsResponse.Transactions))
for i, unsignedTransaction := range createUnsignedTransactionsResponse.Transactions {
signedTransaction, err := libkaspawallet.Sign(conf.NetParams(), mnemonics, unsignedTransaction, keysFile.ECDSA)
if err != nil {
return err
}
signedTransactions[i] = signedTransaction
}
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()
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.BroadcastReplacement(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 {
fmt.Println("Serialized Transaction(s) (can be parsed via the `parse` command or resent via `broadcast`): ")
for _, signedTx := range signedTransactions {
fmt.Printf("\t%x\n\n", signedTx)
}
}
return nil
}

View File

@ -0,0 +1,58 @@
package main
import (
"context"
"fmt"
"os"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
)
func bumpFeeUnsigned(conf *bumpFeeUnsignedConfig) error {
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
if err != nil {
return err
}
defer tearDown()
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
if err != nil {
return err
}
var feePolicy *pb.FeePolicy
if conf.FeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_ExactFeeRate{
ExactFeeRate: conf.FeeRate,
},
}
} else if conf.MaxFeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFeeRate{MaxFeeRate: conf.MaxFeeRate},
}
} else if conf.MaxFee > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFee{MaxFee: conf.MaxFee},
}
}
response, err := daemonClient.BumpFee(ctx, &pb.BumpFeeRequest{
TxID: conf.TxID,
From: conf.FromAddresses,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
FeePolicy: feePolicy,
})
if err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Created unsigned transaction")
fmt.Println(server.EncodeTransactionsToHex(response.Transactions))
return nil
}

View File

@ -1,9 +1,10 @@
package main
import (
"os"
"github.com/kaspanet/kaspad/infrastructure/config"
"github.com/pkg/errors"
"os"
"github.com/jessevdk/go-flags"
)
@ -23,6 +24,9 @@ const (
startDaemonSubCmd = "start-daemon"
versionSubCmd = "version"
getDaemonVersionSubCmd = "get-daemon-version"
bumpFeeSubCmd = "bump-fee"
bumpFeeUnsignedSubCmd = "bump-fee-unsigned"
broadcastReplacementSubCmd = "broadcast-replacement"
)
const (
@ -58,10 +62,13 @@ 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"`
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)"`
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)"`
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the minimum between the fee rate estimate from the connected node and this value."`
FeeRate float64 `long:"fee-rate" short:"r" description:"Fee rate in Sompi/gram to use for the transaction. This option will override any fee estimate from the connected node."`
MaxFee uint64 `long:"max-fee" short:"x" description:"Maximum fee in Sompi (not Sompi/gram) to use for the transaction. The wallet will take the minimum between the fee estimate from the connected node and this value. If no other fee policy is specified, it will set the max fee to 1 KAS"`
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
config.NetworkFlags
}
@ -79,6 +86,9 @@ type createUnsignedTransactionConfig struct {
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)"`
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the minimum between the fee rate estimate from the connected node and this value."`
FeeRate float64 `long:"fee-rate" short:"r" description:"Fee rate in Sompi/gram to use for the transaction. This option will override any fee estimate from the connected node."`
MaxFee uint64 `long:"max-fee" short:"x" description:"Maximum fee in Sompi (not Sompi/gram) to use for the transaction. The wallet will take the minimum between the fee estimate from the connected node and this value. If no other fee policy is specified, it will set the max fee to 1 KAS"`
config.NetworkFlags
}
@ -98,6 +108,7 @@ type broadcastConfig struct {
}
type parseConfig struct {
KeysFile string `long:"keys-file" short:"f" description:"Keys file location (default: ~/.kaspawallet/keys.json (*nix), %USERPROFILE%\\AppData\\Local\\Kaspawallet\\key.json (Windows))"`
Transaction string `long:"transaction" short:"t" description:"The transaction to parse (encoded in hex)"`
TransactionFile string `long:"transaction-file" short:"F" description:"The file containing the transaction to parse (encoded in hex)"`
Verbose bool `long:"verbose" short:"v" description:"Verbose: show transaction inputs"`
@ -131,6 +142,31 @@ type dumpUnencryptedDataConfig struct {
config.NetworkFlags
}
type bumpFeeUnsignedConfig struct {
TxID string `long:"txid" short:"i" description:"The transaction ID to bump the fee for"`
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the minimum between the fee rate estimate from the connected node and this value."`
FeeRate float64 `long:"fee-rate" short:"r" description:"Fee rate in Sompi/gram to use for the transaction. This option will override any fee estimate from the connected node."`
MaxFee uint64 `long:"max-fee" short:"x" description:"Maximum fee in Sompi (not Sompi/gram) to use for the transaction. The wallet will take the minimum between the fee estimate from the connected node and this value. If no other fee policy is specified, it will set the max fee to 1 KAS"`
config.NetworkFlags
}
type bumpFeeConfig struct {
TxID string `long:"txid" short:"i" description:"The transaction ID to bump the fee for"`
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"`
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
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"`
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the minimum between the fee rate estimate from the connected node and this value."`
FeeRate float64 `long:"fee-rate" short:"r" description:"Fee rate in Sompi/gram to use for the transaction. This option will override any fee estimate from the connected node."`
MaxFee uint64 `long:"max-fee" short:"x" description:"Maximum fee in Sompi (not Sompi/gram) to use for the transaction. The wallet will take the minimum between the fee estimate from the connected node and this value. If no other fee policy is specified, it will set the max fee to 1 KAS"`
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
config.NetworkFlags
}
type versionConfig struct {
}
@ -197,6 +233,12 @@ func parseCommandLine() (subCommand string, config interface{}) {
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)
bumpFeeConf := &bumpFeeConfig{DaemonAddress: defaultListen}
parser.AddCommand(bumpFeeSubCmd, "Bump transaction fee (with signing and broadcast)", "Bump transaction fee (with signing and broadcast)", bumpFeeConf)
bumpFeeUnsignedConf := &bumpFeeUnsignedConfig{DaemonAddress: defaultListen}
parser.AddCommand(bumpFeeUnsignedSubCmd, "Bump transaction fee (without signing)", "Bump transaction fee (without signing)", bumpFeeUnsignedConf)
parser.AddCommand(broadcastReplacementSubCmd, "Broadcast the given transaction replacement",
"Broadcast the given transaction replacement", broadcastConf)
_, err := parser.Parse()
if err != nil {
@ -267,6 +309,13 @@ func parseCommandLine() (subCommand string, config interface{}) {
printErrorAndExit(err)
}
config = broadcastConf
case broadcastReplacementSubCmd:
combineNetworkFlags(&broadcastConf.NetworkFlags, &cfg.NetworkFlags)
err := broadcastConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
config = broadcastConf
case parseSubCmd:
combineNetworkFlags(&parseConf.NetworkFlags, &cfg.NetworkFlags)
err := parseConf.ResolveNetwork(parser)
@ -305,6 +354,32 @@ func parseCommandLine() (subCommand string, config interface{}) {
case versionSubCmd:
case getDaemonVersionSubCmd:
config = getDaemonVersionConf
case bumpFeeSubCmd:
combineNetworkFlags(&bumpFeeConf.NetworkFlags, &cfg.NetworkFlags)
err := bumpFeeConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
err = validateBumpFeeConfig(bumpFeeConf)
if err != nil {
printErrorAndExit(err)
}
config = bumpFeeConf
case bumpFeeUnsignedSubCmd:
combineNetworkFlags(&bumpFeeUnsignedConf.NetworkFlags, &cfg.NetworkFlags)
err := bumpFeeUnsignedConf.ResolveNetwork(parser)
if err != nil {
printErrorAndExit(err)
}
err = validateBumpFeeUnsignedConfig(bumpFeeUnsignedConf)
if err != nil {
printErrorAndExit(err)
}
config = bumpFeeUnsignedConf
}
return parser.Command.Active.Name, config
@ -316,6 +391,19 @@ func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
}
if conf.MaxFeeRate < 0 {
return errors.New("--max-fee-rate must be a positive number")
}
if conf.FeeRate < 0 {
return errors.New("--fee-rate must be a positive number")
}
if boolToUint8(conf.MaxFeeRate > 0)+boolToUint8(conf.FeeRate > 0)+boolToUint8(conf.MaxFee > 0) > 1 {
return errors.New("at most one of '--max-fee-rate', '--fee-rate' or '--max-fee' can be specified")
}
return nil
}
@ -325,9 +413,61 @@ func validateSendConfig(conf *sendConfig) error {
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
}
if conf.MaxFeeRate < 0 {
return errors.New("--max-fee-rate must be a positive number")
}
if conf.FeeRate < 0 {
return errors.New("--fee-rate must be a positive number")
}
if boolToUint8(conf.MaxFeeRate > 0)+boolToUint8(conf.FeeRate > 0)+boolToUint8(conf.MaxFee > 0) > 1 {
return errors.New("at most one of '--max-fee-rate', '--fee-rate' or '--max-fee' can be specified")
}
return nil
}
func validateBumpFeeConfig(conf *bumpFeeConfig) error {
if conf.MaxFeeRate < 0 {
return errors.New("--max-fee-rate must be a positive number")
}
if conf.FeeRate < 0 {
return errors.New("--fee-rate must be a positive number")
}
if boolToUint8(conf.MaxFeeRate > 0)+boolToUint8(conf.FeeRate > 0)+boolToUint8(conf.MaxFee > 0) > 1 {
return errors.New("at most one of '--max-fee-rate', '--fee-rate' or '--max-fee' can be specified")
}
return nil
}
func validateBumpFeeUnsignedConfig(conf *bumpFeeUnsignedConfig) error {
if conf.MaxFeeRate < 0 {
return errors.New("--max-fee-rate must be a positive number")
}
if conf.FeeRate < 0 {
return errors.New("--fee-rate must be a positive number")
}
if boolToUint8(conf.MaxFeeRate > 0)+boolToUint8(conf.FeeRate > 0)+boolToUint8(conf.MaxFee > 0) > 1 {
return errors.New("at most one of '--max-fee-rate', '--fee-rate' or '--max-fee' can be specified")
}
return nil
}
func boolToUint8(b bool) uint8 {
if b {
return 1
}
return 0
}
func combineNetworkFlags(dst, src *config.NetworkFlags) {
dst.Testnet = dst.Testnet || src.Testnet
dst.Simnet = dst.Simnet || src.Simnet

View File

@ -7,6 +7,7 @@ import (
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
)
@ -20,10 +21,30 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
defer cancel()
sendAmountSompi, err := utils.KasToSompi(conf.SendAmount)
var sendAmountSompi uint64
if err != nil {
return err
if !conf.IsSendAll {
sendAmountSompi, err = utils.KasToSompi(conf.SendAmount)
if err != nil {
return err
}
}
var feePolicy *pb.FeePolicy
if conf.FeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_ExactFeeRate{
ExactFeeRate: conf.FeeRate,
},
}
} else if conf.MaxFeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFeeRate{MaxFeeRate: conf.MaxFeeRate},
}
} else if conf.MaxFee > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFee{MaxFee: conf.MaxFee},
}
}
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
@ -32,13 +53,14 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
Amount: sendAmountSompi,
IsSendAll: conf.IsSendAll,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
FeePolicy: feePolicy,
})
if err != nil {
return err
}
fmt.Fprintln(os.Stderr, "Created unsigned transaction")
fmt.Println(encodeTransactionsToHex(response.UnsignedTransactions))
fmt.Println(server.EncodeTransactionsToHex(response.UnsignedTransactions))
return nil
}

View File

@ -2,9 +2,10 @@ package client
import (
"context"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"time"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"github.com/pkg/errors"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"

File diff suppressed because it is too large Load Diff

View File

@ -4,22 +4,28 @@ option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb";
package kaspawalletd;
service kaspawalletd {
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
rpc GetExternalSpendableUTXOs (GetExternalSpendableUTXOsRequest) returns (GetExternalSpendableUTXOsResponse) {}
rpc CreateUnsignedTransactions (CreateUnsignedTransactionsRequest) returns (CreateUnsignedTransactionsResponse) {}
rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {}
rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
rpc Broadcast (BroadcastRequest) returns (BroadcastResponse) {}
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse) {}
rpc GetExternalSpendableUTXOs(GetExternalSpendableUTXOsRequest)
returns (GetExternalSpendableUTXOsResponse) {}
rpc CreateUnsignedTransactions(CreateUnsignedTransactionsRequest)
returns (CreateUnsignedTransactionsResponse) {}
rpc ShowAddresses(ShowAddressesRequest) returns (ShowAddressesResponse) {}
rpc NewAddress(NewAddressRequest) returns (NewAddressResponse) {}
rpc Shutdown(ShutdownRequest) returns (ShutdownResponse) {}
rpc Broadcast(BroadcastRequest) returns (BroadcastResponse) {}
// BroadcastReplacement assumes that all transactions depend on the first one
rpc BroadcastReplacement(BroadcastRequest) returns (BroadcastResponse) {}
// Since SendRequest contains a password - this command should only be used on
// a trusted or secure connection
rpc Send(SendRequest) returns (SendResponse) {}
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
// Since SignRequest contains a password - this command should only be used on
// a trusted or secure connection
rpc Sign(SignRequest) returns (SignResponse) {}
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
rpc BumpFee(BumpFeeRequest) returns (BumpFeeResponse) {}
}
message GetBalanceRequest {
}
message GetBalanceRequest {}
message GetBalanceResponse {
uint64 available = 1;
@ -33,46 +39,45 @@ message AddressBalances {
uint64 pending = 3;
}
message FeePolicy {
oneof feePolicy {
double maxFeeRate = 6;
double exactFeeRate = 7;
uint64 maxFee = 8;
}
}
message CreateUnsignedTransactionsRequest {
string address = 1;
uint64 amount = 2;
repeated string from = 3;
bool useExistingChangeAddress = 4;
bool isSendAll = 5;
FeePolicy feePolicy = 6;
}
message CreateUnsignedTransactionsResponse {
repeated bytes unsignedTransactions = 1;
}
message ShowAddressesRequest {
}
message ShowAddressesRequest {}
message ShowAddressesResponse {
repeated string address = 1;
}
message ShowAddressesResponse { repeated string address = 1; }
message NewAddressRequest {
}
message NewAddressRequest {}
message NewAddressResponse {
string address = 1;
}
message NewAddressResponse { string address = 1; }
message BroadcastRequest {
bool isDomain = 1;
repeated bytes transactions = 2;
}
message BroadcastResponse {
repeated string txIDs = 1;
}
message BroadcastResponse { repeated string txIDs = 1; }
message ShutdownRequest {
}
message ShutdownRequest {}
message ShutdownResponse {
}
message ShutdownResponse {}
message Outpoint {
string transactionId = 1;
@ -97,41 +102,50 @@ message UtxoEntry {
bool isCoinbase = 4;
}
message GetExternalSpendableUTXOsRequest{
string address = 1;
}
message GetExternalSpendableUTXOsRequest { string address = 1; }
message GetExternalSpendableUTXOsResponse{
message GetExternalSpendableUTXOsResponse {
repeated UtxosByAddressesEntry Entries = 1;
}
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
message SendRequest{
// Since SendRequest contains a password - this command should only be used on a
// trusted or secure connection
message SendRequest {
string toAddress = 1;
uint64 amount = 2;
string password = 3;
repeated string from = 4;
bool useExistingChangeAddress = 5;
bool isSendAll = 6;
FeePolicy feePolicy = 7;
}
message SendResponse{
message SendResponse {
repeated string txIDs = 1;
repeated bytes signedTransactions = 2;
}
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
message SignRequest{
// Since SignRequest contains a password - this command should only be used on a
// trusted or secure connection
message SignRequest {
repeated bytes unsignedTransactions = 1;
string password = 2;
}
message SignResponse{
repeated bytes signedTransactions = 1;
message SignResponse { repeated bytes signedTransactions = 1; }
message GetVersionRequest {}
message GetVersionResponse { string version = 1; }
message BumpFeeRequest {
string password = 1;
repeated string from = 2;
bool useExistingChangeAddress = 3;
FeePolicy feePolicy = 4;
string txID = 5;
}
message GetVersionRequest{
message BumpFeeResponse {
repeated bytes transactions = 1;
repeated string txIDs = 2;
}
message GetVersionResponse{
string version = 1;
}

View File

@ -29,11 +29,16 @@ type KaspawalletdClient interface {
NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error)
Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
// BroadcastReplacement assumes that all transactions depend on the first one
BroadcastReplacement(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
// Since SendRequest contains a password - this command should only be used on
// a trusted or secure connection
Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error)
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
// Since SignRequest contains a password - this command should only be used on
// a trusted or secure connection
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
BumpFee(ctx context.Context, in *BumpFeeRequest, opts ...grpc.CallOption) (*BumpFeeResponse, error)
}
type kaspawalletdClient struct {
@ -107,6 +112,15 @@ func (c *kaspawalletdClient) Broadcast(ctx context.Context, in *BroadcastRequest
return out, nil
}
func (c *kaspawalletdClient) BroadcastReplacement(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error) {
out := new(BroadcastResponse)
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/BroadcastReplacement", in, out, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *kaspawalletdClient) Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error) {
out := new(SendResponse)
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/Send", in, out, opts...)
@ -134,6 +148,15 @@ func (c *kaspawalletdClient) GetVersion(ctx context.Context, in *GetVersionReque
return out, nil
}
func (c *kaspawalletdClient) BumpFee(ctx context.Context, in *BumpFeeRequest, opts ...grpc.CallOption) (*BumpFeeResponse, error) {
out := new(BumpFeeResponse)
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/BumpFee", 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
@ -145,11 +168,16 @@ type KaspawalletdServer interface {
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
// BroadcastReplacement assumes that all transactions depend on the first one
BroadcastReplacement(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
// Since SendRequest contains a password - this command should only be used on
// a trusted or secure connection
Send(context.Context, *SendRequest) (*SendResponse, error)
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
// Since SignRequest contains a password - this command should only be used on
// a trusted or secure connection
Sign(context.Context, *SignRequest) (*SignResponse, error)
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
BumpFee(context.Context, *BumpFeeRequest) (*BumpFeeResponse, error)
mustEmbedUnimplementedKaspawalletdServer()
}
@ -178,6 +206,9 @@ func (UnimplementedKaspawalletdServer) Shutdown(context.Context, *ShutdownReques
func (UnimplementedKaspawalletdServer) Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Broadcast not implemented")
}
func (UnimplementedKaspawalletdServer) BroadcastReplacement(context.Context, *BroadcastRequest) (*BroadcastResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BroadcastReplacement not implemented")
}
func (UnimplementedKaspawalletdServer) Send(context.Context, *SendRequest) (*SendResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method Send not implemented")
}
@ -187,6 +218,9 @@ func (UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*Sig
func (UnimplementedKaspawalletdServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
}
func (UnimplementedKaspawalletdServer) BumpFee(context.Context, *BumpFeeRequest) (*BumpFeeResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method BumpFee not implemented")
}
func (UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {}
// UnsafeKaspawalletdServer may be embedded to opt out of forward compatibility for this service.
@ -326,6 +360,24 @@ func _Kaspawalletd_Broadcast_Handler(srv interface{}, ctx context.Context, dec f
return interceptor(ctx, in, info, handler)
}
func _Kaspawalletd_BroadcastReplacement_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BroadcastRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(KaspawalletdServer).BroadcastReplacement(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/kaspawalletd.kaspawalletd/BroadcastReplacement",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(KaspawalletdServer).BroadcastReplacement(ctx, req.(*BroadcastRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Kaspawalletd_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(SendRequest)
if err := dec(in); err != nil {
@ -380,6 +432,24 @@ func _Kaspawalletd_GetVersion_Handler(srv interface{}, ctx context.Context, dec
return interceptor(ctx, in, info, handler)
}
func _Kaspawalletd_BumpFee_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(BumpFeeRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(KaspawalletdServer).BumpFee(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/kaspawalletd.kaspawalletd/BumpFee",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(KaspawalletdServer).BumpFee(ctx, req.(*BumpFeeRequest))
}
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)
@ -415,6 +485,10 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
MethodName: "Broadcast",
Handler: _Kaspawalletd_Broadcast_Handler,
},
{
MethodName: "BroadcastReplacement",
Handler: _Kaspawalletd_BroadcastReplacement_Handler,
},
{
MethodName: "Send",
Handler: _Kaspawalletd_Send_Handler,
@ -427,6 +501,10 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
MethodName: "GetVersion",
Handler: _Kaspawalletd_GetVersion_Handler,
},
{
MethodName: "BumpFee",
Handler: _Kaspawalletd_BumpFee_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "kaspawalletd.proto",

View File

@ -0,0 +1,81 @@
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"
)
func (s *server) BroadcastReplacement(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
txIDs, err := s.broadcastReplacement(request.Transactions, request.IsDomain)
if err != nil {
return nil, err
}
return &pb.BroadcastResponse{TxIDs: txIDs}, nil
}
// broadcastReplacement assumes that all transactions depend on the first one
func (s *server) broadcastReplacement(transactions [][]byte, isDomain bool) ([]string, error) {
txIDs := make([]string, len(transactions))
var tx *externalapi.DomainTransaction
var err error
for i, transaction := range transactions {
if isDomain {
tx, err = serialization.DeserializeDomainTransaction(transaction)
if err != nil {
return nil, err
}
} else if !isDomain { //default in proto3 is false
tx, err = libkaspawallet.ExtractTransaction(transaction, s.keysFile.ECDSA)
if err != nil {
return nil, err
}
}
// Once the first transaction is added to the mempool, the transactions that depend
// on the replaced transaction will be removed, so there's no need to submit them
// as RBF transactions.
if i == 0 {
txIDs[i], err = sendTransactionRBF(s.rpcClient, tx)
if err != nil {
return nil, err
}
} else {
txIDs[i], err = sendTransaction(s.rpcClient, tx)
if err != nil {
return nil, err
}
}
for _, input := range tx.Inputs {
s.usedOutpoints[input.PreviousOutpoint] = time.Now()
}
}
s.forceSync()
return txIDs, nil
}
func sendTransactionRBF(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
submitTransactionResponse, err := client.SubmitTransactionReplacement(appmessage.DomainTransactionToRPCTransaction(tx), consensushashing.TransactionID(tx).String())
if err != nil {
return "", errors.Wrapf(err, "error submitting transaction replacement")
}
return submitTransactionResponse.TransactionID, nil
}

View File

@ -0,0 +1,156 @@
package server
import (
"context"
"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/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/pkg/errors"
)
func (s *server) BumpFee(_ context.Context, request *pb.BumpFeeRequest) (*pb.BumpFeeResponse, error) {
s.lock.Lock()
defer s.lock.Unlock()
entry, err := s.rpcClient.GetMempoolEntry(request.TxID, false, false)
if err != nil {
return nil, err
}
domainTx, err := appmessage.RPCTransactionToDomainTransaction(entry.Entry.Transaction)
if err != nil {
return nil, err
}
outpointsToInputs := make(map[externalapi.DomainOutpoint]*externalapi.DomainTransactionInput)
var maxUTXO *walletUTXO
for _, input := range domainTx.Inputs {
outpointsToInputs[input.PreviousOutpoint] = input
utxo, ok := s.mempoolExcludedUTXOs[input.PreviousOutpoint]
if !ok {
continue
}
input.UTXOEntry = utxo.UTXOEntry
if maxUTXO == nil || utxo.UTXOEntry.Amount() > maxUTXO.UTXOEntry.Amount() {
maxUTXO = utxo
}
}
if maxUTXO == nil {
// If we got here it means for some reason s.mempoolExcludedUTXOs is not up to date and we need to search for the UTXOs in s.utxosSortedByAmount
for _, utxo := range s.utxosSortedByAmount {
input, ok := outpointsToInputs[*utxo.Outpoint]
if !ok {
continue
}
input.UTXOEntry = utxo.UTXOEntry
if maxUTXO == nil || utxo.UTXOEntry.Amount() > maxUTXO.UTXOEntry.Amount() {
maxUTXO = utxo
}
}
}
if maxUTXO == nil {
return nil, errors.Errorf("no UTXOs were found for transaction %s. This probably means the transaction is already accepted", request.TxID)
}
mass := s.txMassCalculator.CalculateTransactionOverallMass(domainTx)
feeRate := float64(entry.Entry.Fee) / float64(mass)
newFeeRate, maxFee, err := s.calculateFeeLimits(request.FeePolicy)
if err != nil {
return nil, err
}
if feeRate >= newFeeRate {
return nil, errors.Errorf("new fee rate (%f) is not higher than the current fee rate (%f)", newFeeRate, feeRate)
}
if len(domainTx.Outputs) == 0 || len(domainTx.Outputs) > 2 {
return nil, errors.Errorf("kaspawallet supports only transactions with 1 or 2 outputs in transaction %s, but this transaction got %d", request.TxID, len(domainTx.Outputs))
}
var fromAddresses []*walletAddress
for _, from := range request.From {
fromAddress, exists := s.addressSet[from]
if !exists {
return nil, errors.Errorf("specified from address %s does not exists", from)
}
fromAddresses = append(fromAddresses, fromAddress)
}
allowUsed := make(map[externalapi.DomainOutpoint]struct{})
for outpoint := range outpointsToInputs {
allowUsed[outpoint] = struct{}{}
}
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOsWithPreselected([]*walletUTXO{maxUTXO}, allowUsed, domainTx.Outputs[0].Value, false, newFeeRate, maxFee, fromAddresses)
if err != nil {
return nil, err
}
_, toAddress, err := txscript.ExtractScriptPubKeyAddress(domainTx.Outputs[0].ScriptPublicKey, s.params)
if err != nil {
return nil, err
}
changeAddress, changeWalletAddress, err := s.changeAddress(request.UseExistingChangeAddress, fromAddresses)
if err != nil {
return nil, err
}
if len(selectedUTXOs) == 0 {
return nil, errors.Errorf("couldn't find funds to spend")
}
payments := []*libkaspawallet.Payment{{
Address: toAddress,
Amount: spendValue,
}}
if changeSompi > 0 {
changeAddress, _, err := s.changeAddress(request.UseExistingChangeAddress, fromAddresses)
if err != nil {
return nil, err
}
payments = append(payments, &libkaspawallet.Payment{
Address: changeAddress,
Amount: changeSompi,
})
}
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures,
payments, selectedUTXOs)
if err != nil {
return nil, err
}
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, newFeeRate, maxFee)
if err != nil {
return nil, err
}
if request.Password == "" {
return &pb.BumpFeeResponse{
Transactions: unsignedTransactions,
}, nil
}
signedTransactions, err := s.signTransactions(unsignedTransactions, request.Password)
if err != nil {
return nil, err
}
txIDs, err := s.broadcastReplacement(signedTransactions, false)
if err != nil {
return nil, err
}
return &pb.BumpFeeResponse{
TxIDs: txIDs,
Transactions: signedTransactions,
}, nil
}

View File

@ -3,16 +3,27 @@ package server
import (
"context"
"fmt"
"math"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
// TODO: Implement a better fee estimation mechanism
const feePerInput = 10000
// The minimal change amount to target in order to avoid large storage mass (see KIP9 for more details).
// By having at least 10KAS in the change output we make sure that the storage mass charged for change is
// at most 1000 gram. Generally, if the payment is above 10KAS as well, the resulting storage mass will be
// in the order of magnitude of compute mass and wil not incur additional charges.
// Additionally, every transaction with send value > ~0.1 KAS should succeed (at most ~99K storage mass for payment
// output, thus overall lower than standard mass upper bound which is 100K gram)
const minChangeTarget = constants.SompiPerKaspa * 10
// The current minimal fee rate according to mempool standards
const minFeeRate = 1.0
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
*pb.CreateUnsignedTransactionsResponse, error,
@ -21,7 +32,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
defer s.lock.Unlock()
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.IsSendAll,
request.From, request.UseExistingChangeAddress)
request.From, request.UseExistingChangeAddress, request.FeePolicy)
if err != nil {
return nil, err
}
@ -29,10 +40,59 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
}
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool) ([][]byte, error) {
func (s *server) calculateFeeLimits(requestFeePolicy *pb.FeePolicy) (feeRate float64, maxFee uint64, err error) {
feeRate = minFeeRate
maxFee = math.MaxUint64
if requestFeePolicy == nil {
requestFeePolicy = &pb.FeePolicy{}
}
switch requestFeePolicy := requestFeePolicy.FeePolicy.(type) {
case *pb.FeePolicy_ExactFeeRate:
feeRate = requestFeePolicy.ExactFeeRate
if feeRate < minFeeRate {
return 0, 0, errors.Errorf("requested fee rate %f is too low, minimum fee rate is %f", feeRate, minFeeRate)
}
case *pb.FeePolicy_MaxFeeRate:
estimate, err := s.rpcClient.GetFeeEstimate()
if err != nil {
return 0, 0, err
}
if requestFeePolicy.MaxFeeRate < minFeeRate {
return 0, 0, errors.Errorf("requested max fee rate %f is too low, minimum fee rate is %f", requestFeePolicy.MaxFeeRate, minFeeRate)
}
feeRate = math.Min(estimate.Estimate.NormalBuckets[0].Feerate, requestFeePolicy.MaxFeeRate)
case *pb.FeePolicy_MaxFee:
estimate, err := s.rpcClient.GetFeeEstimate()
if err != nil {
return 0, 0, err
}
feeRate = estimate.Estimate.NormalBuckets[0].Feerate
maxFee = requestFeePolicy.MaxFee
case nil:
estimate, err := s.rpcClient.GetFeeEstimate()
if err != nil {
return 0, 0, err
}
feeRate = estimate.Estimate.NormalBuckets[0].Feerate
// Default to a bound of max 1 KAS as fee
maxFee = constants.SompiPerKaspa
}
return feeRate, maxFee, nil
}
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool, requestFeePolicy *pb.FeePolicy) ([][]byte, error) {
if !s.isSynced() {
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
}
feeRate, maxFee, err := s.calculateFeeLimits(requestFeePolicy)
if err != nil {
return nil, err
}
// make sure address string is correct before proceeding to a
// potentially long UTXO refreshment operation
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
@ -49,7 +109,12 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
fromAddresses = append(fromAddresses, fromAddress)
}
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feePerInput, fromAddresses)
changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress, fromAddresses)
if err != nil {
return nil, err
}
selectedUTXOs, spendValue, changeSompi, err := s.selectUTXOs(amount, isSendAll, feeRate, maxFee, fromAddresses)
if err != nil {
return nil, err
}
@ -58,11 +123,6 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
return nil, errors.Errorf("couldn't find funds to spend")
}
changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress, fromAddresses)
if err != nil {
return nil, err
}
payments := []*libkaspawallet.Payment{{
Address: toAddress,
Amount: spendValue,
@ -80,17 +140,25 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
return nil, err
}
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress)
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
if err != nil {
return nil, err
}
return unsignedTransactions, nil
}
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uint64, fromAddresses []*walletAddress) (
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feeRate float64, maxFee uint64, fromAddresses []*walletAddress) (
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
return s.selectUTXOsWithPreselected(nil, map[externalapi.DomainOutpoint]struct{}{}, spendAmount, isSendAll, feeRate, maxFee, fromAddresses)
}
func (s *server) selectUTXOsWithPreselected(preSelectedUTXOs []*walletUTXO, allowUsed map[externalapi.DomainOutpoint]struct{}, spendAmount uint64, isSendAll bool, feeRate float64, maxFee uint64, fromAddresses []*walletAddress) (
selectedUTXOs []*libkaspawallet.UTXO, totalReceived uint64, changeSompi uint64, err error) {
selectedUTXOs = []*libkaspawallet.UTXO{}
preSelectedSet := make(map[externalapi.DomainOutpoint]struct{})
for _, utxo := range preSelectedUTXOs {
preSelectedSet[*utxo.Outpoint] = struct{}{}
}
totalValue := uint64(0)
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
@ -98,17 +166,26 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
return nil, 0, 0, err
}
for _, utxo := range s.utxosSortedByAmount {
var fee uint64
iteration := func(utxo *walletUTXO, avoidPreselected bool) (bool, error) {
if (fromAddresses != nil && !walletAddressesContain(fromAddresses, utxo.address)) ||
!s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
continue
return true, nil
}
if broadcastTime, ok := s.usedOutpoints[*utxo.Outpoint]; ok {
if s.usedOutpointHasExpired(broadcastTime) {
delete(s.usedOutpoints, *utxo.Outpoint)
} else {
continue
if _, ok := allowUsed[*utxo.Outpoint]; !ok {
if s.usedOutpointHasExpired(broadcastTime) {
delete(s.usedOutpoints, *utxo.Outpoint)
} else {
return true, nil
}
}
}
if avoidPreselected {
if _, ok := preSelectedSet[*utxo.Outpoint]; ok {
return true, nil
}
}
@ -119,19 +196,54 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
})
totalValue += utxo.UTXOEntry.Amount()
estimatedRecipientValue := spendAmount
if isSendAll {
estimatedRecipientValue = totalValue
}
fee, err = s.estimateFee(selectedUTXOs, feeRate, maxFee, estimatedRecipientValue)
if err != nil {
return false, err
}
fee := feePerInput * uint64(len(selectedUTXOs))
totalSpend := spendAmount + fee
// 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 new dust
// rules we try and find at least 2 inputs (even though the next one is not necessary in terms of spend value)
if !isSendAll && (totalValue == totalSpend || (totalValue > totalSpend && len(selectedUTXOs) > 1)) {
// 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)) {
return false, nil
}
return true, nil
}
shouldContinue := true
for _, utxo := range preSelectedUTXOs {
shouldContinue, err = iteration(utxo, false)
if err != nil {
return nil, 0, 0, err
}
if !shouldContinue {
break
}
}
fee := feePerInput * uint64(len(selectedUTXOs))
if shouldContinue {
for _, utxo := range s.utxosSortedByAmount {
shouldContinue, err := iteration(utxo, true)
if err != nil {
return nil, 0, 0, err
}
if !shouldContinue {
break
}
}
}
var totalSpend uint64
if isSendAll {
totalSpend = totalValue
@ -148,6 +260,99 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
}
func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float64, maxFee uint64, recipientValue uint64) (uint64, error) {
fakePubKey := [util.PublicKeySizeECDSA]byte{}
fakeAddr, err := util.NewAddressPublicKeyECDSA(fakePubKey[:], s.params.Prefix) // We assume the worst case where the recipient address is ECDSA. In this case the scriptPubKey will be the longest.
if err != nil {
return 0, err
}
totalValue := uint64(0)
for _, utxo := range selectedUTXOs {
totalValue += utxo.UTXOEntry.Amount()
}
// This is an approximation for the distribution of value between the recipient output and the change output.
var mockPayments []*libkaspawallet.Payment
if totalValue > recipientValue {
mockPayments = []*libkaspawallet.Payment{
{
Address: fakeAddr,
Amount: recipientValue,
},
{
Address: fakeAddr,
Amount: totalValue - recipientValue, // We ignore the fee since we expect it to be insignificant in mass calculation.
},
}
} else {
mockPayments = []*libkaspawallet.Payment{
{
Address: fakeAddr,
Amount: totalValue,
},
}
}
mockTx, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures,
mockPayments, selectedUTXOs)
if err != nil {
return 0, err
}
mass, err := s.estimateMassAfterSignatures(mockTx)
if err != nil {
return 0, err
}
return min(uint64(math.Ceil(float64(mass)*feeRate)), maxFee), nil
}
func (s *server) estimateFeePerInput(feeRate float64) (uint64, error) {
mockUTXO := &libkaspawallet.UTXO{
Outpoint: &externalapi.DomainOutpoint{
TransactionID: externalapi.DomainTransactionID{},
Index: 0,
},
UTXOEntry: utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{
Script: nil,
Version: 0,
}, false, 0),
DerivationPath: "m",
}
mockTx, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures,
nil, []*libkaspawallet.UTXO{mockUTXO})
if err != nil {
return 0, err
}
// Here we use compute mass to avoid dividing by zero. This is ok since `s.estimateFeePerInput` is only used
// in the case of compound transactions that have a compute mass higher than its storage mass.
mass, err := s.estimateComputeMassAfterSignatures(mockTx)
if err != nil {
return 0, err
}
mockTxWithoutUTXO, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures,
nil, nil)
if err != nil {
return 0, err
}
massWithoutUTXO, err := s.estimateComputeMassAfterSignatures(mockTxWithoutUTXO)
if err != nil {
return 0, err
}
inputMass := mass - massWithoutUTXO
return uint64(float64(inputMass) * feeRate), nil
}
func walletAddressesContain(addresses []*walletAddress, contain *walletAddress) bool {
for _, address := range addresses {
if *address == *contain {

View File

@ -21,7 +21,15 @@ func (s *server) GetExternalSpendableUTXOs(_ context.Context, request *pb.GetExt
if err != nil {
return nil, err
}
selectedUTXOs, err := s.selectExternalSpendableUTXOs(externalUTXOs, request.Address)
estimate, err := s.rpcClient.GetFeeEstimate()
if err != nil {
return nil, err
}
feeRate := estimate.Estimate.NormalBuckets[0].Feerate
selectedUTXOs, err := s.selectExternalSpendableUTXOs(externalUTXOs, feeRate)
if err != nil {
return nil, err
}
@ -30,7 +38,7 @@ func (s *server) GetExternalSpendableUTXOs(_ context.Context, request *pb.GetExt
}, nil
}
func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOsByAddressesResponseMessage, address string) ([]*pb.UtxosByAddressesEntry, error) {
func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOsByAddressesResponseMessage, feeRate float64) ([]*pb.UtxosByAddressesEntry, error) {
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
if err != nil {
return nil, err
@ -42,8 +50,13 @@ func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOs
//we do not make because we do not know size, because of unspendable utxos
var selectedExternalUtxos []*pb.UtxosByAddressesEntry
feePerInput, err := s.estimateFeePerInput(feeRate)
if err != nil {
return nil, err
}
for _, entry := range externalUTXOs.Entries {
if !isExternalUTXOSpendable(entry, daaScore, maturity) {
if !isExternalUTXOSpendable(entry, daaScore, maturity, feePerInput) {
continue
}
selectedExternalUtxos = append(selectedExternalUtxos, libkaspawallet.AppMessageUTXOToKaspawalletdUTXO(entry))
@ -52,7 +65,7 @@ func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOs
return selectedExternalUtxos, nil
}
func isExternalUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
func isExternalUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64, feePerInput uint64) bool {
if !entry.UTXOEntry.IsCoinbase {
return true
} else if entry.UTXOEntry.Amount <= feePerInput {

View File

@ -4,6 +4,7 @@ import (
"context"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
"github.com/pkg/errors"
)
func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendResponse, error) {
@ -11,7 +12,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
defer s.lock.Unlock()
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.IsSendAll,
request.From, request.UseExistingChangeAddress)
request.From, request.UseExistingChangeAddress, request.FeePolicy)
if err != nil {
return nil, err
@ -24,7 +25,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
txIDs, err := s.broadcast(signedTransactions, false)
if err != nil {
return nil, err
return nil, errors.Wrapf(err, "error broadcasting transactions %s", EncodeTransactionsToHex(signedTransactions))
}
return &pb.SendResponse{TxIDs: txIDs, SignedTransactions: signedTransactions}, nil

View File

@ -37,6 +37,7 @@ type server struct {
lock sync.RWMutex
utxosSortedByAmount []*walletUTXO
mempoolExcludedUTXOs map[externalapi.DomainOutpoint]*walletUTXO
nextSyncStartIndex uint32
keysFile *keys.File
shutdown chan struct{}
@ -111,6 +112,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
params: params,
coinbaseMaturity: coinbaseMaturity,
utxosSortedByAmount: []*walletUTXO{},
mempoolExcludedUTXOs: map[externalapi.DomainOutpoint]*walletUTXO{},
nextSyncStartIndex: 0,
keysFile: keysFile,
shutdown: make(chan struct{}),

View File

@ -12,6 +12,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
"github.com/kaspanet/kaspad/util"
"github.com/kaspanet/kaspad/util/txmass"
)
// maybeAutoCompoundTransaction checks if a transaction's mass is higher that what is allowed for a standard
@ -20,14 +21,10 @@ import (
// into a change address.
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
// paying to the original transaction's payee.
func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte, toAddress util.Address,
changeAddress util.Address, changeWalletAddress *walletAddress) ([][]byte, error) {
transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes)
if err != nil {
return nil, err
}
func (s *server) maybeAutoCompoundTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64, maxFee uint64) ([][]byte, error) {
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress)
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
if err != nil {
return nil, err
}
@ -47,6 +44,8 @@ func (s *server) mergeTransaction(
toAddress util.Address,
changeAddress util.Address,
changeWalletAddress *walletAddress,
feeRate float64,
maxFee uint64,
) (*serialization.PartiallySignedTransaction, error) {
numOutputs := len(originalTransaction.Tx.Outputs)
if numOutputs > 2 || numOutputs == 0 {
@ -71,13 +70,19 @@ func (s *server) mergeTransaction(
DerivationPath: s.walletAddressPath(changeWalletAddress),
}
totalValue += output.Value
totalValue -= feePerInput
}
// We're overestimating a bit by assuming that any transaction will have a change output
fee, err := s.estimateFee(utxos, feeRate, maxFee, sentValue)
if err != nil {
return nil, err
}
totalValue -= fee
if totalValue < sentValue {
// sometimes the fees from compound transactions make the total output higher than what's available from selected
// utxos, in such cases - find one more UTXO and use it.
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue)
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue, feeRate)
if err != nil {
return nil, err
}
@ -96,19 +101,54 @@ func (s *server) mergeTransaction(
})
}
mergeTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
return libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures, payments, utxos)
}
func (s *server) transactionFeeRate(psTx *serialization.PartiallySignedTransaction) (float64, error) {
totalOuts := 0
for _, output := range psTx.Tx.Outputs {
totalOuts += int(output.Value)
}
totalIns := 0
for _, input := range psTx.PartiallySignedInputs {
totalIns += int(input.PrevOutput.Value)
}
if totalIns < totalOuts {
return 0, errors.Errorf("Transaction don't have enough funds to pay for the outputs")
}
fee := totalIns - totalOuts
mass, err := s.estimateComputeMassAfterSignatures(psTx)
if err != nil {
return 0, err
}
return float64(fee) / float64(mass), nil
}
func (s *server) checkTransactionFeeRate(psTx *serialization.PartiallySignedTransaction, maxFee uint64) error {
feeRate, err := s.transactionFeeRate(psTx)
if err != nil {
return err
}
if feeRate < 1 {
return errors.Errorf("setting --max-fee to %d results in a fee rate of %f, which is below the minimum allowed fee rate of 1 sompi/gram", maxFee, feeRate)
}
return nil
}
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64, maxFee uint64) ([]*serialization.PartiallySignedTransaction, error) {
err := s.checkTransactionFeeRate(transaction, maxFee)
if err != nil {
return nil, err
}
return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes)
}
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
changeAddress util.Address, changeWalletAddress *walletAddress) ([]*serialization.PartiallySignedTransaction, error) {
transactionMass, err := s.estimateMassAfterSignatures(transaction)
transactionMass, err := s.estimateComputeMassAfterSignatures(transaction)
if err != nil {
return nil, err
}
@ -117,7 +157,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
return []*serialization.PartiallySignedTransaction{transaction}, nil
}
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress)
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress, feeRate, maxFee)
if err != nil {
return nil, err
}
@ -127,19 +167,24 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
startIndex := i * inputCountPerSplit
endIndex := startIndex + inputCountPerSplit
var err error
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex)
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex, feeRate, maxFee)
if err != nil {
return nil, err
}
err = s.checkTransactionFeeRate(splitTransactions[i], maxFee)
if err != nil {
return nil, err
}
}
if len(splitTransactions) > 1 {
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
if err != nil {
return nil, err
}
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress)
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
if err != nil {
return nil, err
}
@ -152,7 +197,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
changeAddress util.Address) (splitCount, inputsPerSplitCount int, err error) {
changeAddress util.Address, feeRate float64, maxFee uint64) (splitCount, inputsPerSplitCount int, err error) {
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
// to calculate how much mass do all the inputs have
@ -172,7 +217,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0)
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0, feeRate, maxFee)
if err != nil {
return 0, 0, err
}
@ -190,7 +235,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
}
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) {
changeAddress util.Address, startIndex int, endIndex int, feeRate float64, maxFee uint64) (*serialization.PartiallySignedTransaction, error) {
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
totalSompi := uint64(0)
@ -206,25 +251,36 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign
})
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
totalSompi -= feePerInput
}
unsignedTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
if len(selectedUTXOs) != 0 {
fee, err := s.estimateFee(selectedUTXOs, feeRate, maxFee, totalSompi)
if err != nil {
return nil, err
}
totalSompi -= fee
}
return libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
s.keysFile.MinimumSignatures,
[]*libkaspawallet.Payment{{
Address: changeAddress,
Amount: totalSompi,
}}, selectedUTXOs)
if err != nil {
return nil, err
}
return serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
}
func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
return EstimateMassAfterSignatures(transaction, s.keysFile.ECDSA, s.keysFile.MinimumSignatures, s.txMassCalculator)
}
func (s *server) estimateComputeMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
return estimateComputeMassAfterSignatures(transaction, s.keysFile.ECDSA, s.keysFile.MinimumSignatures, s.txMassCalculator)
}
func createTransactionWithJunkFieldsForMassCalculation(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (*externalapi.DomainTransaction, error) {
transaction = transaction.Clone()
var signatureSize uint64
if s.keysFile.ECDSA {
if ecdsa {
signatureSize = secp256k1.SerializedECDSASignatureSize
} else {
signatureSize = secp256k1.SerializedSchnorrSignatureSize
@ -232,7 +288,7 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall
for i, input := range transaction.PartiallySignedInputs {
for j, pubKeyPair := range input.PubKeySignaturePairs {
if uint32(j) >= s.keysFile.MinimumSignatures {
if uint32(j) >= minimumSignatures {
break
}
pubKeyPair.Signature = make([]byte, signatureSize+1) // +1 for SigHashType
@ -240,15 +296,28 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall
transaction.Tx.Inputs[i].SigOpCount = byte(len(input.PubKeySignaturePairs))
}
transactionWithSignatures, err := libkaspawallet.ExtractTransactionDeserialized(transaction, s.keysFile.ECDSA)
return libkaspawallet.ExtractTransactionDeserialized(transaction, ecdsa)
}
func estimateComputeMassAfterSignatures(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (uint64, error) {
transactionWithSignatures, err := createTransactionWithJunkFieldsForMassCalculation(transaction, ecdsa, minimumSignatures, txMassCalculator)
if err != nil {
return 0, err
}
return s.txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil
return txMassCalculator.CalculateTransactionMass(transactionWithSignatures), nil
}
func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64) (
func EstimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction, ecdsa bool, minimumSignatures uint32, txMassCalculator *txmass.Calculator) (uint64, error) {
transactionWithSignatures, err := createTransactionWithJunkFieldsForMassCalculation(transaction, ecdsa, minimumSignatures, txMassCalculator)
if err != nil {
return 0, err
}
return txMassCalculator.CalculateTransactionOverallMass(transactionWithSignatures), nil
}
func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawallet.UTXO, requiredAmount uint64, feeRate float64) (
additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) {
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
@ -260,6 +329,11 @@ func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawa
alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
}
feePerInput, err := s.estimateFeePerInput(feeRate)
if err != nil {
return nil, 0, err
}
for _, utxo := range s.utxosSortedByAmount {
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
continue

View File

@ -20,9 +20,9 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
)
func TestEstimateMassAfterSignatures(t *testing.T) {
func TestEstimateComputeMassAfterSignatures(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
unsignedTransactionBytes, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
unsignedTransaction, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
defer teardown(false)
serverInstance := &server{
@ -33,14 +33,14 @@ func TestEstimateMassAfterSignatures(t *testing.T) {
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
}
unsignedTransaction, err := serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
estimatedMassAfterSignatures, err := serverInstance.estimateComputeMassAfterSignatures(unsignedTransaction)
if err != nil {
t.Fatalf("Error deserializing unsignedTransaction: %s", err)
t.Fatalf("Error from estimateComputeMassAfterSignatures: %s", err)
}
estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction)
unsignedTransactionBytes, err := serialization.SerializePartiallySignedTransaction(unsignedTransaction)
if err != nil {
t.Fatalf("Error from estimateMassAfterSignatures: %s", err)
t.Fatalf("Error deserializing unsignedTransaction: %s", err)
}
signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false)
@ -67,8 +67,67 @@ func TestEstimateMassAfterSignatures(t *testing.T) {
})
}
func TestEstimateMassAfterSignatures(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
unsignedTransaction, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
defer teardown(false)
for i := range unsignedTransaction.Tx.Inputs {
unsignedTransaction.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{}, false, 0)
unsignedTransaction.PartiallySignedInputs[i].PrevOutput = &externalapi.DomainTransactionOutput{
Value: 1,
ScriptPublicKey: &externalapi.ScriptPublicKey{},
}
}
serverInstance := &server{
params: params,
keysFile: &keys.File{MinimumSignatures: 2},
shutdown: make(chan struct{}),
addressSet: make(walletAddressSet),
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
}
estimatedMassAfterSignatures, err := serverInstance.estimateMassAfterSignatures(unsignedTransaction)
if err != nil {
t.Fatalf("Error from estimateMassAfterSignatures: %s", err)
}
unsignedTransactionBytes, err := serialization.SerializePartiallySignedTransaction(unsignedTransaction)
if err != nil {
t.Fatalf("Error deserializing unsignedTransaction: %s", err)
}
signedTxStep1Bytes, err := libkaspawallet.Sign(params, mnemonics[:1], unsignedTransactionBytes, false)
if err != nil {
t.Fatalf("Sign: %+v", err)
}
signedTxStep2Bytes, err := libkaspawallet.Sign(params, mnemonics[1:2], signedTxStep1Bytes, false)
if err != nil {
t.Fatalf("Sign: %+v", err)
}
extractedSignedTx, err := libkaspawallet.ExtractTransaction(signedTxStep2Bytes, false)
if err != nil {
t.Fatalf("ExtractTransaction: %+v", err)
}
for i := range extractedSignedTx.Inputs {
extractedSignedTx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{}, false, 0)
}
actualMassAfterSignatures := serverInstance.txMassCalculator.CalculateTransactionOverallMass(extractedSignedTx)
if estimatedMassAfterSignatures != actualMassAfterSignatures {
t.Errorf("Estimated mass after signatures: %d but actually got %d",
estimatedMassAfterSignatures, actualMassAfterSignatures)
}
})
}
func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) (
[]byte, []string, *dagconfig.Params, func(keepDataDir bool)) {
*serialization.PartiallySignedTransaction, []string, *dagconfig.Params, func(keepDataDir bool)) {
consensusConfig.BlockCoinbaseMaturity = 0
params := &consensusConfig.Params

View File

@ -6,6 +6,7 @@ import (
"time"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/pkg/errors"
@ -240,11 +241,8 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
}
}
mempoolExcludedUTXOs := make(map[externalapi.DomainOutpoint]*walletUTXO)
for _, entry := range entries {
if _, ok := exclude[*entry.Outpoint]; ok {
continue
}
outpoint, err := appmessage.RPCOutpointToDomainOutpoint(entry.Outpoint)
if err != nil {
return err
@ -260,11 +258,22 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
if !ok {
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
}
utxos = append(utxos, &walletUTXO{
utxo := &walletUTXO{
Outpoint: outpoint,
UTXOEntry: utxoEntry,
address: address,
})
}
if _, ok := exclude[*entry.Outpoint]; ok {
mempoolExcludedUTXOs[*outpoint] = utxo
} else {
utxos = append(utxos, &walletUTXO{
Outpoint: outpoint,
UTXOEntry: utxoEntry,
address: address,
})
}
}
sort.Slice(utxos, func(i, j int) bool { return utxos[i].UTXOEntry.Amount() > utxos[j].UTXOEntry.Amount() })
@ -272,6 +281,7 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
s.lock.Lock()
s.startTimeOfLastCompletedRefresh = refreshStart
s.utxosSortedByAmount = utxos
s.mempoolExcludedUTXOs = mempoolExcludedUTXOs
// Cleanup expired used outpoints to avoid a memory leak
for outpoint, broadcastTime := range s.usedOutpoints {

View File

@ -1,4 +1,4 @@
package main
package server
import (
"encoding/hex"
@ -9,7 +9,7 @@ import (
// We use a separator that is not in the hex alphabet, but which will not split selection with a double click
const hexTransactionsSeparator = "_"
func encodeTransactionsToHex(transactions [][]byte) string {
func EncodeTransactionsToHex(transactions [][]byte) string {
transactionsInHex := make([]string, len(transactions))
for i, transaction := range transactions {
transactionsInHex[i] = hex.EncodeToString(transaction)
@ -17,7 +17,7 @@ func encodeTransactionsToHex(transactions [][]byte) string {
return strings.Join(transactionsInHex, hexTransactionsSeparator)
}
func decodeTransactionsFromHex(transactionsHex string) ([][]byte, error) {
func DecodeTransactionsFromHex(transactionsHex string) ([][]byte, error) {
splitTransactionsHexes := strings.Split(transactionsHex, hexTransactionsSeparator)
transactions := make([][]byte, len(splitTransactionsHexes))

View File

@ -1,5 +1,5 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.18-alpine AS build
FROM golang:1.23-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad

View File

@ -7,6 +7,7 @@ import (
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
"github.com/kaspanet/kaspad/util"
"github.com/pkg/errors"
)
@ -31,15 +32,10 @@ func CreateUnsignedTransaction(
extendedPublicKeys []string,
minimumSignatures uint32,
payments []*Payment,
selectedUTXOs []*UTXO) ([]byte, error) {
selectedUTXOs []*UTXO) (*serialization.PartiallySignedTransaction, error) {
sortPublicKeys(extendedPublicKeys)
unsignedTransaction, err := createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
if err != nil {
return nil, err
}
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
return createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
}
func multiSigRedeemScript(extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) ([]byte, error) {
@ -247,6 +243,12 @@ func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.Pa
}
partiallySignedTransaction.Tx.Inputs[i].SignatureScript = sigScript
}
partiallySignedTransaction.Tx.Inputs[i].UTXOEntry = utxo.NewUTXOEntry(
input.PrevOutput.Value,
input.PrevOutput.ScriptPublicKey,
false, // This is a fake value
0, // This is a fake value
)
}
return partiallySignedTransaction.Tx, nil
}

View File

@ -2,6 +2,7 @@ package libkaspawallet_test
import (
"fmt"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"strings"
"testing"
@ -26,6 +27,20 @@ func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
})
}
func createUnsignedTransactionSerialized(
extendedPublicKeys []string,
minimumSignatures uint32,
payments []*libkaspawallet.Payment,
selectedUTXOs []*libkaspawallet.UTXO) ([]byte, error) {
tx, err := libkaspawallet.CreateUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
if err != nil {
return nil, err
}
return serialization.SerializePartiallySignedTransaction(tx)
}
func TestMultisig(t *testing.T) {
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
params := &consensusConfig.Params
@ -102,7 +117,7 @@ func TestMultisig(t *testing.T) {
},
}
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
unsignedTransaction, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
[]*libkaspawallet.Payment{{
Address: address,
Amount: 10,
@ -263,7 +278,7 @@ func TestP2PK(t *testing.T) {
},
}
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
unsignedTransaction, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
[]*libkaspawallet.Payment{{
Address: address,
Amount: 10,
@ -425,7 +440,7 @@ func TestMaxSompi(t *testing.T) {
},
}
unsignedTxWithLargeInputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
unsignedTxWithLargeInputAmount, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
[]*libkaspawallet.Payment{{
Address: address,
Amount: 10,
@ -476,7 +491,7 @@ func TestMaxSompi(t *testing.T) {
},
}
unsignedTxWithLargeInputAndOutputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
unsignedTxWithLargeInputAndOutputAmount, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
[]*libkaspawallet.Payment{{
Address: address,
Amount: 22e6 * constants.SompiPerKaspa,

View File

@ -20,6 +20,8 @@ func main() {
err = sign(config.(*signConfig))
case broadcastSubCmd:
err = broadcast(config.(*broadcastConfig))
case broadcastReplacementSubCmd:
err = broadcastReplacement(config.(*broadcastConfig))
case parseSubCmd:
err = parse(config.(*parseConfig))
case showAddressesSubCmd:
@ -36,6 +38,10 @@ func main() {
showVersion()
case getDaemonVersionSubCmd:
err = getDaemonVersion(config.(*getDaemonVersionConfig))
case bumpFeeSubCmd:
err = bumpFee(config.(*bumpFeeConfig))
case bumpFeeUnsignedSubCmd:
err = bumpFeeUnsigned(config.(*bumpFeeUnsignedConfig))
default:
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
}

View File

@ -3,13 +3,17 @@ package main
import (
"encoding/hex"
"fmt"
"io/ioutil"
"strings"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
"github.com/kaspanet/kaspad/util/txmass"
"github.com/pkg/errors"
"io/ioutil"
"strings"
)
func parse(conf *parseConfig) error {
@ -20,6 +24,11 @@ func parse(conf *parseConfig) error {
return errors.Errorf("Both --transaction and --transaction-file cannot be passed at the same time")
}
keysFile, err := keys.ReadKeysFile(conf.NetParams(), conf.KeysFile)
if err != nil {
return err
}
transactionHex := conf.Transaction
if conf.TransactionFile != "" {
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
@ -29,10 +38,12 @@ func parse(conf *parseConfig) error {
transactionHex = strings.TrimSpace(string(transactionHexBytes))
}
transactions, err := decodeTransactionsFromHex(transactionHex)
transactions, err := server.DecodeTransactionsFromHex(transactionHex)
if err != nil {
return err
}
txMassCalculator := txmass.NewCalculator(conf.NetParams().MassPerTxByte, conf.NetParams().MassPerScriptPubKeyByte, conf.NetParams().MassPerSigOp)
for i, transaction := range transactions {
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(transaction)
@ -78,7 +89,16 @@ func parse(conf *parseConfig) error {
}
fmt.Println()
fmt.Printf("Fee:\t%d Sompi\n\n", allInputSompi-allOutputSompi)
fee := allInputSompi - allOutputSompi
fmt.Printf("Fee:\t%d Sompi (%f KAS)\n", fee, float64(fee)/float64(constants.SompiPerKaspa))
mass, err := server.EstimateMassAfterSignatures(partiallySignedTransaction, keysFile.ECDSA, keysFile.MinimumSignatures, txMassCalculator)
if err != nil {
return err
}
fmt.Printf("Mass: %d grams\n", mass)
feeRate := float64(fee) / float64(mass)
fmt.Printf("Fee rate: %.2f Sompi/Gram\n", feeRate)
}
return nil

View File

@ -42,6 +42,23 @@ func send(conf *sendConfig) error {
}
}
var feePolicy *pb.FeePolicy
if conf.FeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_ExactFeeRate{
ExactFeeRate: conf.FeeRate,
},
}
} else if conf.MaxFeeRate > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFeeRate{MaxFeeRate: conf.MaxFeeRate},
}
} else if conf.MaxFee > 0 {
feePolicy = &pb.FeePolicy{
FeePolicy: &pb.FeePolicy_MaxFee{MaxFee: conf.MaxFee},
}
}
createUnsignedTransactionsResponse, err :=
daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
From: conf.FromAddresses,
@ -49,6 +66,7 @@ func send(conf *sendConfig) error {
Amount: sendAmountSompi,
IsSendAll: conf.IsSendAll,
UseExistingChangeAddress: conf.UseExistingChangeAddress,
FeePolicy: feePolicy,
})
if err != nil {
return err

View File

@ -6,6 +6,7 @@ import (
"os"
"strings"
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
"github.com/pkg/errors"
@ -40,7 +41,7 @@ func sign(conf *signConfig) error {
}
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
}
partiallySignedTransactions, err := decodeTransactionsFromHex(transactionsHex)
partiallySignedTransactions, err := server.DecodeTransactionsFromHex(transactionsHex)
if err != nil {
return err
}
@ -72,6 +73,6 @@ func sign(conf *signConfig) error {
fmt.Fprintln(os.Stderr, "Successfully signed transaction")
}
fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions))
fmt.Println(server.EncodeTransactionsToHex(updatedPartiallySignedTransactions))
return nil
}

View File

@ -1,5 +1,5 @@
# -- multistage docker build: stage #1: build stage
FROM golang:1.19-alpine AS build
FROM golang:1.23-alpine AS build
RUN mkdir -p /go/src/github.com/kaspanet/kaspad

View File

@ -329,6 +329,7 @@ func initTestBlockAcceptanceDataForClone() []*externalapi.BlockAcceptanceData {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -377,6 +378,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -410,6 +412,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -443,6 +446,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -476,6 +480,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -510,6 +515,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -614,6 +620,7 @@ func initTestAcceptanceDataForClone() []externalapi.AcceptanceData {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -664,6 +671,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -697,6 +705,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -730,6 +739,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -763,6 +773,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,

View File

@ -18,8 +18,9 @@ type DomainTransaction struct {
Gas uint64
Payload []byte
Fee uint64
Mass uint64
Fee uint64
Mass uint64
MassCommitment uint64
// ID is a field that is used to cache the transaction ID.
// Always use consensushashing.TransactionID instead of accessing this field directly
@ -47,23 +48,24 @@ func (tx *DomainTransaction) Clone() *DomainTransaction {
}
return &DomainTransaction{
Version: tx.Version,
Inputs: inputsClone,
Outputs: outputsClone,
LockTime: tx.LockTime,
SubnetworkID: *tx.SubnetworkID.Clone(),
Gas: tx.Gas,
Payload: payloadClone,
Fee: tx.Fee,
Mass: tx.Mass,
ID: idClone,
Version: tx.Version,
Inputs: inputsClone,
Outputs: outputsClone,
LockTime: tx.LockTime,
SubnetworkID: *tx.SubnetworkID.Clone(),
Gas: tx.Gas,
Payload: payloadClone,
Fee: tx.Fee,
Mass: tx.Mass,
MassCommitment: tx.MassCommitment,
ID: idClone,
}
}
// If this doesn't compile, it means the type definition has been changed, so it's
// an indication to update Equal and Clone accordingly.
var _ = DomainTransaction{0, []*DomainTransactionInput{}, []*DomainTransactionOutput{}, 0,
DomainSubnetworkID{}, 0, []byte{}, 0, 0,
DomainSubnetworkID{}, 0, []byte{}, 0, 0, 0,
&DomainTransactionID{}}
// Equal returns whether tx equals to other
@ -112,6 +114,10 @@ func (tx *DomainTransaction) Equal(other *DomainTransaction) bool {
return false
}
if tx.MassCommitment != other.MassCommitment {
return false
}
if tx.Fee != 0 && other.Fee != 0 && tx.Fee != other.Fee {
panic(errors.New("identical transactions should always have the same fee"))
}

View File

@ -83,6 +83,7 @@ func initTestBaseTransaction() *externalapi.DomainTransaction {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -112,6 +113,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -137,6 +139,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -162,6 +165,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01, 0x02}, //Changed
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -186,6 +190,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -213,6 +218,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -239,6 +245,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
1000000000, //Changed
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -263,6 +270,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -287,6 +295,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
2, //Changed
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -311,6 +320,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -341,6 +351,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -366,6 +377,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -390,6 +402,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
nil, //changed
},
expectedResult: true,
@ -411,6 +424,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -435,6 +449,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -461,6 +476,34 @@ func initTestTransactionToCompare() []*transactionToCompare {
[]byte{0x01},
0,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02}),
},
expectedResult: false,
},
{
tx: &externalapi.DomainTransaction{
1,
[]*externalapi.DomainTransactionInput{{externalapi.DomainOutpoint{
*externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x01}), 0xFFFF},
[]byte{1, 2, 3},
uint64(0xFFFFFFFF),
1,
utxo.NewUTXOEntry(1, &externalapi.ScriptPublicKey{Script: []byte{0, 1, 2, 3}, Version: 0}, true, 2)}},
[]*externalapi.DomainTransactionOutput{{uint64(0xFFFF),
&externalapi.ScriptPublicKey{Script: []byte{1, 2}, Version: 0}},
{uint64(0xFFFF),
&externalapi.ScriptPublicKey{Script: []byte{1, 3}, Version: 0}}},
1,
externalapi.DomainSubnetworkID{0x01},
1,
[]byte{0x01},
0,
1,
1, // Changed
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -536,6 +579,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
[]byte{0x01},
1,
1,
0,
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -555,6 +599,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
[]byte{0x01},
1,
1,
0,
nil,
},
expectedResult: false,
@ -569,6 +614,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
[]byte{0x01},
1,
1,
0,
nil,
},
expectedResult: true,
@ -583,6 +629,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
[]byte{0x01},
2, // Changed fee
1,
0,
nil,
},
expectsPanic: true,

View File

@ -27,7 +27,7 @@ func TransactionHash(tx *externalapi.DomainTransaction) *externalapi.DomainHash
// Encode the header and hash everything prior to the number of
// transactions.
writer := hashes.NewTransactionHashWriter()
err := serializeTransaction(writer, tx, txEncodingFull)
err := serializeTransaction(writer, tx, txEncodingFull, true)
if err != nil {
// It seems like this could only happen if the writer returned an error.
// and this writer should never return an error (no allocations or possible failures)
@ -52,7 +52,7 @@ func TransactionID(tx *externalapi.DomainTransaction) *externalapi.DomainTransac
encodingFlags = txEncodingExcludeSignatureScript
}
writer := hashes.NewTransactionIDWriter()
err := serializeTransaction(writer, tx, encodingFlags)
err := serializeTransaction(writer, tx, encodingFlags, false)
if err != nil {
// this writer never return errors (no allocations or possible failures) so errors can only come from validity checks,
// and we assume we never construct malformed transactions.
@ -74,7 +74,7 @@ func TransactionIDs(txs []*externalapi.DomainTransaction) []*externalapi.DomainT
return txIDs
}
func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodingFlags txEncoding) error {
func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodingFlags txEncoding, includeMass bool) error {
err := binaryserializer.PutUint16(w, tx.Version)
if err != nil {
return err
@ -126,6 +126,15 @@ func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodi
return err
}
if includeMass {
if tx.MassCommitment > 0 { // For backward compatibility, serialize MassCommitment only if it's not zero
err = binaryserializer.PutUint64(w, tx.MassCommitment)
if err != nil {
return err
}
}
}
return nil
}

19
go.mod
View File

@ -1,6 +1,6 @@
module github.com/kaspanet/kaspad
go 1.18
go 1.23
require (
github.com/btcsuite/btcutil v1.0.2
@ -8,7 +8,7 @@ require (
github.com/btcsuite/winsvc v1.0.0
github.com/davecgh/go-spew v1.1.1
github.com/gofrs/flock v0.8.1
github.com/golang/protobuf v1.5.2
github.com/golang/protobuf v1.5.4
github.com/jessevdk/go-flags v1.4.0
github.com/jrick/logrotate v1.0.0
github.com/kaspanet/go-muhash v0.0.4
@ -16,18 +16,17 @@ require (
github.com/pkg/errors v0.9.1
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
github.com/tyler-smith/go-bip39 v1.1.0
golang.org/x/crypto v0.1.0
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd
golang.org/x/term v0.5.0
google.golang.org/grpc v1.38.0
google.golang.org/protobuf v1.28.1
golang.org/x/crypto v0.28.0
golang.org/x/term v0.25.0
google.golang.org/grpc v1.69.2
google.golang.org/protobuf v1.35.1
)
require (
github.com/golang/snappy v0.0.1 // indirect
golang.org/x/net v0.7.0 // indirect
golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.7.0 // indirect
golang.org/x/net v0.30.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/text v0.19.0 // indirect
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect
gopkg.in/yaml.v2 v2.3.0 // indirect
)

52
go.sum
View File

@ -26,6 +26,10 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@ -40,8 +44,9 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/golang/snappy v0.0.1 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@ -49,9 +54,12 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
@ -80,17 +88,25 @@ github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d/go.mod h1:9OrXJ
github.com/tyler-smith/go-bip39 v1.1.0 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
golang.org/x/crypto v0.0.0-20170930174604-9419663f5a44/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200115085410-6d4e4cb37c7d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.1.0 h1:MDRAIl0xIo9Io2xV565hzXHw3zVseKrJKodhohM5CjU=
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd h1:zVFyTKZN/Q7mNRWSs1GOYnHM9NiFSJ54YVRsD0rNWT4=
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
@ -106,8 +122,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.7.0 h1:rJrUqqhjsgNp7KqAIc25s9pZnjU7TUcSY7HcVZjdn1g=
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
golang.org/x/net v0.30.0/go.mod h1:2wGyMJ5iFasEhkwi13ChkO/t1ECNC4X4eBKkVFyYFlU=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -121,16 +137,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.7.0 h1:4BRB4x83lYWy72KwLD/qYDuTu7q9PjSagHvijDw7cLo=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -142,7 +158,6 @@ golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@ -155,8 +170,9 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.38.0 h1:/9BgsAsa5nWe26HqOlvlgJnqBuktYOLCgjCPqsa56W0=
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -168,8 +184,8 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
google.golang.org/protobuf v1.35.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=

View File

@ -139,6 +139,28 @@ message KaspadMessage {
GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085;
GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086;
GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087;
PingRequestMessage pingRequest = 1088;
GetMetricsRequestMessage getMetricsRequest = 1090;
GetServerInfoRequestMessage getServerInfoRequest = 1092;
GetSyncStatusRequestMessage getSyncStatusRequest = 1094;
GetDaaScoreTimestampEstimateRequestMessage getDaaScoreTimestampEstimateRequest = 1096;
SubmitTransactionReplacementRequestMessage submitTransactionReplacementRequest = 1100;
GetConnectionsRequestMessage getConnectionsRequest = 1102;
GetSystemInfoRequestMessage getSystemInfoRequest = 1104;
GetFeeEstimateRequestMessage getFeeEstimateRequest = 1106;
GetFeeEstimateExperimentalRequestMessage getFeeEstimateExperimentalRequest = 1108;
GetCurrentBlockColorRequestMessage getCurrentBlockColorRequest = 1110;
PingResponseMessage pingResponse= 1089;
GetMetricsResponseMessage getMetricsResponse= 1091;
GetServerInfoResponseMessage getServerInfoResponse = 1093;
GetSyncStatusResponseMessage getSyncStatusResponse = 1095;
GetDaaScoreTimestampEstimateResponseMessage getDaaScoreTimestampEstimateResponse = 1097;
SubmitTransactionReplacementResponseMessage submitTransactionReplacementResponse = 1101;
GetConnectionsResponseMessage getConnectionsResponse= 1103;
GetSystemInfoResponseMessage getSystemInfoResponse= 1105;
GetFeeEstimateResponseMessage getFeeEstimateResponse = 1107;
GetFeeEstimateExperimentalResponseMessage getFeeEstimateExperimentalResponse = 1109;
GetCurrentBlockColorResponseMessage getCurrentBlockColorResponse = 1111;
}
}

View File

@ -1,7 +1,7 @@
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
// versions:
// - protoc-gen-go-grpc v1.2.0
// - protoc v3.17.2
// - protoc v3.12.3
// source: messages.proto
package protowire

View File

@ -3,26 +3,22 @@ package protowire;
option go_package = "github.com/kaspanet/kaspad/protowire";
message RequestAddressesMessage{
message RequestAddressesMessage {
bool includeAllSubnetworks = 1;
SubnetworkId subnetworkId = 2;
}
message AddressesMessage{
repeated NetAddress addressList = 1;
}
message AddressesMessage { repeated NetAddress addressList = 1; }
message NetAddress{
message NetAddress {
int64 timestamp = 1;
bytes ip = 3;
uint32 port = 4;
}
message SubnetworkId{
bytes bytes = 1;
}
message SubnetworkId { bytes bytes = 1; }
message TransactionMessage{
message TransactionMessage {
uint32 version = 1;
repeated TransactionInput inputs = 2;
repeated TransactionOutput outputs = 3;
@ -30,39 +26,38 @@ message TransactionMessage{
SubnetworkId subnetworkId = 5;
uint64 gas = 6;
bytes payload = 8;
uint64 mass = 9;
}
message TransactionInput{
message TransactionInput {
Outpoint previousOutpoint = 1;
bytes signatureScript = 2;
uint64 sequence = 3;
uint32 sigOpCount = 4;
}
message Outpoint{
message Outpoint {
TransactionId transactionId = 1;
uint32 index = 2;
}
message TransactionId{
bytes bytes = 1;
}
message TransactionId { bytes bytes = 1; }
message ScriptPublicKey {
bytes script = 1;
uint32 version = 2;
}
message TransactionOutput{
message TransactionOutput {
uint64 value = 1;
ScriptPublicKey scriptPublicKey = 2;
}
message BlockMessage{
message BlockMessage {
BlockHeader header = 1;
repeated TransactionMessage transactions = 2;
}
message BlockHeader{
message BlockHeader {
uint32 version = 1;
repeated BlockLevelParents parents = 12;
Hash hashMerkleRoot = 3;
@ -77,66 +72,43 @@ message BlockHeader{
uint64 blueScore = 13;
}
message BlockLevelParents {
repeated Hash parentHashes = 1;
}
message BlockLevelParents { repeated Hash parentHashes = 1; }
message Hash{
bytes bytes = 1;
}
message Hash { bytes bytes = 1; }
message RequestBlockLocatorMessage{
message RequestBlockLocatorMessage {
Hash highHash = 1;
uint32 limit = 2;
}
message BlockLocatorMessage{
repeated Hash hashes = 1;
}
message BlockLocatorMessage { repeated Hash hashes = 1; }
message RequestHeadersMessage{
message RequestHeadersMessage {
Hash lowHash = 1;
Hash highHash = 2;
}
message RequestNextHeadersMessage{
}
message RequestNextHeadersMessage {}
message DoneHeadersMessage{
}
message DoneHeadersMessage {}
message RequestRelayBlocksMessage{
repeated Hash hashes = 1;
}
message RequestRelayBlocksMessage { repeated Hash hashes = 1; }
message RequestTransactionsMessage {
repeated TransactionId ids = 1;
}
message RequestTransactionsMessage { repeated TransactionId ids = 1; }
message TransactionNotFoundMessage{
TransactionId id = 1;
}
message TransactionNotFoundMessage { TransactionId id = 1; }
message InvRelayBlockMessage{
Hash hash = 1;
}
message InvRelayBlockMessage { Hash hash = 1; }
message InvTransactionsMessage{
repeated TransactionId ids = 1;
}
message InvTransactionsMessage { repeated TransactionId ids = 1; }
message PingMessage{
uint64 nonce = 1;
}
message PingMessage { uint64 nonce = 1; }
message PongMessage{
uint64 nonce = 1;
}
message PongMessage { uint64 nonce = 1; }
message VerackMessage{
}
message VerackMessage {}
message VersionMessage{
message VersionMessage {
uint32 protocolVersion = 1;
uint64 services = 2;
int64 timestamp = 3;
@ -148,19 +120,15 @@ message VersionMessage{
string network = 10;
}
message RejectMessage{
string reason = 1;
}
message RejectMessage { string reason = 1; }
message RequestPruningPointUTXOSetMessage{
Hash pruningPointHash = 1;
}
message RequestPruningPointUTXOSetMessage { Hash pruningPointHash = 1; }
message PruningPointUtxoSetChunkMessage{
message PruningPointUtxoSetChunkMessage {
repeated OutpointAndUtxoEntryPair outpointAndUtxoEntryPairs = 1;
}
message OutpointAndUtxoEntryPair{
message OutpointAndUtxoEntryPair {
Outpoint outpoint = 1;
UtxoEntry utxoEntry = 2;
}
@ -172,54 +140,40 @@ message UtxoEntry {
bool isCoinbase = 4;
}
message RequestNextPruningPointUtxoSetChunkMessage {
}
message RequestNextPruningPointUtxoSetChunkMessage {}
message DonePruningPointUtxoSetChunksMessage {
}
message DonePruningPointUtxoSetChunksMessage {}
message RequestIBDBlocksMessage{
repeated Hash hashes = 1;
}
message RequestIBDBlocksMessage { repeated Hash hashes = 1; }
message UnexpectedPruningPointMessage{
}
message UnexpectedPruningPointMessage {}
message IbdBlockLocatorMessage {
Hash targetHash = 1;
repeated Hash blockLocatorHashes = 2;
}
message RequestIBDChainBlockLocatorMessage{
message RequestIBDChainBlockLocatorMessage {
Hash lowHash = 1;
Hash highHash = 2;
}
message IbdChainBlockLocatorMessage {
repeated Hash blockLocatorHashes = 1;
}
message IbdChainBlockLocatorMessage { repeated Hash blockLocatorHashes = 1; }
message RequestAnticoneMessage{
message RequestAnticoneMessage {
Hash blockHash = 1;
Hash contextHash = 2;
}
message IbdBlockLocatorHighestHashMessage {
Hash highestHash = 1;
}
message IbdBlockLocatorHighestHashMessage { Hash highestHash = 1; }
message IbdBlockLocatorHighestHashNotFoundMessage {
}
message IbdBlockLocatorHighestHashNotFoundMessage {}
message BlockHeadersMessage {
repeated BlockHeader blockHeaders = 1;
}
message BlockHeadersMessage { repeated BlockHeader blockHeaders = 1; }
message RequestPruningPointAndItsAnticoneMessage {
}
message RequestPruningPointAndItsAnticoneMessage {}
message RequestNextPruningPointAndItsAnticoneBlocksMessage{
}
message RequestNextPruningPointAndItsAnticoneBlocksMessage {}
message BlockWithTrustedDataMessage {
BlockMessage block = 1;
@ -257,26 +211,19 @@ message BluesAnticoneSizes {
uint32 anticoneSize = 2;
}
message DoneBlocksWithTrustedDataMessage {
}
message DoneBlocksWithTrustedDataMessage {}
message PruningPointsMessage {
repeated BlockHeader headers = 1;
}
message PruningPointsMessage { repeated BlockHeader headers = 1; }
message RequestPruningPointProofMessage {
}
message RequestPruningPointProofMessage {}
message PruningPointProofMessage {
repeated PruningPointProofHeaderArray headers = 1;
}
message PruningPointProofHeaderArray {
repeated BlockHeader headers = 1;
}
message PruningPointProofHeaderArray { repeated BlockHeader headers = 1; }
message ReadyMessage {
}
message ReadyMessage {}
message BlockWithTrustedDataV4Message {
BlockMessage block = 1;

View File

@ -1,11 +1,14 @@
// RPC-related types. Request messages, response messages, and dependant types.
//
// Clients are expected to build RequestMessages and wrap them in KaspadMessage. (see messages.proto)
// Clients are expected to build RequestMessages and wrap them in KaspadMessage.
// (see messages.proto)
//
// Having received a RequestMessage, (wrapped in a KaspadMessage) the RPC server will respond with a
// ResponseMessage (likewise wrapped in a KaspadMessage) respective to the original RequestMessage.
// Having received a RequestMessage, (wrapped in a KaspadMessage) the RPC server
// will respond with a ResponseMessage (likewise wrapped in a KaspadMessage)
// respective to the original RequestMessage.
//
// **IMPORTANT:** This API is a work in progress and is subject to break between versions.
// **IMPORTANT:** This API is a work in progress and is subject to break between
// versions.
//
syntax = "proto3";
package protowire;
@ -14,10 +17,9 @@ option go_package = "github.com/kaspanet/kaspad/protowire";
// RPCError represents a generic non-internal error.
//
// Receivers of any ResponseMessage are expected to check whether its error field is not null.
message RPCError{
string message = 1;
}
// Receivers of any ResponseMessage are expected to check whether its error
// field is not null.
message RPCError { string message = 1; }
message RpcBlock {
RpcBlockHeader header = 1;
@ -40,11 +42,9 @@ message RpcBlockHeader {
uint64 blueScore = 13;
}
message RpcBlockLevelParents {
repeated string parentHashes = 1;
}
message RpcBlockLevelParents { repeated string parentHashes = 1; }
message RpcBlockVerboseData{
message RpcBlockVerboseData {
string hash = 1;
double difficulty = 11;
string selectedParentHash = 13;
@ -66,6 +66,7 @@ message RpcTransaction {
uint64 gas = 6;
string payload = 8;
RpcTransactionVerboseData verboseData = 9;
uint64 mass = 10;
}
message RpcTransactionInput {
@ -99,7 +100,7 @@ message RpcUtxoEntry {
bool isCoinbase = 4;
}
message RpcTransactionVerboseData{
message RpcTransactionVerboseData {
string transactionId = 1;
string hash = 2;
uint64 mass = 4;
@ -107,35 +108,35 @@ message RpcTransactionVerboseData{
uint64 blockTime = 14;
}
message RpcTransactionInputVerboseData{
}
message RpcTransactionInputVerboseData {}
message RpcTransactionOutputVerboseData{
message RpcTransactionOutputVerboseData {
string scriptPublicKeyType = 5;
string scriptPublicKeyAddress = 6;
}
// GetCurrentNetworkRequestMessage requests the network kaspad is currently running against.
// GetCurrentNetworkRequestMessage requests the network kaspad is currently
// running against.
//
// Possible networks are: Mainnet, Testnet, Simnet, Devnet
message GetCurrentNetworkRequestMessage{
}
message GetCurrentNetworkRequestMessage {}
message GetCurrentNetworkResponseMessage{
message GetCurrentNetworkResponseMessage {
string currentNetwork = 1;
RPCError error = 1000;
}
// SubmitBlockRequestMessage requests to submit a block into the DAG.
// Blocks are generally expected to have been generated using the getBlockTemplate call.
// Blocks are generally expected to have been generated using the
// getBlockTemplate call.
//
// See: GetBlockTemplateRequestMessage
message SubmitBlockRequestMessage{
message SubmitBlockRequestMessage {
RpcBlock block = 2;
bool allowNonDAABlocks = 3;
}
message SubmitBlockResponseMessage{
message SubmitBlockResponseMessage {
enum RejectReason {
NONE = 0;
BLOCK_INVALID = 1;
@ -146,115 +147,108 @@ message SubmitBlockResponseMessage{
}
// GetBlockTemplateRequestMessage requests a current block template.
// Callers are expected to solve the block template and submit it using the submitBlock call
// Callers are expected to solve the block template and submit it using the
// submitBlock call
//
// See: SubmitBlockRequestMessage
message GetBlockTemplateRequestMessage{
message GetBlockTemplateRequestMessage {
// Which kaspa address should the coinbase block reward transaction pay into
string payAddress = 1;
string extraData = 2;
}
message GetBlockTemplateResponseMessage{
message GetBlockTemplateResponseMessage {
RpcBlock block = 3;
// Whether kaspad thinks that it's synced.
// Callers are discouraged (but not forbidden) from solving blocks when kaspad is not synced.
// That is because when kaspad isn't in sync with the rest of the network there's a high
// chance the block will never be accepted, thus the solving effort would have been wasted.
// Callers are discouraged (but not forbidden) from solving blocks when kaspad
// is not synced. That is because when kaspad isn't in sync with the rest of
// the network there's a high chance the block will never be accepted, thus
// the solving effort would have been wasted.
bool isSynced = 2;
RPCError error = 1000;
}
// NotifyBlockAddedRequestMessage registers this connection for blockAdded notifications.
// NotifyBlockAddedRequestMessage registers this connection for blockAdded
// notifications.
//
// See: BlockAddedNotificationMessage
message NotifyBlockAddedRequestMessage{
}
message NotifyBlockAddedRequestMessage {}
message NotifyBlockAddedResponseMessage{
RPCError error = 1000;
}
message NotifyBlockAddedResponseMessage { RPCError error = 1000; }
// BlockAddedNotificationMessage is sent whenever a blocks has been added (NOT accepted)
// into the DAG.
// BlockAddedNotificationMessage is sent whenever a blocks has been added (NOT
// accepted) into the DAG.
//
// See: NotifyBlockAddedRequestMessage
message BlockAddedNotificationMessage{
RpcBlock block = 3;
}
message BlockAddedNotificationMessage { RpcBlock block = 3; }
// GetPeerAddressesRequestMessage requests the list of known kaspad addresses in the
// current network. (mainnet, testnet, etc.)
message GetPeerAddressesRequestMessage{
}
// GetPeerAddressesRequestMessage requests the list of known kaspad addresses in
// the current network. (mainnet, testnet, etc.)
message GetPeerAddressesRequestMessage {}
message GetPeerAddressesResponseMessage{
message GetPeerAddressesResponseMessage {
repeated GetPeerAddressesKnownAddressMessage addresses = 1;
repeated GetPeerAddressesKnownAddressMessage bannedAddresses = 2;
RPCError error = 1000;
}
message GetPeerAddressesKnownAddressMessage {
string Addr = 1;
}
message GetPeerAddressesKnownAddressMessage { string Addr = 1; }
// GetSelectedTipHashRequestMessage requests the hash of the current virtual's
// selected parent.
message GetSelectedTipHashRequestMessage{
}
message GetSelectedTipHashRequestMessage {}
message GetSelectedTipHashResponseMessage{
message GetSelectedTipHashResponseMessage {
string selectedTipHash = 1;
RPCError error = 1000;
}
// GetMempoolEntryRequestMessage requests information about a specific transaction
// in the mempool.
message GetMempoolEntryRequestMessage{
// GetMempoolEntryRequestMessage requests information about a specific
// transaction in the mempool.
message GetMempoolEntryRequestMessage {
// The transaction's TransactionID.
string txId = 1;
bool includeOrphanPool = 2;
bool filterTransactionPool = 3;
}
message GetMempoolEntryResponseMessage{
message GetMempoolEntryResponseMessage {
MempoolEntry entry = 1;
RPCError error = 1000;
}
// GetMempoolEntriesRequestMessage requests information about all the transactions
// currently in the mempool.
message GetMempoolEntriesRequestMessage{
bool includeOrphanPool = 1;
bool filterTransactionPool = 2;
// GetMempoolEntriesRequestMessage requests information about all the
// transactions currently in the mempool.
message GetMempoolEntriesRequestMessage {
bool includeOrphanPool = 1;
bool filterTransactionPool = 2;
}
message GetMempoolEntriesResponseMessage{
message GetMempoolEntriesResponseMessage {
repeated MempoolEntry entries = 1;
RPCError error = 1000;
}
message MempoolEntry{
message MempoolEntry {
uint64 fee = 1;
RpcTransaction transaction = 3;
bool isOrphan = 4;
}
// GetConnectedPeerInfoRequestMessage requests information about all the p2p peers
// currently connected to this kaspad.
message GetConnectedPeerInfoRequestMessage{
}
// GetConnectedPeerInfoRequestMessage requests information about all the p2p
// peers currently connected to this kaspad.
message GetConnectedPeerInfoRequestMessage {}
message GetConnectedPeerInfoResponseMessage{
message GetConnectedPeerInfoResponseMessage {
repeated GetConnectedPeerInfoMessage infos = 1;
RPCError error = 1000;
}
message GetConnectedPeerInfoMessage{
message GetConnectedPeerInfoMessage {
string id = 1;
string address = 2;
@ -278,58 +272,58 @@ message GetConnectedPeerInfoMessage{
// AddPeerRequestMessage adds a peer to kaspad's outgoing connection list.
// This will, in most cases, result in kaspad connecting to said peer.
message AddPeerRequestMessage{
message AddPeerRequestMessage {
string address = 1;
// Whether to keep attempting to connect to this peer after disconnection
bool isPermanent = 2;
}
message AddPeerResponseMessage{
RPCError error = 1000;
}
message AddPeerResponseMessage { RPCError error = 1000; }
// SubmitTransactionRequestMessage submits a transaction to the mempool
message SubmitTransactionRequestMessage{
message SubmitTransactionRequestMessage {
RpcTransaction transaction = 1;
bool allowOrphan = 2;
}
message SubmitTransactionResponseMessage{
message SubmitTransactionResponseMessage {
// The transaction ID of the submitted transaction
string transactionId = 1;
RPCError error = 1000;
}
// NotifyVirtualSelectedParentChainChangedRequestMessage registers this connection for virtualSelectedParentChainChanged notifications.
// NotifyVirtualSelectedParentChainChangedRequestMessage registers this
// connection for virtualSelectedParentChainChanged notifications.
//
// See: VirtualSelectedParentChainChangedNotificationMessage
message NotifyVirtualSelectedParentChainChangedRequestMessage{
message NotifyVirtualSelectedParentChainChangedRequestMessage {
bool includeAcceptedTransactionIds = 1;
}
message NotifyVirtualSelectedParentChainChangedResponseMessage{
message NotifyVirtualSelectedParentChainChangedResponseMessage {
RPCError error = 1000;
}
// VirtualSelectedParentChainChangedNotificationMessage is sent whenever the DAG's selected parent
// chain had changed.
// VirtualSelectedParentChainChangedNotificationMessage is sent whenever the
// DAG's selected parent chain had changed.
//
// See: NotifyVirtualSelectedParentChainChangedRequestMessage
message VirtualSelectedParentChainChangedNotificationMessage{
message VirtualSelectedParentChainChangedNotificationMessage {
// The chain blocks that were removed, in high-to-low order
repeated string removedChainBlockHashes = 1;
// The chain blocks that were added, in low-to-high order
repeated string addedChainBlockHashes = 3;
// Will be filled only if `includeAcceptedTransactionIds = true` in the notify request.
// Will be filled only if `includeAcceptedTransactionIds = true` in the notify
// request.
repeated AcceptedTransactionIds acceptedTransactionIds = 2;
}
// GetBlockRequestMessage requests information about a specific block
message GetBlockRequestMessage{
message GetBlockRequestMessage {
// The hash of the requested block
string hash = 1;
@ -337,7 +331,7 @@ message GetBlockRequestMessage{
bool includeTransactions = 3;
}
message GetBlockResponseMessage{
message GetBlockResponseMessage {
RpcBlock block = 3;
RPCError error = 1000;
}
@ -345,28 +339,26 @@ message GetBlockResponseMessage{
// GetSubnetworkRequestMessage requests information about a specific subnetwork
//
// Currently unimplemented
message GetSubnetworkRequestMessage{
string subnetworkId = 1;
}
message GetSubnetworkRequestMessage { string subnetworkId = 1; }
message GetSubnetworkResponseMessage{
message GetSubnetworkResponseMessage {
uint64 gasLimit = 1;
RPCError error = 1000;
}
// GetVirtualSelectedParentChainFromBlockRequestMessage requests the virtual selected
// parent chain from some startHash to this kaspad's current virtual
message GetVirtualSelectedParentChainFromBlockRequestMessage{
// GetVirtualSelectedParentChainFromBlockRequestMessage requests the virtual
// selected parent chain from some startHash to this kaspad's current virtual
message GetVirtualSelectedParentChainFromBlockRequestMessage {
string startHash = 1;
bool includeAcceptedTransactionIds = 2;
}
message AcceptedTransactionIds{
message AcceptedTransactionIds {
string acceptingBlockHash = 1;
repeated string acceptedTransactionIds = 2;
}
message GetVirtualSelectedParentChainFromBlockResponseMessage{
message GetVirtualSelectedParentChainFromBlockResponseMessage {
// The chain blocks that were removed, in high-to-low order
repeated string removedChainBlockHashes = 1;
@ -374,43 +366,42 @@ message GetVirtualSelectedParentChainFromBlockResponseMessage{
repeated string addedChainBlockHashes = 3;
// The transactions accepted by each block in addedChainBlockHashes.
// Will be filled only if `includeAcceptedTransactionIds = true` in the request.
// Will be filled only if `includeAcceptedTransactionIds = true` in the
// request.
repeated AcceptedTransactionIds acceptedTransactionIds = 2;
RPCError error = 1000;
}
// GetBlocksRequestMessage requests blocks between a certain block lowHash up to this
// kaspad's current virtual.
message GetBlocksRequestMessage{
// GetBlocksRequestMessage requests blocks between a certain block lowHash up to
// this kaspad's current virtual.
message GetBlocksRequestMessage {
string lowHash = 1;
bool includeBlocks = 2;
bool includeTransactions = 3;
}
message GetBlocksResponseMessage{
message GetBlocksResponseMessage {
repeated string blockHashes = 4;
repeated RpcBlock blocks = 3;
RPCError error = 1000;
}
// GetBlockCountRequestMessage requests the current number of blocks in this kaspad.
// Note that this number may decrease as pruning occurs.
message GetBlockCountRequestMessage{
}
// GetBlockCountRequestMessage requests the current number of blocks in this
// kaspad. Note that this number may decrease as pruning occurs.
message GetBlockCountRequestMessage {}
message GetBlockCountResponseMessage{
message GetBlockCountResponseMessage {
uint64 blockCount = 1;
uint64 headerCount = 2;
RPCError error = 1000;
}
// GetBlockDagInfoRequestMessage requests general information about the current state
// of this kaspad's DAG.
message GetBlockDagInfoRequestMessage{
}
// GetBlockDagInfoRequestMessage requests general information about the current
// state of this kaspad's DAG.
message GetBlockDagInfoRequestMessage {}
message GetBlockDagInfoResponseMessage{
message GetBlockDagInfoResponseMessage {
string networkName = 1;
uint64 blockCount = 2;
uint64 headerCount = 3;
@ -423,52 +414,40 @@ message GetBlockDagInfoResponseMessage{
RPCError error = 1000;
}
message ResolveFinalityConflictRequestMessage{
string finalityBlockHash = 1;
}
message ResolveFinalityConflictRequestMessage { string finalityBlockHash = 1; }
message ResolveFinalityConflictResponseMessage{
RPCError error = 1000;
}
message ResolveFinalityConflictResponseMessage { RPCError error = 1000; }
message NotifyFinalityConflictsRequestMessage{
}
message NotifyFinalityConflictsRequestMessage {}
message NotifyFinalityConflictsResponseMessage{
RPCError error = 1000;
}
message NotifyFinalityConflictsResponseMessage { RPCError error = 1000; }
message FinalityConflictNotificationMessage{
string violatingBlockHash = 1;
}
message FinalityConflictNotificationMessage { string violatingBlockHash = 1; }
message FinalityConflictResolvedNotificationMessage{
message FinalityConflictResolvedNotificationMessage {
string finalityBlockHash = 1;
}
// ShutDownRequestMessage shuts down this kaspad.
message ShutDownRequestMessage{
}
message ShutDownRequestMessage {}
message ShutDownResponseMessage{
RPCError error = 1000;
}
message ShutDownResponseMessage { RPCError error = 1000; }
// GetHeadersRequestMessage requests headers between the given startHash and the
// current virtual, up to the given limit.
message GetHeadersRequestMessage{
message GetHeadersRequestMessage {
string startHash = 1;
uint64 limit = 2;
bool isAscending = 3;
}
message GetHeadersResponseMessage{
message GetHeadersResponseMessage {
repeated string headers = 1;
RPCError error = 1000;
}
// NotifyUtxosChangedRequestMessage registers this connection for utxoChanged notifications
// for the given addresses.
// NotifyUtxosChangedRequestMessage registers this connection for utxoChanged
// notifications for the given addresses.
//
// This call is only available when this kaspad was started with `--utxoindex`
//
@ -477,11 +456,10 @@ message NotifyUtxosChangedRequestMessage {
repeated string addresses = 1; // Leave empty to get all updates
}
message NotifyUtxosChangedResponseMessage {
RPCError error = 1000;
}
message NotifyUtxosChangedResponseMessage { RPCError error = 1000; }
// UtxosChangedNotificationMessage is sent whenever the UTXO index had been updated.
// UtxosChangedNotificationMessage is sent whenever the UTXO index had been
// updated.
//
// See: NotifyUtxosChangedRequestMessage
message UtxosChangedNotificationMessage {
@ -495,8 +473,8 @@ message UtxosByAddressesEntry {
RpcUtxoEntry utxoEntry = 3;
}
// StopNotifyingUtxosChangedRequestMessage unregisters this connection for utxoChanged notifications
// for the given addresses.
// StopNotifyingUtxosChangedRequestMessage unregisters this connection for
// utxoChanged notifications for the given addresses.
//
// This call is only available when this kaspad was started with `--utxoindex`
//
@ -505,16 +483,13 @@ message StopNotifyingUtxosChangedRequestMessage {
repeated string addresses = 1;
}
message StopNotifyingUtxosChangedResponseMessage {
RPCError error = 1000;
}
message StopNotifyingUtxosChangedResponseMessage { RPCError error = 1000; }
// GetUtxosByAddressesRequestMessage requests all current UTXOs for the given kaspad addresses
// GetUtxosByAddressesRequestMessage requests all current UTXOs for the given
// kaspad addresses
//
// This call is only available when this kaspad was started with `--utxoindex`
message GetUtxosByAddressesRequestMessage {
repeated string addresses = 1;
}
message GetUtxosByAddressesRequestMessage { repeated string addresses = 1; }
message GetUtxosByAddressesResponseMessage {
repeated UtxosByAddressesEntry entries = 1;
@ -522,12 +497,11 @@ message GetUtxosByAddressesResponseMessage {
RPCError error = 1000;
}
// GetBalanceByAddressRequest returns the total balance in unspent transactions towards a given address
//
// GetBalanceByAddressRequest returns the total balance in unspent transactions
// towards a given address
//
// This call is only available when this kaspad was started with `--utxoindex`
message GetBalanceByAddressRequestMessage {
string address = 1;
}
message GetBalanceByAddressRequestMessage { string address = 1; }
message GetBalanceByAddressResponseMessage {
uint64 balance = 1;
@ -535,11 +509,9 @@ message GetBalanceByAddressResponseMessage {
RPCError error = 1000;
}
message GetBalancesByAddressesRequestMessage {
repeated string addresses = 1;
}
message GetBalancesByAddressesRequestMessage { repeated string addresses = 1; }
message BalancesByAddressEntry{
message BalancesByAddressEntry {
string address = 1;
uint64 balance = 2;
@ -552,10 +524,9 @@ message GetBalancesByAddressesResponseMessage {
RPCError error = 1000;
}
// GetVirtualSelectedParentBlueScoreRequestMessage requests the blue score of the current selected parent
// of the virtual block.
message GetVirtualSelectedParentBlueScoreRequestMessage {
}
// GetVirtualSelectedParentBlueScoreRequestMessage requests the blue score of
// the current selected parent of the virtual block.
message GetVirtualSelectedParentBlueScoreRequestMessage {}
message GetVirtualSelectedParentBlueScoreResponseMessage {
uint64 blueScore = 1;
@ -563,19 +534,18 @@ message GetVirtualSelectedParentBlueScoreResponseMessage {
RPCError error = 1000;
}
// NotifyVirtualSelectedParentBlueScoreChangedRequestMessage registers this connection for
// virtualSelectedParentBlueScoreChanged notifications.
// NotifyVirtualSelectedParentBlueScoreChangedRequestMessage registers this
// connection for virtualSelectedParentBlueScoreChanged notifications.
//
// See: VirtualSelectedParentBlueScoreChangedNotificationMessage
message NotifyVirtualSelectedParentBlueScoreChangedRequestMessage {
}
message NotifyVirtualSelectedParentBlueScoreChangedRequestMessage {}
message NotifyVirtualSelectedParentBlueScoreChangedResponseMessage {
RPCError error = 1000;
}
// VirtualSelectedParentBlueScoreChangedNotificationMessage is sent whenever the blue score
// of the virtual's selected parent changes.
// VirtualSelectedParentBlueScoreChangedNotificationMessage is sent whenever the
// blue score of the virtual's selected parent changes.
//
// See NotifyVirtualSelectedParentBlueScoreChangedRequestMessage
message VirtualSelectedParentBlueScoreChangedNotificationMessage {
@ -586,12 +556,9 @@ message VirtualSelectedParentBlueScoreChangedNotificationMessage {
// virtualDaaScoreChanged notifications.
//
// See: VirtualDaaScoreChangedNotificationMessage
message NotifyVirtualDaaScoreChangedRequestMessage {
}
message NotifyVirtualDaaScoreChangedRequestMessage {}
message NotifyVirtualDaaScoreChangedResponseMessage {
RPCError error = 1000;
}
message NotifyVirtualDaaScoreChangedResponseMessage { RPCError error = 1000; }
// VirtualDaaScoreChangedNotificationMessage is sent whenever the DAA score
// of the virtual changes.
@ -607,57 +574,44 @@ message VirtualDaaScoreChangedNotificationMessage {
// This call is only available when this kaspad was started with `--utxoindex`
//
// See: NotifyPruningPointUTXOSetOverrideResponseMessage
message NotifyPruningPointUTXOSetOverrideRequestMessage {
}
message NotifyPruningPointUTXOSetOverrideRequestMessage {}
message NotifyPruningPointUTXOSetOverrideResponseMessage {
RPCError error = 1000;
}
// PruningPointUTXOSetOverrideNotificationMessage is sent whenever the UTXO index
// resets due to pruning point change via IBD.
// PruningPointUTXOSetOverrideNotificationMessage is sent whenever the UTXO
// index resets due to pruning point change via IBD.
//
// See NotifyPruningPointUTXOSetOverrideRequestMessage
message PruningPointUTXOSetOverrideNotificationMessage {
}
message PruningPointUTXOSetOverrideNotificationMessage {}
// StopNotifyingPruningPointUTXOSetOverrideRequestMessage unregisters this connection for
// pruning point UTXO set override notifications.
// StopNotifyingPruningPointUTXOSetOverrideRequestMessage unregisters this
// connection for pruning point UTXO set override notifications.
//
// This call is only available when this kaspad was started with `--utxoindex`
//
// See: PruningPointUTXOSetOverrideNotificationMessage
message StopNotifyingPruningPointUTXOSetOverrideRequestMessage {
}
message StopNotifyingPruningPointUTXOSetOverrideRequestMessage {}
message StopNotifyingPruningPointUTXOSetOverrideResponseMessage {
RPCError error = 1000;
}
// BanRequestMessage bans the given ip.
message BanRequestMessage{
string ip = 1;
}
message BanRequestMessage { string ip = 1; }
message BanResponseMessage{
RPCError error = 1000;
}
message BanResponseMessage { RPCError error = 1000; }
// UnbanRequestMessage unbans the given ip.
message UnbanRequestMessage{
string ip = 1;
}
message UnbanRequestMessage { string ip = 1; }
message UnbanResponseMessage{
RPCError error = 1000;
}
message UnbanResponseMessage { RPCError error = 1000; }
// GetInfoRequestMessage returns info about the node.
message GetInfoRequestMessage{
}
message GetInfoRequestMessage {}
message GetInfoResponseMessage{
message GetInfoResponseMessage {
string p2pId = 1;
uint64 mempoolSize = 2;
string serverVersion = 3;
@ -666,12 +620,12 @@ message GetInfoResponseMessage{
RPCError error = 1000;
}
message EstimateNetworkHashesPerSecondRequestMessage{
message EstimateNetworkHashesPerSecondRequestMessage {
uint32 windowSize = 1;
string startHash = 2;
}
message EstimateNetworkHashesPerSecondResponseMessage{
message EstimateNetworkHashesPerSecondResponseMessage {
uint64 networkHashesPerSecond = 1;
RPCError error = 1000;
}
@ -680,44 +634,258 @@ message EstimateNetworkHashesPerSecondResponseMessage{
// NewBlockTemplate notifications.
//
// See: NewBlockTemplateNotificationMessage
message NotifyNewBlockTemplateRequestMessage {
}
message NotifyNewBlockTemplateRequestMessage {}
message NotifyNewBlockTemplateResponseMessage {
RPCError error = 1000;
}
message NotifyNewBlockTemplateResponseMessage { RPCError error = 1000; }
// NewBlockTemplateNotificationMessage is sent whenever a new updated block template is
// available for miners.
// NewBlockTemplateNotificationMessage is sent whenever a new updated block
// template is available for miners.
//
// See NotifyNewBlockTemplateRequestMessage
message NewBlockTemplateNotificationMessage {
}
message NewBlockTemplateNotificationMessage {}
message MempoolEntryByAddress{
message MempoolEntryByAddress {
string address = 1;
repeated MempoolEntry sending = 2;
repeated MempoolEntry receiving = 3;
}
message GetMempoolEntriesByAddressesRequestMessage{
message GetMempoolEntriesByAddressesRequestMessage {
repeated string addresses = 1;
bool includeOrphanPool = 2;
bool filterTransactionPool = 3;
}
message GetMempoolEntriesByAddressesResponseMessage{
message GetMempoolEntriesByAddressesResponseMessage {
repeated MempoolEntryByAddress entries = 1;
RPCError error = 1000;
}
message GetCoinSupplyRequestMessage{
message GetCoinSupplyRequestMessage {}
message GetCoinSupplyResponseMessage {
uint64 maxSompi =
1; // note: this is a hard coded maxSupply, actual maxSupply is expected
// to deviate by upto -5%, but cannot be measured exactly.
uint64 circulatingSompi = 2;
RPCError error = 1000;
}
message GetCoinSupplyResponseMessage{
uint64 maxSompi = 1; // note: this is a hard coded maxSupply, actual maxSupply is expected to deviate by upto -5%, but cannot be measured exactly.
uint64 circulatingSompi = 2;
message PingRequestMessage {}
RPCError error = 1000;
message PingResponseMessage { RPCError error = 1000; }
message ProcessMetrics {
uint64 residentSetSize = 1;
uint64 virtualMemorySize = 2;
uint32 coreNum = 3;
float cpuUsage = 4;
uint32 fdNum = 5;
uint64 diskIoReadBytes = 6;
uint64 diskIoWriteBytes = 7;
float diskIoReadPerSec = 8;
float diskIoWritePerSec = 9;
}
message ConnectionMetrics {
uint32 borshLiveConnections = 31;
uint64 borshConnectionAttempts = 32;
uint64 borshHandshakeFailures = 33;
uint32 jsonLiveConnections = 41;
uint64 jsonConnectionAttempts = 42;
uint64 jsonHandshakeFailures = 43;
uint32 activePeers = 51;
}
message BandwidthMetrics {
uint64 borshBytesTx = 61;
uint64 borshBytesRx = 62;
uint64 jsonBytesTx = 63;
uint64 jsonBytesRx = 64;
uint64 grpcP2pBytesTx = 65;
uint64 grpcP2pBytesRx = 66;
uint64 grpcUserBytesTx = 67;
uint64 grpcUserBytesRx = 68;
}
message ConsensusMetrics {
uint64 blocksSubmitted = 1;
uint64 headerCounts = 2;
uint64 depCounts = 3;
uint64 bodyCounts = 4;
uint64 txsCounts = 5;
uint64 chainBlockCounts = 6;
uint64 massCounts = 7;
uint64 blockCount = 11;
uint64 headerCount = 12;
uint64 mempoolSize = 13;
uint32 tipHashesCount = 14;
double difficulty = 15;
uint64 pastMedianTime = 16;
uint32 virtualParentHashesCount = 17;
uint64 virtualDaaScore = 18;
}
message StorageMetrics { uint64 storageSizeBytes = 1; }
message GetConnectionsRequestMessage { bool includeProfileData = 1; }
message ConnectionsProfileData {
double cpuUsage = 1;
uint64 memoryUsage = 2;
}
message GetConnectionsResponseMessage {
uint32 clients = 1;
uint32 peers = 2;
ConnectionsProfileData profileData = 3;
RPCError error = 1000;
}
message GetSystemInfoRequestMessage {}
message GetSystemInfoResponseMessage {
string version = 1;
string systemId = 2;
string gitHash = 3;
uint32 coreNum = 4;
uint64 totalMemory = 5;
uint32 fdLimit = 6;
RPCError error = 1000;
}
message GetMetricsRequestMessage {
bool processMetrics = 1;
bool connectionMetrics = 2;
bool bandwidthMetrics = 3;
bool consensusMetrics = 4;
bool storageMetrics = 5;
bool customMetrics = 6;
}
message GetMetricsResponseMessage {
uint64 serverTime = 1;
ProcessMetrics processMetrics = 11;
ConnectionMetrics connectionMetrics = 12;
BandwidthMetrics bandwidthMetrics = 13;
ConsensusMetrics consensusMetrics = 14;
StorageMetrics storageMetrics = 15;
RPCError error = 1000;
}
message GetServerInfoRequestMessage {}
message GetServerInfoResponseMessage {
uint32 rpcApiVersion = 1;
uint32 rpcApiRevision = 2;
string serverVersion = 3;
string networkId = 4;
bool hasUtxoIndex = 5;
bool isSynced = 6;
uint64 virtualDaaScore = 7;
RPCError error = 1000;
}
message GetSyncStatusRequestMessage {}
message GetSyncStatusResponseMessage {
bool isSynced = 1;
RPCError error = 1000;
}
message GetDaaScoreTimestampEstimateRequestMessage {
repeated uint64 daaScores = 1;
}
message GetDaaScoreTimestampEstimateResponseMessage {
repeated uint64 timestamps = 1;
RPCError error = 1000;
}
message RpcFeerateBucket {
// Fee/mass of a transaction in `sompi/gram` units
double feerate = 1;
double estimatedSeconds = 2;
}
// Data required for making fee estimates.
//
// Feerate values represent fee/mass of a transaction in `sompi/gram` units.
// Given a feerate value recommendation, calculate the required fee by
// taking the transaction mass and multiplying it by feerate: `fee = feerate *
// mass(tx)`
message RpcFeeEstimate {
// Top-priority feerate bucket. Provides an estimation of the feerate required
// for sub-second DAG inclusion.
RpcFeerateBucket priority_bucket = 1;
// A vector of *normal* priority feerate values. The first value of this
// vector is guaranteed to exist and provide an estimation for sub-*minute*
// DAG inclusion. All other values will have shorter estimation times than all
// `lowBuckets` values. Therefor by chaining `[priority] | normal | low` and
// interpolating between them, one can compose a complete feerate function on
// the client side. The API makes an effort to sample enough "interesting"
// points on the feerate-to-time curve, so that the interpolation is
// meaningful.
repeated RpcFeerateBucket normalBuckets = 2;
// A vector of *low* priority feerate values. The first value of this vector
// is guaranteed to exist and provide an estimation for sub-*hour* DAG
// inclusion.
repeated RpcFeerateBucket lowBuckets = 3;
}
message RpcFeeEstimateVerboseExperimentalData {
uint64 mempoolReadyTransactionsCount = 1;
uint64 mempoolReadyTransactionsTotalMass = 2;
uint64 networkMassPerSecond = 3;
double nextBlockTemplateFeerateMin = 11;
double nextBlockTemplateFeerateMedian = 12;
double nextBlockTemplateFeerateMax = 13;
}
message GetFeeEstimateRequestMessage {}
message GetFeeEstimateResponseMessage {
RpcFeeEstimate estimate = 1;
RPCError error = 1000;
}
message GetFeeEstimateExperimentalRequestMessage { bool verbose = 1; }
message GetFeeEstimateExperimentalResponseMessage {
RpcFeeEstimate estimate = 1;
RpcFeeEstimateVerboseExperimentalData verbose = 2;
RPCError error = 1000;
}
message GetCurrentBlockColorRequestMessage { string hash = 1; }
message GetCurrentBlockColorResponseMessage {
bool blue = 1;
RPCError error = 1000;
}
// SubmitTransactionReplacementRequestMessage submits a transaction to the
// mempool, applying a mandatory Replace by Fee policy
message SubmitTransactionReplacementRequestMessage {
RpcTransaction transaction = 1;
}
message SubmitTransactionReplacementResponseMessage {
// The transaction ID of the submitted transaction
string transactionId = 1;
// The previous transaction replaced in the mempool by the newly submitted one
RpcTransaction replacedTransaction = 2;
RPCError error = 1000;
}

View File

@ -17,7 +17,7 @@ func (x *KaspadMessage_GetCurrentNetworkResponse) toAppMessage() (appmessage.Mes
if x == nil {
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetCurrentNetworkResponse is nil")
}
return x.toAppMessage()
return x.GetCurrentNetworkResponse.toAppMessage()
}
func (x *KaspadMessage_GetCurrentNetworkResponse) fromAppMessage(message *appmessage.GetCurrentNetworkResponseMessage) error {

View File

@ -0,0 +1,67 @@
package protowire
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/pkg/errors"
)
func (x *KaspadMessage_GetFeeEstimateRequest) toAppMessage() (appmessage.Message, error) {
return &appmessage.GetFeeEstimateRequestMessage{}, nil
}
func (x *KaspadMessage_GetFeeEstimateRequest) fromAppMessage(_ *appmessage.GetFeeEstimateRequestMessage) error {
return nil
}
func (x *KaspadMessage_GetFeeEstimateResponse) toAppMessage() (appmessage.Message, error) {
if x == nil {
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetFeeEstimateResponse is nil")
}
return x.GetFeeEstimateResponse.toAppMessage()
}
func (x *GetFeeEstimateResponseMessage) toAppMessage() (appmessage.Message, error) {
if x == nil {
return nil, errors.Wrapf(errorNil, "GetFeeEstimateResponseMessage is nil")
}
rpcErr, err := x.Error.toAppMessage()
// Error is an optional field
if err != nil && !errors.Is(err, errorNil) {
return nil, err
}
estimate, err := x.Estimate.toAppMessage()
if err != nil {
return nil, err
}
return &appmessage.GetFeeEstimateResponseMessage{
Error: rpcErr,
Estimate: estimate,
}, nil
}
func (x *RpcFeeEstimate) toAppMessage() (appmessage.RPCFeeEstimate, error) {
if x == nil {
return appmessage.RPCFeeEstimate{}, errors.Wrapf(errorNil, "RpcFeeEstimate is nil")
}
return appmessage.RPCFeeEstimate{
PriorityBucket: appmessage.RPCFeeRateBucket{
Feerate: x.PriorityBucket.Feerate,
EstimatedSeconds: x.PriorityBucket.EstimatedSeconds,
},
NormalBuckets: feeRateBucketsToAppMessage(x.NormalBuckets),
LowBuckets: feeRateBucketsToAppMessage(x.LowBuckets),
}, nil
}
func feeRateBucketsToAppMessage(protoBuckets []*RpcFeerateBucket) []appmessage.RPCFeeRateBucket {
appMsgBuckets := make([]appmessage.RPCFeeRateBucket, len(protoBuckets))
for i, bucket := range protoBuckets {
appMsgBuckets[i] = appmessage.RPCFeeRateBucket{
Feerate: bucket.Feerate,
EstimatedSeconds: bucket.EstimatedSeconds,
}
}
return appMsgBuckets
}

View File

@ -110,6 +110,7 @@ func (x *RpcTransaction) toAppMessage() (*appmessage.RPCTransaction, error) {
SubnetworkID: x.SubnetworkId,
Gas: x.Gas,
Payload: x.Payload,
Mass: x.Mass,
VerboseData: verboseData,
}, nil
}
@ -138,6 +139,7 @@ func (x *RpcTransaction) fromAppMessage(transaction *appmessage.RPCTransaction)
SubnetworkId: transaction.SubnetworkID,
Gas: transaction.Gas,
Payload: transaction.Payload,
Mass: transaction.Mass,
VerboseData: verboseData,
}
}

View File

@ -0,0 +1,85 @@
package protowire
import (
"github.com/kaspanet/kaspad/app/appmessage"
"github.com/pkg/errors"
)
func (x *KaspadMessage_SubmitTransactionReplacementRequest) toAppMessage() (appmessage.Message, error) {
if x == nil {
return nil, errors.Wrapf(errorNil, "KaspadMessage_SubmitTransactionReplacementRequest is nil")
}
return x.SubmitTransactionReplacementRequest.toAppMessage()
}
func (x *KaspadMessage_SubmitTransactionReplacementRequest) fromAppMessage(message *appmessage.SubmitTransactionReplacementRequestMessage) error {
x.SubmitTransactionReplacementRequest = &SubmitTransactionReplacementRequestMessage{
Transaction: &RpcTransaction{},
}
x.SubmitTransactionReplacementRequest.Transaction.fromAppMessage(message.Transaction)
return nil
}
func (x *SubmitTransactionReplacementRequestMessage) toAppMessage() (appmessage.Message, error) {
if x == nil {
return nil, errors.Wrapf(errorNil, "SubmitBlockRequestMessage is nil")
}
rpcTransaction, err := x.Transaction.toAppMessage()
if err != nil {
return nil, err
}
return &appmessage.SubmitTransactionReplacementRequestMessage{
Transaction: rpcTransaction,
}, nil
}
func (x *KaspadMessage_SubmitTransactionReplacementResponse) toAppMessage() (appmessage.Message, error) {
if x == nil {
return nil, errors.Wrapf(errorNil, "KaspadMessage_SubmitTransactionReplacementResponse is nil")
}
return x.SubmitTransactionReplacementResponse.toAppMessage()
}
func (x *KaspadMessage_SubmitTransactionReplacementResponse) fromAppMessage(message *appmessage.SubmitTransactionReplacementResponseMessage) error {
var err *RPCError
if message.Error != nil {
err = &RPCError{Message: message.Error.Message}
}
x.SubmitTransactionReplacementResponse = &SubmitTransactionReplacementResponseMessage{
TransactionId: message.TransactionID,
ReplacedTransaction: &RpcTransaction{},
Error: err,
}
if message.ReplacedTransaction != nil {
x.SubmitTransactionReplacementResponse.ReplacedTransaction.fromAppMessage(message.ReplacedTransaction)
}
return nil
}
func (x *SubmitTransactionReplacementResponseMessage) toAppMessage() (appmessage.Message, error) {
if x == nil {
return nil, errors.Wrapf(errorNil, "SubmitTransactionReplacementResponseMessage is nil")
}
if x.Error != nil {
rpcErr, err := x.Error.toAppMessage()
// Error is an optional field
if err != nil && !errors.Is(err, errorNil) {
return nil, err
}
return &appmessage.SubmitTransactionReplacementResponseMessage{
TransactionID: x.TransactionId,
Error: rpcErr,
}, nil
}
replacedTx, err := x.ReplacedTransaction.toAppMessage()
if err != nil {
return nil, err
}
return &appmessage.SubmitTransactionReplacementResponseMessage{
TransactionID: x.TransactionId,
ReplacedTransaction: replacedTx,
}, nil
}

View File

@ -968,6 +968,27 @@ func toRPCPayload(message appmessage.Message) (isKaspadMessage_Payload, error) {
return nil, err
}
return payload, nil
case *appmessage.GetFeeEstimateRequestMessage:
payload := new(KaspadMessage_GetFeeEstimateRequest)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.SubmitTransactionReplacementRequestMessage:
payload := new(KaspadMessage_SubmitTransactionReplacementRequest)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
case *appmessage.SubmitTransactionReplacementResponseMessage:
payload := new(KaspadMessage_SubmitTransactionReplacementResponse)
err := payload.fromAppMessage(message)
if err != nil {
return nil, err
}
return payload, nil
default:
return nil, nil
}

View File

@ -0,0 +1,20 @@
package rpcclient
import "github.com/kaspanet/kaspad/app/appmessage"
// GetFeeEstimate sends an RPC request respective to the function's name and returns the RPC server's response
func (c *RPCClient) GetFeeEstimate() (*appmessage.GetFeeEstimateResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewGetFeeEstimateRequestMessage())
if err != nil {
return nil, err
}
response, err := c.route(appmessage.CmdGetFeeEstimateResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return nil, err
}
resp := response.(*appmessage.GetFeeEstimateResponseMessage)
if resp.Error != nil {
return nil, c.convertRPCError(resp.Error)
}
return resp, nil
}

View File

@ -0,0 +1,44 @@
package rpcclient
import (
"strings"
"github.com/kaspanet/kaspad/app/appmessage"
)
// SubmitTransactionReplacement sends an RPC request respective to the function's name and returns the RPC server's response
func (c *RPCClient) SubmitTransactionReplacement(transaction *appmessage.RPCTransaction, transactionID string) (*appmessage.SubmitTransactionReplacementResponseMessage, error) {
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionReplacementRequestMessage(transaction))
if err != nil {
return nil, err
}
for {
response, err := c.route(appmessage.CmdSubmitTransactionReplacementResponseMessage).DequeueWithTimeout(c.timeout)
if err != nil {
return nil, err
}
SubmitTransactionReplacementResponse := response.(*appmessage.SubmitTransactionReplacementResponseMessage)
// 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 SubmitTransactionReplacementResponse.TransactionID != transactionID {
if SubmitTransactionReplacementResponse.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 SubmitTransactionReplacementResponse.TransactionID != "" || !strings.Contains(SubmitTransactionReplacementResponse.Error.Message, transactionID) {
log.Warnf("SubmitTransactionReplacement: received an error response for previous request: %s", SubmitTransactionReplacementResponse.Error)
continue
}
} else {
log.Warnf("SubmitTransactionReplacement: received a successful response for previous request with ID %s",
SubmitTransactionReplacementResponse.TransactionID)
continue
}
}
if SubmitTransactionReplacementResponse.Error != nil {
return nil, c.convertRPCError(SubmitTransactionReplacementResponse.Error)
}
return SubmitTransactionReplacementResponse, nil
}
}

View File

@ -4,7 +4,7 @@ ARG KASPAMINER_IMAGE
FROM ${KASPAD_IMAGE} as kaspad
FROM ${KASPAMINER_IMAGE} as kaspaminer
FROM golang:1.19-alpine
FROM golang:1.23-alpine
RUN mkdir -p /go/src/github.com/kaspanet/kaspad

View File

@ -5,7 +5,6 @@ FLAGS=$@
go version
go get $FLAGS -t -d ../...
GO111MODULE=off go get $FLAGS golang.org/x/lint/golint
go install $FLAGS honnef.co/go/tools/cmd/staticcheck@latest
test -z "$(go fmt ./...)"
@ -13,7 +12,6 @@ test -z "$(go fmt ./...)"
staticcheck -checks SA4006,SA4008,SA4009,SA4010,SA5003,SA1004,SA1014,SA1021,SA1023,SA1024,SA1025,SA1026,SA1027,SA1028,SA2000,SA2001,SA2003,SA4000,SA4001,SA4003,SA4004,SA4011,SA4012,SA4013,SA4014,SA4015,SA4016,SA4017,SA4018,SA4019,SA4020,SA4021,SA4022,SA4023,SA5000,SA5002,SA5004,SA5005,SA5007,SA5008,SA5009,SA5010,SA5011,SA5012,SA6001,SA6002,SA9001,SA9002,SA9003,SA9004,SA9005,SA9006,ST1019 ./...
go vet -composites=false $FLAGS ./...
golint -set_exit_status $FLAGS ./...
go install $FLAGS ../...

View File

@ -2,6 +2,7 @@ package txmass
import (
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
)
@ -10,6 +11,9 @@ type Calculator struct {
massPerTxByte uint64
massPerScriptPubKeyByte uint64
massPerSigOp uint64
// The parameter for scaling inverse KAS value to mass units (KIP-0009)
storageMassParameter uint64
}
// NewCalculator creates a new instance of Calculator
@ -18,6 +22,7 @@ func NewCalculator(massPerTxByte, massPerScriptPubKeyByte, massPerSigOp uint64)
massPerTxByte: massPerTxByte,
massPerScriptPubKeyByte: massPerScriptPubKeyByte,
massPerSigOp: massPerSigOp,
storageMassParameter: constants.SompiPerKaspa * 10_000,
}
}
@ -59,6 +64,75 @@ func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTra
return massForSize + massForScriptPubKey + massForSigOps
}
// CalculateTransactionStorageMass calculates the storage mass of the given transaction (see KIP-0009)
func (c *Calculator) CalculateTransactionStorageMass(transaction *externalapi.DomainTransaction) uint64 {
if transactionhelper.IsCoinBase(transaction) {
return 0
}
outsLen := uint64(len(transaction.Outputs))
insLen := uint64(len(transaction.Inputs))
if insLen == 0 {
panic("Storage mass calculation expects at least one input")
}
harmonicOuts := uint64(0)
for _, output := range transaction.Outputs {
inverseOut := c.storageMassParameter / output.Value
if harmonicOuts+inverseOut < harmonicOuts {
// Overflow detected. This requires 10^7 outputs so is unrealistic for wallet usages.
// If this method is ever used for consensus, this case should be handled by returning an err
panic("Unexpected overflow in storage mass calculation")
}
harmonicOuts += inverseOut
}
if outsLen == 1 || insLen == 1 || (outsLen == 2 && insLen == 2) {
harmonicDiff := harmonicOuts
for _, input := range transaction.Inputs {
if input.UTXOEntry == nil {
panic("Storage mass calculation expects a fully populated transaction")
}
inverseIn := c.storageMassParameter / input.UTXOEntry.Amount()
if harmonicDiff < inverseIn {
harmonicDiff = 0
} else {
harmonicDiff -= inverseIn
}
}
return harmonicDiff
}
sumIns := uint64(0)
for _, input := range transaction.Inputs {
if input.UTXOEntry == nil {
panic("Storage mass calculation expects a fully populated transaction")
}
// Total supply is bounded, so a sum of existing UTXO entries cannot overflow (nor can it be zero)
sumIns += input.UTXOEntry.Amount()
}
meanIns := sumIns / insLen
inverseMeanIns := c.storageMassParameter / meanIns
arithmeticIns := insLen * inverseMeanIns
if arithmeticIns < inverseMeanIns {
// overflow (so subtraction would be negative)
return 0
}
if harmonicOuts < arithmeticIns {
// underflow
return 0
} else {
return harmonicOuts - arithmeticIns
}
}
// CalculateTransactionOverallMass calculates the overall mass of the transaction including compute and storage mass components (see KIP-0009)
func (c *Calculator) CalculateTransactionOverallMass(transaction *externalapi.DomainTransaction) uint64 {
return max(c.CalculateTransactionMass(transaction), c.CalculateTransactionStorageMass(transaction))
}
// transactionEstimatedSerializedSize is the estimated size of a transaction in some
// serialization. This has to be deterministic, but not necessarily accurate, since
// it's only used as the size component in the transaction and block mass limit

View File

@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
const (
appMajor uint = 0
appMinor uint = 12
appPatch uint = 17
appPatch uint = 20
)
// appBuild is defined as a variable so it can be overridden during the build