mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-09-13 13:00:10 +00:00
Compare commits
28 Commits
increased-
...
master
Author | SHA1 | Date | |
---|---|---|---|
![]() |
4bb5bf25d3 | ||
![]() |
25c2dd8670 | ||
![]() |
c93100ccd0 | ||
![]() |
03cc7dfc19 | ||
![]() |
ed745a9acb | ||
![]() |
c23c1d141c | ||
![]() |
352d261fd6 | ||
![]() |
43b9523919 | ||
![]() |
6085d1fc84 | ||
![]() |
1e9ddc42d0 | ||
![]() |
48a142e12f | ||
![]() |
86b89065cf | ||
![]() |
f41dc7fa0b | ||
![]() |
6b38bf7069 | ||
![]() |
d2453f8e7b | ||
![]() |
629faa8436 | ||
![]() |
91e6c6b74b | ||
![]() |
0819244ba1 | ||
![]() |
a0149cd8d0 | ||
![]() |
5a3b8a0066 | ||
![]() |
8e71f79f98 | ||
![]() |
346341a709 | ||
![]() |
8c881aea39 | ||
![]() |
40ec440dcf | ||
![]() |
88bdcb43bc | ||
![]() |
9d1e44673f | ||
![]() |
387fade044 | ||
![]() |
c417c8b525 |
18
.github/workflows/deploy.yaml
vendored
18
.github/workflows/deploy.yaml
vendored
@ -1,7 +1,7 @@
|
|||||||
name: Build and upload assets
|
name: Build and upload assets
|
||||||
on:
|
on:
|
||||||
release:
|
release:
|
||||||
types: [ published ]
|
types: [published]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
@ -9,7 +9,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, windows-latest, macos-latest ]
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
name: Building, ${{ matrix.os }}
|
name: Building, ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- name: Fix CRLF on Windows
|
- name: Fix CRLF on Windows
|
||||||
@ -17,13 +17,12 @@ jobs:
|
|||||||
run: git config --global core.autocrlf false
|
run: git config --global core.autocrlf false
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.21
|
||||||
|
|
||||||
- name: Build on Linux
|
- name: Build on Linux
|
||||||
if: runner.os == 'Linux'
|
if: runner.os == 'Linux'
|
||||||
@ -31,7 +30,7 @@ jobs:
|
|||||||
# `-tags netgo,osusergo` means use pure go replacements for "os/user" and "net"
|
# `-tags netgo,osusergo` means use pure go replacements for "os/user" and "net"
|
||||||
# `-s -w` strips the binary to produce smaller size binaries
|
# `-s -w` strips the binary to produce smaller size binaries
|
||||||
run: |
|
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"
|
archive="bin/kaspad-${{ github.event.release.tag_name }}-linux.zip"
|
||||||
asset_name="kaspad-${{ github.event.release.tag_name }}-linux.zip"
|
asset_name="kaspad-${{ github.event.release.tag_name }}-linux.zip"
|
||||||
zip -r "${archive}" ./bin/*
|
zip -r "${archive}" ./bin/*
|
||||||
@ -42,7 +41,7 @@ jobs:
|
|||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
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"
|
archive="bin/kaspad-${{ github.event.release.tag_name }}-win64.zip"
|
||||||
asset_name="kaspad-${{ github.event.release.tag_name }}-win64.zip"
|
asset_name="kaspad-${{ github.event.release.tag_name }}-win64.zip"
|
||||||
powershell "Compress-Archive bin/* \"${archive}\""
|
powershell "Compress-Archive bin/* \"${archive}\""
|
||||||
@ -52,14 +51,13 @@ jobs:
|
|||||||
- name: Build on MacOS
|
- name: Build on MacOS
|
||||||
if: runner.os == 'macOS'
|
if: runner.os == 'macOS'
|
||||||
run: |
|
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"
|
archive="bin/kaspad-${{ github.event.release.tag_name }}-osx.zip"
|
||||||
asset_name="kaspad-${{ github.event.release.tag_name }}-osx.zip"
|
asset_name="kaspad-${{ github.event.release.tag_name }}-osx.zip"
|
||||||
zip -r "${archive}" ./bin/*
|
zip -r "${archive}" ./bin/*
|
||||||
echo "archive=${archive}" >> $GITHUB_ENV
|
echo "archive=${archive}" >> $GITHUB_ENV
|
||||||
echo "asset_name=${asset_name}" >> $GITHUB_ENV
|
echo "asset_name=${asset_name}" >> $GITHUB_ENV
|
||||||
|
|
||||||
|
|
||||||
- name: Upload release asset
|
- name: Upload release asset
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
|
8
.github/workflows/race.yaml
vendored
8
.github/workflows/race.yaml
vendored
@ -11,18 +11,18 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
branch: [ master, latest ]
|
branch: [master, latest]
|
||||||
name: Race detection on ${{ matrix.branch }}
|
name: Race detection on ${{ matrix.branch }}
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.23
|
||||||
|
|
||||||
- name: Set scheduled branch name
|
- name: Set scheduled branch name
|
||||||
shell: bash
|
shell: bash
|
||||||
|
28
.github/workflows/tests.yaml
vendored
28
.github/workflows/tests.yaml
vendored
@ -8,22 +8,20 @@ on:
|
|||||||
types: [opened, synchronize, edited]
|
types: [opened, synchronize, edited]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
|
||||||
build:
|
build:
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-latest, macos-latest ]
|
os: [ubuntu-latest, macos-latest]
|
||||||
name: Tests, ${{ matrix.os }}
|
name: Tests, ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Fix CRLF on Windows
|
- name: Fix CRLF on Windows
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
run: git config --global core.autocrlf false
|
run: git config --global core.autocrlf false
|
||||||
|
|
||||||
- name: Check out code into the Go module directory
|
- 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
|
# Increase the pagefile size on Windows to aviod running out of memory
|
||||||
- name: Increase pagefile size on Windows
|
- name: Increase pagefile size on Windows
|
||||||
@ -31,14 +29,13 @@ jobs:
|
|||||||
run: powershell -command .github\workflows\SetPageFileSize.ps1
|
run: powershell -command .github\workflows\SetPageFileSize.ps1
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.23
|
||||||
|
|
||||||
|
|
||||||
# Source: https://github.com/actions/cache/blob/main/examples.md#go---modules
|
# Source: https://github.com/actions/cache/blob/main/examples.md#go---modules
|
||||||
- name: Go Cache
|
- name: Go Cache
|
||||||
uses: actions/cache@v2
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/go/pkg/mod
|
path: ~/go/pkg/mod
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
@ -49,19 +46,17 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: ./build_and_test.sh -v
|
run: ./build_and_test.sh -v
|
||||||
|
|
||||||
|
|
||||||
stability-test-fast:
|
stability-test-fast:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Fast stability tests, ${{ github.head_ref }}
|
name: Fast stability tests, ${{ github.head_ref }}
|
||||||
steps:
|
steps:
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.23
|
||||||
|
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
@ -75,18 +70,17 @@ jobs:
|
|||||||
working-directory: stability-tests
|
working-directory: stability-tests
|
||||||
run: ./install_and_test.sh
|
run: ./install_and_test.sh
|
||||||
|
|
||||||
|
|
||||||
coverage:
|
coverage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
name: Produce code coverage
|
name: Produce code coverage
|
||||||
steps:
|
steps:
|
||||||
- name: Check out code into the Go module directory
|
- name: Check out code into the Go module directory
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Setup Go
|
- name: Setup Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v5
|
||||||
with:
|
with:
|
||||||
go-version: 1.19
|
go-version: 1.23
|
||||||
|
|
||||||
- name: Delete the stability tests from coverage
|
- name: Delete the stability tests from coverage
|
||||||
run: rm -r stability-tests
|
run: rm -r stability-tests
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -53,6 +53,7 @@ _testmain.go
|
|||||||
debug
|
debug
|
||||||
debug.test
|
debug.test
|
||||||
__debug_bin
|
__debug_bin
|
||||||
|
*__debug_*
|
||||||
|
|
||||||
# CI
|
# CI
|
||||||
version.txt
|
version.txt
|
||||||
|
14
README.md
14
README.md
@ -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
|
||||||
|
|
||||||
[](https://choosealicense.com/licenses/isc/)
|
[](https://choosealicense.com/licenses/isc/)
|
||||||
[](http://godoc.org/github.com/kaspanet/kaspad)
|
[](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
|
## What is kaspa
|
||||||
|
|
||||||
@ -13,7 +17,7 @@ Kaspa is an attempt at a proof-of-work cryptocurrency with instant confirmations
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Go 1.18 or later.
|
Go 1.23 or later.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -40,7 +44,6 @@ $ go install . ./cmd/...
|
|||||||
not already add the bin directory to your system path during Go installation,
|
not already add the bin directory to your system path during Go installation,
|
||||||
you are encouraged to do so now.
|
you are encouraged to do so now.
|
||||||
|
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Kaspad has several configuration options available to tweak how it runs, but all
|
Kaspad has several configuration options available to tweak how it runs, but all
|
||||||
@ -51,6 +54,7 @@ $ kaspad
|
|||||||
```
|
```
|
||||||
|
|
||||||
## Discord
|
## Discord
|
||||||
|
|
||||||
Join our discord server using the following link: https://discord.gg/YNYnNN5Pf2
|
Join our discord server using the following link: https://discord.gg/YNYnNN5Pf2
|
||||||
|
|
||||||
## Issue Tracker
|
## Issue Tracker
|
||||||
|
@ -2,9 +2,10 @@ package appmessage
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"github.com/pkg/errors"
|
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/blockheader"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/hashes"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||||
@ -213,13 +214,14 @@ func RPCTransactionToDomainTransaction(rpcTransaction *RPCTransaction) (*externa
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &externalapi.DomainTransaction{
|
return &externalapi.DomainTransaction{
|
||||||
Version: rpcTransaction.Version,
|
Version: rpcTransaction.Version,
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
LockTime: rpcTransaction.LockTime,
|
LockTime: rpcTransaction.LockTime,
|
||||||
SubnetworkID: *subnetworkID,
|
SubnetworkID: *subnetworkID,
|
||||||
Gas: rpcTransaction.LockTime,
|
Gas: rpcTransaction.Gas,
|
||||||
Payload: payload,
|
MassCommitment: rpcTransaction.Mass,
|
||||||
|
Payload: payload,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -286,7 +288,8 @@ func DomainTransactionToRPCTransaction(transaction *externalapi.DomainTransactio
|
|||||||
Outputs: outputs,
|
Outputs: outputs,
|
||||||
LockTime: transaction.LockTime,
|
LockTime: transaction.LockTime,
|
||||||
SubnetworkID: subnetworkID,
|
SubnetworkID: subnetworkID,
|
||||||
Gas: transaction.LockTime,
|
Gas: transaction.Gas,
|
||||||
|
Mass: transaction.MassCommitment,
|
||||||
Payload: payload,
|
Payload: payload,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -163,6 +163,10 @@ const (
|
|||||||
CmdGetMempoolEntriesByAddressesResponseMessage
|
CmdGetMempoolEntriesByAddressesResponseMessage
|
||||||
CmdGetCoinSupplyRequestMessage
|
CmdGetCoinSupplyRequestMessage
|
||||||
CmdGetCoinSupplyResponseMessage
|
CmdGetCoinSupplyResponseMessage
|
||||||
|
CmdGetFeeEstimateRequestMessage
|
||||||
|
CmdGetFeeEstimateResponseMessage
|
||||||
|
CmdSubmitTransactionReplacementRequestMessage
|
||||||
|
CmdSubmitTransactionReplacementResponseMessage
|
||||||
)
|
)
|
||||||
|
|
||||||
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
// ProtocolMessageCommandToString maps all MessageCommands to their string representation
|
||||||
@ -300,6 +304,10 @@ var RPCMessageCommandToString = map[MessageCommand]string{
|
|||||||
CmdGetMempoolEntriesByAddressesResponseMessage: "GetMempoolEntriesByAddressesResponse",
|
CmdGetMempoolEntriesByAddressesResponseMessage: "GetMempoolEntriesByAddressesResponse",
|
||||||
CmdGetCoinSupplyRequestMessage: "GetCoinSupplyRequest",
|
CmdGetCoinSupplyRequestMessage: "GetCoinSupplyRequest",
|
||||||
CmdGetCoinSupplyResponseMessage: "GetCoinSupplyResponse",
|
CmdGetCoinSupplyResponseMessage: "GetCoinSupplyResponse",
|
||||||
|
CmdGetFeeEstimateRequestMessage: "GetFeeEstimateRequest",
|
||||||
|
CmdGetFeeEstimateResponseMessage: "GetFeeEstimateResponse",
|
||||||
|
CmdSubmitTransactionReplacementRequestMessage: "SubmitTransactionReplacementRequest",
|
||||||
|
CmdSubmitTransactionReplacementResponseMessage: "SubmitTransactionReplacementResponse",
|
||||||
}
|
}
|
||||||
|
|
||||||
// Message is an interface that describes a kaspa message. A type that
|
// Message is an interface that describes a kaspa message. A type that
|
||||||
|
47
app/appmessage/rpc_fee_estimate.go
Normal file
47
app/appmessage/rpc_fee_estimate.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package appmessage
|
||||||
|
|
||||||
|
// GetFeeEstimateRequestMessage is an appmessage corresponding to
|
||||||
|
// its respective RPC message
|
||||||
|
type GetFeeEstimateRequestMessage struct {
|
||||||
|
baseMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message
|
||||||
|
func (msg *GetFeeEstimateRequestMessage) Command() MessageCommand {
|
||||||
|
return CmdGetFeeEstimateRequestMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetFeeEstimateRequestMessage returns a instance of the message
|
||||||
|
func NewGetFeeEstimateRequestMessage() *GetFeeEstimateRequestMessage {
|
||||||
|
return &GetFeeEstimateRequestMessage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCFeeRateBucket struct {
|
||||||
|
Feerate float64
|
||||||
|
EstimatedSeconds float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type RPCFeeEstimate struct {
|
||||||
|
PriorityBucket RPCFeeRateBucket
|
||||||
|
NormalBuckets []RPCFeeRateBucket
|
||||||
|
LowBuckets []RPCFeeRateBucket
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCoinSupplyResponseMessage is an appmessage corresponding to
|
||||||
|
// its respective RPC message
|
||||||
|
type GetFeeEstimateResponseMessage struct {
|
||||||
|
baseMessage
|
||||||
|
Estimate RPCFeeEstimate
|
||||||
|
|
||||||
|
Error *RPCError
|
||||||
|
}
|
||||||
|
|
||||||
|
// Command returns the protocol command string for the message
|
||||||
|
func (msg *GetFeeEstimateResponseMessage) Command() MessageCommand {
|
||||||
|
return CmdGetFeeEstimateResponseMessage
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetFeeEstimateResponseMessage returns a instance of the message
|
||||||
|
func NewGetFeeEstimateResponseMessage() *GetFeeEstimateResponseMessage {
|
||||||
|
return &GetFeeEstimateResponseMessage{}
|
||||||
|
}
|
@ -52,6 +52,7 @@ type RPCTransaction struct {
|
|||||||
SubnetworkID string
|
SubnetworkID string
|
||||||
Gas uint64
|
Gas uint64
|
||||||
Payload string
|
Payload string
|
||||||
|
Mass uint64
|
||||||
VerboseData *RPCTransactionVerboseData
|
VerboseData *RPCTransactionVerboseData
|
||||||
}
|
}
|
||||||
|
|
||||||
|
42
app/appmessage/rpc_submit_transaction_replacement.go
Normal file
42
app/appmessage/rpc_submit_transaction_replacement.go
Normal 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,
|
||||||
|
}
|
||||||
|
}
|
@ -81,10 +81,6 @@ func (ctx *Context) PopulateBlockWithVerboseData(block *appmessage.RPCBlock, dom
|
|||||||
block.VerboseData.SelectedParentHash = blockInfo.SelectedParent.String()
|
block.VerboseData.SelectedParentHash = blockInfo.SelectedParent.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if blockInfo.BlockStatus == externalapi.StatusHeaderOnly {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get the block if we didn't receive it previously
|
// Get the block if we didn't receive it previously
|
||||||
if domainBlock == nil {
|
if domainBlock == nil {
|
||||||
domainBlock, err = ctx.Domain.Consensus().GetBlockEvenIfHeaderOnly(blockHash)
|
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))
|
transactionIDs := make([]string, len(domainBlock.Transactions))
|
||||||
for i, transaction := range domainBlock.Transactions {
|
for i, transaction := range domainBlock.Transactions {
|
||||||
transactionIDs[i] = consensushashing.TransactionID(transaction).String()
|
transactionIDs[i] = consensushashing.TransactionID(transaction).String()
|
||||||
|
@ -28,7 +28,8 @@ func HandleSubmitTransaction(context *rpccontext.Context, _ *router.Router, requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Rejected transaction %s: %s", transactionID, err)
|
log.Debugf("Rejected transaction %s: %s", transactionID, err)
|
||||||
errorMessage := &appmessage.SubmitTransactionResponseMessage{}
|
// Return the ID also in the case of error, so that clients can match the response to the correct transaction submit request
|
||||||
|
errorMessage := appmessage.NewSubmitTransactionResponseMessage(transactionID.String())
|
||||||
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", transactionID, err)
|
errorMessage.Error = appmessage.RPCErrorf("Rejected transaction %s: %s", transactionID, err)
|
||||||
return errorMessage, nil
|
return errorMessage, nil
|
||||||
}
|
}
|
||||||
|
@ -5,13 +5,10 @@ FLAGS=$@
|
|||||||
go version
|
go version
|
||||||
|
|
||||||
go get $FLAGS -t -d ./...
|
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
|
go install $FLAGS honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
|
|
||||||
test -z "$(go fmt ./...)"
|
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 ./...
|
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 .
|
go build $FLAGS -o kaspad .
|
||||||
|
@ -1,3 +1,30 @@
|
|||||||
|
Kaspad v0.12.17 - 2024-02-19
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Wallet-related improvements and fixes (#2253, #2257, #2258, #2262)
|
||||||
|
|
||||||
|
Kaspad v0.12.16 - 2023-12-25
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Adapt wallet UTXO selection to dust patch (#2254)
|
||||||
|
|
||||||
|
Kaspad v0.12.15 - 2023-12-16
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Update ECDSA address test to use a valid public key (#2202)
|
||||||
|
* Fix off by small amounts in sent amount kaspa (#2220)
|
||||||
|
* Use removeRedeemers correctly by (#2235)
|
||||||
|
* Fix windows asset building by increasing go version (#2245)
|
||||||
|
* Added a mainnet dnsseeder (#2247)
|
||||||
|
* Fix extract atomic swap data pushes (#2203)
|
||||||
|
* Adapt kaspawallet to support testnet 11 (#2211)
|
||||||
|
* Fix type detection in RemoveInvalidTransactions (#2252)
|
||||||
|
|
||||||
|
Kaspad v0.12.14 - 2023-09-26
|
||||||
|
===========================
|
||||||
|
|
||||||
|
* Anti-spam measurements against dust attack (#2223)
|
||||||
|
|
||||||
Kaspad v0.12.13 - 2023-03-06
|
Kaspad v0.12.13 - 2023-03-06
|
||||||
===========================
|
===========================
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ kaspactl is an RPC client for kaspad
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Go 1.19 or later.
|
Go 1.23 or later.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -50,4 +50,4 @@ For example:
|
|||||||
$ kaspactl '{"getBlockDagInfoRequest":{}}'
|
$ 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)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# -- multistage docker build: stage #1: build stage
|
# -- 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
|
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ Kaspaminer is a CPU-based miner for kaspad
|
|||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
Go 1.19 or later.
|
Go 1.23 or later.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@ -30,7 +30,7 @@ $ go install .
|
|||||||
- Kapaminer should now be installed in `$(go env GOPATH)/bin`. If you did
|
- 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,
|
not already add the bin directory to your system path during Go installation,
|
||||||
you are encouraged to do so now.
|
you are encouraged to do so now.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The full kaspaminer configuration options can be seen with:
|
The full kaspaminer configuration options can be seen with:
|
||||||
@ -40,6 +40,7 @@ $ kaspaminer --help
|
|||||||
```
|
```
|
||||||
|
|
||||||
But the minimum configuration needed to run it is:
|
But the minimum configuration needed to run it is:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ kaspaminer --miningaddr=<YOUR_MINING_ADDRESS>
|
$ kaspaminer --miningaddr=<YOUR_MINING_ADDRESS>
|
||||||
```
|
```
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# -- multistage docker build: stage #1: build stage
|
# -- 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
|
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -37,7 +38,7 @@ func broadcast(conf *broadcastConfig) error {
|
|||||||
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
|
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions, err := decodeTransactionsFromHex(transactionsHex)
|
transactions, err := server.DecodeTransactionsFromHex(transactionsHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
57
cmd/kaspawallet/broadcast_replacement.go
Normal file
57
cmd/kaspawallet/broadcast_replacement.go
Normal 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
117
cmd/kaspawallet/bump_fee.go
Normal 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
|
||||||
|
}
|
58
cmd/kaspawallet/bump_fee_unsigned.go
Normal file
58
cmd/kaspawallet/bump_fee_unsigned.go
Normal 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
|
||||||
|
}
|
@ -22,6 +22,11 @@ const (
|
|||||||
newAddressSubCmd = "new-address"
|
newAddressSubCmd = "new-address"
|
||||||
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
|
dumpUnencryptedDataSubCmd = "dump-unencrypted-data"
|
||||||
startDaemonSubCmd = "start-daemon"
|
startDaemonSubCmd = "start-daemon"
|
||||||
|
versionSubCmd = "version"
|
||||||
|
getDaemonVersionSubCmd = "get-daemon-version"
|
||||||
|
bumpFeeSubCmd = "bump-fee"
|
||||||
|
bumpFeeUnsignedSubCmd = "bump-fee-unsigned"
|
||||||
|
broadcastReplacementSubCmd = "broadcast-replacement"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -30,6 +35,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type configFlags struct {
|
type configFlags struct {
|
||||||
|
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||||
config.NetworkFlags
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,10 +62,13 @@ type sendConfig struct {
|
|||||||
Password string `long:"password" short:"p" description:"Wallet password"`
|
Password string `long:"password" short:"p" description:"Wallet password"`
|
||||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
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"`
|
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 float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||||
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount). If --from-address was used, will send all only from the specified addresses."`
|
||||||
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
||||||
|
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the 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"`
|
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
|
||||||
config.NetworkFlags
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
@ -74,9 +83,12 @@ type createUnsignedTransactionConfig struct {
|
|||||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
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"`
|
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. Use multiple times to accept several addresses" required:"false"`
|
||||||
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||||
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
||||||
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
||||||
|
MaxFeeRate float64 `long:"max-fee-rate" short:"m" description:"Maximum fee rate in Sompi/gram to use for the transaction. The wallet will take the 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
|
config.NetworkFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -96,6 +108,7 @@ type broadcastConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type parseConfig 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)"`
|
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)"`
|
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"`
|
Verbose bool `long:"verbose" short:"v" description:"Verbose: show transaction inputs"`
|
||||||
@ -129,6 +142,38 @@ type dumpUnencryptedDataConfig struct {
|
|||||||
config.NetworkFlags
|
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 {
|
||||||
|
}
|
||||||
|
|
||||||
|
type getDaemonVersionConfig struct {
|
||||||
|
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
||||||
|
}
|
||||||
|
|
||||||
func parseCommandLine() (subCommand string, config interface{}) {
|
func parseCommandLine() (subCommand string, config interface{}) {
|
||||||
cfg := &configFlags{}
|
cfg := &configFlags{}
|
||||||
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
parser := flags.NewParser(cfg, flags.PrintErrors|flags.HelpFlag)
|
||||||
@ -185,6 +230,15 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
|||||||
Listen: defaultListen,
|
Listen: defaultListen,
|
||||||
}
|
}
|
||||||
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
|
parser.AddCommand(startDaemonSubCmd, "Start the wallet daemon", "Start the wallet daemon", startDaemonConf)
|
||||||
|
parser.AddCommand(versionSubCmd, "Get the wallet version", "Get the wallet version", &versionConfig{})
|
||||||
|
getDaemonVersionConf := &getDaemonVersionConfig{DaemonAddress: defaultListen}
|
||||||
|
parser.AddCommand(getDaemonVersionSubCmd, "Get the wallet daemon version", "Get the wallet daemon version", getDaemonVersionConf)
|
||||||
|
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()
|
_, err := parser.Parse()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -255,6 +309,13 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
|||||||
printErrorAndExit(err)
|
printErrorAndExit(err)
|
||||||
}
|
}
|
||||||
config = broadcastConf
|
config = broadcastConf
|
||||||
|
case broadcastReplacementSubCmd:
|
||||||
|
combineNetworkFlags(&broadcastConf.NetworkFlags, &cfg.NetworkFlags)
|
||||||
|
err := broadcastConf.ResolveNetwork(parser)
|
||||||
|
if err != nil {
|
||||||
|
printErrorAndExit(err)
|
||||||
|
}
|
||||||
|
config = broadcastConf
|
||||||
case parseSubCmd:
|
case parseSubCmd:
|
||||||
combineNetworkFlags(&parseConf.NetworkFlags, &cfg.NetworkFlags)
|
combineNetworkFlags(&parseConf.NetworkFlags, &cfg.NetworkFlags)
|
||||||
err := parseConf.ResolveNetwork(parser)
|
err := parseConf.ResolveNetwork(parser)
|
||||||
@ -290,29 +351,123 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
|||||||
printErrorAndExit(err)
|
printErrorAndExit(err)
|
||||||
}
|
}
|
||||||
config = startDaemonConf
|
config = startDaemonConf
|
||||||
|
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
|
return parser.Command.Active.Name, config
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
|
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
|
||||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
(conf.IsSendAll && conf.SendAmount != "") {
|
||||||
|
|
||||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.MaxFeeRate < 0 {
|
||||||
|
return errors.New("--max-fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.FeeRate < 0 {
|
||||||
|
return errors.New("--fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSendConfig(conf *sendConfig) error {
|
func validateSendConfig(conf *sendConfig) error {
|
||||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
(conf.IsSendAll && conf.SendAmount != "") {
|
||||||
|
|
||||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if conf.MaxFeeRate < 0 {
|
||||||
|
return errors.New("--max-fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if conf.FeeRate < 0 {
|
||||||
|
return errors.New("--fee-rate must be a positive number")
|
||||||
|
}
|
||||||
|
|
||||||
|
if 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
|
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) {
|
func combineNetworkFlags(dst, src *config.NetworkFlags) {
|
||||||
dst.Testnet = dst.Testnet || src.Testnet
|
dst.Testnet = dst.Testnet || src.Testnet
|
||||||
dst.Simnet = dst.Simnet || src.Simnet
|
dst.Simnet = dst.Simnet || src.Simnet
|
||||||
|
@ -7,7 +7,8 @@ import (
|
|||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||||
@ -20,20 +21,46 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
var sendAmountSompi uint64
|
||||||
|
|
||||||
|
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{
|
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||||
From: conf.FromAddresses,
|
From: conf.FromAddresses,
|
||||||
Address: conf.ToAddress,
|
Address: conf.ToAddress,
|
||||||
Amount: sendAmountSompi,
|
Amount: sendAmountSompi,
|
||||||
IsSendAll: conf.IsSendAll,
|
IsSendAll: conf.IsSendAll,
|
||||||
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
||||||
|
FeePolicy: feePolicy,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(os.Stderr, "Created unsigned transaction")
|
fmt.Fprintln(os.Stderr, "Created unsigned transaction")
|
||||||
fmt.Println(encodeTransactionsToHex(response.UnsignedTransactions))
|
fmt.Println(server.EncodeTransactionsToHex(response.UnsignedTransactions))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,9 +2,10 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -4,21 +4,28 @@ option go_package = "github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb";
|
|||||||
package kaspawalletd;
|
package kaspawalletd;
|
||||||
|
|
||||||
service kaspawalletd {
|
service kaspawalletd {
|
||||||
rpc GetBalance (GetBalanceRequest) returns (GetBalanceResponse) {}
|
rpc GetBalance(GetBalanceRequest) returns (GetBalanceResponse) {}
|
||||||
rpc GetExternalSpendableUTXOs (GetExternalSpendableUTXOsRequest) returns (GetExternalSpendableUTXOsResponse) {}
|
rpc GetExternalSpendableUTXOs(GetExternalSpendableUTXOsRequest)
|
||||||
rpc CreateUnsignedTransactions (CreateUnsignedTransactionsRequest) returns (CreateUnsignedTransactionsResponse) {}
|
returns (GetExternalSpendableUTXOsResponse) {}
|
||||||
rpc ShowAddresses (ShowAddressesRequest) returns (ShowAddressesResponse) {}
|
rpc CreateUnsignedTransactions(CreateUnsignedTransactionsRequest)
|
||||||
rpc NewAddress (NewAddressRequest) returns (NewAddressResponse) {}
|
returns (CreateUnsignedTransactionsResponse) {}
|
||||||
rpc Shutdown (ShutdownRequest) returns (ShutdownResponse) {}
|
rpc ShowAddresses(ShowAddressesRequest) returns (ShowAddressesResponse) {}
|
||||||
rpc Broadcast (BroadcastRequest) returns (BroadcastResponse) {}
|
rpc NewAddress(NewAddressRequest) returns (NewAddressResponse) {}
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
rpc Shutdown(ShutdownRequest) returns (ShutdownResponse) {}
|
||||||
|
rpc Broadcast(BroadcastRequest) returns (BroadcastResponse) {}
|
||||||
|
// 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) {}
|
rpc Send(SendRequest) returns (SendResponse) {}
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
rpc Sign(SignRequest) returns (SignResponse) {}
|
rpc Sign(SignRequest) returns (SignResponse) {}
|
||||||
|
rpc GetVersion(GetVersionRequest) returns (GetVersionResponse) {}
|
||||||
|
rpc BumpFee(BumpFeeRequest) returns (BumpFeeResponse) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetBalanceRequest {
|
message GetBalanceRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetBalanceResponse {
|
message GetBalanceResponse {
|
||||||
uint64 available = 1;
|
uint64 available = 1;
|
||||||
@ -32,46 +39,45 @@ message AddressBalances {
|
|||||||
uint64 pending = 3;
|
uint64 pending = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
message FeePolicy {
|
||||||
|
oneof feePolicy {
|
||||||
|
double maxFeeRate = 6;
|
||||||
|
double exactFeeRate = 7;
|
||||||
|
uint64 maxFee = 8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
message CreateUnsignedTransactionsRequest {
|
message CreateUnsignedTransactionsRequest {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
uint64 amount = 2;
|
uint64 amount = 2;
|
||||||
repeated string from = 3;
|
repeated string from = 3;
|
||||||
bool useExistingChangeAddress = 4;
|
bool useExistingChangeAddress = 4;
|
||||||
bool isSendAll = 5;
|
bool isSendAll = 5;
|
||||||
|
FeePolicy feePolicy = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
message CreateUnsignedTransactionsResponse {
|
message CreateUnsignedTransactionsResponse {
|
||||||
repeated bytes unsignedTransactions = 1;
|
repeated bytes unsignedTransactions = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ShowAddressesRequest {
|
message ShowAddressesRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message ShowAddressesResponse {
|
message ShowAddressesResponse { repeated string address = 1; }
|
||||||
repeated string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NewAddressRequest {
|
message NewAddressRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message NewAddressResponse {
|
message NewAddressResponse { string address = 1; }
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BroadcastRequest {
|
message BroadcastRequest {
|
||||||
bool isDomain = 1;
|
bool isDomain = 1;
|
||||||
repeated bytes transactions = 2;
|
repeated bytes transactions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BroadcastResponse {
|
message BroadcastResponse { repeated string txIDs = 1; }
|
||||||
repeated string txIDs = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ShutdownRequest {
|
message ShutdownRequest {}
|
||||||
}
|
|
||||||
|
|
||||||
message ShutdownResponse {
|
message ShutdownResponse {}
|
||||||
}
|
|
||||||
|
|
||||||
message Outpoint {
|
message Outpoint {
|
||||||
string transactionId = 1;
|
string transactionId = 1;
|
||||||
@ -96,34 +102,50 @@ message UtxoEntry {
|
|||||||
bool isCoinbase = 4;
|
bool isCoinbase = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetExternalSpendableUTXOsRequest{
|
message GetExternalSpendableUTXOsRequest { string address = 1; }
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetExternalSpendableUTXOsResponse{
|
message GetExternalSpendableUTXOsResponse {
|
||||||
repeated UtxosByAddressesEntry Entries = 1;
|
repeated UtxosByAddressesEntry Entries = 1;
|
||||||
}
|
}
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SendRequest contains a password - this command should only be used on a
|
||||||
message SendRequest{
|
// trusted or secure connection
|
||||||
|
message SendRequest {
|
||||||
string toAddress = 1;
|
string toAddress = 1;
|
||||||
uint64 amount = 2;
|
uint64 amount = 2;
|
||||||
string password = 3;
|
string password = 3;
|
||||||
repeated string from = 4;
|
repeated string from = 4;
|
||||||
bool useExistingChangeAddress = 5;
|
bool useExistingChangeAddress = 5;
|
||||||
bool isSendAll = 6;
|
bool isSendAll = 6;
|
||||||
|
FeePolicy feePolicy = 7;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SendResponse{
|
message SendResponse {
|
||||||
repeated string txIDs = 1;
|
repeated string txIDs = 1;
|
||||||
repeated bytes signedTransactions = 2;
|
repeated bytes signedTransactions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on a
|
||||||
message SignRequest{
|
// trusted or secure connection
|
||||||
|
message SignRequest {
|
||||||
repeated bytes unsignedTransactions = 1;
|
repeated bytes unsignedTransactions = 1;
|
||||||
string password = 2;
|
string password = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SignResponse{
|
message SignResponse { repeated bytes signedTransactions = 1; }
|
||||||
repeated bytes signedTransactions = 1;
|
|
||||||
|
message GetVersionRequest {}
|
||||||
|
|
||||||
|
message GetVersionResponse { string version = 1; }
|
||||||
|
|
||||||
|
message BumpFeeRequest {
|
||||||
|
string password = 1;
|
||||||
|
repeated string from = 2;
|
||||||
|
bool useExistingChangeAddress = 3;
|
||||||
|
FeePolicy feePolicy = 4;
|
||||||
|
string txID = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BumpFeeResponse {
|
||||||
|
repeated bytes transactions = 1;
|
||||||
|
repeated string txIDs = 2;
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,8 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
|
// versions:
|
||||||
|
// - protoc-gen-go-grpc v1.2.0
|
||||||
|
// - protoc v3.12.3
|
||||||
|
// source: kaspawalletd.proto
|
||||||
|
|
||||||
package pb
|
package pb
|
||||||
|
|
||||||
@ -25,10 +29,16 @@ type KaspawalletdClient interface {
|
|||||||
NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
|
NewAddress(ctx context.Context, in *NewAddressRequest, opts ...grpc.CallOption) (*NewAddressResponse, error)
|
||||||
Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error)
|
Shutdown(ctx context.Context, in *ShutdownRequest, opts ...grpc.CallOption) (*ShutdownResponse, error)
|
||||||
Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
|
Broadcast(ctx context.Context, in *BroadcastRequest, opts ...grpc.CallOption) (*BroadcastResponse, error)
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
// 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)
|
Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error)
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
|
Sign(ctx context.Context, in *SignRequest, opts ...grpc.CallOption) (*SignResponse, error)
|
||||||
|
GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error)
|
||||||
|
BumpFee(ctx context.Context, in *BumpFeeRequest, opts ...grpc.CallOption) (*BumpFeeResponse, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type kaspawalletdClient struct {
|
type kaspawalletdClient struct {
|
||||||
@ -102,6 +112,15 @@ func (c *kaspawalletdClient) Broadcast(ctx context.Context, in *BroadcastRequest
|
|||||||
return out, nil
|
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) {
|
func (c *kaspawalletdClient) Send(ctx context.Context, in *SendRequest, opts ...grpc.CallOption) (*SendResponse, error) {
|
||||||
out := new(SendResponse)
|
out := new(SendResponse)
|
||||||
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/Send", in, out, opts...)
|
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/Send", in, out, opts...)
|
||||||
@ -120,6 +139,24 @@ func (c *kaspawalletdClient) Sign(ctx context.Context, in *SignRequest, opts ...
|
|||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *kaspawalletdClient) GetVersion(ctx context.Context, in *GetVersionRequest, opts ...grpc.CallOption) (*GetVersionResponse, error) {
|
||||||
|
out := new(GetVersionResponse)
|
||||||
|
err := c.cc.Invoke(ctx, "/kaspawalletd.kaspawalletd/GetVersion", in, out, opts...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
// KaspawalletdServer is the server API for Kaspawalletd service.
|
||||||
// All implementations must embed UnimplementedKaspawalletdServer
|
// All implementations must embed UnimplementedKaspawalletdServer
|
||||||
// for forward compatibility
|
// for forward compatibility
|
||||||
@ -131,10 +168,16 @@ type KaspawalletdServer interface {
|
|||||||
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
|
NewAddress(context.Context, *NewAddressRequest) (*NewAddressResponse, error)
|
||||||
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
Shutdown(context.Context, *ShutdownRequest) (*ShutdownResponse, error)
|
||||||
Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
|
Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error)
|
||||||
// Since SendRequest contains a password - this command should only be used on a trusted or secure connection
|
// 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)
|
Send(context.Context, *SendRequest) (*SendResponse, error)
|
||||||
// Since SignRequest contains a password - this command should only be used on a trusted or secure connection
|
// Since SignRequest contains a password - this command should only be used on
|
||||||
|
// a trusted or secure connection
|
||||||
Sign(context.Context, *SignRequest) (*SignResponse, error)
|
Sign(context.Context, *SignRequest) (*SignResponse, error)
|
||||||
|
GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error)
|
||||||
|
BumpFee(context.Context, *BumpFeeRequest) (*BumpFeeResponse, error)
|
||||||
mustEmbedUnimplementedKaspawalletdServer()
|
mustEmbedUnimplementedKaspawalletdServer()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,12 +206,21 @@ func (UnimplementedKaspawalletdServer) Shutdown(context.Context, *ShutdownReques
|
|||||||
func (UnimplementedKaspawalletdServer) Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error) {
|
func (UnimplementedKaspawalletdServer) Broadcast(context.Context, *BroadcastRequest) (*BroadcastResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Broadcast not implemented")
|
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) {
|
func (UnimplementedKaspawalletdServer) Send(context.Context, *SendRequest) (*SendResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Send not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method Send not implemented")
|
||||||
}
|
}
|
||||||
func (UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*SignResponse, error) {
|
func (UnimplementedKaspawalletdServer) Sign(context.Context, *SignRequest) (*SignResponse, error) {
|
||||||
return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented")
|
return nil, status.Errorf(codes.Unimplemented, "method Sign not implemented")
|
||||||
}
|
}
|
||||||
|
func (UnimplementedKaspawalletdServer) GetVersion(context.Context, *GetVersionRequest) (*GetVersionResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method GetVersion not implemented")
|
||||||
|
}
|
||||||
|
func (UnimplementedKaspawalletdServer) BumpFee(context.Context, *BumpFeeRequest) (*BumpFeeResponse, error) {
|
||||||
|
return nil, status.Errorf(codes.Unimplemented, "method BumpFee not implemented")
|
||||||
|
}
|
||||||
func (UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {}
|
func (UnimplementedKaspawalletdServer) mustEmbedUnimplementedKaspawalletdServer() {}
|
||||||
|
|
||||||
// UnsafeKaspawalletdServer may be embedded to opt out of forward compatibility for this service.
|
// UnsafeKaspawalletdServer may be embedded to opt out of forward compatibility for this service.
|
||||||
@ -308,6 +360,24 @@ func _Kaspawalletd_Broadcast_Handler(srv interface{}, ctx context.Context, dec f
|
|||||||
return interceptor(ctx, in, info, handler)
|
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) {
|
func _Kaspawalletd_Send_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
in := new(SendRequest)
|
in := new(SendRequest)
|
||||||
if err := dec(in); err != nil {
|
if err := dec(in); err != nil {
|
||||||
@ -344,6 +414,42 @@ func _Kaspawalletd_Sign_Handler(srv interface{}, ctx context.Context, dec func(i
|
|||||||
return interceptor(ctx, in, info, handler)
|
return interceptor(ctx, in, info, handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func _Kaspawalletd_GetVersion_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||||
|
in := new(GetVersionRequest)
|
||||||
|
if err := dec(in); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if interceptor == nil {
|
||||||
|
return srv.(KaspawalletdServer).GetVersion(ctx, in)
|
||||||
|
}
|
||||||
|
info := &grpc.UnaryServerInfo{
|
||||||
|
Server: srv,
|
||||||
|
FullMethod: "/kaspawalletd.kaspawalletd/GetVersion",
|
||||||
|
}
|
||||||
|
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||||
|
return srv.(KaspawalletdServer).GetVersion(ctx, req.(*GetVersionRequest))
|
||||||
|
}
|
||||||
|
return interceptor(ctx, in, info, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
// Kaspawalletd_ServiceDesc is the grpc.ServiceDesc for Kaspawalletd service.
|
||||||
// It's only intended for direct use with grpc.RegisterService,
|
// It's only intended for direct use with grpc.RegisterService,
|
||||||
// and not to be introspected or modified (even as a copy)
|
// and not to be introspected or modified (even as a copy)
|
||||||
@ -379,6 +485,10 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Broadcast",
|
MethodName: "Broadcast",
|
||||||
Handler: _Kaspawalletd_Broadcast_Handler,
|
Handler: _Kaspawalletd_Broadcast_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "BroadcastReplacement",
|
||||||
|
Handler: _Kaspawalletd_BroadcastReplacement_Handler,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
MethodName: "Send",
|
MethodName: "Send",
|
||||||
Handler: _Kaspawalletd_Send_Handler,
|
Handler: _Kaspawalletd_Send_Handler,
|
||||||
@ -387,6 +497,14 @@ var Kaspawalletd_ServiceDesc = grpc.ServiceDesc{
|
|||||||
MethodName: "Sign",
|
MethodName: "Sign",
|
||||||
Handler: _Kaspawalletd_Sign_Handler,
|
Handler: _Kaspawalletd_Sign_Handler,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
MethodName: "GetVersion",
|
||||||
|
Handler: _Kaspawalletd_GetVersion_Handler,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
MethodName: "BumpFee",
|
||||||
|
Handler: _Kaspawalletd_BumpFee_Handler,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Streams: []grpc.StreamDesc{},
|
Streams: []grpc.StreamDesc{},
|
||||||
Metadata: "kaspawalletd.proto",
|
Metadata: "kaspawalletd.proto",
|
||||||
|
@ -2,6 +2,7 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
@ -14,13 +15,15 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
|||||||
s.lock.RLock()
|
s.lock.RLock()
|
||||||
defer s.lock.RUnlock()
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
if !s.isSynced() {
|
||||||
|
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
||||||
|
}
|
||||||
|
|
||||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
daaScore := dagInfo.VirtualDAAScore
|
daaScore := dagInfo.VirtualDAAScore
|
||||||
maturity := s.params.BlockCoinbaseMaturity
|
|
||||||
|
|
||||||
balancesMap := make(balancesMapType, 0)
|
balancesMap := make(balancesMapType, 0)
|
||||||
for _, entry := range s.utxosSortedByAmount {
|
for _, entry := range s.utxosSortedByAmount {
|
||||||
amount := entry.UTXOEntry.Amount()
|
amount := entry.UTXOEntry.Amount()
|
||||||
@ -30,7 +33,7 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
|||||||
balances = new(balancesType)
|
balances = new(balancesType)
|
||||||
balancesMap[address] = balances
|
balancesMap[address] = balances
|
||||||
}
|
}
|
||||||
if isUTXOSpendable(entry, daaScore, maturity) {
|
if s.isUTXOSpendable(entry, daaScore) {
|
||||||
balances.available += amount
|
balances.available += amount
|
||||||
} else {
|
} else {
|
||||||
balances.pending += amount
|
balances.pending += amount
|
||||||
@ -55,6 +58,8 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
|||||||
pending += balances.pending
|
pending += balances.pending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("GetBalance request scanned %d UTXOs overall over %d addresses", len(s.utxosSortedByAmount), len(balancesMap))
|
||||||
|
|
||||||
return &pb.GetBalanceResponse{
|
return &pb.GetBalanceResponse{
|
||||||
Available: available,
|
Available: available,
|
||||||
Pending: pending,
|
Pending: pending,
|
||||||
@ -62,9 +67,9 @@ func (s *server) GetBalance(_ context.Context, _ *pb.GetBalanceRequest) (*pb.Get
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
|
func (s *server) isUTXOSpendable(entry *walletUTXO, virtualDAAScore uint64) bool {
|
||||||
if !entry.UTXOEntry.IsCoinbase() {
|
if !entry.UTXOEntry.IsCoinbase() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return entry.UTXOEntry.BlockDAAScore()+coinbaseMaturity < virtualDAAScore
|
return entry.UTXOEntry.BlockDAAScore()+s.coinbaseMaturity < virtualDAAScore
|
||||||
}
|
}
|
||||||
|
@ -2,14 +2,16 @@ package server
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/app/appmessage"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"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/kaspanet/kaspad/infrastructure/network/rpcclient"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
|
func (s *server) Broadcast(_ context.Context, request *pb.BroadcastRequest) (*pb.BroadcastResponse, error) {
|
||||||
@ -54,16 +56,12 @@ func (s *server) broadcast(transactions [][]byte, isDomain bool) ([]string, erro
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.refreshUTXOs()
|
s.forceSync()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return txIDs, nil
|
return txIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
|
func sendTransaction(client *rpcclient.RPCClient, tx *externalapi.DomainTransaction) (string, error) {
|
||||||
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), false)
|
submitTransactionResponse, err := client.SubmitTransaction(appmessage.DomainTransactionToRPCTransaction(tx), consensushashing.TransactionID(tx).String(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrapf(err, "error submitting transaction")
|
return "", errors.Wrapf(err, "error submitting transaction")
|
||||||
}
|
}
|
||||||
|
81
cmd/kaspawallet/daemon/server/broadcast_replacement.go
Normal file
81
cmd/kaspawallet/daemon/server/broadcast_replacement.go
Normal 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
|
||||||
|
}
|
156
cmd/kaspawallet/daemon/server/bump_fee.go
Normal file
156
cmd/kaspawallet/daemon/server/bump_fee.go
Normal 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
|
||||||
|
}
|
@ -3,18 +3,27 @@ package server
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"math"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||||
"github.com/kaspanet/kaspad/util"
|
"github.com/kaspanet/kaspad/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"golang.org/x/exp/slices"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// TODO: Implement a better fee estimation mechanism
|
// The minimal change amount to target in order to avoid large storage mass (see KIP9 for more details).
|
||||||
const feePerInput = 10000
|
// 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) (
|
func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.CreateUnsignedTransactionsRequest) (
|
||||||
*pb.CreateUnsignedTransactionsResponse, error,
|
*pb.CreateUnsignedTransactionsResponse, error,
|
||||||
@ -23,7 +32,7 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
|
|||||||
defer s.lock.Unlock()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.IsSendAll,
|
unsignedTransactions, err := s.createUnsignedTransactions(request.Address, request.Amount, request.IsSendAll,
|
||||||
request.From, request.UseExistingChangeAddress)
|
request.From, request.UseExistingChangeAddress, request.FeePolicy)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -31,11 +40,59 @@ func (s *server) CreateUnsignedTransactions(_ context.Context, request *pb.Creat
|
|||||||
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
return &pb.CreateUnsignedTransactionsResponse{UnsignedTransactions: unsignedTransactions}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createUnsignedTransactions(address string, amount uint64, isSendAll bool, fromAddressesString []string, useExistingChangeAddress bool) ([][]byte, error) {
|
func (s *server) 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() {
|
if !s.isSynced() {
|
||||||
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
return nil, errors.Errorf("wallet daemon is not synced yet, %s", s.formatSyncStateReport())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feeRate, maxFee, err := s.calculateFeeLimits(requestFeePolicy)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// make sure address string is correct before proceeding to a
|
// make sure address string is correct before proceeding to a
|
||||||
// potentially long UTXO refreshment operation
|
// potentially long UTXO refreshment operation
|
||||||
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
|
toAddress, err := util.DecodeAddress(address, s.params.Prefix)
|
||||||
@ -43,21 +100,21 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.refreshUTXOs()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var fromAddresses []*walletAddress
|
var fromAddresses []*walletAddress
|
||||||
for _, from := range fromAddressesString {
|
for _, from := range fromAddressesString {
|
||||||
fromAddress, exists := s.addressSet[from]
|
fromAddress, exists := s.addressSet[from]
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("Specified from address %s does not exists", from)
|
return nil, fmt.Errorf("specified from address %s does not exists", from)
|
||||||
}
|
}
|
||||||
fromAddresses = append(fromAddresses, fromAddress)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -66,11 +123,6 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, errors.Errorf("couldn't find funds to spend")
|
return nil, errors.Errorf("couldn't find funds to spend")
|
||||||
}
|
}
|
||||||
|
|
||||||
changeAddress, changeWalletAddress, err := s.changeAddress(useExistingChangeAddress, fromAddresses)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
payments := []*libkaspawallet.Payment{{
|
payments := []*libkaspawallet.Payment{{
|
||||||
Address: toAddress,
|
Address: toAddress,
|
||||||
Amount: spendValue,
|
Amount: spendValue,
|
||||||
@ -88,17 +140,25 @@ func (s *server) createUnsignedTransactions(address string, amount uint64, isSen
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress)
|
unsignedTransactions, err := s.maybeAutoCompoundTransaction(unsignedTransaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return unsignedTransactions, nil
|
return unsignedTransactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uint64, fromAddresses []*walletAddress) (
|
func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feeRate float64, 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, 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)
|
totalValue := uint64(0)
|
||||||
|
|
||||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||||
@ -106,17 +166,26 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
return nil, 0, 0, err
|
return nil, 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, utxo := range s.utxosSortedByAmount {
|
var fee uint64
|
||||||
if (fromAddresses != nil && !slices.Contains(fromAddresses, utxo.address)) ||
|
iteration := func(utxo *walletUTXO, avoidPreselected bool) (bool, error) {
|
||||||
!isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
if (fromAddresses != nil && !walletAddressesContain(fromAddresses, utxo.address)) ||
|
||||||
continue
|
!s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if broadcastTime, ok := s.usedOutpoints[*utxo.Outpoint]; ok {
|
if broadcastTime, ok := s.usedOutpoints[*utxo.Outpoint]; ok {
|
||||||
if time.Since(broadcastTime) > time.Minute {
|
if _, ok := allowUsed[*utxo.Outpoint]; !ok {
|
||||||
delete(s.usedOutpoints, *utxo.Outpoint)
|
if s.usedOutpointHasExpired(broadcastTime) {
|
||||||
} else {
|
delete(s.usedOutpoints, *utxo.Outpoint)
|
||||||
continue
|
} else {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if avoidPreselected {
|
||||||
|
if _, ok := preSelectedSet[*utxo.Outpoint]; ok {
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,15 +196,54 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
})
|
})
|
||||||
|
|
||||||
totalValue += utxo.UTXOEntry.Amount()
|
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
|
totalSpend := spendAmount + fee
|
||||||
if !isSendAll && totalValue >= totalSpend {
|
// Two break cases (if not send all):
|
||||||
|
// 1. totalValue == totalSpend, so there's no change needed -> number of outputs = 1, so a single input is sufficient
|
||||||
|
// 2. totalValue > totalSpend, so there will be change and 2 outputs, therefor in order to not struggle with --
|
||||||
|
// 2.1 go-nodes dust patch we try and find at least 2 inputs (even though the next one is not necessary in terms of spend value)
|
||||||
|
// 2.2 KIP9 we try and make sure that the change amount is not too small
|
||||||
|
if !isSendAll && (totalValue == totalSpend || (totalValue >= totalSpend+minChangeTarget && len(selectedUTXOs) > 1)) {
|
||||||
|
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
|
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
|
var totalSpend uint64
|
||||||
if isSendAll {
|
if isSendAll {
|
||||||
totalSpend = totalValue
|
totalSpend = totalValue
|
||||||
@ -151,3 +259,106 @@ func (s *server) selectUTXOs(spendAmount uint64, isSendAll bool, feePerInput uin
|
|||||||
|
|
||||||
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
return selectedUTXOs, totalReceived, totalValue - totalSpend, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *server) estimateFee(selectedUTXOs []*libkaspawallet.UTXO, feeRate float64, 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 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -21,7 +21,15 @@ func (s *server) GetExternalSpendableUTXOs(_ context.Context, request *pb.GetExt
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
selectedUTXOs, err := s.selectExternalSpendableUTXOs(externalUTXOs, request.Address)
|
|
||||||
|
estimate, err := s.rpcClient.GetFeeEstimate()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
feeRate := estimate.Estimate.NormalBuckets[0].Feerate
|
||||||
|
|
||||||
|
selectedUTXOs, err := s.selectExternalSpendableUTXOs(externalUTXOs, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -30,7 +38,7 @@ func (s *server) GetExternalSpendableUTXOs(_ context.Context, request *pb.GetExt
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOsByAddressesResponseMessage, address string) ([]*pb.UtxosByAddressesEntry, error) {
|
func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOsByAddressesResponseMessage, feeRate float64) ([]*pb.UtxosByAddressesEntry, error) {
|
||||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -42,8 +50,13 @@ func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOs
|
|||||||
//we do not make because we do not know size, because of unspendable utxos
|
//we do not make because we do not know size, because of unspendable utxos
|
||||||
var selectedExternalUtxos []*pb.UtxosByAddressesEntry
|
var selectedExternalUtxos []*pb.UtxosByAddressesEntry
|
||||||
|
|
||||||
|
feePerInput, err := s.estimateFeePerInput(feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, entry := range externalUTXOs.Entries {
|
for _, entry := range externalUTXOs.Entries {
|
||||||
if !isExternalUTXOSpendable(entry, daaScore, maturity) {
|
if !isExternalUTXOSpendable(entry, daaScore, maturity, feePerInput) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
selectedExternalUtxos = append(selectedExternalUtxos, libkaspawallet.AppMessageUTXOToKaspawalletdUTXO(entry))
|
selectedExternalUtxos = append(selectedExternalUtxos, libkaspawallet.AppMessageUTXOToKaspawalletdUTXO(entry))
|
||||||
@ -52,7 +65,7 @@ func (s *server) selectExternalSpendableUTXOs(externalUTXOs *appmessage.GetUTXOs
|
|||||||
return selectedExternalUtxos, nil
|
return selectedExternalUtxos, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func isExternalUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64) bool {
|
func isExternalUTXOSpendable(entry *appmessage.UTXOsByAddressesEntry, virtualDAAScore uint64, coinbaseMaturity uint64, feePerInput uint64) bool {
|
||||||
if !entry.UTXOEntry.IsCoinbase {
|
if !entry.UTXOEntry.IsCoinbase {
|
||||||
return true
|
return true
|
||||||
} else if entry.UTXOEntry.Amount <= feePerInput {
|
} else if entry.UTXOEntry.Amount <= feePerInput {
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendResponse, error) {
|
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()
|
defer s.lock.Unlock()
|
||||||
|
|
||||||
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.IsSendAll,
|
unsignedTransactions, err := s.createUnsignedTransactions(request.ToAddress, request.Amount, request.IsSendAll,
|
||||||
request.From, request.UseExistingChangeAddress)
|
request.From, request.UseExistingChangeAddress, request.FeePolicy)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -24,7 +25,7 @@ func (s *server) Send(_ context.Context, request *pb.SendRequest) (*pb.SendRespo
|
|||||||
|
|
||||||
txIDs, err := s.broadcast(signedTransactions, false)
|
txIDs, err := s.broadcast(signedTransactions, false)
|
||||||
if err != nil {
|
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
|
return &pb.SendResponse{TxIDs: txIDs, SignedTransactions: signedTransactions}, nil
|
||||||
|
@ -5,8 +5,11 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"sync"
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/version"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/util/txmass"
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
@ -27,17 +30,23 @@ import (
|
|||||||
type server struct {
|
type server struct {
|
||||||
pb.UnimplementedKaspawalletdServer
|
pb.UnimplementedKaspawalletdServer
|
||||||
|
|
||||||
rpcClient *rpcclient.RPCClient
|
rpcClient *rpcclient.RPCClient // RPC client for ongoing user requests
|
||||||
params *dagconfig.Params
|
backgroundRPCClient *rpcclient.RPCClient // RPC client dedicated for address and UTXO background fetching
|
||||||
|
params *dagconfig.Params
|
||||||
|
coinbaseMaturity uint64 // Different from go-kaspad default following Crescendo
|
||||||
|
|
||||||
lock sync.RWMutex
|
lock sync.RWMutex
|
||||||
utxosSortedByAmount []*walletUTXO
|
utxosSortedByAmount []*walletUTXO
|
||||||
nextSyncStartIndex uint32
|
mempoolExcludedUTXOs map[externalapi.DomainOutpoint]*walletUTXO
|
||||||
keysFile *keys.File
|
nextSyncStartIndex uint32
|
||||||
shutdown chan struct{}
|
keysFile *keys.File
|
||||||
addressSet walletAddressSet
|
shutdown chan struct{}
|
||||||
txMassCalculator *txmass.Calculator
|
forceSyncChan chan struct{}
|
||||||
usedOutpoints map[externalapi.DomainOutpoint]time.Time
|
startTimeOfLastCompletedRefresh time.Time
|
||||||
|
addressSet walletAddressSet
|
||||||
|
txMassCalculator *txmass.Calculator
|
||||||
|
usedOutpoints map[externalapi.DomainOutpoint]time.Time
|
||||||
|
firstSyncDone atomic.Bool
|
||||||
|
|
||||||
isLogFinalProgressLineShown bool
|
isLogFinalProgressLineShown bool
|
||||||
maxUsedAddressesForLog uint32
|
maxUsedAddressesForLog uint32
|
||||||
@ -59,6 +68,7 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
|||||||
profiling.Start(profile, log)
|
profiling.Start(profile, log)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("Version %s", version.Version())
|
||||||
listener, err := net.Listen("tcp", listen)
|
listener, err := net.Listen("tcp", listen)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return (errors.Wrapf(err, "Error listening to TCP on %s", listen))
|
return (errors.Wrapf(err, "Error listening to TCP on %s", listen))
|
||||||
@ -70,6 +80,10 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
|
return (errors.Wrapf(err, "Error connecting to RPC server %s", rpcServer))
|
||||||
}
|
}
|
||||||
|
backgroundRPCClient, err := connectToRPC(params, rpcServer, timeout)
|
||||||
|
if err != nil {
|
||||||
|
return (errors.Wrapf(err, "Error making a second connection to RPC server %s", rpcServer))
|
||||||
|
}
|
||||||
|
|
||||||
log.Infof("Connected, reading keys file %s...", keysFilePath)
|
log.Infof("Connected, reading keys file %s...", keysFilePath)
|
||||||
keysFile, err := keys.ReadKeysFile(params, keysFilePath)
|
keysFile, err := keys.ReadKeysFile(params, keysFilePath)
|
||||||
@ -82,13 +96,20 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Post-Crescendo coinbase maturity
|
||||||
|
coinbaseMaturity := uint64(1000)
|
||||||
|
|
||||||
serverInstance := &server{
|
serverInstance := &server{
|
||||||
rpcClient: rpcClient,
|
rpcClient: rpcClient,
|
||||||
|
backgroundRPCClient: backgroundRPCClient,
|
||||||
params: params,
|
params: params,
|
||||||
|
coinbaseMaturity: coinbaseMaturity,
|
||||||
utxosSortedByAmount: []*walletUTXO{},
|
utxosSortedByAmount: []*walletUTXO{},
|
||||||
|
mempoolExcludedUTXOs: map[externalapi.DomainOutpoint]*walletUTXO{},
|
||||||
nextSyncStartIndex: 0,
|
nextSyncStartIndex: 0,
|
||||||
keysFile: keysFile,
|
keysFile: keysFile,
|
||||||
shutdown: make(chan struct{}),
|
shutdown: make(chan struct{}),
|
||||||
|
forceSyncChan: make(chan struct{}),
|
||||||
addressSet: make(walletAddressSet),
|
addressSet: make(walletAddressSet),
|
||||||
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
||||||
usedOutpoints: map[externalapi.DomainOutpoint]time.Time{},
|
usedOutpoints: map[externalapi.DomainOutpoint]time.Time{},
|
||||||
@ -98,8 +119,8 @@ func Start(params *dagconfig.Params, listen, rpcServer string, keysFilePath stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("Read, syncing the wallet...")
|
log.Infof("Read, syncing the wallet...")
|
||||||
spawn("serverInstance.sync", func() {
|
spawn("serverInstance.syncLoop", func() {
|
||||||
err := serverInstance.sync()
|
err := serverInstance.syncLoop()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
printErrorAndExit(errors.Wrap(err, "error syncing the wallet"))
|
printErrorAndExit(errors.Wrap(err, "error syncing the wallet"))
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
|
"github.com/kaspanet/kaspad/domain/miningmanager/mempool"
|
||||||
"github.com/kaspanet/kaspad/util"
|
"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
|
// 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.
|
// into a change address.
|
||||||
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
|
// An additional `mergeTransaction` is generated - which merges the outputs of the above splits into a single output
|
||||||
// paying to the original transaction's payee.
|
// paying to the original transaction's payee.
|
||||||
func (s *server) maybeAutoCompoundTransaction(transactionBytes []byte, toAddress util.Address,
|
func (s *server) maybeAutoCompoundTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
||||||
changeAddress util.Address, changeWalletAddress *walletAddress) ([][]byte, error) {
|
changeAddress util.Address, changeWalletAddress *walletAddress, feeRate float64, maxFee uint64) ([][]byte, error) {
|
||||||
transaction, err := serialization.DeserializePartiallySignedTransaction(transactionBytes)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress)
|
splitTransactions, err := s.maybeSplitAndMergeTransaction(transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -47,6 +44,8 @@ func (s *server) mergeTransaction(
|
|||||||
toAddress util.Address,
|
toAddress util.Address,
|
||||||
changeAddress util.Address,
|
changeAddress util.Address,
|
||||||
changeWalletAddress *walletAddress,
|
changeWalletAddress *walletAddress,
|
||||||
|
feeRate float64,
|
||||||
|
maxFee uint64,
|
||||||
) (*serialization.PartiallySignedTransaction, error) {
|
) (*serialization.PartiallySignedTransaction, error) {
|
||||||
numOutputs := len(originalTransaction.Tx.Outputs)
|
numOutputs := len(originalTransaction.Tx.Outputs)
|
||||||
if numOutputs > 2 || numOutputs == 0 {
|
if numOutputs > 2 || numOutputs == 0 {
|
||||||
@ -71,13 +70,19 @@ func (s *server) mergeTransaction(
|
|||||||
DerivationPath: s.walletAddressPath(changeWalletAddress),
|
DerivationPath: s.walletAddressPath(changeWalletAddress),
|
||||||
}
|
}
|
||||||
totalValue += output.Value
|
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 {
|
if totalValue < sentValue {
|
||||||
// sometimes the fees from compound transactions make the total output higher than what's available from selected
|
// sometimes the fees from compound transactions make the total output higher than what's available from selected
|
||||||
// utxos, in such cases - find one more UTXO and use it.
|
// utxos, in such cases - find one more UTXO and use it.
|
||||||
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue)
|
additionalUTXOs, totalValueAdded, err := s.moreUTXOsForMergeTransaction(utxos, sentValue-totalValue, feeRate)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -96,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)
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return serialization.DeserializePartiallySignedTransaction(mergeTransactionBytes)
|
transactionMass, err := s.estimateComputeMassAfterSignatures(transaction)
|
||||||
}
|
|
||||||
|
|
||||||
func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.PartiallySignedTransaction, toAddress util.Address,
|
|
||||||
changeAddress util.Address, changeWalletAddress *walletAddress) ([]*serialization.PartiallySignedTransaction, error) {
|
|
||||||
|
|
||||||
transactionMass, err := s.estimateMassAfterSignatures(transaction)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -117,7 +157,7 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
return []*serialization.PartiallySignedTransaction{transaction}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress)
|
splitCount, inputCountPerSplit, err := s.splitAndInputPerSplitCounts(transaction, transactionMass, changeAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -127,19 +167,24 @@ func (s *server) maybeSplitAndMergeTransaction(transaction *serialization.Partia
|
|||||||
startIndex := i * inputCountPerSplit
|
startIndex := i * inputCountPerSplit
|
||||||
endIndex := startIndex + inputCountPerSplit
|
endIndex := startIndex + inputCountPerSplit
|
||||||
var err error
|
var err error
|
||||||
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex)
|
splitTransactions[i], err = s.createSplitTransaction(transaction, changeAddress, startIndex, endIndex, feeRate, maxFee)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.checkTransactionFeeRate(splitTransactions[i], maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(splitTransactions) > 1 {
|
if len(splitTransactions) > 1 {
|
||||||
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress)
|
mergeTransaction, err := s.mergeTransaction(splitTransactions, transaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
|
// Recursion will be 2-3 iterations deep even in the rarest` cases, so considered safe..
|
||||||
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress)
|
splitMergeTransaction, err := s.maybeSplitAndMergeTransaction(mergeTransaction, toAddress, changeAddress, changeWalletAddress, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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.
|
// splitAndInputPerSplitCounts calculates the number of splits to create, and the number of inputs to assign per split.
|
||||||
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
|
func (s *server) splitAndInputPerSplitCounts(transaction *serialization.PartiallySignedTransaction, transactionMass uint64,
|
||||||
changeAddress util.Address) (splitCount, inputsPerSplitCount int, err error) {
|
changeAddress util.Address, feeRate float64, maxFee uint64) (splitCount, inputsPerSplitCount int, err error) {
|
||||||
|
|
||||||
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
|
// Create a dummy transaction which is a clone of the original transaction, but without inputs,
|
||||||
// to calculate how much mass do all the inputs have
|
// to calculate how much mass do all the inputs have
|
||||||
@ -172,7 +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,
|
// Create another dummy transaction, this time one similar to the split transactions we wish to generate,
|
||||||
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
|
// but with 0 inputs, to calculate how much mass for inputs do we have available in the split transactions
|
||||||
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0)
|
splitTransactionWithoutInputs, err := s.createSplitTransaction(transaction, changeAddress, 0, 0, feeRate, maxFee)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
@ -190,7 +235,7 @@ func (s *server) splitAndInputPerSplitCounts(transaction *serialization.Partiall
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
func (s *server) createSplitTransaction(transaction *serialization.PartiallySignedTransaction,
|
||||||
changeAddress util.Address, startIndex int, endIndex int) (*serialization.PartiallySignedTransaction, error) {
|
changeAddress util.Address, startIndex int, endIndex int, feeRate float64, maxFee uint64) (*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
selectedUTXOs := make([]*libkaspawallet.UTXO, 0, endIndex-startIndex)
|
||||||
totalSompi := uint64(0)
|
totalSompi := uint64(0)
|
||||||
@ -206,25 +251,36 @@ func (s *server) createSplitTransaction(transaction *serialization.PartiallySign
|
|||||||
})
|
})
|
||||||
|
|
||||||
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
|
totalSompi += selectedUTXOs[i-startIndex].UTXOEntry.Amount()
|
||||||
totalSompi -= feePerInput
|
|
||||||
}
|
}
|
||||||
unsignedTransactionBytes, err := libkaspawallet.CreateUnsignedTransaction(s.keysFile.ExtendedPublicKeys,
|
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,
|
s.keysFile.MinimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: changeAddress,
|
Address: changeAddress,
|
||||||
Amount: totalSompi,
|
Amount: totalSompi,
|
||||||
}}, selectedUTXOs)
|
}}, selectedUTXOs)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
|
func (s *server) estimateMassAfterSignatures(transaction *serialization.PartiallySignedTransaction) (uint64, error) {
|
||||||
|
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()
|
transaction = transaction.Clone()
|
||||||
var signatureSize uint64
|
var signatureSize uint64
|
||||||
if s.keysFile.ECDSA {
|
if ecdsa {
|
||||||
signatureSize = secp256k1.SerializedECDSASignatureSize
|
signatureSize = secp256k1.SerializedECDSASignatureSize
|
||||||
} else {
|
} else {
|
||||||
signatureSize = secp256k1.SerializedSchnorrSignatureSize
|
signatureSize = secp256k1.SerializedSchnorrSignatureSize
|
||||||
@ -232,7 +288,7 @@ func (s *server) estimateMassAfterSignatures(transaction *serialization.Partiall
|
|||||||
|
|
||||||
for i, input := range transaction.PartiallySignedInputs {
|
for i, input := range transaction.PartiallySignedInputs {
|
||||||
for j, pubKeyPair := range input.PubKeySignaturePairs {
|
for j, pubKeyPair := range input.PubKeySignaturePairs {
|
||||||
if uint32(j) >= s.keysFile.MinimumSignatures {
|
if uint32(j) >= minimumSignatures {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
pubKeyPair.Signature = make([]byte, signatureSize+1) // +1 for SigHashType
|
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))
|
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 {
|
if err != nil {
|
||||||
return 0, err
|
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) {
|
additionalUTXOs []*libkaspawallet.UTXO, totalValueAdded uint64, err error) {
|
||||||
|
|
||||||
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
dagInfo, err := s.rpcClient.GetBlockDAGInfo()
|
||||||
@ -260,11 +329,16 @@ func (s *server) moreUTXOsForMergeTransaction(alreadySelectedUTXOs []*libkaspawa
|
|||||||
alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
|
alreadySelectedUTXOsMap[*alreadySelectedUTXO.Outpoint] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
feePerInput, err := s.estimateFeePerInput(feeRate)
|
||||||
|
if err != nil {
|
||||||
|
return nil, 0, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, utxo := range s.utxosSortedByAmount {
|
for _, utxo := range s.utxosSortedByAmount {
|
||||||
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
|
if _, ok := alreadySelectedUTXOsMap[*utxo.Outpoint]; ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !isUTXOSpendable(utxo, dagInfo.VirtualDAAScore, s.params.BlockCoinbaseMaturity) {
|
if !s.isUTXOSpendable(utxo, dagInfo.VirtualDAAScore) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{
|
additionalUTXOs = append(additionalUTXOs, &libkaspawallet.UTXO{
|
||||||
|
@ -20,9 +20,9 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/testutils"
|
"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) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
unsignedTransactionBytes, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
|
unsignedTransaction, mnemonics, params, teardown := testEstimateMassIncreaseForSignaturesSetUp(t, consensusConfig)
|
||||||
defer teardown(false)
|
defer teardown(false)
|
||||||
|
|
||||||
serverInstance := &server{
|
serverInstance := &server{
|
||||||
@ -33,14 +33,14 @@ func TestEstimateMassAfterSignatures(t *testing.T) {
|
|||||||
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
txMassCalculator: txmass.NewCalculator(params.MassPerTxByte, params.MassPerScriptPubKeyByte, params.MassPerSigOp),
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransaction, err := serialization.DeserializePartiallySignedTransaction(unsignedTransactionBytes)
|
estimatedMassAfterSignatures, err := serverInstance.estimateComputeMassAfterSignatures(unsignedTransaction)
|
||||||
if err != nil {
|
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 {
|
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)
|
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) (
|
func testEstimateMassIncreaseForSignaturesSetUp(t *testing.T, consensusConfig *consensus.Config) (
|
||||||
[]byte, []string, *dagconfig.Params, func(keepDataDir bool)) {
|
*serialization.PartiallySignedTransaction, []string, *dagconfig.Params, func(keepDataDir bool)) {
|
||||||
|
|
||||||
consensusConfig.BlockCoinbaseMaturity = 0
|
consensusConfig.BlockCoinbaseMaturity = 0
|
||||||
params := &consensusConfig.Params
|
params := &consensusConfig.Params
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/app/appmessage"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -23,7 +24,7 @@ func (was walletAddressSet) strings() []string {
|
|||||||
return addresses
|
return addresses
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) sync() error {
|
func (s *server) syncLoop() error {
|
||||||
ticker := time.NewTicker(time.Second)
|
ticker := time.NewTicker(time.Second)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@ -32,29 +33,39 @@ func (s *server) sync() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.refreshExistingUTXOsWithLock()
|
err = s.refreshUTXOs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for range ticker.C {
|
s.firstSyncDone.Store(true)
|
||||||
err = s.collectFarAddresses()
|
log.Infof("Wallet is synced and ready for operation")
|
||||||
if err != nil {
|
|
||||||
return err
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
case <-s.forceSyncChan:
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.collectRecentAddresses()
|
err := s.sync()
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = s.refreshExistingUTXOsWithLock()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
func (s *server) sync() error {
|
||||||
|
err := s.collectFarAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = s.collectRecentAddresses()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.refreshUTXOs()
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@ -158,7 +169,7 @@ func (s *server) collectAddresses(start, end uint32) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
getBalancesByAddressesResponse, err := s.rpcClient.GetBalancesByAddresses(addressSet.strings())
|
getBalancesByAddressesResponse, err := s.backgroundRPCClient.GetBalancesByAddresses(addressSet.strings())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -208,15 +219,17 @@ func (s *server) updateAddressesAndLastUsedIndexes(requestedAddressSet walletAdd
|
|||||||
return s.keysFile.SetLastUsedInternalIndex(lastUsedInternalIndex)
|
return s.keysFile.SetLastUsedInternalIndex(lastUsedInternalIndex)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) refreshExistingUTXOsWithLock() error {
|
func (s *server) usedOutpointHasExpired(outpointBroadcastTime time.Time) bool {
|
||||||
s.lock.Lock()
|
// If the node returns a UTXO we previously attempted to spend and enough time has passed, we assume
|
||||||
defer s.lock.Unlock()
|
// that the network rejected or lost the previous transaction and allow a reuse. We set this time
|
||||||
|
// interval to a minute.
|
||||||
return s.refreshUTXOs()
|
// We also verify that a full refresh UTXO operation started after this time point and has already
|
||||||
|
// completed, in order to make sure that indeed this state reflects a state obtained following the required wait time.
|
||||||
|
return s.startTimeOfLastCompletedRefresh.After(outpointBroadcastTime.Add(time.Minute))
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateUTXOSet clears the current UTXO set, and re-fills it with the given entries
|
// updateUTXOSet clears the current UTXO set, and re-fills it with the given entries
|
||||||
func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, mempoolEntries []*appmessage.MempoolEntryByAddress) error {
|
func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, mempoolEntries []*appmessage.MempoolEntryByAddress, refreshStart time.Time) error {
|
||||||
utxos := make([]*walletUTXO, 0, len(entries))
|
utxos := make([]*walletUTXO, 0, len(entries))
|
||||||
|
|
||||||
exclude := make(map[appmessage.RPCOutpoint]struct{})
|
exclude := make(map[appmessage.RPCOutpoint]struct{})
|
||||||
@ -228,11 +241,8 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mempoolExcludedUTXOs := make(map[externalapi.DomainOutpoint]*walletUTXO)
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
if _, ok := exclude[*entry.Outpoint]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
outpoint, err := appmessage.RPCOutpointToDomainOutpoint(entry.Outpoint)
|
outpoint, err := appmessage.RPCOutpointToDomainOutpoint(entry.Outpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -243,45 +253,82 @@ func (s *server) updateUTXOSet(entries []*appmessage.UTXOsByAddressesEntry, memp
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// No need to lock for reading since the only writer of this set is on `syncLoop` on the same goroutine.
|
||||||
address, ok := s.addressSet[entry.Address]
|
address, ok := s.addressSet[entry.Address]
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
|
return errors.Errorf("Got result from address %s even though it wasn't requested", entry.Address)
|
||||||
}
|
}
|
||||||
utxos = append(utxos, &walletUTXO{
|
|
||||||
|
utxo := &walletUTXO{
|
||||||
Outpoint: outpoint,
|
Outpoint: outpoint,
|
||||||
UTXOEntry: utxoEntry,
|
UTXOEntry: utxoEntry,
|
||||||
address: address,
|
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() })
|
sort.Slice(utxos, func(i, j int) bool { return utxos[i].UTXOEntry.Amount() > utxos[j].UTXOEntry.Amount() })
|
||||||
|
|
||||||
|
s.lock.Lock()
|
||||||
|
s.startTimeOfLastCompletedRefresh = refreshStart
|
||||||
s.utxosSortedByAmount = utxos
|
s.utxosSortedByAmount = utxos
|
||||||
|
s.mempoolExcludedUTXOs = mempoolExcludedUTXOs
|
||||||
|
|
||||||
|
// Cleanup expired used outpoints to avoid a memory leak
|
||||||
|
for outpoint, broadcastTime := range s.usedOutpoints {
|
||||||
|
if s.usedOutpointHasExpired(broadcastTime) {
|
||||||
|
delete(s.usedOutpoints, outpoint)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s.lock.Unlock()
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) refreshUTXOs() error {
|
func (s *server) refreshUTXOs() error {
|
||||||
|
refreshStart := time.Now()
|
||||||
|
|
||||||
|
// No need to lock for reading since the only writer of this set is on `syncLoop` on the same goroutine.
|
||||||
|
addresses := s.addressSet.strings()
|
||||||
// It's important to check the mempool before calling `GetUTXOsByAddresses`:
|
// It's important to check the mempool before calling `GetUTXOsByAddresses`:
|
||||||
// If we would do it the other way around an output can be spent in the mempool
|
// If we would do it the other way around an output can be spent in the mempool
|
||||||
// and not in consensus, and between the calls its spending transaction will be
|
// and not in consensus, and between the calls its spending transaction will be
|
||||||
// added to consensus and removed from the mempool, so `getUTXOsByAddressesResponse`
|
// added to consensus and removed from the mempool, so `getUTXOsByAddressesResponse`
|
||||||
// will include an obsolete output.
|
// will include an obsolete output.
|
||||||
mempoolEntriesByAddresses, err := s.rpcClient.GetMempoolEntriesByAddresses(s.addressSet.strings(), true, true)
|
mempoolEntriesByAddresses, err := s.backgroundRPCClient.GetMempoolEntriesByAddresses(addresses, true, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
getUTXOsByAddressesResponse, err := s.rpcClient.GetUTXOsByAddresses(s.addressSet.strings())
|
getUTXOsByAddressesResponse, err := s.backgroundRPCClient.GetUTXOsByAddresses(addresses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.updateUTXOSet(getUTXOsByAddressesResponse.Entries, mempoolEntriesByAddresses.Entries)
|
return s.updateUTXOSet(getUTXOsByAddressesResponse.Entries, mempoolEntriesByAddresses.Entries, refreshStart)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *server) forceSync() {
|
||||||
|
// Technically if two callers check the `if` simultaneously they will both spawn a
|
||||||
|
// goroutine, but we don't care about the small redundancy in such a rare case.
|
||||||
|
if len(s.forceSyncChan) == 0 {
|
||||||
|
go func() {
|
||||||
|
s.forceSyncChan <- struct{}{}
|
||||||
|
}()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) isSynced() bool {
|
func (s *server) isSynced() bool {
|
||||||
return s.nextSyncStartIndex > s.maxUsedIndex()
|
return s.nextSyncStartIndex > s.maxUsedIndex() && s.firstSyncDone.Load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) formatSyncStateReport() string {
|
func (s *server) formatSyncStateReport() string {
|
||||||
@ -291,8 +338,11 @@ func (s *server) formatSyncStateReport() string {
|
|||||||
maxUsedIndex = s.nextSyncStartIndex
|
maxUsedIndex = s.nextSyncStartIndex
|
||||||
}
|
}
|
||||||
|
|
||||||
return fmt.Sprintf("scanned %d out of %d addresses (%.2f%%)",
|
if s.nextSyncStartIndex < s.maxUsedIndex() {
|
||||||
s.nextSyncStartIndex, maxUsedIndex, float64(s.nextSyncStartIndex)*100.0/float64(maxUsedIndex))
|
return fmt.Sprintf("scanned %d out of %d addresses (%.2f%%)",
|
||||||
|
s.nextSyncStartIndex, maxUsedIndex, float64(s.nextSyncStartIndex)*100.0/float64(maxUsedIndex))
|
||||||
|
}
|
||||||
|
return "loading the wallet UTXO set"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAddresses uint32) {
|
func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAddresses uint32) {
|
||||||
@ -311,7 +361,7 @@ func (s *server) updateSyncingProgressLog(currProcessedAddresses, currMaxUsedAdd
|
|||||||
|
|
||||||
if s.maxProcessedAddressesForLog >= s.maxUsedAddressesForLog {
|
if s.maxProcessedAddressesForLog >= s.maxUsedAddressesForLog {
|
||||||
if !s.isLogFinalProgressLineShown {
|
if !s.isLogFinalProgressLineShown {
|
||||||
log.Infof("Wallet is synced, ready for queries")
|
log.Infof("Finished scanning recent addresses")
|
||||||
s.isLogFinalProgressLineShown = true
|
s.isLogFinalProgressLineShown = true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package main
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"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
|
// We use a separator that is not in the hex alphabet, but which will not split selection with a double click
|
||||||
const hexTransactionsSeparator = "_"
|
const hexTransactionsSeparator = "_"
|
||||||
|
|
||||||
func encodeTransactionsToHex(transactions [][]byte) string {
|
func EncodeTransactionsToHex(transactions [][]byte) string {
|
||||||
transactionsInHex := make([]string, len(transactions))
|
transactionsInHex := make([]string, len(transactions))
|
||||||
for i, transaction := range transactions {
|
for i, transaction := range transactions {
|
||||||
transactionsInHex[i] = hex.EncodeToString(transaction)
|
transactionsInHex[i] = hex.EncodeToString(transaction)
|
||||||
@ -17,7 +17,7 @@ func encodeTransactionsToHex(transactions [][]byte) string {
|
|||||||
return strings.Join(transactionsInHex, hexTransactionsSeparator)
|
return strings.Join(transactionsInHex, hexTransactionsSeparator)
|
||||||
}
|
}
|
||||||
|
|
||||||
func decodeTransactionsFromHex(transactionsHex string) ([][]byte, error) {
|
func DecodeTransactionsFromHex(transactionsHex string) ([][]byte, error) {
|
||||||
splitTransactionsHexes := strings.Split(transactionsHex, hexTransactionsSeparator)
|
splitTransactionsHexes := strings.Split(transactionsHex, hexTransactionsSeparator)
|
||||||
transactions := make([][]byte, len(splitTransactionsHexes))
|
transactions := make([][]byte, len(splitTransactionsHexes))
|
||||||
|
|
16
cmd/kaspawallet/daemon/server/version.go
Normal file
16
cmd/kaspawallet/daemon/server/version.go
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
|
"github.com/kaspanet/kaspad/version"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (s *server) GetVersion(_ context.Context, _ *pb.GetVersionRequest) (*pb.GetVersionResponse, error) {
|
||||||
|
s.lock.RLock()
|
||||||
|
defer s.lock.RUnlock()
|
||||||
|
|
||||||
|
return &pb.GetVersionResponse{
|
||||||
|
Version: version.Version(),
|
||||||
|
}, nil
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
# -- multistage docker build: stage #1: build stage
|
# -- 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
|
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||||
|
|
||||||
|
26
cmd/kaspawallet/get_daemon_version.go
Normal file
26
cmd/kaspawallet/get_daemon_version.go
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getDaemonVersion(conf *getDaemonVersionConfig) error {
|
||||||
|
daemonClient, tearDown, err := client.Connect(conf.DaemonAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer tearDown()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||||
|
defer cancel()
|
||||||
|
response, err := daemonClient.GetVersion(ctx, &pb.GetVersionRequest{})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Println(response.Version)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
@ -7,6 +7,7 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/subnetworks"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/utxo"
|
||||||
"github.com/kaspanet/kaspad/util"
|
"github.com/kaspanet/kaspad/util"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
@ -31,15 +32,10 @@ func CreateUnsignedTransaction(
|
|||||||
extendedPublicKeys []string,
|
extendedPublicKeys []string,
|
||||||
minimumSignatures uint32,
|
minimumSignatures uint32,
|
||||||
payments []*Payment,
|
payments []*Payment,
|
||||||
selectedUTXOs []*UTXO) ([]byte, error) {
|
selectedUTXOs []*UTXO) (*serialization.PartiallySignedTransaction, error) {
|
||||||
|
|
||||||
sortPublicKeys(extendedPublicKeys)
|
sortPublicKeys(extendedPublicKeys)
|
||||||
unsignedTransaction, err := createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
return createUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return serialization.SerializePartiallySignedTransaction(unsignedTransaction)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func multiSigRedeemScript(extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) ([]byte, error) {
|
func multiSigRedeemScript(extendedPublicKeys []string, minimumSignatures uint32, path string, ecdsa bool) ([]byte, error) {
|
||||||
@ -247,6 +243,12 @@ func ExtractTransactionDeserialized(partiallySignedTransaction *serialization.Pa
|
|||||||
}
|
}
|
||||||
partiallySignedTransaction.Tx.Inputs[i].SignatureScript = sigScript
|
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
|
return partiallySignedTransaction.Tx, nil
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package libkaspawallet_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet/serialization"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
@ -26,6 +27,20 @@ func forSchnorrAndECDSA(t *testing.T, testFunc func(t *testing.T, ecdsa bool)) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUnsignedTransactionSerialized(
|
||||||
|
extendedPublicKeys []string,
|
||||||
|
minimumSignatures uint32,
|
||||||
|
payments []*libkaspawallet.Payment,
|
||||||
|
selectedUTXOs []*libkaspawallet.UTXO) ([]byte, error) {
|
||||||
|
|
||||||
|
tx, err := libkaspawallet.CreateUnsignedTransaction(extendedPublicKeys, minimumSignatures, payments, selectedUTXOs)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return serialization.SerializePartiallySignedTransaction(tx)
|
||||||
|
}
|
||||||
|
|
||||||
func TestMultisig(t *testing.T) {
|
func TestMultisig(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
params := &consensusConfig.Params
|
params := &consensusConfig.Params
|
||||||
@ -102,7 +117,7 @@ func TestMultisig(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTransaction, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
@ -263,7 +278,7 @@ func TestP2PK(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTransaction, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTransaction, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
@ -425,7 +440,7 @@ func TestMaxSompi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTxWithLargeInputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTxWithLargeInputAmount, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 10,
|
Amount: 10,
|
||||||
@ -476,7 +491,7 @@ func TestMaxSompi(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
unsignedTxWithLargeInputAndOutputAmount, err := libkaspawallet.CreateUnsignedTransaction(publicKeys, minimumSignatures,
|
unsignedTxWithLargeInputAndOutputAmount, err := createUnsignedTransactionSerialized(publicKeys, minimumSignatures,
|
||||||
[]*libkaspawallet.Payment{{
|
[]*libkaspawallet.Payment{{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: 22e6 * constants.SompiPerKaspa,
|
Amount: 22e6 * constants.SompiPerKaspa,
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import "github.com/pkg/errors"
|
import (
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
subCmd, config := parseCommandLine()
|
subCmd, config := parseCommandLine()
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
switch subCmd {
|
switch subCmd {
|
||||||
case createSubCmd:
|
case createSubCmd:
|
||||||
@ -19,6 +20,8 @@ func main() {
|
|||||||
err = sign(config.(*signConfig))
|
err = sign(config.(*signConfig))
|
||||||
case broadcastSubCmd:
|
case broadcastSubCmd:
|
||||||
err = broadcast(config.(*broadcastConfig))
|
err = broadcast(config.(*broadcastConfig))
|
||||||
|
case broadcastReplacementSubCmd:
|
||||||
|
err = broadcastReplacement(config.(*broadcastConfig))
|
||||||
case parseSubCmd:
|
case parseSubCmd:
|
||||||
err = parse(config.(*parseConfig))
|
err = parse(config.(*parseConfig))
|
||||||
case showAddressesSubCmd:
|
case showAddressesSubCmd:
|
||||||
@ -31,6 +34,14 @@ func main() {
|
|||||||
err = startDaemon(config.(*startDaemonConfig))
|
err = startDaemon(config.(*startDaemonConfig))
|
||||||
case sweepSubCmd:
|
case sweepSubCmd:
|
||||||
err = sweep(config.(*sweepConfig))
|
err = sweep(config.(*sweepConfig))
|
||||||
|
case versionSubCmd:
|
||||||
|
showVersion()
|
||||||
|
case getDaemonVersionSubCmd:
|
||||||
|
err = getDaemonVersion(config.(*getDaemonVersionConfig))
|
||||||
|
case bumpFeeSubCmd:
|
||||||
|
err = bumpFee(config.(*bumpFeeConfig))
|
||||||
|
case bumpFeeUnsignedSubCmd:
|
||||||
|
err = bumpFeeUnsigned(config.(*bumpFeeUnsignedConfig))
|
||||||
default:
|
default:
|
||||||
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
|
err = errors.Errorf("Unknown sub-command '%s'\n", subCmd)
|
||||||
}
|
}
|
||||||
|
@ -3,13 +3,17 @@ package main
|
|||||||
import (
|
import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"fmt"
|
"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/cmd/kaspawallet/libkaspawallet/serialization"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/txscript"
|
||||||
|
"github.com/kaspanet/kaspad/util/txmass"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func parse(conf *parseConfig) error {
|
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")
|
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
|
transactionHex := conf.Transaction
|
||||||
if conf.TransactionFile != "" {
|
if conf.TransactionFile != "" {
|
||||||
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
|
transactionHexBytes, err := ioutil.ReadFile(conf.TransactionFile)
|
||||||
@ -29,10 +38,12 @@ func parse(conf *parseConfig) error {
|
|||||||
transactionHex = strings.TrimSpace(string(transactionHexBytes))
|
transactionHex = strings.TrimSpace(string(transactionHexBytes))
|
||||||
}
|
}
|
||||||
|
|
||||||
transactions, err := decodeTransactionsFromHex(transactionHex)
|
transactions, err := server.DecodeTransactionsFromHex(transactionHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
txMassCalculator := txmass.NewCalculator(conf.NetParams().MassPerTxByte, conf.NetParams().MassPerScriptPubKeyByte, conf.NetParams().MassPerSigOp)
|
||||||
for i, transaction := range transactions {
|
for i, transaction := range transactions {
|
||||||
|
|
||||||
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(transaction)
|
partiallySignedTransaction, err := serialization.DeserializePartiallySignedTransaction(transaction)
|
||||||
@ -78,7 +89,16 @@ func parse(conf *parseConfig) error {
|
|||||||
}
|
}
|
||||||
fmt.Println()
|
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
|
return nil
|
||||||
|
@ -10,7 +10,7 @@ import (
|
|||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,7 +35,28 @@ func send(conf *sendConfig) error {
|
|||||||
|
|
||||||
var sendAmountSompi uint64
|
var sendAmountSompi uint64
|
||||||
if !conf.IsSendAll {
|
if !conf.IsSendAll {
|
||||||
sendAmountSompi = uint64(conf.SendAmount * constants.SompiPerKaspa)
|
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},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createUnsignedTransactionsResponse, err :=
|
createUnsignedTransactionsResponse, err :=
|
||||||
@ -45,6 +66,7 @@ func send(conf *sendConfig) error {
|
|||||||
Amount: sendAmountSompi,
|
Amount: sendAmountSompi,
|
||||||
IsSendAll: conf.IsSendAll,
|
IsSendAll: conf.IsSendAll,
|
||||||
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
UseExistingChangeAddress: conf.UseExistingChangeAddress,
|
||||||
|
FeePolicy: feePolicy,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -71,23 +93,29 @@ func send(conf *sendConfig) error {
|
|||||||
signedTransactions[i] = signedTransaction
|
signedTransactions[i] = signedTransaction
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(signedTransactions) > 1 {
|
fmt.Printf("Broadcasting %d transaction(s)\n", len(signedTransactions))
|
||||||
fmt.Printf("Broadcasting %d transactions\n", len(signedTransactions))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Since we waited for user input when getting the password, which could take unbound amount of time -
|
// 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.
|
// create a new context for broadcast, to reset the timeout.
|
||||||
broadcastCtx, broadcastCancel := context.WithTimeout(context.Background(), daemonTimeout)
|
broadcastCtx, broadcastCancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||||
defer broadcastCancel()
|
defer broadcastCancel()
|
||||||
|
|
||||||
response, err := daemonClient.Broadcast(broadcastCtx, &pb.BroadcastRequest{Transactions: signedTransactions})
|
const chunkSize = 100 // To avoid sending a message bigger than the gRPC max message size, we split it to chunks
|
||||||
if err != nil {
|
for offset := 0; offset < len(signedTransactions); offset += chunkSize {
|
||||||
return err
|
end := len(signedTransactions)
|
||||||
}
|
if offset+chunkSize <= len(signedTransactions) {
|
||||||
fmt.Println("Transactions were sent successfully")
|
end = offset + chunkSize
|
||||||
fmt.Println("Transaction ID(s): ")
|
}
|
||||||
for _, txID := range response.TxIDs {
|
|
||||||
fmt.Printf("\t%s\n", txID)
|
chunk := signedTransactions[offset:end]
|
||||||
|
response, err := daemonClient.Broadcast(broadcastCtx, &pb.BroadcastRequest{Transactions: chunk})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
fmt.Printf("Broadcasted %d transaction(s) (broadcasted %.2f%% of the transactions so far)\n", len(chunk), 100*float64(end)/float64(len(signedTransactions)))
|
||||||
|
fmt.Println("Broadcasted Transaction ID(s): ")
|
||||||
|
for _, txID := range response.TxIDs {
|
||||||
|
fmt.Printf("\t%s\n", txID)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.Verbose {
|
if conf.Verbose {
|
||||||
|
@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/server"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
@ -40,7 +41,7 @@ func sign(conf *signConfig) error {
|
|||||||
}
|
}
|
||||||
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
|
transactionsHex = strings.TrimSpace(string(transactionHexBytes))
|
||||||
}
|
}
|
||||||
partiallySignedTransactions, err := decodeTransactionsFromHex(transactionsHex)
|
partiallySignedTransactions, err := server.DecodeTransactionsFromHex(transactionsHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -72,6 +73,6 @@ func sign(conf *signConfig) error {
|
|||||||
fmt.Fprintln(os.Stderr, "Successfully signed transaction")
|
fmt.Fprintln(os.Stderr, "Successfully signed transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(encodeTransactionsToHex(updatedPartiallySignedTransactions))
|
fmt.Println(server.EncodeTransactionsToHex(updatedPartiallySignedTransactions))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,8 +2,13 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormatKas takes the amount of sompis as uint64, and returns amount of KAS with 8 decimal places
|
// FormatKas takes the amount of sompis as uint64, and returns amount of KAS with 8 decimal places
|
||||||
@ -14,3 +19,50 @@ func FormatKas(amount uint64) string {
|
|||||||
}
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KasToSompi takes in a string representation of the Kas value to convert to Sompi
|
||||||
|
func KasToSompi(amount string) (uint64, error) {
|
||||||
|
err := validateKASAmountFormat(amount)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// after validation, amount can only be either an int OR
|
||||||
|
// a float with an int component and decimal places
|
||||||
|
parts := strings.Split(amount, ".")
|
||||||
|
amountStr := ""
|
||||||
|
|
||||||
|
if constants.SompiPerKaspa%10 != 0 {
|
||||||
|
return 0, errors.Errorf("Unable to convert to sompi when SompiPerKaspa is not a multiple of 10")
|
||||||
|
}
|
||||||
|
|
||||||
|
decimalPlaces := int(math.Log10(constants.SompiPerKaspa))
|
||||||
|
decimalStr := ""
|
||||||
|
|
||||||
|
if len(parts) == 2 {
|
||||||
|
decimalStr = parts[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
amountStr = fmt.Sprintf("%s%-*s", parts[0], decimalPlaces, decimalStr) // Padded with spaces at the end to fill for missing decimals: Sample "0.01234 "
|
||||||
|
amountStr = strings.ReplaceAll(amountStr, " ", "0") // Make the spaces be 0s. Sample "0.012340000"
|
||||||
|
|
||||||
|
convertedAmount, err := strconv.ParseUint(amountStr, 10, 64)
|
||||||
|
|
||||||
|
return convertedAmount, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func validateKASAmountFormat(amount string) error {
|
||||||
|
// Check whether it's an integer, or a float with max 8 digits
|
||||||
|
match, err := regexp.MatchString("^([1-9]\\d{0,11}|0)(\\.\\d{0,8})?$", amount)
|
||||||
|
|
||||||
|
if !match {
|
||||||
|
return errors.Errorf("Invalid amount")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
90
cmd/kaspawallet/utils/format_kas_test.go
Normal file
90
cmd/kaspawallet/utils/format_kas_test.go
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
package utils
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
// Takes in a string representation of the Kas value to convert to Sompi
|
||||||
|
func TestKasToSompi(t *testing.T) {
|
||||||
|
type testVector struct {
|
||||||
|
originalAmount string
|
||||||
|
convertedAmount uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
validCases := []testVector{
|
||||||
|
{originalAmount: "0", convertedAmount: 0},
|
||||||
|
{originalAmount: "1", convertedAmount: 100000000},
|
||||||
|
{originalAmount: "33184.1489732", convertedAmount: 3318414897320},
|
||||||
|
{originalAmount: "21.35808032", convertedAmount: 2135808032},
|
||||||
|
{originalAmount: "184467440737.09551615", convertedAmount: 18446744073709551615},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, currentTestVector := range validCases {
|
||||||
|
convertedAmount, err := KasToSompi(currentTestVector.originalAmount)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
} else if convertedAmount != currentTestVector.convertedAmount {
|
||||||
|
t.Errorf("Expected %s, to convert to %d. Got: %d", currentTestVector.originalAmount, currentTestVector.convertedAmount, convertedAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidCases := []string{
|
||||||
|
"184467440737.09551616", // Bigger than max uint64
|
||||||
|
"-1",
|
||||||
|
"a",
|
||||||
|
"",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, currentTestVector := range invalidCases {
|
||||||
|
_, err := KasToSompi(currentTestVector)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error but succeeded validation for test case %s", currentTestVector)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateAmountFormat(t *testing.T) {
|
||||||
|
validCases := []string{
|
||||||
|
"0",
|
||||||
|
"1",
|
||||||
|
"1.0",
|
||||||
|
"0.1",
|
||||||
|
"0.12345678",
|
||||||
|
"111111111111.11111111", // 12 digits to the left of decimal, 8 digits to the right
|
||||||
|
"184467440737.09551615", // Maximum input that can be represented in sompi later
|
||||||
|
"184467440737.09551616", // Cannot be represented in sompi, but we'll acccept for "correct format"
|
||||||
|
"999999999999.99999999", // Cannot be represented in sompi, but we'll acccept for "correct format"
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range validCases {
|
||||||
|
err := validateKASAmountFormat(testCase)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
invalidCases := []string{
|
||||||
|
"",
|
||||||
|
"a",
|
||||||
|
"-1",
|
||||||
|
"0.123456789", // 9 decimal digits
|
||||||
|
".1", // decimal but no integer component
|
||||||
|
"0a", // Extra character
|
||||||
|
"0000000000000", // 13 zeros
|
||||||
|
"012", // Int padded with zero
|
||||||
|
"00.1", // Decimal padded with zeros
|
||||||
|
"111111111111111111111", // all digits
|
||||||
|
"111111111111A11111111", // non-period/non-digit where decimal would be
|
||||||
|
"000000000000.00000000", // all zeros
|
||||||
|
"kaspa", // all text
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, testCase := range invalidCases {
|
||||||
|
err := validateKASAmountFormat(testCase)
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected an error but succeeded validation for test case %s", testCase)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
15
cmd/kaspawallet/version.go
Normal file
15
cmd/kaspawallet/version.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/version"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func showVersion() {
|
||||||
|
appName := filepath.Base(os.Args[0])
|
||||||
|
appName = strings.TrimSuffix(appName, filepath.Ext(appName))
|
||||||
|
fmt.Println(appName, "version", version.Version())
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
# -- multistage docker build: stage #1: build stage
|
# -- 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
|
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||||
|
|
||||||
|
@ -329,6 +329,7 @@ func initTestBlockAcceptanceDataForClone() []*externalapi.BlockAcceptanceData {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -377,6 +378,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -410,6 +412,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -443,6 +446,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -476,6 +480,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -510,6 +515,7 @@ func iniBlockAcceptanceDataForEqual() []testBlockAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -614,6 +620,7 @@ func initTestAcceptanceDataForClone() []externalapi.AcceptanceData {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -664,6 +671,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -697,6 +705,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -730,6 +739,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
@ -763,6 +773,7 @@ func initAcceptanceDataForEqual() []testAcceptanceDataStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{
|
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, 0x00,
|
||||||
|
@ -18,8 +18,9 @@ type DomainTransaction struct {
|
|||||||
Gas uint64
|
Gas uint64
|
||||||
Payload []byte
|
Payload []byte
|
||||||
|
|
||||||
Fee uint64
|
Fee uint64
|
||||||
Mass uint64
|
Mass uint64
|
||||||
|
MassCommitment uint64
|
||||||
|
|
||||||
// ID is a field that is used to cache the transaction ID.
|
// ID is a field that is used to cache the transaction ID.
|
||||||
// Always use consensushashing.TransactionID instead of accessing this field directly
|
// Always use consensushashing.TransactionID instead of accessing this field directly
|
||||||
@ -47,23 +48,24 @@ func (tx *DomainTransaction) Clone() *DomainTransaction {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return &DomainTransaction{
|
return &DomainTransaction{
|
||||||
Version: tx.Version,
|
Version: tx.Version,
|
||||||
Inputs: inputsClone,
|
Inputs: inputsClone,
|
||||||
Outputs: outputsClone,
|
Outputs: outputsClone,
|
||||||
LockTime: tx.LockTime,
|
LockTime: tx.LockTime,
|
||||||
SubnetworkID: *tx.SubnetworkID.Clone(),
|
SubnetworkID: *tx.SubnetworkID.Clone(),
|
||||||
Gas: tx.Gas,
|
Gas: tx.Gas,
|
||||||
Payload: payloadClone,
|
Payload: payloadClone,
|
||||||
Fee: tx.Fee,
|
Fee: tx.Fee,
|
||||||
Mass: tx.Mass,
|
Mass: tx.Mass,
|
||||||
ID: idClone,
|
MassCommitment: tx.MassCommitment,
|
||||||
|
ID: idClone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If this doesn't compile, it means the type definition has been changed, so it's
|
// If this doesn't compile, it means the type definition has been changed, so it's
|
||||||
// an indication to update Equal and Clone accordingly.
|
// an indication to update Equal and Clone accordingly.
|
||||||
var _ = DomainTransaction{0, []*DomainTransactionInput{}, []*DomainTransactionOutput{}, 0,
|
var _ = DomainTransaction{0, []*DomainTransactionInput{}, []*DomainTransactionOutput{}, 0,
|
||||||
DomainSubnetworkID{}, 0, []byte{}, 0, 0,
|
DomainSubnetworkID{}, 0, []byte{}, 0, 0, 0,
|
||||||
&DomainTransactionID{}}
|
&DomainTransactionID{}}
|
||||||
|
|
||||||
// Equal returns whether tx equals to other
|
// Equal returns whether tx equals to other
|
||||||
@ -112,6 +114,10 @@ func (tx *DomainTransaction) Equal(other *DomainTransaction) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tx.MassCommitment != other.MassCommitment {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
if tx.Fee != 0 && other.Fee != 0 && tx.Fee != other.Fee {
|
if tx.Fee != 0 && other.Fee != 0 && tx.Fee != other.Fee {
|
||||||
panic(errors.New("identical transactions should always have the same fee"))
|
panic(errors.New("identical transactions should always have the same fee"))
|
||||||
}
|
}
|
||||||
|
@ -83,6 +83,7 @@ func initTestBaseTransaction() *externalapi.DomainTransaction {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -112,6 +113,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -137,6 +139,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -162,6 +165,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01, 0x02}, //Changed
|
[]byte{0x01, 0x02}, //Changed
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -186,6 +190,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -213,6 +218,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -239,6 +245,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
1000000000, //Changed
|
1000000000, //Changed
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -263,6 +270,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -287,6 +295,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
2, //Changed
|
2, //Changed
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -311,6 +320,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -341,6 +351,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -366,6 +377,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -390,6 +402,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
nil, //changed
|
nil, //changed
|
||||||
},
|
},
|
||||||
expectedResult: true,
|
expectedResult: true,
|
||||||
@ -411,6 +424,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -435,6 +449,7 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -461,6 +476,34 @@ func initTestTransactionToCompare() []*transactionToCompare {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
0,
|
0,
|
||||||
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,
|
||||||
|
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,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -536,6 +579,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
externalapi.NewDomainTransactionIDFromByteArray(&[externalapi.DomainHashSize]byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
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, 0x00,
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
@ -555,6 +599,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
expectedResult: false,
|
expectedResult: false,
|
||||||
@ -569,6 +614,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
expectedResult: true,
|
expectedResult: true,
|
||||||
@ -583,6 +629,7 @@ func initTestDomainTransactionForEqual() []testDomainTransactionStruct {
|
|||||||
[]byte{0x01},
|
[]byte{0x01},
|
||||||
2, // Changed fee
|
2, // Changed fee
|
||||||
1,
|
1,
|
||||||
|
0,
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
expectsPanic: true,
|
expectsPanic: true,
|
||||||
|
@ -43,6 +43,9 @@ type TestConsensus interface {
|
|||||||
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
|
AddBlock(parentHashes []*externalapi.DomainHash, coinbaseData *externalapi.DomainCoinbaseData,
|
||||||
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||||
|
|
||||||
|
AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
|
||||||
|
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||||
|
|
||||||
AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error)
|
||||||
|
|
||||||
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
AddUTXOInvalidBlock(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
||||||
|
@ -131,11 +131,12 @@ func (bb *blockBuilder) validateTransactions(stagingArea *model.StagingArea,
|
|||||||
for _, transaction := range transactions {
|
for _, transaction := range transactions {
|
||||||
err := bb.validateTransaction(stagingArea, transaction)
|
err := bb.validateTransaction(stagingArea, transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if !errors.As(err, &ruleerrors.RuleError{}) {
|
ruleError := ruleerrors.RuleError{}
|
||||||
|
if !errors.As(err, &ruleError) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
invalidTransactions = append(invalidTransactions,
|
invalidTransactions = append(invalidTransactions,
|
||||||
ruleerrors.InvalidTransaction{Transaction: transaction, Error: err})
|
ruleerrors.InvalidTransaction{Transaction: transaction, Error: &ruleError})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ func NewErrMissingParents(missingParentHashes []*externalapi.DomainHash) error {
|
|||||||
// InvalidTransaction is a struct containing an invalid transaction, and the error explaining why it's invalid.
|
// InvalidTransaction is a struct containing an invalid transaction, and the error explaining why it's invalid.
|
||||||
type InvalidTransaction struct {
|
type InvalidTransaction struct {
|
||||||
Transaction *externalapi.DomainTransaction
|
Transaction *externalapi.DomainTransaction
|
||||||
Error error
|
Error *RuleError
|
||||||
}
|
}
|
||||||
|
|
||||||
func (invalid InvalidTransaction) String() string {
|
func (invalid InvalidTransaction) String() string {
|
||||||
|
@ -3,9 +3,10 @@ package ruleerrors
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,7 +50,7 @@ func TestNewErrMissingTxOut(t *testing.T) {
|
|||||||
func TestNewErrInvalidTransactionsInNewBlock(t *testing.T) {
|
func TestNewErrInvalidTransactionsInNewBlock(t *testing.T) {
|
||||||
tx := &externalapi.DomainTransaction{Fee: 1337}
|
tx := &externalapi.DomainTransaction{Fee: 1337}
|
||||||
txID := consensushashing.TransactionID(tx)
|
txID := consensushashing.TransactionID(tx)
|
||||||
outer := NewErrInvalidTransactionsInNewBlock([]InvalidTransaction{{tx, ErrNoTxInputs}})
|
outer := NewErrInvalidTransactionsInNewBlock([]InvalidTransaction{{tx, &ErrNoTxInputs}})
|
||||||
//TODO: Implement Stringer for `DomainTransaction`
|
//TODO: Implement Stringer for `DomainTransaction`
|
||||||
expectedOuterErr := fmt.Sprintf("ErrInvalidTransactionsInNewBlock: [(%s: ErrNoTxInputs)]", txID)
|
expectedOuterErr := fmt.Sprintf("ErrInvalidTransactionsInNewBlock: [(%s: ErrNoTxInputs)]", txID)
|
||||||
inner := &ErrInvalidTransactionsInNewBlock{}
|
inner := &ErrInvalidTransactionsInNewBlock{}
|
||||||
@ -60,7 +61,7 @@ func TestNewErrInvalidTransactionsInNewBlock(t *testing.T) {
|
|||||||
if len(inner.InvalidTransactions) != 1 {
|
if len(inner.InvalidTransactions) != 1 {
|
||||||
t.Fatalf("TestNewErrInvalidTransactionsInNewBlock: Expected len(inner.MissingOutpoints) 1, found: %d", len(inner.InvalidTransactions))
|
t.Fatalf("TestNewErrInvalidTransactionsInNewBlock: Expected len(inner.MissingOutpoints) 1, found: %d", len(inner.InvalidTransactions))
|
||||||
}
|
}
|
||||||
if inner.InvalidTransactions[0].Error != ErrNoTxInputs {
|
if *inner.InvalidTransactions[0].Error != ErrNoTxInputs {
|
||||||
t.Fatalf("TestNewErrInvalidTransactionsInNewBlock: Expected ErrNoTxInputs. found: %v", inner.InvalidTransactions[0].Error)
|
t.Fatalf("TestNewErrInvalidTransactionsInNewBlock: Expected ErrNoTxInputs. found: %v", inner.InvalidTransactions[0].Error)
|
||||||
}
|
}
|
||||||
if inner.InvalidTransactions[0].Transaction.Fee != 1337 {
|
if inner.InvalidTransactions[0].Transaction.Fee != 1337 {
|
||||||
|
@ -69,6 +69,17 @@ func (tc *testConsensus) AddBlock(parentHashes []*externalapi.DomainHash, coinba
|
|||||||
return consensushashing.BlockHash(block), virtualChangeSet, nil
|
return consensushashing.BlockHash(block), virtualChangeSet, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (tc *testConsensus) AddBlockOnTips(coinbaseData *externalapi.DomainCoinbaseData,
|
||||||
|
transactions []*externalapi.DomainTransaction) (*externalapi.DomainHash, *externalapi.VirtualChangeSet, error) {
|
||||||
|
|
||||||
|
tips, err := tc.Tips()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return tc.AddBlock(tips, coinbaseData, transactions)
|
||||||
|
}
|
||||||
|
|
||||||
func (tc *testConsensus) AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
func (tc *testConsensus) AddUTXOInvalidHeader(parentHashes []*externalapi.DomainHash) (*externalapi.DomainHash,
|
||||||
*externalapi.VirtualChangeSet, error) {
|
*externalapi.VirtualChangeSet, error) {
|
||||||
|
|
||||||
|
@ -210,7 +210,7 @@ func getOutputsHash(tx *externalapi.DomainTransaction, inputIndex int, hashType
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getPayloadHash(tx *externalapi.DomainTransaction, reusedValues *SighashReusedValues) *externalapi.DomainHash {
|
func getPayloadHash(tx *externalapi.DomainTransaction, reusedValues *SighashReusedValues) *externalapi.DomainHash {
|
||||||
if tx.SubnetworkID.Equal(&subnetworks.SubnetworkIDNative) {
|
if tx.SubnetworkID.Equal(&subnetworks.SubnetworkIDNative) && len(tx.Payload) == 0 {
|
||||||
return externalapi.NewZeroHash()
|
return externalapi.NewZeroHash()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ func TransactionHash(tx *externalapi.DomainTransaction) *externalapi.DomainHash
|
|||||||
// Encode the header and hash everything prior to the number of
|
// Encode the header and hash everything prior to the number of
|
||||||
// transactions.
|
// transactions.
|
||||||
writer := hashes.NewTransactionHashWriter()
|
writer := hashes.NewTransactionHashWriter()
|
||||||
err := serializeTransaction(writer, tx, txEncodingFull)
|
err := serializeTransaction(writer, tx, txEncodingFull, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// It seems like this could only happen if the writer returned an error.
|
// 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)
|
// 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
|
encodingFlags = txEncodingExcludeSignatureScript
|
||||||
}
|
}
|
||||||
writer := hashes.NewTransactionIDWriter()
|
writer := hashes.NewTransactionIDWriter()
|
||||||
err := serializeTransaction(writer, tx, encodingFlags)
|
err := serializeTransaction(writer, tx, encodingFlags, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// this writer never return errors (no allocations or possible failures) so errors can only come from validity checks,
|
// 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.
|
// and we assume we never construct malformed transactions.
|
||||||
@ -74,7 +74,7 @@ func TransactionIDs(txs []*externalapi.DomainTransaction) []*externalapi.DomainT
|
|||||||
return txIDs
|
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)
|
err := binaryserializer.PutUint16(w, tx.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -126,6 +126,15 @@ func serializeTransaction(w io.Writer, tx *externalapi.DomainTransaction, encodi
|
|||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,7 +387,7 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(pops) != 20 {
|
if len(pops) != 19 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
isAtomicSwap := pops[0].opcode.value == OpIf &&
|
isAtomicSwap := pops[0].opcode.value == OpIf &&
|
||||||
@ -403,13 +403,12 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
pops[10].opcode.value == OpElse &&
|
pops[10].opcode.value == OpElse &&
|
||||||
canonicalPush(pops[11]) &&
|
canonicalPush(pops[11]) &&
|
||||||
pops[12].opcode.value == OpCheckLockTimeVerify &&
|
pops[12].opcode.value == OpCheckLockTimeVerify &&
|
||||||
pops[13].opcode.value == OpDrop &&
|
pops[13].opcode.value == OpDup &&
|
||||||
pops[14].opcode.value == OpDup &&
|
pops[14].opcode.value == OpBlake2b &&
|
||||||
pops[15].opcode.value == OpBlake2b &&
|
pops[15].opcode.value == OpData32 &&
|
||||||
pops[16].opcode.value == OpData32 &&
|
pops[16].opcode.value == OpEndIf &&
|
||||||
pops[17].opcode.value == OpEndIf &&
|
pops[17].opcode.value == OpEqualVerify &&
|
||||||
pops[18].opcode.value == OpEqualVerify &&
|
pops[18].opcode.value == OpCheckSig
|
||||||
pops[19].opcode.value == OpCheckSig
|
|
||||||
if !isAtomicSwap {
|
if !isAtomicSwap {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -417,9 +416,9 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
pushes := new(AtomicSwapDataPushes)
|
pushes := new(AtomicSwapDataPushes)
|
||||||
copy(pushes.SecretHash[:], pops[5].data)
|
copy(pushes.SecretHash[:], pops[5].data)
|
||||||
copy(pushes.RecipientBlake2b[:], pops[9].data)
|
copy(pushes.RecipientBlake2b[:], pops[9].data)
|
||||||
copy(pushes.RefundBlake2b[:], pops[16].data)
|
copy(pushes.RefundBlake2b[:], pops[15].data)
|
||||||
if pops[2].data != nil {
|
if pops[2].data != nil {
|
||||||
locktime, err := makeScriptNum(pops[2].data, 5)
|
locktime, err := makeScriptNum(pops[2].data, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
@ -430,7 +429,7 @@ func ExtractAtomicSwapDataPushes(version uint16, scriptPubKey []byte) (*AtomicSw
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
if pops[11].data != nil {
|
if pops[11].data != nil {
|
||||||
locktime, err := makeScriptNum(pops[11].data, 5)
|
locktime, err := makeScriptNum(pops[11].data, 8)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
@ -232,6 +232,8 @@ var MainnetParams = Params{
|
|||||||
"seeder4.kaspad.net",
|
"seeder4.kaspad.net",
|
||||||
// This DNS seeder is run by Tim
|
// This DNS seeder is run by Tim
|
||||||
"kaspadns.kaspacalc.net",
|
"kaspadns.kaspacalc.net",
|
||||||
|
// This DNS seeder is run by supertypo
|
||||||
|
"n-mainnet.kaspa.ws",
|
||||||
},
|
},
|
||||||
|
|
||||||
// DAG parameters
|
// DAG parameters
|
||||||
|
@ -147,16 +147,12 @@ func (btb *blockTemplateBuilder) BuildBlockTemplate(
|
|||||||
invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
|
invalidTxsErr := ruleerrors.ErrInvalidTransactionsInNewBlock{}
|
||||||
if errors.As(err, &invalidTxsErr) {
|
if errors.As(err, &invalidTxsErr) {
|
||||||
log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate")
|
log.Criticalf("consensusReference.Consensus().BuildBlock returned invalid txs in BuildBlockTemplate")
|
||||||
invalidTxs := make([]*consensusexternalapi.DomainTransaction, 0, len(invalidTxsErr.InvalidTransactions))
|
err = btb.mempool.RemoveInvalidTransactions(&invalidTxsErr)
|
||||||
for _, tx := range invalidTxsErr.InvalidTransactions {
|
|
||||||
invalidTxs = append(invalidTxs, tx.Transaction)
|
|
||||||
}
|
|
||||||
err = btb.mempool.RemoveTransactions(invalidTxs, true)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// mempool.RemoveTransactions might return errors in situations that are perfectly fine in this context.
|
// mempool.RemoveInvalidTransactions might return errors in situations that are perfectly fine in this context.
|
||||||
// TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`:
|
// TODO: Once the mempool invariants are clear, this should be converted back `return nil, err`:
|
||||||
// https://github.com/kaspanet/kaspad/issues/1553
|
// https://github.com/kaspanet/kaspad/issues/1553
|
||||||
log.Criticalf("Error from mempool.RemoveTransactions: %+v", err)
|
log.Criticalf("Error from mempool.RemoveInvalidTransactions: %+v", err)
|
||||||
}
|
}
|
||||||
// We can call this recursively without worry because this should almost never happen
|
// We can call this recursively without worry because this should almost never happen
|
||||||
return btb.BuildBlockTemplate(coinbaseData)
|
return btb.BuildBlockTemplate(coinbaseData)
|
||||||
|
@ -51,6 +51,7 @@ const (
|
|||||||
RejectDifficulty RejectCode = 0x44
|
RejectDifficulty RejectCode = 0x44
|
||||||
RejectImmatureSpend RejectCode = 0x45
|
RejectImmatureSpend RejectCode = 0x45
|
||||||
RejectBadOrphan RejectCode = 0x64
|
RejectBadOrphan RejectCode = 0x64
|
||||||
|
RejectSpamTx RejectCode = 0x65
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map of reject codes back strings for pretty printing.
|
// Map of reject codes back strings for pretty printing.
|
||||||
|
@ -3,6 +3,11 @@ package mempool
|
|||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensusreference"
|
"github.com/kaspanet/kaspad/domain/consensusreference"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
@ -141,7 +146,57 @@ func (mp *mempool) BlockCandidateTransactions() []*externalapi.DomainTransaction
|
|||||||
mp.mtx.RLock()
|
mp.mtx.RLock()
|
||||||
defer mp.mtx.RUnlock()
|
defer mp.mtx.RUnlock()
|
||||||
|
|
||||||
return mp.transactionsPool.allReadyTransactions()
|
readyTxs := mp.transactionsPool.allReadyTransactions()
|
||||||
|
var candidateTxs []*externalapi.DomainTransaction
|
||||||
|
var spamTx *externalapi.DomainTransaction
|
||||||
|
var spamTxNewestUTXODaaScore uint64
|
||||||
|
for _, tx := range readyTxs {
|
||||||
|
if len(tx.Outputs) > len(tx.Inputs) {
|
||||||
|
hasCoinbaseInput := false
|
||||||
|
for _, input := range tx.Inputs {
|
||||||
|
if input.UTXOEntry.IsCoinbase() {
|
||||||
|
hasCoinbaseInput = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numExtraOuts := len(tx.Outputs) - len(tx.Inputs)
|
||||||
|
if !hasCoinbaseInput && numExtraOuts > 2 && tx.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa {
|
||||||
|
log.Debugf("Filtered spam tx %s", consensushashing.TransactionID(tx))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasCoinbaseInput || tx.Fee > uint64(numExtraOuts)*constants.SompiPerKaspa {
|
||||||
|
candidateTxs = append(candidateTxs, tx)
|
||||||
|
} else {
|
||||||
|
txNewestUTXODaaScore := tx.Inputs[0].UTXOEntry.BlockDAAScore()
|
||||||
|
for _, input := range tx.Inputs {
|
||||||
|
if input.UTXOEntry.BlockDAAScore() > txNewestUTXODaaScore {
|
||||||
|
txNewestUTXODaaScore = input.UTXOEntry.BlockDAAScore()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if spamTx != nil {
|
||||||
|
if txNewestUTXODaaScore < spamTxNewestUTXODaaScore {
|
||||||
|
spamTx = tx
|
||||||
|
spamTxNewestUTXODaaScore = txNewestUTXODaaScore
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
spamTx = tx
|
||||||
|
spamTxNewestUTXODaaScore = txNewestUTXODaaScore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
candidateTxs = append(candidateTxs, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if spamTx != nil {
|
||||||
|
log.Debugf("Adding spam tx candidate %s", consensushashing.TransactionID(spamTx))
|
||||||
|
candidateTxs = append(candidateTxs, spamTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
return candidateTxs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) {
|
func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*externalapi.DomainTransaction, err error) {
|
||||||
@ -151,11 +206,19 @@ func (mp *mempool) RevalidateHighPriorityTransactions() (validTransactions []*ex
|
|||||||
return mp.revalidateHighPriorityTransactions()
|
return mp.revalidateHighPriorityTransactions()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *mempool) RemoveTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
|
func (mp *mempool) RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error {
|
||||||
mp.mtx.Lock()
|
mp.mtx.Lock()
|
||||||
defer mp.mtx.Unlock()
|
defer mp.mtx.Unlock()
|
||||||
|
|
||||||
return mp.removeTransactions(transactions, removeRedeemers)
|
for _, tx := range err.InvalidTransactions {
|
||||||
|
removeRedeemers := !errors.As(tx.Error, &ruleerrors.ErrMissingTxOut{})
|
||||||
|
err := mp.removeTransaction(consensushashing.TransactionID(tx.Transaction), removeRedeemers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
func (mp *mempool) RemoveTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
||||||
|
@ -2,20 +2,9 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
|
||||||
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
"github.com/kaspanet/kaspad/domain/miningmanager/mempool/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (mp *mempool) removeTransactions(transactions []*externalapi.DomainTransaction, removeRedeemers bool) error {
|
|
||||||
for _, transaction := range transactions {
|
|
||||||
err := mp.removeTransaction(consensushashing.TransactionID(transaction), removeRedeemers)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
func (mp *mempool) removeTransaction(transactionID *externalapi.DomainTransactionID, removeRedeemers bool) error {
|
||||||
if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok {
|
if _, ok := mp.orphansPool.allOrphans[*transactionID]; ok {
|
||||||
return mp.orphansPool.removeOrphan(transactionID, true)
|
return mp.orphansPool.removeOrphan(transactionID, true)
|
||||||
|
@ -7,20 +7,85 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTransaction, error) {
|
func (mp *mempool) revalidateHighPriorityTransactions() ([]*externalapi.DomainTransaction, error) {
|
||||||
|
type txNode struct {
|
||||||
|
children map[externalapi.DomainTransactionID]struct{}
|
||||||
|
nonVisitedParents int
|
||||||
|
tx *model.MempoolTransaction
|
||||||
|
visited bool
|
||||||
|
}
|
||||||
|
|
||||||
onEnd := logger.LogAndMeasureExecutionTime(log, "revalidateHighPriorityTransactions")
|
onEnd := logger.LogAndMeasureExecutionTime(log, "revalidateHighPriorityTransactions")
|
||||||
defer onEnd()
|
defer onEnd()
|
||||||
|
|
||||||
|
// We revalidate transactions in topological order in case there are dependencies between them
|
||||||
|
|
||||||
|
// Naturally transactions point to their dependencies, but since we want to start processing the dependencies
|
||||||
|
// first, we build the opposite DAG. We initially fill `queue` with transactions with no dependencies.
|
||||||
|
txDAG := make(map[externalapi.DomainTransactionID]*txNode)
|
||||||
|
|
||||||
|
maybeAddNode := func(txID externalapi.DomainTransactionID) *txNode {
|
||||||
|
if node, ok := txDAG[txID]; ok {
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
node := &txNode{
|
||||||
|
children: make(map[externalapi.DomainTransactionID]struct{}),
|
||||||
|
nonVisitedParents: 0,
|
||||||
|
tx: mp.transactionsPool.highPriorityTransactions[txID],
|
||||||
|
}
|
||||||
|
txDAG[txID] = node
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
|
||||||
|
queue := make([]*txNode, 0, len(mp.transactionsPool.highPriorityTransactions))
|
||||||
|
for id, transaction := range mp.transactionsPool.highPriorityTransactions {
|
||||||
|
node := maybeAddNode(id)
|
||||||
|
|
||||||
|
parents := make(map[externalapi.DomainTransactionID]struct{})
|
||||||
|
for _, input := range transaction.Transaction().Inputs {
|
||||||
|
if _, ok := mp.transactionsPool.highPriorityTransactions[input.PreviousOutpoint.TransactionID]; !ok {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
parents[input.PreviousOutpoint.TransactionID] = struct{}{} // To avoid duplicate parents, we first add it to a set and then count it
|
||||||
|
maybeAddNode(input.PreviousOutpoint.TransactionID).children[id] = struct{}{}
|
||||||
|
}
|
||||||
|
node.nonVisitedParents = len(parents)
|
||||||
|
|
||||||
|
if node.nonVisitedParents == 0 {
|
||||||
|
queue = append(queue, node)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
validTransactions := []*externalapi.DomainTransaction{}
|
validTransactions := []*externalapi.DomainTransaction{}
|
||||||
for _, transaction := range mp.transactionsPool.highPriorityTransactions {
|
|
||||||
|
// Now we iterate the DAG in topological order using BFS
|
||||||
|
for len(queue) > 0 {
|
||||||
|
var node *txNode
|
||||||
|
node, queue = queue[0], queue[1:]
|
||||||
|
|
||||||
|
if node.visited {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
node.visited = true
|
||||||
|
|
||||||
|
transaction := node.tx
|
||||||
isValid, err := mp.revalidateTransaction(transaction)
|
isValid, err := mp.revalidateTransaction(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !isValid {
|
|
||||||
continue
|
for child := range node.children {
|
||||||
|
childNode := txDAG[child]
|
||||||
|
childNode.nonVisitedParents--
|
||||||
|
if childNode.nonVisitedParents == 0 {
|
||||||
|
queue = append(queue, txDAG[child])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
validTransactions = append(validTransactions, transaction.Transaction().Clone())
|
if isValid {
|
||||||
|
validTransactions = append(validTransactions, transaction.Transaction().Clone())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return validTransactions, nil
|
return validTransactions, nil
|
||||||
@ -35,7 +100,7 @@ func (mp *mempool) revalidateTransaction(transaction *model.MempoolTransaction)
|
|||||||
}
|
}
|
||||||
if len(missingParents) > 0 {
|
if len(missingParents) > 0 {
|
||||||
log.Debugf("Removing transaction %s, it failed revalidation", transaction.TransactionID())
|
log.Debugf("Removing transaction %s, it failed revalidation", transaction.TransactionID())
|
||||||
err := mp.removeTransaction(transaction.TransactionID(), true)
|
err := mp.removeTransaction(transaction.TransactionID(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/infrastructure/logger"
|
"github.com/kaspanet/kaspad/infrastructure/logger"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
@ -2,6 +2,7 @@ package mempool
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/consensushashing"
|
||||||
@ -44,6 +45,20 @@ func (mp *mempool) validateTransactionInIsolation(transaction *externalapi.Domai
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (mp *mempool) validateTransactionInContext(transaction *externalapi.DomainTransaction) error {
|
func (mp *mempool) validateTransactionInContext(transaction *externalapi.DomainTransaction) error {
|
||||||
|
hasCoinbaseInput := false
|
||||||
|
for _, input := range transaction.Inputs {
|
||||||
|
if input.UTXOEntry.IsCoinbase() {
|
||||||
|
hasCoinbaseInput = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
numExtraOuts := len(transaction.Outputs) - len(transaction.Inputs)
|
||||||
|
if !hasCoinbaseInput && numExtraOuts > 2 && transaction.Fee < uint64(numExtraOuts)*constants.SompiPerKaspa {
|
||||||
|
log.Warnf("Rejected spam tx %s from mempool (%d outputs)", consensushashing.TransactionID(transaction), len(transaction.Outputs))
|
||||||
|
return transactionRuleError(RejectSpamTx, fmt.Sprintf("Rejected spam tx %s from mempool", consensushashing.TransactionID(transaction)))
|
||||||
|
}
|
||||||
|
|
||||||
if !mp.config.AcceptNonStandard {
|
if !mp.config.AcceptNonStandard {
|
||||||
err := mp.checkTransactionStandardInContext(transaction)
|
err := mp.checkTransactionStandardInContext(transaction)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -577,6 +577,72 @@ func TestRevalidateHighPriorityTransactions(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRevalidateHighPriorityTransactionsWithChain(t *testing.T) {
|
||||||
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
|
consensusConfig.BlockCoinbaseMaturity = 0
|
||||||
|
factory := consensus.NewFactory()
|
||||||
|
tc, teardown, err := factory.NewTestConsensus(consensusConfig, "TestRevalidateHighPriorityTransactions")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed setting up TestConsensus: %+v", err)
|
||||||
|
}
|
||||||
|
defer teardown(false)
|
||||||
|
|
||||||
|
miningFactory := miningmanager.NewFactory()
|
||||||
|
mempoolConfig := mempool.DefaultConfig(&consensusConfig.Params)
|
||||||
|
tcAsConsensus := tc.(externalapi.Consensus)
|
||||||
|
tcAsConsensusPointer := &tcAsConsensus
|
||||||
|
consensusReference := consensusreference.NewConsensusReference(&tcAsConsensusPointer)
|
||||||
|
miningManager := miningFactory.NewMiningManager(consensusReference, &consensusConfig.Params, mempoolConfig)
|
||||||
|
|
||||||
|
const chainSize = 10
|
||||||
|
chain, err := createTxChain(tc, chainSize)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = miningManager.ValidateAndInsertTransaction(chain[0], true, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockHash, _, err := tc.AddBlockOnTips(nil, []*externalapi.DomainTransaction{chain[0].Clone()})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
block, _, err := tc.GetBlock(blockHash)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = miningManager.HandleNewBlockTransactions(block.Transactions)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, transaction := range chain[1:] {
|
||||||
|
_, err = miningManager.ValidateAndInsertTransaction(transaction, true, false)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_, _, err = tc.AddBlockOnTips(nil, []*externalapi.DomainTransaction{chain[1].Clone()})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
revalidated, err := miningManager.RevalidateHighPriorityTransactions()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(revalidated) != chainSize-2 {
|
||||||
|
t.Fatalf("expected %d transactions to revalidate but instead only %d revalidated", chainSize-2, len(revalidated))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// TestModifyBlockTemplate verifies that modifying a block template changes coinbase data correctly.
|
// TestModifyBlockTemplate verifies that modifying a block template changes coinbase data correctly.
|
||||||
func TestModifyBlockTemplate(t *testing.T) {
|
func TestModifyBlockTemplate(t *testing.T) {
|
||||||
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
testutils.ForAllNets(t, true, func(t *testing.T, consensusConfig *consensus.Config) {
|
||||||
@ -904,40 +970,58 @@ func createArraysOfParentAndChildrenTransactions(tc testapi.TestConsensus) ([]*e
|
|||||||
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (txParent *externalapi.DomainTransaction,
|
func createParentAndChildrenTransactions(tc testapi.TestConsensus) (txParent *externalapi.DomainTransaction,
|
||||||
txChild *externalapi.DomainTransaction, err error) {
|
txChild *externalapi.DomainTransaction, err error) {
|
||||||
|
|
||||||
|
chain, err := createTxChain(tc, 2)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain[0], chain[1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createTxChain(tc testapi.TestConsensus, numTxs int) ([]*externalapi.DomainTransaction, error) {
|
||||||
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
|
// We will add two blocks by consensus before the parent transactions, in order to fund the parent transactions.
|
||||||
tips, err := tc.Tips()
|
tips, err := tc.Tips()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = tc.AddBlock(tips, nil, nil)
|
_, _, err = tc.AddBlock(tips, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrapf(err, "AddBlock: %v", err)
|
return nil, errors.Wrapf(err, "AddBlock: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
tips, err = tc.Tips()
|
tips, err = tc.Tips()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
|
fundingBlockHashForParent, _, err := tc.AddBlock(tips, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "AddBlock: ")
|
return nil, errors.Wrap(err, "AddBlock: ")
|
||||||
}
|
}
|
||||||
fundingBlockForParent, _, err := tc.GetBlock(fundingBlockHashForParent)
|
fundingBlockForParent, _, err := tc.GetBlock(fundingBlockHashForParent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrap(err, "GetBlock: ")
|
return nil, errors.Wrap(err, "GetBlock: ")
|
||||||
}
|
}
|
||||||
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
fundingTransactionForParent := fundingBlockForParent.Transactions[transactionhelper.CoinbaseTransactionIndex]
|
||||||
txParent, err = testutils.CreateTransaction(fundingTransactionForParent, 1000)
|
|
||||||
|
transactions := make([]*externalapi.DomainTransaction, numTxs)
|
||||||
|
transactions[0], err = testutils.CreateTransaction(fundingTransactionForParent, 1000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
txChild, err = testutils.CreateTransaction(txParent, 1000)
|
|
||||||
if err != nil {
|
txParent := transactions[0]
|
||||||
return nil, nil, err
|
for i := 1; i < numTxs; i++ {
|
||||||
|
transactions[i], err = testutils.CreateTransaction(txParent, 1000)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
txParent = transactions[i]
|
||||||
}
|
}
|
||||||
return txParent, txChild, nil
|
|
||||||
|
return transactions, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
func createChildAndParentTxsAndAddParentToConsensus(tc testapi.TestConsensus) (*externalapi.DomainTransaction, error) {
|
||||||
|
@ -2,6 +2,7 @@ package model
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/ruleerrors"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mempool maintains a set of known transactions that
|
// Mempool maintains a set of known transactions that
|
||||||
@ -11,7 +12,7 @@ type Mempool interface {
|
|||||||
BlockCandidateTransactions() []*externalapi.DomainTransaction
|
BlockCandidateTransactions() []*externalapi.DomainTransaction
|
||||||
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
|
ValidateAndInsertTransaction(transaction *externalapi.DomainTransaction, isHighPriority bool, allowOrphan bool) (
|
||||||
acceptedTransactions []*externalapi.DomainTransaction, err error)
|
acceptedTransactions []*externalapi.DomainTransaction, err error)
|
||||||
RemoveTransactions(txs []*externalapi.DomainTransaction, removeRedeemers bool) error
|
RemoveInvalidTransactions(err *ruleerrors.ErrInvalidTransactionsInNewBlock) error
|
||||||
GetTransaction(
|
GetTransaction(
|
||||||
transactionID *externalapi.DomainTransactionID,
|
transactionID *externalapi.DomainTransactionID,
|
||||||
includeTransactionPool bool,
|
includeTransactionPool bool,
|
||||||
|
@ -52,6 +52,8 @@ func (ui *UTXOIndex) Reset() error {
|
|||||||
ui.mutex.Lock()
|
ui.mutex.Lock()
|
||||||
defer ui.mutex.Unlock()
|
defer ui.mutex.Unlock()
|
||||||
|
|
||||||
|
log.Infof("Starting UTXO index reset")
|
||||||
|
|
||||||
err := ui.store.deleteAll()
|
err := ui.store.deleteAll()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -88,7 +90,13 @@ func (ui *UTXOIndex) Reset() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// This has to be done last to mark that the reset went smoothly and no reset has to be called next time.
|
// This has to be done last to mark that the reset went smoothly and no reset has to be called next time.
|
||||||
return ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
|
err = ui.store.updateAndCommitVirtualParentsWithoutTransaction(virtualInfo.ParentHashes)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Finished UTXO index reset")
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ui *UTXOIndex) isSynced() (bool, error) {
|
func (ui *UTXOIndex) isSynced() (bool, error) {
|
||||||
|
19
go.mod
19
go.mod
@ -1,6 +1,6 @@
|
|||||||
module github.com/kaspanet/kaspad
|
module github.com/kaspanet/kaspad
|
||||||
|
|
||||||
go 1.18
|
go 1.23
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/btcsuite/btcutil v1.0.2
|
github.com/btcsuite/btcutil v1.0.2
|
||||||
@ -8,7 +8,7 @@ require (
|
|||||||
github.com/btcsuite/winsvc v1.0.0
|
github.com/btcsuite/winsvc v1.0.0
|
||||||
github.com/davecgh/go-spew v1.1.1
|
github.com/davecgh/go-spew v1.1.1
|
||||||
github.com/gofrs/flock v0.8.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/jessevdk/go-flags v1.4.0
|
||||||
github.com/jrick/logrotate v1.0.0
|
github.com/jrick/logrotate v1.0.0
|
||||||
github.com/kaspanet/go-muhash v0.0.4
|
github.com/kaspanet/go-muhash v0.0.4
|
||||||
@ -16,18 +16,17 @@ require (
|
|||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
|
github.com/syndtr/goleveldb v1.0.1-0.20190923125748-758128399b1d
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0
|
github.com/tyler-smith/go-bip39 v1.1.0
|
||||||
golang.org/x/crypto v0.1.0
|
golang.org/x/crypto v0.28.0
|
||||||
golang.org/x/exp v0.0.0-20220414153411-bcd21879b8fd
|
golang.org/x/term v0.25.0
|
||||||
golang.org/x/term v0.5.0
|
google.golang.org/grpc v1.69.2
|
||||||
google.golang.org/grpc v1.38.0
|
google.golang.org/protobuf v1.35.1
|
||||||
google.golang.org/protobuf v1.28.1
|
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/snappy v0.0.1 // indirect
|
github.com/golang/snappy v0.0.1 // indirect
|
||||||
golang.org/x/net v0.7.0 // indirect
|
golang.org/x/net v0.30.0 // indirect
|
||||||
golang.org/x/sys v0.5.0 // indirect
|
golang.org/x/sys v0.26.0 // indirect
|
||||||
golang.org/x/text v0.7.0 // indirect
|
golang.org/x/text v0.19.0 // indirect
|
||||||
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect
|
google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08 // indirect
|
||||||
gopkg.in/yaml.v2 v2.3.0 // indirect
|
gopkg.in/yaml.v2 v2.3.0 // indirect
|
||||||
)
|
)
|
||||||
|
52
go.sum
52
go.sum
@ -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/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/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/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 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
|
||||||
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
|
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=
|
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.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
|
||||||
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
|
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.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.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 h1:Qgr9rKW7uDUkrbSmQeiDsGa8SjGyCOGtuasMWwvp2P4=
|
||||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
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.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.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.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.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.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 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI=
|
||||||
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
|
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=
|
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 h1:5eUemwrMargf3BSLRRCalXT93Ns6pQJIjYQN2nyfOP8=
|
||||||
github.com/tyler-smith/go-bip39 v1.1.0/go.mod h1:gUYDtqQw1JS3ZJ8UWVcGTGqqr6YIN3CWg+kkNaLt55U=
|
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=
|
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-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-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-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-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-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210317152858-513c2a44f670/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
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.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw=
|
||||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
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-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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
|
||||||
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
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-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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
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.30.0 h1:AcW1SDZMkb8IpzCdQUaIq2sP4sZ4zw+55h6ynffypl4=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
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/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-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/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-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-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.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.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
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.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.5.0 h1:n2a8QNdAb0sZNpU9R1ALUXBbY+w51fCQDN+7EdxNBsY=
|
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
|
||||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
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.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.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.5/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.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
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-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-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
|
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-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-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-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=
|
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.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||||
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
|
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.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||||
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
|
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.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.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-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-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
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.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-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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
google.golang.org/protobuf v1.35.1 h1:m3LfL6/Ca+fqnjnlqQXNpFPABW1UD7mjh8KO2mKFytA=
|
||||||
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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=
|
gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4=
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -139,6 +139,28 @@ message KaspadMessage {
|
|||||||
GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085;
|
GetMempoolEntriesByAddressesResponseMessage getMempoolEntriesByAddressesResponse = 1085;
|
||||||
GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086;
|
GetCoinSupplyRequestMessage getCoinSupplyRequest = 1086;
|
||||||
GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087;
|
GetCoinSupplyResponseMessage getCoinSupplyResponse= 1087;
|
||||||
|
PingRequestMessage pingRequest = 1088;
|
||||||
|
GetMetricsRequestMessage getMetricsRequest = 1090;
|
||||||
|
GetServerInfoRequestMessage getServerInfoRequest = 1092;
|
||||||
|
GetSyncStatusRequestMessage getSyncStatusRequest = 1094;
|
||||||
|
GetDaaScoreTimestampEstimateRequestMessage getDaaScoreTimestampEstimateRequest = 1096;
|
||||||
|
SubmitTransactionReplacementRequestMessage submitTransactionReplacementRequest = 1100;
|
||||||
|
GetConnectionsRequestMessage getConnectionsRequest = 1102;
|
||||||
|
GetSystemInfoRequestMessage getSystemInfoRequest = 1104;
|
||||||
|
GetFeeEstimateRequestMessage getFeeEstimateRequest = 1106;
|
||||||
|
GetFeeEstimateExperimentalRequestMessage getFeeEstimateExperimentalRequest = 1108;
|
||||||
|
GetCurrentBlockColorRequestMessage getCurrentBlockColorRequest = 1110;
|
||||||
|
PingResponseMessage pingResponse= 1089;
|
||||||
|
GetMetricsResponseMessage getMetricsResponse= 1091;
|
||||||
|
GetServerInfoResponseMessage getServerInfoResponse = 1093;
|
||||||
|
GetSyncStatusResponseMessage getSyncStatusResponse = 1095;
|
||||||
|
GetDaaScoreTimestampEstimateResponseMessage getDaaScoreTimestampEstimateResponse = 1097;
|
||||||
|
SubmitTransactionReplacementResponseMessage submitTransactionReplacementResponse = 1101;
|
||||||
|
GetConnectionsResponseMessage getConnectionsResponse= 1103;
|
||||||
|
GetSystemInfoResponseMessage getSystemInfoResponse= 1105;
|
||||||
|
GetFeeEstimateResponseMessage getFeeEstimateResponse = 1107;
|
||||||
|
GetFeeEstimateExperimentalResponseMessage getFeeEstimateExperimentalResponse = 1109;
|
||||||
|
GetCurrentBlockColorResponseMessage getCurrentBlockColorResponse = 1111;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||||
// versions:
|
// versions:
|
||||||
// - protoc-gen-go-grpc v1.2.0
|
// - protoc-gen-go-grpc v1.2.0
|
||||||
// - protoc v3.17.2
|
// - protoc v3.12.3
|
||||||
// source: messages.proto
|
// source: messages.proto
|
||||||
|
|
||||||
package protowire
|
package protowire
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -3,26 +3,22 @@ package protowire;
|
|||||||
|
|
||||||
option go_package = "github.com/kaspanet/kaspad/protowire";
|
option go_package = "github.com/kaspanet/kaspad/protowire";
|
||||||
|
|
||||||
message RequestAddressesMessage{
|
message RequestAddressesMessage {
|
||||||
bool includeAllSubnetworks = 1;
|
bool includeAllSubnetworks = 1;
|
||||||
SubnetworkId subnetworkId = 2;
|
SubnetworkId subnetworkId = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddressesMessage{
|
message AddressesMessage { repeated NetAddress addressList = 1; }
|
||||||
repeated NetAddress addressList = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NetAddress{
|
message NetAddress {
|
||||||
int64 timestamp = 1;
|
int64 timestamp = 1;
|
||||||
bytes ip = 3;
|
bytes ip = 3;
|
||||||
uint32 port = 4;
|
uint32 port = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SubnetworkId{
|
message SubnetworkId { bytes bytes = 1; }
|
||||||
bytes bytes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TransactionMessage{
|
message TransactionMessage {
|
||||||
uint32 version = 1;
|
uint32 version = 1;
|
||||||
repeated TransactionInput inputs = 2;
|
repeated TransactionInput inputs = 2;
|
||||||
repeated TransactionOutput outputs = 3;
|
repeated TransactionOutput outputs = 3;
|
||||||
@ -30,39 +26,38 @@ message TransactionMessage{
|
|||||||
SubnetworkId subnetworkId = 5;
|
SubnetworkId subnetworkId = 5;
|
||||||
uint64 gas = 6;
|
uint64 gas = 6;
|
||||||
bytes payload = 8;
|
bytes payload = 8;
|
||||||
|
uint64 mass = 9;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TransactionInput{
|
message TransactionInput {
|
||||||
Outpoint previousOutpoint = 1;
|
Outpoint previousOutpoint = 1;
|
||||||
bytes signatureScript = 2;
|
bytes signatureScript = 2;
|
||||||
uint64 sequence = 3;
|
uint64 sequence = 3;
|
||||||
uint32 sigOpCount = 4;
|
uint32 sigOpCount = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message Outpoint{
|
message Outpoint {
|
||||||
TransactionId transactionId = 1;
|
TransactionId transactionId = 1;
|
||||||
uint32 index = 2;
|
uint32 index = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TransactionId{
|
message TransactionId { bytes bytes = 1; }
|
||||||
bytes bytes = 1;
|
|
||||||
}
|
|
||||||
message ScriptPublicKey {
|
message ScriptPublicKey {
|
||||||
bytes script = 1;
|
bytes script = 1;
|
||||||
uint32 version = 2;
|
uint32 version = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TransactionOutput{
|
message TransactionOutput {
|
||||||
uint64 value = 1;
|
uint64 value = 1;
|
||||||
ScriptPublicKey scriptPublicKey = 2;
|
ScriptPublicKey scriptPublicKey = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BlockMessage{
|
message BlockMessage {
|
||||||
BlockHeader header = 1;
|
BlockHeader header = 1;
|
||||||
repeated TransactionMessage transactions = 2;
|
repeated TransactionMessage transactions = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BlockHeader{
|
message BlockHeader {
|
||||||
uint32 version = 1;
|
uint32 version = 1;
|
||||||
repeated BlockLevelParents parents = 12;
|
repeated BlockLevelParents parents = 12;
|
||||||
Hash hashMerkleRoot = 3;
|
Hash hashMerkleRoot = 3;
|
||||||
@ -77,66 +72,43 @@ message BlockHeader{
|
|||||||
uint64 blueScore = 13;
|
uint64 blueScore = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BlockLevelParents {
|
message BlockLevelParents { repeated Hash parentHashes = 1; }
|
||||||
repeated Hash parentHashes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message Hash{
|
message Hash { bytes bytes = 1; }
|
||||||
bytes bytes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestBlockLocatorMessage{
|
message RequestBlockLocatorMessage {
|
||||||
Hash highHash = 1;
|
Hash highHash = 1;
|
||||||
uint32 limit = 2;
|
uint32 limit = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message BlockLocatorMessage{
|
message BlockLocatorMessage { repeated Hash hashes = 1; }
|
||||||
repeated Hash hashes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestHeadersMessage{
|
message RequestHeadersMessage {
|
||||||
Hash lowHash = 1;
|
Hash lowHash = 1;
|
||||||
Hash highHash = 2;
|
Hash highHash = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RequestNextHeadersMessage{
|
message RequestNextHeadersMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message DoneHeadersMessage{
|
message DoneHeadersMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message RequestRelayBlocksMessage{
|
message RequestRelayBlocksMessage { repeated Hash hashes = 1; }
|
||||||
repeated Hash hashes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestTransactionsMessage {
|
message RequestTransactionsMessage { repeated TransactionId ids = 1; }
|
||||||
repeated TransactionId ids = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message TransactionNotFoundMessage{
|
message TransactionNotFoundMessage { TransactionId id = 1; }
|
||||||
TransactionId id = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InvRelayBlockMessage{
|
message InvRelayBlockMessage { Hash hash = 1; }
|
||||||
Hash hash = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message InvTransactionsMessage{
|
message InvTransactionsMessage { repeated TransactionId ids = 1; }
|
||||||
repeated TransactionId ids = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PingMessage{
|
message PingMessage { uint64 nonce = 1; }
|
||||||
uint64 nonce = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PongMessage{
|
message PongMessage { uint64 nonce = 1; }
|
||||||
uint64 nonce = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message VerackMessage{
|
message VerackMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message VersionMessage{
|
message VersionMessage {
|
||||||
uint32 protocolVersion = 1;
|
uint32 protocolVersion = 1;
|
||||||
uint64 services = 2;
|
uint64 services = 2;
|
||||||
int64 timestamp = 3;
|
int64 timestamp = 3;
|
||||||
@ -148,19 +120,15 @@ message VersionMessage{
|
|||||||
string network = 10;
|
string network = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RejectMessage{
|
message RejectMessage { string reason = 1; }
|
||||||
string reason = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestPruningPointUTXOSetMessage{
|
message RequestPruningPointUTXOSetMessage { Hash pruningPointHash = 1; }
|
||||||
Hash pruningPointHash = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message PruningPointUtxoSetChunkMessage{
|
message PruningPointUtxoSetChunkMessage {
|
||||||
repeated OutpointAndUtxoEntryPair outpointAndUtxoEntryPairs = 1;
|
repeated OutpointAndUtxoEntryPair outpointAndUtxoEntryPairs = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message OutpointAndUtxoEntryPair{
|
message OutpointAndUtxoEntryPair {
|
||||||
Outpoint outpoint = 1;
|
Outpoint outpoint = 1;
|
||||||
UtxoEntry utxoEntry = 2;
|
UtxoEntry utxoEntry = 2;
|
||||||
}
|
}
|
||||||
@ -172,54 +140,40 @@ message UtxoEntry {
|
|||||||
bool isCoinbase = 4;
|
bool isCoinbase = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RequestNextPruningPointUtxoSetChunkMessage {
|
message RequestNextPruningPointUtxoSetChunkMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message DonePruningPointUtxoSetChunksMessage {
|
message DonePruningPointUtxoSetChunksMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message RequestIBDBlocksMessage{
|
message RequestIBDBlocksMessage { repeated Hash hashes = 1; }
|
||||||
repeated Hash hashes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnexpectedPruningPointMessage{
|
message UnexpectedPruningPointMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message IbdBlockLocatorMessage {
|
message IbdBlockLocatorMessage {
|
||||||
Hash targetHash = 1;
|
Hash targetHash = 1;
|
||||||
repeated Hash blockLocatorHashes = 2;
|
repeated Hash blockLocatorHashes = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RequestIBDChainBlockLocatorMessage{
|
message RequestIBDChainBlockLocatorMessage {
|
||||||
Hash lowHash = 1;
|
Hash lowHash = 1;
|
||||||
Hash highHash = 2;
|
Hash highHash = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IbdChainBlockLocatorMessage {
|
message IbdChainBlockLocatorMessage { repeated Hash blockLocatorHashes = 1; }
|
||||||
repeated Hash blockLocatorHashes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestAnticoneMessage{
|
message RequestAnticoneMessage {
|
||||||
Hash blockHash = 1;
|
Hash blockHash = 1;
|
||||||
Hash contextHash = 2;
|
Hash contextHash = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message IbdBlockLocatorHighestHashMessage {
|
message IbdBlockLocatorHighestHashMessage { Hash highestHash = 1; }
|
||||||
Hash highestHash = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message IbdBlockLocatorHighestHashNotFoundMessage {
|
message IbdBlockLocatorHighestHashNotFoundMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message BlockHeadersMessage {
|
message BlockHeadersMessage { repeated BlockHeader blockHeaders = 1; }
|
||||||
repeated BlockHeader blockHeaders = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestPruningPointAndItsAnticoneMessage {
|
message RequestPruningPointAndItsAnticoneMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message RequestNextPruningPointAndItsAnticoneBlocksMessage{
|
message RequestNextPruningPointAndItsAnticoneBlocksMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message BlockWithTrustedDataMessage {
|
message BlockWithTrustedDataMessage {
|
||||||
BlockMessage block = 1;
|
BlockMessage block = 1;
|
||||||
@ -257,26 +211,19 @@ message BluesAnticoneSizes {
|
|||||||
uint32 anticoneSize = 2;
|
uint32 anticoneSize = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message DoneBlocksWithTrustedDataMessage {
|
message DoneBlocksWithTrustedDataMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message PruningPointsMessage {
|
message PruningPointsMessage { repeated BlockHeader headers = 1; }
|
||||||
repeated BlockHeader headers = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RequestPruningPointProofMessage {
|
message RequestPruningPointProofMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message PruningPointProofMessage {
|
message PruningPointProofMessage {
|
||||||
repeated PruningPointProofHeaderArray headers = 1;
|
repeated PruningPointProofHeaderArray headers = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message PruningPointProofHeaderArray {
|
message PruningPointProofHeaderArray { repeated BlockHeader headers = 1; }
|
||||||
repeated BlockHeader headers = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ReadyMessage {
|
message ReadyMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message BlockWithTrustedDataV4Message {
|
message BlockWithTrustedDataV4Message {
|
||||||
BlockMessage block = 1;
|
BlockMessage block = 1;
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,11 +1,14 @@
|
|||||||
// RPC-related types. Request messages, response messages, and dependant types.
|
// 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
|
// Having received a RequestMessage, (wrapped in a KaspadMessage) the RPC server
|
||||||
// ResponseMessage (likewise wrapped in a KaspadMessage) respective to the original RequestMessage.
|
// 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";
|
syntax = "proto3";
|
||||||
package protowire;
|
package protowire;
|
||||||
@ -14,10 +17,9 @@ option go_package = "github.com/kaspanet/kaspad/protowire";
|
|||||||
|
|
||||||
// RPCError represents a generic non-internal error.
|
// RPCError represents a generic non-internal error.
|
||||||
//
|
//
|
||||||
// Receivers of any ResponseMessage are expected to check whether its error field is not null.
|
// Receivers of any ResponseMessage are expected to check whether its error
|
||||||
message RPCError{
|
// field is not null.
|
||||||
string message = 1;
|
message RPCError { string message = 1; }
|
||||||
}
|
|
||||||
|
|
||||||
message RpcBlock {
|
message RpcBlock {
|
||||||
RpcBlockHeader header = 1;
|
RpcBlockHeader header = 1;
|
||||||
@ -40,11 +42,9 @@ message RpcBlockHeader {
|
|||||||
uint64 blueScore = 13;
|
uint64 blueScore = 13;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcBlockLevelParents {
|
message RpcBlockLevelParents { repeated string parentHashes = 1; }
|
||||||
repeated string parentHashes = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message RpcBlockVerboseData{
|
message RpcBlockVerboseData {
|
||||||
string hash = 1;
|
string hash = 1;
|
||||||
double difficulty = 11;
|
double difficulty = 11;
|
||||||
string selectedParentHash = 13;
|
string selectedParentHash = 13;
|
||||||
@ -66,6 +66,7 @@ message RpcTransaction {
|
|||||||
uint64 gas = 6;
|
uint64 gas = 6;
|
||||||
string payload = 8;
|
string payload = 8;
|
||||||
RpcTransactionVerboseData verboseData = 9;
|
RpcTransactionVerboseData verboseData = 9;
|
||||||
|
uint64 mass = 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcTransactionInput {
|
message RpcTransactionInput {
|
||||||
@ -99,7 +100,7 @@ message RpcUtxoEntry {
|
|||||||
bool isCoinbase = 4;
|
bool isCoinbase = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcTransactionVerboseData{
|
message RpcTransactionVerboseData {
|
||||||
string transactionId = 1;
|
string transactionId = 1;
|
||||||
string hash = 2;
|
string hash = 2;
|
||||||
uint64 mass = 4;
|
uint64 mass = 4;
|
||||||
@ -107,35 +108,35 @@ message RpcTransactionVerboseData{
|
|||||||
uint64 blockTime = 14;
|
uint64 blockTime = 14;
|
||||||
}
|
}
|
||||||
|
|
||||||
message RpcTransactionInputVerboseData{
|
message RpcTransactionInputVerboseData {}
|
||||||
}
|
|
||||||
|
|
||||||
message RpcTransactionOutputVerboseData{
|
message RpcTransactionOutputVerboseData {
|
||||||
string scriptPublicKeyType = 5;
|
string scriptPublicKeyType = 5;
|
||||||
string scriptPublicKeyAddress = 6;
|
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
|
// Possible networks are: Mainnet, Testnet, Simnet, Devnet
|
||||||
message GetCurrentNetworkRequestMessage{
|
message GetCurrentNetworkRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetCurrentNetworkResponseMessage{
|
message GetCurrentNetworkResponseMessage {
|
||||||
string currentNetwork = 1;
|
string currentNetwork = 1;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitBlockRequestMessage requests to submit a block into the DAG.
|
// 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
|
// See: GetBlockTemplateRequestMessage
|
||||||
message SubmitBlockRequestMessage{
|
message SubmitBlockRequestMessage {
|
||||||
RpcBlock block = 2;
|
RpcBlock block = 2;
|
||||||
bool allowNonDAABlocks = 3;
|
bool allowNonDAABlocks = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SubmitBlockResponseMessage{
|
message SubmitBlockResponseMessage {
|
||||||
enum RejectReason {
|
enum RejectReason {
|
||||||
NONE = 0;
|
NONE = 0;
|
||||||
BLOCK_INVALID = 1;
|
BLOCK_INVALID = 1;
|
||||||
@ -146,115 +147,108 @@ message SubmitBlockResponseMessage{
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockTemplateRequestMessage requests a current block template.
|
// 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
|
// See: SubmitBlockRequestMessage
|
||||||
message GetBlockTemplateRequestMessage{
|
message GetBlockTemplateRequestMessage {
|
||||||
// Which kaspa address should the coinbase block reward transaction pay into
|
// Which kaspa address should the coinbase block reward transaction pay into
|
||||||
string payAddress = 1;
|
string payAddress = 1;
|
||||||
string extraData = 2;
|
string extraData = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetBlockTemplateResponseMessage{
|
message GetBlockTemplateResponseMessage {
|
||||||
RpcBlock block = 3;
|
RpcBlock block = 3;
|
||||||
|
|
||||||
// Whether kaspad thinks that it's synced.
|
// Whether kaspad thinks that it's synced.
|
||||||
// Callers are discouraged (but not forbidden) from solving blocks when kaspad is not synced.
|
// Callers are discouraged (but not forbidden) from solving blocks when kaspad
|
||||||
// That is because when kaspad isn't in sync with the rest of the network there's a high
|
// is not synced. That is because when kaspad isn't in sync with the rest of
|
||||||
// chance the block will never be accepted, thus the solving effort would have been wasted.
|
// the network there's a high chance the block will never be accepted, thus
|
||||||
|
// the solving effort would have been wasted.
|
||||||
bool isSynced = 2;
|
bool isSynced = 2;
|
||||||
|
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyBlockAddedRequestMessage registers this connection for blockAdded notifications.
|
// NotifyBlockAddedRequestMessage registers this connection for blockAdded
|
||||||
|
// notifications.
|
||||||
//
|
//
|
||||||
// See: BlockAddedNotificationMessage
|
// See: BlockAddedNotificationMessage
|
||||||
message NotifyBlockAddedRequestMessage{
|
message NotifyBlockAddedRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message NotifyBlockAddedResponseMessage{
|
message NotifyBlockAddedResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// BlockAddedNotificationMessage is sent whenever a blocks has been added (NOT accepted)
|
// BlockAddedNotificationMessage is sent whenever a blocks has been added (NOT
|
||||||
// into the DAG.
|
// accepted) into the DAG.
|
||||||
//
|
//
|
||||||
// See: NotifyBlockAddedRequestMessage
|
// See: NotifyBlockAddedRequestMessage
|
||||||
message BlockAddedNotificationMessage{
|
message BlockAddedNotificationMessage { RpcBlock block = 3; }
|
||||||
RpcBlock block = 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetPeerAddressesRequestMessage requests the list of known kaspad addresses in the
|
// GetPeerAddressesRequestMessage requests the list of known kaspad addresses in
|
||||||
// current network. (mainnet, testnet, etc.)
|
// the current network. (mainnet, testnet, etc.)
|
||||||
message GetPeerAddressesRequestMessage{
|
message GetPeerAddressesRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetPeerAddressesResponseMessage{
|
message GetPeerAddressesResponseMessage {
|
||||||
repeated GetPeerAddressesKnownAddressMessage addresses = 1;
|
repeated GetPeerAddressesKnownAddressMessage addresses = 1;
|
||||||
repeated GetPeerAddressesKnownAddressMessage bannedAddresses = 2;
|
repeated GetPeerAddressesKnownAddressMessage bannedAddresses = 2;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetPeerAddressesKnownAddressMessage {
|
message GetPeerAddressesKnownAddressMessage { string Addr = 1; }
|
||||||
string Addr = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetSelectedTipHashRequestMessage requests the hash of the current virtual's
|
// GetSelectedTipHashRequestMessage requests the hash of the current virtual's
|
||||||
// selected parent.
|
// selected parent.
|
||||||
message GetSelectedTipHashRequestMessage{
|
message GetSelectedTipHashRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetSelectedTipHashResponseMessage{
|
message GetSelectedTipHashResponseMessage {
|
||||||
string selectedTipHash = 1;
|
string selectedTipHash = 1;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMempoolEntryRequestMessage requests information about a specific transaction
|
// GetMempoolEntryRequestMessage requests information about a specific
|
||||||
// in the mempool.
|
// transaction in the mempool.
|
||||||
message GetMempoolEntryRequestMessage{
|
message GetMempoolEntryRequestMessage {
|
||||||
// The transaction's TransactionID.
|
// The transaction's TransactionID.
|
||||||
string txId = 1;
|
string txId = 1;
|
||||||
bool includeOrphanPool = 2;
|
bool includeOrphanPool = 2;
|
||||||
bool filterTransactionPool = 3;
|
bool filterTransactionPool = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetMempoolEntryResponseMessage{
|
message GetMempoolEntryResponseMessage {
|
||||||
MempoolEntry entry = 1;
|
MempoolEntry entry = 1;
|
||||||
|
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetMempoolEntriesRequestMessage requests information about all the transactions
|
// GetMempoolEntriesRequestMessage requests information about all the
|
||||||
// currently in the mempool.
|
// transactions currently in the mempool.
|
||||||
message GetMempoolEntriesRequestMessage{
|
message GetMempoolEntriesRequestMessage {
|
||||||
bool includeOrphanPool = 1;
|
bool includeOrphanPool = 1;
|
||||||
bool filterTransactionPool = 2;
|
bool filterTransactionPool = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetMempoolEntriesResponseMessage{
|
message GetMempoolEntriesResponseMessage {
|
||||||
repeated MempoolEntry entries = 1;
|
repeated MempoolEntry entries = 1;
|
||||||
|
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
message MempoolEntry{
|
message MempoolEntry {
|
||||||
uint64 fee = 1;
|
uint64 fee = 1;
|
||||||
RpcTransaction transaction = 3;
|
RpcTransaction transaction = 3;
|
||||||
bool isOrphan = 4;
|
bool isOrphan = 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetConnectedPeerInfoRequestMessage requests information about all the p2p peers
|
// GetConnectedPeerInfoRequestMessage requests information about all the p2p
|
||||||
// currently connected to this kaspad.
|
// peers currently connected to this kaspad.
|
||||||
message GetConnectedPeerInfoRequestMessage{
|
message GetConnectedPeerInfoRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetConnectedPeerInfoResponseMessage{
|
message GetConnectedPeerInfoResponseMessage {
|
||||||
repeated GetConnectedPeerInfoMessage infos = 1;
|
repeated GetConnectedPeerInfoMessage infos = 1;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetConnectedPeerInfoMessage{
|
message GetConnectedPeerInfoMessage {
|
||||||
string id = 1;
|
string id = 1;
|
||||||
string address = 2;
|
string address = 2;
|
||||||
|
|
||||||
@ -278,58 +272,58 @@ message GetConnectedPeerInfoMessage{
|
|||||||
|
|
||||||
// AddPeerRequestMessage adds a peer to kaspad's outgoing connection list.
|
// AddPeerRequestMessage adds a peer to kaspad's outgoing connection list.
|
||||||
// This will, in most cases, result in kaspad connecting to said peer.
|
// This will, in most cases, result in kaspad connecting to said peer.
|
||||||
message AddPeerRequestMessage{
|
message AddPeerRequestMessage {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
|
|
||||||
// Whether to keep attempting to connect to this peer after disconnection
|
// Whether to keep attempting to connect to this peer after disconnection
|
||||||
bool isPermanent = 2;
|
bool isPermanent = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AddPeerResponseMessage{
|
message AddPeerResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmitTransactionRequestMessage submits a transaction to the mempool
|
// SubmitTransactionRequestMessage submits a transaction to the mempool
|
||||||
message SubmitTransactionRequestMessage{
|
message SubmitTransactionRequestMessage {
|
||||||
RpcTransaction transaction = 1;
|
RpcTransaction transaction = 1;
|
||||||
bool allowOrphan = 2;
|
bool allowOrphan = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message SubmitTransactionResponseMessage{
|
message SubmitTransactionResponseMessage {
|
||||||
// The transaction ID of the submitted transaction
|
// The transaction ID of the submitted transaction
|
||||||
string transactionId = 1;
|
string transactionId = 1;
|
||||||
|
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyVirtualSelectedParentChainChangedRequestMessage registers this connection for virtualSelectedParentChainChanged notifications.
|
// NotifyVirtualSelectedParentChainChangedRequestMessage registers this
|
||||||
|
// connection for virtualSelectedParentChainChanged notifications.
|
||||||
//
|
//
|
||||||
// See: VirtualSelectedParentChainChangedNotificationMessage
|
// See: VirtualSelectedParentChainChangedNotificationMessage
|
||||||
message NotifyVirtualSelectedParentChainChangedRequestMessage{
|
message NotifyVirtualSelectedParentChainChangedRequestMessage {
|
||||||
bool includeAcceptedTransactionIds = 1;
|
bool includeAcceptedTransactionIds = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message NotifyVirtualSelectedParentChainChangedResponseMessage{
|
message NotifyVirtualSelectedParentChainChangedResponseMessage {
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtualSelectedParentChainChangedNotificationMessage is sent whenever the DAG's selected parent
|
// VirtualSelectedParentChainChangedNotificationMessage is sent whenever the
|
||||||
// chain had changed.
|
// DAG's selected parent chain had changed.
|
||||||
//
|
//
|
||||||
// See: NotifyVirtualSelectedParentChainChangedRequestMessage
|
// See: NotifyVirtualSelectedParentChainChangedRequestMessage
|
||||||
message VirtualSelectedParentChainChangedNotificationMessage{
|
message VirtualSelectedParentChainChangedNotificationMessage {
|
||||||
// The chain blocks that were removed, in high-to-low order
|
// The chain blocks that were removed, in high-to-low order
|
||||||
repeated string removedChainBlockHashes = 1;
|
repeated string removedChainBlockHashes = 1;
|
||||||
|
|
||||||
// The chain blocks that were added, in low-to-high order
|
// The chain blocks that were added, in low-to-high order
|
||||||
repeated string addedChainBlockHashes = 3;
|
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;
|
repeated AcceptedTransactionIds acceptedTransactionIds = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockRequestMessage requests information about a specific block
|
// GetBlockRequestMessage requests information about a specific block
|
||||||
message GetBlockRequestMessage{
|
message GetBlockRequestMessage {
|
||||||
// The hash of the requested block
|
// The hash of the requested block
|
||||||
string hash = 1;
|
string hash = 1;
|
||||||
|
|
||||||
@ -337,7 +331,7 @@ message GetBlockRequestMessage{
|
|||||||
bool includeTransactions = 3;
|
bool includeTransactions = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetBlockResponseMessage{
|
message GetBlockResponseMessage {
|
||||||
RpcBlock block = 3;
|
RpcBlock block = 3;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
@ -345,28 +339,26 @@ message GetBlockResponseMessage{
|
|||||||
// GetSubnetworkRequestMessage requests information about a specific subnetwork
|
// GetSubnetworkRequestMessage requests information about a specific subnetwork
|
||||||
//
|
//
|
||||||
// Currently unimplemented
|
// Currently unimplemented
|
||||||
message GetSubnetworkRequestMessage{
|
message GetSubnetworkRequestMessage { string subnetworkId = 1; }
|
||||||
string subnetworkId = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetSubnetworkResponseMessage{
|
message GetSubnetworkResponseMessage {
|
||||||
uint64 gasLimit = 1;
|
uint64 gasLimit = 1;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVirtualSelectedParentChainFromBlockRequestMessage requests the virtual selected
|
// GetVirtualSelectedParentChainFromBlockRequestMessage requests the virtual
|
||||||
// parent chain from some startHash to this kaspad's current virtual
|
// selected parent chain from some startHash to this kaspad's current virtual
|
||||||
message GetVirtualSelectedParentChainFromBlockRequestMessage{
|
message GetVirtualSelectedParentChainFromBlockRequestMessage {
|
||||||
string startHash = 1;
|
string startHash = 1;
|
||||||
bool includeAcceptedTransactionIds = 2;
|
bool includeAcceptedTransactionIds = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message AcceptedTransactionIds{
|
message AcceptedTransactionIds {
|
||||||
string acceptingBlockHash = 1;
|
string acceptingBlockHash = 1;
|
||||||
repeated string acceptedTransactionIds = 2;
|
repeated string acceptedTransactionIds = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetVirtualSelectedParentChainFromBlockResponseMessage{
|
message GetVirtualSelectedParentChainFromBlockResponseMessage {
|
||||||
// The chain blocks that were removed, in high-to-low order
|
// The chain blocks that were removed, in high-to-low order
|
||||||
repeated string removedChainBlockHashes = 1;
|
repeated string removedChainBlockHashes = 1;
|
||||||
|
|
||||||
@ -374,43 +366,42 @@ message GetVirtualSelectedParentChainFromBlockResponseMessage{
|
|||||||
repeated string addedChainBlockHashes = 3;
|
repeated string addedChainBlockHashes = 3;
|
||||||
|
|
||||||
// The transactions accepted by each block in addedChainBlockHashes.
|
// 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;
|
repeated AcceptedTransactionIds acceptedTransactionIds = 2;
|
||||||
|
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlocksRequestMessage requests blocks between a certain block lowHash up to this
|
// GetBlocksRequestMessage requests blocks between a certain block lowHash up to
|
||||||
// kaspad's current virtual.
|
// this kaspad's current virtual.
|
||||||
message GetBlocksRequestMessage{
|
message GetBlocksRequestMessage {
|
||||||
string lowHash = 1;
|
string lowHash = 1;
|
||||||
bool includeBlocks = 2;
|
bool includeBlocks = 2;
|
||||||
bool includeTransactions = 3;
|
bool includeTransactions = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetBlocksResponseMessage{
|
message GetBlocksResponseMessage {
|
||||||
repeated string blockHashes = 4;
|
repeated string blockHashes = 4;
|
||||||
repeated RpcBlock blocks = 3;
|
repeated RpcBlock blocks = 3;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockCountRequestMessage requests the current number of blocks in this kaspad.
|
// GetBlockCountRequestMessage requests the current number of blocks in this
|
||||||
// Note that this number may decrease as pruning occurs.
|
// kaspad. Note that this number may decrease as pruning occurs.
|
||||||
message GetBlockCountRequestMessage{
|
message GetBlockCountRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetBlockCountResponseMessage{
|
message GetBlockCountResponseMessage {
|
||||||
uint64 blockCount = 1;
|
uint64 blockCount = 1;
|
||||||
uint64 headerCount = 2;
|
uint64 headerCount = 2;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockDagInfoRequestMessage requests general information about the current state
|
// GetBlockDagInfoRequestMessage requests general information about the current
|
||||||
// of this kaspad's DAG.
|
// state of this kaspad's DAG.
|
||||||
message GetBlockDagInfoRequestMessage{
|
message GetBlockDagInfoRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetBlockDagInfoResponseMessage{
|
message GetBlockDagInfoResponseMessage {
|
||||||
string networkName = 1;
|
string networkName = 1;
|
||||||
uint64 blockCount = 2;
|
uint64 blockCount = 2;
|
||||||
uint64 headerCount = 3;
|
uint64 headerCount = 3;
|
||||||
@ -423,52 +414,40 @@ message GetBlockDagInfoResponseMessage{
|
|||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
message ResolveFinalityConflictRequestMessage{
|
message ResolveFinalityConflictRequestMessage { string finalityBlockHash = 1; }
|
||||||
string finalityBlockHash = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message ResolveFinalityConflictResponseMessage{
|
message ResolveFinalityConflictResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
message NotifyFinalityConflictsRequestMessage{
|
message NotifyFinalityConflictsRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message NotifyFinalityConflictsResponseMessage{
|
message NotifyFinalityConflictsResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FinalityConflictNotificationMessage{
|
message FinalityConflictNotificationMessage { string violatingBlockHash = 1; }
|
||||||
string violatingBlockHash = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message FinalityConflictResolvedNotificationMessage{
|
message FinalityConflictResolvedNotificationMessage {
|
||||||
string finalityBlockHash = 1;
|
string finalityBlockHash = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ShutDownRequestMessage shuts down this kaspad.
|
// ShutDownRequestMessage shuts down this kaspad.
|
||||||
message ShutDownRequestMessage{
|
message ShutDownRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message ShutDownResponseMessage{
|
message ShutDownResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetHeadersRequestMessage requests headers between the given startHash and the
|
// GetHeadersRequestMessage requests headers between the given startHash and the
|
||||||
// current virtual, up to the given limit.
|
// current virtual, up to the given limit.
|
||||||
message GetHeadersRequestMessage{
|
message GetHeadersRequestMessage {
|
||||||
string startHash = 1;
|
string startHash = 1;
|
||||||
uint64 limit = 2;
|
uint64 limit = 2;
|
||||||
bool isAscending = 3;
|
bool isAscending = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetHeadersResponseMessage{
|
message GetHeadersResponseMessage {
|
||||||
repeated string headers = 1;
|
repeated string headers = 1;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyUtxosChangedRequestMessage registers this connection for utxoChanged notifications
|
// NotifyUtxosChangedRequestMessage registers this connection for utxoChanged
|
||||||
// for the given addresses.
|
// notifications for the given addresses.
|
||||||
//
|
//
|
||||||
// This call is only available when this kaspad was started with `--utxoindex`
|
// 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
|
repeated string addresses = 1; // Leave empty to get all updates
|
||||||
}
|
}
|
||||||
|
|
||||||
message NotifyUtxosChangedResponseMessage {
|
message NotifyUtxosChangedResponseMessage { RPCError error = 1000; }
|
||||||
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
|
// See: NotifyUtxosChangedRequestMessage
|
||||||
message UtxosChangedNotificationMessage {
|
message UtxosChangedNotificationMessage {
|
||||||
@ -495,8 +473,8 @@ message UtxosByAddressesEntry {
|
|||||||
RpcUtxoEntry utxoEntry = 3;
|
RpcUtxoEntry utxoEntry = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
// StopNotifyingUtxosChangedRequestMessage unregisters this connection for utxoChanged notifications
|
// StopNotifyingUtxosChangedRequestMessage unregisters this connection for
|
||||||
// for the given addresses.
|
// utxoChanged notifications for the given addresses.
|
||||||
//
|
//
|
||||||
// This call is only available when this kaspad was started with `--utxoindex`
|
// This call is only available when this kaspad was started with `--utxoindex`
|
||||||
//
|
//
|
||||||
@ -505,16 +483,13 @@ message StopNotifyingUtxosChangedRequestMessage {
|
|||||||
repeated string addresses = 1;
|
repeated string addresses = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
message StopNotifyingUtxosChangedResponseMessage {
|
message StopNotifyingUtxosChangedResponseMessage { RPCError error = 1000; }
|
||||||
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`
|
// This call is only available when this kaspad was started with `--utxoindex`
|
||||||
message GetUtxosByAddressesRequestMessage {
|
message GetUtxosByAddressesRequestMessage { repeated string addresses = 1; }
|
||||||
repeated string addresses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetUtxosByAddressesResponseMessage {
|
message GetUtxosByAddressesResponseMessage {
|
||||||
repeated UtxosByAddressesEntry entries = 1;
|
repeated UtxosByAddressesEntry entries = 1;
|
||||||
@ -522,12 +497,11 @@ message GetUtxosByAddressesResponseMessage {
|
|||||||
RPCError error = 1000;
|
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`
|
// This call is only available when this kaspad was started with `--utxoindex`
|
||||||
message GetBalanceByAddressRequestMessage {
|
message GetBalanceByAddressRequestMessage { string address = 1; }
|
||||||
string address = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message GetBalanceByAddressResponseMessage {
|
message GetBalanceByAddressResponseMessage {
|
||||||
uint64 balance = 1;
|
uint64 balance = 1;
|
||||||
@ -535,11 +509,9 @@ message GetBalanceByAddressResponseMessage {
|
|||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetBalancesByAddressesRequestMessage {
|
message GetBalancesByAddressesRequestMessage { repeated string addresses = 1; }
|
||||||
repeated string addresses = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BalancesByAddressEntry{
|
message BalancesByAddressEntry {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
uint64 balance = 2;
|
uint64 balance = 2;
|
||||||
|
|
||||||
@ -552,10 +524,9 @@ message GetBalancesByAddressesResponseMessage {
|
|||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetVirtualSelectedParentBlueScoreRequestMessage requests the blue score of the current selected parent
|
// GetVirtualSelectedParentBlueScoreRequestMessage requests the blue score of
|
||||||
// of the virtual block.
|
// the current selected parent of the virtual block.
|
||||||
message GetVirtualSelectedParentBlueScoreRequestMessage {
|
message GetVirtualSelectedParentBlueScoreRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetVirtualSelectedParentBlueScoreResponseMessage {
|
message GetVirtualSelectedParentBlueScoreResponseMessage {
|
||||||
uint64 blueScore = 1;
|
uint64 blueScore = 1;
|
||||||
@ -563,19 +534,18 @@ message GetVirtualSelectedParentBlueScoreResponseMessage {
|
|||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// NotifyVirtualSelectedParentBlueScoreChangedRequestMessage registers this connection for
|
// NotifyVirtualSelectedParentBlueScoreChangedRequestMessage registers this
|
||||||
// virtualSelectedParentBlueScoreChanged notifications.
|
// connection for virtualSelectedParentBlueScoreChanged notifications.
|
||||||
//
|
//
|
||||||
// See: VirtualSelectedParentBlueScoreChangedNotificationMessage
|
// See: VirtualSelectedParentBlueScoreChangedNotificationMessage
|
||||||
message NotifyVirtualSelectedParentBlueScoreChangedRequestMessage {
|
message NotifyVirtualSelectedParentBlueScoreChangedRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message NotifyVirtualSelectedParentBlueScoreChangedResponseMessage {
|
message NotifyVirtualSelectedParentBlueScoreChangedResponseMessage {
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// VirtualSelectedParentBlueScoreChangedNotificationMessage is sent whenever the blue score
|
// VirtualSelectedParentBlueScoreChangedNotificationMessage is sent whenever the
|
||||||
// of the virtual's selected parent changes.
|
// blue score of the virtual's selected parent changes.
|
||||||
//
|
//
|
||||||
// See NotifyVirtualSelectedParentBlueScoreChangedRequestMessage
|
// See NotifyVirtualSelectedParentBlueScoreChangedRequestMessage
|
||||||
message VirtualSelectedParentBlueScoreChangedNotificationMessage {
|
message VirtualSelectedParentBlueScoreChangedNotificationMessage {
|
||||||
@ -586,12 +556,9 @@ message VirtualSelectedParentBlueScoreChangedNotificationMessage {
|
|||||||
// virtualDaaScoreChanged notifications.
|
// virtualDaaScoreChanged notifications.
|
||||||
//
|
//
|
||||||
// See: VirtualDaaScoreChangedNotificationMessage
|
// See: VirtualDaaScoreChangedNotificationMessage
|
||||||
message NotifyVirtualDaaScoreChangedRequestMessage {
|
message NotifyVirtualDaaScoreChangedRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message NotifyVirtualDaaScoreChangedResponseMessage {
|
message NotifyVirtualDaaScoreChangedResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// VirtualDaaScoreChangedNotificationMessage is sent whenever the DAA score
|
// VirtualDaaScoreChangedNotificationMessage is sent whenever the DAA score
|
||||||
// of the virtual changes.
|
// of the virtual changes.
|
||||||
@ -607,57 +574,44 @@ message VirtualDaaScoreChangedNotificationMessage {
|
|||||||
// This call is only available when this kaspad was started with `--utxoindex`
|
// This call is only available when this kaspad was started with `--utxoindex`
|
||||||
//
|
//
|
||||||
// See: NotifyPruningPointUTXOSetOverrideResponseMessage
|
// See: NotifyPruningPointUTXOSetOverrideResponseMessage
|
||||||
message NotifyPruningPointUTXOSetOverrideRequestMessage {
|
message NotifyPruningPointUTXOSetOverrideRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
message NotifyPruningPointUTXOSetOverrideResponseMessage {
|
message NotifyPruningPointUTXOSetOverrideResponseMessage {
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// PruningPointUTXOSetOverrideNotificationMessage is sent whenever the UTXO index
|
// PruningPointUTXOSetOverrideNotificationMessage is sent whenever the UTXO
|
||||||
// resets due to pruning point change via IBD.
|
// index resets due to pruning point change via IBD.
|
||||||
//
|
//
|
||||||
// See NotifyPruningPointUTXOSetOverrideRequestMessage
|
// See NotifyPruningPointUTXOSetOverrideRequestMessage
|
||||||
message PruningPointUTXOSetOverrideNotificationMessage {
|
message PruningPointUTXOSetOverrideNotificationMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
// StopNotifyingPruningPointUTXOSetOverrideRequestMessage unregisters this connection for
|
// StopNotifyingPruningPointUTXOSetOverrideRequestMessage unregisters this
|
||||||
// pruning point UTXO set override notifications.
|
// connection for pruning point UTXO set override notifications.
|
||||||
//
|
//
|
||||||
// This call is only available when this kaspad was started with `--utxoindex`
|
// This call is only available when this kaspad was started with `--utxoindex`
|
||||||
//
|
//
|
||||||
// See: PruningPointUTXOSetOverrideNotificationMessage
|
// See: PruningPointUTXOSetOverrideNotificationMessage
|
||||||
message StopNotifyingPruningPointUTXOSetOverrideRequestMessage {
|
message StopNotifyingPruningPointUTXOSetOverrideRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message StopNotifyingPruningPointUTXOSetOverrideResponseMessage {
|
message StopNotifyingPruningPointUTXOSetOverrideResponseMessage {
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BanRequestMessage bans the given ip.
|
// BanRequestMessage bans the given ip.
|
||||||
message BanRequestMessage{
|
message BanRequestMessage { string ip = 1; }
|
||||||
string ip = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message BanResponseMessage{
|
message BanResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnbanRequestMessage unbans the given ip.
|
// UnbanRequestMessage unbans the given ip.
|
||||||
message UnbanRequestMessage{
|
message UnbanRequestMessage { string ip = 1; }
|
||||||
string ip = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
message UnbanResponseMessage{
|
message UnbanResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetInfoRequestMessage returns info about the node.
|
// GetInfoRequestMessage returns info about the node.
|
||||||
message GetInfoRequestMessage{
|
message GetInfoRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message GetInfoResponseMessage{
|
message GetInfoResponseMessage {
|
||||||
string p2pId = 1;
|
string p2pId = 1;
|
||||||
uint64 mempoolSize = 2;
|
uint64 mempoolSize = 2;
|
||||||
string serverVersion = 3;
|
string serverVersion = 3;
|
||||||
@ -666,12 +620,12 @@ message GetInfoResponseMessage{
|
|||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EstimateNetworkHashesPerSecondRequestMessage{
|
message EstimateNetworkHashesPerSecondRequestMessage {
|
||||||
uint32 windowSize = 1;
|
uint32 windowSize = 1;
|
||||||
string startHash = 2;
|
string startHash = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message EstimateNetworkHashesPerSecondResponseMessage{
|
message EstimateNetworkHashesPerSecondResponseMessage {
|
||||||
uint64 networkHashesPerSecond = 1;
|
uint64 networkHashesPerSecond = 1;
|
||||||
RPCError error = 1000;
|
RPCError error = 1000;
|
||||||
}
|
}
|
||||||
@ -680,44 +634,258 @@ message EstimateNetworkHashesPerSecondResponseMessage{
|
|||||||
// NewBlockTemplate notifications.
|
// NewBlockTemplate notifications.
|
||||||
//
|
//
|
||||||
// See: NewBlockTemplateNotificationMessage
|
// See: NewBlockTemplateNotificationMessage
|
||||||
message NotifyNewBlockTemplateRequestMessage {
|
message NotifyNewBlockTemplateRequestMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message NotifyNewBlockTemplateResponseMessage {
|
message NotifyNewBlockTemplateResponseMessage { RPCError error = 1000; }
|
||||||
RPCError error = 1000;
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewBlockTemplateNotificationMessage is sent whenever a new updated block template is
|
// NewBlockTemplateNotificationMessage is sent whenever a new updated block
|
||||||
// available for miners.
|
// template is available for miners.
|
||||||
//
|
//
|
||||||
// See NotifyNewBlockTemplateRequestMessage
|
// See NotifyNewBlockTemplateRequestMessage
|
||||||
message NewBlockTemplateNotificationMessage {
|
message NewBlockTemplateNotificationMessage {}
|
||||||
}
|
|
||||||
|
|
||||||
message MempoolEntryByAddress{
|
message MempoolEntryByAddress {
|
||||||
string address = 1;
|
string address = 1;
|
||||||
repeated MempoolEntry sending = 2;
|
repeated MempoolEntry sending = 2;
|
||||||
repeated MempoolEntry receiving = 3;
|
repeated MempoolEntry receiving = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetMempoolEntriesByAddressesRequestMessage{
|
message GetMempoolEntriesByAddressesRequestMessage {
|
||||||
repeated string addresses = 1;
|
repeated string addresses = 1;
|
||||||
bool includeOrphanPool = 2;
|
bool includeOrphanPool = 2;
|
||||||
bool filterTransactionPool = 3;
|
bool filterTransactionPool = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
message GetMempoolEntriesByAddressesResponseMessage{
|
message GetMempoolEntriesByAddressesResponseMessage {
|
||||||
repeated MempoolEntryByAddress entries = 1;
|
repeated MempoolEntryByAddress entries = 1;
|
||||||
|
|
||||||
RPCError error = 1000;
|
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{
|
message PingRequestMessage {}
|
||||||
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 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;
|
||||||
|
}
|
@ -17,7 +17,7 @@ func (x *KaspadMessage_GetCurrentNetworkResponse) toAppMessage() (appmessage.Mes
|
|||||||
if x == nil {
|
if x == nil {
|
||||||
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetCurrentNetworkResponse is nil")
|
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetCurrentNetworkResponse is nil")
|
||||||
}
|
}
|
||||||
return x.toAppMessage()
|
return x.GetCurrentNetworkResponse.toAppMessage()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *KaspadMessage_GetCurrentNetworkResponse) fromAppMessage(message *appmessage.GetCurrentNetworkResponseMessage) error {
|
func (x *KaspadMessage_GetCurrentNetworkResponse) fromAppMessage(message *appmessage.GetCurrentNetworkResponseMessage) error {
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
package protowire
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/kaspanet/kaspad/app/appmessage"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (x *KaspadMessage_GetFeeEstimateRequest) toAppMessage() (appmessage.Message, error) {
|
||||||
|
return &appmessage.GetFeeEstimateRequestMessage{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KaspadMessage_GetFeeEstimateRequest) fromAppMessage(_ *appmessage.GetFeeEstimateRequestMessage) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *KaspadMessage_GetFeeEstimateResponse) toAppMessage() (appmessage.Message, error) {
|
||||||
|
if x == nil {
|
||||||
|
return nil, errors.Wrapf(errorNil, "KaspadMessage_GetFeeEstimateResponse is nil")
|
||||||
|
}
|
||||||
|
return x.GetFeeEstimateResponse.toAppMessage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *GetFeeEstimateResponseMessage) toAppMessage() (appmessage.Message, error) {
|
||||||
|
if x == nil {
|
||||||
|
return nil, errors.Wrapf(errorNil, "GetFeeEstimateResponseMessage is nil")
|
||||||
|
}
|
||||||
|
rpcErr, err := x.Error.toAppMessage()
|
||||||
|
// Error is an optional field
|
||||||
|
if err != nil && !errors.Is(err, errorNil) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
estimate, err := x.Estimate.toAppMessage()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &appmessage.GetFeeEstimateResponseMessage{
|
||||||
|
Error: rpcErr,
|
||||||
|
Estimate: estimate,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (x *RpcFeeEstimate) toAppMessage() (appmessage.RPCFeeEstimate, error) {
|
||||||
|
if x == nil {
|
||||||
|
return appmessage.RPCFeeEstimate{}, errors.Wrapf(errorNil, "RpcFeeEstimate is nil")
|
||||||
|
}
|
||||||
|
return appmessage.RPCFeeEstimate{
|
||||||
|
PriorityBucket: appmessage.RPCFeeRateBucket{
|
||||||
|
Feerate: x.PriorityBucket.Feerate,
|
||||||
|
EstimatedSeconds: x.PriorityBucket.EstimatedSeconds,
|
||||||
|
},
|
||||||
|
NormalBuckets: feeRateBucketsToAppMessage(x.NormalBuckets),
|
||||||
|
LowBuckets: feeRateBucketsToAppMessage(x.LowBuckets),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func feeRateBucketsToAppMessage(protoBuckets []*RpcFeerateBucket) []appmessage.RPCFeeRateBucket {
|
||||||
|
appMsgBuckets := make([]appmessage.RPCFeeRateBucket, len(protoBuckets))
|
||||||
|
for i, bucket := range protoBuckets {
|
||||||
|
appMsgBuckets[i] = appmessage.RPCFeeRateBucket{
|
||||||
|
Feerate: bucket.Feerate,
|
||||||
|
EstimatedSeconds: bucket.EstimatedSeconds,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return appMsgBuckets
|
||||||
|
}
|
@ -110,6 +110,7 @@ func (x *RpcTransaction) toAppMessage() (*appmessage.RPCTransaction, error) {
|
|||||||
SubnetworkID: x.SubnetworkId,
|
SubnetworkID: x.SubnetworkId,
|
||||||
Gas: x.Gas,
|
Gas: x.Gas,
|
||||||
Payload: x.Payload,
|
Payload: x.Payload,
|
||||||
|
Mass: x.Mass,
|
||||||
VerboseData: verboseData,
|
VerboseData: verboseData,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
@ -138,6 +139,7 @@ func (x *RpcTransaction) fromAppMessage(transaction *appmessage.RPCTransaction)
|
|||||||
SubnetworkId: transaction.SubnetworkID,
|
SubnetworkId: transaction.SubnetworkID,
|
||||||
Gas: transaction.Gas,
|
Gas: transaction.Gas,
|
||||||
Payload: transaction.Payload,
|
Payload: transaction.Payload,
|
||||||
|
Mass: transaction.Mass,
|
||||||
VerboseData: verboseData,
|
VerboseData: verboseData,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
@ -968,6 +968,27 @@ func toRPCPayload(message appmessage.Message) (isKaspadMessage_Payload, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return payload, nil
|
return payload, nil
|
||||||
|
case *appmessage.GetFeeEstimateRequestMessage:
|
||||||
|
payload := new(KaspadMessage_GetFeeEstimateRequest)
|
||||||
|
err := payload.fromAppMessage(message)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return payload, nil
|
||||||
|
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:
|
default:
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
20
infrastructure/network/rpcclient/rpc_get_fee_estimate.go
Normal file
20
infrastructure/network/rpcclient/rpc_get_fee_estimate.go
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package rpcclient
|
||||||
|
|
||||||
|
import "github.com/kaspanet/kaspad/app/appmessage"
|
||||||
|
|
||||||
|
// GetFeeEstimate sends an RPC request respective to the function's name and returns the RPC server's response
|
||||||
|
func (c *RPCClient) GetFeeEstimate() (*appmessage.GetFeeEstimateResponseMessage, error) {
|
||||||
|
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewGetFeeEstimateRequestMessage())
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
response, err := c.route(appmessage.CmdGetFeeEstimateResponseMessage).DequeueWithTimeout(c.timeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
resp := response.(*appmessage.GetFeeEstimateResponseMessage)
|
||||||
|
if resp.Error != nil {
|
||||||
|
return nil, c.convertRPCError(resp.Error)
|
||||||
|
}
|
||||||
|
return resp, nil
|
||||||
|
}
|
@ -1,23 +1,44 @@
|
|||||||
package rpcclient
|
package rpcclient
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/kaspanet/kaspad/app/appmessage"
|
"github.com/kaspanet/kaspad/app/appmessage"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SubmitTransaction sends an RPC request respective to the function's name and returns the RPC server's response
|
// SubmitTransaction sends an RPC request respective to the function's name and returns the RPC server's response
|
||||||
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction, allowOrphan bool) (*appmessage.SubmitTransactionResponseMessage, error) {
|
func (c *RPCClient) SubmitTransaction(transaction *appmessage.RPCTransaction, transactionID string, allowOrphan bool) (*appmessage.SubmitTransactionResponseMessage, error) {
|
||||||
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionRequestMessage(transaction, allowOrphan))
|
err := c.rpcRouter.outgoingRoute().Enqueue(appmessage.NewSubmitTransactionRequestMessage(transaction, allowOrphan))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
response, err := c.route(appmessage.CmdSubmitTransactionResponseMessage).DequeueWithTimeout(c.timeout)
|
for {
|
||||||
if err != nil {
|
response, err := c.route(appmessage.CmdSubmitTransactionResponseMessage).DequeueWithTimeout(c.timeout)
|
||||||
return nil, err
|
if err != nil {
|
||||||
}
|
return nil, err
|
||||||
submitTransactionResponse := response.(*appmessage.SubmitTransactionResponseMessage)
|
}
|
||||||
if submitTransactionResponse.Error != nil {
|
submitTransactionResponse := response.(*appmessage.SubmitTransactionResponseMessage)
|
||||||
return nil, c.convertRPCError(submitTransactionResponse.Error)
|
// Match the response to the expected ID. If they are different it means we got an old response which we
|
||||||
}
|
// previously timed-out on, so we log and continue waiting for the correct current response.
|
||||||
|
if submitTransactionResponse.TransactionID != transactionID {
|
||||||
|
if submitTransactionResponse.Error != nil {
|
||||||
|
// A non-updated Kaspad might return an empty ID in the case of error, so in
|
||||||
|
// such a case we fallback to checking if the error contains the expected ID
|
||||||
|
if submitTransactionResponse.TransactionID != "" || !strings.Contains(submitTransactionResponse.Error.Message, transactionID) {
|
||||||
|
log.Warnf("SubmitTransaction: received an error response for previous request: %s", submitTransactionResponse.Error)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
return submitTransactionResponse, nil
|
} else {
|
||||||
|
log.Warnf("SubmitTransaction: received a successful response for previous request with ID %s",
|
||||||
|
submitTransactionResponse.TransactionID)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if submitTransactionResponse.Error != nil {
|
||||||
|
return nil, c.convertRPCError(submitTransactionResponse.Error)
|
||||||
|
}
|
||||||
|
|
||||||
|
return submitTransactionResponse, nil
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@ ARG KASPAMINER_IMAGE
|
|||||||
FROM ${KASPAD_IMAGE} as kaspad
|
FROM ${KASPAD_IMAGE} as kaspad
|
||||||
FROM ${KASPAMINER_IMAGE} as kaspaminer
|
FROM ${KASPAMINER_IMAGE} as kaspaminer
|
||||||
|
|
||||||
FROM golang:1.19-alpine
|
FROM golang:1.23-alpine
|
||||||
|
|
||||||
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
RUN mkdir -p /go/src/github.com/kaspanet/kaspad
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ FLAGS=$@
|
|||||||
go version
|
go version
|
||||||
|
|
||||||
go get $FLAGS -t -d ../...
|
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
|
go install $FLAGS honnef.co/go/tools/cmd/staticcheck@latest
|
||||||
|
|
||||||
test -z "$(go fmt ./...)"
|
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 ./...
|
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 ./...
|
go vet -composites=false $FLAGS ./...
|
||||||
golint -set_exit_status $FLAGS ./...
|
|
||||||
|
|
||||||
go install $FLAGS ../...
|
go install $FLAGS ../...
|
||||||
|
|
||||||
|
@ -86,7 +86,7 @@ func submitAnAmountOfTransactionsToTheMempool(t *testing.T, rpcClient *rpcclient
|
|||||||
|
|
||||||
for i, transaction := range transactions {
|
for i, transaction := range transactions {
|
||||||
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
|
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(transaction)
|
||||||
_, err := rpcClient.SubmitTransaction(rpcTransaction, false)
|
_, err := rpcClient.SubmitTransaction(rpcTransaction, consensushashing.TransactionID(transaction).String(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if ignoreOrphanRejects && strings.Contains(err.Error(), "orphan") {
|
if ignoreOrphanRejects && strings.Contains(err.Error(), "orphan") {
|
||||||
continue
|
continue
|
||||||
|
@ -53,7 +53,7 @@ func TestTxRelay(t *testing.T) {
|
|||||||
msgTx := generateTx(t, secondBlock.Transactions[transactionhelper.CoinbaseTransactionIndex], payer, payee)
|
msgTx := generateTx(t, secondBlock.Transactions[transactionhelper.CoinbaseTransactionIndex], payer, payee)
|
||||||
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
||||||
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
|
rpcTransaction := appmessage.DomainTransactionToRPCTransaction(domainTransaction)
|
||||||
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction, false)
|
response, err := payer.rpcClient.SubmitTransaction(rpcTransaction, consensushashing.TransactionID(domainTransaction).String(), false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error submitting transaction: %+v", err)
|
t.Fatalf("Error submitting transaction: %+v", err)
|
||||||
}
|
}
|
||||||
|
@ -88,8 +88,8 @@ func TestUTXOIndex(t *testing.T) {
|
|||||||
// Submit a few transactions that spends some UTXOs
|
// Submit a few transactions that spends some UTXOs
|
||||||
const transactionAmountToSpend = 5
|
const transactionAmountToSpend = 5
|
||||||
for i := 0; i < transactionAmountToSpend; i++ {
|
for i := 0; i < transactionAmountToSpend; i++ {
|
||||||
rpcTransaction := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
|
rpcTransaction, transactionID := buildTransactionForUTXOIndexTest(t, notificationEntries[i])
|
||||||
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction, false)
|
_, err = kaspad.rpcClient.SubmitTransaction(rpcTransaction, transactionID, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error submitting transaction: %s", err)
|
t.Fatalf("Error submitting transaction: %s", err)
|
||||||
}
|
}
|
||||||
@ -171,7 +171,7 @@ func TestUTXOIndex(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) *appmessage.RPCTransaction {
|
func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAddressesEntry) (*appmessage.RPCTransaction, string) {
|
||||||
transactionIDBytes, err := hex.DecodeString(entry.Outpoint.TransactionID)
|
transactionIDBytes, err := hex.DecodeString(entry.Outpoint.TransactionID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error decoding transaction ID: %s", err)
|
t.Fatalf("Error decoding transaction ID: %s", err)
|
||||||
@ -224,5 +224,5 @@ func buildTransactionForUTXOIndexTest(t *testing.T, entry *appmessage.UTXOsByAdd
|
|||||||
msgTx.TxIn[0].SignatureScript = signatureScript
|
msgTx.TxIn[0].SignatureScript = signatureScript
|
||||||
|
|
||||||
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
domainTransaction := appmessage.MsgTxToDomainTransaction(msgTx)
|
||||||
return appmessage.DomainTransactionToRPCTransaction(domainTransaction)
|
return appmessage.DomainTransactionToRPCTransaction(domainTransaction), consensushashing.TransactionID(domainTransaction).String()
|
||||||
}
|
}
|
||||||
|
@ -105,23 +105,18 @@ func TestAddresses(t *testing.T) {
|
|||||||
// ECDSA P2PK tests.
|
// ECDSA P2PK tests.
|
||||||
{
|
{
|
||||||
name: "mainnet ecdsa p2pk",
|
name: "mainnet ecdsa p2pk",
|
||||||
addr: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
addr: "kaspa:qyp0r5mcq4rd5grj3652ra09u5dcgwqq9ntuswp247nama5quyj40eq03sc2dkx",
|
||||||
encoded: "kaspa:q835ennsep3hxfe7lnz5ee7j5jgmkjswsn35ennsep3hxfe7ln35e2sm7yrlr4w",
|
encoded: "kaspa:qyp0r5mcq4rd5grj3652ra09u5dcgwqq9ntuswp247nama5quyj40eq03sc2dkx",
|
||||||
valid: true,
|
valid: true,
|
||||||
result: util.TstAddressPubKeyECDSA(
|
result: util.TstAddressPubKeyECDSA(
|
||||||
util.Bech32PrefixKaspa,
|
util.Bech32PrefixKaspa,
|
||||||
[util.PublicKeySizeECDSA]byte{
|
[util.PublicKeySizeECDSA]byte{
|
||||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
0x02, 0xf1, 0xd3, 0x78, 0x05, 0x46, 0xda, 0x20, 0x72, 0x8e, 0xa8, 0xa1, 0xf5, 0xe5, 0xe5, 0x1b, 0x84, 0x38, 0x00, 0x2c, 0xd7, 0xc8, 0x38, 0x2a, 0xaf, 0xa7, 0xdd, 0xf6, 0x80, 0xe1, 0x25, 0x57, 0xe4,
|
||||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
|
||||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
|
||||||
0xe3, 0x4c, 0xaa,
|
|
||||||
}),
|
}),
|
||||||
f: func() (util.Address, error) {
|
f: func() (util.Address, error) {
|
||||||
publicKey := []byte{
|
publicKey := []byte{
|
||||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
0x02, 0xf1, 0xd3, 0x78, 0x05, 0x46, 0xda, 0x20, 0x72, 0x8e, 0xa8, 0xa1, 0xf5, 0xe5, 0xe5, 0x1b, 0x84, 0x38, 0x00, 0x2c, 0xd7, 0xc8, 0x38, 0x2a, 0xaf, 0xa7, 0xdd, 0xf6, 0x80, 0xe1, 0x25, 0x57, 0xe4,
|
||||||
0xc5, 0x4c, 0xe7, 0xd2, 0xa4, 0x91, 0xbb, 0x4a, 0x0e, 0x84,
|
}
|
||||||
0xe3, 0x4c, 0xce, 0x70, 0xc8, 0x63, 0x73, 0x27, 0x3e, 0xfc,
|
|
||||||
0xe3, 0x4c, 0xaa}
|
|
||||||
return util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
return util.NewAddressPublicKeyECDSA(publicKey, util.Bech32PrefixKaspa)
|
||||||
},
|
},
|
||||||
passedPrefix: util.Bech32PrefixUnknown,
|
passedPrefix: util.Bech32PrefixUnknown,
|
||||||
|
@ -2,6 +2,7 @@ package txmass
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
"github.com/kaspanet/kaspad/domain/consensus/model/externalapi"
|
||||||
|
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||||
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
"github.com/kaspanet/kaspad/domain/consensus/utils/transactionhelper"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -10,6 +11,9 @@ type Calculator struct {
|
|||||||
massPerTxByte uint64
|
massPerTxByte uint64
|
||||||
massPerScriptPubKeyByte uint64
|
massPerScriptPubKeyByte uint64
|
||||||
massPerSigOp uint64
|
massPerSigOp uint64
|
||||||
|
|
||||||
|
// The parameter for scaling inverse KAS value to mass units (KIP-0009)
|
||||||
|
storageMassParameter uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCalculator creates a new instance of Calculator
|
// NewCalculator creates a new instance of Calculator
|
||||||
@ -18,6 +22,7 @@ func NewCalculator(massPerTxByte, massPerScriptPubKeyByte, massPerSigOp uint64)
|
|||||||
massPerTxByte: massPerTxByte,
|
massPerTxByte: massPerTxByte,
|
||||||
massPerScriptPubKeyByte: massPerScriptPubKeyByte,
|
massPerScriptPubKeyByte: massPerScriptPubKeyByte,
|
||||||
massPerSigOp: massPerSigOp,
|
massPerSigOp: massPerSigOp,
|
||||||
|
storageMassParameter: constants.SompiPerKaspa * 10_000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,6 +64,75 @@ func (c *Calculator) CalculateTransactionMass(transaction *externalapi.DomainTra
|
|||||||
return massForSize + massForScriptPubKey + massForSigOps
|
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
|
// transactionEstimatedSerializedSize is the estimated size of a transaction in some
|
||||||
// serialization. This has to be deterministic, but not necessarily accurate, since
|
// 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
|
// it's only used as the size component in the transaction and block mass limit
|
||||||
|
@ -11,7 +11,7 @@ const validCharacters = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrs
|
|||||||
const (
|
const (
|
||||||
appMajor uint = 0
|
appMajor uint = 0
|
||||||
appMinor uint = 12
|
appMinor uint = 12
|
||||||
appPatch uint = 13
|
appPatch uint = 22
|
||||||
)
|
)
|
||||||
|
|
||||||
// appBuild is defined as a variable so it can be overridden during the build
|
// appBuild is defined as a variable so it can be overridden during the build
|
||||||
|
Loading…
x
Reference in New Issue
Block a user