Compare commits
46 commits
v0.22.100-
...
master
Author | SHA1 | Date | |
---|---|---|---|
|
a0ff51b84a | ||
|
4c39a9842c | ||
|
f513fca6a7 | ||
|
6728bf4b08 | ||
|
979d643594 | ||
|
cbc4d489e8 | ||
|
987a533423 | ||
|
6bc9a2b4dd | ||
|
9bcd3d0591 | ||
|
2adfcd211d | ||
|
81ec217899 | ||
|
5acfa4c81b | ||
|
c5193e74ac | ||
|
8a80f0683a | ||
|
5d7a219e35 | ||
|
2d04d31894 | ||
|
ce37025d5a | ||
|
98e5771989 | ||
|
ff324e0fdb | ||
|
be0d7de8da | ||
|
fcfb2af76f | ||
|
78bed14956 | ||
|
fdedbf86f8 | ||
|
a9351b3e3a | ||
|
e323751218 | ||
|
66c8567a27 | ||
|
6b0e7592c6 | ||
|
05f52c11a1 | ||
|
ea63a44c7b | ||
|
daa3137dc4 | ||
|
b147fe2a5b | ||
|
7f9fe4b970 | ||
|
eefb1260eb | ||
|
a8a44aa988 | ||
|
abb1b8b388 | ||
|
13e31d033a | ||
|
5499a2c1b3 | ||
|
fae4063046 | ||
|
8d1005706b | ||
|
bb93a49349 | ||
|
d5922cd725 | ||
|
3a179a0eee | ||
|
ca9b4e5529 | ||
|
2b7f065855 | ||
|
b859832907 | ||
|
70852905e0 |
94 changed files with 3446 additions and 1453 deletions
2
.github/workflows/basic-check.yml
vendored
2
.github/workflows/basic-check.yml
vendored
|
@ -9,7 +9,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.18.2]
|
go: [1.19]
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
|
|
2
.github/workflows/full-sync-part-1.yml
vendored
2
.github/workflows/full-sync-part-1.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.18.2]
|
go: [1.19]
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
echo "Note ${{ github.event.inputs.note }}!"
|
echo "Note ${{ github.event.inputs.note }}!"
|
||||||
|
|
2
.github/workflows/full-sync-part-2.yml
vendored
2
.github/workflows/full-sync-part-2.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
||||||
runs-on: self-hosted
|
runs-on: self-hosted
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.18.2]
|
go: [1.19]
|
||||||
steps:
|
steps:
|
||||||
- run: |
|
- run: |
|
||||||
echo "Note ${{ github.event.inputs.note }}!"
|
echo "Note ${{ github.event.inputs.note }}!"
|
||||||
|
|
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
|
@ -4,7 +4,7 @@ env:
|
||||||
# go needs absolute directories, using the $HOME variable doesn't work here.
|
# go needs absolute directories, using the $HOME variable doesn't work here.
|
||||||
GOCACHE: /home/runner/work/go/pkg/build
|
GOCACHE: /home/runner/work/go/pkg/build
|
||||||
GOPATH: /home/runner/work/go
|
GOPATH: /home/runner/work/go
|
||||||
GO_VERSION: '^1.18.2'
|
GO_VERSION: '^1.19'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
|
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
|
@ -28,7 +28,7 @@ jobs:
|
||||||
name: Set up Go
|
name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
||||||
with:
|
with:
|
||||||
go-version: 1.18.2
|
go-version: 1.19
|
||||||
|
|
||||||
# Login against a Docker registry except on PR
|
# Login against a Docker registry except on PR
|
||||||
# https://github.com/docker/login-action
|
# https://github.com/docker/login-action
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
ARG ARCH=amd64
|
ARG ARCH=amd64
|
||||||
|
|
||||||
FROM golang:1.18.2 AS build-container
|
FROM golang:1.19 AS build-container
|
||||||
|
|
||||||
ARG ARCH
|
ARG ARCH
|
||||||
|
|
||||||
|
|
398
README.md
398
README.md
|
@ -5,17 +5,310 @@
|
||||||
[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
|
[![ISC License](https://img.shields.io/badge/license-ISC-blue.svg)](http://copyfree.org)
|
||||||
<!--[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/lbryio/lbcd)-->
|
<!--[![GoDoc](https://img.shields.io/badge/godoc-reference-blue.svg)](https://pkg.go.dev/github.com/lbryio/lbcd)-->
|
||||||
|
|
||||||
`lbcd` is a full node implementation of LBRY's blockchain written in Go (golang).
|
**lbcd** is a full node implementation of LBRY's blockchain written in Go (golang).
|
||||||
|
|
||||||
This project is currently under active development and is in a Beta state while
|
Software stack developed by LBRY teams has been all migrated to **lbcd**.
|
||||||
we ensure it matches LBRYcrd's functionality. The intention is that it properly
|
|
||||||
downloads, validates, and serves the block chain using the exact rules
|
|
||||||
(including consensus bugs) for block acceptance as LBRYcrd.
|
|
||||||
We have taken great care to avoid lbcd causing a fork to the blockchain.
|
|
||||||
|
|
||||||
Note: `lbcd` does *NOT* include wallet functionality. That functionality is provided by the
|
We're working with exchanges and pool oerators to migrate from **lbrycrd** to **lbcd**.
|
||||||
|
|
||||||
|
If you're integrating with **lbcd+lbcwallet**, please check the Wiki for current [supported RPCs](wiki/RPC-availability).
|
||||||
|
|
||||||
|
Note: **lbcd** does *NOT* include wallet functionality. That functionality is provided by the
|
||||||
[lbcwallet](https://github.com/lbryio/lbcwallet) and the [LBRY SDK](https://github.com/lbryio/lbry-sdk).
|
[lbcwallet](https://github.com/lbryio/lbcwallet) and the [LBRY SDK](https://github.com/lbryio/lbry-sdk).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
All common operating systems are supported. lbcd requires at least 8GB of RAM
|
||||||
|
and at least 100GB of disk storage. Both RAM and disk requirements increase slowly over time.
|
||||||
|
Using a fast NVMe disk is recommended.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Acquire binary files from [releases](https://github.com/lbryio/lbcd/releases)
|
||||||
|
|
||||||
|
For compilation, [Go](http://golang.org) 1.19 or newer is required.
|
||||||
|
Install Go according to its [installation instructions](http://golang.org/doc/install).
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
# lbcd (full node)
|
||||||
|
$ go install github.com/lbryio/lbcd@latest
|
||||||
|
|
||||||
|
# lbcctl (rpc client utility)
|
||||||
|
$ go install github.com/lbryio/lbcd/cmd/lbcctl@latest
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Default application folder `${LBCDDIR}`:
|
||||||
|
|
||||||
|
- Linux: `~/.lbcd/`
|
||||||
|
- MacOS: `/Users/<username>/Library/Application Support/Lbcd/`
|
||||||
|
|
||||||
|
### Start the **lbcd**
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
./lbcd
|
||||||
|
```
|
||||||
|
|
||||||
|
**lbcd** loads config file at `"${LBCDDIR}/lbcd.conf"`.
|
||||||
|
|
||||||
|
If no config is found, it creates a [default one](sample-lbcd.conf), which includes all available options with default settings except randomly generated *RPC credentials* (see below).
|
||||||
|
|
||||||
|
### RPC server
|
||||||
|
|
||||||
|
RPC credentials (`rpcuser` and `rpcpass`) is required to enable RPC server. It can be specify in the `"${LBCDDIR}/lbcd.conf"`, using command line options:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
./lbcd --rpcuser=rpcuser --rpcpass=rpcpass
|
||||||
|
|
||||||
|
2022-07-28 12:28:19.627 [INF] RPCS: RPC server listening on 0.0.0.0:9245
|
||||||
|
2022-07-28 12:28:19.627 [INF] RPCS: RPC server listening on [::]:9245
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working with TLS (Default)
|
||||||
|
|
||||||
|
By default, **lbcd** runs RPC server with TLS enabled, and generates the `rpc.cert` and `rpc.key` under `${LBCDDIR}`, if not exist already.
|
||||||
|
|
||||||
|
To interact with the RPC server, a client has to either specify the `rpc.cert`, or disable the certification verification for TLS.
|
||||||
|
|
||||||
|
Interact with **lbcd** RPC using `lbcctl`
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ ./lbcctl --rpccert "${LBCDDIR}/rpc.cert" getblockcount
|
||||||
|
|
||||||
|
# or disable the certificate verification
|
||||||
|
$ ./lbcctl --skipverify getblockcount
|
||||||
|
|
||||||
|
1200062
|
||||||
|
```
|
||||||
|
|
||||||
|
Interact with **lbcd** RPC using `curl`
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ curl --user rpcuser:rpcpass \
|
||||||
|
--cacert "${LBCDDIR}/rpc.cert" \
|
||||||
|
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockcount", "params": []}' \
|
||||||
|
-H 'content-type: text/plain;' \
|
||||||
|
https://127.0.0.1:9245/
|
||||||
|
|
||||||
|
# or disable the certificate verification
|
||||||
|
$ curl --user rpcuser:rpcpass \
|
||||||
|
--insecure \
|
||||||
|
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockcount", "params": []}' \
|
||||||
|
-H 'content-type: text/plain;' \
|
||||||
|
https://127.0.0.1:9245/
|
||||||
|
```
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{"jsonrpc":"1.0","result":1200062,"error":null,"id":"curltest"}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Working without TLS
|
||||||
|
|
||||||
|
TLS can be disabled using the `--notls` option:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ ./lbcd --notls
|
||||||
|
```
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ ./lbcctl --notls getblockcount
|
||||||
|
|
||||||
|
1200062
|
||||||
|
```
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ curl --user rpcuser:rpcpass \
|
||||||
|
--data-binary '{"jsonrpc": "1.0", "id": "curltest", "method": "getblockcount", "params": []}' \
|
||||||
|
-H 'content-type: text/plain;' \
|
||||||
|
http://127.0.0.1:9245/
|
||||||
|
```
|
||||||
|
|
||||||
|
``` json
|
||||||
|
{"jsonrpc":"1.0","result":1200062,"error":null,"id":"curltest"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Using Snapshots (optional)
|
||||||
|
|
||||||
|
[Snapshots](https://snapshots.lbry.com/blockchain/) are created bi-weekly to help new users catch up current block height.
|
||||||
|
|
||||||
|
The snapshots are archived and compressed in [zstd](https://facebook.github.io/zstd/) format for it's compression ratio and speed.
|
||||||
|
|
||||||
|
Download the snapshot, and uncompress it:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
time curl -O https://snapshots.lbry.com/blockchain/lbcd_snapshot_1199527_v0.22.105_2022-07-27.tar.zst
|
||||||
|
zstd -d --stdout lbcd_snapshot_1199527_v0.22.105_2022-07-27.tar.zst | tar xf - -C "${LBCDDIR}"
|
||||||
|
```
|
||||||
|
|
||||||
|
If preferred, a user can download and uncompress the snapshot on the fly:
|
||||||
|
By the time the download is finished, the snapshots should be almost uncompressed already.
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
mkdir -p "${LBCDDIR}"
|
||||||
|
|
||||||
|
time curl https://snapshots.lbry.com/blockchain/lbcd_snapshot_1199527_v0.22.105_2022-07-27.tar.zst | zstd -d --stdout | tar xf - -C "${LBCDDIR}"
|
||||||
|
|
||||||
|
# % Total % Received % Xferd Average Speed Time Time Time Current
|
||||||
|
# Dload Upload Total Spent Left Speed
|
||||||
|
# 100 64.9G 100 64.9G 0 0 37.0M 0 0:29:49 0:29:49 --:--:-- 33.0M
|
||||||
|
#
|
||||||
|
# real 29m49.962s
|
||||||
|
# user 6m53.710s
|
||||||
|
# sys 8m56.545s
|
||||||
|
```
|
||||||
|
|
||||||
|
## Working with RPCs
|
||||||
|
|
||||||
|
Using `lbcctl -l` to list available RPCs:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ lbcctl -l
|
||||||
|
|
||||||
|
Chain Server Commands:
|
||||||
|
addnode "addr" "add|remove|onetry"
|
||||||
|
createrawtransaction [{"txid":"value","vout":n},...] {"address":amount,...} (locktime)
|
||||||
|
debuglevel "levelspec"
|
||||||
|
decoderawtransaction "hextx"
|
||||||
|
decodescript "hexscript"
|
||||||
|
deriveaddresses "descriptor" ({"value":value})
|
||||||
|
fundrawtransaction "hextx" {"changeaddress":changeaddress,"changeposition":changeposition,"changetype":changetype,"includewatching":includewatching,"lockunspents":lockunspents,"feerate":feerate,"subtractfeefromoutputs":[subtractfeefromoutput,...],"replaceable":replaceable,"conftarget":conftarget,"estimatemode":estimatemode} (iswitness)
|
||||||
|
generate numblocks
|
||||||
|
|
||||||
|
[skipped]
|
||||||
|
|
||||||
|
Wallet Server Commands (--wallet):
|
||||||
|
addmultisigaddress nrequired ["key",...] ("account")
|
||||||
|
addwitnessaddress "address"
|
||||||
|
backupwallet "destination"
|
||||||
|
createmultisig nrequired ["key",...]
|
||||||
|
createnewaccount "account"
|
||||||
|
createwallet "walletname" (disableprivatekeys=false blank=false passphrase="" avoidreuse=false)
|
||||||
|
dumpprivkey "address"
|
||||||
|
dumpwallet "filename"
|
||||||
|
encryptwallet "passphrase"
|
||||||
|
estimatefee numblocks
|
||||||
|
estimatepriority numblocks
|
||||||
|
estimatesmartfee conftarget (estimatemode="CONSERVATIVE")
|
||||||
|
getaccount "address"
|
||||||
|
getaccountaddress "account"
|
||||||
|
getaddressesbyaccount "account"
|
||||||
|
|
||||||
|
[skipped]
|
||||||
|
```
|
||||||
|
|
||||||
|
Using `lbcctl help rpcname` to show the RPC spec:
|
||||||
|
|
||||||
|
``` sh
|
||||||
|
$ lbcctl help getblock
|
||||||
|
|
||||||
|
getblock "hash" (verbosity=1)
|
||||||
|
|
||||||
|
Returns information about a block given its hash.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
1. hash (string, required) The hash of the block
|
||||||
|
2. verbosity (numeric, optional, default=1) Specifies whether the block data should be returned as a hex-encoded string (0), as parsed data with a slice of TXIDs (1), or as parsed data with parsed transaction data (2)
|
||||||
|
|
||||||
|
Result (verbosity=0):
|
||||||
|
"value" (string) Hex-encoded bytes of the serialized block
|
||||||
|
|
||||||
|
Result (verbosity=1):
|
||||||
|
{
|
||||||
|
"getblockverboseresultbase": { (object)
|
||||||
|
"hash": "value", (string) The hash of the block (same as provided)
|
||||||
|
"confirmations": n, (numeric) The number of confirmations
|
||||||
|
"strippedsize": n, (numeric) The size of the block without witness data
|
||||||
|
"size": n, (numeric) The size of the block
|
||||||
|
"weight": n, (numeric) The weight of the block
|
||||||
|
"height": n, (numeric) The height of the block in the block chain
|
||||||
|
"version": n, (numeric) The block version
|
||||||
|
"versionHex": "value", (string) The block version in hexadecimal
|
||||||
|
"merkleroot": "value", (string) Root hash of the merkle tree
|
||||||
|
"time": n, (numeric) The block time in seconds since 1 Jan 1970 GMT
|
||||||
|
"mediantime": n, (numeric) The median block time in seconds since 1 Jan 1970 GMT
|
||||||
|
"nonce": n, (numeric) The block nonce
|
||||||
|
"bits": "value", (string) The bits which represent the block difficulty
|
||||||
|
"difficulty": n.nnn, (numeric) The proof-of-work difficulty as a multiple of the minimum difficulty
|
||||||
|
"chainwork": "value", (string) Expected number of hashes required to produce the chain up to this block (in hex)
|
||||||
|
"previousblockhash": "value", (string) The hash of the previous block
|
||||||
|
"nextblockhash": "value", (string) The hash of the next block (only if there is one)
|
||||||
|
"nameclaimroot": "value", (string) Root hash of the claim trie
|
||||||
|
"nTx": n, (numeric) The number of transactions (aka, count of TX)
|
||||||
|
},
|
||||||
|
"tx": ["value",...], (array of string) The transaction hashes (only when verbosity=1)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## **lbcd** & **lbcwallet**
|
||||||
|
|
||||||
|
*Wallet* related functianlities and RPCs are provided by a separate programe - [**lbcwallet**](https://github.com/lbryio/lbcwallet).
|
||||||
|
|
||||||
|
Once setup, lbcwallet can serve wallet related RPCs as well as proxy lbcd RPCs to an assocated lbcd now.
|
||||||
|
It's sufficient for user to connect just the **lbcwallet** instead of both.
|
||||||
|
|
||||||
|
``` mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor C as lbcctl
|
||||||
|
participant W as lbcwallet (port: 9244)
|
||||||
|
participant D as lbcd (port: 9245)
|
||||||
|
|
||||||
|
rect rgb(200,200,200)
|
||||||
|
Note over C,D: lbcctl getblockcount
|
||||||
|
C ->>+ D: getblockcount
|
||||||
|
D -->>- C: response
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(200,200,200)
|
||||||
|
Note over C,W: lbcctl --wallet balance
|
||||||
|
C ->>+ W: getbalance
|
||||||
|
W -->>- C: response
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(200,200,200)
|
||||||
|
Note over C,D: lbcctl --wallet getblockcount (lbcd RPC service proxied by lbcwallet)
|
||||||
|
C ->>+ W: getblockcount
|
||||||
|
W ->>+ D: getblockcount
|
||||||
|
D -->>- W: response
|
||||||
|
W -->>- C: response
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
While **lbcd** can run standalone as a full node, **lbcwallet** requires an associated **lbcd** instance for scanning and sync'ing block data.
|
||||||
|
|
||||||
|
``` mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
participant W as lbcwallet (RPC port: 9244)
|
||||||
|
participant D as lbcd (RPC port: 9245, P2P port: 9246)
|
||||||
|
participant D2 as other lbcd node(s) (P2P port: 9246)
|
||||||
|
|
||||||
|
rect rgb(200,200,200)
|
||||||
|
Note over W,D: Asynchronous websocket notifications
|
||||||
|
W ->> D: subscribe to notifications
|
||||||
|
D -->> W: notification
|
||||||
|
D -->> W: notification
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(200,200,200)
|
||||||
|
Note over W,D: lbcd RPCs
|
||||||
|
W ->>+ D: getblockheader
|
||||||
|
D ->>- W: response
|
||||||
|
end
|
||||||
|
|
||||||
|
rect rgb(200,200,200)
|
||||||
|
Note over D,D2: P2P messages over port 9246
|
||||||
|
D -->> D2: P2P message
|
||||||
|
D2 -->> D: P2P message
|
||||||
|
end
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data integrity
|
||||||
|
|
||||||
|
**lbcd** is not immune to data loss. It expects a clean shutdown via SIGINT or
|
||||||
|
SIGTERM. SIGKILL, immediate VM kills, and sudden power loss can cause data
|
||||||
|
corruption, thus requiring chain resynchronization for recovery.
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
We take security seriously. Please contact [security](mailto:security@lbry.com) regarding any security issues.
|
We take security seriously. Please contact [security](mailto:security@lbry.com) regarding any security issues.
|
||||||
|
@ -24,97 +317,6 @@ Our PGP key is [here](https://lbry.com/faq/pgp-key) if you need it.
|
||||||
We maintain a mailing list for notifications of upgrades, security issues,
|
We maintain a mailing list for notifications of upgrades, security issues,
|
||||||
and soft/hard forks. To join, visit [fork list](https://lbry.com/forklist)
|
and soft/hard forks. To join, visit [fork list](https://lbry.com/forklist)
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
All common operating systems are supported. lbcd requires at least 8GB of RAM
|
|
||||||
and at least 100GB of disk storage. Both RAM and disk requirements increase slowly over time.
|
|
||||||
Using a fast NVMe disk is recommended.
|
|
||||||
|
|
||||||
`lbcd` is not immune to data loss. It expects a clean shutdown via SIGINT or
|
|
||||||
SIGTERM. SIGKILL, immediate VM kills, and sudden power loss can cause data
|
|
||||||
corruption, thus requiring chain resynchronization for recovery.
|
|
||||||
|
|
||||||
For compilation, [Go](http://golang.org) 1.16 or newer is required.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Acquire binary files from [releases](https://github.com/lbryio/lbcd/releases)
|
|
||||||
|
|
||||||
### To build from Source on Linux/BSD/MacOSX/POSIX
|
|
||||||
|
|
||||||
Install Go according to its [installation instructions](http://golang.org/doc/install).
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
git clone https://github.com/lbryio/lbcd
|
|
||||||
cd lbcd
|
|
||||||
|
|
||||||
# Build lbcd
|
|
||||||
go build .
|
|
||||||
|
|
||||||
# Build lbcctl
|
|
||||||
go build ./cmd/lbcctl
|
|
||||||
```
|
|
||||||
|
|
||||||
Both [GoLand](https://www.jetbrains.com/go/)
|
|
||||||
and [VS Code](https://code.visualstudio.com/docs/languages/go) IDEs are supported.
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
By default, data and logs are stored in `<LBCDDIR>`:
|
|
||||||
|
|
||||||
- Linux: `~/.lbcd/`
|
|
||||||
- MacOS: `/Users/<username>/Library/Application Support/Lbcd/`
|
|
||||||
|
|
||||||
To enable RPC access a username and password is required. Example:
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
./lbcd --rpcuser=rpcuser --rpcpass=rpcpass
|
|
||||||
```
|
|
||||||
|
|
||||||
Interact with lbcd via RPC using `lbcctl`
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass getblockcount
|
|
||||||
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass getblocktemplate
|
|
||||||
```
|
|
||||||
|
|
||||||
By default, the RPCs are served over TLS. `lbcd` generates (if not exists) `rpc.cert` and
|
|
||||||
`rpc.key` under `<LBCDDIR>` where `lbcctl` would search and use them.
|
|
||||||
|
|
||||||
The RPCs can also be served without TLS *(on localhost only)* using (`--notls`)
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
./lbcd --rpcuser=rpcuser --rpcpass=rpcpass --notls
|
|
||||||
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass --notls getblockcount
|
|
||||||
```
|
|
||||||
|
|
||||||
## Working with Different Networks
|
|
||||||
|
|
||||||
By default, `lbcd` and `lbcctl` use the following ports for different networks respectively:
|
|
||||||
|
|
||||||
| Network | RPC Port | Network Port |
|
|
||||||
| ------- | -------- | ------------ |
|
|
||||||
| mainnet | 9245 | 9246 |
|
|
||||||
| testnet | 19245 | 19246 |
|
|
||||||
| regtest | 29245 | 29246 |
|
|
||||||
|
|
||||||
Running `lbcd` and `lbcctl` with `--testnet` or `--regtest` would use different chain params as well as default RPC and Network ports.
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
./lbcd --rpcuser=rpcuser --rpcpass=rpcpass --regtest
|
|
||||||
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass --regtest getblockcount
|
|
||||||
```
|
|
||||||
|
|
||||||
The default Network and RPC ports of `lbcd` can be overriden using `--listen` and `--rpclisten`
|
|
||||||
`lbcctl` can also connect to RPC server specified by `--rpcserver`
|
|
||||||
|
|
||||||
``` sh
|
|
||||||
./lbcd --rpcuser=rpcuser --rpcpass=rpcpass --regtest --listen=127.0.0.1:29248 --rpclisten=127.0.0.1:29247
|
|
||||||
./lbcctl --rpcuser=rpcuser --rpcpass=rpcpass --regtest --rpcserver=127.0.0.1:29247 getblockcount
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: Wallet related RPCs are provided by [lbcwallet](https://github.com/lbryio/lbcwallet).
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
Contributions to this project are welcome, encouraged, and compensated.
|
Contributions to this project are welcome, encouraged, and compensated.
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
/*
|
/*
|
||||||
Package addrmgr implements concurrency safe Bitcoin address manager.
|
Package addrmgr implements concurrency safe Bitcoin address manager.
|
||||||
|
|
||||||
Address Manager Overview
|
# Address Manager Overview
|
||||||
|
|
||||||
In order maintain the peer-to-peer Bitcoin network, there needs to be a source
|
In order maintain the peer-to-peer Bitcoin network, there needs to be a source
|
||||||
of addresses to connect to as nodes come and go. The Bitcoin protocol provides
|
of addresses to connect to as nodes come and go. The Bitcoin protocol provides
|
||||||
|
|
|
@ -8,6 +8,7 @@ package blockchain
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -36,8 +37,9 @@ const (
|
||||||
// from the block being located.
|
// from the block being located.
|
||||||
//
|
//
|
||||||
// For example, assume a block chain with a side chain as depicted below:
|
// For example, assume a block chain with a side chain as depicted below:
|
||||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
//
|
||||||
// \-> 16a -> 17a
|
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||||
|
// \-> 16a -> 17a
|
||||||
//
|
//
|
||||||
// The block locator for block 17a would be the hashes of blocks:
|
// The block locator for block 17a would be the hashes of blocks:
|
||||||
// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis]
|
// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis]
|
||||||
|
@ -487,7 +489,7 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx, utxoView
|
||||||
// LockTimeToSequence converts the passed relative locktime to a sequence
|
// LockTimeToSequence converts the passed relative locktime to a sequence
|
||||||
// number in accordance to BIP-68.
|
// number in accordance to BIP-68.
|
||||||
// See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
|
// See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
|
||||||
// * (Compatibility)
|
// - (Compatibility)
|
||||||
func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
|
func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
|
||||||
// If we're expressing the relative lock time in blocks, then the
|
// If we're expressing the relative lock time in blocks, then the
|
||||||
// corresponding sequence number is simply the desired input age.
|
// corresponding sequence number is simply the desired input age.
|
||||||
|
@ -1106,8 +1108,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
||||||
// a reorganization to become the main chain).
|
// a reorganization to become the main chain).
|
||||||
//
|
//
|
||||||
// The flags modify the behavior of this function as follows:
|
// The flags modify the behavior of this function as follows:
|
||||||
// - BFFastAdd: Avoids several expensive transaction validation operations.
|
// - BFFastAdd: Avoids several expensive transaction validation operations.
|
||||||
// This is useful when using checkpoints.
|
// This is useful when using checkpoints.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
// This function MUST be called with the chain state lock held (for writes).
|
||||||
func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
||||||
|
@ -1248,8 +1250,8 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
||||||
// isCurrent returns whether or not the chain believes it is current. Several
|
// isCurrent returns whether or not the chain believes it is current. Several
|
||||||
// factors are used to guess, but the key factors that allow the chain to
|
// factors are used to guess, but the key factors that allow the chain to
|
||||||
// believe it is current are:
|
// believe it is current are:
|
||||||
// - Latest block height is after the latest checkpoint (if enabled)
|
// - Latest block height is after the latest checkpoint (if enabled)
|
||||||
// - Latest block has a timestamp newer than ~6 hours ago (as LBRY block time is one fourth of bitcoin)
|
// - Latest block has a timestamp newer than ~6 hours ago (as LBRY block time is one fourth of bitcoin)
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for reads).
|
// This function MUST be called with the chain state lock held (for reads).
|
||||||
func (b *BlockChain) isCurrent() bool {
|
func (b *BlockChain) isCurrent() bool {
|
||||||
|
@ -1272,8 +1274,8 @@ func (b *BlockChain) isCurrent() bool {
|
||||||
// IsCurrent returns whether or not the chain believes it is current. Several
|
// IsCurrent returns whether or not the chain believes it is current. Several
|
||||||
// factors are used to guess, but the key factors that allow the chain to
|
// factors are used to guess, but the key factors that allow the chain to
|
||||||
// believe it is current are:
|
// believe it is current are:
|
||||||
// - Latest block height is after the latest checkpoint (if enabled)
|
// - Latest block height is after the latest checkpoint (if enabled)
|
||||||
// - Latest block has a timestamp newer than 24 hours ago
|
// - Latest block has a timestamp newer than 24 hours ago
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) IsCurrent() bool {
|
func (b *BlockChain) IsCurrent() bool {
|
||||||
|
@ -1373,6 +1375,57 @@ func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, erro
|
||||||
return &node.hash, nil
|
return &node.hash, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BlockAttributes desribes a Block in relation to others on the main chain.
|
||||||
|
type BlockAttributes struct {
|
||||||
|
Height int32
|
||||||
|
Confirmations int32
|
||||||
|
MedianTime time.Time
|
||||||
|
ChainWork *big.Int
|
||||||
|
PrevHash *chainhash.Hash
|
||||||
|
NextHash *chainhash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlockAttributesByHash returns BlockAttributes for the block with the given hash
|
||||||
|
// relative to other blocks in the main chain. A BestState snapshot describing
|
||||||
|
// the main chain is also returned for convenience.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access.
|
||||||
|
func (b *BlockChain) BlockAttributesByHash(hash *chainhash.Hash, prevHash *chainhash.Hash) (
|
||||||
|
attrs *BlockAttributes, best *BestState, err error) {
|
||||||
|
best = b.BestSnapshot()
|
||||||
|
node := b.index.LookupNode(hash)
|
||||||
|
if node == nil {
|
||||||
|
str := fmt.Sprintf("block %s not found", hash)
|
||||||
|
return nil, best, errNotInMainChain(str)
|
||||||
|
}
|
||||||
|
|
||||||
|
attrs = &BlockAttributes{
|
||||||
|
Height: node.height,
|
||||||
|
Confirmations: 1 + best.Height - node.height,
|
||||||
|
MedianTime: node.CalcPastMedianTime(),
|
||||||
|
ChainWork: node.workSum,
|
||||||
|
}
|
||||||
|
if !b.bestChain.Contains(node) {
|
||||||
|
attrs.Confirmations = -1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate prev block hash if there is one.
|
||||||
|
if node.height > 0 {
|
||||||
|
attrs.PrevHash = prevHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate next block hash if there is one.
|
||||||
|
if node.height < best.Height {
|
||||||
|
nextHash, err := b.BlockHashByHeight(node.height + 1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, best, err
|
||||||
|
}
|
||||||
|
attrs.NextHash = nextHash
|
||||||
|
}
|
||||||
|
|
||||||
|
return attrs, best, nil
|
||||||
|
}
|
||||||
|
|
||||||
// HeightRange returns a range of block hashes for the given start and end
|
// HeightRange returns a range of block hashes for the given start and end
|
||||||
// heights. It is inclusive of the start height and exclusive of the end
|
// heights. It is inclusive of the start height and exclusive of the end
|
||||||
// height. The end height will be limited to the current main chain height.
|
// height. The end height will be limited to the current main chain height.
|
||||||
|
@ -1508,11 +1561,11 @@ func (b *BlockChain) IntervalBlockHashes(endHash *chainhash.Hash, interval int,
|
||||||
//
|
//
|
||||||
// In addition, there are two special cases:
|
// In addition, there are two special cases:
|
||||||
//
|
//
|
||||||
// - When no locators are provided, the stop hash is treated as a request for
|
// - When no locators are provided, the stop hash is treated as a request for
|
||||||
// that block, so it will either return the node associated with the stop hash
|
// that block, so it will either return the node associated with the stop hash
|
||||||
// if it is known, or nil if it is unknown
|
// if it is known, or nil if it is unknown
|
||||||
// - When locators are provided, but none of them are known, nodes starting
|
// - When locators are provided, but none of them are known, nodes starting
|
||||||
// after the genesis block will be returned
|
// after the genesis block will be returned
|
||||||
//
|
//
|
||||||
// This is primarily a helper function for the locateBlocks and locateHeaders
|
// This is primarily a helper function for the locateBlocks and locateHeaders
|
||||||
// functions.
|
// functions.
|
||||||
|
@ -1596,11 +1649,11 @@ func (b *BlockChain) locateBlocks(locator BlockLocator, hashStop *chainhash.Hash
|
||||||
//
|
//
|
||||||
// In addition, there are two special cases:
|
// In addition, there are two special cases:
|
||||||
//
|
//
|
||||||
// - When no locators are provided, the stop hash is treated as a request for
|
// - When no locators are provided, the stop hash is treated as a request for
|
||||||
// that block, so it will either return the stop hash itself if it is known,
|
// that block, so it will either return the stop hash itself if it is known,
|
||||||
// or nil if it is unknown
|
// or nil if it is unknown
|
||||||
// - When locators are provided, but none of them are known, hashes starting
|
// - When locators are provided, but none of them are known, hashes starting
|
||||||
// after the genesis block will be returned
|
// after the genesis block will be returned
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) LocateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash {
|
func (b *BlockChain) LocateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash {
|
||||||
|
@ -1641,11 +1694,11 @@ func (b *BlockChain) locateHeaders(locator BlockLocator, hashStop *chainhash.Has
|
||||||
//
|
//
|
||||||
// In addition, there are two special cases:
|
// In addition, there are two special cases:
|
||||||
//
|
//
|
||||||
// - When no locators are provided, the stop hash is treated as a request for
|
// - When no locators are provided, the stop hash is treated as a request for
|
||||||
// that header, so it will either return the header for the stop hash itself
|
// that header, so it will either return the header for the stop hash itself
|
||||||
// if it is known, or nil if it is unknown
|
// if it is known, or nil if it is unknown
|
||||||
// - When locators are provided, but none of them are known, headers starting
|
// - When locators are provided, but none of them are known, headers starting
|
||||||
// after the genesis block will be returned
|
// after the genesis block will be returned
|
||||||
//
|
//
|
||||||
// This function is safe for concurrent access.
|
// This function is safe for concurrent access.
|
||||||
func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Hash) []wire.BlockHeader {
|
func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Hash) []wire.BlockHeader {
|
||||||
|
|
|
@ -36,11 +36,13 @@ func fastLog2Floor(n uint32) uint8 {
|
||||||
// for comparing chains.
|
// for comparing chains.
|
||||||
//
|
//
|
||||||
// For example, assume a block chain with a side chain as depicted below:
|
// For example, assume a block chain with a side chain as depicted below:
|
||||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
//
|
||||||
// \-> 4a -> 5a -> 6a
|
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||||
|
// \-> 4a -> 5a -> 6a
|
||||||
//
|
//
|
||||||
// The chain view for the branch ending in 6a consists of:
|
// The chain view for the branch ending in 6a consists of:
|
||||||
// genesis -> 1 -> 2 -> 3 -> 4a -> 5a -> 6a
|
//
|
||||||
|
// genesis -> 1 -> 2 -> 3 -> 4a -> 5a -> 6a
|
||||||
type chainView struct {
|
type chainView struct {
|
||||||
mtx sync.Mutex
|
mtx sync.Mutex
|
||||||
nodes []*blockNode
|
nodes []*blockNode
|
||||||
|
@ -258,12 +260,14 @@ func (c *chainView) next(node *blockNode) *blockNode {
|
||||||
// view.
|
// view.
|
||||||
//
|
//
|
||||||
// For example, assume a block chain with a side chain as depicted below:
|
// For example, assume a block chain with a side chain as depicted below:
|
||||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
//
|
||||||
// \-> 4a -> 5a -> 6a
|
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||||
|
// \-> 4a -> 5a -> 6a
|
||||||
//
|
//
|
||||||
// Further, assume the view is for the longer chain depicted above. That is to
|
// Further, assume the view is for the longer chain depicted above. That is to
|
||||||
// say it consists of:
|
// say it consists of:
|
||||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
//
|
||||||
|
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||||
//
|
//
|
||||||
// Invoking this function with block node 5 would return block node 6 while
|
// Invoking this function with block node 5 would return block node 6 while
|
||||||
// invoking it with block node 5a would return nil since that node is not part
|
// invoking it with block node 5a would return nil since that node is not part
|
||||||
|
@ -321,12 +325,14 @@ func (c *chainView) findFork(node *blockNode) *blockNode {
|
||||||
// the chain view. It will return nil if there is no common block.
|
// the chain view. It will return nil if there is no common block.
|
||||||
//
|
//
|
||||||
// For example, assume a block chain with a side chain as depicted below:
|
// For example, assume a block chain with a side chain as depicted below:
|
||||||
// genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8
|
//
|
||||||
// \-> 6a -> 7a
|
// genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8
|
||||||
|
// \-> 6a -> 7a
|
||||||
//
|
//
|
||||||
// Further, assume the view is for the longer chain depicted above. That is to
|
// Further, assume the view is for the longer chain depicted above. That is to
|
||||||
// say it consists of:
|
// say it consists of:
|
||||||
// genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8.
|
//
|
||||||
|
// genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8.
|
||||||
//
|
//
|
||||||
// Invoking this function with block node 7a would return block node 5 while
|
// Invoking this function with block node 7a would return block node 5 while
|
||||||
// invoking it with block node 7 would return itself since it is already part of
|
// invoking it with block node 7 would return itself since it is already part of
|
||||||
|
|
|
@ -185,14 +185,14 @@ func isNonstandardTransaction(tx *btcutil.Tx) bool {
|
||||||
// checkpoint candidate.
|
// checkpoint candidate.
|
||||||
//
|
//
|
||||||
// The factors used to determine a good checkpoint are:
|
// The factors used to determine a good checkpoint are:
|
||||||
// - The block must be in the main chain
|
// - The block must be in the main chain
|
||||||
// - The block must be at least 'CheckpointConfirmations' blocks prior to the
|
// - The block must be at least 'CheckpointConfirmations' blocks prior to the
|
||||||
// current end of the main chain
|
// current end of the main chain
|
||||||
// - The timestamps for the blocks before and after the checkpoint must have
|
// - The timestamps for the blocks before and after the checkpoint must have
|
||||||
// timestamps which are also before and after the checkpoint, respectively
|
// timestamps which are also before and after the checkpoint, respectively
|
||||||
// (due to the median time allowance this is not always the case)
|
// (due to the median time allowance this is not always the case)
|
||||||
// - The block must not contain any strange transaction such as those with
|
// - The block must not contain any strange transaction such as those with
|
||||||
// nonstandard scripts
|
// nonstandard scripts
|
||||||
//
|
//
|
||||||
// The intent is that candidates are reviewed by a developer to make the final
|
// The intent is that candidates are reviewed by a developer to make the final
|
||||||
// decision and then manually added to the list of checkpoints for a network.
|
// decision and then manually added to the list of checkpoints for a network.
|
||||||
|
|
|
@ -42,18 +42,21 @@ func HashToBig(hash *chainhash.Hash) *big.Int {
|
||||||
// Like IEEE754 floating point, there are three basic components: the sign,
|
// Like IEEE754 floating point, there are three basic components: the sign,
|
||||||
// the exponent, and the mantissa. They are broken out as follows:
|
// the exponent, and the mantissa. They are broken out as follows:
|
||||||
//
|
//
|
||||||
// * the most significant 8 bits represent the unsigned base 256 exponent
|
// - the most significant 8 bits represent the unsigned base 256 exponent
|
||||||
// * bit 23 (the 24th bit) represents the sign bit
|
|
||||||
// * the least significant 23 bits represent the mantissa
|
|
||||||
//
|
//
|
||||||
// -------------------------------------------------
|
// - bit 23 (the 24th bit) represents the sign bit
|
||||||
// | Exponent | Sign | Mantissa |
|
//
|
||||||
// -------------------------------------------------
|
// - the least significant 23 bits represent the mantissa
|
||||||
// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] |
|
//
|
||||||
// -------------------------------------------------
|
// -------------------------------------------------
|
||||||
|
// | Exponent | Sign | Mantissa |
|
||||||
|
// -------------------------------------------------
|
||||||
|
// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] |
|
||||||
|
// -------------------------------------------------
|
||||||
//
|
//
|
||||||
// The formula to calculate N is:
|
// The formula to calculate N is:
|
||||||
// N = (-1^sign) * mantissa * 256^(exponent-3)
|
//
|
||||||
|
// N = (-1^sign) * mantissa * 256^(exponent-3)
|
||||||
//
|
//
|
||||||
// This compact form is only used in bitcoin to encode unsigned 256-bit numbers
|
// This compact form is only used in bitcoin to encode unsigned 256-bit numbers
|
||||||
// which represent difficulty targets, thus there really is not a need for a
|
// which represent difficulty targets, thus there really is not a need for a
|
||||||
|
|
|
@ -26,42 +26,42 @@ caller a high level of flexibility in how they want to react to certain events
|
||||||
such as orphan blocks which need their parents requested and newly connected
|
such as orphan blocks which need their parents requested and newly connected
|
||||||
main chain blocks which might result in wallet updates.
|
main chain blocks which might result in wallet updates.
|
||||||
|
|
||||||
Bitcoin Chain Processing Overview
|
# Bitcoin Chain Processing Overview
|
||||||
|
|
||||||
Before a block is allowed into the block chain, it must go through an intensive
|
Before a block is allowed into the block chain, it must go through an intensive
|
||||||
series of validation rules. The following list serves as a general outline of
|
series of validation rules. The following list serves as a general outline of
|
||||||
those rules to provide some intuition into what is going on under the hood, but
|
those rules to provide some intuition into what is going on under the hood, but
|
||||||
is by no means exhaustive:
|
is by no means exhaustive:
|
||||||
|
|
||||||
- Reject duplicate blocks
|
- Reject duplicate blocks
|
||||||
- Perform a series of sanity checks on the block and its transactions such as
|
- Perform a series of sanity checks on the block and its transactions such as
|
||||||
verifying proof of work, timestamps, number and character of transactions,
|
verifying proof of work, timestamps, number and character of transactions,
|
||||||
transaction amounts, script complexity, and merkle root calculations
|
transaction amounts, script complexity, and merkle root calculations
|
||||||
- Compare the block against predetermined checkpoints for expected timestamps
|
- Compare the block against predetermined checkpoints for expected timestamps
|
||||||
and difficulty based on elapsed time since the checkpoint
|
and difficulty based on elapsed time since the checkpoint
|
||||||
- Save the most recent orphan blocks for a limited time in case their parent
|
- Save the most recent orphan blocks for a limited time in case their parent
|
||||||
blocks become available
|
blocks become available
|
||||||
- Stop processing if the block is an orphan as the rest of the processing
|
- Stop processing if the block is an orphan as the rest of the processing
|
||||||
depends on the block's position within the block chain
|
depends on the block's position within the block chain
|
||||||
- Perform a series of more thorough checks that depend on the block's position
|
- Perform a series of more thorough checks that depend on the block's position
|
||||||
within the block chain such as verifying block difficulties adhere to
|
within the block chain such as verifying block difficulties adhere to
|
||||||
difficulty retarget rules, timestamps are after the median of the last
|
difficulty retarget rules, timestamps are after the median of the last
|
||||||
several blocks, all transactions are finalized, checkpoint blocks match, and
|
several blocks, all transactions are finalized, checkpoint blocks match, and
|
||||||
block versions are in line with the previous blocks
|
block versions are in line with the previous blocks
|
||||||
- Determine how the block fits into the chain and perform different actions
|
- Determine how the block fits into the chain and perform different actions
|
||||||
accordingly in order to ensure any side chains which have higher difficulty
|
accordingly in order to ensure any side chains which have higher difficulty
|
||||||
than the main chain become the new main chain
|
than the main chain become the new main chain
|
||||||
- When a block is being connected to the main chain (either through
|
- When a block is being connected to the main chain (either through
|
||||||
reorganization of a side chain to the main chain or just extending the
|
reorganization of a side chain to the main chain or just extending the
|
||||||
main chain), perform further checks on the block's transactions such as
|
main chain), perform further checks on the block's transactions such as
|
||||||
verifying transaction duplicates, script complexity for the combination of
|
verifying transaction duplicates, script complexity for the combination of
|
||||||
connected scripts, coinbase maturity, double spends, and connected
|
connected scripts, coinbase maturity, double spends, and connected
|
||||||
transaction values
|
transaction values
|
||||||
- Run the transaction scripts to verify the spender is allowed to spend the
|
- Run the transaction scripts to verify the spender is allowed to spend the
|
||||||
coins
|
coins
|
||||||
- Insert the block into the block database
|
- Insert the block into the block database
|
||||||
|
|
||||||
Errors
|
# Errors
|
||||||
|
|
||||||
Errors returned by this package are either the raw errors provided by underlying
|
Errors returned by this package are either the raw errors provided by underlying
|
||||||
calls or of type blockchain.RuleError. This allows the caller to differentiate
|
calls or of type blockchain.RuleError. This allows the caller to differentiate
|
||||||
|
@ -70,12 +70,12 @@ violations through type assertions. In addition, callers can programmatically
|
||||||
determine the specific rule violation by examining the ErrorCode field of the
|
determine the specific rule violation by examining the ErrorCode field of the
|
||||||
type asserted blockchain.RuleError.
|
type asserted blockchain.RuleError.
|
||||||
|
|
||||||
Bitcoin Improvement Proposals
|
# Bitcoin Improvement Proposals
|
||||||
|
|
||||||
This package includes spec changes outlined by the following BIPs:
|
This package includes spec changes outlined by the following BIPs:
|
||||||
|
|
||||||
BIP0016 (https://en.bitcoin.it/wiki/BIP_0016)
|
BIP0016 (https://en.bitcoin.it/wiki/BIP_0016)
|
||||||
BIP0030 (https://en.bitcoin.it/wiki/BIP_0030)
|
BIP0030 (https://en.bitcoin.it/wiki/BIP_0030)
|
||||||
BIP0034 (https://en.bitcoin.it/wiki/BIP_0034)
|
BIP0034 (https://en.bitcoin.it/wiki/BIP_0034)
|
||||||
*/
|
*/
|
||||||
package blockchain
|
package blockchain
|
||||||
|
|
|
@ -464,9 +464,9 @@ func createSpendTxForTx(tx *wire.MsgTx, fee btcutil.Amount) *wire.MsgTx {
|
||||||
// - A coinbase that pays the required subsidy to an OP_TRUE script
|
// - A coinbase that pays the required subsidy to an OP_TRUE script
|
||||||
// - When a spendable output is provided:
|
// - When a spendable output is provided:
|
||||||
// - A transaction that spends from the provided output the following outputs:
|
// - A transaction that spends from the provided output the following outputs:
|
||||||
// - One that pays the inputs amount minus 1 atom to an OP_TRUE script
|
// - One that pays the inputs amount minus 1 atom to an OP_TRUE script
|
||||||
// - One that contains an OP_RETURN output with a random uint64 in order to
|
// - One that contains an OP_RETURN output with a random uint64 in order to
|
||||||
// ensure the transaction has a unique hash
|
// ensure the transaction has a unique hash
|
||||||
//
|
//
|
||||||
// Additionally, if one or more munge functions are specified, they will be
|
// Additionally, if one or more munge functions are specified, they will be
|
||||||
// invoked with the block prior to solving it. This provides callers with the
|
// invoked with the block prior to solving it. This provides callers with the
|
||||||
|
|
|
@ -27,8 +27,9 @@ type blockProgressLogger struct {
|
||||||
|
|
||||||
// newBlockProgressLogger returns a new block progress logger.
|
// newBlockProgressLogger returns a new block progress logger.
|
||||||
// The progress message is templated as follows:
|
// The progress message is templated as follows:
|
||||||
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
//
|
||||||
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
||||||
|
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
||||||
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
|
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
|
||||||
return &blockProgressLogger{
|
return &blockProgressLogger{
|
||||||
lastBlockLogTime: time.Now(),
|
lastBlockLogTime: time.Now(),
|
||||||
|
|
|
@ -11,6 +11,7 @@ import (
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||||
"github.com/lbryio/lbcd/txscript"
|
"github.com/lbryio/lbcd/txscript"
|
||||||
|
"github.com/lbryio/lbcd/wire"
|
||||||
btcutil "github.com/lbryio/lbcutil"
|
btcutil "github.com/lbryio/lbcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -86,7 +87,7 @@ func HashMerkleBranches(left *chainhash.Hash, right *chainhash.Hash) *chainhash.
|
||||||
//
|
//
|
||||||
// The above stored as a linear array is as follows:
|
// The above stored as a linear array is as follows:
|
||||||
//
|
//
|
||||||
// [h1 h2 h3 h4 h12 h34 root]
|
// [h1 h2 h3 h4 h12 h34 root]
|
||||||
//
|
//
|
||||||
// As the above shows, the merkle root is always the last element in the array.
|
// As the above shows, the merkle root is always the last element in the array.
|
||||||
//
|
//
|
||||||
|
@ -227,6 +228,20 @@ func ValidateWitnessCommitment(blk *btcutil.Block) error {
|
||||||
// coinbase transaction MUST have exactly one witness element within
|
// coinbase transaction MUST have exactly one witness element within
|
||||||
// its witness data and that element must be exactly
|
// its witness data and that element must be exactly
|
||||||
// CoinbaseWitnessDataLen bytes.
|
// CoinbaseWitnessDataLen bytes.
|
||||||
|
//
|
||||||
|
// Some popular pool software, for example yiimp, uses pre-BIP0141
|
||||||
|
// coinbase struture. In this case, we don't just accept it, but also
|
||||||
|
// turn it into post-BIP0141 format.
|
||||||
|
if len(coinbaseTx.MsgTx().TxIn[0].Witness) == 0 {
|
||||||
|
log.Infof("pre-BIP0141 coinbase transaction detected. Height: %d", blk.Height())
|
||||||
|
|
||||||
|
var witnessNonce [CoinbaseWitnessDataLen]byte
|
||||||
|
coinbaseTx.MsgTx().TxIn[0].Witness = wire.TxWitness{witnessNonce[:]}
|
||||||
|
blk.MsgBlock().Transactions[0].TxIn[0].Witness = wire.TxWitness{witnessNonce[:]}
|
||||||
|
|
||||||
|
// Clear cached serialized block.
|
||||||
|
blk.SetBytes(nil)
|
||||||
|
}
|
||||||
coinbaseWitness := coinbaseTx.MsgTx().TxIn[0].Witness
|
coinbaseWitness := coinbaseTx.MsgTx().TxIn[0].Witness
|
||||||
if len(coinbaseWitness) != 1 {
|
if len(coinbaseWitness) != 1 {
|
||||||
str := fmt.Sprintf("the coinbase transaction has %d items in "+
|
str := fmt.Sprintf("the coinbase transaction has %d items in "+
|
||||||
|
|
|
@ -50,9 +50,9 @@ func (n NotificationType) String() string {
|
||||||
// Notification defines notification that is sent to the caller via the callback
|
// Notification defines notification that is sent to the caller via the callback
|
||||||
// function provided during the call to New and consists of a notification type
|
// function provided during the call to New and consists of a notification type
|
||||||
// as well as associated data that depends on the type as follows:
|
// as well as associated data that depends on the type as follows:
|
||||||
// - NTBlockAccepted: *btcutil.Block
|
// - NTBlockAccepted: *btcutil.Block
|
||||||
// - NTBlockConnected: *btcutil.Block
|
// - NTBlockConnected: *btcutil.Block
|
||||||
// - NTBlockDisconnected: *btcutil.Block
|
// - NTBlockDisconnected: *btcutil.Block
|
||||||
type Notification struct {
|
type Notification struct {
|
||||||
Type NotificationType
|
Type NotificationType
|
||||||
Data interface{}
|
Data interface{}
|
||||||
|
|
|
@ -232,24 +232,25 @@ func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, t
|
||||||
//
|
//
|
||||||
// The legacy format is as follows:
|
// The legacy format is as follows:
|
||||||
//
|
//
|
||||||
// <version><height><header code><unspentness bitmap>[<compressed txouts>,...]
|
// <version><height><header code><unspentness bitmap>[<compressed txouts>,...]
|
||||||
//
|
//
|
||||||
// Field Type Size
|
// Field Type Size
|
||||||
// version VLQ variable
|
// version VLQ variable
|
||||||
// block height VLQ variable
|
// block height VLQ variable
|
||||||
// header code VLQ variable
|
// header code VLQ variable
|
||||||
// unspentness bitmap []byte variable
|
// unspentness bitmap []byte variable
|
||||||
// compressed txouts
|
// compressed txouts
|
||||||
// compressed amount VLQ variable
|
// compressed amount VLQ variable
|
||||||
// compressed script []byte variable
|
// compressed script []byte variable
|
||||||
//
|
//
|
||||||
// The serialized header code format is:
|
// The serialized header code format is:
|
||||||
// bit 0 - containing transaction is a coinbase
|
//
|
||||||
// bit 1 - output zero is unspent
|
// bit 0 - containing transaction is a coinbase
|
||||||
// bit 2 - output one is unspent
|
// bit 1 - output zero is unspent
|
||||||
// bits 3-x - number of bytes in unspentness bitmap. When both bits 1 and 2
|
// bit 2 - output one is unspent
|
||||||
// are unset, it encodes N-1 since there must be at least one unspent
|
// bits 3-x - number of bytes in unspentness bitmap. When both bits 1 and 2
|
||||||
// output.
|
// are unset, it encodes N-1 since there must be at least one unspent
|
||||||
|
// output.
|
||||||
//
|
//
|
||||||
// The rationale for the header code scheme is as follows:
|
// The rationale for the header code scheme is as follows:
|
||||||
// - Transactions which only pay to a single output and a change output are
|
// - Transactions which only pay to a single output and a change output are
|
||||||
|
@ -269,65 +270,65 @@ func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, t
|
||||||
// From tx in main blockchain:
|
// From tx in main blockchain:
|
||||||
// Blk 1, 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
// Blk 1, 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
||||||
//
|
//
|
||||||
// 010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52
|
// 010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52
|
||||||
// <><><><------------------------------------------------------------------>
|
// <><><><------------------------------------------------------------------>
|
||||||
// | | \--------\ |
|
// | | \--------\ |
|
||||||
// | height | compressed txout 0
|
// | height | compressed txout 0
|
||||||
// version header code
|
// version header code
|
||||||
//
|
//
|
||||||
// - version: 1
|
// - version: 1
|
||||||
// - height: 1
|
// - height: 1
|
||||||
// - header code: 0x03 (coinbase, output zero unspent, 0 bytes of unspentness)
|
// - header code: 0x03 (coinbase, output zero unspent, 0 bytes of unspentness)
|
||||||
// - unspentness: Nothing since it is zero bytes
|
// - unspentness: Nothing since it is zero bytes
|
||||||
// - compressed txout 0:
|
// - compressed txout 0:
|
||||||
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 BTC)
|
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 BTC)
|
||||||
// - 0x04: special script type pay-to-pubkey
|
// - 0x04: special script type pay-to-pubkey
|
||||||
// - 0x96...52: x-coordinate of the pubkey
|
// - 0x96...52: x-coordinate of the pubkey
|
||||||
//
|
//
|
||||||
// Example 2:
|
// Example 2:
|
||||||
// From tx in main blockchain:
|
// From tx in main blockchain:
|
||||||
// Blk 113931, 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
// Blk 113931, 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||||
//
|
//
|
||||||
// 0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58
|
// 0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58
|
||||||
// <><----><><><------------------------------------------><-------------------------------------------->
|
// <><----><><><------------------------------------------><-------------------------------------------->
|
||||||
// | | | \-------------------\ | |
|
// | | | \-------------------\ | |
|
||||||
// version | \--------\ unspentness | compressed txout 2
|
// version | \--------\ unspentness | compressed txout 2
|
||||||
// height header code compressed txout 0
|
// height header code compressed txout 0
|
||||||
//
|
//
|
||||||
// - version: 1
|
// - version: 1
|
||||||
// - height: 113931
|
// - height: 113931
|
||||||
// - header code: 0x0a (output zero unspent, 1 byte in unspentness bitmap)
|
// - header code: 0x0a (output zero unspent, 1 byte in unspentness bitmap)
|
||||||
// - unspentness: [0x01] (bit 0 is set, so output 0+2 = 2 is unspent)
|
// - unspentness: [0x01] (bit 0 is set, so output 0+2 = 2 is unspent)
|
||||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||||
// - compressed txout 0:
|
// - compressed txout 0:
|
||||||
// - 0x12: VLQ-encoded compressed amount for 20000000 (0.2 BTC)
|
// - 0x12: VLQ-encoded compressed amount for 20000000 (0.2 BTC)
|
||||||
// - 0x00: special script type pay-to-pubkey-hash
|
// - 0x00: special script type pay-to-pubkey-hash
|
||||||
// - 0xe2...8a: pubkey hash
|
// - 0xe2...8a: pubkey hash
|
||||||
// - compressed txout 2:
|
// - compressed txout 2:
|
||||||
// - 0x8009: VLQ-encoded compressed amount for 15000000 (0.15 BTC)
|
// - 0x8009: VLQ-encoded compressed amount for 15000000 (0.15 BTC)
|
||||||
// - 0x00: special script type pay-to-pubkey-hash
|
// - 0x00: special script type pay-to-pubkey-hash
|
||||||
// - 0xb8...58: pubkey hash
|
// - 0xb8...58: pubkey hash
|
||||||
//
|
//
|
||||||
// Example 3:
|
// Example 3:
|
||||||
// From tx in main blockchain:
|
// From tx in main blockchain:
|
||||||
// Blk 338156, 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620
|
// Blk 338156, 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620
|
||||||
//
|
//
|
||||||
// 0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6
|
// 0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6
|
||||||
// <><----><><----><-------------------------------------------------->
|
// <><----><><----><-------------------------------------------------->
|
||||||
// | | | \-----------------\ |
|
// | | | \-----------------\ |
|
||||||
// version | \--------\ unspentness |
|
// version | \--------\ unspentness |
|
||||||
// height header code compressed txout 22
|
// height header code compressed txout 22
|
||||||
//
|
//
|
||||||
// - version: 1
|
// - version: 1
|
||||||
// - height: 338156
|
// - height: 338156
|
||||||
// - header code: 0x10 (2+1 = 3 bytes in unspentness bitmap)
|
// - header code: 0x10 (2+1 = 3 bytes in unspentness bitmap)
|
||||||
// NOTE: It's +1 since neither bit 1 nor 2 are set, so N-1 is encoded.
|
// NOTE: It's +1 since neither bit 1 nor 2 are set, so N-1 is encoded.
|
||||||
// - unspentness: [0x00 0x00 0x10] (bit 20 is set, so output 20+2 = 22 is unspent)
|
// - unspentness: [0x00 0x00 0x10] (bit 20 is set, so output 20+2 = 22 is unspent)
|
||||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||||
// - compressed txout 22:
|
// - compressed txout 22:
|
||||||
// - 0x8ba5b9e763: VLQ-encoded compressed amount for 366875659 (3.66875659 BTC)
|
// - 0x8ba5b9e763: VLQ-encoded compressed amount for 366875659 (3.66875659 BTC)
|
||||||
// - 0x01: special script type pay-to-script-hash
|
// - 0x01: special script type pay-to-script-hash
|
||||||
// - 0x1d...e6: script hash
|
// - 0x1d...e6: script hash
|
||||||
func deserializeUtxoEntryV0(serialized []byte) (map[uint32]*UtxoEntry, error) {
|
func deserializeUtxoEntryV0(serialized []byte) (map[uint32]*UtxoEntry, error) {
|
||||||
// Deserialize the version.
|
// Deserialize the version.
|
||||||
//
|
//
|
||||||
|
|
|
@ -334,8 +334,8 @@ func CheckTransactionSanity(tx *btcutil.Tx, enforceSoftFork bool) error {
|
||||||
// target difficulty as claimed.
|
// target difficulty as claimed.
|
||||||
//
|
//
|
||||||
// The flags modify the behavior of this function as follows:
|
// The flags modify the behavior of this function as follows:
|
||||||
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
|
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
|
||||||
// difficulty is not performed.
|
// difficulty is not performed.
|
||||||
func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error {
|
func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error {
|
||||||
// The target difficulty must be larger than zero.
|
// The target difficulty must be larger than zero.
|
||||||
target := CompactToBig(header.Bits)
|
target := CompactToBig(header.Bits)
|
||||||
|
@ -669,8 +669,8 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error {
|
||||||
// which depend on its position within the block chain.
|
// which depend on its position within the block chain.
|
||||||
//
|
//
|
||||||
// The flags modify the behavior of this function as follows:
|
// The flags modify the behavior of this function as follows:
|
||||||
// - BFFastAdd: All checks except those involving comparing the header against
|
// - BFFastAdd: All checks except those involving comparing the header against
|
||||||
// the checkpoints are not performed.
|
// the checkpoints are not performed.
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for writes).
|
// This function MUST be called with the chain state lock held (for writes).
|
||||||
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error {
|
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error {
|
||||||
|
@ -748,8 +748,8 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
|
||||||
// on its position within the block chain.
|
// on its position within the block chain.
|
||||||
//
|
//
|
||||||
// The flags modify the behavior of this function as follows:
|
// The flags modify the behavior of this function as follows:
|
||||||
// - BFFastAdd: The transaction are not checked to see if they are finalized
|
// - BFFastAdd: The transaction are not checked to see if they are finalized
|
||||||
// and the somewhat expensive BIP0034 validation is not performed.
|
// and the somewhat expensive BIP0034 validation is not performed.
|
||||||
//
|
//
|
||||||
// The flags are also passed to checkBlockHeaderContext. See its documentation
|
// The flags are also passed to checkBlockHeaderContext. See its documentation
|
||||||
// for how the flags modify its behavior.
|
// for how the flags modify its behavior.
|
||||||
|
|
|
@ -527,7 +527,7 @@ type baseMultTest struct {
|
||||||
x, y string
|
x, y string
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: add more test vectors
|
// TODO: add more test vectors
|
||||||
var s256BaseMultTests = []baseMultTest{
|
var s256BaseMultTests = []baseMultTest{
|
||||||
{
|
{
|
||||||
"AA5E28D6A97A2479A65527F7290311A3624D4CC0FA1578598EE3C2613BF99522",
|
"AA5E28D6A97A2479A65527F7290311A3624D4CC0FA1578598EE3C2613BF99522",
|
||||||
|
@ -556,7 +556,7 @@ var s256BaseMultTests = []baseMultTest{
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: test different curves as well?
|
// TODO: test different curves as well?
|
||||||
func TestBaseMult(t *testing.T) {
|
func TestBaseMult(t *testing.T) {
|
||||||
s256 := S256()
|
s256 := S256()
|
||||||
for i, e := range s256BaseMultTests {
|
for i, e := range s256BaseMultTests {
|
||||||
|
|
|
@ -125,27 +125,30 @@ var (
|
||||||
// the arithmetic needed for elliptic curve operations.
|
// the arithmetic needed for elliptic curve operations.
|
||||||
//
|
//
|
||||||
// The following depicts the internal representation:
|
// The following depicts the internal representation:
|
||||||
// -----------------------------------------------------------------
|
//
|
||||||
// | n[9] | n[8] | ... | n[0] |
|
// -----------------------------------------------------------------
|
||||||
// | 32 bits available | 32 bits available | ... | 32 bits available |
|
// | n[9] | n[8] | ... | n[0] |
|
||||||
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
|
// | 32 bits available | 32 bits available | ... | 32 bits available |
|
||||||
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
|
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
|
||||||
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
|
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
|
||||||
// -----------------------------------------------------------------
|
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
|
||||||
|
// -----------------------------------------------------------------
|
||||||
//
|
//
|
||||||
// For example, consider the number 2^49 + 1. It would be represented as:
|
// For example, consider the number 2^49 + 1. It would be represented as:
|
||||||
// n[0] = 1
|
//
|
||||||
// n[1] = 2^23
|
// n[0] = 1
|
||||||
// n[2..9] = 0
|
// n[1] = 2^23
|
||||||
|
// n[2..9] = 0
|
||||||
//
|
//
|
||||||
// The full 256-bit value is then calculated by looping i from 9..0 and
|
// The full 256-bit value is then calculated by looping i from 9..0 and
|
||||||
// doing sum(n[i] * 2^(26i)) like so:
|
// doing sum(n[i] * 2^(26i)) like so:
|
||||||
// n[9] * 2^(26*9) = 0 * 2^234 = 0
|
//
|
||||||
// n[8] * 2^(26*8) = 0 * 2^208 = 0
|
// n[9] * 2^(26*9) = 0 * 2^234 = 0
|
||||||
// ...
|
// n[8] * 2^(26*8) = 0 * 2^208 = 0
|
||||||
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
|
// ...
|
||||||
// n[0] * 2^(26*0) = 1 * 2^0 = 1
|
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
|
||||||
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
|
// n[0] * 2^(26*0) = 1 * 2^0 = 1
|
||||||
|
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
|
||||||
type fieldVal struct {
|
type fieldVal struct {
|
||||||
n [10]uint32
|
n [10]uint32
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,15 @@ func NewAddNodeCmd(addr string, subCmd AddNodeSubCmd) *AddNodeCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClearBannedCmd defines the clearbanned JSON-RPC command.
|
||||||
|
type ClearBannedCmd struct{}
|
||||||
|
|
||||||
|
// NewClearBannedCmd returns a new instance which can be used to issue an clearbanned
|
||||||
|
// JSON-RPC command.
|
||||||
|
func NewClearBannedCmd() *ClearBannedCmd {
|
||||||
|
return &ClearBannedCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
// TransactionInput represents the inputs to a transaction. Specifically a
|
// TransactionInput represents the inputs to a transaction. Specifically a
|
||||||
// transaction hash and output number pair.
|
// transaction hash and output number pair.
|
||||||
type TransactionInput struct {
|
type TransactionInput struct {
|
||||||
|
@ -58,7 +67,7 @@ type TransactionInput struct {
|
||||||
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
||||||
type CreateRawTransactionCmd struct {
|
type CreateRawTransactionCmd struct {
|
||||||
Inputs []TransactionInput
|
Inputs []TransactionInput
|
||||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
Outputs map[string]interface{} `jsonrpcusage:"{\"address\":amount, \"data\":\"hex\", ...}"`
|
||||||
LockTime *int64
|
LockTime *int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -67,7 +76,7 @@ type CreateRawTransactionCmd struct {
|
||||||
//
|
//
|
||||||
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
||||||
// both gets interpreted as the empty slice.
|
// both gets interpreted as the empty slice.
|
||||||
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
|
func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]interface{},
|
||||||
lockTime *int64) *CreateRawTransactionCmd {
|
lockTime *int64) *CreateRawTransactionCmd {
|
||||||
// to make sure we're serializing this to the empty list and not null, we
|
// to make sure we're serializing this to the empty list and not null, we
|
||||||
// explicitly initialize the list
|
// explicitly initialize the list
|
||||||
|
@ -76,7 +85,7 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]fl
|
||||||
}
|
}
|
||||||
return &CreateRawTransactionCmd{
|
return &CreateRawTransactionCmd{
|
||||||
Inputs: inputs,
|
Inputs: inputs,
|
||||||
Amounts: amounts,
|
Outputs: outputs,
|
||||||
LockTime: lockTime,
|
LockTime: lockTime,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -757,6 +766,15 @@ func NewInvalidateBlockCmd(blockHash string) *InvalidateBlockCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListBannedCmd defines the listbanned JSON-RPC command.
|
||||||
|
type ListBannedCmd struct{}
|
||||||
|
|
||||||
|
// NewListBannedCmd returns a new instance which can be used to issue a listbanned
|
||||||
|
// JSON-RPC command.
|
||||||
|
func NewListBannedCmd() *ListBannedCmd {
|
||||||
|
return &ListBannedCmd{}
|
||||||
|
}
|
||||||
|
|
||||||
// PingCmd defines the ping JSON-RPC command.
|
// PingCmd defines the ping JSON-RPC command.
|
||||||
type PingCmd struct{}
|
type PingCmd struct{}
|
||||||
|
|
||||||
|
@ -903,6 +921,39 @@ func NewBitcoindSendRawTransactionCmd(hexTx string, maxFeeRate int32) *SendRawTr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetBanSubCmd defines the type used in the setban JSON-RPC command for the
|
||||||
|
// sub command field.
|
||||||
|
type SetBanSubCmd string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// SBAdd indicates the specified host should be added as a persistent
|
||||||
|
// peer.
|
||||||
|
SBAdd SetBanSubCmd = "add"
|
||||||
|
|
||||||
|
// SBRemove indicates the specified peer should be removed.
|
||||||
|
SBRemove SetBanSubCmd = "remove"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SetBanCmd defines the setban JSON-RPC command.
|
||||||
|
type SetBanCmd struct {
|
||||||
|
Addr string
|
||||||
|
SubCmd SetBanSubCmd `jsonrpcusage:"\"add|remove\""`
|
||||||
|
BanTime *int `jsonrpcdefault:"0"`
|
||||||
|
Absolute *bool `jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSetBanCmd returns a new instance which can be used to issue an setban
|
||||||
|
// JSON-RPC command.
|
||||||
|
func NewSetBanCmd(addr string, subCmd SetBanSubCmd, banTime *int,
|
||||||
|
absolute *bool) *SetBanCmd {
|
||||||
|
return &SetBanCmd{
|
||||||
|
Addr: addr,
|
||||||
|
SubCmd: subCmd,
|
||||||
|
BanTime: banTime,
|
||||||
|
Absolute: absolute,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// SetGenerateCmd defines the setgenerate JSON-RPC command.
|
// SetGenerateCmd defines the setgenerate JSON-RPC command.
|
||||||
type SetGenerateCmd struct {
|
type SetGenerateCmd struct {
|
||||||
Generate bool
|
Generate bool
|
||||||
|
@ -1080,6 +1131,9 @@ func init() {
|
||||||
MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags)
|
MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags)
|
||||||
MustRegisterCmd("getnodeaddresses", (*GetNodeAddressesCmd)(nil), flags)
|
MustRegisterCmd("getnodeaddresses", (*GetNodeAddressesCmd)(nil), flags)
|
||||||
MustRegisterCmd("getpeerinfo", (*GetPeerInfoCmd)(nil), flags)
|
MustRegisterCmd("getpeerinfo", (*GetPeerInfoCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("listbanned", (*ListBannedCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("setban", (*SetBanCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("clearbanned", (*ClearBannedCmd)(nil), flags)
|
||||||
MustRegisterCmd("getrawmempool", (*GetRawMempoolCmd)(nil), flags)
|
MustRegisterCmd("getrawmempool", (*GetRawMempoolCmd)(nil), flags)
|
||||||
MustRegisterCmd("getrawtransaction", (*GetRawTransactionCmd)(nil), flags)
|
MustRegisterCmd("getrawtransaction", (*GetRawTransactionCmd)(nil), flags)
|
||||||
MustRegisterCmd("gettxout", (*GetTxOutCmd)(nil), flags)
|
MustRegisterCmd("gettxout", (*GetTxOutCmd)(nil), flags)
|
||||||
|
|
|
@ -52,13 +52,13 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
txInputs := []btcjson.TransactionInput{
|
txInputs := []btcjson.TransactionInput{
|
||||||
{Txid: "123", Vout: 1},
|
{Txid: "123", Vout: 1},
|
||||||
}
|
}
|
||||||
amounts := map[string]float64{"456": .0123}
|
txOutputs := map[string]interface{}{"456": .0123}
|
||||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, nil)
|
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
||||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
Amounts: map[string]float64{"456": .0123},
|
Outputs: map[string]interface{}{"456": .0123},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -67,13 +67,13 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
|
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
amounts := map[string]float64{"456": .0123}
|
txOutputs := map[string]interface{}{"456": .0123}
|
||||||
return btcjson.NewCreateRawTransactionCmd(nil, amounts, nil)
|
return btcjson.NewCreateRawTransactionCmd(nil, txOutputs, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
|
||||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
Inputs: []btcjson.TransactionInput{},
|
Inputs: []btcjson.TransactionInput{},
|
||||||
Amounts: map[string]float64{"456": .0123},
|
Outputs: map[string]interface{}{"456": .0123},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -86,16 +86,35 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
txInputs := []btcjson.TransactionInput{
|
txInputs := []btcjson.TransactionInput{
|
||||||
{Txid: "123", Vout: 1},
|
{Txid: "123", Vout: 1},
|
||||||
}
|
}
|
||||||
amounts := map[string]float64{"456": .0123}
|
txOutputs := map[string]interface{}{"456": .0123}
|
||||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, btcjson.Int64(12312333333))
|
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, btcjson.Int64(12312333333))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
|
||||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
Amounts: map[string]float64{"456": .0123},
|
Outputs: map[string]interface{}{"456": .0123},
|
||||||
LockTime: btcjson.Int64(12312333333),
|
LockTime: btcjson.Int64(12312333333),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "createrawtransaction with data",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("createrawtransaction", `[{"txid":"123","vout":1}]`,
|
||||||
|
`{"data":"6a134920616d204672616374616c456e6372797074"}`)
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
txInputs := []btcjson.TransactionInput{
|
||||||
|
{Txid: "123", Vout: 1},
|
||||||
|
}
|
||||||
|
txOutputs := map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"}
|
||||||
|
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"data":"6a134920616d204672616374616c456e6372797074"}],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||||
|
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||||
|
Outputs: map[string]interface{}{"data": "6a134920616d204672616374616c456e6372797074"},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "fundrawtransaction - empty opts",
|
name: "fundrawtransaction - empty opts",
|
||||||
newCmd: func() (i interface{}, e error) {
|
newCmd: func() (i interface{}, e error) {
|
||||||
|
|
|
@ -35,35 +35,37 @@ type GetBlockHeaderVerboseResult struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBlockStatsResult models the data from the getblockstats command.
|
// GetBlockStatsResult models the data from the getblockstats command.
|
||||||
|
// Pointers are used instead of values to allow for optional fields.
|
||||||
type GetBlockStatsResult struct {
|
type GetBlockStatsResult struct {
|
||||||
AverageFee int64 `json:"avgfee"`
|
AverageFee *int64 `json:"avgfee,omitempty"`
|
||||||
AverageFeeRate int64 `json:"avgfeerate"`
|
AverageFeeRate *int64 `json:"avgfeerate,omitempty"`
|
||||||
AverageTxSize int64 `json:"avgtxsize"`
|
AverageTxSize *int64 `json:"avgtxsize,omitempty"`
|
||||||
FeeratePercentiles []int64 `json:"feerate_percentiles"`
|
FeeratePercentiles *[]int64 `json:"feerate_percentiles,omitempty"`
|
||||||
Hash string `json:"blockhash"`
|
Hash *string `json:"blockhash,omitempty"`
|
||||||
Height int64 `json:"height"`
|
Height *int64 `json:"height,omitempty"`
|
||||||
Ins int64 `json:"ins"`
|
Ins *int64 `json:"ins,omitempty"`
|
||||||
MaxFee int64 `json:"maxfee"`
|
MaxFee *int64 `json:"maxfee,omitempty"`
|
||||||
MaxFeeRate int64 `json:"maxfeerate"`
|
MaxFeeRate *int64 `json:"maxfeerate,omitempty"`
|
||||||
MaxTxSize int64 `json:"maxtxsize"`
|
MaxTxSize *int64 `json:"maxtxsize,omitempty"`
|
||||||
MedianFee int64 `json:"medianfee"`
|
MedianFee *int64 `json:"medianfee,omitempty"`
|
||||||
MedianTime int64 `json:"mediantime"`
|
MedianTime *int64 `json:"mediantime,omitempty"`
|
||||||
MedianTxSize int64 `json:"mediantxsize"`
|
MedianTxSize *int64 `json:"mediantxsize,omitempty"`
|
||||||
MinFee int64 `json:"minfee"`
|
MinFee *int64 `json:"minfee,omitempty"`
|
||||||
MinFeeRate int64 `json:"minfeerate"`
|
MinFeeRate *int64 `json:"minfeerate,omitempty"`
|
||||||
MinTxSize int64 `json:"mintxsize"`
|
MinTxSize *int64 `json:"mintxsize,omitempty"`
|
||||||
Outs int64 `json:"outs"`
|
Outs *int64 `json:"outs,omitempty"`
|
||||||
SegWitTotalSize int64 `json:"swtotal_size"`
|
SegWitTotalSize *int64 `json:"swtotal_size,omitempty"`
|
||||||
SegWitTotalWeight int64 `json:"swtotal_weight"`
|
SegWitTotalWeight *int64 `json:"swtotal_weight,omitempty"`
|
||||||
SegWitTxs int64 `json:"swtxs"`
|
SegWitTxs *int64 `json:"swtxs,omitempty"`
|
||||||
Subsidy int64 `json:"subsidy"`
|
Subsidy *int64 `json:"subsidy,omitempty"`
|
||||||
Time int64 `json:"time"`
|
Time *int64 `json:"time,omitempty"`
|
||||||
TotalOut int64 `json:"total_out"`
|
TotalOut *int64 `json:"total_out,omitempty"`
|
||||||
TotalSize int64 `json:"total_size"`
|
TotalSize *int64 `json:"total_size,omitempty"`
|
||||||
TotalWeight int64 `json:"total_weight"`
|
TotalWeight *int64 `json:"total_weight,omitempty"`
|
||||||
Txs int64 `json:"txs"`
|
TotalFee *int64 `json:"totalfee,omitempty"`
|
||||||
UTXOIncrease int64 `json:"utxo_increase"`
|
Txs *int64 `json:"txs,omitempty"`
|
||||||
UTXOSizeIncrease int64 `json:"utxo_size_inc"`
|
UTXOIncrease *int64 `json:"utxo_increase,omitempty"`
|
||||||
|
UTXOSizeIncrease *int64 `json:"utxo_size_inc,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type GetBlockVerboseResultBase struct {
|
type GetBlockVerboseResultBase struct {
|
||||||
|
@ -77,9 +79,11 @@ type GetBlockVerboseResultBase struct {
|
||||||
VersionHex string `json:"versionHex"`
|
VersionHex string `json:"versionHex"`
|
||||||
MerkleRoot string `json:"merkleroot"`
|
MerkleRoot string `json:"merkleroot"`
|
||||||
Time int64 `json:"time"`
|
Time int64 `json:"time"`
|
||||||
|
MedianTime int64 `json:"mediantime"`
|
||||||
Nonce uint32 `json:"nonce"`
|
Nonce uint32 `json:"nonce"`
|
||||||
Bits string `json:"bits"`
|
Bits string `json:"bits"`
|
||||||
Difficulty float64 `json:"difficulty"`
|
Difficulty float64 `json:"difficulty"`
|
||||||
|
ChainWork string `json:"chainwork"`
|
||||||
PreviousHash string `json:"previousblockhash,omitempty"`
|
PreviousHash string `json:"previousblockhash,omitempty"`
|
||||||
NextHash string `json:"nextblockhash,omitempty"`
|
NextHash string `json:"nextblockhash,omitempty"`
|
||||||
|
|
||||||
|
@ -337,8 +341,13 @@ type GetChainTipsResult struct {
|
||||||
// GetMempoolInfoResult models the data returned from the getmempoolinfo
|
// GetMempoolInfoResult models the data returned from the getmempoolinfo
|
||||||
// command.
|
// command.
|
||||||
type GetMempoolInfoResult struct {
|
type GetMempoolInfoResult struct {
|
||||||
Size int64 `json:"size"`
|
Size int64 `json:"size"` // Current tx count
|
||||||
Bytes int64 `json:"bytes"`
|
Bytes int64 `json:"bytes"` // Sum of all virtual transaction sizes as defined in BIP 141. Differs from actual serialized size because witness data is discounted
|
||||||
|
Usage int64 `json:"usage"` // Total memory usage for the mempool
|
||||||
|
TotalFee float64 `json:"total_fee"` // Total fees for the mempool in LBC, ignoring modified fees through prioritizetransaction
|
||||||
|
MemPoolMinFee float64 `json:"mempoolminfee"` // Minimum fee rate in LBC/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee
|
||||||
|
MinRelayTxFee float64 `json:"minrelaytxfee"` // Current minimum relay fee for transactions
|
||||||
|
UnbroadcastCount int64 `json:"unbroadcastcount"` // Current number of transactions that haven't passed initial broadcast yet
|
||||||
}
|
}
|
||||||
|
|
||||||
// NetworksResult models the networks data from the getnetworkinfo command.
|
// NetworksResult models the networks data from the getnetworkinfo command.
|
||||||
|
@ -714,6 +723,15 @@ type InfoChainResult struct {
|
||||||
Errors string `json:"errors"`
|
Errors string `json:"errors"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ListBannedResult models the data returned from the listbanned command.
|
||||||
|
type ListBannedResult struct {
|
||||||
|
Address string `json:"address"`
|
||||||
|
BanCreated int64 `json:"ban_created"`
|
||||||
|
BannedUntil int64 `json:"banned_until"`
|
||||||
|
BanDuration int64 `json:"ban_duration"`
|
||||||
|
TimeRemaining int64 `json:"time_remaining"`
|
||||||
|
}
|
||||||
|
|
||||||
// TxRawResult models the data from the getrawtransaction command.
|
// TxRawResult models the data from the getrawtransaction command.
|
||||||
type TxRawResult struct {
|
type TxRawResult struct {
|
||||||
Hex string `json:"hex"`
|
Hex string `json:"hex"`
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
/*
|
/*
|
||||||
Package btcjson provides primitives for working with the bitcoin JSON-RPC API.
|
Package btcjson provides primitives for working with the bitcoin JSON-RPC API.
|
||||||
|
|
||||||
Overview
|
# Overview
|
||||||
|
|
||||||
When communicating via the JSON-RPC protocol, all of the commands need to be
|
When communicating via the JSON-RPC protocol, all of the commands need to be
|
||||||
marshalled to and from the the wire in the appropriate format. This package
|
marshalled to and from the the wire in the appropriate format. This package
|
||||||
|
@ -14,7 +14,7 @@ provides data structures and primitives to ease this process.
|
||||||
In addition, it also provides some additional features such as custom command
|
In addition, it also provides some additional features such as custom command
|
||||||
registration, command categorization, and reflection-based help generation.
|
registration, command categorization, and reflection-based help generation.
|
||||||
|
|
||||||
JSON-RPC Protocol Overview
|
# JSON-RPC Protocol Overview
|
||||||
|
|
||||||
This information is not necessary in order to use this package, but it does
|
This information is not necessary in order to use this package, but it does
|
||||||
provide some intuition into what the marshalling and unmarshalling that is
|
provide some intuition into what the marshalling and unmarshalling that is
|
||||||
|
@ -47,39 +47,39 @@ with it) doesn't always follow the spec and will sometimes return an error
|
||||||
string in the result field with a null error for certain commands. However,
|
string in the result field with a null error for certain commands. However,
|
||||||
for the most part, the error field will be set as described on failure.
|
for the most part, the error field will be set as described on failure.
|
||||||
|
|
||||||
Marshalling and Unmarshalling
|
# Marshalling and Unmarshalling
|
||||||
|
|
||||||
Based upon the discussion above, it should be easy to see how the types of this
|
Based upon the discussion above, it should be easy to see how the types of this
|
||||||
package map into the required parts of the protocol
|
package map into the required parts of the protocol
|
||||||
|
|
||||||
- Request Objects (type Request)
|
- Request Objects (type Request)
|
||||||
- Commands (type <Foo>Cmd)
|
- Commands (type <Foo>Cmd)
|
||||||
- Notifications (type <Foo>Ntfn)
|
- Notifications (type <Foo>Ntfn)
|
||||||
- Response Objects (type Response)
|
- Response Objects (type Response)
|
||||||
- Result (type <Foo>Result)
|
- Result (type <Foo>Result)
|
||||||
|
|
||||||
To simplify the marshalling of the requests and responses, the MarshalCmd and
|
To simplify the marshalling of the requests and responses, the MarshalCmd and
|
||||||
MarshalResponse functions are provided. They return the raw bytes ready to be
|
MarshalResponse functions are provided. They return the raw bytes ready to be
|
||||||
sent across the wire.
|
sent across the wire.
|
||||||
|
|
||||||
Unmarshalling a received Request object is a two step process:
|
Unmarshalling a received Request object is a two step process:
|
||||||
1) Unmarshal the raw bytes into a Request struct instance via json.Unmarshal
|
1. Unmarshal the raw bytes into a Request struct instance via json.Unmarshal
|
||||||
2) Use UnmarshalCmd on the Result field of the unmarshalled Request to create
|
2. Use UnmarshalCmd on the Result field of the unmarshalled Request to create
|
||||||
a concrete command or notification instance with all struct fields set
|
a concrete command or notification instance with all struct fields set
|
||||||
accordingly
|
accordingly
|
||||||
|
|
||||||
This approach is used since it provides the caller with access to the additional
|
This approach is used since it provides the caller with access to the additional
|
||||||
fields in the request that are not part of the command such as the ID.
|
fields in the request that are not part of the command such as the ID.
|
||||||
|
|
||||||
Unmarshalling a received Response object is also a two step process:
|
Unmarshalling a received Response object is also a two step process:
|
||||||
1) Unmarhsal the raw bytes into a Response struct instance via json.Unmarshal
|
1. Unmarhsal the raw bytes into a Response struct instance via json.Unmarshal
|
||||||
2) Depending on the ID, unmarshal the Result field of the unmarshalled
|
2. Depending on the ID, unmarshal the Result field of the unmarshalled
|
||||||
Response to create a concrete type instance
|
Response to create a concrete type instance
|
||||||
|
|
||||||
As above, this approach is used since it provides the caller with access to the
|
As above, this approach is used since it provides the caller with access to the
|
||||||
fields in the response such as the ID and Error.
|
fields in the response such as the ID and Error.
|
||||||
|
|
||||||
Command Creation
|
# Command Creation
|
||||||
|
|
||||||
This package provides two approaches for creating a new command. This first,
|
This package provides two approaches for creating a new command. This first,
|
||||||
and preferred, method is to use one of the New<Foo>Cmd functions. This allows
|
and preferred, method is to use one of the New<Foo>Cmd functions. This allows
|
||||||
|
@ -93,7 +93,7 @@ obviously, run-time which means any mistakes won't be found until the code is
|
||||||
actually executed. However, it is quite useful for user-supplied commands
|
actually executed. However, it is quite useful for user-supplied commands
|
||||||
that are intentionally dynamic.
|
that are intentionally dynamic.
|
||||||
|
|
||||||
Custom Command Registration
|
# Custom Command Registration
|
||||||
|
|
||||||
The command handling of this package is built around the concept of registered
|
The command handling of this package is built around the concept of registered
|
||||||
commands. This is true for the wide variety of commands already provided by the
|
commands. This is true for the wide variety of commands already provided by the
|
||||||
|
@ -104,7 +104,7 @@ function for this purpose.
|
||||||
A list of all registered methods can be obtained with the RegisteredCmdMethods
|
A list of all registered methods can be obtained with the RegisteredCmdMethods
|
||||||
function.
|
function.
|
||||||
|
|
||||||
Command Inspection
|
# Command Inspection
|
||||||
|
|
||||||
All registered commands are registered with flags that identify information such
|
All registered commands are registered with flags that identify information such
|
||||||
as whether the command applies to a chain server, wallet server, or is a
|
as whether the command applies to a chain server, wallet server, or is a
|
||||||
|
@ -112,7 +112,7 @@ notification along with the method name to use. These flags can be obtained
|
||||||
with the MethodUsageFlags flags, and the method can be obtained with the
|
with the MethodUsageFlags flags, and the method can be obtained with the
|
||||||
CmdMethod function.
|
CmdMethod function.
|
||||||
|
|
||||||
Help Generation
|
# Help Generation
|
||||||
|
|
||||||
To facilitate providing consistent help to users of the RPC server, this package
|
To facilitate providing consistent help to users of the RPC server, this package
|
||||||
exposes the GenerateHelp and function which uses reflection on registered
|
exposes the GenerateHelp and function which uses reflection on registered
|
||||||
|
@ -122,7 +122,7 @@ generate the final help text.
|
||||||
In addition, the MethodUsageText function is provided to generate consistent
|
In addition, the MethodUsageText function is provided to generate consistent
|
||||||
one-line usage for registered commands and notifications using reflection.
|
one-line usage for registered commands and notifications using reflection.
|
||||||
|
|
||||||
Errors
|
# Errors
|
||||||
|
|
||||||
There are 2 distinct type of errors supported by this package:
|
There are 2 distinct type of errors supported by this package:
|
||||||
|
|
||||||
|
|
|
@ -476,11 +476,12 @@ func isValidResultType(kind reflect.Kind) bool {
|
||||||
// an error will use the key in place of the description.
|
// an error will use the key in place of the description.
|
||||||
//
|
//
|
||||||
// The following outlines the required keys:
|
// The following outlines the required keys:
|
||||||
// "<method>--synopsis" Synopsis for the command
|
//
|
||||||
// "<method>-<lowerfieldname>" Description for each command argument
|
// "<method>--synopsis" Synopsis for the command
|
||||||
// "<typename>-<lowerfieldname>" Description for each object field
|
// "<method>-<lowerfieldname>" Description for each command argument
|
||||||
// "<method>--condition<#>" Description for each result condition
|
// "<typename>-<lowerfieldname>" Description for each object field
|
||||||
// "<method>--result<#>" Description for each primitive result num
|
// "<method>--condition<#>" Description for each result condition
|
||||||
|
// "<method>--result<#>" Description for each primitive result num
|
||||||
//
|
//
|
||||||
// Notice that the "special" keys synopsis, condition<#>, and result<#> are
|
// Notice that the "special" keys synopsis, condition<#>, and result<#> are
|
||||||
// preceded by a double dash to ensure they don't conflict with field names.
|
// preceded by a double dash to ensure they don't conflict with field names.
|
||||||
|
@ -492,16 +493,17 @@ func isValidResultType(kind reflect.Kind) bool {
|
||||||
// For example, consider the 'help' command itself. There are two possible
|
// For example, consider the 'help' command itself. There are two possible
|
||||||
// returns depending on the provided parameters. So, the help would be
|
// returns depending on the provided parameters. So, the help would be
|
||||||
// generated by calling the function as follows:
|
// generated by calling the function as follows:
|
||||||
// GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
|
//
|
||||||
|
// GenerateHelp("help", descs, (*string)(nil), (*string)(nil)).
|
||||||
//
|
//
|
||||||
// The following keys would then be required in the provided descriptions map:
|
// The following keys would then be required in the provided descriptions map:
|
||||||
//
|
//
|
||||||
// "help--synopsis": "Returns a list of all commands or help for ...."
|
// "help--synopsis": "Returns a list of all commands or help for ...."
|
||||||
// "help-command": "The command to retrieve help for",
|
// "help-command": "The command to retrieve help for",
|
||||||
// "help--condition0": "no command provided"
|
// "help--condition0": "no command provided"
|
||||||
// "help--condition1": "command specified"
|
// "help--condition1": "command specified"
|
||||||
// "help--result0": "List of commands"
|
// "help--result0": "List of commands"
|
||||||
// "help--result1": "Help for specified command"
|
// "help--result1": "Help for specified command"
|
||||||
func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
|
func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
|
||||||
// Look up details about the provided method and error out if not
|
// Look up details about the provided method and error out if not
|
||||||
// registered.
|
// registered.
|
||||||
|
|
|
@ -176,12 +176,13 @@ func NewGetAccountCmd(address string) *GetAccountCmd {
|
||||||
|
|
||||||
// GetAccountAddressCmd defines the getaccountaddress JSON-RPC command.
|
// GetAccountAddressCmd defines the getaccountaddress JSON-RPC command.
|
||||||
type GetAccountAddressCmd struct {
|
type GetAccountAddressCmd struct {
|
||||||
Account string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetAccountAddressCmd returns a new instance which can be used to issue a
|
// NewGetAccountAddressCmd returns a new instance which can be used to issue a
|
||||||
// getaccountaddress JSON-RPC command.
|
// getaccountaddress JSON-RPC command.
|
||||||
func NewGetAccountAddressCmd(account string) *GetAccountAddressCmd {
|
func NewGetAccountAddressCmd(account *string) *GetAccountAddressCmd {
|
||||||
return &GetAccountAddressCmd{
|
return &GetAccountAddressCmd{
|
||||||
Account: account,
|
Account: account,
|
||||||
}
|
}
|
||||||
|
@ -189,12 +190,13 @@ func NewGetAccountAddressCmd(account string) *GetAccountAddressCmd {
|
||||||
|
|
||||||
// GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command.
|
// GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command.
|
||||||
type GetAddressesByAccountCmd struct {
|
type GetAddressesByAccountCmd struct {
|
||||||
Account string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetAddressesByAccountCmd returns a new instance which can be used to issue
|
// NewGetAddressesByAccountCmd returns a new instance which can be used to issue
|
||||||
// a getaddressesbyaccount JSON-RPC command.
|
// a getaddressesbyaccount JSON-RPC command.
|
||||||
func NewGetAddressesByAccountCmd(account string) *GetAddressesByAccountCmd {
|
func NewGetAddressesByAccountCmd(account *string) *GetAddressesByAccountCmd {
|
||||||
return &GetAddressesByAccountCmd{
|
return &GetAddressesByAccountCmd{
|
||||||
Account: account,
|
Account: account,
|
||||||
}
|
}
|
||||||
|
@ -215,8 +217,9 @@ func NewGetAddressInfoCmd(address string) *GetAddressInfoCmd {
|
||||||
|
|
||||||
// GetBalanceCmd defines the getbalance JSON-RPC command.
|
// GetBalanceCmd defines the getbalance JSON-RPC command.
|
||||||
type GetBalanceCmd struct {
|
type GetBalanceCmd struct {
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
MinConf *int `jsonrpcdefault:"1"`
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetBalanceCmd returns a new instance which can be used to issue a
|
// NewGetBalanceCmd returns a new instance which can be used to issue a
|
||||||
|
@ -242,8 +245,8 @@ func NewGetBalancesCmd() *GetBalancesCmd {
|
||||||
|
|
||||||
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
|
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
|
||||||
type GetNewAddressCmd struct {
|
type GetNewAddressCmd struct {
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
AddressType *string // must be one of legacy / p2pkh or p2sh-p2wkh / p2sh-segwit, or p2wkh / bech32
|
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
||||||
|
@ -259,7 +262,8 @@ func NewGetNewAddressCmd(account *string) *GetNewAddressCmd {
|
||||||
|
|
||||||
// GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command.
|
// GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command.
|
||||||
type GetRawChangeAddressCmd struct {
|
type GetRawChangeAddressCmd struct {
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetRawChangeAddressCmd returns a new instance which can be used to issue a
|
// NewGetRawChangeAddressCmd returns a new instance which can be used to issue a
|
||||||
|
@ -275,8 +279,8 @@ func NewGetRawChangeAddressCmd(account *string) *GetRawChangeAddressCmd {
|
||||||
|
|
||||||
// GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command.
|
// GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command.
|
||||||
type GetReceivedByAccountCmd struct {
|
type GetReceivedByAccountCmd struct {
|
||||||
Account string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
MinConf *int `jsonrpcdefault:"1"`
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetReceivedByAccountCmd returns a new instance which can be used to issue
|
// NewGetReceivedByAccountCmd returns a new instance which can be used to issue
|
||||||
|
@ -284,7 +288,7 @@ type GetReceivedByAccountCmd struct {
|
||||||
//
|
//
|
||||||
// The parameters which are pointers indicate they are optional. Passing nil
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
// for optional parameters will use the default value.
|
// for optional parameters will use the default value.
|
||||||
func NewGetReceivedByAccountCmd(account string, minConf *int) *GetReceivedByAccountCmd {
|
func NewGetReceivedByAccountCmd(account *string, minConf *int) *GetReceivedByAccountCmd {
|
||||||
return &GetReceivedByAccountCmd{
|
return &GetReceivedByAccountCmd{
|
||||||
Account: account,
|
Account: account,
|
||||||
MinConf: minConf,
|
MinConf: minConf,
|
||||||
|
@ -407,7 +411,8 @@ func NewKeyPoolRefillCmd(newSize *uint) *KeyPoolRefillCmd {
|
||||||
|
|
||||||
// ListAccountsCmd defines the listaccounts JSON-RPC command.
|
// ListAccountsCmd defines the listaccounts JSON-RPC command.
|
||||||
type ListAccountsCmd struct {
|
type ListAccountsCmd struct {
|
||||||
MinConf *int `jsonrpcdefault:"1"`
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListAccountsCmd returns a new instance which can be used to issue a
|
// NewListAccountsCmd returns a new instance which can be used to issue a
|
||||||
|
@ -501,10 +506,10 @@ func NewListSinceBlockCmd(blockHash *string, targetConfirms *int, includeWatchOn
|
||||||
|
|
||||||
// ListTransactionsCmd defines the listtransactions JSON-RPC command.
|
// ListTransactionsCmd defines the listtransactions JSON-RPC command.
|
||||||
type ListTransactionsCmd struct {
|
type ListTransactionsCmd struct {
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
Count *int `jsonrpcdefault:"10"`
|
Count *int `jsonrpcdefault:"10"`
|
||||||
From *int `jsonrpcdefault:"0"`
|
From *int `jsonrpcdefault:"0"`
|
||||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListTransactionsCmd returns a new instance which can be used to issue a
|
// NewListTransactionsCmd returns a new instance which can be used to issue a
|
||||||
|
@ -556,36 +561,13 @@ func NewLockUnspentCmd(unlock bool, transactions []TransactionInput) *LockUnspen
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveCmd defines the move JSON-RPC command.
|
|
||||||
type MoveCmd struct {
|
|
||||||
FromAccount string
|
|
||||||
ToAccount string
|
|
||||||
Amount float64 // In BTC
|
|
||||||
MinConf *int `jsonrpcdefault:"1"`
|
|
||||||
Comment *string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewMoveCmd returns a new instance which can be used to issue a move JSON-RPC
|
|
||||||
// command.
|
|
||||||
//
|
|
||||||
// The parameters which are pointers indicate they are optional. Passing nil
|
|
||||||
// for optional parameters will use the default value.
|
|
||||||
func NewMoveCmd(fromAccount, toAccount string, amount float64, minConf *int, comment *string) *MoveCmd {
|
|
||||||
return &MoveCmd{
|
|
||||||
FromAccount: fromAccount,
|
|
||||||
ToAccount: toAccount,
|
|
||||||
Amount: amount,
|
|
||||||
MinConf: minConf,
|
|
||||||
Comment: comment,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SendFromCmd defines the sendfrom JSON-RPC command.
|
// SendFromCmd defines the sendfrom JSON-RPC command.
|
||||||
type SendFromCmd struct {
|
type SendFromCmd struct {
|
||||||
FromAccount string
|
FromAccount string
|
||||||
ToAddress string
|
ToAddress string
|
||||||
Amount float64 // In BTC
|
Amount float64 // In BTC
|
||||||
MinConf *int `jsonrpcdefault:"1"`
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
Comment *string
|
Comment *string
|
||||||
CommentTo *string
|
CommentTo *string
|
||||||
}
|
}
|
||||||
|
@ -595,12 +577,15 @@ type SendFromCmd struct {
|
||||||
//
|
//
|
||||||
// The parameters which are pointers indicate they are optional. Passing nil
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
// for optional parameters will use the default value.
|
// for optional parameters will use the default value.
|
||||||
func NewSendFromCmd(fromAccount, toAddress string, amount float64, minConf *int, comment, commentTo *string) *SendFromCmd {
|
func NewSendFromCmd(fromAccount, toAddress string, amount float64,
|
||||||
|
minConf *int, addrType *string, comment, commentTo *string) *SendFromCmd {
|
||||||
|
|
||||||
return &SendFromCmd{
|
return &SendFromCmd{
|
||||||
FromAccount: fromAccount,
|
FromAccount: fromAccount,
|
||||||
ToAddress: toAddress,
|
ToAddress: toAddress,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
MinConf: minConf,
|
MinConf: minConf,
|
||||||
|
AddressType: addrType,
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
CommentTo: commentTo,
|
CommentTo: commentTo,
|
||||||
}
|
}
|
||||||
|
@ -611,6 +596,7 @@ type SendManyCmd struct {
|
||||||
FromAccount string
|
FromAccount string
|
||||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||||
MinConf *int `jsonrpcdefault:"1"`
|
MinConf *int `jsonrpcdefault:"1"`
|
||||||
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
Comment *string
|
Comment *string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,21 +605,24 @@ type SendManyCmd struct {
|
||||||
//
|
//
|
||||||
// The parameters which are pointers indicate they are optional. Passing nil
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
// for optional parameters will use the default value.
|
// for optional parameters will use the default value.
|
||||||
func NewSendManyCmd(fromAccount string, amounts map[string]float64, minConf *int, comment *string) *SendManyCmd {
|
func NewSendManyCmd(fromAccount string, amounts map[string]float64,
|
||||||
|
minConf *int, addrType *string, comment *string) *SendManyCmd {
|
||||||
return &SendManyCmd{
|
return &SendManyCmd{
|
||||||
FromAccount: fromAccount,
|
FromAccount: fromAccount,
|
||||||
Amounts: amounts,
|
Amounts: amounts,
|
||||||
MinConf: minConf,
|
MinConf: minConf,
|
||||||
|
AddressType: addrType,
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendToAddressCmd defines the sendtoaddress JSON-RPC command.
|
// SendToAddressCmd defines the sendtoaddress JSON-RPC command.
|
||||||
type SendToAddressCmd struct {
|
type SendToAddressCmd struct {
|
||||||
Address string
|
Address string
|
||||||
Amount float64
|
Amount float64
|
||||||
Comment *string
|
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||||
CommentTo *string
|
Comment *string
|
||||||
|
CommentTo *string
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewSendToAddressCmd returns a new instance which can be used to issue a
|
// NewSendToAddressCmd returns a new instance which can be used to issue a
|
||||||
|
@ -641,27 +630,14 @@ type SendToAddressCmd struct {
|
||||||
//
|
//
|
||||||
// The parameters which are pointers indicate they are optional. Passing nil
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
// for optional parameters will use the default value.
|
// for optional parameters will use the default value.
|
||||||
func NewSendToAddressCmd(address string, amount float64, comment, commentTo *string) *SendToAddressCmd {
|
func NewSendToAddressCmd(address string, amount float64, addrType *string,
|
||||||
|
comment, commentTo *string) *SendToAddressCmd {
|
||||||
return &SendToAddressCmd{
|
return &SendToAddressCmd{
|
||||||
Address: address,
|
Address: address,
|
||||||
Amount: amount,
|
Amount: amount,
|
||||||
Comment: comment,
|
AddressType: addrType,
|
||||||
CommentTo: commentTo,
|
Comment: comment,
|
||||||
}
|
CommentTo: commentTo,
|
||||||
}
|
|
||||||
|
|
||||||
// SetAccountCmd defines the setaccount JSON-RPC command.
|
|
||||||
type SetAccountCmd struct {
|
|
||||||
Address string
|
|
||||||
Account string
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewSetAccountCmd returns a new instance which can be used to issue a
|
|
||||||
// setaccount JSON-RPC command.
|
|
||||||
func NewSetAccountCmd(address, account string) *SetAccountCmd {
|
|
||||||
return &SetAccountCmd{
|
|
||||||
Address: address,
|
|
||||||
Account: account,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -872,7 +848,8 @@ func (s *ScriptPubKey) UnmarshalJSON(data []byte) error {
|
||||||
//
|
//
|
||||||
// Descriptors are typically ranged when specified in the form of generic HD
|
// Descriptors are typically ranged when specified in the form of generic HD
|
||||||
// chain paths.
|
// chain paths.
|
||||||
// Example of a ranged descriptor: pkh(tpub.../*)
|
//
|
||||||
|
// Example of a ranged descriptor: pkh(tpub.../*)
|
||||||
//
|
//
|
||||||
// The value can be an int to specify the end of the range, or the range
|
// The value can be an int to specify the end of the range, or the range
|
||||||
// itself, as []int{begin, end}.
|
// itself, as []int{begin, end}.
|
||||||
|
@ -998,6 +975,24 @@ func NewImportMultiCmd(requests []ImportMultiRequest, options *ImportMultiOption
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RescanBlockchainCmd defines the RescanBlockchain JSON-RPC command.
|
||||||
|
type RescanBlockchainCmd struct {
|
||||||
|
StartHeight *int32 `jsonrpcdefault:"0"`
|
||||||
|
StopHeight *int32
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRescanBlockchainCmd returns a new instance which can be used to issue
|
||||||
|
// an RescanBlockchain JSON-RPC command.
|
||||||
|
//
|
||||||
|
// The parameters which are pointers indicate they are optional. Passing nil
|
||||||
|
// for optional parameters will use the default value.
|
||||||
|
func NewRescanBlockchainCmd(startHeight *int32, stopHeight *int32) *RescanBlockchainCmd {
|
||||||
|
return &RescanBlockchainCmd{
|
||||||
|
StartHeight: startHeight,
|
||||||
|
StopHeight: stopHeight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PsbtInput represents an input to include in the PSBT created by the
|
// PsbtInput represents an input to include in the PSBT created by the
|
||||||
// WalletCreateFundedPsbtCmd command.
|
// WalletCreateFundedPsbtCmd command.
|
||||||
type PsbtInput struct {
|
type PsbtInput struct {
|
||||||
|
@ -1119,11 +1114,10 @@ func init() {
|
||||||
MustRegisterCmd("listunspent", (*ListUnspentCmd)(nil), flags)
|
MustRegisterCmd("listunspent", (*ListUnspentCmd)(nil), flags)
|
||||||
MustRegisterCmd("loadwallet", (*LoadWalletCmd)(nil), flags)
|
MustRegisterCmd("loadwallet", (*LoadWalletCmd)(nil), flags)
|
||||||
MustRegisterCmd("lockunspent", (*LockUnspentCmd)(nil), flags)
|
MustRegisterCmd("lockunspent", (*LockUnspentCmd)(nil), flags)
|
||||||
MustRegisterCmd("move", (*MoveCmd)(nil), flags)
|
MustRegisterCmd("rescanblockchain", (*RescanBlockchainCmd)(nil), flags)
|
||||||
MustRegisterCmd("sendfrom", (*SendFromCmd)(nil), flags)
|
MustRegisterCmd("sendfrom", (*SendFromCmd)(nil), flags)
|
||||||
MustRegisterCmd("sendmany", (*SendManyCmd)(nil), flags)
|
MustRegisterCmd("sendmany", (*SendManyCmd)(nil), flags)
|
||||||
MustRegisterCmd("sendtoaddress", (*SendToAddressCmd)(nil), flags)
|
MustRegisterCmd("sendtoaddress", (*SendToAddressCmd)(nil), flags)
|
||||||
MustRegisterCmd("setaccount", (*SetAccountCmd)(nil), flags)
|
|
||||||
MustRegisterCmd("settxfee", (*SetTxFeeCmd)(nil), flags)
|
MustRegisterCmd("settxfee", (*SetTxFeeCmd)(nil), flags)
|
||||||
MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags)
|
MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags)
|
||||||
MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags)
|
MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags)
|
||||||
|
|
|
@ -287,11 +287,12 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("getaccountaddress", "acct")
|
return btcjson.NewCmd("getaccountaddress", "acct")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewGetAccountAddressCmd("acct")
|
return btcjson.NewGetAccountAddressCmd(btcjson.String("acct"))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getaccountaddress","params":["acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getaccountaddress","params":["acct"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetAccountAddressCmd{
|
unmarshalled: &btcjson.GetAccountAddressCmd{
|
||||||
Account: "acct",
|
Account: btcjson.String("acct"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -300,11 +301,12 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("getaddressesbyaccount", "acct")
|
return btcjson.NewCmd("getaddressesbyaccount", "acct")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewGetAddressesByAccountCmd("acct")
|
return btcjson.NewGetAddressesByAccountCmd(btcjson.String("acct"))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getaddressesbyaccount","params":["acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getaddressesbyaccount","params":["acct"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetAddressesByAccountCmd{
|
unmarshalled: &btcjson.GetAddressesByAccountCmd{
|
||||||
Account: "acct",
|
Account: btcjson.String("acct"),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -330,8 +332,9 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetBalanceCmd{
|
unmarshalled: &btcjson.GetBalanceCmd{
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -344,8 +347,9 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetBalanceCmd{
|
unmarshalled: &btcjson.GetBalanceCmd{
|
||||||
Account: btcjson.String("acct"),
|
Account: btcjson.String("acct"),
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -358,8 +362,9 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct",6],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct",6],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetBalanceCmd{
|
unmarshalled: &btcjson.GetBalanceCmd{
|
||||||
Account: btcjson.String("acct"),
|
Account: btcjson.String("acct"),
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -383,7 +388,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetNewAddressCmd{
|
unmarshalled: &btcjson.GetNewAddressCmd{
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -396,7 +402,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":["acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":["acct"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetNewAddressCmd{
|
unmarshalled: &btcjson.GetNewAddressCmd{
|
||||||
Account: btcjson.String("acct"),
|
Account: btcjson.String("acct"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -409,7 +416,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetRawChangeAddressCmd{
|
unmarshalled: &btcjson.GetRawChangeAddressCmd{
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -422,7 +430,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":["acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":["acct"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetRawChangeAddressCmd{
|
unmarshalled: &btcjson.GetRawChangeAddressCmd{
|
||||||
Account: btcjson.String("acct"),
|
Account: btcjson.String("acct"),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -431,11 +440,11 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("getreceivedbyaccount", "acct")
|
return btcjson.NewCmd("getreceivedbyaccount", "acct")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewGetReceivedByAccountCmd("acct", nil)
|
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("acct"), nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetReceivedByAccountCmd{
|
unmarshalled: &btcjson.GetReceivedByAccountCmd{
|
||||||
Account: "acct",
|
Account: btcjson.String("acct"),
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -445,11 +454,11 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("getreceivedbyaccount", "acct", 6)
|
return btcjson.NewCmd("getreceivedbyaccount", "acct", 6)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewGetReceivedByAccountCmd("acct", btcjson.Int(6))
|
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("acct"), btcjson.Int(6))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct",6],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct",6],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetReceivedByAccountCmd{
|
unmarshalled: &btcjson.GetReceivedByAccountCmd{
|
||||||
Account: "acct",
|
Account: btcjson.String("acct"),
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -601,7 +610,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.ListAccountsCmd{
|
unmarshalled: &btcjson.ListAccountsCmd{
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -614,7 +624,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[6],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[6],"id":1}`,
|
||||||
unmarshalled: &btcjson.ListAccountsCmd{
|
unmarshalled: &btcjson.ListAccountsCmd{
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -844,7 +855,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.ListTransactionsCmd{
|
unmarshalled: &btcjson.ListTransactionsCmd{
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
Count: btcjson.Int(10),
|
Count: btcjson.Int(10),
|
||||||
From: btcjson.Int(0),
|
From: btcjson.Int(0),
|
||||||
IncludeWatchOnly: btcjson.Bool(false),
|
IncludeWatchOnly: btcjson.Bool(false),
|
||||||
|
@ -996,64 +1007,13 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "move",
|
|
||||||
newCmd: func() (interface{}, error) {
|
|
||||||
return btcjson.NewCmd("move", "from", "to", 0.5)
|
|
||||||
},
|
|
||||||
staticCmd: func() interface{} {
|
|
||||||
return btcjson.NewMoveCmd("from", "to", 0.5, nil, nil)
|
|
||||||
},
|
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"move","params":["from","to",0.5],"id":1}`,
|
|
||||||
unmarshalled: &btcjson.MoveCmd{
|
|
||||||
FromAccount: "from",
|
|
||||||
ToAccount: "to",
|
|
||||||
Amount: 0.5,
|
|
||||||
MinConf: btcjson.Int(1),
|
|
||||||
Comment: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "move optional1",
|
|
||||||
newCmd: func() (interface{}, error) {
|
|
||||||
return btcjson.NewCmd("move", "from", "to", 0.5, 6)
|
|
||||||
},
|
|
||||||
staticCmd: func() interface{} {
|
|
||||||
return btcjson.NewMoveCmd("from", "to", 0.5, btcjson.Int(6), nil)
|
|
||||||
},
|
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"move","params":["from","to",0.5,6],"id":1}`,
|
|
||||||
unmarshalled: &btcjson.MoveCmd{
|
|
||||||
FromAccount: "from",
|
|
||||||
ToAccount: "to",
|
|
||||||
Amount: 0.5,
|
|
||||||
MinConf: btcjson.Int(6),
|
|
||||||
Comment: nil,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "move optional2",
|
|
||||||
newCmd: func() (interface{}, error) {
|
|
||||||
return btcjson.NewCmd("move", "from", "to", 0.5, 6, "comment")
|
|
||||||
},
|
|
||||||
staticCmd: func() interface{} {
|
|
||||||
return btcjson.NewMoveCmd("from", "to", 0.5, btcjson.Int(6), btcjson.String("comment"))
|
|
||||||
},
|
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"move","params":["from","to",0.5,6,"comment"],"id":1}`,
|
|
||||||
unmarshalled: &btcjson.MoveCmd{
|
|
||||||
FromAccount: "from",
|
|
||||||
ToAccount: "to",
|
|
||||||
Amount: 0.5,
|
|
||||||
MinConf: btcjson.Int(6),
|
|
||||||
Comment: btcjson.String("comment"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
name: "sendfrom",
|
name: "sendfrom",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5)
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil)
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendFromCmd{
|
unmarshalled: &btcjson.SendFromCmd{
|
||||||
|
@ -1061,6 +1021,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
ToAddress: "1Address",
|
ToAddress: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
Comment: nil,
|
Comment: nil,
|
||||||
CommentTo: nil,
|
CommentTo: nil,
|
||||||
},
|
},
|
||||||
|
@ -1071,7 +1032,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6)
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil)
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendFromCmd{
|
unmarshalled: &btcjson.SendFromCmd{
|
||||||
|
@ -1079,6 +1040,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
ToAddress: "1Address",
|
ToAddress: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
Comment: nil,
|
Comment: nil,
|
||||||
CommentTo: nil,
|
CommentTo: nil,
|
||||||
},
|
},
|
||||||
|
@ -1086,37 +1048,59 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "sendfrom optional2",
|
name: "sendfrom optional2",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment")
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||||
btcjson.String("comment"), nil)
|
nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy"],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendFromCmd{
|
unmarshalled: &btcjson.SendFromCmd{
|
||||||
FromAccount: "from",
|
FromAccount: "from",
|
||||||
ToAddress: "1Address",
|
ToAddress: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
Comment: btcjson.String("comment"),
|
AddressType: btcjson.String("legacy"),
|
||||||
|
Comment: nil,
|
||||||
CommentTo: nil,
|
CommentTo: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sendfrom optional3",
|
name: "sendfrom optional3",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment", "commentto")
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy", "comment")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||||
btcjson.String("comment"), btcjson.String("commentto"))
|
btcjson.String("comment"), nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment","commentto"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment"],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendFromCmd{
|
unmarshalled: &btcjson.SendFromCmd{
|
||||||
FromAccount: "from",
|
FromAccount: "from",
|
||||||
ToAddress: "1Address",
|
ToAddress: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
|
Comment: btcjson.String("comment"),
|
||||||
|
CommentTo: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sendfrom optional4",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy", "comment", "commentto")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||||
|
btcjson.String("comment"), btcjson.String("commentto"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment","commentto"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SendFromCmd{
|
||||||
|
FromAccount: "from",
|
||||||
|
ToAddress: "1Address",
|
||||||
|
Amount: 0.5,
|
||||||
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
Comment: btcjson.String("comment"),
|
Comment: btcjson.String("comment"),
|
||||||
CommentTo: btcjson.String("commentto"),
|
CommentTo: btcjson.String("commentto"),
|
||||||
},
|
},
|
||||||
|
@ -1128,13 +1112,14 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
amounts := map[string]float64{"1Address": 0.5}
|
amounts := map[string]float64{"1Address": 0.5}
|
||||||
return btcjson.NewSendManyCmd("from", amounts, nil, nil)
|
return btcjson.NewSendManyCmd("from", amounts, nil, nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendManyCmd{
|
unmarshalled: &btcjson.SendManyCmd{
|
||||||
FromAccount: "from",
|
FromAccount: "from",
|
||||||
Amounts: map[string]float64{"1Address": 0.5},
|
Amounts: map[string]float64{"1Address": 0.5},
|
||||||
MinConf: btcjson.Int(1),
|
MinConf: btcjson.Int(1),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
Comment: nil,
|
Comment: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1145,30 +1130,50 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
amounts := map[string]float64{"1Address": 0.5}
|
amounts := map[string]float64{"1Address": 0.5}
|
||||||
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil)
|
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendManyCmd{
|
unmarshalled: &btcjson.SendManyCmd{
|
||||||
FromAccount: "from",
|
FromAccount: "from",
|
||||||
Amounts: map[string]float64{"1Address": 0.5},
|
Amounts: map[string]float64{"1Address": 0.5},
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("*"),
|
||||||
Comment: nil,
|
Comment: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sendmany optional2",
|
name: "sendmany optional2",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "comment")
|
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "legacy")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
amounts := map[string]float64{"1Address": 0.5}
|
amounts := map[string]float64{"1Address": 0.5}
|
||||||
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("comment"))
|
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("legacy"), nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"comment"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"legacy"],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendManyCmd{
|
unmarshalled: &btcjson.SendManyCmd{
|
||||||
FromAccount: "from",
|
FromAccount: "from",
|
||||||
Amounts: map[string]float64{"1Address": 0.5},
|
Amounts: map[string]float64{"1Address": 0.5},
|
||||||
MinConf: btcjson.Int(6),
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
|
Comment: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "sendmany optional3",
|
||||||
|
newCmd: func() (interface{}, error) {
|
||||||
|
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "legacy", "comment")
|
||||||
|
},
|
||||||
|
staticCmd: func() interface{} {
|
||||||
|
amounts := map[string]float64{"1Address": 0.5}
|
||||||
|
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("legacy"), btcjson.String("comment"))
|
||||||
|
},
|
||||||
|
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"legacy","comment"],"id":1}`,
|
||||||
|
unmarshalled: &btcjson.SendManyCmd{
|
||||||
|
FromAccount: "from",
|
||||||
|
Amounts: map[string]float64{"1Address": 0.5},
|
||||||
|
MinConf: btcjson.Int(6),
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
Comment: btcjson.String("comment"),
|
Comment: btcjson.String("comment"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1178,45 +1183,50 @@ func TestWalletSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5)
|
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil)
|
return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil, nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendToAddressCmd{
|
unmarshalled: &btcjson.SendToAddressCmd{
|
||||||
Address: "1Address",
|
Address: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
Comment: nil,
|
AddressType: btcjson.String("*"),
|
||||||
CommentTo: nil,
|
Comment: nil,
|
||||||
|
CommentTo: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "sendtoaddress optional1",
|
name: "sendtoaddress optional1",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto")
|
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("comment"),
|
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), nil, nil)
|
||||||
btcjson.String("commentto"))
|
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"comment","commentto"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy"],"id":1}`,
|
||||||
unmarshalled: &btcjson.SendToAddressCmd{
|
unmarshalled: &btcjson.SendToAddressCmd{
|
||||||
Address: "1Address",
|
Address: "1Address",
|
||||||
Amount: 0.5,
|
Amount: 0.5,
|
||||||
Comment: btcjson.String("comment"),
|
AddressType: btcjson.String("legacy"),
|
||||||
CommentTo: btcjson.String("commentto"),
|
Comment: nil,
|
||||||
|
CommentTo: nil,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "setaccount",
|
name: "sendtoaddress optional2",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("setaccount", "1Address", "acct")
|
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy", "comment", "commentto")
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewSetAccountCmd("1Address", "acct")
|
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), btcjson.String("comment"),
|
||||||
|
btcjson.String("commentto"))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"setaccount","params":["1Address","acct"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy","comment","commentto"],"id":1}`,
|
||||||
unmarshalled: &btcjson.SetAccountCmd{
|
unmarshalled: &btcjson.SendToAddressCmd{
|
||||||
Address: "1Address",
|
Address: "1Address",
|
||||||
Account: "acct",
|
Amount: 0.5,
|
||||||
|
AddressType: btcjson.String("legacy"),
|
||||||
|
Comment: btcjson.String("comment"),
|
||||||
|
CommentTo: btcjson.String("commentto"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -47,13 +47,32 @@ type embeddedAddressInfo struct {
|
||||||
// Reference: https://bitcoincore.org/en/doc/0.20.0/rpc/wallet/getaddressinfo
|
// Reference: https://bitcoincore.org/en/doc/0.20.0/rpc/wallet/getaddressinfo
|
||||||
//
|
//
|
||||||
// The GetAddressInfoResult has three segments:
|
// The GetAddressInfoResult has three segments:
|
||||||
// 1. General information about the address.
|
// 1. General information about the address.
|
||||||
// 2. Metadata (Timestamp, HDKeyPath, HDSeedID) and wallet fields
|
// 2. Metadata (Timestamp, HDKeyPath, HDSeedID) and wallet fields
|
||||||
// (IsMine, IsWatchOnly).
|
// (IsMine, IsWatchOnly).
|
||||||
// 3. Information about the embedded address in case of P2SH or P2WSH.
|
// 3. Information about the embedded address in case of P2SH or P2WSH.
|
||||||
// Same structure as (1).
|
// Same structure as (1).
|
||||||
type GetAddressInfoResult struct {
|
type GetAddressInfoResult struct {
|
||||||
embeddedAddressInfo
|
// The following fields are identical to embeddedAddressInfo.
|
||||||
|
// However, the utility to generate RPC help message can't handle
|
||||||
|
// embedded field properly. So, spell out the attributes individually.
|
||||||
|
Address string `json:"address"`
|
||||||
|
ScriptPubKey string `json:"scriptPubKey"`
|
||||||
|
Descriptor *string `json:"desc,omitempty"`
|
||||||
|
IsScript bool `json:"isscript"`
|
||||||
|
IsChange bool `json:"ischange"`
|
||||||
|
IsWitness bool `json:"iswitness"`
|
||||||
|
WitnessVersion int `json:"witness_version,omitempty"`
|
||||||
|
WitnessProgram *string `json:"witness_program,omitempty"`
|
||||||
|
ScriptType *txscript.ScriptClass `json:"script,omitempty"`
|
||||||
|
Hex *string `json:"hex,omitempty"`
|
||||||
|
PubKeys *[]string `json:"pubkeys,omitempty"`
|
||||||
|
SignaturesRequired *int `json:"sigsrequired,omitempty"`
|
||||||
|
PubKey *string `json:"pubkey,omitempty"`
|
||||||
|
IsCompressed *bool `json:"iscompressed,omitempty"`
|
||||||
|
HDMasterFingerprint *string `json:"hdmasterfingerprint,omitempty"`
|
||||||
|
Labels []string `json:"labels"`
|
||||||
|
|
||||||
IsMine bool `json:"ismine"`
|
IsMine bool `json:"ismine"`
|
||||||
IsWatchOnly bool `json:"iswatchonly"`
|
IsWatchOnly bool `json:"iswatchonly"`
|
||||||
Timestamp *int `json:"timestamp,omitempty"`
|
Timestamp *int `json:"timestamp,omitempty"`
|
||||||
|
@ -155,6 +174,7 @@ type GetTransactionResult struct {
|
||||||
TimeReceived int64 `json:"timereceived"`
|
TimeReceived int64 `json:"timereceived"`
|
||||||
Details []GetTransactionDetailsResult `json:"details"`
|
Details []GetTransactionDetailsResult `json:"details"`
|
||||||
Hex string `json:"hex"`
|
Hex string `json:"hex"`
|
||||||
|
Generated bool `json:"generated"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type ScanningOrFalse struct {
|
type ScanningOrFalse struct {
|
||||||
|
@ -269,7 +289,6 @@ type ListReceivedByAccountResult struct {
|
||||||
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
|
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
|
||||||
// command.
|
// command.
|
||||||
type ListReceivedByAddressResult struct {
|
type ListReceivedByAddressResult struct {
|
||||||
Account string `json:"account"`
|
|
||||||
Address string `json:"address"`
|
Address string `json:"address"`
|
||||||
Amount float64 `json:"amount"`
|
Amount float64 `json:"amount"`
|
||||||
Confirmations uint64 `json:"confirmations"`
|
Confirmations uint64 `json:"confirmations"`
|
||||||
|
@ -298,6 +317,12 @@ type ListUnspentResult struct {
|
||||||
IsStake bool `json:"isstake"`
|
IsStake bool `json:"isstake"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// RescanBlockchainResult models the data returned from the rescanblockchain command.
|
||||||
|
type RescanBlockchainResult struct {
|
||||||
|
StartHeight int32 `json:"start_height"`
|
||||||
|
StoptHeight int32 `json:"stop_height"`
|
||||||
|
}
|
||||||
|
|
||||||
// SignRawTransactionError models the data that contains script verification
|
// SignRawTransactionError models the data that contains script verification
|
||||||
// errors from the signrawtransaction request.
|
// errors from the signrawtransaction request.
|
||||||
type SignRawTransactionError struct {
|
type SignRawTransactionError struct {
|
||||||
|
|
|
@ -37,10 +37,8 @@ func TestGetAddressInfoResult(t *testing.T) {
|
||||||
name: "GetAddressInfoResult - ScriptType",
|
name: "GetAddressInfoResult - ScriptType",
|
||||||
result: `{"script":"nonstandard","address":"1abc"}`,
|
result: `{"script":"nonstandard","address":"1abc"}`,
|
||||||
want: GetAddressInfoResult{
|
want: GetAddressInfoResult{
|
||||||
embeddedAddressInfo: embeddedAddressInfo{
|
Address: "1abc",
|
||||||
Address: "1abc",
|
ScriptType: nonStandard,
|
||||||
ScriptType: nonStandard,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -40,7 +40,7 @@ func NewExportWatchingWalletCmd(account *string, download *bool) *ExportWatching
|
||||||
|
|
||||||
// GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command.
|
// GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command.
|
||||||
type GetUnconfirmedBalanceCmd struct {
|
type GetUnconfirmedBalanceCmd struct {
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue
|
// NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue
|
||||||
|
@ -58,7 +58,7 @@ func NewGetUnconfirmedBalanceCmd(account *string) *GetUnconfirmedBalanceCmd {
|
||||||
// command.
|
// command.
|
||||||
type ListAddressTransactionsCmd struct {
|
type ListAddressTransactionsCmd struct {
|
||||||
Addresses []string
|
Addresses []string
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListAddressTransactionsCmd returns a new instance which can be used to
|
// NewListAddressTransactionsCmd returns a new instance which can be used to
|
||||||
|
@ -75,7 +75,7 @@ func NewListAddressTransactionsCmd(addresses []string, account *string) *ListAdd
|
||||||
|
|
||||||
// ListAllTransactionsCmd defines the listalltransactions JSON-RPC command.
|
// ListAllTransactionsCmd defines the listalltransactions JSON-RPC command.
|
||||||
type ListAllTransactionsCmd struct {
|
type ListAllTransactionsCmd struct {
|
||||||
Account *string
|
Account *string `jsonrpcdefault:"\"default\""`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
||||||
|
@ -114,9 +114,8 @@ func NewWalletIsLockedCmd() *WalletIsLockedCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
// The commands in this file are only usable with a wallet server via
|
// The commands in this file are only usable with a wallet server.
|
||||||
// websockets.
|
flags := UFWalletOnly
|
||||||
flags := UFWalletOnly | UFWebsocketOnly
|
|
||||||
|
|
||||||
MustRegisterCmd("createencryptedwallet", (*CreateEncryptedWalletCmd)(nil), flags)
|
MustRegisterCmd("createencryptedwallet", (*CreateEncryptedWalletCmd)(nil), flags)
|
||||||
MustRegisterCmd("exportwatchingwallet", (*ExportWatchingWalletCmd)(nil), flags)
|
MustRegisterCmd("exportwatchingwallet", (*ExportWatchingWalletCmd)(nil), flags)
|
||||||
|
|
|
@ -71,7 +71,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "exportwatchingwallet optional2",
|
name: "exportwatchingwallet optional2",
|
||||||
newCmd: func() (interface{}, error) {
|
newCmd: func() (interface{}, error) {
|
||||||
return btcjson.NewCmd("exportwatchingwallet", "acct", true)
|
return btcjson.NewCmd("exportwatchingwallet", btcjson.String("acct"), true)
|
||||||
},
|
},
|
||||||
staticCmd: func() interface{} {
|
staticCmd: func() interface{} {
|
||||||
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"),
|
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"),
|
||||||
|
@ -93,7 +93,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -120,7 +120,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`,
|
||||||
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
||||||
Addresses: []string{"1Address"},
|
Addresses: []string{"1Address"},
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -148,7 +148,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`,
|
||||||
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
||||||
Account: nil,
|
Account: btcjson.String("default"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,11 +39,11 @@ func DoubleHashH(b []byte) Hash {
|
||||||
|
|
||||||
// LbryPoWHashH calculates returns the PoW Hash.
|
// LbryPoWHashH calculates returns the PoW Hash.
|
||||||
//
|
//
|
||||||
// doubled := SHA256(SHA256(b))
|
// doubled := SHA256(SHA256(b))
|
||||||
// expanded := SHA512(doubled)
|
// expanded := SHA512(doubled)
|
||||||
// left := RIPEMD160(expanded[0:32])
|
// left := RIPEMD160(expanded[0:32])
|
||||||
// right := RIPEMD160(expanded[32:64])
|
// right := RIPEMD160(expanded[32:64])
|
||||||
// result := SHA256(SHA256(left||right))
|
// result := SHA256(SHA256(left||right))
|
||||||
func LbryPoWHashH(b []byte) Hash {
|
func LbryPoWHashH(b []byte) Hash {
|
||||||
doubled := DoubleHashB(b)
|
doubled := DoubleHashB(b)
|
||||||
expanded := sha512.Sum512(doubled)
|
expanded := sha512.Sum512(doubled)
|
||||||
|
|
|
@ -18,40 +18,40 @@
|
||||||
// When a network parameter is needed, it may then be looked up through this
|
// When a network parameter is needed, it may then be looked up through this
|
||||||
// variable (either directly, or hidden in a library call).
|
// variable (either directly, or hidden in a library call).
|
||||||
//
|
//
|
||||||
// package main
|
// package main
|
||||||
//
|
//
|
||||||
// import (
|
// import (
|
||||||
// "flag"
|
// "flag"
|
||||||
// "fmt"
|
// "fmt"
|
||||||
// "log"
|
// "log"
|
||||||
//
|
//
|
||||||
// btcutil "github.com/lbryio/lbcutil"
|
// btcutil "github.com/lbryio/lbcutil"
|
||||||
// "github.com/lbryio/lbcd/chaincfg"
|
// "github.com/lbryio/lbcd/chaincfg"
|
||||||
// )
|
// )
|
||||||
//
|
//
|
||||||
// var testnet = flag.Bool("testnet", false, "operate on the testnet Bitcoin network")
|
// var testnet = flag.Bool("testnet", false, "operate on the testnet Bitcoin network")
|
||||||
//
|
//
|
||||||
// // By default (without -testnet), use mainnet.
|
// // By default (without -testnet), use mainnet.
|
||||||
// var chainParams = &chaincfg.MainNetParams
|
// var chainParams = &chaincfg.MainNetParams
|
||||||
//
|
//
|
||||||
// func main() {
|
// func main() {
|
||||||
// flag.Parse()
|
// flag.Parse()
|
||||||
//
|
//
|
||||||
// // Modify active network parameters if operating on testnet.
|
// // Modify active network parameters if operating on testnet.
|
||||||
// if *testnet {
|
// if *testnet {
|
||||||
// chainParams = &chaincfg.TestNet3Params
|
// chainParams = &chaincfg.TestNet3Params
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// // later...
|
// // later...
|
||||||
//
|
//
|
||||||
// // Create and print new payment address, specific to the active network.
|
// // Create and print new payment address, specific to the active network.
|
||||||
// pubKeyHash := make([]byte, 20)
|
// pubKeyHash := make([]byte, 20)
|
||||||
// addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, chainParams)
|
// addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, chainParams)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// log.Fatal(err)
|
// log.Fatal(err)
|
||||||
// }
|
// }
|
||||||
// fmt.Println(addr)
|
// fmt.Println(addr)
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
// If an application does not use one of the three standard Bitcoin networks,
|
// If an application does not use one of the three standard Bitcoin networks,
|
||||||
// a new Params struct may be created which defines the parameters for the
|
// a new Params struct may be created which defines the parameters for the
|
||||||
|
|
|
@ -794,8 +794,9 @@ func IsBech32SegwitPrefix(prefix string) bool {
|
||||||
// ErrInvalidHDKeyID error will be returned.
|
// ErrInvalidHDKeyID error will be returned.
|
||||||
//
|
//
|
||||||
// Reference:
|
// Reference:
|
||||||
// SLIP-0132 : Registered HD version bytes for BIP-0032
|
//
|
||||||
// https://github.com/satoshilabs/slips/blob/master/slip-0132.md
|
// SLIP-0132 : Registered HD version bytes for BIP-0032
|
||||||
|
// https://github.com/satoshilabs/slips/blob/master/slip-0132.md
|
||||||
func RegisterHDKeyID(hdPublicKeyID []byte, hdPrivateKeyID []byte) error {
|
func RegisterHDKeyID(hdPublicKeyID []byte, hdPrivateKeyID []byte) error {
|
||||||
if len(hdPublicKeyID) != 4 || len(hdPrivateKeyID) != 4 {
|
if len(hdPublicKeyID) != 4 || len(hdPrivateKeyID) != 4 {
|
||||||
return ErrInvalidHDKeyID
|
return ErrInvalidHDKeyID
|
||||||
|
|
|
@ -4,9 +4,7 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
@ -49,6 +47,9 @@ type ClaimTrie struct {
|
||||||
|
|
||||||
// Registrered cleanup functions which are invoked in the Close() in reverse order.
|
// Registrered cleanup functions which are invoked in the Close() in reverse order.
|
||||||
cleanups []func() error
|
cleanups []func() error
|
||||||
|
|
||||||
|
// claimLogger communicates progress of claimtrie rebuild.
|
||||||
|
claimLogger *claimProgressLogger
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(cfg config.Config) (*ClaimTrie, error) {
|
func New(cfg config.Config) (*ClaimTrie, error) {
|
||||||
|
@ -246,17 +247,17 @@ func (ct *ClaimTrie) AppendBlock(temporary bool) error {
|
||||||
names = append(names, expirations...)
|
names = append(names, expirations...)
|
||||||
names = removeDuplicates(names)
|
names = removeDuplicates(names)
|
||||||
|
|
||||||
nhns := ct.makeNameHashNext(names, false, nil)
|
for _, name := range names {
|
||||||
for nhn := range nhns {
|
|
||||||
|
|
||||||
ct.merkleTrie.Update(nhn.Name, nhn.Hash, true)
|
hash, next := ct.nodeManager.Hash(name)
|
||||||
if nhn.Next <= 0 {
|
ct.merkleTrie.Update(name, hash, true)
|
||||||
|
if next <= 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
newName := normalization.NormalizeIfNecessary(nhn.Name, nhn.Next)
|
newName := normalization.NormalizeIfNecessary(name, next)
|
||||||
updateNames = append(updateNames, newName)
|
updateNames = append(updateNames, newName)
|
||||||
updateHeights = append(updateHeights, nhn.Next)
|
updateHeights = append(updateHeights, next)
|
||||||
}
|
}
|
||||||
if !temporary && len(updateNames) > 0 {
|
if !temporary && len(updateNames) > 0 {
|
||||||
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
|
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
|
||||||
|
@ -330,31 +331,52 @@ func (ct *ClaimTrie) ResetHeight(height int32) error {
|
||||||
if passedHashFork {
|
if passedHashFork {
|
||||||
names = nil // force them to reconsider all names
|
names = nil // force them to reconsider all names
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fullRebuildRequired bool
|
||||||
|
|
||||||
err = ct.merkleTrie.SetRoot(hash)
|
err = ct.merkleTrie.SetRoot(hash)
|
||||||
if err == merkletrie.ErrFullRebuildRequired {
|
if err == merkletrie.ErrFullRebuildRequired {
|
||||||
|
fullRebuildRequired = true
|
||||||
|
} else if err != nil {
|
||||||
|
return errors.Wrapf(err, "setRoot")
|
||||||
|
}
|
||||||
|
|
||||||
|
if fullRebuildRequired {
|
||||||
ct.runFullTrieRebuild(names, nil)
|
ct.runFullTrieRebuild(names, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ct.MerkleHash().IsEqual(hash) {
|
if !ct.MerkleHash().IsEqual(hash) {
|
||||||
return errors.Errorf("unable to restore the hash at height %d", height)
|
return errors.Errorf("unable to restore the hash at height %d"+
|
||||||
|
" (fullTriedRebuilt: %t)", height, fullRebuildRequired)
|
||||||
}
|
}
|
||||||
|
|
||||||
return errors.WithStack(ct.blockRepo.Delete(height+1, oldHeight))
|
return errors.WithStack(ct.blockRepo.Delete(height+1, oldHeight))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ct *ClaimTrie) runFullTrieRebuild(names [][]byte, interrupt <-chan struct{}) {
|
func (ct *ClaimTrie) runFullTrieRebuild(names [][]byte, interrupt <-chan struct{}) {
|
||||||
var nhns chan NameHashNext
|
|
||||||
if names == nil {
|
if names == nil {
|
||||||
node.LogOnce("Building the entire claim trie in RAM...")
|
node.Log("Building the entire claim trie in RAM...")
|
||||||
|
ct.claimLogger = newClaimProgressLogger("Processed", node.GetLogger())
|
||||||
|
|
||||||
|
ct.nodeManager.IterateNames(func(name []byte) bool {
|
||||||
|
if interruptRequested(interrupt) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
clone := make([]byte, len(name))
|
||||||
|
copy(clone, name)
|
||||||
|
hash, _ := ct.nodeManager.Hash(clone)
|
||||||
|
ct.merkleTrie.Update(clone, hash, false)
|
||||||
|
ct.claimLogger.LogName(name)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
nhns = ct.makeNameHashNext(nil, true, interrupt)
|
|
||||||
} else {
|
} else {
|
||||||
nhns = ct.makeNameHashNext(names, false, interrupt)
|
for _, name := range names {
|
||||||
|
hash, _ := ct.nodeManager.Hash(name)
|
||||||
|
ct.merkleTrie.Update(name, hash, false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for nhn := range nhns {
|
|
||||||
ct.merkleTrie.Update(nhn.Name, nhn.Hash, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// MerkleHash returns the Merkle Hash of the claimTrie.
|
// MerkleHash returns the Merkle Hash of the claimTrie.
|
||||||
|
@ -420,12 +442,6 @@ func (ct *ClaimTrie) FlushToDisk() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type NameHashNext struct {
|
|
||||||
Name []byte
|
|
||||||
Hash *chainhash.Hash
|
|
||||||
Next int32
|
|
||||||
}
|
|
||||||
|
|
||||||
func interruptRequested(interrupted <-chan struct{}) bool {
|
func interruptRequested(interrupted <-chan struct{}) bool {
|
||||||
select {
|
select {
|
||||||
case <-interrupted: // should never block on nil
|
case <-interrupted: // should never block on nil
|
||||||
|
@ -435,53 +451,3 @@ func interruptRequested(interrupted <-chan struct{}) bool {
|
||||||
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ct *ClaimTrie) makeNameHashNext(names [][]byte, all bool, interrupt <-chan struct{}) chan NameHashNext {
|
|
||||||
inputs := make(chan []byte, 512)
|
|
||||||
outputs := make(chan NameHashNext, 512)
|
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
hashComputationWorker := func() {
|
|
||||||
for name := range inputs {
|
|
||||||
hash, next := ct.nodeManager.Hash(name)
|
|
||||||
outputs <- NameHashNext{name, hash, next}
|
|
||||||
}
|
|
||||||
wg.Done()
|
|
||||||
}
|
|
||||||
|
|
||||||
threads := int(0.8 * float32(runtime.NumCPU()))
|
|
||||||
if threads < 1 {
|
|
||||||
threads = 1
|
|
||||||
}
|
|
||||||
for threads > 0 {
|
|
||||||
threads--
|
|
||||||
wg.Add(1)
|
|
||||||
go hashComputationWorker()
|
|
||||||
}
|
|
||||||
go func() {
|
|
||||||
if all {
|
|
||||||
ct.nodeManager.IterateNames(func(name []byte) bool {
|
|
||||||
if interruptRequested(interrupt) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
clone := make([]byte, len(name))
|
|
||||||
copy(clone, name) // iteration name buffer is reused on future loops
|
|
||||||
inputs <- clone
|
|
||||||
return true
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
for _, name := range names {
|
|
||||||
if interruptRequested(interrupt) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
inputs <- name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
close(inputs)
|
|
||||||
}()
|
|
||||||
go func() {
|
|
||||||
wg.Wait()
|
|
||||||
close(outputs)
|
|
||||||
}()
|
|
||||||
return outputs
|
|
||||||
}
|
|
||||||
|
|
73
claimtrie/logger.go
Normal file
73
claimtrie/logger.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
// Copyright (c) 2015-2017 The btcsuite developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package claimtrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// claimProgressLogger provides periodic logging for other services in order
|
||||||
|
// to show users progress of certain "actions" involving some or all current
|
||||||
|
// claim names. Ex: rebuilding claimtrie.
|
||||||
|
type claimProgressLogger struct {
|
||||||
|
totalLogName int64
|
||||||
|
recentLogName int64
|
||||||
|
lastLogNameTime time.Time
|
||||||
|
|
||||||
|
subsystemLogger btclog.Logger
|
||||||
|
progressAction string
|
||||||
|
sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
// newClaimProgressLogger returns a new name progress logger.
|
||||||
|
// The progress message is templated as follows:
|
||||||
|
//
|
||||||
|
// {progressAction} {numProcessed} {names|name} in the last {timePeriod} (total {totalProcessed})
|
||||||
|
func newClaimProgressLogger(progressMessage string, logger btclog.Logger) *claimProgressLogger {
|
||||||
|
return &claimProgressLogger{
|
||||||
|
lastLogNameTime: time.Now(),
|
||||||
|
progressAction: progressMessage,
|
||||||
|
subsystemLogger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogName logs a new claim name as an information message to show progress
|
||||||
|
// to the user. In order to prevent spam, it limits logging to one message
|
||||||
|
// every 10 seconds with duration and totals included.
|
||||||
|
func (n *claimProgressLogger) LogName(name []byte) {
|
||||||
|
n.Lock()
|
||||||
|
defer n.Unlock()
|
||||||
|
|
||||||
|
n.totalLogName++
|
||||||
|
n.recentLogName++
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
duration := now.Sub(n.lastLogNameTime)
|
||||||
|
if duration < time.Second*10 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Truncate the duration to 10s of milliseconds.
|
||||||
|
durationMillis := int64(duration / time.Millisecond)
|
||||||
|
tDuration := 10 * time.Millisecond * time.Duration(durationMillis/10)
|
||||||
|
|
||||||
|
// Log information about progress.
|
||||||
|
nameStr := "names"
|
||||||
|
if n.recentLogName == 1 {
|
||||||
|
nameStr = "name"
|
||||||
|
}
|
||||||
|
n.subsystemLogger.Infof("%s %d claim %s in the last %s (total %d)",
|
||||||
|
n.progressAction, n.recentLogName, nameStr, tDuration, n.totalLogName)
|
||||||
|
|
||||||
|
n.recentLogName = 0
|
||||||
|
n.lastLogNameTime = now
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *claimProgressLogger) SetLastLogTime(time time.Time) {
|
||||||
|
n.lastLogNameTime = time
|
||||||
|
}
|
|
@ -17,10 +17,11 @@ func newVertex(hash *chainhash.Hash) *vertex {
|
||||||
// TODO: more professional to use msgpack here?
|
// TODO: more professional to use msgpack here?
|
||||||
|
|
||||||
// nbuf decodes the on-disk format of a node, which has the following form:
|
// nbuf decodes the on-disk format of a node, which has the following form:
|
||||||
// ch(1B) hash(32B)
|
//
|
||||||
// ...
|
// ch(1B) hash(32B)
|
||||||
// ch(1B) hash(32B)
|
// ...
|
||||||
// vhash(32B)
|
// ch(1B) hash(32B)
|
||||||
|
// vhash(32B)
|
||||||
type nbuf []byte
|
type nbuf []byte
|
||||||
|
|
||||||
func (nb nbuf) entries() int {
|
func (nb nbuf) entries() int {
|
||||||
|
|
|
@ -2,7 +2,6 @@ package node
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/claimtrie/change"
|
"github.com/lbryio/lbcd/claimtrie/change"
|
||||||
)
|
)
|
||||||
|
@ -17,16 +16,12 @@ type cacheLeaf struct {
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
nodes map[string]*cacheLeaf
|
nodes map[string]*cacheLeaf
|
||||||
order *list.List
|
order *list.List
|
||||||
mtx sync.Mutex
|
|
||||||
limit int
|
limit int
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Cache) insert(name []byte, n *Node, height int32) {
|
func (nc *Cache) insert(name []byte, n *Node, height int32) {
|
||||||
key := string(name)
|
key := string(name)
|
||||||
|
|
||||||
nc.mtx.Lock()
|
|
||||||
defer nc.mtx.Unlock()
|
|
||||||
|
|
||||||
existing := nc.nodes[key]
|
existing := nc.nodes[key]
|
||||||
if existing != nil {
|
if existing != nil {
|
||||||
existing.node = n
|
existing.node = n
|
||||||
|
@ -49,9 +44,6 @@ func (nc *Cache) insert(name []byte, n *Node, height int32) {
|
||||||
func (nc *Cache) fetch(name []byte, height int32) (*Node, []change.Change, int32) {
|
func (nc *Cache) fetch(name []byte, height int32) (*Node, []change.Change, int32) {
|
||||||
key := string(name)
|
key := string(name)
|
||||||
|
|
||||||
nc.mtx.Lock()
|
|
||||||
defer nc.mtx.Unlock()
|
|
||||||
|
|
||||||
existing := nc.nodes[key]
|
existing := nc.nodes[key]
|
||||||
if existing != nil && existing.height <= height {
|
if existing != nil && existing.height <= height {
|
||||||
nc.order.MoveToFront(existing.element)
|
nc.order.MoveToFront(existing.element)
|
||||||
|
@ -61,9 +53,6 @@ func (nc *Cache) fetch(name []byte, height int32) (*Node, []change.Change, int32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Cache) addChanges(changes []change.Change, height int32) {
|
func (nc *Cache) addChanges(changes []change.Change, height int32) {
|
||||||
nc.mtx.Lock()
|
|
||||||
defer nc.mtx.Unlock()
|
|
||||||
|
|
||||||
for _, c := range changes {
|
for _, c := range changes {
|
||||||
key := string(c.Name)
|
key := string(c.Name)
|
||||||
existing := nc.nodes[key]
|
existing := nc.nodes[key]
|
||||||
|
@ -74,9 +63,6 @@ func (nc *Cache) addChanges(changes []change.Change, height int32) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Cache) drop(names [][]byte) {
|
func (nc *Cache) drop(names [][]byte) {
|
||||||
nc.mtx.Lock()
|
|
||||||
defer nc.mtx.Unlock()
|
|
||||||
|
|
||||||
for _, name := range names {
|
for _, name := range names {
|
||||||
key := string(name)
|
key := string(name)
|
||||||
existing := nc.nodes[key]
|
existing := nc.nodes[key]
|
||||||
|
@ -89,8 +75,6 @@ func (nc *Cache) drop(names [][]byte) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nc *Cache) clear() {
|
func (nc *Cache) clear() {
|
||||||
nc.mtx.Lock()
|
|
||||||
defer nc.mtx.Unlock()
|
|
||||||
nc.nodes = map[string]*cacheLeaf{}
|
nc.nodes = map[string]*cacheLeaf{}
|
||||||
nc.order = list.New()
|
nc.order = list.New()
|
||||||
// we'll let the GC sort out the remains...
|
// we'll let the GC sort out the remains...
|
||||||
|
|
|
@ -29,6 +29,10 @@ func UseLogger(logger btclog.Logger) {
|
||||||
log = logger
|
log = logger
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func GetLogger() btclog.Logger {
|
||||||
|
return log
|
||||||
|
}
|
||||||
|
|
||||||
var loggedStrings = map[string]bool{} // is this gonna get too large?
|
var loggedStrings = map[string]bool{} // is this gonna get too large?
|
||||||
var loggedStringsMutex sync.Mutex
|
var loggedStringsMutex sync.Mutex
|
||||||
|
|
||||||
|
@ -42,6 +46,10 @@ func LogOnce(s string) {
|
||||||
log.Info(s)
|
log.Info(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func Log(s string) {
|
||||||
|
log.Info(s)
|
||||||
|
}
|
||||||
|
|
||||||
func Warn(s string) {
|
func Warn(s string) {
|
||||||
log.Warn(s)
|
log.Warn(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,14 +67,16 @@ func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) {
|
||||||
return nil, errors.Wrap(err, "in new node")
|
return nil, errors.Wrap(err, "in new node")
|
||||||
}
|
}
|
||||||
// TODO: how can we tell what needs to be cached?
|
// TODO: how can we tell what needs to be cached?
|
||||||
if nm.tempChanges == nil && height == nm.height && n != nil && (len(changes) > 7 || len(name) < 12) {
|
if nm.tempChanges == nil && height == nm.height && n != nil && (len(changes) > 4 || len(name) < 12) {
|
||||||
nm.cache.insert(name, n, height)
|
nm.cache.insert(name, n, height)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block
|
if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block
|
||||||
changes = append(changes, nm.tempChanges[string(name)]...)
|
changes = append(changes, nm.tempChanges[string(name)]...)
|
||||||
|
n = n.Clone()
|
||||||
|
} else if height != nm.height {
|
||||||
|
n = n.Clone()
|
||||||
}
|
}
|
||||||
n = n.Clone()
|
|
||||||
updated, err := nm.updateFromChanges(n, changes, height)
|
updated, err := nm.updateFromChanges(n, changes, height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "in update from changes")
|
return nil, errors.Wrap(err, "in update from changes")
|
||||||
|
@ -82,7 +84,7 @@ func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) {
|
||||||
if !updated {
|
if !updated {
|
||||||
n.AdjustTo(oldHeight, height, name)
|
n.AdjustTo(oldHeight, height, name)
|
||||||
}
|
}
|
||||||
if nm.tempChanges == nil && height == nm.height { // TODO: how many changes before we update the cache?
|
if nm.tempChanges == nil && height == nm.height {
|
||||||
nm.cache.insert(name, n, height)
|
nm.cache.insert(name, n, height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,6 +111,8 @@ type config struct {
|
||||||
SigNet bool `long:"signet" description:"Connect to signet (default RPC server: localhost:49245)"`
|
SigNet bool `long:"signet" description:"Connect to signet (default RPC server: localhost:49245)"`
|
||||||
Wallet bool `long:"wallet" description:"Connect to wallet RPC server instead (default: localhost:9244, testnet: localhost:19244, regtest: localhost:29244)"`
|
Wallet bool `long:"wallet" description:"Connect to wallet RPC server instead (default: localhost:9244, testnet: localhost:19244, regtest: localhost:29244)"`
|
||||||
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
ShowVersion bool `short:"V" long:"version" description:"Display version information and exit"`
|
||||||
|
Timed bool `short:"t" long:"timed" description:"Display RPC response time"`
|
||||||
|
Quiet bool `short:"q" long:"quiet" description:"Do not output results to stdout"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// normalizeAddress returns addr with the passed default port appended if
|
// normalizeAddress returns addr with the passed default port appended if
|
||||||
|
@ -175,10 +177,10 @@ func cleanAndExpandPath(path string) string {
|
||||||
// line options.
|
// line options.
|
||||||
//
|
//
|
||||||
// The configuration proceeds as follows:
|
// The configuration proceeds as follows:
|
||||||
// 1) Start with a default config with sane settings
|
// 1. Start with a default config with sane settings
|
||||||
// 2) Pre-parse the command line to check for an alternative config file
|
// 2. Pre-parse the command line to check for an alternative config file
|
||||||
// 3) Load configuration file overwriting defaults with any specified options
|
// 3. Load configuration file overwriting defaults with any specified options
|
||||||
// 4) Parse CLI options and overwrite/add any specified options
|
// 4. Parse CLI options and overwrite/add any specified options
|
||||||
//
|
//
|
||||||
// The above results in functioning properly without any config settings
|
// The above results in functioning properly without any config settings
|
||||||
// while still allowing the user to override settings with config files and
|
// while still allowing the user to override settings with config files and
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/btcjson"
|
"github.com/lbryio/lbcd/btcjson"
|
||||||
)
|
)
|
||||||
|
@ -133,6 +134,8 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
started := time.Now()
|
||||||
|
|
||||||
// Send the JSON-RPC request to the server using the user-specified
|
// Send the JSON-RPC request to the server using the user-specified
|
||||||
// connection configuration.
|
// connection configuration.
|
||||||
result, err := sendPostRequest(marshalledJSON, cfg)
|
result, err := sendPostRequest(marshalledJSON, cfg)
|
||||||
|
@ -141,6 +144,16 @@ func main() {
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if cfg.Timed {
|
||||||
|
elapsed := time.Since(started)
|
||||||
|
defer fmt.Fprintf(os.Stderr, "%s\n", elapsed)
|
||||||
|
}
|
||||||
|
|
||||||
|
var output io.Writer = os.Stdout
|
||||||
|
if cfg.Quiet {
|
||||||
|
output = io.Discard
|
||||||
|
}
|
||||||
|
|
||||||
// Choose how to display the result based on its type.
|
// Choose how to display the result based on its type.
|
||||||
strResult := string(result)
|
strResult := string(result)
|
||||||
if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") {
|
if strings.HasPrefix(strResult, "{") || strings.HasPrefix(strResult, "[") {
|
||||||
|
@ -150,7 +163,7 @@ func main() {
|
||||||
err)
|
err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println(dst.String())
|
fmt.Fprintln(output, dst.String())
|
||||||
|
|
||||||
} else if strings.HasPrefix(strResult, `"`) {
|
} else if strings.HasPrefix(strResult, `"`) {
|
||||||
var str string
|
var str string
|
||||||
|
@ -159,9 +172,9 @@ func main() {
|
||||||
err)
|
err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
fmt.Println(str)
|
fmt.Fprintln(output, str)
|
||||||
|
|
||||||
} else if strResult != "null" {
|
} else if strResult != "null" {
|
||||||
fmt.Println(strResult)
|
fmt.Fprintln(output, strResult)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
24
config.go
24
config.go
|
@ -409,10 +409,10 @@ func newConfigParser(cfg *config, so *serviceOptions, options flags.Options) *fl
|
||||||
// line options.
|
// line options.
|
||||||
//
|
//
|
||||||
// The configuration proceeds as follows:
|
// The configuration proceeds as follows:
|
||||||
// 1) Start with a default config with sane settings
|
// 1. Start with a default config with sane settings
|
||||||
// 2) Pre-parse the command line to check for an alternative config file
|
// 2. Pre-parse the command line to check for an alternative config file
|
||||||
// 3) Load configuration file overwriting defaults with any specified options
|
// 3. Load configuration file overwriting defaults with any specified options
|
||||||
// 4) Parse CLI options and overwrite/add any specified options
|
// 4. Parse CLI options and overwrite/add any specified options
|
||||||
//
|
//
|
||||||
// The above results in lbcd functioning properly without any config settings
|
// The above results in lbcd functioning properly without any config settings
|
||||||
// while still allowing the user to override settings with config files and
|
// while still allowing the user to override settings with config files and
|
||||||
|
@ -977,13 +977,8 @@ func loadConfig() (*config, []string, error) {
|
||||||
// Only allow TLS to be disabled if the RPC is bound to localhost
|
// Only allow TLS to be disabled if the RPC is bound to localhost
|
||||||
// addresses.
|
// addresses.
|
||||||
if !cfg.DisableRPC && cfg.DisableTLS {
|
if !cfg.DisableRPC && cfg.DisableTLS {
|
||||||
allowedTLSListeners := map[string]struct{}{
|
|
||||||
"localhost": {},
|
|
||||||
"127.0.0.1": {},
|
|
||||||
"::1": {},
|
|
||||||
}
|
|
||||||
for _, addr := range cfg.RPCListeners {
|
for _, addr := range cfg.RPCListeners {
|
||||||
host, _, err := net.SplitHostPort(addr)
|
_, _, err := net.SplitHostPort(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
str := "%s: RPC listen interface '%s' is " +
|
str := "%s: RPC listen interface '%s' is " +
|
||||||
"invalid: %v"
|
"invalid: %v"
|
||||||
|
@ -992,15 +987,6 @@ func loadConfig() (*config, []string, error) {
|
||||||
fmt.Fprintln(os.Stderr, usageMessage)
|
fmt.Fprintln(os.Stderr, usageMessage)
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if _, ok := allowedTLSListeners[host]; !ok {
|
|
||||||
str := "%s: the --notls option may not be used " +
|
|
||||||
"when binding RPC to non localhost " +
|
|
||||||
"addresses: %s"
|
|
||||||
err := fmt.Errorf(str, funcName, addr)
|
|
||||||
fmt.Fprintln(os.Stderr, err)
|
|
||||||
fmt.Fprintln(os.Stderr, usageMessage)
|
|
||||||
return nil, nil, err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,7 +20,6 @@ var (
|
||||||
// parameters which are command-line only. These fields are copied line-by-line
|
// parameters which are command-line only. These fields are copied line-by-line
|
||||||
// from "config" struct in "config.go", and the field names, types, and tags must
|
// from "config" struct in "config.go", and the field names, types, and tags must
|
||||||
// match for the test to work.
|
// match for the test to work.
|
||||||
//
|
|
||||||
type configCmdLineOnly struct {
|
type configCmdLineOnly struct {
|
||||||
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||||
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
|
DbType string `long:"dbtype" description:"Database backend to use for the Block Chain"`
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
/*
|
/*
|
||||||
Package connmgr implements a generic Bitcoin network connection manager.
|
Package connmgr implements a generic Bitcoin network connection manager.
|
||||||
|
|
||||||
Connection Manager Overview
|
# Connection Manager Overview
|
||||||
|
|
||||||
Connection Manager handles all the general connection concerns such as
|
Connection Manager handles all the general connection concerns such as
|
||||||
maintaining a set number of outbound connections, sourcing peers, banning,
|
maintaining a set number of outbound connections, sourcing peers, banning,
|
||||||
|
|
42
contrib/showminer.sh
Executable file
42
contrib/showminer.sh
Executable file
|
@ -0,0 +1,42 @@
|
||||||
|
#! /bin/bash
|
||||||
|
|
||||||
|
read -r -d '' help << EOM
|
||||||
|
$0 - helper script for displaying miner of a mined block.
|
||||||
|
|
||||||
|
Options:
|
||||||
|
|
||||||
|
-h Display this message.
|
||||||
|
|
||||||
|
--height Specify blockheight.
|
||||||
|
--hash Specify blockhash.
|
||||||
|
EOM
|
||||||
|
|
||||||
|
while getopts ":h-:" optchar; do
|
||||||
|
case "${optchar}" in
|
||||||
|
-)
|
||||||
|
case "${OPTARG}" in
|
||||||
|
hash)
|
||||||
|
blockhash="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
|
||||||
|
;;
|
||||||
|
height)
|
||||||
|
blockheight="${!OPTIND}"; OPTIND=$(( $OPTIND + 1 ))
|
||||||
|
blockhash=$(lbcctl getblockhash ${blockheight})
|
||||||
|
;;
|
||||||
|
*) echo "Unknown long option --${OPTARG}" >&2; exit -2 ;;
|
||||||
|
esac
|
||||||
|
;;
|
||||||
|
h) printf "${help}\n\n"; exit 2;;
|
||||||
|
*) echo "Unknown option -${OPTARG}" >&2; exit -2;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
|
||||||
|
|
||||||
|
block=$(lbcctl getblock $blockhash)
|
||||||
|
blockheight=$(lbcctl getblock $blockhash | jq -r .height)
|
||||||
|
|
||||||
|
coinbase_txid=$(echo ${block} | jq -r '.tx[0]')
|
||||||
|
coinbase_raw=$(lbcctl getrawtransaction ${coinbase_txid} 1)
|
||||||
|
coinbase=$(echo ${coinbase_raw} | jq '.vin[0].coinbase')
|
||||||
|
miner=$(echo ${coinbase} | grep -o '2f.*2f' | xxd -r -p | strings)
|
||||||
|
|
||||||
|
echo ${blockheight}: ${blockhash}: ${miner}
|
|
@ -5,7 +5,7 @@
|
||||||
/*
|
/*
|
||||||
Package database provides a block and metadata storage database.
|
Package database provides a block and metadata storage database.
|
||||||
|
|
||||||
Overview
|
# Overview
|
||||||
|
|
||||||
As of Feb 2016, there are over 400,000 blocks in the Bitcoin block chain and
|
As of Feb 2016, there are over 400,000 blocks in the Bitcoin block chain and
|
||||||
and over 112 million transactions (which turns out to be over 60GB of data).
|
and over 112 million transactions (which turns out to be over 60GB of data).
|
||||||
|
@ -18,15 +18,15 @@ storage, and strict checksums in key areas to ensure data integrity.
|
||||||
|
|
||||||
A quick overview of the features database provides are as follows:
|
A quick overview of the features database provides are as follows:
|
||||||
|
|
||||||
- Key/value metadata store
|
- Key/value metadata store
|
||||||
- Bitcoin block storage
|
- Bitcoin block storage
|
||||||
- Efficient retrieval of block headers and regions (transactions, scripts, etc)
|
- Efficient retrieval of block headers and regions (transactions, scripts, etc)
|
||||||
- Read-only and read-write transactions with both manual and managed modes
|
- Read-only and read-write transactions with both manual and managed modes
|
||||||
- Nested buckets
|
- Nested buckets
|
||||||
- Supports registration of backend databases
|
- Supports registration of backend databases
|
||||||
- Comprehensive test coverage
|
- Comprehensive test coverage
|
||||||
|
|
||||||
Database
|
# Database
|
||||||
|
|
||||||
The main entry point is the DB interface. It exposes functionality for
|
The main entry point is the DB interface. It exposes functionality for
|
||||||
transactional-based access and storage of metadata and block data. It is
|
transactional-based access and storage of metadata and block data. It is
|
||||||
|
@ -43,14 +43,14 @@ The Begin function provides an unmanaged transaction while the View and Update
|
||||||
functions provide a managed transaction. These are described in more detail
|
functions provide a managed transaction. These are described in more detail
|
||||||
below.
|
below.
|
||||||
|
|
||||||
Transactions
|
# Transactions
|
||||||
|
|
||||||
The Tx interface provides facilities for rolling back or committing changes that
|
The Tx interface provides facilities for rolling back or committing changes that
|
||||||
took place while the transaction was active. It also provides the root metadata
|
took place while the transaction was active. It also provides the root metadata
|
||||||
bucket under which all keys, values, and nested buckets are stored. A
|
bucket under which all keys, values, and nested buckets are stored. A
|
||||||
transaction can either be read-only or read-write and managed or unmanaged.
|
transaction can either be read-only or read-write and managed or unmanaged.
|
||||||
|
|
||||||
Managed versus Unmanaged Transactions
|
# Managed versus Unmanaged Transactions
|
||||||
|
|
||||||
A managed transaction is one where the caller provides a function to execute
|
A managed transaction is one where the caller provides a function to execute
|
||||||
within the context of the transaction and the commit or rollback is handled
|
within the context of the transaction and the commit or rollback is handled
|
||||||
|
@ -63,7 +63,7 @@ call Commit or Rollback when they are finished with it. Leaving transactions
|
||||||
open for long periods of time can have several adverse effects, so it is
|
open for long periods of time can have several adverse effects, so it is
|
||||||
recommended that managed transactions are used instead.
|
recommended that managed transactions are used instead.
|
||||||
|
|
||||||
Buckets
|
# Buckets
|
||||||
|
|
||||||
The Bucket interface provides the ability to manipulate key/value pairs and
|
The Bucket interface provides the ability to manipulate key/value pairs and
|
||||||
nested buckets as well as iterate through them.
|
nested buckets as well as iterate through them.
|
||||||
|
@ -73,7 +73,7 @@ CreateBucket, CreateBucketIfNotExists, and DeleteBucket functions work with
|
||||||
buckets. The ForEach function allows the caller to provide a function to be
|
buckets. The ForEach function allows the caller to provide a function to be
|
||||||
called with each key/value pair and nested bucket in the current bucket.
|
called with each key/value pair and nested bucket in the current bucket.
|
||||||
|
|
||||||
Metadata Bucket
|
# Metadata Bucket
|
||||||
|
|
||||||
As discussed above, all of the functions which are used to manipulate key/value
|
As discussed above, all of the functions which are used to manipulate key/value
|
||||||
pairs and nested buckets exist on the Bucket interface. The root metadata
|
pairs and nested buckets exist on the Bucket interface. The root metadata
|
||||||
|
@ -81,7 +81,7 @@ bucket is the upper-most bucket in which data is stored and is created at the
|
||||||
same time as the database. Use the Metadata function on the Tx interface
|
same time as the database. Use the Metadata function on the Tx interface
|
||||||
to retrieve it.
|
to retrieve it.
|
||||||
|
|
||||||
Nested Buckets
|
# Nested Buckets
|
||||||
|
|
||||||
The CreateBucket and CreateBucketIfNotExists functions on the Bucket interface
|
The CreateBucket and CreateBucketIfNotExists functions on the Bucket interface
|
||||||
provide the ability to create an arbitrary number of nested buckets. It is
|
provide the ability to create an arbitrary number of nested buckets. It is
|
||||||
|
|
|
@ -622,8 +622,8 @@ func (s *blockStore) syncBlocks() error {
|
||||||
// were partially written.
|
// were partially written.
|
||||||
//
|
//
|
||||||
// There are effectively two scenarios to consider here:
|
// There are effectively two scenarios to consider here:
|
||||||
// 1) Transient write failures from which recovery is possible
|
// 1. Transient write failures from which recovery is possible
|
||||||
// 2) More permanent failures such as hard disk death and/or removal
|
// 2. More permanent failures such as hard disk death and/or removal
|
||||||
//
|
//
|
||||||
// In either case, the write cursor will be repositioned to the old block file
|
// In either case, the write cursor will be repositioned to the old block file
|
||||||
// offset regardless of any other errors that occur while attempting to undo
|
// offset regardless of any other errors that occur while attempting to undo
|
||||||
|
|
|
@ -10,7 +10,7 @@ This driver is the recommended driver for use with lbcd. It makes use leveldb
|
||||||
for the metadata, flat files for block storage, and checksums in key areas to
|
for the metadata, flat files for block storage, and checksums in key areas to
|
||||||
ensure data integrity.
|
ensure data integrity.
|
||||||
|
|
||||||
Usage
|
# Usage
|
||||||
|
|
||||||
This package is a driver to the database package and provides the database type
|
This package is a driver to the database package and provides the database type
|
||||||
of "ffldb". The parameters the Open and Create functions take are the
|
of "ffldb". The parameters the Open and Create functions take are the
|
||||||
|
|
|
@ -318,13 +318,14 @@ func (iter *Iterator) ForceReseek() {
|
||||||
// unexpected keys and/or values.
|
// unexpected keys and/or values.
|
||||||
//
|
//
|
||||||
// For example:
|
// For example:
|
||||||
// iter := t.Iterator(nil, nil)
|
//
|
||||||
// for iter.Next() {
|
// iter := t.Iterator(nil, nil)
|
||||||
// if someCondition {
|
// for iter.Next() {
|
||||||
// t.Delete(iter.Key())
|
// if someCondition {
|
||||||
// iter.ForceReseek()
|
// t.Delete(iter.Key())
|
||||||
// }
|
// iter.ForceReseek()
|
||||||
// }
|
// }
|
||||||
|
// }
|
||||||
func (t *Mutable) Iterator(startKey, limitKey []byte) *Iterator {
|
func (t *Mutable) Iterator(startKey, limitKey []byte) *Iterator {
|
||||||
iter := &Iterator{
|
iter := &Iterator{
|
||||||
t: t,
|
t: t,
|
||||||
|
|
268
doc.go
268
doc.go
|
@ -18,144 +18,144 @@ on Windows. The -C (--configfile) flag, as shown below, can be used to override
|
||||||
this location.
|
this location.
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
lbcd [OPTIONS]
|
|
||||||
|
lbcd [OPTIONS]
|
||||||
|
|
||||||
Application Options:
|
Application Options:
|
||||||
--addcheckpoint= Add a custom checkpoint. Format:
|
|
||||||
'<height>:<hash>'
|
--addcheckpoint= Add a custom checkpoint. Format:
|
||||||
-a, --addpeer= Add a peer to connect with at startup
|
'<height>:<hash>'
|
||||||
--addrindex Maintain a full address-based transaction index
|
-a, --addpeer= Add a peer to connect with at startup
|
||||||
which makes the searchrawtransactions RPC
|
--addrindex Maintain a full address-based transaction index
|
||||||
available
|
which makes the searchrawtransactions RPC
|
||||||
--banduration= How long to ban misbehaving peers. Valid time
|
available
|
||||||
units are {s, m, h}. Minimum 1 second (default:
|
--banduration= How long to ban misbehaving peers. Valid time
|
||||||
24h0m0s)
|
units are {s, m, h}. Minimum 1 second (default:
|
||||||
--banthreshold= Maximum allowed ban score before disconnecting
|
24h0m0s)
|
||||||
and banning misbehaving peers. (default: 100)
|
--banthreshold= Maximum allowed ban score before disconnecting
|
||||||
--blockmaxsize= Maximum block size in bytes to be used when
|
and banning misbehaving peers. (default: 100)
|
||||||
creating a block (default: 750000)
|
--blockmaxsize= Maximum block size in bytes to be used when
|
||||||
--blockminsize= Mininum block size in bytes to be used when
|
creating a block (default: 750000)
|
||||||
creating a block
|
--blockminsize= Mininum block size in bytes to be used when
|
||||||
--blockmaxweight= Maximum block weight to be used when creating a
|
creating a block
|
||||||
block (default: 3000000)
|
--blockmaxweight= Maximum block weight to be used when creating a
|
||||||
--blockminweight= Mininum block weight to be used when creating a
|
block (default: 3000000)
|
||||||
block
|
--blockminweight= Mininum block weight to be used when creating a
|
||||||
--blockprioritysize= Size in bytes for high-priority/low-fee
|
block
|
||||||
transactions when creating a block (default:
|
--blockprioritysize= Size in bytes for high-priority/low-fee
|
||||||
50000)
|
transactions when creating a block (default:
|
||||||
--blocksonly Do not accept transactions from remote peers.
|
50000)
|
||||||
-C, --configfile= Path to configuration file
|
--blocksonly Do not accept transactions from remote peers.
|
||||||
--connect= Connect only to the specified peers at startup
|
-C, --configfile= Path to configuration file
|
||||||
--cpuprofile= Write CPU profile to the specified file
|
--connect= Connect only to the specified peers at startup
|
||||||
-b, --datadir= Directory to store data
|
--cpuprofile= Write CPU profile to the specified file
|
||||||
--dbtype= Database backend to use for the Block Chain
|
-b, --datadir= Directory to store data
|
||||||
(default: ffldb)
|
--dbtype= Database backend to use for the Block Chain
|
||||||
-d, --debuglevel= Logging level for all subsystems {trace, debug,
|
(default: ffldb)
|
||||||
info, warn, error, critical} -- You may also
|
-d, --debuglevel= Logging level for all subsystems {trace, debug,
|
||||||
specify
|
info, warn, error, critical} -- You may also
|
||||||
<subsystem>=<level>,<subsystem2>=<level>,... to
|
specify
|
||||||
set the log level for individual subsystems --
|
<subsystem>=<level>,<subsystem2>=<level>,... to
|
||||||
Use show to list available subsystems (default:
|
set the log level for individual subsystems --
|
||||||
info)
|
Use show to list available subsystems (default:
|
||||||
--dropaddrindex Deletes the address-based transaction index from
|
info)
|
||||||
the database on start up and then exits.
|
--dropaddrindex Deletes the address-based transaction index from
|
||||||
--dropcfindex Deletes the index used for committed filtering
|
the database on start up and then exits.
|
||||||
(CF) support from the database on start up and
|
--dropcfindex Deletes the index used for committed filtering
|
||||||
then exits.
|
(CF) support from the database on start up and
|
||||||
--droptxindex Deletes the hash-based transaction index from the
|
then exits.
|
||||||
database on start up and then exits.
|
--droptxindex Deletes the hash-based transaction index from the
|
||||||
--externalip= Add an ip to the list of local addresses we claim
|
database on start up and then exits.
|
||||||
to listen on to peers
|
--externalip= Add an ip to the list of local addresses we claim
|
||||||
--generate Generate (mine) bitcoins using the CPU
|
to listen on to peers
|
||||||
--limitfreerelay= Limit relay of transactions with no transaction
|
--generate Generate (mine) bitcoins using the CPU
|
||||||
fee to the given amount in thousands of bytes per
|
--limitfreerelay= Limit relay of transactions with no transaction
|
||||||
minute (default: 15)
|
fee to the given amount in thousands of bytes per
|
||||||
--listen= Add an interface/port to listen for connections
|
minute (default: 15)
|
||||||
(default all interfaces port: 9246, testnet:
|
--listen= Add an interface/port to listen for connections
|
||||||
19246, regtest: 29246, signet: 39246)
|
(default all interfaces port: 9246, testnet:
|
||||||
--logdir= Directory to log output
|
19246, regtest: 29246, signet: 39246)
|
||||||
--maxorphantx= Max number of orphan transactions to keep in
|
--logdir= Directory to log output
|
||||||
memory (default: 100)
|
--maxorphantx= Max number of orphan transactions to keep in
|
||||||
--maxpeers= Max number of inbound and outbound peers
|
memory (default: 100)
|
||||||
(default: 125)
|
--maxpeers= Max number of inbound and outbound peers
|
||||||
--memprofile= Write memory profile to the specified file
|
(default: 125)
|
||||||
--miningaddr= Add the specified payment address to the list of
|
--memprofile= Write memory profile to the specified file
|
||||||
addresses to use for generated blocks -- At least
|
--miningaddr= Add the specified payment address to the list of
|
||||||
one address is required if the generate option is
|
addresses to use for generated blocks -- At least
|
||||||
set
|
one address is required if the generate option is
|
||||||
--minrelaytxfee= The minimum transaction fee in BTC/kB to be
|
set
|
||||||
considered a non-zero fee. (default: 1e-05)
|
--minrelaytxfee= The minimum transaction fee in BTC/kB to be
|
||||||
--nobanning Disable banning of misbehaving peers
|
considered a non-zero fee. (default: 1e-05)
|
||||||
--nocfilters Disable committed filtering (CF) support
|
--nobanning Disable banning of misbehaving peers
|
||||||
--nocheckpoints Disable built-in checkpoints. Don't do this
|
--nocfilters Disable committed filtering (CF) support
|
||||||
unless you know what you're doing.
|
--nocheckpoints Disable built-in checkpoints. Don't do this
|
||||||
--nodnsseed Disable DNS seeding for peers
|
unless you know what you're doing.
|
||||||
--nolisten Disable listening for incoming connections --
|
--nodnsseed Disable DNS seeding for peers
|
||||||
NOTE: Listening is automatically disabled if the
|
--nolisten Disable listening for incoming connections --
|
||||||
--connect or --proxy options are used without
|
NOTE: Listening is automatically disabled if the
|
||||||
also specifying listen interfaces via --listen
|
--connect or --proxy options are used without
|
||||||
--noonion Disable connecting to tor hidden services
|
also specifying listen interfaces via --listen
|
||||||
--nopeerbloomfilters Disable bloom filtering support
|
--noonion Disable connecting to tor hidden services
|
||||||
--norelaypriority Do not require free or low-fee transactions to
|
--nopeerbloomfilters Disable bloom filtering support
|
||||||
have high priority for relaying
|
--norelaypriority Do not require free or low-fee transactions to
|
||||||
--norpc Disable built-in RPC server -- NOTE: The RPC
|
have high priority for relaying
|
||||||
server is disabled by default if no
|
--norpc Disable built-in RPC server -- NOTE: The RPC
|
||||||
rpcuser/rpcpass or rpclimituser/rpclimitpass is
|
server is disabled by default if no
|
||||||
specified
|
rpcuser/rpcpass or rpclimituser/rpclimitpass is
|
||||||
--notls Disable TLS for the RPC server -- NOTE: This is
|
specified
|
||||||
only allowed if the RPC server is bound to
|
--notls Disable TLS for the RPC server
|
||||||
localhost
|
--onion= Connect to tor hidden services via SOCKS5 proxy
|
||||||
--onion= Connect to tor hidden services via SOCKS5 proxy
|
(eg. 127.0.0.1:9050)
|
||||||
(eg. 127.0.0.1:9050)
|
--onionpass= Password for onion proxy server
|
||||||
--onionpass= Password for onion proxy server
|
--onionuser= Username for onion proxy server
|
||||||
--onionuser= Username for onion proxy server
|
--profile= Enable HTTP profiling on given port -- NOTE port
|
||||||
--profile= Enable HTTP profiling on given port -- NOTE port
|
must be between 1024 and 65536
|
||||||
must be between 1024 and 65536
|
--proxy= Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)
|
||||||
--proxy= Connect via SOCKS5 proxy (eg. 127.0.0.1:9050)
|
--proxypass= Password for proxy server
|
||||||
--proxypass= Password for proxy server
|
--proxyuser= Username for proxy server
|
||||||
--proxyuser= Username for proxy server
|
--regtest Use the regression test network
|
||||||
--regtest Use the regression test network
|
--rejectnonstd Reject non-standard transactions regardless of
|
||||||
--rejectnonstd Reject non-standard transactions regardless of
|
the default settings for the active network.
|
||||||
the default settings for the active network.
|
--relaynonstd Relay non-standard transactions regardless of the
|
||||||
--relaynonstd Relay non-standard transactions regardless of the
|
default settings for the active network.
|
||||||
default settings for the active network.
|
--rpccert= File containing the certificate file
|
||||||
--rpccert= File containing the certificate file
|
--rpckey= File containing the certificate key
|
||||||
--rpckey= File containing the certificate key
|
--rpclimitpass= Password for limited RPC connections
|
||||||
--rpclimitpass= Password for limited RPC connections
|
--rpclimituser= Username for limited RPC connections
|
||||||
--rpclimituser= Username for limited RPC connections
|
--rpclisten= Add an interface/port to listen for RPC
|
||||||
--rpclisten= Add an interface/port to listen for RPC
|
connections (default port: 9245, testnet: 19245, regtest: 29245)
|
||||||
connections (default port: 9245, testnet: 19245, regtest: 29245)
|
--rpcmaxclients= Max number of RPC clients for standard
|
||||||
--rpcmaxclients= Max number of RPC clients for standard
|
connections (default: 10)
|
||||||
connections (default: 10)
|
--rpcmaxconcurrentreqs= Max number of concurrent RPC requests that may be
|
||||||
--rpcmaxconcurrentreqs= Max number of concurrent RPC requests that may be
|
processed concurrently (default: 20)
|
||||||
processed concurrently (default: 20)
|
--rpcmaxwebsockets= Max number of RPC websocket connections (default:
|
||||||
--rpcmaxwebsockets= Max number of RPC websocket connections (default:
|
25)
|
||||||
25)
|
--rpcquirks Mirror some JSON-RPC quirks of Bitcoin Core --
|
||||||
--rpcquirks Mirror some JSON-RPC quirks of Bitcoin Core --
|
NOTE: Discouraged unless interoperability issues
|
||||||
NOTE: Discouraged unless interoperability issues
|
need to be worked around
|
||||||
need to be worked around
|
-P, --rpcpass= Password for RPC connections
|
||||||
-P, --rpcpass= Password for RPC connections
|
-u, --rpcuser= Username for RPC connections
|
||||||
-u, --rpcuser= Username for RPC connections
|
--sigcachemaxsize= The maximum number of entries in the signature
|
||||||
--sigcachemaxsize= The maximum number of entries in the signature
|
verification cache (default: 100000)
|
||||||
verification cache (default: 100000)
|
--simnet Use the simulation test network
|
||||||
--simnet Use the simulation test network
|
--testnet Use the test network
|
||||||
--testnet Use the test network
|
--torisolation Enable Tor stream isolation by randomizing user
|
||||||
--torisolation Enable Tor stream isolation by randomizing user
|
credentials for each connection.
|
||||||
credentials for each connection.
|
--trickleinterval= Minimum time between attempts to send new
|
||||||
--trickleinterval= Minimum time between attempts to send new
|
inventory to a connected peer (default: 10s)
|
||||||
inventory to a connected peer (default: 10s)
|
--txindex Maintain a full hash-based transaction index
|
||||||
--txindex Maintain a full hash-based transaction index
|
which makes all transactions available via the
|
||||||
which makes all transactions available via the
|
getrawtransaction RPC
|
||||||
getrawtransaction RPC
|
--uacomment= Comment to add to the user agent -- See BIP 14
|
||||||
--uacomment= Comment to add to the user agent -- See BIP 14
|
for more information.
|
||||||
for more information.
|
--upnp Use UPnP to map our listening port outside of NAT
|
||||||
--upnp Use UPnP to map our listening port outside of NAT
|
-V, --version Display version information and exit
|
||||||
-V, --version Display version information and exit
|
--whitelist= Add an IP network or IP that will not be banned.
|
||||||
--whitelist= Add an IP network or IP that will not be banned.
|
(eg. 192.168.1.0/24 or ::1)
|
||||||
(eg. 192.168.1.0/24 or ::1)
|
|
||||||
|
|
||||||
Help Options:
|
Help Options:
|
||||||
-h, --help Show this help message
|
|
||||||
|
|
||||||
|
-h, --help Show this help message
|
||||||
*/
|
*/
|
||||||
package main
|
package main
|
||||||
|
|
42
fees/doc.go
42
fees/doc.go
|
@ -7,11 +7,11 @@ Package fees provides decred-specific methods for tracking and estimating fee
|
||||||
rates for new transactions to be mined into the network. Fee rate estimation has
|
rates for new transactions to be mined into the network. Fee rate estimation has
|
||||||
two main goals:
|
two main goals:
|
||||||
|
|
||||||
- Ensuring transactions are mined within a target _confirmation range_
|
- Ensuring transactions are mined within a target _confirmation range_
|
||||||
(expressed in blocks);
|
(expressed in blocks);
|
||||||
- Attempting to minimize fees while maintaining be above restriction.
|
- Attempting to minimize fees while maintaining be above restriction.
|
||||||
|
|
||||||
Preliminaries
|
# Preliminaries
|
||||||
|
|
||||||
There are two main regimes against which fee estimation needs to be evaluated
|
There are two main regimes against which fee estimation needs to be evaluated
|
||||||
according to how full blocks being mined are (and consequently how important fee
|
according to how full blocks being mined are (and consequently how important fee
|
||||||
|
@ -39,7 +39,7 @@ The current approach to implement this estimation is based on bitcoin core's
|
||||||
algorithm. References [1] and [2] provide a high level description of how it
|
algorithm. References [1] and [2] provide a high level description of how it
|
||||||
works there. Actual code is linked in references [3] and [4].
|
works there. Actual code is linked in references [3] and [4].
|
||||||
|
|
||||||
Outline of the Algorithm
|
# Outline of the Algorithm
|
||||||
|
|
||||||
The algorithm is currently based in fee estimation as used in v0.14 of bitcoin
|
The algorithm is currently based in fee estimation as used in v0.14 of bitcoin
|
||||||
core (which is also the basis for the v0.15+ method). A more comprehensive
|
core (which is also the basis for the v0.15+ method). A more comprehensive
|
||||||
|
@ -54,31 +54,31 @@ The basic algorithm is as follows (as executed by a single full node):
|
||||||
|
|
||||||
Stats building stage:
|
Stats building stage:
|
||||||
|
|
||||||
- For each transaction observed entering mempool, record the block at which it
|
- For each transaction observed entering mempool, record the block at which it
|
||||||
was first seen
|
was first seen
|
||||||
- For each mined transaction which was previously observed to enter the mempool,
|
- For each mined transaction which was previously observed to enter the mempool,
|
||||||
record how long (in blocks) it took to be mined and its fee rate
|
record how long (in blocks) it took to be mined and its fee rate
|
||||||
- Group mined transactions into fee rate _buckets_ and _confirmation ranges_,
|
- Group mined transactions into fee rate _buckets_ and _confirmation ranges_,
|
||||||
creating a table of how many transactions were mined at each confirmation
|
creating a table of how many transactions were mined at each confirmation
|
||||||
range and fee rate bucket and their total committed fee
|
range and fee rate bucket and their total committed fee
|
||||||
- Whenever a new block is mined, decay older transactions to account for a
|
- Whenever a new block is mined, decay older transactions to account for a
|
||||||
dynamic fee environment
|
dynamic fee environment
|
||||||
|
|
||||||
Estimation stage:
|
Estimation stage:
|
||||||
|
|
||||||
- Input a target confirmation range (how many blocks to wait for the tx to be
|
- Input a target confirmation range (how many blocks to wait for the tx to be
|
||||||
mined)
|
mined)
|
||||||
- Starting at the highest fee bucket, look for buckets where the chance of
|
- Starting at the highest fee bucket, look for buckets where the chance of
|
||||||
confirmation within the desired confirmation window is > 95%
|
confirmation within the desired confirmation window is > 95%
|
||||||
- Average all such buckets to get the estimated fee rate
|
- Average all such buckets to get the estimated fee rate
|
||||||
|
|
||||||
Simulation
|
# Simulation
|
||||||
|
|
||||||
Development of the estimator was originally performed and simulated using the
|
Development of the estimator was originally performed and simulated using the
|
||||||
code in [5]. Simulation of the current code can be performed by using the
|
code in [5]. Simulation of the current code can be performed by using the
|
||||||
dcrfeesim tool available in [6].
|
dcrfeesim tool available in [6].
|
||||||
|
|
||||||
Acknowledgements
|
# Acknowledgements
|
||||||
|
|
||||||
Thanks to @davecgh for providing the initial review of the results and the
|
Thanks to @davecgh for providing the initial review of the results and the
|
||||||
original developers of the bitcoin core code (the brunt of which seems to have
|
original developers of the bitcoin core code (the brunt of which seems to have
|
||||||
|
|
4
go.mod
4
go.mod
|
@ -1,6 +1,6 @@
|
||||||
module github.com/lbryio/lbcd
|
module github.com/lbryio/lbcd
|
||||||
|
|
||||||
go 1.18
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
github.com/btcsuite/btclog v0.0.0-20170628155309-84c8d2346e9f
|
||||||
|
@ -14,7 +14,7 @@ require (
|
||||||
github.com/felixge/fgprof v0.9.2
|
github.com/felixge/fgprof v0.9.2
|
||||||
github.com/jessevdk/go-flags v1.5.0
|
github.com/jessevdk/go-flags v1.5.0
|
||||||
github.com/jrick/logrotate v1.0.0
|
github.com/jrick/logrotate v1.0.0
|
||||||
github.com/lbryio/lbcutil v1.0.202-rc3
|
github.com/lbryio/lbcutil v1.0.202
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/shirou/gopsutil/v3 v3.22.4
|
github.com/shirou/gopsutil/v3 v3.22.4
|
||||||
github.com/spf13/cobra v1.1.3
|
github.com/spf13/cobra v1.1.3
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -284,8 +284,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
github.com/labstack/echo/v4 v4.1.11/go.mod h1:i541M3Fj6f76NZtHSj7TXnyM8n2gaodfvfxNnFqi74g=
|
||||||
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
github.com/labstack/echo/v4 v4.5.0/go.mod h1:czIriw4a0C1dFun+ObrXp7ok03xON0N1awStJ6ArI7Y=
|
||||||
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
github.com/labstack/gommon v0.3.0/go.mod h1:MULnywXg0yavhxWKc+lOruYdAhDwPK9wf0OL7NoOu+k=
|
||||||
github.com/lbryio/lbcutil v1.0.202-rc3 h1:J7zYnIj3iN/ndPYKqMKBukLaLM1GhCEaiaMOYIMdUCU=
|
github.com/lbryio/lbcutil v1.0.202 h1:L0aRMs2bdCUAicD8Xe4NmUEvevDDea3qkIpCSACnftI=
|
||||||
github.com/lbryio/lbcutil v1.0.202-rc3/go.mod h1:LGPtVBBzh4cFXfLFb8ginlFcbA2QwumLNFd0yk/as2o=
|
github.com/lbryio/lbcutil v1.0.202/go.mod h1:LGPtVBBzh4cFXfLFb8ginlFcbA2QwumLNFd0yk/as2o=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
||||||
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 h1:aczX6NMOtt6L4YT0fQvKkDK6LZEtdOso9sUH89V1+P0=
|
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281 h1:aczX6NMOtt6L4YT0fQvKkDK6LZEtdOso9sUH89V1+P0=
|
||||||
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281/go.mod h1:lc+czkgO/8F7puNki5jk8QyujbfK1LOT7Wl0ON2hxyk=
|
github.com/lufia/plan9stats v0.0.0-20220517141722-cf486979b281/go.mod h1:lc+czkgO/8F7puNki5jk8QyujbfK1LOT7Wl0ON2hxyk=
|
||||||
|
|
|
@ -282,19 +282,20 @@ func testBIP0009(t *testing.T, forkKey string, deploymentID uint32) {
|
||||||
// - Assert the chain height is 0 and the state is ThresholdDefined
|
// - Assert the chain height is 0 and the state is ThresholdDefined
|
||||||
// - Generate 1 fewer blocks than needed to reach the first state transition
|
// - Generate 1 fewer blocks than needed to reach the first state transition
|
||||||
// - Assert chain height is expected and state is still ThresholdDefined
|
// - Assert chain height is expected and state is still ThresholdDefined
|
||||||
|
//
|
||||||
// - Generate 1 more block to reach the first state transition
|
// - Generate 1 more block to reach the first state transition
|
||||||
// - Assert chain height is expected and state moved to ThresholdStarted
|
// - Assert chain height is expected and state moved to ThresholdStarted
|
||||||
// - Generate enough blocks to reach the next state transition window, but only
|
// - Generate enough blocks to reach the next state transition window, but only
|
||||||
// signal support in 1 fewer than the required number to achieve
|
// signal support in 1 fewer than the required number to achieve
|
||||||
// ThresholdLockedIn
|
// ThresholdLockedIn
|
||||||
// - Assert chain height is expected and state is still ThresholdStarted
|
// - Assert chain height is expected and state is still ThresholdStarted
|
||||||
// - Generate enough blocks to reach the next state transition window with only
|
// - Generate enough blocks to reach the next state transition window with only
|
||||||
// the exact number of blocks required to achieve locked in status signalling
|
// the exact number of blocks required to achieve locked in status signalling
|
||||||
// support.
|
// support.
|
||||||
// - Assert chain height is expected and state moved to ThresholdLockedIn
|
// - Assert chain height is expected and state moved to ThresholdLockedIn
|
||||||
// - Generate 1 fewer blocks than needed to reach the next state transition
|
// - Generate 1 fewer blocks than needed to reach the next state transition
|
||||||
// - Assert chain height is expected and state is still ThresholdLockedIn
|
// - Assert chain height is expected and state is still ThresholdLockedIn
|
||||||
// - Generate 1 more block to reach the next state transition
|
// - Generate 1 more block to reach the next state transition
|
||||||
// - Assert chain height is expected and state moved to ThresholdActive
|
// - Assert chain height is expected and state moved to ThresholdActive
|
||||||
func TestBIP0009(t *testing.T) {
|
func TestBIP0009(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
@ -309,11 +310,14 @@ func TestBIP0009(t *testing.T) {
|
||||||
// Overview:
|
// Overview:
|
||||||
// - Generate block 1
|
// - Generate block 1
|
||||||
// - Assert bit is NOT set (ThresholdDefined)
|
// - Assert bit is NOT set (ThresholdDefined)
|
||||||
|
//
|
||||||
// - Generate enough blocks to reach first state transition
|
// - Generate enough blocks to reach first state transition
|
||||||
// - Assert bit is NOT set for block prior to state transition
|
// - Assert bit is NOT set for block prior to state transition
|
||||||
// - Assert bit is set for block at state transition (ThresholdStarted)
|
// - Assert bit is set for block at state transition (ThresholdStarted)
|
||||||
|
//
|
||||||
// - Generate enough blocks to reach second state transition
|
// - Generate enough blocks to reach second state transition
|
||||||
// - Assert bit is set for block at state transition (ThresholdLockedIn)
|
// - Assert bit is set for block at state transition (ThresholdLockedIn)
|
||||||
|
//
|
||||||
// - Generate enough blocks to reach third state transition
|
// - Generate enough blocks to reach third state transition
|
||||||
// - Assert bit is set for block prior to state transition (ThresholdLockedIn)
|
// - Assert bit is set for block prior to state transition (ThresholdLockedIn)
|
||||||
// - Assert bit is NOT set for block at state transition (ThresholdActive)
|
// - Assert bit is NOT set for block at state transition (ThresholdActive)
|
||||||
|
|
|
@ -95,17 +95,22 @@ func makeTestOutput(r *rpctest.Harness, t *testing.T,
|
||||||
// them.
|
// them.
|
||||||
//
|
//
|
||||||
// Overview:
|
// Overview:
|
||||||
// - Pre soft-fork:
|
|
||||||
// - Transactions with non-final lock-times from the PoV of MTP should be
|
|
||||||
// rejected from the mempool.
|
|
||||||
// - Transactions within non-final MTP based lock-times should be accepted
|
|
||||||
// in valid blocks.
|
|
||||||
//
|
//
|
||||||
// - Post soft-fork:
|
// - Pre soft-fork:
|
||||||
// - Transactions with non-final lock-times from the PoV of MTP should be
|
//
|
||||||
// rejected from the mempool and when found within otherwise valid blocks.
|
// - Transactions with non-final lock-times from the PoV of MTP should be
|
||||||
// - Transactions with final lock-times from the PoV of MTP should be
|
// rejected from the mempool.
|
||||||
// accepted to the mempool and mined in future block.
|
//
|
||||||
|
// - Transactions within non-final MTP based lock-times should be accepted
|
||||||
|
// in valid blocks.
|
||||||
|
//
|
||||||
|
// - Post soft-fork:
|
||||||
|
//
|
||||||
|
// - Transactions with non-final lock-times from the PoV of MTP should be
|
||||||
|
// rejected from the mempool and when found within otherwise valid blocks.
|
||||||
|
//
|
||||||
|
// - Transactions with final lock-times from the PoV of MTP should be
|
||||||
|
// accepted to the mempool and mined in future block.
|
||||||
func TestBIP0113Activation(t *testing.T) {
|
func TestBIP0113Activation(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
@ -391,13 +396,13 @@ func assertTxInBlock(r *rpctest.Harness, t *testing.T, blockHash *chainhash.Hash
|
||||||
// 112 and BIP 68 rule-set after the activation of the CSV-package soft-fork.
|
// 112 and BIP 68 rule-set after the activation of the CSV-package soft-fork.
|
||||||
//
|
//
|
||||||
// Overview:
|
// Overview:
|
||||||
// - Pre soft-fork:
|
// - Pre soft-fork:
|
||||||
// - A transaction spending a CSV output validly should be rejected from the
|
// - A transaction spending a CSV output validly should be rejected from the
|
||||||
// mempool, but accepted in a valid generated block including the
|
// mempool, but accepted in a valid generated block including the
|
||||||
// transaction.
|
// transaction.
|
||||||
// - Post soft-fork:
|
// - Post soft-fork:
|
||||||
// - See the cases exercised within the table driven tests towards the end
|
// - See the cases exercised within the table driven tests towards the end
|
||||||
// of this test.
|
// of this test.
|
||||||
func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
func TestBIP0068AndBIP0112Activation(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|
|
@ -13,12 +13,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/chaincfg"
|
"github.com/lbryio/lbcd/chaincfg"
|
||||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||||
"github.com/lbryio/lbcd/integration/rpctest"
|
"github.com/lbryio/lbcd/integration/rpctest"
|
||||||
"github.com/lbryio/lbcd/rpcclient"
|
"github.com/lbryio/lbcd/rpcclient"
|
||||||
|
"github.com/lbryio/lbcd/txscript"
|
||||||
|
"github.com/lbryio/lbcd/wire"
|
||||||
|
"github.com/lbryio/lbcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
|
func testGetBestBlock(r *rpctest.Harness, t *testing.T) {
|
||||||
|
@ -133,13 +138,278 @@ func testBulkClient(r *rpctest.Harness, t *testing.T) {
|
||||||
t.Fatalf("expected hash %s to be in generated hash list", blockHash)
|
t.Fatalf("expected hash %s to be in generated hash list", blockHash)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testGetBlockStats(r *rpctest.Harness, t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
baseFeeRate := int64(10)
|
||||||
|
txValue := int64(50000000)
|
||||||
|
txQuantity := 10
|
||||||
|
txs := make([]*lbcutil.Tx, txQuantity)
|
||||||
|
fees := make([]int64, txQuantity)
|
||||||
|
sizes := make([]int64, txQuantity)
|
||||||
|
feeRates := make([]int64, txQuantity)
|
||||||
|
var outputCount int
|
||||||
|
|
||||||
|
// Generate test sample.
|
||||||
|
for i := 0; i < txQuantity; i++ {
|
||||||
|
address, err := r.NewAddress()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to generate address: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pkScript, err := txscript.PayToAddrScript(address)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to generate PKScript: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// This feerate is not the actual feerate. See comment below.
|
||||||
|
feeRate := baseFeeRate * int64(i)
|
||||||
|
|
||||||
|
tx, err := r.CreateTransaction([]*wire.TxOut{wire.NewTxOut(txValue, pkScript)}, lbcutil.Amount(feeRate), true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to generate segwit transaction: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
txs[i] = lbcutil.NewTx(tx)
|
||||||
|
sizes[i] = int64(tx.SerializeSize())
|
||||||
|
|
||||||
|
// memWallet.fundTx makes some assumptions when calculating fees.
|
||||||
|
// For instance, it assumes the signature script has exactly 108 bytes
|
||||||
|
// and it does not account for the size of the change output.
|
||||||
|
// This needs to be taken into account when getting the true feerate.
|
||||||
|
scriptSigOffset := 108 - len(tx.TxIn[0].SignatureScript)
|
||||||
|
changeOutputSize := tx.TxOut[len(tx.TxOut)-1].SerializeSize()
|
||||||
|
fees[i] = (sizes[i] + int64(scriptSigOffset) - int64(changeOutputSize)) * feeRate
|
||||||
|
feeRates[i] = fees[i] / sizes[i]
|
||||||
|
|
||||||
|
outputCount += len(tx.TxOut)
|
||||||
|
}
|
||||||
|
|
||||||
|
stats := func(slice []int64) (int64, int64, int64, int64, int64) {
|
||||||
|
var total, average, min, max, median int64
|
||||||
|
min = slice[0]
|
||||||
|
length := len(slice)
|
||||||
|
for _, item := range slice {
|
||||||
|
if min > item {
|
||||||
|
min = item
|
||||||
|
}
|
||||||
|
if max < item {
|
||||||
|
max = item
|
||||||
|
}
|
||||||
|
total += item
|
||||||
|
}
|
||||||
|
average = total / int64(length)
|
||||||
|
sort.Slice(slice, func(i, j int) bool { return slice[i] < slice[j] })
|
||||||
|
if length == 0 {
|
||||||
|
median = 0
|
||||||
|
} else if length%2 == 0 {
|
||||||
|
median = (slice[length/2-1] + slice[length/2]) / 2
|
||||||
|
} else {
|
||||||
|
median = slice[length/2]
|
||||||
|
}
|
||||||
|
return total, average, min, max, median
|
||||||
|
}
|
||||||
|
|
||||||
|
totalFee, avgFee, minFee, maxFee, medianFee := stats(fees)
|
||||||
|
totalSize, avgSize, minSize, maxSize, medianSize := stats(sizes)
|
||||||
|
_, avgFeeRate, minFeeRate, maxFeeRate, _ := stats(feeRates)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
txs []*lbcutil.Tx
|
||||||
|
stats []string
|
||||||
|
expectedResults map[string]interface{}
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "empty block",
|
||||||
|
txs: []*lbcutil.Tx{},
|
||||||
|
stats: []string{},
|
||||||
|
expectedResults: map[string]interface{}{
|
||||||
|
"avgfee": int64(0),
|
||||||
|
"avgfeerate": int64(0),
|
||||||
|
"avgtxsize": int64(0),
|
||||||
|
"feerate_percentiles": []int64{0, 0, 0, 0, 0},
|
||||||
|
"ins": int64(0),
|
||||||
|
"maxfee": int64(0),
|
||||||
|
"maxfeerate": int64(0),
|
||||||
|
"maxtxsize": int64(0),
|
||||||
|
"medianfee": int64(0),
|
||||||
|
"mediantxsize": int64(0),
|
||||||
|
"minfee": int64(0),
|
||||||
|
"mintxsize": int64(0),
|
||||||
|
"outs": int64(1),
|
||||||
|
"swtotal_size": int64(0),
|
||||||
|
"swtotal_weight": int64(0),
|
||||||
|
"swtxs": int64(0),
|
||||||
|
"total_out": int64(0),
|
||||||
|
"total_size": int64(0),
|
||||||
|
"total_weight": int64(0),
|
||||||
|
"txs": int64(1),
|
||||||
|
"utxo_increase": int64(1),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "block with 10 transactions + coinbase",
|
||||||
|
txs: txs,
|
||||||
|
stats: []string{"avgfee", "avgfeerate", "avgtxsize", "feerate_percentiles",
|
||||||
|
"ins", "maxfee", "maxfeerate", "maxtxsize", "medianfee", "mediantxsize",
|
||||||
|
"minfee", "minfeerate", "mintxsize", "outs", "subsidy", "swtxs",
|
||||||
|
"total_size", "total_weight", "totalfee", "txs", "utxo_increase"},
|
||||||
|
expectedResults: map[string]interface{}{
|
||||||
|
"avgfee": avgFee,
|
||||||
|
"avgfeerate": avgFeeRate,
|
||||||
|
"avgtxsize": avgSize,
|
||||||
|
"feerate_percentiles": []int64{feeRates[0], feeRates[2],
|
||||||
|
feeRates[4], feeRates[7], feeRates[8]},
|
||||||
|
"ins": int64(txQuantity),
|
||||||
|
"maxfee": maxFee,
|
||||||
|
"maxfeerate": maxFeeRate,
|
||||||
|
"maxtxsize": maxSize,
|
||||||
|
"medianfee": medianFee,
|
||||||
|
"mediantxsize": medianSize,
|
||||||
|
"minfee": minFee,
|
||||||
|
"minfeerate": minFeeRate,
|
||||||
|
"mintxsize": minSize,
|
||||||
|
"outs": int64(outputCount + 1), // Coinbase output also counts.
|
||||||
|
"subsidy": int64(100000000),
|
||||||
|
"swtotal_weight": nil, // This stat was not selected, so it should be nil.
|
||||||
|
"swtxs": int64(0),
|
||||||
|
"total_size": totalSize,
|
||||||
|
"total_weight": totalSize * 4,
|
||||||
|
"totalfee": totalFee,
|
||||||
|
"txs": int64(txQuantity + 1), // Coinbase transaction also counts.
|
||||||
|
"utxo_increase": int64(outputCount + 1 - txQuantity),
|
||||||
|
"utxo_size_inc": nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
// Submit a new block with the provided transactions.
|
||||||
|
block, err := r.GenerateAndSubmitBlock(test.txs, -1, time.Time{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Unable to generate block: %v from test %s", err, test.name)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockStats, err := r.GetBlockStats(block.Hash(), &test.stats)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Call to `getblockstats` on test %s failed: %v", test.name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if blockStats.Height != (*int64)(nil) && *blockStats.Height != int64(block.Height()) {
|
||||||
|
t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, "height", block.Height(), *blockStats.Height)
|
||||||
|
}
|
||||||
|
|
||||||
|
for stat, value := range test.expectedResults {
|
||||||
|
var result interface{}
|
||||||
|
switch stat {
|
||||||
|
case "avgfee":
|
||||||
|
result = blockStats.AverageFee
|
||||||
|
case "avgfeerate":
|
||||||
|
result = blockStats.AverageFeeRate
|
||||||
|
case "avgtxsize":
|
||||||
|
result = blockStats.AverageTxSize
|
||||||
|
case "feerate_percentiles":
|
||||||
|
result = blockStats.FeeratePercentiles
|
||||||
|
case "blockhash":
|
||||||
|
result = blockStats.Hash
|
||||||
|
case "height":
|
||||||
|
result = blockStats.Height
|
||||||
|
case "ins":
|
||||||
|
result = blockStats.Ins
|
||||||
|
case "maxfee":
|
||||||
|
result = blockStats.MaxFee
|
||||||
|
case "maxfeerate":
|
||||||
|
result = blockStats.MaxFeeRate
|
||||||
|
case "maxtxsize":
|
||||||
|
result = blockStats.MaxTxSize
|
||||||
|
case "medianfee":
|
||||||
|
result = blockStats.MedianFee
|
||||||
|
case "mediantime":
|
||||||
|
result = blockStats.MedianTime
|
||||||
|
case "mediantxsize":
|
||||||
|
result = blockStats.MedianTxSize
|
||||||
|
case "minfee":
|
||||||
|
result = blockStats.MinFee
|
||||||
|
case "minfeerate":
|
||||||
|
result = blockStats.MinFeeRate
|
||||||
|
case "mintxsize":
|
||||||
|
result = blockStats.MinTxSize
|
||||||
|
case "outs":
|
||||||
|
result = blockStats.Outs
|
||||||
|
case "swtotal_size":
|
||||||
|
result = blockStats.SegWitTotalSize
|
||||||
|
case "swtotal_weight":
|
||||||
|
result = blockStats.SegWitTotalWeight
|
||||||
|
case "swtxs":
|
||||||
|
result = blockStats.SegWitTxs
|
||||||
|
case "subsidy":
|
||||||
|
result = blockStats.Subsidy
|
||||||
|
case "time":
|
||||||
|
result = blockStats.Time
|
||||||
|
case "total_out":
|
||||||
|
result = blockStats.TotalOut
|
||||||
|
case "total_size":
|
||||||
|
result = blockStats.TotalSize
|
||||||
|
case "total_weight":
|
||||||
|
result = blockStats.TotalWeight
|
||||||
|
case "totalfee":
|
||||||
|
result = blockStats.TotalFee
|
||||||
|
case "txs":
|
||||||
|
result = blockStats.Txs
|
||||||
|
case "utxo_increase":
|
||||||
|
result = blockStats.UTXOIncrease
|
||||||
|
case "utxo_size_inc":
|
||||||
|
result = blockStats.UTXOSizeIncrease
|
||||||
|
}
|
||||||
|
|
||||||
|
var equality bool
|
||||||
|
|
||||||
|
// Check for nil equality.
|
||||||
|
if value == nil && result == (*int64)(nil) {
|
||||||
|
equality = true
|
||||||
|
break
|
||||||
|
} else if result == nil || value == nil {
|
||||||
|
equality = false
|
||||||
|
}
|
||||||
|
|
||||||
|
var resultValue interface{}
|
||||||
|
switch v := value.(type) {
|
||||||
|
case int64:
|
||||||
|
resultValue = *result.(*int64)
|
||||||
|
equality = v == resultValue
|
||||||
|
case string:
|
||||||
|
resultValue = *result.(*string)
|
||||||
|
equality = v == resultValue
|
||||||
|
case []int64:
|
||||||
|
resultValue = *result.(*[]int64)
|
||||||
|
resultSlice := resultValue.([]int64)
|
||||||
|
equality = true
|
||||||
|
for i, item := range resultSlice {
|
||||||
|
if item != v[i] {
|
||||||
|
equality = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !equality {
|
||||||
|
if result != nil {
|
||||||
|
t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, stat, value, resultValue)
|
||||||
|
} else {
|
||||||
|
t.Fatalf("Unexpected result in test %s, stat: %v, expected: %v, got: %v", test.name, stat, value, "<nil>")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var rpcTestCases = []rpctest.HarnessTestCase{
|
var rpcTestCases = []rpctest.HarnessTestCase{
|
||||||
testGetBestBlock,
|
testGetBestBlock,
|
||||||
testGetBlockCount,
|
testGetBlockCount,
|
||||||
testGetBlockHash,
|
testGetBlockHash,
|
||||||
|
testGetBlockStats,
|
||||||
testBulkClient,
|
testBulkClient,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,7 +421,8 @@ func TestMain(m *testing.M) {
|
||||||
// In order to properly test scenarios on as if we were on mainnet,
|
// In order to properly test scenarios on as if we were on mainnet,
|
||||||
// ensure that non-standard transactions aren't accepted into the
|
// ensure that non-standard transactions aren't accepted into the
|
||||||
// mempool or relayed.
|
// mempool or relayed.
|
||||||
btcdCfg := []string{"--rejectnonstd"}
|
// Enable transaction index to be able to fully test GetBlockStats
|
||||||
|
btcdCfg := []string{"--rejectnonstd", "--txindex"}
|
||||||
primaryHarness, err = rpctest.New(
|
primaryHarness, err = rpctest.New(
|
||||||
&chaincfg.SimNetParams, nil, btcdCfg, "",
|
&chaincfg.SimNetParams, nil, btcdCfg, "",
|
||||||
)
|
)
|
||||||
|
|
|
@ -16,6 +16,7 @@ import (
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbcd/btcjson"
|
||||||
"github.com/lbryio/lbcd/chaincfg"
|
"github.com/lbryio/lbcd/chaincfg"
|
||||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||||
"github.com/lbryio/lbcd/rpcclient"
|
"github.com/lbryio/lbcd/rpcclient"
|
||||||
|
@ -512,6 +513,18 @@ func (h *Harness) GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
|
||||||
return newBlock, nil
|
return newBlock, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetBlockStats returns block statistics. First argument specifies height or
|
||||||
|
// hash of the target block. Second argument allows to select certain stats to
|
||||||
|
// return. If second argument is empty, all stats are returned.
|
||||||
|
func (h *Harness) GetBlockStats(hashOrHeight interface{}, stats *[]string) (
|
||||||
|
*btcjson.GetBlockStatsResult, error) {
|
||||||
|
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
|
return h.Client.GetBlockStats(hashOrHeight, stats)
|
||||||
|
}
|
||||||
|
|
||||||
// generateListeningAddresses returns two strings representing listening
|
// generateListeningAddresses returns two strings representing listening
|
||||||
// addresses designated for the current rpc test. If there haven't been any
|
// addresses designated for the current rpc test. If there haven't been any
|
||||||
// test instances created, the default ports are used. Otherwise, in order to
|
// test instances created, the default ports are used. Otherwise, in order to
|
||||||
|
|
|
@ -31,40 +31,40 @@ proceed. Typically, this will involve things such as relaying the transactions
|
||||||
to other peers on the network and notifying the mining process that new
|
to other peers on the network and notifying the mining process that new
|
||||||
transactions are available.
|
transactions are available.
|
||||||
|
|
||||||
Feature Overview
|
# Feature Overview
|
||||||
|
|
||||||
The following is a quick overview of the major features. It is not intended to
|
The following is a quick overview of the major features. It is not intended to
|
||||||
be an exhaustive list.
|
be an exhaustive list.
|
||||||
|
|
||||||
- Maintain a pool of fully validated transactions
|
- Maintain a pool of fully validated transactions
|
||||||
- Reject non-fully-spent duplicate transactions
|
- Reject non-fully-spent duplicate transactions
|
||||||
- Reject coinbase transactions
|
- Reject coinbase transactions
|
||||||
- Reject double spends (both from the chain and other transactions in pool)
|
- Reject double spends (both from the chain and other transactions in pool)
|
||||||
- Reject invalid transactions according to the network consensus rules
|
- Reject invalid transactions according to the network consensus rules
|
||||||
- Full script execution and validation with signature cache support
|
- Full script execution and validation with signature cache support
|
||||||
- Individual transaction query support
|
- Individual transaction query support
|
||||||
- Orphan transaction support (transactions that spend from unknown outputs)
|
- Orphan transaction support (transactions that spend from unknown outputs)
|
||||||
- Configurable limits (see transaction acceptance policy)
|
- Configurable limits (see transaction acceptance policy)
|
||||||
- Automatic addition of orphan transactions that are no longer orphans as new
|
- Automatic addition of orphan transactions that are no longer orphans as new
|
||||||
transactions are added to the pool
|
transactions are added to the pool
|
||||||
- Individual orphan transaction query support
|
- Individual orphan transaction query support
|
||||||
- Configurable transaction acceptance policy
|
- Configurable transaction acceptance policy
|
||||||
- Option to accept or reject standard transactions
|
- Option to accept or reject standard transactions
|
||||||
- Option to accept or reject transactions based on priority calculations
|
- Option to accept or reject transactions based on priority calculations
|
||||||
- Rate limiting of low-fee and free transactions
|
- Rate limiting of low-fee and free transactions
|
||||||
- Non-zero fee threshold
|
- Non-zero fee threshold
|
||||||
- Max signature operations per transaction
|
- Max signature operations per transaction
|
||||||
- Max orphan transaction size
|
- Max orphan transaction size
|
||||||
- Max number of orphan transactions allowed
|
- Max number of orphan transactions allowed
|
||||||
- Additional metadata tracking for each transaction
|
- Additional metadata tracking for each transaction
|
||||||
- Timestamp when the transaction was added to the pool
|
- Timestamp when the transaction was added to the pool
|
||||||
- Most recent block height when the transaction was added to the pool
|
- Most recent block height when the transaction was added to the pool
|
||||||
- The fee the transaction pays
|
- The fee the transaction pays
|
||||||
- The starting priority for the transaction
|
- The starting priority for the transaction
|
||||||
- Manual control of transaction removal
|
- Manual control of transaction removal
|
||||||
- Recursive removal of all dependent transactions
|
- Recursive removal of all dependent transactions
|
||||||
|
|
||||||
Errors
|
# Errors
|
||||||
|
|
||||||
Errors returned by this package are either the raw errors provided by underlying
|
Errors returned by this package are either the raw errors provided by underlying
|
||||||
calls or of type mempool.RuleError. Since there are two classes of rules
|
calls or of type mempool.RuleError. Since there are two classes of rules
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
|
"reflect"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
"time"
|
"time"
|
||||||
|
@ -156,6 +157,15 @@ type Policy struct {
|
||||||
RejectReplacement bool
|
RejectReplacement bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// aggregateInfo tracks aggregated serialized size, memory usage, and fees
|
||||||
|
// for TxDesc in the mempool.
|
||||||
|
type aggregateInfo struct {
|
||||||
|
totalCount int64
|
||||||
|
totalBytes int64
|
||||||
|
totalMem int64
|
||||||
|
totalFee int64
|
||||||
|
}
|
||||||
|
|
||||||
// TxDesc is a descriptor containing a transaction in the mempool along with
|
// TxDesc is a descriptor containing a transaction in the mempool along with
|
||||||
// additional metadata.
|
// additional metadata.
|
||||||
type TxDesc struct {
|
type TxDesc struct {
|
||||||
|
@ -166,6 +176,20 @@ type TxDesc struct {
|
||||||
StartingPriority float64
|
StartingPriority float64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (txD *TxDesc) incr(info *aggregateInfo) {
|
||||||
|
info.totalCount += 1
|
||||||
|
info.totalBytes += int64(txD.Tx.MsgTx().SerializeSize())
|
||||||
|
info.totalMem += int64(dynamicMemUsage(reflect.ValueOf(txD)))
|
||||||
|
info.totalFee += txD.Fee
|
||||||
|
}
|
||||||
|
|
||||||
|
func (txD *TxDesc) decr(info *aggregateInfo) {
|
||||||
|
info.totalCount -= 1
|
||||||
|
info.totalBytes -= int64(txD.Tx.MsgTx().SerializeSize())
|
||||||
|
info.totalMem -= int64(dynamicMemUsage(reflect.ValueOf(txD)))
|
||||||
|
info.totalFee -= txD.Fee
|
||||||
|
}
|
||||||
|
|
||||||
// orphanTx is normal transaction that references an ancestor transaction
|
// orphanTx is normal transaction that references an ancestor transaction
|
||||||
// that is not yet available. It also contains additional information related
|
// that is not yet available. It also contains additional information related
|
||||||
// to it such as an expiration time to help prevent caching the orphan forever.
|
// to it such as an expiration time to help prevent caching the orphan forever.
|
||||||
|
@ -175,6 +199,18 @@ type orphanTx struct {
|
||||||
expiration time.Time
|
expiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (otx *orphanTx) incr(info *aggregateInfo) {
|
||||||
|
info.totalCount += 1
|
||||||
|
info.totalBytes += int64(otx.tx.MsgTx().SerializeSize())
|
||||||
|
info.totalMem += int64(dynamicMemUsage(reflect.ValueOf(otx)))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (otx *orphanTx) decr(info *aggregateInfo) {
|
||||||
|
info.totalCount -= 1
|
||||||
|
info.totalBytes -= int64(otx.tx.MsgTx().SerializeSize())
|
||||||
|
info.totalMem -= int64(dynamicMemUsage(reflect.ValueOf(otx)))
|
||||||
|
}
|
||||||
|
|
||||||
// TxPool is used as a source of transactions that need to be mined into blocks
|
// TxPool is used as a source of transactions that need to be mined into blocks
|
||||||
// and relayed to other peers. It is safe for concurrent access from multiple
|
// and relayed to other peers. It is safe for concurrent access from multiple
|
||||||
// peers.
|
// peers.
|
||||||
|
@ -196,6 +232,12 @@ type TxPool struct {
|
||||||
// the scan will only run when an orphan is added to the pool as opposed
|
// the scan will only run when an orphan is added to the pool as opposed
|
||||||
// to on an unconditional timer.
|
// to on an unconditional timer.
|
||||||
nextExpireScan time.Time
|
nextExpireScan time.Time
|
||||||
|
|
||||||
|
// stats are aggregated over pool, orphans, etc.
|
||||||
|
stats aggregateInfo
|
||||||
|
|
||||||
|
// unbroadcast is a set of transactions yet to be broadcast.
|
||||||
|
unbroadcast map[chainhash.Hash]bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the TxPool type implements the mining.TxSource interface.
|
// Ensure the TxPool type implements the mining.TxSource interface.
|
||||||
|
@ -240,6 +282,9 @@ func (mp *TxPool) removeOrphan(tx *btcutil.Tx, removeRedeemers bool) {
|
||||||
|
|
||||||
// Remove the transaction from the orphan pool.
|
// Remove the transaction from the orphan pool.
|
||||||
delete(mp.orphans, *txHash)
|
delete(mp.orphans, *txHash)
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
otx.decr(&mp.stats)
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveOrphan removes the passed orphan transaction from the orphan pool and
|
// RemoveOrphan removes the passed orphan transaction from the orphan pool and
|
||||||
|
@ -336,11 +381,12 @@ func (mp *TxPool) addOrphan(tx *btcutil.Tx, tag Tag) {
|
||||||
// orphan if space is still needed.
|
// orphan if space is still needed.
|
||||||
mp.limitNumOrphans()
|
mp.limitNumOrphans()
|
||||||
|
|
||||||
mp.orphans[*tx.Hash()] = &orphanTx{
|
otx := &orphanTx{
|
||||||
tx: tx,
|
tx: tx,
|
||||||
tag: tag,
|
tag: tag,
|
||||||
expiration: time.Now().Add(orphanTTL),
|
expiration: time.Now().Add(orphanTTL),
|
||||||
}
|
}
|
||||||
|
mp.orphans[*tx.Hash()] = otx
|
||||||
for _, txIn := range tx.MsgTx().TxIn {
|
for _, txIn := range tx.MsgTx().TxIn {
|
||||||
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
|
if _, exists := mp.orphansByPrev[txIn.PreviousOutPoint]; !exists {
|
||||||
mp.orphansByPrev[txIn.PreviousOutPoint] =
|
mp.orphansByPrev[txIn.PreviousOutPoint] =
|
||||||
|
@ -349,6 +395,9 @@ func (mp *TxPool) addOrphan(tx *btcutil.Tx, tag Tag) {
|
||||||
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
|
mp.orphansByPrev[txIn.PreviousOutPoint][*tx.Hash()] = tx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
otx.incr(&mp.stats)
|
||||||
|
|
||||||
log.Debugf("Stored orphan transaction %v (total: %d)", tx.Hash(),
|
log.Debugf("Stored orphan transaction %v (total: %d)", tx.Hash(),
|
||||||
len(mp.orphans))
|
len(mp.orphans))
|
||||||
}
|
}
|
||||||
|
@ -498,6 +547,9 @@ func (mp *TxPool) removeTransaction(tx *btcutil.Tx, removeRedeemers bool) {
|
||||||
}
|
}
|
||||||
delete(mp.pool, *txHash)
|
delete(mp.pool, *txHash)
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
txDesc.decr(&mp.stats)
|
||||||
|
|
||||||
// Inform associated fee estimator that the transaction has been removed
|
// Inform associated fee estimator that the transaction has been removed
|
||||||
// from the mempool
|
// from the mempool
|
||||||
if mp.cfg.RemoveTxFromFeeEstimation != nil {
|
if mp.cfg.RemoveTxFromFeeEstimation != nil {
|
||||||
|
@ -579,6 +631,9 @@ func (mp *TxPool) addTransaction(utxoView *blockchain.UtxoViewpoint, tx *btcutil
|
||||||
mp.cfg.AddTxToFeeEstimation(txD.Tx.Hash(), txD.Fee, size)
|
mp.cfg.AddTxToFeeEstimation(txD.Tx.Hash(), txD.Fee, size)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Update stats.
|
||||||
|
txD.incr(&mp.stats)
|
||||||
|
|
||||||
return txD
|
return txD
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1503,6 +1558,38 @@ func (mp *TxPool) MiningDescs() []*mining.TxDesc {
|
||||||
return descs
|
return descs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mp *TxPool) AddUnbroadcastTx(hash *chainhash.Hash) {
|
||||||
|
mp.mtx.Lock()
|
||||||
|
mp.unbroadcast[*hash] = true
|
||||||
|
mp.mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *TxPool) RemoveUnbroadcastTx(hash *chainhash.Hash) {
|
||||||
|
mp.mtx.Lock()
|
||||||
|
delete(mp.unbroadcast, *hash)
|
||||||
|
mp.mtx.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mp *TxPool) MempoolInfo() *btcjson.GetMempoolInfoResult {
|
||||||
|
mp.mtx.RLock()
|
||||||
|
policy := mp.cfg.Policy
|
||||||
|
stats := mp.stats
|
||||||
|
unbroadcastCount := int64(len(mp.unbroadcast))
|
||||||
|
mp.mtx.RUnlock()
|
||||||
|
|
||||||
|
ret := &btcjson.GetMempoolInfoResult{
|
||||||
|
Size: stats.totalCount,
|
||||||
|
Usage: stats.totalMem,
|
||||||
|
Bytes: stats.totalBytes,
|
||||||
|
TotalFee: btcutil.Amount(stats.totalFee).ToBTC(),
|
||||||
|
MemPoolMinFee: btcutil.Amount(calcMinRequiredTxRelayFee(1000, policy.MinRelayTxFee)).ToBTC(),
|
||||||
|
MinRelayTxFee: policy.MinRelayTxFee.ToBTC(),
|
||||||
|
UnbroadcastCount: unbroadcastCount,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
// RawMempoolVerbose returns all the entries in the mempool as a fully
|
// RawMempoolVerbose returns all the entries in the mempool as a fully
|
||||||
// populated btcjson result.
|
// populated btcjson result.
|
||||||
//
|
//
|
||||||
|
@ -1576,5 +1663,6 @@ func New(cfg *Config) *TxPool {
|
||||||
orphansByPrev: make(map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx),
|
orphansByPrev: make(map[wire.OutPoint]map[chainhash.Hash]*btcutil.Tx),
|
||||||
nextExpireScan: time.Now().Add(orphanExpireScanInterval),
|
nextExpireScan: time.Now().Add(orphanExpireScanInterval),
|
||||||
outpoints: make(map[wire.OutPoint]*btcutil.Tx),
|
outpoints: make(map[wire.OutPoint]*btcutil.Tx),
|
||||||
|
unbroadcast: make(map[chainhash.Hash]bool),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,12 @@ import (
|
||||||
btcutil "github.com/lbryio/lbcutil"
|
btcutil "github.com/lbryio/lbcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// Toggle assert & debug messages when running tests.
|
||||||
|
dynamicMemUsageAssert = true
|
||||||
|
dynamicMemUsageDebug = false
|
||||||
|
}
|
||||||
|
|
||||||
// fakeChain is used by the pool harness to provide generated test utxos and
|
// fakeChain is used by the pool harness to provide generated test utxos and
|
||||||
// a current faked chain height to the pool callbacks. This, in turn, allows
|
// a current faked chain height to the pool callbacks. This, in turn, allows
|
||||||
// transactions to appear as though they are spending completely valid utxos.
|
// transactions to appear as though they are spending completely valid utxos.
|
||||||
|
|
89
mempool/memusage.go
Normal file
89
mempool/memusage.go
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
// Copyright (c) 2013-2016 The btcsuite developers
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package mempool
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
dynamicMemUsageAssert = false
|
||||||
|
dynamicMemUsageDebug = false
|
||||||
|
dynamicMemUsageMaxDepth = 10
|
||||||
|
)
|
||||||
|
|
||||||
|
func dynamicMemUsage(v reflect.Value) uintptr {
|
||||||
|
return dynamicMemUsageCrawl(v, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
func dynamicMemUsageCrawl(v reflect.Value, depth int) uintptr {
|
||||||
|
t := v.Type()
|
||||||
|
bytes := t.Size()
|
||||||
|
if dynamicMemUsageDebug {
|
||||||
|
println("[", depth, "]", t.Kind().String(), "(", t.String(), ") ->", t.Size())
|
||||||
|
}
|
||||||
|
|
||||||
|
if depth >= dynamicMemUsageMaxDepth {
|
||||||
|
if dynamicMemUsageAssert {
|
||||||
|
panic("crawl reached maximum depth")
|
||||||
|
}
|
||||||
|
return bytes
|
||||||
|
}
|
||||||
|
|
||||||
|
// For complex types, we need to peek inside slices/arrays/structs and chase pointers.
|
||||||
|
switch t.Kind() {
|
||||||
|
case reflect.Pointer, reflect.Interface:
|
||||||
|
if !v.IsNil() {
|
||||||
|
bytes += dynamicMemUsageCrawl(v.Elem(), depth+1)
|
||||||
|
}
|
||||||
|
case reflect.Array, reflect.Slice:
|
||||||
|
for j := 0; j < v.Len(); j++ {
|
||||||
|
vi := v.Index(j)
|
||||||
|
k := vi.Type().Kind()
|
||||||
|
if dynamicMemUsageDebug {
|
||||||
|
println("[", depth, "] index:", j, "kind:", k.String())
|
||||||
|
}
|
||||||
|
elemBytes := uintptr(0)
|
||||||
|
if t.Kind() == reflect.Array {
|
||||||
|
if (k == reflect.Pointer || k == reflect.Interface) && !vi.IsNil() {
|
||||||
|
elemBytes += dynamicMemUsageCrawl(vi.Elem(), depth+1)
|
||||||
|
}
|
||||||
|
} else { // slice
|
||||||
|
elemBytes += dynamicMemUsageCrawl(vi, depth+1)
|
||||||
|
}
|
||||||
|
if k == reflect.Uint8 {
|
||||||
|
// short circuit for byte slice/array
|
||||||
|
bytes += elemBytes * uintptr(v.Len())
|
||||||
|
if dynamicMemUsageDebug {
|
||||||
|
println("...", v.Len(), "elements")
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
bytes += elemBytes
|
||||||
|
}
|
||||||
|
case reflect.Struct:
|
||||||
|
for _, f := range reflect.VisibleFields(t) {
|
||||||
|
vf := v.FieldByIndex(f.Index)
|
||||||
|
k := vf.Type().Kind()
|
||||||
|
if dynamicMemUsageDebug {
|
||||||
|
println("[", depth, "] field:", f.Name, "kind:", k.String())
|
||||||
|
}
|
||||||
|
if (k == reflect.Pointer || k == reflect.Interface) && !vf.IsNil() {
|
||||||
|
bytes += dynamicMemUsageCrawl(vf.Elem(), depth+1)
|
||||||
|
} else if k == reflect.Array || k == reflect.Slice {
|
||||||
|
bytes -= vf.Type().Size()
|
||||||
|
bytes += dynamicMemUsageCrawl(vf, depth+1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case reflect.Uint8:
|
||||||
|
default:
|
||||||
|
if dynamicMemUsageAssert {
|
||||||
|
panic(fmt.Sprintf("unsupported kind: %v", t.Kind()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytes
|
||||||
|
}
|
|
@ -249,7 +249,7 @@ func GetDustThreshold(txOut *wire.TxOut) int64 {
|
||||||
totalSize += 107
|
totalSize += 107
|
||||||
}
|
}
|
||||||
|
|
||||||
return 3 * int64(totalSize)
|
return int64(totalSize)
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDust returns whether or not the passed transaction output amount is
|
// IsDust returns whether or not the passed transaction output amount is
|
||||||
|
@ -264,7 +264,7 @@ func IsDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The output is considered dust if the cost to the network to spend the
|
// The output is considered dust if the cost to the network to spend the
|
||||||
// coins is more than 1/3 of the minimum free transaction relay fee.
|
// coins is more than the minimum free transaction relay fee.
|
||||||
// minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to
|
// minFreeTxRelayFee is in Satoshi/KB, so multiply by 1000 to
|
||||||
// convert to bytes.
|
// convert to bytes.
|
||||||
//
|
//
|
||||||
|
@ -273,7 +273,7 @@ func IsDust(txOut *wire.TxOut, minRelayTxFee btcutil.Amount) bool {
|
||||||
// fee of 1000, this equates to values less than 546 satoshi being
|
// fee of 1000, this equates to values less than 546 satoshi being
|
||||||
// considered dust.
|
// considered dust.
|
||||||
//
|
//
|
||||||
// The following is equivalent to (value/totalSize) * (1/3) * 1000
|
// The following is equivalent to (value/totalSize) * 1000
|
||||||
// without needing to do floating point math.
|
// without needing to do floating point math.
|
||||||
if txOut.Value > dustCap {
|
if txOut.Value > dustCap {
|
||||||
return false
|
return false
|
||||||
|
|
|
@ -233,14 +233,14 @@ func TestDust(t *testing.T) {
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"38 byte public key script with value 584",
|
"38 byte public key script with value 194",
|
||||||
wire.TxOut{Value: 584, PkScript: pkScript},
|
wire.TxOut{Value: 194, PkScript: pkScript},
|
||||||
1000,
|
1000,
|
||||||
true,
|
true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"38 byte public key script with value 585",
|
"38 byte public key script with value 195",
|
||||||
wire.TxOut{Value: 585, PkScript: pkScript},
|
wire.TxOut{Value: 195, PkScript: pkScript},
|
||||||
1000,
|
1000,
|
||||||
false,
|
false,
|
||||||
},
|
},
|
||||||
|
|
|
@ -420,26 +420,26 @@ func NewBlkTmplGenerator(policy *Policy, params *chaincfg.Params,
|
||||||
//
|
//
|
||||||
// Given the above, a block generated by this function is of the following form:
|
// Given the above, a block generated by this function is of the following form:
|
||||||
//
|
//
|
||||||
// ----------------------------------- -- --
|
// ----------------------------------- -- --
|
||||||
// | Coinbase Transaction | | |
|
// | Coinbase Transaction | | |
|
||||||
// |-----------------------------------| | |
|
// |-----------------------------------| | |
|
||||||
// | | | | ----- policy.BlockPrioritySize
|
// | | | | ----- policy.BlockPrioritySize
|
||||||
// | High-priority Transactions | | |
|
// | High-priority Transactions | | |
|
||||||
// | | | |
|
// | | | |
|
||||||
// |-----------------------------------| | --
|
// |-----------------------------------| | --
|
||||||
// | | |
|
// | | |
|
||||||
// | | |
|
// | | |
|
||||||
// | | |--- policy.BlockMaxSize
|
// | | |--- policy.BlockMaxSize
|
||||||
// | Transactions prioritized by fee | |
|
// | Transactions prioritized by fee | |
|
||||||
// | until <= policy.TxMinFreeFee | |
|
// | until <= policy.TxMinFreeFee | |
|
||||||
// | | |
|
// | | |
|
||||||
// | | |
|
// | | |
|
||||||
// | | |
|
// | | |
|
||||||
// |-----------------------------------| |
|
// |-----------------------------------| |
|
||||||
// | Low-fee/Non high-priority (free) | |
|
// | Low-fee/Non high-priority (free) | |
|
||||||
// | transactions (while block size | |
|
// | transactions (while block size | |
|
||||||
// | <= policy.BlockMinSize) | |
|
// | <= policy.BlockMinSize) | |
|
||||||
// ----------------------------------- --
|
// ----------------------------------- --
|
||||||
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress btcutil.Address) (*BlockTemplate, error) {
|
func (g *BlkTmplGenerator) NewBlockTemplate(payToAddress btcutil.Address) (*BlockTemplate, error) {
|
||||||
// Extend the most recently known best block.
|
// Extend the most recently known best block.
|
||||||
best := g.chain.BestSnapshot()
|
best := g.chain.BestSnapshot()
|
||||||
|
|
|
@ -27,8 +27,9 @@ type blockProgressLogger struct {
|
||||||
|
|
||||||
// newBlockProgressLogger returns a new block progress logger.
|
// newBlockProgressLogger returns a new block progress logger.
|
||||||
// The progress message is templated as follows:
|
// The progress message is templated as follows:
|
||||||
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
//
|
||||||
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
||||||
|
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
||||||
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
|
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
|
||||||
return &blockProgressLogger{
|
return &blockProgressLogger{
|
||||||
lastBlockLogTime: time.Now(),
|
lastBlockLogTime: time.Now(),
|
||||||
|
|
|
@ -1294,16 +1294,9 @@ func (sm *SyncManager) handleInvMsg(imsg *invMsg) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
e := wire.BaseEncoding
|
|
||||||
// we think that the iv.Type set above is sufficient. If not:
|
|
||||||
// if peer.IsWitnessEnabled() {
|
|
||||||
// e = wire.WitnessEncoding
|
|
||||||
//}
|
|
||||||
|
|
||||||
state.requestQueue = requestQueue
|
state.requestQueue = requestQueue
|
||||||
if len(gdmsg.InvList) > 0 {
|
if len(gdmsg.InvList) > 0 {
|
||||||
peer.QueueMessageWithEncoding(gdmsg, nil, e)
|
peer.QueueMessage(gdmsg, nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
84
peer/doc.go
84
peer/doc.go
|
@ -6,7 +6,7 @@
|
||||||
Package peer provides a common base for creating and managing Bitcoin network
|
Package peer provides a common base for creating and managing Bitcoin network
|
||||||
peers.
|
peers.
|
||||||
|
|
||||||
Overview
|
# Overview
|
||||||
|
|
||||||
This package builds upon the wire package, which provides the fundamental
|
This package builds upon the wire package, which provides the fundamental
|
||||||
primitives necessary to speak the bitcoin wire protocol, in order to simplify
|
primitives necessary to speak the bitcoin wire protocol, in order to simplify
|
||||||
|
@ -16,41 +16,41 @@ Payment Verification (SPV) nodes, proxies, etc.
|
||||||
|
|
||||||
A quick overview of the major features peer provides are as follows:
|
A quick overview of the major features peer provides are as follows:
|
||||||
|
|
||||||
- Provides a basic concurrent safe bitcoin peer for handling bitcoin
|
- Provides a basic concurrent safe bitcoin peer for handling bitcoin
|
||||||
communications via the peer-to-peer protocol
|
communications via the peer-to-peer protocol
|
||||||
- Full duplex reading and writing of bitcoin protocol messages
|
- Full duplex reading and writing of bitcoin protocol messages
|
||||||
- Automatic handling of the initial handshake process including protocol
|
- Automatic handling of the initial handshake process including protocol
|
||||||
version negotiation
|
version negotiation
|
||||||
- Asynchronous message queuing of outbound messages with optional channel for
|
- Asynchronous message queuing of outbound messages with optional channel for
|
||||||
notification when the message is actually sent
|
notification when the message is actually sent
|
||||||
- Flexible peer configuration
|
- Flexible peer configuration
|
||||||
- Caller is responsible for creating outgoing connections and listening for
|
- Caller is responsible for creating outgoing connections and listening for
|
||||||
incoming connections so they have flexibility to establish connections as
|
incoming connections so they have flexibility to establish connections as
|
||||||
they see fit (proxies, etc)
|
they see fit (proxies, etc)
|
||||||
- User agent name and version
|
- User agent name and version
|
||||||
- Bitcoin network
|
- Bitcoin network
|
||||||
- Service support signalling (full nodes, bloom filters, etc)
|
- Service support signalling (full nodes, bloom filters, etc)
|
||||||
- Maximum supported protocol version
|
- Maximum supported protocol version
|
||||||
- Ability to register callbacks for handling bitcoin protocol messages
|
- Ability to register callbacks for handling bitcoin protocol messages
|
||||||
- Inventory message batching and send trickling with known inventory detection
|
- Inventory message batching and send trickling with known inventory detection
|
||||||
and avoidance
|
and avoidance
|
||||||
- Automatic periodic keep-alive pinging and pong responses
|
- Automatic periodic keep-alive pinging and pong responses
|
||||||
- Random nonce generation and self connection detection
|
- Random nonce generation and self connection detection
|
||||||
- Proper handling of bloom filter related commands when the caller does not
|
- Proper handling of bloom filter related commands when the caller does not
|
||||||
specify the related flag to signal support
|
specify the related flag to signal support
|
||||||
- Disconnects the peer when the protocol version is high enough
|
- Disconnects the peer when the protocol version is high enough
|
||||||
- Does not invoke the related callbacks for older protocol versions
|
- Does not invoke the related callbacks for older protocol versions
|
||||||
- Snapshottable peer statistics such as the total number of bytes read and
|
- Snapshottable peer statistics such as the total number of bytes read and
|
||||||
written, the remote address, user agent, and negotiated protocol version
|
written, the remote address, user agent, and negotiated protocol version
|
||||||
- Helper functions pushing addresses, getblocks, getheaders, and reject
|
- Helper functions pushing addresses, getblocks, getheaders, and reject
|
||||||
messages
|
messages
|
||||||
- These could all be sent manually via the standard message output function,
|
- These could all be sent manually via the standard message output function,
|
||||||
but the helpers provide additional nice functionality such as duplicate
|
but the helpers provide additional nice functionality such as duplicate
|
||||||
filtering and address randomization
|
filtering and address randomization
|
||||||
- Ability to wait for shutdown/disconnect
|
- Ability to wait for shutdown/disconnect
|
||||||
- Comprehensive test coverage
|
- Comprehensive test coverage
|
||||||
|
|
||||||
Peer Configuration
|
# Peer Configuration
|
||||||
|
|
||||||
All peer configuration is handled with the Config struct. This allows the
|
All peer configuration is handled with the Config struct. This allows the
|
||||||
caller to specify things such as the user agent name and version, the bitcoin
|
caller to specify things such as the user agent name and version, the bitcoin
|
||||||
|
@ -58,7 +58,7 @@ network to use, which services it supports, and callbacks to invoke when bitcoin
|
||||||
messages are received. See the documentation for each field of the Config
|
messages are received. See the documentation for each field of the Config
|
||||||
struct for more details.
|
struct for more details.
|
||||||
|
|
||||||
Inbound and Outbound Peers
|
# Inbound and Outbound Peers
|
||||||
|
|
||||||
A peer can either be inbound or outbound. The caller is responsible for
|
A peer can either be inbound or outbound. The caller is responsible for
|
||||||
establishing the connection to remote peers and listening for incoming peers.
|
establishing the connection to remote peers and listening for incoming peers.
|
||||||
|
@ -73,7 +73,7 @@ Disconnect to disconnect from the peer and clean up all resources.
|
||||||
WaitForDisconnect can be used to block until peer disconnection and resource
|
WaitForDisconnect can be used to block until peer disconnection and resource
|
||||||
cleanup has completed.
|
cleanup has completed.
|
||||||
|
|
||||||
Callbacks
|
# Callbacks
|
||||||
|
|
||||||
In order to do anything useful with a peer, it is necessary to react to bitcoin
|
In order to do anything useful with a peer, it is necessary to react to bitcoin
|
||||||
messages. This is accomplished by creating an instance of the MessageListeners
|
messages. This is accomplished by creating an instance of the MessageListeners
|
||||||
|
@ -92,7 +92,7 @@ It is often useful to use closures which encapsulate state when specifying the
|
||||||
callback handlers. This provides a clean method for accessing that state when
|
callback handlers. This provides a clean method for accessing that state when
|
||||||
callbacks are invoked.
|
callbacks are invoked.
|
||||||
|
|
||||||
Queuing Messages and Inventory
|
# Queuing Messages and Inventory
|
||||||
|
|
||||||
The QueueMessage function provides the fundamental means to send messages to the
|
The QueueMessage function provides the fundamental means to send messages to the
|
||||||
remote peer. As the name implies, this employs a non-blocking queue. A done
|
remote peer. As the name implies, this employs a non-blocking queue. A done
|
||||||
|
@ -106,7 +106,7 @@ QueueInventory function. It employs batching and trickling along with
|
||||||
intelligent known remote peer inventory detection and avoidance through the use
|
intelligent known remote peer inventory detection and avoidance through the use
|
||||||
of a most-recently used algorithm.
|
of a most-recently used algorithm.
|
||||||
|
|
||||||
Message Sending Helper Functions
|
# Message Sending Helper Functions
|
||||||
|
|
||||||
In addition to the bare QueueMessage function previously described, the
|
In addition to the bare QueueMessage function previously described, the
|
||||||
PushAddrMsg, PushGetBlocksMsg, PushGetHeadersMsg, and PushRejectMsg functions
|
PushAddrMsg, PushGetBlocksMsg, PushGetHeadersMsg, and PushRejectMsg functions
|
||||||
|
@ -128,13 +128,13 @@ appropriate reject message based on the provided parameters as well as
|
||||||
optionally provides a flag to cause it to block until the message is actually
|
optionally provides a flag to cause it to block until the message is actually
|
||||||
sent.
|
sent.
|
||||||
|
|
||||||
Peer Statistics
|
# Peer Statistics
|
||||||
|
|
||||||
A snapshot of the current peer statistics can be obtained with the StatsSnapshot
|
A snapshot of the current peer statistics can be obtained with the StatsSnapshot
|
||||||
function. This includes statistics such as the total number of bytes read and
|
function. This includes statistics such as the total number of bytes read and
|
||||||
written, the remote address, user agent, and negotiated protocol version.
|
written, the remote address, user agent, and negotiated protocol version.
|
||||||
|
|
||||||
Logging
|
# Logging
|
||||||
|
|
||||||
This package provides extensive logging capabilities through the UseLogger
|
This package provides extensive logging capabilities through the UseLogger
|
||||||
function which allows a btclog.Logger to be specified. For example, logging at
|
function which allows a btclog.Logger to be specified. For example, logging at
|
||||||
|
@ -142,7 +142,7 @@ the debug level provides summaries of every message sent and received, and
|
||||||
logging at the trace level provides full dumps of parsed messages as well as the
|
logging at the trace level provides full dumps of parsed messages as well as the
|
||||||
raw message bytes using a format similar to hexdump -C.
|
raw message bytes using a format similar to hexdump -C.
|
||||||
|
|
||||||
Bitcoin Improvement Proposals
|
# Bitcoin Improvement Proposals
|
||||||
|
|
||||||
This package supports all BIPS supported by the wire package.
|
This package supports all BIPS supported by the wire package.
|
||||||
(https://pkg.go.dev/github.com/lbryio/lbcd/wire#hdr-Bitcoin_Improvement_Proposals)
|
(https://pkg.go.dev/github.com/lbryio/lbcd/wire#hdr-Bitcoin_Improvement_Proposals)
|
||||||
|
|
16
peer/peer.go
16
peer/peer.go
|
@ -2096,10 +2096,10 @@ func (p *Peer) writeLocalVersionMsg() error {
|
||||||
// peer. The events should occur in the following order, otherwise an error is
|
// peer. The events should occur in the following order, otherwise an error is
|
||||||
// returned:
|
// returned:
|
||||||
//
|
//
|
||||||
// 1. Remote peer sends their version.
|
// 1. Remote peer sends their version.
|
||||||
// 2. We send our version.
|
// 2. We send our version.
|
||||||
// 3. We send our verack.
|
// 3. We send our verack.
|
||||||
// 4. Remote peer sends their verack.
|
// 4. Remote peer sends their verack.
|
||||||
func (p *Peer) negotiateInboundProtocol() error {
|
func (p *Peer) negotiateInboundProtocol() error {
|
||||||
if err := p.readRemoteVersionMsg(); err != nil {
|
if err := p.readRemoteVersionMsg(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
@ -2121,10 +2121,10 @@ func (p *Peer) negotiateInboundProtocol() error {
|
||||||
// peer. The events should occur in the following order, otherwise an error is
|
// peer. The events should occur in the following order, otherwise an error is
|
||||||
// returned:
|
// returned:
|
||||||
//
|
//
|
||||||
// 1. We send our version.
|
// 1. We send our version.
|
||||||
// 2. Remote peer sends their version.
|
// 2. Remote peer sends their version.
|
||||||
// 3. Remote peer sends their verack.
|
// 3. Remote peer sends their verack.
|
||||||
// 4. We send our verack.
|
// 4. We send our verack.
|
||||||
func (p *Peer) negotiateOutboundProtocol() error {
|
func (p *Peer) negotiateOutboundProtocol() error {
|
||||||
if err := p.writeLocalVersionMsg(); err != nil {
|
if err := p.writeLocalVersionMsg(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -6,6 +6,7 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/blockchain"
|
"github.com/lbryio/lbcd/blockchain"
|
||||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||||
|
@ -181,6 +182,57 @@ func (cm *rpcConnManager) ConnectedPeers() []rpcserverPeer {
|
||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BannedPeers returns a map consisting of all banned host with banned period.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access and is part of the
|
||||||
|
// rpcserverConnManager interface implementation.
|
||||||
|
func (cm *rpcConnManager) BannedPeers() map[string]bannedPeriod {
|
||||||
|
replyChan := make(chan map[string]bannedPeriod)
|
||||||
|
cm.server.query <- listBannedPeersMsg{reply: replyChan}
|
||||||
|
return <-replyChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBan removes the peer associated with the provided address from the
|
||||||
|
// list of persistent peers.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access and is part of the
|
||||||
|
// rpcserverConnManager interface implementation.
|
||||||
|
func (cm *rpcConnManager) SetBan(addr string, since, until time.Time) error {
|
||||||
|
replyChan := make(chan error)
|
||||||
|
cm.server.query <- setBanMsg{
|
||||||
|
addr: addr,
|
||||||
|
since: since,
|
||||||
|
until: until,
|
||||||
|
reply: replyChan,
|
||||||
|
}
|
||||||
|
return <-replyChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveBan removes a host from banned list.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access and is part of the
|
||||||
|
// rpcserverConnManager interface implementation.
|
||||||
|
func (cm *rpcConnManager) RemoveBan(addr string) error {
|
||||||
|
replyChan := make(chan error)
|
||||||
|
cm.server.query <- removeBanMsg{
|
||||||
|
addr: addr,
|
||||||
|
reply: replyChan,
|
||||||
|
}
|
||||||
|
return <-replyChan
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearBanned removes all banned host with banned period.
|
||||||
|
//
|
||||||
|
// This function is safe for concurrent access and is part of the
|
||||||
|
// rpcserverConnManager interface implementation.
|
||||||
|
func (cm *rpcConnManager) ClearBanned() error {
|
||||||
|
replyChan := make(chan error)
|
||||||
|
cm.server.query <- clearBannedMsg{
|
||||||
|
reply: replyChan,
|
||||||
|
}
|
||||||
|
return <-replyChan
|
||||||
|
}
|
||||||
|
|
||||||
// PersistentPeers returns an array consisting of all the added persistent
|
// PersistentPeers returns an array consisting of all the added persistent
|
||||||
// peers.
|
// peers.
|
||||||
//
|
//
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
/*
|
/*
|
||||||
Package rpcclient implements a websocket-enabled Bitcoin JSON-RPC client.
|
Package rpcclient implements a websocket-enabled Bitcoin JSON-RPC client.
|
||||||
|
|
||||||
Overview
|
# Overview
|
||||||
|
|
||||||
This client provides a robust and easy to use client for interfacing with a
|
This client provides a robust and easy to use client for interfacing with a
|
||||||
Bitcoin RPC server that uses a btcd/bitcoin core compatible Bitcoin JSON-RPC
|
Bitcoin RPC server that uses a btcd/bitcoin core compatible Bitcoin JSON-RPC
|
||||||
|
@ -24,7 +24,7 @@ btcd or btcwallet by default. However, configuration options are provided to
|
||||||
fall back to HTTP POST and disable TLS to support talking with inferior bitcoin
|
fall back to HTTP POST and disable TLS to support talking with inferior bitcoin
|
||||||
core style RPC servers.
|
core style RPC servers.
|
||||||
|
|
||||||
Websockets vs HTTP POST
|
# Websockets vs HTTP POST
|
||||||
|
|
||||||
In HTTP POST-based JSON-RPC, every request creates a new HTTP connection,
|
In HTTP POST-based JSON-RPC, every request creates a new HTTP connection,
|
||||||
issues the call, waits for the response, and closes the connection. This adds
|
issues the call, waits for the response, and closes the connection. This adds
|
||||||
|
@ -40,7 +40,7 @@ can be invoked without having to go through a connect/disconnect cycle for every
|
||||||
call. In addition, the websocket interface provides other nice features such as
|
call. In addition, the websocket interface provides other nice features such as
|
||||||
the ability to register for asynchronous notifications of various events.
|
the ability to register for asynchronous notifications of various events.
|
||||||
|
|
||||||
Synchronous vs Asynchronous API
|
# Synchronous vs Asynchronous API
|
||||||
|
|
||||||
The client provides both a synchronous (blocking) and asynchronous API.
|
The client provides both a synchronous (blocking) and asynchronous API.
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@ the Receive method on the returned instance will either return the result
|
||||||
immediately if it has already arrived, or block until it has. This is useful
|
immediately if it has already arrived, or block until it has. This is useful
|
||||||
since it provides the caller with greater control over concurrency.
|
since it provides the caller with greater control over concurrency.
|
||||||
|
|
||||||
Notifications
|
# Notifications
|
||||||
|
|
||||||
The first important part of notifications is to realize that they will only
|
The first important part of notifications is to realize that they will only
|
||||||
work when connected via websockets. This should intuitively make sense
|
work when connected via websockets. This should intuitively make sense
|
||||||
|
@ -67,7 +67,7 @@ All notifications provided by btcd require registration to opt-in. For example,
|
||||||
if you want to be notified when funds are received by a set of addresses, you
|
if you want to be notified when funds are received by a set of addresses, you
|
||||||
register the addresses via the NotifyReceived (or NotifyReceivedAsync) function.
|
register the addresses via the NotifyReceived (or NotifyReceivedAsync) function.
|
||||||
|
|
||||||
Notification Handlers
|
# Notification Handlers
|
||||||
|
|
||||||
Notifications are exposed by the client through the use of callback handlers
|
Notifications are exposed by the client through the use of callback handlers
|
||||||
which are setup via a NotificationHandlers instance that is specified by the
|
which are setup via a NotificationHandlers instance that is specified by the
|
||||||
|
@ -83,7 +83,7 @@ will cause a deadlock as more server responses won't be read until the callback
|
||||||
returns, but the callback would be waiting for a response. Thus, any
|
returns, but the callback would be waiting for a response. Thus, any
|
||||||
additional RPCs must be issued an a completely decoupled manner.
|
additional RPCs must be issued an a completely decoupled manner.
|
||||||
|
|
||||||
Automatic Reconnection
|
# Automatic Reconnection
|
||||||
|
|
||||||
By default, when running in websockets mode, this client will automatically
|
By default, when running in websockets mode, this client will automatically
|
||||||
keep trying to reconnect to the RPC server should the connection be lost. There
|
keep trying to reconnect to the RPC server should the connection be lost. There
|
||||||
|
@ -116,7 +116,7 @@ chain services will be available. Depending on your application, you might only
|
||||||
need chain-related RPCs. In contrast, btcwallet provides pass through treatment
|
need chain-related RPCs. In contrast, btcwallet provides pass through treatment
|
||||||
for chain-related RPCs, so it supports them in addition to wallet-related RPCs.
|
for chain-related RPCs, so it supports them in addition to wallet-related RPCs.
|
||||||
|
|
||||||
Errors
|
# Errors
|
||||||
|
|
||||||
There are 3 categories of errors that will be returned throughout this package:
|
There are 3 categories of errors that will be returned throughout this package:
|
||||||
|
|
||||||
|
@ -144,35 +144,35 @@ The third category of errors, that is errors returned by the server, can be
|
||||||
detected by type asserting the error in a *btcjson.RPCError. For example, to
|
detected by type asserting the error in a *btcjson.RPCError. For example, to
|
||||||
detect if a command is unimplemented by the remote RPC server:
|
detect if a command is unimplemented by the remote RPC server:
|
||||||
|
|
||||||
amount, err := client.GetBalance("")
|
amount, err := client.GetBalance("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if jerr, ok := err.(*btcjson.RPCError); ok {
|
if jerr, ok := err.(*btcjson.RPCError); ok {
|
||||||
switch jerr.Code {
|
switch jerr.Code {
|
||||||
case btcjson.ErrRPCUnimplemented:
|
case btcjson.ErrRPCUnimplemented:
|
||||||
// Handle not implemented error
|
// Handle not implemented error
|
||||||
|
|
||||||
// Handle other specific errors you care about
|
// Handle other specific errors you care about
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log or otherwise handle the error knowing it was not one returned
|
// Log or otherwise handle the error knowing it was not one returned
|
||||||
// from the remote RPC server.
|
// from the remote RPC server.
|
||||||
}
|
}
|
||||||
|
|
||||||
Example Usage
|
# Example Usage
|
||||||
|
|
||||||
The following full-blown client examples are in the examples directory:
|
The following full-blown client examples are in the examples directory:
|
||||||
|
|
||||||
- bitcoincorehttp
|
- bitcoincorehttp
|
||||||
Connects to a bitcoin core RPC server using HTTP POST mode with TLS disabled
|
Connects to a bitcoin core RPC server using HTTP POST mode with TLS disabled
|
||||||
and gets the current block count
|
and gets the current block count
|
||||||
- btcdwebsockets
|
- btcdwebsockets
|
||||||
Connects to a btcd RPC server using TLS-secured websockets, registers for
|
Connects to a btcd RPC server using TLS-secured websockets, registers for
|
||||||
block connected and block disconnected notifications, and gets the current
|
block connected and block disconnected notifications, and gets the current
|
||||||
block count
|
block count
|
||||||
- btcwalletwebsockets
|
- btcwalletwebsockets
|
||||||
Connects to a btcwallet RPC server using TLS-secured websockets, registers
|
Connects to a btcwallet RPC server using TLS-secured websockets, registers
|
||||||
for notifications about changes to account balances, and gets a list of
|
for notifications about changes to account balances, and gets a list of
|
||||||
unspent transaction outputs (utxos) the wallet can sign
|
unspent transaction outputs (utxos) the wallet can sign
|
||||||
*/
|
*/
|
||||||
package rpcclient
|
package rpcclient
|
||||||
|
|
|
@ -1,15 +1,21 @@
|
||||||
# lbcd Websockets Example
|
# lbcdbloknotify
|
||||||
|
|
||||||
This example shows how to use the rpcclient package to connect to a btcd RPC
|
This bridge program subscribes to lbcd's notifications over websockets using the rpcclient package.
|
||||||
server using TLS-secured websockets, register for block connected and block
|
Users can specify supported actions upon receiving this notifications.
|
||||||
disconnected notifications, and get the current block count.
|
|
||||||
|
|
||||||
## Running the Example
|
## Building(or Running) the Program
|
||||||
|
|
||||||
The first step is to clone the lbcd package:
|
Clone the lbcd package:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ git clone github.com/lbryio/lbcd
|
$ git clone github.com/lbryio/lbcd
|
||||||
|
$ cd lbcd/rpcclient/examples
|
||||||
|
|
||||||
|
# build the program
|
||||||
|
$ go build .
|
||||||
|
|
||||||
|
# or directly run it (build implicitly behind the scene)
|
||||||
|
$ go run .
|
||||||
```
|
```
|
||||||
|
|
||||||
Display available options:
|
Display available options:
|
||||||
|
@ -29,18 +35,31 @@ $ go run . -h
|
||||||
Stratum server (default "lbrypool.net:3334")
|
Stratum server (default "lbrypool.net:3334")
|
||||||
-stratumpass string
|
-stratumpass string
|
||||||
Stratum server password (default "password")
|
Stratum server password (default "password")
|
||||||
|
-quiet
|
||||||
|
Do not print periodic logs
|
||||||
```
|
```
|
||||||
|
|
||||||
Start the program:
|
Running the program:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
$ go run . -stratumpass <STRATUM PASSWD> -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD>
|
# Send stratum mining.update_block mesage upon receving block connected notifiations.
|
||||||
|
$ go run . -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD> --notls -stratum <STRATUM SERVER> -stratumpass <STRATUM PASSWD>
|
||||||
|
|
||||||
2022/01/10 23:16:21 NotifyBlocks: Registration Complete
|
2022/01/10 23:16:21 Current block count: 1093112
|
||||||
2022/01/10 23:16:21 Block count: 1093112
|
|
||||||
...
|
...
|
||||||
|
|
||||||
|
# Execute a custome command (with blockhash) upon receving block connected notifiations.
|
||||||
|
$ go run . -rpcuser <RPC USERNAME> -rpcpass <RPC PASSWD> --notls -run "echo %s"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
* Stratum TCP connection is persisted with auto-reconnect. (retry backoff increases from 1s to 60s maximum)
|
||||||
|
|
||||||
|
* Stratum update_block jobs on previous notifications are canceled when a new notification arrives.
|
||||||
|
Usually, the jobs are so short and completed immediately. However, if the Stratum connection is broken, this
|
||||||
|
prevents the bridge from accumulating stale jobs.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This example is licensed under the [copyfree](http://copyfree.org) ISC License.
|
This example is licensed under the [copyfree](http://copyfree.org) ISC License.
|
||||||
|
|
20
rpcclient/examples/lbcdblocknotify/adapter.go
Normal file
20
rpcclient/examples/lbcdblocknotify/adapter.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/lbryio/lbcd/wire"
|
||||||
|
"github.com/lbryio/lbcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
type eventBlockConected struct {
|
||||||
|
height int32
|
||||||
|
header *wire.BlockHeader
|
||||||
|
txns []*lbcutil.Tx
|
||||||
|
}
|
||||||
|
|
||||||
|
type adapter struct {
|
||||||
|
*bridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *adapter) onFilteredBlockConnected(height int32, header *wire.BlockHeader, txns []*lbcutil.Tx) {
|
||||||
|
a.eventCh <- &eventBlockConected{height, header, txns}
|
||||||
|
}
|
172
rpcclient/examples/lbcdblocknotify/bridge.go
Normal file
172
rpcclient/examples/lbcdblocknotify/bridge.go
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type bridge struct {
|
||||||
|
ctx context.Context
|
||||||
|
|
||||||
|
prevJobContext context.Context
|
||||||
|
prevJobCancel context.CancelFunc
|
||||||
|
|
||||||
|
eventCh chan interface{}
|
||||||
|
errorc chan error
|
||||||
|
wg sync.WaitGroup
|
||||||
|
|
||||||
|
stratum *stratumClient
|
||||||
|
|
||||||
|
customCmd string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBridge(stratumServer, stratumPass, coinid string) *bridge {
|
||||||
|
|
||||||
|
s := &bridge{
|
||||||
|
ctx: context.Background(),
|
||||||
|
eventCh: make(chan interface{}),
|
||||||
|
errorc: make(chan error),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(stratumServer) > 0 {
|
||||||
|
s.stratum = newStratumClient(stratumServer, stratumPass, coinid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridge) start() {
|
||||||
|
|
||||||
|
if b.stratum != nil {
|
||||||
|
backoff := time.Second
|
||||||
|
for {
|
||||||
|
err := b.stratum.dial()
|
||||||
|
if err == nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Printf("WARN: stratum.dial() error: %s, retry in %s", err, backoff)
|
||||||
|
time.Sleep(backoff)
|
||||||
|
if backoff < 60*time.Second {
|
||||||
|
backoff += time.Second
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for e := range b.eventCh {
|
||||||
|
switch e := e.(type) {
|
||||||
|
case *eventBlockConected:
|
||||||
|
b.handleFilteredBlockConnected(e)
|
||||||
|
default:
|
||||||
|
b.errorc <- fmt.Errorf("unknown event type: %T", e)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *bridge) handleFilteredBlockConnected(e *eventBlockConected) {
|
||||||
|
|
||||||
|
if !*quiet {
|
||||||
|
log.Printf("Block connected: %s (%d) %v", e.header.BlockHash(), e.height, e.header.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := e.header.BlockHash().String()
|
||||||
|
height := e.height
|
||||||
|
|
||||||
|
// Cancel jobs on previous block. It's safe if they are already done.
|
||||||
|
if b.prevJobContext != nil {
|
||||||
|
select {
|
||||||
|
case <-b.prevJobContext.Done():
|
||||||
|
log.Printf("prev one canceled")
|
||||||
|
default:
|
||||||
|
b.prevJobCancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait until all previous jobs are done or canceled.
|
||||||
|
b.wg.Wait()
|
||||||
|
|
||||||
|
// Create and save cancelable subcontext for new jobs.
|
||||||
|
ctx, cancel := context.WithCancel(b.ctx)
|
||||||
|
b.prevJobContext, b.prevJobCancel = ctx, cancel
|
||||||
|
|
||||||
|
if len(b.customCmd) > 0 {
|
||||||
|
go b.execCustomCommand(ctx, hash, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send stratum update block message
|
||||||
|
if b.stratum != nil {
|
||||||
|
go b.stratumUpdateBlock(ctx, hash, height)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *bridge) stratumUpdateBlock(ctx context.Context, hash string, height int32) {
|
||||||
|
s.wg.Add(1)
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
backoff := time.Second
|
||||||
|
retry := func(err error) {
|
||||||
|
if backoff < 60*time.Second {
|
||||||
|
backoff += time.Second
|
||||||
|
}
|
||||||
|
log.Printf("WARN: stratum.send() on block %d error: %s", height, err)
|
||||||
|
time.Sleep(backoff)
|
||||||
|
s.stratum.dial()
|
||||||
|
}
|
||||||
|
|
||||||
|
msg := stratumUpdateBlockMsg(*stratumPass, *coinid, hash)
|
||||||
|
|
||||||
|
for {
|
||||||
|
switch err := s.stratum.send(ctx, msg); {
|
||||||
|
case err == nil:
|
||||||
|
return
|
||||||
|
case errors.Is(err, context.Canceled):
|
||||||
|
log.Printf("INFO: stratum.send() on block %d: %s.", height, err)
|
||||||
|
return
|
||||||
|
case errors.Is(err, syscall.EPIPE):
|
||||||
|
errClose := s.stratum.conn.Close()
|
||||||
|
if errClose != nil {
|
||||||
|
log.Printf("WARN: stratum.conn.Close() on block %d: %s.", height, errClose)
|
||||||
|
}
|
||||||
|
retry(err)
|
||||||
|
case errors.Is(err, net.ErrClosed):
|
||||||
|
retry(err)
|
||||||
|
default:
|
||||||
|
retry(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *bridge) execCustomCommand(ctx context.Context, hash string, height int32) {
|
||||||
|
s.wg.Add(1)
|
||||||
|
defer s.wg.Done()
|
||||||
|
|
||||||
|
cmd := strings.ReplaceAll(s.customCmd, "%s", hash)
|
||||||
|
err := doExecCustomCommand(ctx, cmd)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("ERROR: execCustomCommand on block %s(%d): %s", hash, height, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func doExecCustomCommand(ctx context.Context, cmd string) error {
|
||||||
|
strs := strings.Split(cmd, " ")
|
||||||
|
path, err := exec.LookPath(strs[0])
|
||||||
|
if errors.Is(err, exec.ErrDot) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c := exec.CommandContext(ctx, path, strs[1:]...)
|
||||||
|
c.Stdout = os.Stdout
|
||||||
|
return c.Run()
|
||||||
|
}
|
53
rpcclient/examples/lbcdblocknotify/lbcdclient.go
Normal file
53
rpcclient/examples/lbcdblocknotify/lbcdclient.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"log"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/lbryio/lbcd/rpcclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newLbcdClient(server, user, pass string, notls bool, adpt adapter) *rpcclient.Client {
|
||||||
|
|
||||||
|
ntfnHandlers := rpcclient.NotificationHandlers{
|
||||||
|
OnFilteredBlockConnected: adpt.onFilteredBlockConnected,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config lbcd RPC client with websockets.
|
||||||
|
connCfg := &rpcclient.ConnConfig{
|
||||||
|
Host: server,
|
||||||
|
Endpoint: "ws",
|
||||||
|
User: user,
|
||||||
|
Pass: pass,
|
||||||
|
DisableTLS: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
if !notls {
|
||||||
|
cert, err := ioutil.ReadFile(filepath.Join(lbcdHomeDir, "rpc.cert"))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can't read lbcd certificate: %s", err)
|
||||||
|
}
|
||||||
|
connCfg.Certificates = cert
|
||||||
|
connCfg.DisableTLS = false
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := rpcclient.New(connCfg, &ntfnHandlers)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can't create rpc client: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register for block connect and disconnect notifications.
|
||||||
|
if err = client.NotifyBlocks(); err != nil {
|
||||||
|
log.Fatalf("can't register block notification: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current block count.
|
||||||
|
blockCount, err := client.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("can't get block count: %s", err)
|
||||||
|
}
|
||||||
|
log.Printf("Current block count: %d", blockCount)
|
||||||
|
|
||||||
|
return client
|
||||||
|
}
|
|
@ -1,103 +1,63 @@
|
||||||
// Copyright (c) 2014-2017 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"log"
|
"log"
|
||||||
"net"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/lbryio/lbcd/rpcclient"
|
|
||||||
"github.com/lbryio/lbcd/wire"
|
|
||||||
"github.com/lbryio/lbcutil"
|
"github.com/lbryio/lbcutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func send(stratum, stratumPass, coinid, blockHash string) error {
|
var (
|
||||||
addr, err := net.ResolveTCPAddr("tcp", stratum)
|
lbcdHomeDir = lbcutil.AppDataDir("lbcd", false)
|
||||||
if err != nil {
|
defaultCert = filepath.Join(lbcdHomeDir, "rpc.cert")
|
||||||
return fmt.Errorf("can't resolve addr: %w", err)
|
)
|
||||||
}
|
var (
|
||||||
|
coinid = flag.String("coinid", "1425", "Coin ID")
|
||||||
conn, err := net.DialTCP("tcp", nil, addr)
|
stratumServer = flag.String("stratum", "", "Stratum server")
|
||||||
if err != nil {
|
stratumPass = flag.String("stratumpass", "", "Stratum server password")
|
||||||
return fmt.Errorf("can't dial tcp: %w", err)
|
rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server")
|
||||||
}
|
rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username")
|
||||||
defer conn.Close()
|
rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password")
|
||||||
|
rpccert = flag.String("rpccert", defaultCert, "LBCD RPC certificate")
|
||||||
msg := fmt.Sprintf(`{"id":1,"method":"mining.update_block","params":[%q,%q,%q]}`,
|
notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled")
|
||||||
stratumPass, coinid, blockHash)
|
run = flag.String("run", "", "Run custom shell command")
|
||||||
|
quiet = flag.Bool("quiet", false, "Do not print logs")
|
||||||
_, err = conn.Write([]byte(msg))
|
)
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("can't write message: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
var (
|
|
||||||
coinid = flag.String("coinid", "1425", "Coin ID")
|
|
||||||
stratum = flag.String("stratum", "lbrypool.net:3334", "Stratum server")
|
|
||||||
stratumPass = flag.String("stratumpass", "password", "Stratum server password")
|
|
||||||
rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server")
|
|
||||||
rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username")
|
|
||||||
rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password")
|
|
||||||
notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled")
|
|
||||||
)
|
|
||||||
|
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
ntfnHandlers := rpcclient.NotificationHandlers{
|
// Setup notification handler
|
||||||
OnFilteredBlockConnected: func(height int32, header *wire.BlockHeader, txns []*lbcutil.Tx) {
|
b := newBridge(*stratumServer, *stratumPass, *coinid)
|
||||||
|
|
||||||
blockHash := header.BlockHash().String()
|
if len(*run) > 0 {
|
||||||
|
// Check if ccommand exists.
|
||||||
log.Printf("Block connected: %v (%d) %v", blockHash, height, header.Timestamp)
|
strs := strings.Split(*run, " ")
|
||||||
|
cmd := strs[0]
|
||||||
if err := send(*stratum, *stratumPass, *coinid, blockHash); err != nil {
|
_, err := exec.LookPath(cmd)
|
||||||
log.Printf("ERROR: failed to notify stratum: %s", err)
|
if err != nil {
|
||||||
}
|
log.Fatalf("ERROR: %s not found: %s", cmd, err)
|
||||||
},
|
}
|
||||||
|
b.customCmd = *run
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connect to local lbcd RPC server using websockets.
|
// Start the eventt handler.
|
||||||
lbcdHomeDir := lbcutil.AppDataDir("lbcd", false)
|
go b.start()
|
||||||
certs, err := ioutil.ReadFile(filepath.Join(lbcdHomeDir, "rpc.cert"))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("can't read lbcd certificate: %s", err)
|
|
||||||
}
|
|
||||||
connCfg := &rpcclient.ConnConfig{
|
|
||||||
Host: *rpcserver,
|
|
||||||
Endpoint: "ws",
|
|
||||||
User: *rpcuser,
|
|
||||||
Pass: *rpcpass,
|
|
||||||
Certificates: certs,
|
|
||||||
DisableTLS: *notls,
|
|
||||||
}
|
|
||||||
client, err := rpcclient.New(connCfg, &ntfnHandlers)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("can't create rpc client: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register for block connect and disconnect notifications.
|
// Adaptater receives lbcd notifications, and emit events.
|
||||||
if err = client.NotifyBlocks(); err != nil {
|
adpt := adapter{b}
|
||||||
log.Fatalf("can't register block notification: %s", err)
|
|
||||||
}
|
|
||||||
log.Printf("NotifyBlocks: Registration Complete")
|
|
||||||
|
|
||||||
// Get the current block count.
|
client := newLbcdClient(*rpcserver, *rpcuser, *rpcpass, *notls, adpt)
|
||||||
blockCount, err := client.GetBlockCount()
|
|
||||||
if err != nil {
|
go func() {
|
||||||
log.Fatalf("can't get block count: %s", err)
|
err := <-b.errorc
|
||||||
}
|
log.Fatalf("ERROR: %s", err)
|
||||||
log.Printf("Block count: %d", blockCount)
|
client.Shutdown()
|
||||||
|
}()
|
||||||
|
|
||||||
// Wait until the client either shuts down gracefully (or the user
|
// Wait until the client either shuts down gracefully (or the user
|
||||||
// terminates the process with Ctrl+C).
|
// terminates the process with Ctrl+C).
|
||||||
|
|
56
rpcclient/examples/lbcdblocknotify/stratumclient.go
Normal file
56
rpcclient/examples/lbcdblocknotify/stratumclient.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stratumClient struct {
|
||||||
|
server string
|
||||||
|
passwd string
|
||||||
|
coinid string
|
||||||
|
conn *net.TCPConn
|
||||||
|
}
|
||||||
|
|
||||||
|
func newStratumClient(server, passwd, coinid string) *stratumClient {
|
||||||
|
|
||||||
|
return &stratumClient{
|
||||||
|
server: server,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *stratumClient) dial() error {
|
||||||
|
|
||||||
|
addr, err := net.ResolveTCPAddr("tcp", c.server)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolve tcp addr: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := net.DialTCP("tcp", nil, addr)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("dial tcp: %w", err)
|
||||||
|
}
|
||||||
|
c.conn = conn
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *stratumClient) send(ctx context.Context, msg string) error {
|
||||||
|
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return ctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := c.conn.Write([]byte(msg))
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func stratumUpdateBlockMsg(stratumPass, coinid, blockHash string) string {
|
||||||
|
|
||||||
|
return fmt.Sprintf(`{"id":1,"method":"mining.update_block","params":[%q,%s,%q]}`,
|
||||||
|
stratumPass, coinid, blockHash)
|
||||||
|
}
|
|
@ -56,7 +56,8 @@ func (c *Client) DebugLevelAsync(levelSpec string) FutureDebugLevelResult {
|
||||||
// specification.
|
// specification.
|
||||||
//
|
//
|
||||||
// The levelspec can be either a debug level or of the form:
|
// The levelspec can be either a debug level or of the form:
|
||||||
// <subsystem>=<level>,<subsystem2>=<level2>,...
|
//
|
||||||
|
// <subsystem>=<level>,<subsystem2>=<level2>,...
|
||||||
//
|
//
|
||||||
// Additionally, the special keyword 'show' can be used to get a list of the
|
// Additionally, the special keyword 'show' can be used to get a list of the
|
||||||
// available subsystems.
|
// available subsystems.
|
||||||
|
|
|
@ -774,7 +774,8 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) {
|
||||||
tries := 10
|
tries := 10
|
||||||
for i := 0; tries == 0 || i < tries; i++ {
|
for i := 0; tries == 0 || i < tries; i++ {
|
||||||
bodyReader := bytes.NewReader(jReq.marshalledJSON)
|
bodyReader := bytes.NewReader(jReq.marshalledJSON)
|
||||||
httpReq, err := http.NewRequest("POST", url, bodyReader)
|
var httpReq *http.Request
|
||||||
|
httpReq, err = http.NewRequest("POST", url, bodyReader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jReq.responseChan <- &Response{result: nil, err: err}
|
jReq.responseChan <- &Response{result: nil, err: err}
|
||||||
return
|
return
|
||||||
|
@ -786,7 +787,8 @@ func (c *Client) handleSendPostMessage(jReq *jsonRequest) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure basic access authorization.
|
// Configure basic access authorization.
|
||||||
user, pass, err := c.config.getAuth()
|
var user, pass string
|
||||||
|
user, pass, err = c.config.getAuth()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
jReq.responseChan <- &Response{result: nil, err: err}
|
jReq.responseChan <- &Response{result: nil, err: err}
|
||||||
return
|
return
|
||||||
|
|
|
@ -355,6 +355,75 @@ func (c *Client) GetPeerInfo() ([]btcjson.GetPeerInfoResult, error) {
|
||||||
return c.GetPeerInfoAsync().Receive()
|
return c.GetPeerInfoAsync().Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FutureListBannedResult is a future promise to deliver the result of a
|
||||||
|
// ListBannedAsync RPC invocation (or an applicable error).
|
||||||
|
type FutureListBannedResult chan *Response
|
||||||
|
|
||||||
|
// Receive waits for the Response promised by the future and returns data about
|
||||||
|
// each connected network peer.
|
||||||
|
func (r FutureListBannedResult) Receive() ([]btcjson.ListBannedResult, error) {
|
||||||
|
res, err := ReceiveFuture(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal result as an array of ListBanned result objects.
|
||||||
|
var bannedPeers []btcjson.ListBannedResult
|
||||||
|
err = json.Unmarshal(res, &bannedPeers)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return bannedPeers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBanCommand enumerates the available commands that the SetBanCommand function
|
||||||
|
// accepts.
|
||||||
|
type SetBanCommand string
|
||||||
|
|
||||||
|
// Constants used to indicate the command for the SetBanCommand function.
|
||||||
|
const (
|
||||||
|
// SBAdd indicates the specified host should be added as a banned
|
||||||
|
// peer.
|
||||||
|
SBAdd SetBanCommand = "add"
|
||||||
|
|
||||||
|
// SBRemove indicates the specified peer should be removed.
|
||||||
|
SBRemove SetBanCommand = "remove"
|
||||||
|
)
|
||||||
|
|
||||||
|
// String returns the SetBanCommand in human-readable form.
|
||||||
|
func (cmd SetBanCommand) String() string {
|
||||||
|
return string(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FutureSetBanResult is a future promise to deliver the result of an
|
||||||
|
// SetBanAsync RPC invocation (or an applicable error).
|
||||||
|
type FutureSetBanResult chan *Response
|
||||||
|
|
||||||
|
// Receive waits for the Response promised by the future and returns an error if
|
||||||
|
// any occurred when performing the specified command.
|
||||||
|
func (r FutureSetBanResult) Receive() error {
|
||||||
|
_, err := ReceiveFuture(r)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBanAsync returns an instance of a type that can be used to get the result
|
||||||
|
// of the RPC at some future time by invoking the Receive function on the
|
||||||
|
// returned instance.
|
||||||
|
func (c *Client) SetBanAsync(addr string, command string, banTime *int,
|
||||||
|
absolute *bool) FutureSetBanResult {
|
||||||
|
cmd := btcjson.NewSetBanCmd(addr, btcjson.SetBanSubCmd(command), banTime,
|
||||||
|
absolute)
|
||||||
|
return c.SendCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetBan attempts to perform the passed command on the passed persistent peer.
|
||||||
|
// For example, it can be used to add or a remove a banned peer.
|
||||||
|
func (c *Client) SetBan(addr string, command string, banTime *int,
|
||||||
|
absolute *bool) error {
|
||||||
|
return c.SetBanAsync(addr, command, banTime, absolute).Receive()
|
||||||
|
}
|
||||||
|
|
||||||
// FutureGetNetTotalsResult is a future promise to deliver the result of a
|
// FutureGetNetTotalsResult is a future promise to deliver the result of a
|
||||||
// GetNetTotalsAsync RPC invocation (or an applicable error).
|
// GetNetTotalsAsync RPC invocation (or an applicable error).
|
||||||
type FutureGetNetTotalsResult chan *Response
|
type FutureGetNetTotalsResult chan *Response
|
||||||
|
|
|
@ -291,13 +291,18 @@ func (r FutureCreateRawTransactionResult) Receive() (*wire.MsgTx, error) {
|
||||||
//
|
//
|
||||||
// See CreateRawTransaction for the blocking version and more details.
|
// See CreateRawTransaction for the blocking version and more details.
|
||||||
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
||||||
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) FutureCreateRawTransactionResult {
|
outputs map[btcutil.Address]interface{}, lockTime *int64) FutureCreateRawTransactionResult {
|
||||||
|
|
||||||
convertedAmts := make(map[string]float64, len(amounts))
|
convertedData := make(map[string]interface{}, len(outputs))
|
||||||
for addr, amount := range amounts {
|
for key, value := range outputs {
|
||||||
convertedAmts[addr.String()] = amount.ToBTC()
|
switch val := value.(type) {
|
||||||
|
case btcutil.Amount:
|
||||||
|
convertedData[key.String()] = val.ToBTC()
|
||||||
|
case string:
|
||||||
|
convertedData[key.String()] = val
|
||||||
|
}
|
||||||
}
|
}
|
||||||
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedAmts, lockTime)
|
cmd := btcjson.NewCreateRawTransactionCmd(inputs, convertedData, lockTime)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -305,9 +310,9 @@ func (c *Client) CreateRawTransactionAsync(inputs []btcjson.TransactionInput,
|
||||||
// and sending to the provided addresses. If the inputs are either nil or an
|
// and sending to the provided addresses. If the inputs are either nil or an
|
||||||
// empty slice, it is interpreted as an empty slice.
|
// empty slice, it is interpreted as an empty slice.
|
||||||
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
|
func (c *Client) CreateRawTransaction(inputs []btcjson.TransactionInput,
|
||||||
amounts map[btcutil.Address]btcutil.Amount, lockTime *int64) (*wire.MsgTx, error) {
|
outputs map[btcutil.Address]interface{}, lockTime *int64) (*wire.MsgTx, error) {
|
||||||
|
|
||||||
return c.CreateRawTransactionAsync(inputs, amounts, lockTime).Receive()
|
return c.CreateRawTransactionAsync(inputs, outputs, lockTime).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FutureSendRawTransactionResult is a future promise to deliver the result
|
// FutureSendRawTransactionResult is a future promise to deliver the result
|
||||||
|
|
|
@ -536,9 +536,10 @@ func (r FutureSendToAddressResult) Receive() (*chainhash.Hash, error) {
|
||||||
// returned instance.
|
// returned instance.
|
||||||
//
|
//
|
||||||
// See SendToAddress for the blocking version and more details.
|
// See SendToAddress for the blocking version and more details.
|
||||||
func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amount) FutureSendToAddressResult {
|
func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amount,
|
||||||
|
addrType *string) FutureSendToAddressResult {
|
||||||
addr := address.EncodeAddress()
|
addr := address.EncodeAddress()
|
||||||
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), nil, nil)
|
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), addrType, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,8 +551,9 @@ func (c *Client) SendToAddressAsync(address btcutil.Address, amount btcutil.Amou
|
||||||
//
|
//
|
||||||
// NOTE: This function requires to the wallet to be unlocked. See the
|
// NOTE: This function requires to the wallet to be unlocked. See the
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount) (*chainhash.Hash, error) {
|
func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount,
|
||||||
return c.SendToAddressAsync(address, amount).Receive()
|
addrType *string) (*chainhash.Hash, error) {
|
||||||
|
return c.SendToAddressAsync(address, amount, addrType).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendToAddressCommentAsync returns an instance of a type that can be used to
|
// SendToAddressCommentAsync returns an instance of a type that can be used to
|
||||||
|
@ -560,12 +562,12 @@ func (c *Client) SendToAddress(address btcutil.Address, amount btcutil.Amount) (
|
||||||
//
|
//
|
||||||
// See SendToAddressComment for the blocking version and more details.
|
// See SendToAddressComment for the blocking version and more details.
|
||||||
func (c *Client) SendToAddressCommentAsync(address btcutil.Address,
|
func (c *Client) SendToAddressCommentAsync(address btcutil.Address,
|
||||||
amount btcutil.Amount, comment,
|
amount btcutil.Amount, addrType *string, comment string,
|
||||||
commentTo string) FutureSendToAddressResult {
|
commentTo string) FutureSendToAddressResult {
|
||||||
|
|
||||||
addr := address.EncodeAddress()
|
addr := address.EncodeAddress()
|
||||||
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), &comment,
|
cmd := btcjson.NewSendToAddressCmd(addr, amount.ToBTC(), addrType,
|
||||||
&commentTo)
|
&comment, &commentTo)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -581,9 +583,10 @@ func (c *Client) SendToAddressCommentAsync(address btcutil.Address,
|
||||||
//
|
//
|
||||||
// NOTE: This function requires to the wallet to be unlocked. See the
|
// NOTE: This function requires to the wallet to be unlocked. See the
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendToAddressComment(address btcutil.Address, amount btcutil.Amount, comment, commentTo string) (*chainhash.Hash, error) {
|
func (c *Client) SendToAddressComment(address btcutil.Address, amount btcutil.Amount,
|
||||||
return c.SendToAddressCommentAsync(address, amount, comment,
|
addrType *string, comment, commentTo string) (*chainhash.Hash, error) {
|
||||||
commentTo).Receive()
|
return c.SendToAddressCommentAsync(address, amount, addrType,
|
||||||
|
comment, commentTo).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FutureSendFromResult is a future promise to deliver the result of a
|
// FutureSendFromResult is a future promise to deliver the result of a
|
||||||
|
@ -615,10 +618,11 @@ func (r FutureSendFromResult) Receive() (*chainhash.Hash, error) {
|
||||||
// returned instance.
|
// returned instance.
|
||||||
//
|
//
|
||||||
// See SendFrom for the blocking version and more details.
|
// See SendFrom for the blocking version and more details.
|
||||||
func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount) FutureSendFromResult {
|
func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address,
|
||||||
|
amount btcutil.Amount, addrType *string) FutureSendFromResult {
|
||||||
addr := toAddress.EncodeAddress()
|
addr := toAddress.EncodeAddress()
|
||||||
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(), nil,
|
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(), nil,
|
||||||
nil, nil)
|
addrType, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -630,8 +634,8 @@ func (c *Client) SendFromAsync(fromAccount string, toAddress btcutil.Address, am
|
||||||
//
|
//
|
||||||
// NOTE: This function requires to the wallet to be unlocked. See the
|
// NOTE: This function requires to the wallet to be unlocked. See the
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount) (*chainhash.Hash, error) {
|
func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, addrType *string) (*chainhash.Hash, error) {
|
||||||
return c.SendFromAsync(fromAccount, toAddress, amount).Receive()
|
return c.SendFromAsync(fromAccount, toAddress, amount, addrType).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendFromMinConfAsync returns an instance of a type that can be used to get
|
// SendFromMinConfAsync returns an instance of a type that can be used to get
|
||||||
|
@ -639,10 +643,12 @@ func (c *Client) SendFrom(fromAccount string, toAddress btcutil.Address, amount
|
||||||
// the returned instance.
|
// the returned instance.
|
||||||
//
|
//
|
||||||
// See SendFromMinConf for the blocking version and more details.
|
// See SendFromMinConf for the blocking version and more details.
|
||||||
func (c *Client) SendFromMinConfAsync(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, minConfirms int) FutureSendFromResult {
|
func (c *Client) SendFromMinConfAsync(fromAccount string,
|
||||||
|
toAddress btcutil.Address, amount btcutil.Amount,
|
||||||
|
minConfirms int, addrType *string) FutureSendFromResult {
|
||||||
addr := toAddress.EncodeAddress()
|
addr := toAddress.EncodeAddress()
|
||||||
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(),
|
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(),
|
||||||
&minConfirms, nil, nil)
|
&minConfirms, addrType, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -655,9 +661,10 @@ func (c *Client) SendFromMinConfAsync(fromAccount string, toAddress btcutil.Addr
|
||||||
//
|
//
|
||||||
// NOTE: This function requires to the wallet to be unlocked. See the
|
// NOTE: This function requires to the wallet to be unlocked. See the
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address, amount btcutil.Amount, minConfirms int) (*chainhash.Hash, error) {
|
func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address,
|
||||||
|
amount btcutil.Amount, minConfirms int, addrType *string) (*chainhash.Hash, error) {
|
||||||
return c.SendFromMinConfAsync(fromAccount, toAddress, amount,
|
return c.SendFromMinConfAsync(fromAccount, toAddress, amount,
|
||||||
minConfirms).Receive()
|
minConfirms, addrType).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendFromCommentAsync returns an instance of a type that can be used to get
|
// SendFromCommentAsync returns an instance of a type that can be used to get
|
||||||
|
@ -667,11 +674,11 @@ func (c *Client) SendFromMinConf(fromAccount string, toAddress btcutil.Address,
|
||||||
// See SendFromComment for the blocking version and more details.
|
// See SendFromComment for the blocking version and more details.
|
||||||
func (c *Client) SendFromCommentAsync(fromAccount string,
|
func (c *Client) SendFromCommentAsync(fromAccount string,
|
||||||
toAddress btcutil.Address, amount btcutil.Amount, minConfirms int,
|
toAddress btcutil.Address, amount btcutil.Amount, minConfirms int,
|
||||||
comment, commentTo string) FutureSendFromResult {
|
addrType *string, comment, commentTo string) FutureSendFromResult {
|
||||||
|
|
||||||
addr := toAddress.EncodeAddress()
|
addr := toAddress.EncodeAddress()
|
||||||
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(),
|
cmd := btcjson.NewSendFromCmd(fromAccount, addr, amount.ToBTC(),
|
||||||
&minConfirms, &comment, &commentTo)
|
&minConfirms, addrType, &comment, &commentTo)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -687,11 +694,11 @@ func (c *Client) SendFromCommentAsync(fromAccount string,
|
||||||
// NOTE: This function requires to the wallet to be unlocked. See the
|
// NOTE: This function requires to the wallet to be unlocked. See the
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendFromComment(fromAccount string, toAddress btcutil.Address,
|
func (c *Client) SendFromComment(fromAccount string, toAddress btcutil.Address,
|
||||||
amount btcutil.Amount, minConfirms int,
|
amount btcutil.Amount, minConfirms int, addrType *string,
|
||||||
comment, commentTo string) (*chainhash.Hash, error) {
|
comment, commentTo string) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
return c.SendFromCommentAsync(fromAccount, toAddress, amount,
|
return c.SendFromCommentAsync(fromAccount, toAddress, amount,
|
||||||
minConfirms, comment, commentTo).Receive()
|
minConfirms, addrType, comment, commentTo).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FutureSendManyResult is a future promise to deliver the result of a
|
// FutureSendManyResult is a future promise to deliver the result of a
|
||||||
|
@ -728,7 +735,7 @@ func (c *Client) SendManyAsync(fromAccount string, amounts map[btcutil.Address]b
|
||||||
for addr, amount := range amounts {
|
for addr, amount := range amounts {
|
||||||
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
|
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
|
||||||
}
|
}
|
||||||
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, nil, nil)
|
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts, nil, nil, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -751,14 +758,14 @@ func (c *Client) SendMany(fromAccount string, amounts map[btcutil.Address]btcuti
|
||||||
// See SendManyMinConf for the blocking version and more details.
|
// See SendManyMinConf for the blocking version and more details.
|
||||||
func (c *Client) SendManyMinConfAsync(fromAccount string,
|
func (c *Client) SendManyMinConfAsync(fromAccount string,
|
||||||
amounts map[btcutil.Address]btcutil.Amount,
|
amounts map[btcutil.Address]btcutil.Amount,
|
||||||
minConfirms int) FutureSendManyResult {
|
minConfirms int, addrType *string) FutureSendManyResult {
|
||||||
|
|
||||||
convertedAmounts := make(map[string]float64, len(amounts))
|
convertedAmounts := make(map[string]float64, len(amounts))
|
||||||
for addr, amount := range amounts {
|
for addr, amount := range amounts {
|
||||||
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
|
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
|
||||||
}
|
}
|
||||||
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts,
|
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts,
|
||||||
&minConfirms, nil)
|
&minConfirms, nil, addrType)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -773,9 +780,10 @@ func (c *Client) SendManyMinConfAsync(fromAccount string,
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendManyMinConf(fromAccount string,
|
func (c *Client) SendManyMinConf(fromAccount string,
|
||||||
amounts map[btcutil.Address]btcutil.Amount,
|
amounts map[btcutil.Address]btcutil.Amount,
|
||||||
minConfirms int) (*chainhash.Hash, error) {
|
minConfirms int, addrType *string) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
return c.SendManyMinConfAsync(fromAccount, amounts, minConfirms).Receive()
|
return c.SendManyMinConfAsync(fromAccount, amounts, minConfirms,
|
||||||
|
addrType).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// SendManyCommentAsync returns an instance of a type that can be used to get
|
// SendManyCommentAsync returns an instance of a type that can be used to get
|
||||||
|
@ -785,14 +793,14 @@ func (c *Client) SendManyMinConf(fromAccount string,
|
||||||
// See SendManyComment for the blocking version and more details.
|
// See SendManyComment for the blocking version and more details.
|
||||||
func (c *Client) SendManyCommentAsync(fromAccount string,
|
func (c *Client) SendManyCommentAsync(fromAccount string,
|
||||||
amounts map[btcutil.Address]btcutil.Amount, minConfirms int,
|
amounts map[btcutil.Address]btcutil.Amount, minConfirms int,
|
||||||
comment string) FutureSendManyResult {
|
addrType *string, comment string) FutureSendManyResult {
|
||||||
|
|
||||||
convertedAmounts := make(map[string]float64, len(amounts))
|
convertedAmounts := make(map[string]float64, len(amounts))
|
||||||
for addr, amount := range amounts {
|
for addr, amount := range amounts {
|
||||||
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
|
convertedAmounts[addr.EncodeAddress()] = amount.ToBTC()
|
||||||
}
|
}
|
||||||
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts,
|
cmd := btcjson.NewSendManyCmd(fromAccount, convertedAmounts,
|
||||||
&minConfirms, &comment)
|
&minConfirms, &comment, addrType)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -808,10 +816,10 @@ func (c *Client) SendManyCommentAsync(fromAccount string,
|
||||||
// WalletPassphrase function for more details.
|
// WalletPassphrase function for more details.
|
||||||
func (c *Client) SendManyComment(fromAccount string,
|
func (c *Client) SendManyComment(fromAccount string,
|
||||||
amounts map[btcutil.Address]btcutil.Amount, minConfirms int,
|
amounts map[btcutil.Address]btcutil.Amount, minConfirms int,
|
||||||
comment string) (*chainhash.Hash, error) {
|
addrType *string, comment string) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
return c.SendManyCommentAsync(fromAccount, amounts, minConfirms,
|
return c.SendManyCommentAsync(fromAccount, amounts, minConfirms,
|
||||||
comment).Receive()
|
addrType, comment).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// *************************
|
// *************************
|
||||||
|
@ -1016,10 +1024,10 @@ func (c *Client) CreateWalletAsync(name string, opts ...CreateWalletOpt) FutureC
|
||||||
//
|
//
|
||||||
// Optional parameters can be specified using functional-options pattern. The
|
// Optional parameters can be specified using functional-options pattern. The
|
||||||
// following functions are available:
|
// following functions are available:
|
||||||
// * WithCreateWalletDisablePrivateKeys
|
// - WithCreateWalletDisablePrivateKeys
|
||||||
// * WithCreateWalletBlank
|
// - WithCreateWalletBlank
|
||||||
// * WithCreateWalletPassphrase
|
// - WithCreateWalletPassphrase
|
||||||
// * WithCreateWalletAvoidReuse
|
// - WithCreateWalletAvoidReuse
|
||||||
func (c *Client) CreateWallet(name string, opts ...CreateWalletOpt) (*btcjson.CreateWalletResult, error) {
|
func (c *Client) CreateWallet(name string, opts ...CreateWalletOpt) (*btcjson.CreateWalletResult, error) {
|
||||||
return c.CreateWalletAsync(name, opts...).Receive()
|
return c.CreateWalletAsync(name, opts...).Receive()
|
||||||
}
|
}
|
||||||
|
@ -1135,8 +1143,8 @@ func (r FutureGetRawChangeAddressResult) Receive() (btcutil.Address, error) {
|
||||||
// function on the returned instance.
|
// function on the returned instance.
|
||||||
//
|
//
|
||||||
// See GetRawChangeAddress for the blocking version and more details.
|
// See GetRawChangeAddress for the blocking version and more details.
|
||||||
func (c *Client) GetRawChangeAddressAsync(account string) FutureGetRawChangeAddressResult {
|
func (c *Client) GetRawChangeAddressAsync(account *string) FutureGetRawChangeAddressResult {
|
||||||
cmd := btcjson.NewGetRawChangeAddressCmd(&account)
|
cmd := btcjson.NewGetRawChangeAddressCmd(account)
|
||||||
result := FutureGetRawChangeAddressResult{
|
result := FutureGetRawChangeAddressResult{
|
||||||
network: c.chainParams,
|
network: c.chainParams,
|
||||||
responseChannel: c.SendCmd(cmd),
|
responseChannel: c.SendCmd(cmd),
|
||||||
|
@ -1147,7 +1155,7 @@ func (c *Client) GetRawChangeAddressAsync(account string) FutureGetRawChangeAddr
|
||||||
// GetRawChangeAddress returns a new address for receiving change that will be
|
// GetRawChangeAddress returns a new address for receiving change that will be
|
||||||
// associated with the provided account. Note that this is only for raw
|
// associated with the provided account. Note that this is only for raw
|
||||||
// transactions and NOT for normal use.
|
// transactions and NOT for normal use.
|
||||||
func (c *Client) GetRawChangeAddress(account string) (btcutil.Address, error) {
|
func (c *Client) GetRawChangeAddress(account *string) (btcutil.Address, error) {
|
||||||
return c.GetRawChangeAddressAsync(account).Receive()
|
return c.GetRawChangeAddressAsync(account).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1226,7 +1234,7 @@ func (r FutureGetAccountAddressResult) Receive() (btcutil.Address, error) {
|
||||||
// the returned instance.
|
// the returned instance.
|
||||||
//
|
//
|
||||||
// See GetAccountAddress for the blocking version and more details.
|
// See GetAccountAddress for the blocking version and more details.
|
||||||
func (c *Client) GetAccountAddressAsync(account string) FutureGetAccountAddressResult {
|
func (c *Client) GetAccountAddressAsync(account *string) FutureGetAccountAddressResult {
|
||||||
cmd := btcjson.NewGetAccountAddressCmd(account)
|
cmd := btcjson.NewGetAccountAddressCmd(account)
|
||||||
result := FutureGetAccountAddressResult{
|
result := FutureGetAccountAddressResult{
|
||||||
network: c.chainParams,
|
network: c.chainParams,
|
||||||
|
@ -1237,7 +1245,7 @@ func (c *Client) GetAccountAddressAsync(account string) FutureGetAccountAddressR
|
||||||
|
|
||||||
// GetAccountAddress returns the current Bitcoin address for receiving payments
|
// GetAccountAddress returns the current Bitcoin address for receiving payments
|
||||||
// to the specified account.
|
// to the specified account.
|
||||||
func (c *Client) GetAccountAddress(account string) (btcutil.Address, error) {
|
func (c *Client) GetAccountAddress(account *string) (btcutil.Address, error) {
|
||||||
return c.GetAccountAddressAsync(account).Receive()
|
return c.GetAccountAddressAsync(account).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1279,33 +1287,6 @@ func (c *Client) GetAccount(address btcutil.Address) (string, error) {
|
||||||
return c.GetAccountAsync(address).Receive()
|
return c.GetAccountAsync(address).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
// FutureSetAccountResult is a future promise to deliver the result of a
|
|
||||||
// SetAccountAsync RPC invocation (or an applicable error).
|
|
||||||
type FutureSetAccountResult chan *Response
|
|
||||||
|
|
||||||
// Receive waits for the Response promised by the future and returns the result
|
|
||||||
// of setting the account to be associated with the passed address.
|
|
||||||
func (r FutureSetAccountResult) Receive() error {
|
|
||||||
_, err := ReceiveFuture(r)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAccountAsync returns an instance of a type that can be used to get the
|
|
||||||
// result of the RPC at some future time by invoking the Receive function on the
|
|
||||||
// returned instance.
|
|
||||||
//
|
|
||||||
// See SetAccount for the blocking version and more details.
|
|
||||||
func (c *Client) SetAccountAsync(address btcutil.Address, account string) FutureSetAccountResult {
|
|
||||||
addr := address.EncodeAddress()
|
|
||||||
cmd := btcjson.NewSetAccountCmd(addr, account)
|
|
||||||
return c.SendCmd(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetAccount sets the account associated with the passed address.
|
|
||||||
func (c *Client) SetAccount(address btcutil.Address, account string) error {
|
|
||||||
return c.SetAccountAsync(address, account).Receive()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FutureGetAddressesByAccountResult is a future promise to deliver the result
|
// FutureGetAddressesByAccountResult is a future promise to deliver the result
|
||||||
// of a GetAddressesByAccountAsync RPC invocation (or an applicable error).
|
// of a GetAddressesByAccountAsync RPC invocation (or an applicable error).
|
||||||
type FutureGetAddressesByAccountResult struct {
|
type FutureGetAddressesByAccountResult struct {
|
||||||
|
@ -1344,7 +1325,7 @@ func (r FutureGetAddressesByAccountResult) Receive() ([]btcutil.Address, error)
|
||||||
// function on the returned instance.
|
// function on the returned instance.
|
||||||
//
|
//
|
||||||
// See GetAddressesByAccount for the blocking version and more details.
|
// See GetAddressesByAccount for the blocking version and more details.
|
||||||
func (c *Client) GetAddressesByAccountAsync(account string) FutureGetAddressesByAccountResult {
|
func (c *Client) GetAddressesByAccountAsync(account *string) FutureGetAddressesByAccountResult {
|
||||||
cmd := btcjson.NewGetAddressesByAccountCmd(account)
|
cmd := btcjson.NewGetAddressesByAccountCmd(account)
|
||||||
result := FutureGetAddressesByAccountResult{
|
result := FutureGetAddressesByAccountResult{
|
||||||
network: c.chainParams,
|
network: c.chainParams,
|
||||||
|
@ -1355,7 +1336,7 @@ func (c *Client) GetAddressesByAccountAsync(account string) FutureGetAddressesBy
|
||||||
|
|
||||||
// GetAddressesByAccount returns the list of addresses associated with the
|
// GetAddressesByAccount returns the list of addresses associated with the
|
||||||
// passed account.
|
// passed account.
|
||||||
func (c *Client) GetAddressesByAccount(account string) ([]btcutil.Address, error) {
|
func (c *Client) GetAddressesByAccount(account *string) ([]btcutil.Address, error) {
|
||||||
return c.GetAddressesByAccountAsync(account).Receive()
|
return c.GetAddressesByAccountAsync(account).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1382,74 +1363,6 @@ func (r FutureMoveResult) Receive() (bool, error) {
|
||||||
return moveResult, nil
|
return moveResult, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// MoveAsync returns an instance of a type that can be used to get the result of
|
|
||||||
// the RPC at some future time by invoking the Receive function on the returned
|
|
||||||
// instance.
|
|
||||||
//
|
|
||||||
// See Move for the blocking version and more details.
|
|
||||||
func (c *Client) MoveAsync(fromAccount, toAccount string, amount btcutil.Amount) FutureMoveResult {
|
|
||||||
cmd := btcjson.NewMoveCmd(fromAccount, toAccount, amount.ToBTC(), nil,
|
|
||||||
nil)
|
|
||||||
return c.SendCmd(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move moves specified amount from one account in your wallet to another. Only
|
|
||||||
// funds with the default number of minimum confirmations will be used.
|
|
||||||
//
|
|
||||||
// See MoveMinConf and MoveComment for different options.
|
|
||||||
func (c *Client) Move(fromAccount, toAccount string, amount btcutil.Amount) (bool, error) {
|
|
||||||
return c.MoveAsync(fromAccount, toAccount, amount).Receive()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveMinConfAsync returns an instance of a type that can be used to get the
|
|
||||||
// result of the RPC at some future time by invoking the Receive function on the
|
|
||||||
// returned instance.
|
|
||||||
//
|
|
||||||
// See MoveMinConf for the blocking version and more details.
|
|
||||||
func (c *Client) MoveMinConfAsync(fromAccount, toAccount string,
|
|
||||||
amount btcutil.Amount, minConfirms int) FutureMoveResult {
|
|
||||||
|
|
||||||
cmd := btcjson.NewMoveCmd(fromAccount, toAccount, amount.ToBTC(),
|
|
||||||
&minConfirms, nil)
|
|
||||||
return c.SendCmd(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveMinConf moves specified amount from one account in your wallet to
|
|
||||||
// another. Only funds with the passed number of minimum confirmations will be
|
|
||||||
// used.
|
|
||||||
//
|
|
||||||
// See Move to use the default number of minimum confirmations and MoveComment
|
|
||||||
// for additional options.
|
|
||||||
func (c *Client) MoveMinConf(fromAccount, toAccount string, amount btcutil.Amount, minConf int) (bool, error) {
|
|
||||||
return c.MoveMinConfAsync(fromAccount, toAccount, amount, minConf).Receive()
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveCommentAsync returns an instance of a type that can be used to get the
|
|
||||||
// result of the RPC at some future time by invoking the Receive function on the
|
|
||||||
// returned instance.
|
|
||||||
//
|
|
||||||
// See MoveComment for the blocking version and more details.
|
|
||||||
func (c *Client) MoveCommentAsync(fromAccount, toAccount string,
|
|
||||||
amount btcutil.Amount, minConfirms int, comment string) FutureMoveResult {
|
|
||||||
|
|
||||||
cmd := btcjson.NewMoveCmd(fromAccount, toAccount, amount.ToBTC(),
|
|
||||||
&minConfirms, &comment)
|
|
||||||
return c.SendCmd(cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveComment moves specified amount from one account in your wallet to
|
|
||||||
// another and stores the provided comment in the wallet. The comment
|
|
||||||
// parameter is only available in the wallet. Only funds with the passed number
|
|
||||||
// of minimum confirmations will be used.
|
|
||||||
//
|
|
||||||
// See Move and MoveMinConf to use defaults.
|
|
||||||
func (c *Client) MoveComment(fromAccount, toAccount string, amount btcutil.Amount,
|
|
||||||
minConf int, comment string) (bool, error) {
|
|
||||||
|
|
||||||
return c.MoveCommentAsync(fromAccount, toAccount, amount, minConf,
|
|
||||||
comment).Receive()
|
|
||||||
}
|
|
||||||
|
|
||||||
// FutureRenameAccountResult is a future promise to deliver the result of a
|
// FutureRenameAccountResult is a future promise to deliver the result of a
|
||||||
// RenameAccountAsync RPC invocation (or an applicable error).
|
// RenameAccountAsync RPC invocation (or an applicable error).
|
||||||
type FutureRenameAccountResult chan *Response
|
type FutureRenameAccountResult chan *Response
|
||||||
|
@ -1804,7 +1717,7 @@ func (r FutureGetReceivedByAccountResult) Receive() (btcutil.Amount, error) {
|
||||||
// function on the returned instance.
|
// function on the returned instance.
|
||||||
//
|
//
|
||||||
// See GetReceivedByAccount for the blocking version and more details.
|
// See GetReceivedByAccount for the blocking version and more details.
|
||||||
func (c *Client) GetReceivedByAccountAsync(account string) FutureGetReceivedByAccountResult {
|
func (c *Client) GetReceivedByAccountAsync(account *string) FutureGetReceivedByAccountResult {
|
||||||
cmd := btcjson.NewGetReceivedByAccountCmd(account, nil)
|
cmd := btcjson.NewGetReceivedByAccountCmd(account, nil)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
@ -1814,7 +1727,7 @@ func (c *Client) GetReceivedByAccountAsync(account string) FutureGetReceivedByAc
|
||||||
//
|
//
|
||||||
// See GetReceivedByAccountMinConf to override the minimum number of
|
// See GetReceivedByAccountMinConf to override the minimum number of
|
||||||
// confirmations.
|
// confirmations.
|
||||||
func (c *Client) GetReceivedByAccount(account string) (btcutil.Amount, error) {
|
func (c *Client) GetReceivedByAccount(account *string) (btcutil.Amount, error) {
|
||||||
return c.GetReceivedByAccountAsync(account).Receive()
|
return c.GetReceivedByAccountAsync(account).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1823,8 +1736,8 @@ func (c *Client) GetReceivedByAccount(account string) (btcutil.Amount, error) {
|
||||||
// function on the returned instance.
|
// function on the returned instance.
|
||||||
//
|
//
|
||||||
// See GetReceivedByAccountMinConf for the blocking version and more details.
|
// See GetReceivedByAccountMinConf for the blocking version and more details.
|
||||||
func (c *Client) GetReceivedByAccountMinConfAsync(account string, minConfirms int) FutureGetReceivedByAccountResult {
|
func (c *Client) GetReceivedByAccountMinConfAsync(account *string, minConfirms *int) FutureGetReceivedByAccountResult {
|
||||||
cmd := btcjson.NewGetReceivedByAccountCmd(account, &minConfirms)
|
cmd := btcjson.NewGetReceivedByAccountCmd(account, minConfirms)
|
||||||
return c.SendCmd(cmd)
|
return c.SendCmd(cmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1833,7 +1746,7 @@ func (c *Client) GetReceivedByAccountMinConfAsync(account string, minConfirms in
|
||||||
// confirmations.
|
// confirmations.
|
||||||
//
|
//
|
||||||
// See GetReceivedByAccount to use the default minimum number of confirmations.
|
// See GetReceivedByAccount to use the default minimum number of confirmations.
|
||||||
func (c *Client) GetReceivedByAccountMinConf(account string, minConfirms int) (btcutil.Amount, error) {
|
func (c *Client) GetReceivedByAccountMinConf(account *string, minConfirms *int) (btcutil.Amount, error) {
|
||||||
return c.GetReceivedByAccountMinConfAsync(account, minConfirms).Receive()
|
return c.GetReceivedByAccountMinConfAsync(account, minConfirms).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2122,6 +2035,44 @@ func (c *Client) ListReceivedByAddressIncludeEmpty(minConfirms int, includeEmpty
|
||||||
includeEmpty).Receive()
|
includeEmpty).Receive()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FutureRescanBlockchainResult is a future promise to deliver the error result of a
|
||||||
|
// RescanBlockchainAsync RPC invocation.
|
||||||
|
type FutureRescanBlockchainResult chan *Response
|
||||||
|
|
||||||
|
// Receive waits for the Response promised by the future and returns the result
|
||||||
|
// of locking or unlocking the unspent output(s).
|
||||||
|
func (r FutureRescanBlockchainResult) Receive() (*btcjson.RescanBlockchainResult, error) {
|
||||||
|
res, err := ReceiveFuture(r)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal as an array of listreceivedbyaddress result objects.
|
||||||
|
var received btcjson.RescanBlockchainResult
|
||||||
|
err = json.Unmarshal(res, &received)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &received, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RescanBlockchainAsync returns an instance of a type that can be used to get the
|
||||||
|
// result of the RPC at some future time by invoking the Receive function on the
|
||||||
|
// returned instance.
|
||||||
|
//
|
||||||
|
// See RescanBlockchain for the blocking version and more details.
|
||||||
|
func (c *Client) RescanBlockchainAsync(startHeight *int32, stopHeight *int32) FutureRescanBlockchainResult {
|
||||||
|
cmd := btcjson.NewRescanBlockchainCmd(startHeight, stopHeight)
|
||||||
|
return c.SendCmd(cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RescanBlockchain rescans the local blockchain for wallet related
|
||||||
|
// transactions from the startHeight to the the inclusive stopHeight.
|
||||||
|
func (c *Client) RescanBlockchain(startHeight *int32, stopHeight *int32) (*btcjson.RescanBlockchainResult, error) {
|
||||||
|
return c.RescanBlockchainAsync(startHeight, stopHeight).Receive()
|
||||||
|
}
|
||||||
|
|
||||||
// ************************
|
// ************************
|
||||||
// Wallet Locking Functions
|
// Wallet Locking Functions
|
||||||
// ************************
|
// ************************
|
||||||
|
|
670
rpcserver.go
670
rpcserver.go
|
@ -21,6 +21,7 @@ import (
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
@ -134,6 +135,7 @@ type commandHandler func(*rpcServer, interface{}, <-chan struct{}) (interface{},
|
||||||
var rpcHandlers map[string]commandHandler
|
var rpcHandlers map[string]commandHandler
|
||||||
var rpcHandlersBeforeInit = map[string]commandHandler{
|
var rpcHandlersBeforeInit = map[string]commandHandler{
|
||||||
"addnode": handleAddNode,
|
"addnode": handleAddNode,
|
||||||
|
"clearbanned": handleClearBanned,
|
||||||
"createrawtransaction": handleCreateRawTransaction,
|
"createrawtransaction": handleCreateRawTransaction,
|
||||||
"debuglevel": handleDebugLevel,
|
"debuglevel": handleDebugLevel,
|
||||||
"decoderawtransaction": handleDecodeRawTransaction,
|
"decoderawtransaction": handleDecodeRawTransaction,
|
||||||
|
@ -150,10 +152,11 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
|
||||||
"getblockcount": handleGetBlockCount,
|
"getblockcount": handleGetBlockCount,
|
||||||
"getblockhash": handleGetBlockHash,
|
"getblockhash": handleGetBlockHash,
|
||||||
"getblockheader": handleGetBlockHeader,
|
"getblockheader": handleGetBlockHeader,
|
||||||
"getchaintips": handleGetChainTips,
|
"getblockstats": handleGetBlockStats,
|
||||||
"getblocktemplate": handleGetBlockTemplate,
|
"getblocktemplate": handleGetBlockTemplate,
|
||||||
"getcfilter": handleGetCFilter,
|
"getcfilter": handleGetCFilter,
|
||||||
"getcfilterheader": handleGetCFilterHeader,
|
"getcfilterheader": handleGetCFilterHeader,
|
||||||
|
"getchaintips": handleGetChainTips,
|
||||||
"getconnectioncount": handleGetConnectionCount,
|
"getconnectioncount": handleGetConnectionCount,
|
||||||
"getcurrentnet": handleGetCurrentNet,
|
"getcurrentnet": handleGetCurrentNet,
|
||||||
"getdifficulty": handleGetDifficulty,
|
"getdifficulty": handleGetDifficulty,
|
||||||
|
@ -161,8 +164,8 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
|
||||||
"gethashespersec": handleGetHashesPerSec,
|
"gethashespersec": handleGetHashesPerSec,
|
||||||
"getheaders": handleGetHeaders,
|
"getheaders": handleGetHeaders,
|
||||||
"getinfo": handleGetInfo,
|
"getinfo": handleGetInfo,
|
||||||
"getmempoolinfo": handleGetMempoolInfo,
|
|
||||||
"getmempoolentry": handleGetMempoolEntry,
|
"getmempoolentry": handleGetMempoolEntry,
|
||||||
|
"getmempoolinfo": handleGetMempoolInfo,
|
||||||
"getmininginfo": handleGetMiningInfo,
|
"getmininginfo": handleGetMiningInfo,
|
||||||
"getnettotals": handleGetNetTotals,
|
"getnettotals": handleGetNetTotals,
|
||||||
"getnetworkhashps": handleGetNetworkHashPS,
|
"getnetworkhashps": handleGetNetworkHashPS,
|
||||||
|
@ -174,11 +177,13 @@ var rpcHandlersBeforeInit = map[string]commandHandler{
|
||||||
"gettxout": handleGetTxOut,
|
"gettxout": handleGetTxOut,
|
||||||
"help": handleHelp,
|
"help": handleHelp,
|
||||||
"invalidateblock": handleInvalidateBlock,
|
"invalidateblock": handleInvalidateBlock,
|
||||||
|
"listbanned": handleListBanned,
|
||||||
"node": handleNode,
|
"node": handleNode,
|
||||||
"ping": handlePing,
|
"ping": handlePing,
|
||||||
"reconsiderblock": handleReconsiderBlock,
|
"reconsiderblock": handleReconsiderBlock,
|
||||||
"searchrawtransactions": handleSearchRawTransactions,
|
"searchrawtransactions": handleSearchRawTransactions,
|
||||||
"sendrawtransaction": handleSendRawTransaction,
|
"sendrawtransaction": handleSendRawTransaction,
|
||||||
|
"setban": handleSetBan,
|
||||||
"setgenerate": handleSetGenerate,
|
"setgenerate": handleSetGenerate,
|
||||||
"signmessagewithprivkey": handleSignMessageWithPrivKey,
|
"signmessagewithprivkey": handleSignMessageWithPrivKey,
|
||||||
"stop": handleStop,
|
"stop": handleStop,
|
||||||
|
@ -225,11 +230,10 @@ var rpcAskWallet = map[string]struct{}{
|
||||||
"listtransactions": {},
|
"listtransactions": {},
|
||||||
"listunspent": {},
|
"listunspent": {},
|
||||||
"lockunspent": {},
|
"lockunspent": {},
|
||||||
"move": {},
|
"rescanblockchain": {},
|
||||||
"sendfrom": {},
|
"sendfrom": {},
|
||||||
"sendmany": {},
|
"sendmany": {},
|
||||||
"sendtoaddress": {},
|
"sendtoaddress": {},
|
||||||
"setaccount": {},
|
|
||||||
"settxfee": {},
|
"settxfee": {},
|
||||||
"signmessage": {},
|
"signmessage": {},
|
||||||
"signrawtransaction": {},
|
"signrawtransaction": {},
|
||||||
|
@ -325,6 +329,15 @@ func rpcDecodeHexError(gotHex string) *btcjson.RPCError {
|
||||||
gotHex))
|
gotHex))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// rpcInvalidAddressOrKey is a convenience function for returning a nicely
|
||||||
|
// formatted RPC error which indicates the address or key is invalid.
|
||||||
|
func rpcInvalidAddressOrKeyError(addr string, msg string) *btcjson.RPCError {
|
||||||
|
return &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
||||||
|
Message: msg,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
|
// rpcNoTxInfoError is a convenience function for returning a nicely formatted
|
||||||
// RPC error which indicates there is no information available for the provided
|
// RPC error which indicates there is no information available for the provided
|
||||||
// transaction hash.
|
// transaction hash.
|
||||||
|
@ -400,6 +413,21 @@ func handleAddNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in
|
||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleClearBanned handles clearbanned commands.
|
||||||
|
func handleClearBanned(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
|
|
||||||
|
err := s.cfg.ConnMgr.ClearBanned()
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParameter,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no data returned unless an error.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleNode handles node commands.
|
// handleNode handles node commands.
|
||||||
func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
func handleNode(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
c := cmd.(*btcjson.NodeCmd)
|
c := cmd.(*btcjson.NodeCmd)
|
||||||
|
@ -551,59 +579,92 @@ func handleCreateRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan
|
||||||
// Add all transaction outputs to the transaction after performing
|
// Add all transaction outputs to the transaction after performing
|
||||||
// some validity checks.
|
// some validity checks.
|
||||||
params := s.cfg.ChainParams
|
params := s.cfg.ChainParams
|
||||||
for encodedAddr, amount := range c.Amounts {
|
|
||||||
// Ensure amount is in the valid range for monetary amounts.
|
// Ensure amount is in the valid range for monetary amounts.
|
||||||
if amount <= 0 || amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
|
// Decode the provided address.
|
||||||
|
// Ensure the address is one of the supported types and that
|
||||||
|
// the network encoded with the address matches the network the
|
||||||
|
// server is currently on.
|
||||||
|
// Create a new script which pays to the provided address.
|
||||||
|
// Convert the amount to satoshi.
|
||||||
|
handleAmountFn := func(amount float64, encodedAddr string) (*wire.TxOut,
|
||||||
|
error) {
|
||||||
|
|
||||||
|
if amount <= 0 ||
|
||||||
|
amount*btcutil.SatoshiPerBitcoin > btcutil.MaxSatoshi {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, &btcjson.RPCError{
|
||||||
Code: btcjson.ErrRPCType,
|
Code: btcjson.ErrRPCType,
|
||||||
Message: "Invalid amount",
|
Message: "invalid amount",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Decode the provided address.
|
|
||||||
addr, err := btcutil.DecodeAddress(encodedAddr, params)
|
addr, err := btcutil.DecodeAddress(encodedAddr, params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, rpcInvalidAddressOrKeyError(encodedAddr,
|
||||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
"invalid address or key")
|
||||||
Message: "Invalid address or key: " + err.Error(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the address is one of the supported types and that
|
|
||||||
// the network encoded with the address matches the network the
|
|
||||||
// server is currently on.
|
|
||||||
switch addr.(type) {
|
switch addr.(type) {
|
||||||
case *btcutil.AddressPubKeyHash:
|
case *btcutil.AddressPubKeyHash:
|
||||||
case *btcutil.AddressScriptHash:
|
case *btcutil.AddressScriptHash:
|
||||||
default:
|
default:
|
||||||
return nil, &btcjson.RPCError{
|
return nil, rpcInvalidAddressOrKeyError(addr.String(),
|
||||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
"invalid address or key")
|
||||||
Message: "Invalid address or key: " + addr.String(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if !addr.IsForNet(params) {
|
if !addr.IsForNet(params) {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, rpcInvalidAddressOrKeyError(addr.String(),
|
||||||
Code: btcjson.ErrRPCInvalidAddressOrKey,
|
"wrong network")
|
||||||
Message: "Invalid address: " + encodedAddr +
|
|
||||||
" is for the wrong network",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new script which pays to the provided address.
|
|
||||||
pkScript, err := txscript.PayToAddrScript(addr)
|
pkScript, err := txscript.PayToAddrScript(addr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context := "Failed to generate pay-to-address script"
|
context := "failed to generate pay-to-address script"
|
||||||
return nil, internalRPCError(err.Error(), context)
|
return nil, internalRPCError(err.Error(), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert the amount to satoshi.
|
|
||||||
satoshi, err := btcutil.NewAmount(amount)
|
satoshi, err := btcutil.NewAmount(amount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
context := "Failed to convert amount"
|
context := "failed to convert amount"
|
||||||
return nil, internalRPCError(err.Error(), context)
|
return nil, internalRPCError(err.Error(), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
txOut := wire.NewTxOut(int64(satoshi), pkScript)
|
return wire.NewTxOut(int64(satoshi), pkScript), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
handleDataFn := func(key string, value string) (*wire.TxOut, error) {
|
||||||
|
if key != "data" {
|
||||||
|
context := "output key must be an address or \"data\""
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParameter,
|
||||||
|
Message: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var data []byte
|
||||||
|
data, err := hex.DecodeString(value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rpcDecodeHexError(value)
|
||||||
|
}
|
||||||
|
return wire.NewTxOut(0, data), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for key, value := range c.Outputs {
|
||||||
|
var err error
|
||||||
|
var txOut *wire.TxOut
|
||||||
|
switch value := value.(type) {
|
||||||
|
case float64:
|
||||||
|
txOut, err = handleAmountFn(value, key)
|
||||||
|
case string:
|
||||||
|
txOut, err = handleDataFn(key, value)
|
||||||
|
default:
|
||||||
|
context := "output value must be a string or float"
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCType,
|
||||||
|
Message: context,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
mtx.AddTxOut(txOut)
|
mtx.AddTxOut(txOut)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1194,7 +1255,7 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, &btcjson.RPCError{
|
return nil, &btcjson.RPCError{
|
||||||
Code: btcjson.ErrRPCBlockNotFound,
|
Code: btcjson.ErrRPCBlockNotFound,
|
||||||
Message: "Block not found",
|
Message: "Block not found: " + err.Error(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// If verbosity is 0, return the serialized block as a hex encoded string.
|
// If verbosity is 0, return the serialized block as a hex encoded string.
|
||||||
|
@ -1211,31 +1272,23 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
|
||||||
return nil, internalRPCError(err.Error(), context)
|
return nil, internalRPCError(err.Error(), context)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the block height from chain.
|
|
||||||
blockHeight, err := s.cfg.Chain.BlockHeightByHash(hash)
|
|
||||||
if err != nil {
|
|
||||||
context := "Failed to obtain block height"
|
|
||||||
return nil, internalRPCError(err.Error(), context)
|
|
||||||
}
|
|
||||||
blk.SetHeight(blockHeight)
|
|
||||||
best := s.cfg.Chain.BestSnapshot()
|
|
||||||
|
|
||||||
// Get next block hash unless there are none.
|
|
||||||
var nextHashString string
|
|
||||||
if blockHeight < best.Height {
|
|
||||||
nextHash, err := s.cfg.Chain.BlockHashByHeight(blockHeight + 1)
|
|
||||||
if err != nil {
|
|
||||||
context := "No next block"
|
|
||||||
return nil, internalRPCError(err.Error(), context)
|
|
||||||
}
|
|
||||||
nextHashString = nextHash.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
params := s.cfg.ChainParams
|
params := s.cfg.ChainParams
|
||||||
blockHeader := &blk.MsgBlock().Header
|
blockHeader := &blk.MsgBlock().Header
|
||||||
|
|
||||||
|
// Get further details (height, confirmations, nexthash, mediantime, etc.) from chain.
|
||||||
|
attrs, best, err := s.cfg.Chain.BlockAttributesByHash(hash, &blockHeader.PrevBlock)
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to obtain block details"
|
||||||
|
return nil, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
|
||||||
var prevHashString string
|
var prevHashString string
|
||||||
if blockHeight > 0 {
|
if attrs.PrevHash != nil {
|
||||||
prevHashString = blockHeader.PrevBlock.String()
|
prevHashString = attrs.PrevHash.String()
|
||||||
|
}
|
||||||
|
var nextHashString string
|
||||||
|
if attrs.NextHash != nil {
|
||||||
|
nextHashString = attrs.NextHash.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
base := btcjson.GetBlockVerboseResultBase{
|
base := btcjson.GetBlockVerboseResultBase{
|
||||||
|
@ -1246,13 +1299,15 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
|
||||||
PreviousHash: prevHashString,
|
PreviousHash: prevHashString,
|
||||||
Nonce: blockHeader.Nonce,
|
Nonce: blockHeader.Nonce,
|
||||||
Time: blockHeader.Timestamp.Unix(),
|
Time: blockHeader.Timestamp.Unix(),
|
||||||
Confirmations: int64(1 + best.Height - blockHeight),
|
MedianTime: attrs.MedianTime.Unix(),
|
||||||
Height: int64(blockHeight),
|
Confirmations: int64(attrs.Confirmations),
|
||||||
|
Height: int64(attrs.Height),
|
||||||
Size: int32(len(blkBytes)),
|
Size: int32(len(blkBytes)),
|
||||||
StrippedSize: int32(blk.MsgBlock().SerializeSizeStripped()),
|
StrippedSize: int32(blk.MsgBlock().SerializeSizeStripped()),
|
||||||
Weight: int32(blockchain.GetBlockWeight(blk)),
|
Weight: int32(blockchain.GetBlockWeight(blk)),
|
||||||
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
Bits: strconv.FormatInt(int64(blockHeader.Bits), 16),
|
||||||
Difficulty: getDifficultyRatio(blockHeader.Bits, params),
|
Difficulty: getDifficultyRatio(blockHeader.Bits, params),
|
||||||
|
ChainWork: attrs.ChainWork.Text(16),
|
||||||
NextHash: nextHashString,
|
NextHash: nextHashString,
|
||||||
ClaimTrie: blockHeader.ClaimTrie.String(),
|
ClaimTrie: blockHeader.ClaimTrie.String(),
|
||||||
}
|
}
|
||||||
|
@ -1277,7 +1332,7 @@ func handleGetBlock(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (i
|
||||||
for i, tx := range txns {
|
for i, tx := range txns {
|
||||||
rawTxn, err := createTxRawResult(params, tx.MsgTx(),
|
rawTxn, err := createTxRawResult(params, tx.MsgTx(),
|
||||||
tx.Hash().String(), blockHeader, hash.String(),
|
tx.Hash().String(), blockHeader, hash.String(),
|
||||||
blockHeight, best.Height)
|
attrs.Height, best.Height)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -1525,6 +1580,405 @@ func handleGetChainTips(s *rpcServer, cmd interface{}, closeChan <-chan struct{}
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleGetBlockStats implements the getblockstats command.
|
||||||
|
func handleGetBlockStats(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
|
c := cmd.(*btcjson.GetBlockStatsCmd)
|
||||||
|
|
||||||
|
// Check whether a block height or hash was provided.
|
||||||
|
blockHeight, ok := c.HashOrHeight.Value.(int)
|
||||||
|
var hash *chainhash.Hash
|
||||||
|
var err error
|
||||||
|
if ok {
|
||||||
|
// Block height was provided.
|
||||||
|
hash, err = s.cfg.Chain.BlockHashByHeight(int32(blockHeight))
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCOutOfRange,
|
||||||
|
Message: "Block number out of range",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Block hash was provided.
|
||||||
|
hashString := c.HashOrHeight.Value.(string)
|
||||||
|
hash, err = chainhash.NewHashFromStr(hashString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, rpcDecodeHexError(hashString)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the block height from chain.
|
||||||
|
blockHeightByHash, err := s.cfg.Chain.BlockHeightByHash(hash)
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to obtain block height"
|
||||||
|
return nil, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
blockHeight = int(blockHeightByHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load block bytes from the database.
|
||||||
|
var blkBytes []byte
|
||||||
|
err = s.cfg.DB.View(func(dbTx database.Tx) error {
|
||||||
|
var err error
|
||||||
|
blkBytes, err = dbTx.FetchBlock(hash)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCBlockNotFound,
|
||||||
|
Message: "Block not found",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deserialize the block.
|
||||||
|
blk, err := btcutil.NewBlockFromBytes(blkBytes)
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to deserialize block"
|
||||||
|
return nil, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
|
||||||
|
var selectedStats []string
|
||||||
|
if c.Stats != nil {
|
||||||
|
selectedStats = *c.Stats
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a set of selected stats to facilitate queries.
|
||||||
|
statsSet := make(map[string]bool)
|
||||||
|
for _, value := range selectedStats {
|
||||||
|
statsSet[value] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return all stats if an empty array was provided.
|
||||||
|
allStats := len(selectedStats) == 0
|
||||||
|
calcFees := statsSet["avgfee"] || statsSet["avgfeerate"] || statsSet["maxfee"] || statsSet["maxfeerate"] ||
|
||||||
|
statsSet["medianfee"] || statsSet["totalfee"] || statsSet["feerate_percentiles"]
|
||||||
|
|
||||||
|
if calcFees && s.cfg.TxIndex == nil {
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCNoTxInfo,
|
||||||
|
Message: "The transaction index must be " +
|
||||||
|
"enabled to obtain fee statistics " +
|
||||||
|
"(specify --txindex)",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
txs := blk.Transactions()
|
||||||
|
txCount := len(txs)
|
||||||
|
var inputCount, outputCount int
|
||||||
|
var totalOutputValue int64
|
||||||
|
|
||||||
|
// Create a map of transaction statistics.
|
||||||
|
txStats := make([]map[string]interface{}, txCount)
|
||||||
|
for i, tx := range txs {
|
||||||
|
size := tx.MsgTx().SerializeSize()
|
||||||
|
witnessSize := size - tx.MsgTx().SerializeSizeStripped()
|
||||||
|
weight := int64(tx.MsgTx().SerializeSizeStripped()*4 + witnessSize)
|
||||||
|
|
||||||
|
var fee, feeRate int64
|
||||||
|
if (calcFees || allStats) && s.cfg.TxIndex != nil && !blockchain.IsCoinBaseTx(tx.MsgTx()) {
|
||||||
|
fee, err = calculateFee(tx, s.cfg.TxIndex, s.cfg.DB)
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to calculate fees"
|
||||||
|
return nil, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
if weight != 0 {
|
||||||
|
feeRate = fee * 4 / weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
segwit := tx.HasWitness()
|
||||||
|
txStats[i] = map[string]interface{}{"tx": tx, "fee": fee, "size": int64(size),
|
||||||
|
"feeRate": feeRate, "weight": weight, "segwit": segwit}
|
||||||
|
inputCount += len(tx.MsgTx().TxIn)
|
||||||
|
outputCount += len(tx.MsgTx().TxOut)
|
||||||
|
|
||||||
|
// Coinbase is excluded from the total output.
|
||||||
|
if !blockchain.IsCoinBase(tx) {
|
||||||
|
for _, txOut := range tx.MsgTx().TxOut {
|
||||||
|
totalOutputValue += txOut.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var totalFees, minFee, maxFee, minFeeRate, maxFeeRate, segwitCount,
|
||||||
|
segwitWeight, totalWeight, totalSize, minSize, maxSize, segwitSize int64
|
||||||
|
if txCount > 1 {
|
||||||
|
minFee = txStats[1]["fee"].(int64)
|
||||||
|
minFeeRate = txStats[1]["feeRate"].(int64)
|
||||||
|
}
|
||||||
|
for i := 0; i < len(txStats); i++ {
|
||||||
|
var fee, feeRate int64
|
||||||
|
tx := txStats[i]["tx"].(*btcutil.Tx)
|
||||||
|
if !blockchain.IsCoinBaseTx(tx.MsgTx()) {
|
||||||
|
// Fee statistics.
|
||||||
|
fee = txStats[i]["fee"].(int64)
|
||||||
|
feeRate = txStats[i]["feeRate"].(int64)
|
||||||
|
if minFee > fee {
|
||||||
|
minFee = fee
|
||||||
|
}
|
||||||
|
if maxFee < fee {
|
||||||
|
maxFee = fee
|
||||||
|
}
|
||||||
|
if minFeeRate > feeRate {
|
||||||
|
minFeeRate = feeRate
|
||||||
|
}
|
||||||
|
if maxFeeRate < feeRate {
|
||||||
|
maxFeeRate = feeRate
|
||||||
|
}
|
||||||
|
totalFees += txStats[i]["fee"].(int64)
|
||||||
|
|
||||||
|
// Segwit statistics.
|
||||||
|
if txStats[i]["segwit"].(bool) {
|
||||||
|
segwitCount++
|
||||||
|
segwitSize += txStats[i]["size"].(int64)
|
||||||
|
segwitWeight += txStats[i]["weight"].(int64)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size statistics.
|
||||||
|
size := txStats[i]["size"].(int64)
|
||||||
|
if minSize == 0 {
|
||||||
|
minSize = size
|
||||||
|
}
|
||||||
|
if maxSize < size {
|
||||||
|
maxSize = size
|
||||||
|
} else if minSize > size {
|
||||||
|
minSize = size
|
||||||
|
}
|
||||||
|
totalSize += txStats[i]["size"].(int64)
|
||||||
|
|
||||||
|
totalWeight += txStats[i]["weight"].(int64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var avgFee, avgFeeRate, avgSize int64
|
||||||
|
if txCount > 1 {
|
||||||
|
avgFee = totalFees / int64(txCount-1)
|
||||||
|
}
|
||||||
|
if totalWeight != 0 {
|
||||||
|
avgFeeRate = totalFees * 4 / totalWeight
|
||||||
|
}
|
||||||
|
if txCount > 1 {
|
||||||
|
avgSize = totalSize / int64(txCount-1)
|
||||||
|
}
|
||||||
|
|
||||||
|
subsidy := blockchain.CalcBlockSubsidy(int32(blockHeight), s.cfg.ChainParams)
|
||||||
|
|
||||||
|
medianStat := func(stat string) int64 {
|
||||||
|
size := len(txStats) - 1
|
||||||
|
if size == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
statArray := make([]int64, size)
|
||||||
|
// Start with the second element to ignore entry associated with coinbase.
|
||||||
|
for i, stats := range txStats[1:] {
|
||||||
|
statArray[i] = stats[stat].(int64)
|
||||||
|
}
|
||||||
|
sort.Slice(statArray, func(i, j int) bool {
|
||||||
|
return statArray[i] < statArray[j]
|
||||||
|
})
|
||||||
|
if size%2 == 0 {
|
||||||
|
return (statArray[size/2-1] + statArray[size/2]) / 2
|
||||||
|
}
|
||||||
|
return statArray[size/2]
|
||||||
|
}
|
||||||
|
|
||||||
|
var medianFee int64
|
||||||
|
if totalFees > 0 {
|
||||||
|
medianFee = medianStat("fee")
|
||||||
|
} else {
|
||||||
|
medianFee = 0
|
||||||
|
}
|
||||||
|
medianSize := medianStat("size")
|
||||||
|
|
||||||
|
// Calculate feerate percentiles.
|
||||||
|
var feeratePercentiles []int64
|
||||||
|
if allStats || calcFees {
|
||||||
|
|
||||||
|
// Sort by feerate.
|
||||||
|
sort.Slice(txStats, func(i, j int) bool {
|
||||||
|
return txStats[i]["feeRate"].(int64) < txStats[j]["feeRate"].(int64)
|
||||||
|
})
|
||||||
|
totalWeight := float64(totalWeight)
|
||||||
|
|
||||||
|
// Find 10th, 25th, 50th, 75th and 90th percentile weight units.
|
||||||
|
weights := []float64{
|
||||||
|
totalWeight / 10, totalWeight / 4, totalWeight / 2,
|
||||||
|
(totalWeight * 3) / 4, (totalWeight * 9) / 10}
|
||||||
|
var cumulativeWeight int64
|
||||||
|
feeratePercentiles = make([]int64, len(weights))
|
||||||
|
nextPercentileIndex := 0
|
||||||
|
for i := 0; i < len(txStats); i++ {
|
||||||
|
cumulativeWeight += txStats[i]["weight"].(int64)
|
||||||
|
for nextPercentileIndex < len(weights) && float64(cumulativeWeight) >= weights[nextPercentileIndex] {
|
||||||
|
feeratePercentiles[nextPercentileIndex] = txStats[i]["feeRate"].(int64)
|
||||||
|
nextPercentileIndex++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fill any remaining percentiles with the last value.
|
||||||
|
for i := nextPercentileIndex; i < len(weights); i++ {
|
||||||
|
feeratePercentiles[i] = txStats[len(txStats)-1]["feeRate"].(int64)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var blockHash string
|
||||||
|
if allStats || statsSet["blockhash"] {
|
||||||
|
blockHash = blk.Hash().String()
|
||||||
|
}
|
||||||
|
|
||||||
|
medianTime, err := medianBlockTime(blk.Hash(), s.cfg.Chain)
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to obtain block median time"
|
||||||
|
return nil, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
|
||||||
|
resultMap := map[string]int64{
|
||||||
|
"avgfee": avgFee,
|
||||||
|
"avgfeerate": avgFeeRate,
|
||||||
|
"avgtxsize": avgSize,
|
||||||
|
"height": int64(blockHeight),
|
||||||
|
"ins": int64(inputCount - 1), // Coinbase input is not included.
|
||||||
|
"maxfee": maxFee,
|
||||||
|
"maxfeerate": maxFeeRate,
|
||||||
|
"maxtxsize": maxSize,
|
||||||
|
"medianfee": medianFee,
|
||||||
|
"mediantime": medianTime.Unix(),
|
||||||
|
"mediantxsize": medianSize,
|
||||||
|
"minfee": minFee,
|
||||||
|
"minfeerate": minFeeRate,
|
||||||
|
"mintxsize": minSize,
|
||||||
|
"outs": int64(outputCount),
|
||||||
|
"swtotal_size": segwitSize,
|
||||||
|
"swtotal_weight": segwitWeight,
|
||||||
|
"swtxs": segwitCount,
|
||||||
|
"subsidy": subsidy,
|
||||||
|
"time": blk.MsgBlock().Header.Timestamp.Unix(),
|
||||||
|
"total_out": totalOutputValue,
|
||||||
|
"total_size": totalSize,
|
||||||
|
"total_weight": totalWeight,
|
||||||
|
"totalfee": totalFees,
|
||||||
|
"txs": int64(len(txs)),
|
||||||
|
"utxo_increase": int64(outputCount - (inputCount - 1)),
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function determines whether a statistic goes into the
|
||||||
|
// final result, except for blockhash and feerate_percentiles
|
||||||
|
// which are handled separately.
|
||||||
|
resultFilter := func(stat string) *int64 {
|
||||||
|
if allStats && s.cfg.TxIndex == nil {
|
||||||
|
// There are no fee statistics to send.
|
||||||
|
excludedStats := []string{"avgfee", "avgfeerate", "maxfee", "maxfeerate", "medianfee", "minfee", "minfeerate"}
|
||||||
|
for _, excluded := range excludedStats {
|
||||||
|
if stat == excluded {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allStats || statsSet[stat] {
|
||||||
|
if value, ok := resultMap[stat]; ok {
|
||||||
|
return &value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
result := &btcjson.GetBlockStatsResult{
|
||||||
|
AverageFee: resultFilter("avgfee"),
|
||||||
|
AverageFeeRate: resultFilter("avgfeerate"),
|
||||||
|
AverageTxSize: resultFilter("avgtxsize"),
|
||||||
|
FeeratePercentiles: &feeratePercentiles,
|
||||||
|
Hash: &blockHash,
|
||||||
|
Height: resultFilter("height"),
|
||||||
|
Ins: resultFilter("ins"),
|
||||||
|
MaxFee: resultFilter("maxfee"),
|
||||||
|
MaxFeeRate: resultFilter("maxfeerate"),
|
||||||
|
MaxTxSize: resultFilter("maxtxsize"),
|
||||||
|
MedianFee: resultFilter("medianfee"),
|
||||||
|
MedianTime: resultFilter("mediantime"),
|
||||||
|
MedianTxSize: resultFilter("mediantxsize"),
|
||||||
|
MinFee: resultFilter("minfee"),
|
||||||
|
MinFeeRate: resultFilter("minfeerate"),
|
||||||
|
MinTxSize: resultFilter("mintxsize"),
|
||||||
|
Outs: resultFilter("outs"),
|
||||||
|
SegWitTotalSize: resultFilter("swtotal_size"),
|
||||||
|
SegWitTotalWeight: resultFilter("swtotal_weight"),
|
||||||
|
SegWitTxs: resultFilter("swtxs"),
|
||||||
|
Subsidy: resultFilter("subsidy"),
|
||||||
|
Time: resultFilter("time"),
|
||||||
|
TotalOut: resultFilter("total_out"),
|
||||||
|
TotalSize: resultFilter("total_size"),
|
||||||
|
TotalWeight: resultFilter("total_weight"),
|
||||||
|
TotalFee: resultFilter("totalfee"),
|
||||||
|
Txs: resultFilter("txs"),
|
||||||
|
UTXOIncrease: resultFilter("utxo_increase"),
|
||||||
|
UTXOSizeIncrease: resultFilter("utxo_size_inc"),
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculateFee returns the fee of a transaction.
|
||||||
|
func calculateFee(tx *btcutil.Tx, txIndex *indexers.TxIndex, db database.DB) (int64, error) {
|
||||||
|
var inValue, outValue int64
|
||||||
|
for _, input := range tx.MsgTx().TxIn {
|
||||||
|
prevTxHash := input.PreviousOutPoint.Hash
|
||||||
|
// Look up the location of the previous transaction in the index.
|
||||||
|
blockRegion, err := txIndex.TxBlockRegion(&prevTxHash)
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to retrieve transaction location"
|
||||||
|
return 0, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
if blockRegion == nil {
|
||||||
|
return 0, rpcNoTxInfoError(&prevTxHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the raw transaction bytes from the database.
|
||||||
|
var txBytes []byte
|
||||||
|
err = db.View(func(dbTx database.Tx) error {
|
||||||
|
var err error
|
||||||
|
txBytes, err = dbTx.FetchBlockRegion(blockRegion)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return 0, rpcNoTxInfoError(&prevTxHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
var msgTx wire.MsgTx
|
||||||
|
err = msgTx.Deserialize(bytes.NewReader(txBytes))
|
||||||
|
if err != nil {
|
||||||
|
context := "Failed to deserialize transaction"
|
||||||
|
return 0, internalRPCError(err.Error(), context)
|
||||||
|
}
|
||||||
|
prevOutValue := msgTx.TxOut[input.PreviousOutPoint.Index].Value
|
||||||
|
inValue += prevOutValue
|
||||||
|
}
|
||||||
|
for _, output := range tx.MsgTx().TxOut {
|
||||||
|
outValue += output.Value
|
||||||
|
}
|
||||||
|
fee := inValue - outValue
|
||||||
|
return fee, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// medianBlockTime returns the median time of a block and its 10 previous blocks
|
||||||
|
// as per BIP113.
|
||||||
|
func medianBlockTime(blockHash *chainhash.Hash, chain *blockchain.BlockChain) (*time.Time, error) {
|
||||||
|
blockTimes := make([]time.Time, 0)
|
||||||
|
currentHash := blockHash
|
||||||
|
for i := 0; i < 11; i++ {
|
||||||
|
header, err := chain.HeaderByHash(currentHash)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
blockTimes = append(blockTimes, header.Timestamp)
|
||||||
|
genesisPrevBlock, _ := chainhash.NewHashFromStr("0000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
if header.PrevBlock.IsEqual(genesisPrevBlock) {
|
||||||
|
// This is the genesis block so there's no need to iterate further.
|
||||||
|
break
|
||||||
|
}
|
||||||
|
currentHash = &header.PrevBlock
|
||||||
|
}
|
||||||
|
sort.Slice(blockTimes, func(i, j int) bool {
|
||||||
|
return blockTimes[i].Before(blockTimes[j])
|
||||||
|
})
|
||||||
|
return &blockTimes[len(blockTimes)/2], nil
|
||||||
|
}
|
||||||
|
|
||||||
// encodeTemplateID encodes the passed details into an ID that can be used to
|
// encodeTemplateID encodes the passed details into an ID that can be used to
|
||||||
// uniquely identify a block template.
|
// uniquely identify a block template.
|
||||||
func encodeTemplateID(prevHash *chainhash.Hash, lastGenerated time.Time) string {
|
func encodeTemplateID(prevHash *chainhash.Hash, lastGenerated time.Time) string {
|
||||||
|
@ -1908,11 +2362,12 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
|
||||||
if template.WitnessCommitment != nil {
|
if template.WitnessCommitment != nil {
|
||||||
reply.DefaultWitnessCommitment = hex.EncodeToString(template.WitnessCommitment)
|
reply.DefaultWitnessCommitment = hex.EncodeToString(template.WitnessCommitment)
|
||||||
reply.Rules = append(reply.Rules, "!segwit")
|
reply.Rules = append(reply.Rules, "!segwit")
|
||||||
|
} else {
|
||||||
|
reply.Rules = append(reply.Rules, "segwit")
|
||||||
}
|
}
|
||||||
|
|
||||||
if useCoinbaseValue {
|
if useCoinbaseValue {
|
||||||
reply.CoinbaseAux = gbtCoinbaseAux
|
reply.CoinbaseAux = gbtCoinbaseAux
|
||||||
reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value
|
|
||||||
} else {
|
} else {
|
||||||
// Ensure the template has a valid payment address associated
|
// Ensure the template has a valid payment address associated
|
||||||
// with it when a full coinbase is requested.
|
// with it when a full coinbase is requested.
|
||||||
|
@ -1945,6 +2400,9 @@ func (state *gbtWorkState) blockTemplateResult(useCoinbaseValue bool, submitOld
|
||||||
reply.CoinbaseTxn = &resultTx
|
reply.CoinbaseTxn = &resultTx
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Return coinbasevalue anyway as lbrycrd and bitcoind do.
|
||||||
|
reply.CoinbaseValue = &msgBlock.Transactions[0].TxOut[0].Value
|
||||||
|
|
||||||
return &reply, nil
|
return &reply, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2468,19 +2926,7 @@ func handleGetInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (in
|
||||||
|
|
||||||
// handleGetMempoolInfo implements the getmempoolinfo command.
|
// handleGetMempoolInfo implements the getmempoolinfo command.
|
||||||
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
func handleGetMempoolInfo(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
mempoolTxns := s.cfg.TxMemPool.TxDescs()
|
return s.cfg.TxMemPool.MempoolInfo(), nil
|
||||||
|
|
||||||
var numBytes int64
|
|
||||||
for _, txD := range mempoolTxns {
|
|
||||||
numBytes += int64(txD.Tx.MsgTx().SerializeSize())
|
|
||||||
}
|
|
||||||
|
|
||||||
ret := &btcjson.GetMempoolInfoResult{
|
|
||||||
Size: int64(len(mempoolTxns)),
|
|
||||||
Bytes: numBytes,
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleGetMempoolEntry implements the getmempoolentry command.
|
// handleGetMempoolEntry implements the getmempoolentry command.
|
||||||
|
@ -3164,6 +3610,24 @@ func handleHelp(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (inter
|
||||||
return help, nil
|
return help, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleListBanned handles the listbanned command.
|
||||||
|
func handleListBanned(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
|
banned := s.cfg.ConnMgr.BannedPeers()
|
||||||
|
reply := make([]*btcjson.ListBannedResult, 0, len(banned))
|
||||||
|
for address, period := range banned {
|
||||||
|
since, until := period.since, period.until
|
||||||
|
r := btcjson.ListBannedResult{
|
||||||
|
Address: address,
|
||||||
|
BanCreated: since.Unix(),
|
||||||
|
BannedUntil: until.Unix(),
|
||||||
|
BanDuration: int64(until.Sub(since).Seconds()),
|
||||||
|
TimeRemaining: int64(time.Until(until).Seconds()),
|
||||||
|
}
|
||||||
|
reply = append(reply, &r)
|
||||||
|
}
|
||||||
|
return reply, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handlePing implements the ping command.
|
// handlePing implements the ping command.
|
||||||
func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
func handlePing(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
// Ask server to ping \o_
|
// Ask server to ping \o_
|
||||||
|
@ -3760,12 +4224,66 @@ func handleSendRawTransaction(s *rpcServer, cmd interface{}, closeChan <-chan st
|
||||||
// Keep track of all the sendrawtransaction request txns so that they
|
// Keep track of all the sendrawtransaction request txns so that they
|
||||||
// can be rebroadcast if they don't make their way into a block.
|
// can be rebroadcast if they don't make their way into a block.
|
||||||
txD := acceptedTxs[0]
|
txD := acceptedTxs[0]
|
||||||
|
s.cfg.TxMemPool.AddUnbroadcastTx(txD.Tx.Hash())
|
||||||
iv := wire.NewInvVect(wire.InvTypeTx, txD.Tx.Hash())
|
iv := wire.NewInvVect(wire.InvTypeTx, txD.Tx.Hash())
|
||||||
s.cfg.ConnMgr.AddRebroadcastInventory(iv, txD)
|
s.cfg.ConnMgr.AddRebroadcastInventory(iv, txD)
|
||||||
|
|
||||||
return tx.Hash().String(), nil
|
return tx.Hash().String(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// handleSetBan handles the setban command.
|
||||||
|
func handleSetBan(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
|
c := cmd.(*btcjson.SetBanCmd)
|
||||||
|
|
||||||
|
addr := net.ParseIP(c.Addr)
|
||||||
|
if addr == nil {
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParameter,
|
||||||
|
Message: "invalid addr for setban",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
since := time.Now()
|
||||||
|
until := since.Add(time.Second * time.Duration(*c.BanTime))
|
||||||
|
if *c.BanTime == 0 {
|
||||||
|
until = since.Add(defaultBanDuration)
|
||||||
|
}
|
||||||
|
if *c.Absolute {
|
||||||
|
until = time.Unix(int64(*c.BanTime), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
var err error
|
||||||
|
switch c.SubCmd {
|
||||||
|
case "add":
|
||||||
|
err = s.cfg.ConnMgr.SetBan(addr.String(), since, until)
|
||||||
|
addr := addr.String()
|
||||||
|
peers := s.cfg.ConnMgr.ConnectedPeers()
|
||||||
|
for _, peer := range peers {
|
||||||
|
p := peer.ToPeer()
|
||||||
|
if p.NA().IP.String() == addr {
|
||||||
|
p.Disconnect()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "remove":
|
||||||
|
err = s.cfg.ConnMgr.RemoveBan(addr.String())
|
||||||
|
default:
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParameter,
|
||||||
|
Message: "invalid subcommand for setban",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, &btcjson.RPCError{
|
||||||
|
Code: btcjson.ErrRPCInvalidParameter,
|
||||||
|
Message: err.Error(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// no data returned unless an error.
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
// handleSetGenerate implements the setgenerate command.
|
// handleSetGenerate implements the setgenerate command.
|
||||||
func handleSetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
func handleSetGenerate(s *rpcServer, cmd interface{}, closeChan <-chan struct{}) (interface{}, error) {
|
||||||
c := cmd.(*btcjson.SetGenerateCmd)
|
c := cmd.(*btcjson.SetGenerateCmd)
|
||||||
|
@ -4823,6 +5341,18 @@ type rpcserverConnManager interface {
|
||||||
// ConnectedPeers returns an array consisting of all connected peers.
|
// ConnectedPeers returns an array consisting of all connected peers.
|
||||||
ConnectedPeers() []rpcserverPeer
|
ConnectedPeers() []rpcserverPeer
|
||||||
|
|
||||||
|
// BannedPeers returns an array consisting of all Banned peers.
|
||||||
|
BannedPeers() map[string]bannedPeriod
|
||||||
|
|
||||||
|
// SetBan add the addr to the ban list.
|
||||||
|
SetBan(addr string, since time.Time, until time.Time) error
|
||||||
|
|
||||||
|
// RemoveBan remove the subnet from the ban list.
|
||||||
|
RemoveBan(subnet string) error
|
||||||
|
|
||||||
|
// ClearBanned removes all banned IPs.
|
||||||
|
ClearBanned() error
|
||||||
|
|
||||||
// PersistentPeers returns an array consisting of all the persistent
|
// PersistentPeers returns an array consisting of all the persistent
|
||||||
// peers.
|
// peers.
|
||||||
PersistentPeers() []rpcserverPeer
|
PersistentPeers() []rpcserverPeer
|
||||||
|
|
|
@ -49,13 +49,16 @@ var helpDescsEnUS = map[string]string{
|
||||||
"The transaction inputs are not signed in the created transaction.\n" +
|
"The transaction inputs are not signed in the created transaction.\n" +
|
||||||
"The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.",
|
"The signrawtransaction RPC command provided by wallet must be used to sign the resulting transaction.",
|
||||||
"createrawtransaction-inputs": "The inputs to the transaction",
|
"createrawtransaction-inputs": "The inputs to the transaction",
|
||||||
"createrawtransaction-amounts": "JSON object with the destination addresses as keys and amounts as values",
|
"createrawtransaction-outputs": "JSON object with the destination addresses as keys and amounts as values",
|
||||||
"createrawtransaction-amounts--key": "address",
|
"createrawtransaction-outputs--key": "address or \"data\"",
|
||||||
"createrawtransaction-amounts--value": "n.nnn",
|
"createrawtransaction-outputs--value": "value in BTC as floating point number or hex-encoded data for \"data\"",
|
||||||
"createrawtransaction-amounts--desc": "The destination address as the key and the amount in LBC as the value",
|
"createrawtransaction-outputs--desc": "The destination address as the key and the amount in LBC as the value",
|
||||||
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
|
"createrawtransaction-locktime": "Locktime value; a non-zero value will also locktime-activate the inputs",
|
||||||
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
|
"createrawtransaction--result0": "Hex-encoded bytes of the serialized transaction",
|
||||||
|
|
||||||
|
// ClearBannedCmd help.
|
||||||
|
"clearbanned--synopsis": "Clear all banned IPs.",
|
||||||
|
|
||||||
// ScriptSig help.
|
// ScriptSig help.
|
||||||
"scriptsig-asm": "Disassembly of the script",
|
"scriptsig-asm": "Disassembly of the script",
|
||||||
"scriptsig-hex": "Hex-encoded bytes of the script",
|
"scriptsig-hex": "Hex-encoded bytes of the script",
|
||||||
|
@ -197,6 +200,43 @@ var helpDescsEnUS = map[string]string{
|
||||||
"getblockchaininforesult-softforks": "The status of the super-majority soft-forks",
|
"getblockchaininforesult-softforks": "The status of the super-majority soft-forks",
|
||||||
"getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0",
|
"getblockchaininforesult-unifiedsoftforks": "The status of the super-majority soft-forks used by bitcoind on or after v0.19.0",
|
||||||
|
|
||||||
|
// GetBlockStatsCmd help.
|
||||||
|
"getblockstats--synopsis": "Returns statistics about a block given its hash or height. --txindex must be enabled for fee and feerate statistics.",
|
||||||
|
"getblockstats-hashorheight": "The hash or height of the block",
|
||||||
|
"hashorheight-value": "The hash or height of the block",
|
||||||
|
"getblockstats-stats": "Selected statistics",
|
||||||
|
|
||||||
|
// GetBlockStatsResult help.
|
||||||
|
"getblockstatsresult-avgfee": "The average fee in the block",
|
||||||
|
"getblockstatsresult-avgfeerate": "The average feerate in the block (in satoshis per virtual byte)",
|
||||||
|
"getblockstatsresult-avgtxsize": "The average transaction size in the block",
|
||||||
|
"getblockstatsresult-blockhash": "The block hash",
|
||||||
|
"getblockstatsresult-feerate_percentiles": "Feerates at the 10th, 25th, 50th, 75th, and 90th percentile weight unit (in satoshis per virtual byte)",
|
||||||
|
"getblockstatsresult-height": "The block height",
|
||||||
|
"getblockstatsresult-ins": "The number of inputs (excluding coinbase)",
|
||||||
|
"getblockstatsresult-maxfee": "Maxium fee in the block",
|
||||||
|
"getblockstatsresult-maxfeerate": "Maximum feerate in the block (in satoshis per virtual byte)",
|
||||||
|
"getblockstatsresult-maxtxsize": "Maximum transaction size",
|
||||||
|
"getblockstatsresult-medianfee": "Truncated median fee",
|
||||||
|
"getblockstatsresult-mediantime": "The median time from the block and its previous 10 blocks (BIP113)",
|
||||||
|
"getblockstatsresult-mediantxsize": "Truncated median transaction size",
|
||||||
|
"getblockstatsresult-minfee": "Minimum fee in the block",
|
||||||
|
"getblockstatsresult-minfeerate": "Minimum feerate in the block (in satoshis per virtual byte)",
|
||||||
|
"getblockstatsresult-mintxsize": "Minimum transaction size",
|
||||||
|
"getblockstatsresult-outs": "The number of outputs",
|
||||||
|
"getblockstatsresult-subsidy": "The block subsidy",
|
||||||
|
"getblockstatsresult-swtotal_size": "Total size of all segwit transactions in the block (excluding coinbase)",
|
||||||
|
"getblockstatsresult-swtotal_weight": "Total weight of all segwit transactions in the block (excluding coinbase)",
|
||||||
|
"getblockstatsresult-swtxs": "The number of segwit transactions in the block (excluding coinbase)",
|
||||||
|
"getblockstatsresult-time": "The block time",
|
||||||
|
"getblockstatsresult-total_out": "Total amount in all outputs (excluding coinbase)",
|
||||||
|
"getblockstatsresult-total_size": "Total size of all transactions (excluding coinbase)",
|
||||||
|
"getblockstatsresult-total_weight": "Total weight of all transactions (excluding coinbase)",
|
||||||
|
"getblockstatsresult-totalfee": "The total of fees",
|
||||||
|
"getblockstatsresult-txs": "The number of transactions (excluding coinbase)",
|
||||||
|
"getblockstatsresult-utxo_increase": "The increase/decrease in the number of unspent outputs",
|
||||||
|
"getblockstatsresult-utxo_size_inc": "The increase/decrease in size for the utxo index",
|
||||||
|
|
||||||
// SoftForkDescription help.
|
// SoftForkDescription help.
|
||||||
"softforkdescription-reject": "The current activation status of the softfork",
|
"softforkdescription-reject": "The current activation status of the softfork",
|
||||||
"softforkdescription-version": "The block version that signals enforcement of this softfork",
|
"softforkdescription-version": "The block version that signals enforcement of this softfork",
|
||||||
|
@ -260,9 +300,11 @@ var helpDescsEnUS = map[string]string{
|
||||||
"getblockverboseresult-tx": "The transaction hashes (only when verbosity=1)",
|
"getblockverboseresult-tx": "The transaction hashes (only when verbosity=1)",
|
||||||
"getblockverboseresult-nTx": "The number of transactions (aka, count of TX)",
|
"getblockverboseresult-nTx": "The number of transactions (aka, count of TX)",
|
||||||
"getblockverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT",
|
"getblockverboseresult-time": "The block time in seconds since 1 Jan 1970 GMT",
|
||||||
|
"getblockverboseresult-mediantime": "The median block time in seconds since 1 Jan 1970 GMT",
|
||||||
"getblockverboseresult-nonce": "The block nonce",
|
"getblockverboseresult-nonce": "The block nonce",
|
||||||
"getblockverboseresult-bits": "The bits which represent the block difficulty",
|
"getblockverboseresult-bits": "The bits which represent the block difficulty",
|
||||||
"getblockverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty",
|
"getblockverboseresult-difficulty": "The proof-of-work difficulty as a multiple of the minimum difficulty",
|
||||||
|
"getblockverboseresult-chainwork": "Expected number of hashes required to produce the chain up to this block (in hex)",
|
||||||
"getblockverboseresult-previousblockhash": "The hash of the previous block",
|
"getblockverboseresult-previousblockhash": "The hash of the previous block",
|
||||||
"getblockverboseresult-nextblockhash": "The hash of the next block (only if there is one)",
|
"getblockverboseresult-nextblockhash": "The hash of the next block (only if there is one)",
|
||||||
"getblockverboseresult-strippedsize": "The size of the block without witness data",
|
"getblockverboseresult-strippedsize": "The size of the block without witness data",
|
||||||
|
@ -453,8 +495,13 @@ var helpDescsEnUS = map[string]string{
|
||||||
"getmempoolinfo--synopsis": "Returns memory pool information",
|
"getmempoolinfo--synopsis": "Returns memory pool information",
|
||||||
|
|
||||||
// GetMempoolInfoResult help.
|
// GetMempoolInfoResult help.
|
||||||
"getmempoolinforesult-bytes": "Size in bytes of the mempool",
|
"getmempoolinforesult-bytes": "Size in bytes of the mempool",
|
||||||
"getmempoolinforesult-size": "Number of transactions in the mempool",
|
"getmempoolinforesult-size": "Number of transactions in the mempool",
|
||||||
|
"getmempoolinforesult-usage": "Total memory usage for the mempool",
|
||||||
|
"getmempoolinforesult-total_fee": "Total fees for the mempool in LBC, ignoring modified fees through prioritizetransaction",
|
||||||
|
"getmempoolinforesult-mempoolminfee": "Minimum fee rate in LBC/kvB for tx to be accepted. Is the maximum of minrelaytxfee and minimum mempool fee",
|
||||||
|
"getmempoolinforesult-minrelaytxfee": "Current minimum relay fee for transactions",
|
||||||
|
"getmempoolinforesult-unbroadcastcount": "Current number of transactions that haven't passed initial broadcast yet",
|
||||||
|
|
||||||
// GetMiningInfoResult help.
|
// GetMiningInfoResult help.
|
||||||
"getmininginforesult-blocks": "Height of the latest best block",
|
"getmininginforesult-blocks": "Height of the latest best block",
|
||||||
|
@ -629,6 +676,16 @@ var helpDescsEnUS = map[string]string{
|
||||||
"ping--synopsis": "Queues a ping to be sent to each connected peer.\n" +
|
"ping--synopsis": "Queues a ping to be sent to each connected peer.\n" +
|
||||||
"Ping times are provided by getpeerinfo via the pingtime and pingwait fields.",
|
"Ping times are provided by getpeerinfo via the pingtime and pingwait fields.",
|
||||||
|
|
||||||
|
// ListBannedCmd help.
|
||||||
|
"listbanned--synopsis": "List all banned IPs.",
|
||||||
|
|
||||||
|
// ListBannedResult help.
|
||||||
|
"listbannedresult-address": "The IP of the banned node.",
|
||||||
|
"listbannedresult-ban_created": "The UNIX epoch time the ban was created.",
|
||||||
|
"listbannedresult-banned_until": "The UNIX epoch time the ban expires.",
|
||||||
|
"listbannedresult-ban_duration": "The duration of the ban, in seconds.",
|
||||||
|
"listbannedresult-time_remaining": "The time remaining on the ban, in seconds",
|
||||||
|
|
||||||
// ReconsiderBlockCmd
|
// ReconsiderBlockCmd
|
||||||
"reconsiderblock--synopsis": "Reconsider a block for validation.",
|
"reconsiderblock--synopsis": "Reconsider a block for validation.",
|
||||||
"reconsiderblock-blockhash": "Hash of the block you want to reconsider",
|
"reconsiderblock-blockhash": "Hash of the block you want to reconsider",
|
||||||
|
@ -657,6 +714,13 @@ var helpDescsEnUS = map[string]string{
|
||||||
"sendrawtransaction--result0": "The hash of the transaction",
|
"sendrawtransaction--result0": "The hash of the transaction",
|
||||||
"allowhighfeesormaxfeerate-value": "Either the boolean value for the allowhighfees parameter in bitcoind < v0.19.0 or the numerical value for the maxfeerate field in bitcoind v0.19.0 and later",
|
"allowhighfeesormaxfeerate-value": "Either the boolean value for the allowhighfees parameter in bitcoind < v0.19.0 or the numerical value for the maxfeerate field in bitcoind v0.19.0 and later",
|
||||||
|
|
||||||
|
// SetBanCmd help.
|
||||||
|
"setban--synopsis": "Add or remove an IP from the banned list. (Currently, subnet is not supported.)",
|
||||||
|
"setban-addr": "The IP to ban. (Currently, subnet is not supported.)",
|
||||||
|
"setban-subcmd": "'add' to add an IP to the list, 'remove' to remove an IP from the list",
|
||||||
|
"setban-bantime": "Time in seconds the IP is banned (0 or empty means using the default time of 24h which can also be overwritten by the -bantime startup argument)",
|
||||||
|
"setban-absolute": "If set, the bantime must be an absolute timestamp expressed in UNIX epoch time; default to false.",
|
||||||
|
|
||||||
// SetGenerateCmd help.
|
// SetGenerateCmd help.
|
||||||
"setgenerate--synopsis": "Set the server to generate coins (mine) or not.",
|
"setgenerate--synopsis": "Set the server to generate coins (mine) or not.",
|
||||||
"setgenerate-generate": "Use true to enable generation, false to disable it",
|
"setgenerate-generate": "Use true to enable generation, false to disable it",
|
||||||
|
@ -873,6 +937,7 @@ var helpDescsEnUS = map[string]string{
|
||||||
// pointer to the type (or nil to indicate no return value).
|
// pointer to the type (or nil to indicate no return value).
|
||||||
var rpcResultTypes = map[string][]interface{}{
|
var rpcResultTypes = map[string][]interface{}{
|
||||||
"addnode": nil,
|
"addnode": nil,
|
||||||
|
"clearbanned": nil,
|
||||||
"createrawtransaction": {(*string)(nil)},
|
"createrawtransaction": {(*string)(nil)},
|
||||||
"debuglevel": {(*string)(nil), (*string)(nil)},
|
"debuglevel": {(*string)(nil), (*string)(nil)},
|
||||||
"decoderawtransaction": {(*btcjson.TxRawDecodeResult)(nil)},
|
"decoderawtransaction": {(*btcjson.TxRawDecodeResult)(nil)},
|
||||||
|
@ -885,11 +950,12 @@ var rpcResultTypes = map[string][]interface{}{
|
||||||
"getbestblock": {(*btcjson.GetBestBlockResult)(nil)},
|
"getbestblock": {(*btcjson.GetBestBlockResult)(nil)},
|
||||||
"getbestblockhash": {(*string)(nil)},
|
"getbestblockhash": {(*string)(nil)},
|
||||||
"getblock": {(*string)(nil), (*btcjson.GetBlockVerboseResult)(nil)},
|
"getblock": {(*string)(nil), (*btcjson.GetBlockVerboseResult)(nil)},
|
||||||
|
"getblockchaininfo": {(*btcjson.GetBlockChainInfoResult)(nil)},
|
||||||
"getblockcount": {(*int64)(nil)},
|
"getblockcount": {(*int64)(nil)},
|
||||||
"getblockhash": {(*string)(nil)},
|
"getblockhash": {(*string)(nil)},
|
||||||
"getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},
|
"getblockheader": {(*string)(nil), (*btcjson.GetBlockHeaderVerboseResult)(nil)},
|
||||||
|
"getblockstats": {(*btcjson.GetBlockStatsResult)(nil)},
|
||||||
"getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil},
|
"getblocktemplate": {(*btcjson.GetBlockTemplateResult)(nil), (*string)(nil), nil},
|
||||||
"getblockchaininfo": {(*btcjson.GetBlockChainInfoResult)(nil)},
|
|
||||||
"getcfilter": {(*string)(nil)},
|
"getcfilter": {(*string)(nil)},
|
||||||
"getcfilterheader": {(*string)(nil)},
|
"getcfilterheader": {(*string)(nil)},
|
||||||
"getchaintips": {(*[]btcjson.GetChainTipsResult)(nil)},
|
"getchaintips": {(*[]btcjson.GetChainTipsResult)(nil)},
|
||||||
|
@ -911,13 +977,15 @@ var rpcResultTypes = map[string][]interface{}{
|
||||||
"getrawmempool": {(*[]string)(nil), (*btcjson.GetRawMempoolVerboseResult)(nil)},
|
"getrawmempool": {(*[]string)(nil), (*btcjson.GetRawMempoolVerboseResult)(nil)},
|
||||||
"getrawtransaction": {(*string)(nil), (*btcjson.TxRawResult)(nil)},
|
"getrawtransaction": {(*string)(nil), (*btcjson.TxRawResult)(nil)},
|
||||||
"gettxout": {(*btcjson.GetTxOutResult)(nil)},
|
"gettxout": {(*btcjson.GetTxOutResult)(nil)},
|
||||||
"node": nil,
|
|
||||||
"help": {(*string)(nil), (*string)(nil)},
|
"help": {(*string)(nil), (*string)(nil)},
|
||||||
"invalidateblock": nil,
|
"invalidateblock": nil,
|
||||||
|
"listbanned": {(*[]btcjson.ListBannedResult)(nil)},
|
||||||
|
"node": nil,
|
||||||
"ping": nil,
|
"ping": nil,
|
||||||
"reconsiderblock": nil,
|
"reconsiderblock": nil,
|
||||||
"searchrawtransactions": {(*string)(nil), (*[]btcjson.SearchRawTransactionsResult)(nil)},
|
"searchrawtransactions": {(*string)(nil), (*[]btcjson.SearchRawTransactionsResult)(nil)},
|
||||||
"sendrawtransaction": {(*string)(nil)},
|
"sendrawtransaction": {(*string)(nil)},
|
||||||
|
"setban": nil,
|
||||||
"setgenerate": nil,
|
"setgenerate": nil,
|
||||||
"signmessagewithprivkey": {(*string)(nil)},
|
"signmessagewithprivkey": {(*string)(nil)},
|
||||||
"stop": {(*string)(nil)},
|
"stop": {(*string)(nil)},
|
||||||
|
|
71
server.go
71
server.go
|
@ -156,13 +156,18 @@ type updatePeerHeightsMsg struct {
|
||||||
originPeer *peer.Peer
|
originPeer *peer.Peer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type bannedPeriod struct {
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
}
|
||||||
|
|
||||||
// peerState maintains state of inbound, persistent, outbound peers as well
|
// peerState maintains state of inbound, persistent, outbound peers as well
|
||||||
// as banned peers and outbound groups.
|
// as banned peers and outbound groups.
|
||||||
type peerState struct {
|
type peerState struct {
|
||||||
inboundPeers map[int32]*serverPeer
|
inboundPeers map[int32]*serverPeer
|
||||||
outboundPeers map[int32]*serverPeer
|
outboundPeers map[int32]*serverPeer
|
||||||
persistentPeers map[int32]*serverPeer
|
persistentPeers map[int32]*serverPeer
|
||||||
banned map[string]time.Time
|
banned map[string]bannedPeriod
|
||||||
outboundGroups map[string]int
|
outboundGroups map[string]int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -699,6 +704,11 @@ func (sp *serverPeer) OnGetData(_ *peer.Peer, msg *wire.MsgGetData) {
|
||||||
if i == len(msg.InvList)-1 && c != nil {
|
if i == len(msg.InvList)-1 && c != nil {
|
||||||
<-c
|
<-c
|
||||||
}
|
}
|
||||||
|
} else if iv.Type == wire.InvTypeWitnessTx || iv.Type == wire.InvTypeTx {
|
||||||
|
// We interpret fulfilling a GETDATA for a transaction as a
|
||||||
|
// successful initial broadcast and remove it from our
|
||||||
|
// unbroadcast set.
|
||||||
|
sp.server.txMemPool.RemoveUnbroadcastTx(&iv.Hash)
|
||||||
}
|
}
|
||||||
numAdded++
|
numAdded++
|
||||||
waitChan = c
|
waitChan = c
|
||||||
|
@ -1651,10 +1661,10 @@ func (s *server) handleAddPeerMsg(state *peerState, sp *serverPeer) bool {
|
||||||
sp.Disconnect()
|
sp.Disconnect()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if banEnd, ok := state.banned[host]; ok {
|
if ban, ok := state.banned[host]; ok {
|
||||||
if time.Now().Before(banEnd) {
|
if time.Now().Before(ban.until) {
|
||||||
srvrLog.Debugf("Peer %s is banned for another %v - disconnecting",
|
srvrLog.Infof("Peer %s is banned for another %v - disconnecting",
|
||||||
host, time.Until(banEnd))
|
host, time.Until(ban.until))
|
||||||
sp.Disconnect()
|
sp.Disconnect()
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -1776,7 +1786,12 @@ func (s *server) handleBanPeerMsg(state *peerState, sp *serverPeer) {
|
||||||
direction := directionString(sp.Inbound())
|
direction := directionString(sp.Inbound())
|
||||||
srvrLog.Infof("Banned peer %s (%s) for %v", host, direction,
|
srvrLog.Infof("Banned peer %s (%s) for %v", host, direction,
|
||||||
cfg.BanDuration)
|
cfg.BanDuration)
|
||||||
state.banned[host] = time.Now().Add(cfg.BanDuration)
|
|
||||||
|
since := time.Now()
|
||||||
|
state.banned[host] = bannedPeriod{
|
||||||
|
since: since,
|
||||||
|
until: since.Add(cfg.BanDuration),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleRelayInvMsg deals with relaying inventory to peers that are not already
|
// handleRelayInvMsg deals with relaying inventory to peers that are not already
|
||||||
|
@ -1871,6 +1886,25 @@ type getPeersMsg struct {
|
||||||
reply chan []*serverPeer
|
reply chan []*serverPeer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type listBannedPeersMsg struct {
|
||||||
|
reply chan map[string]bannedPeriod
|
||||||
|
}
|
||||||
|
|
||||||
|
type setBanMsg struct {
|
||||||
|
addr string
|
||||||
|
since time.Time
|
||||||
|
until time.Time
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type removeBanMsg struct {
|
||||||
|
addr string
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
type clearBannedMsg struct {
|
||||||
|
reply chan error
|
||||||
|
}
|
||||||
type getOutboundGroup struct {
|
type getOutboundGroup struct {
|
||||||
key string
|
key string
|
||||||
reply chan int
|
reply chan int
|
||||||
|
@ -1919,6 +1953,29 @@ func (s *server) handleQuery(state *peerState, querymsg interface{}) {
|
||||||
})
|
})
|
||||||
msg.reply <- peers
|
msg.reply <- peers
|
||||||
|
|
||||||
|
case listBannedPeersMsg:
|
||||||
|
banned := map[string]bannedPeriod{}
|
||||||
|
for host, ban := range state.banned {
|
||||||
|
banned[host] = ban
|
||||||
|
}
|
||||||
|
msg.reply <- banned
|
||||||
|
|
||||||
|
case setBanMsg:
|
||||||
|
ban := bannedPeriod{
|
||||||
|
since: msg.since,
|
||||||
|
until: msg.until,
|
||||||
|
}
|
||||||
|
state.banned[msg.addr] = ban
|
||||||
|
msg.reply <- nil
|
||||||
|
|
||||||
|
case removeBanMsg:
|
||||||
|
delete(state.banned, msg.addr)
|
||||||
|
msg.reply <- nil
|
||||||
|
|
||||||
|
case clearBannedMsg:
|
||||||
|
state.banned = map[string]bannedPeriod{}
|
||||||
|
msg.reply <- nil
|
||||||
|
|
||||||
case connectNodeMsg:
|
case connectNodeMsg:
|
||||||
// TODO: duplicate oneshots?
|
// TODO: duplicate oneshots?
|
||||||
// Limit max number of total peers.
|
// Limit max number of total peers.
|
||||||
|
@ -2156,7 +2213,7 @@ func (s *server) peerHandler() {
|
||||||
inboundPeers: make(map[int32]*serverPeer),
|
inboundPeers: make(map[int32]*serverPeer),
|
||||||
persistentPeers: make(map[int32]*serverPeer),
|
persistentPeers: make(map[int32]*serverPeer),
|
||||||
outboundPeers: make(map[int32]*serverPeer),
|
outboundPeers: make(map[int32]*serverPeer),
|
||||||
banned: make(map[string]time.Time),
|
banned: make(map[string]bannedPeriod),
|
||||||
outboundGroups: make(map[string]int),
|
outboundGroups: make(map[string]int),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,9 @@ const (
|
||||||
|
|
||||||
// ErrInvalidClaimUpdateScript is returned a claim update script does not conform to the format.
|
// ErrInvalidClaimUpdateScript is returned a claim update script does not conform to the format.
|
||||||
ErrInvalidClaimUpdateScript
|
ErrInvalidClaimUpdateScript
|
||||||
|
|
||||||
|
// ErrInvalidClaimName is returned when the claim name is invalid.
|
||||||
|
ErrInvalidClaimName
|
||||||
)
|
)
|
||||||
|
|
||||||
func claimScriptError(c ErrorCode, desc string) Error {
|
func claimScriptError(c ErrorCode, desc string) Error {
|
||||||
|
@ -98,11 +101,15 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
|
||||||
|
|
||||||
if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP ||
|
if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP ||
|
||||||
!tokenizer.Next() || tokenizer.Opcode() != OP_DROP {
|
!tokenizer.Next() || tokenizer.Opcode() != OP_DROP {
|
||||||
str := fmt.Sprintf("expect OP_2DROP OP_DROP")
|
return nil, claimScriptError(ErrInvalidClaimNameScript, "expect OP_2DROP OP_DROP")
|
||||||
return nil, claimScriptError(ErrInvalidClaimNameScript, str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.Size = int(tokenizer.ByteIndex())
|
cs.Size = int(tokenizer.ByteIndex())
|
||||||
|
if cs.Size > MaxClaimScriptSize {
|
||||||
|
str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize)
|
||||||
|
return nil, claimScriptError(ErrInvalidClaimNameScript, str)
|
||||||
|
}
|
||||||
|
|
||||||
return &cs, nil
|
return &cs, nil
|
||||||
|
|
||||||
case OP_SUPPORTCLAIM:
|
case OP_SUPPORTCLAIM:
|
||||||
|
@ -128,8 +135,7 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
|
||||||
case tokenizer.Opcode() == OP_2DROP:
|
case tokenizer.Opcode() == OP_2DROP:
|
||||||
// Case 1: OP_SUPPORTCLAIM <Name> <ClaimID> OP_2DROP OP_DROP <P2PKH>
|
// Case 1: OP_SUPPORTCLAIM <Name> <ClaimID> OP_2DROP OP_DROP <P2PKH>
|
||||||
if !tokenizer.Next() || tokenizer.Opcode() != OP_DROP {
|
if !tokenizer.Next() || tokenizer.Opcode() != OP_DROP {
|
||||||
str := fmt.Sprintf("expect OP_2DROP OP_DROP")
|
return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_DROP")
|
||||||
return nil, claimScriptError(ErrInvalidClaimSupportScript, str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case len(tokenizer.Data()) != 0:
|
case len(tokenizer.Data()) != 0:
|
||||||
|
@ -138,19 +144,21 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
|
||||||
cs.Value = tokenizer.Data()
|
cs.Value = tokenizer.Data()
|
||||||
if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP ||
|
if !tokenizer.Next() || tokenizer.Opcode() != OP_2DROP ||
|
||||||
!tokenizer.Next() || tokenizer.Opcode() != OP_2DROP {
|
!tokenizer.Next() || tokenizer.Opcode() != OP_2DROP {
|
||||||
str := fmt.Sprintf("expect OP_2DROP OP_2DROP")
|
return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_2DROP")
|
||||||
return nil, claimScriptError(ErrInvalidClaimSupportScript, str)
|
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
str := fmt.Sprintf("expect OP_2DROP OP_DROP")
|
return nil, claimScriptError(ErrInvalidClaimSupportScript, "expect OP_2DROP OP_DROP")
|
||||||
return nil, claimScriptError(ErrInvalidClaimSupportScript, str)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.Size = int(tokenizer.ByteIndex())
|
cs.Size = int(tokenizer.ByteIndex())
|
||||||
|
if cs.Size > MaxClaimScriptSize {
|
||||||
|
str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize)
|
||||||
|
return nil, claimScriptError(ErrInvalidClaimSupportScript, str)
|
||||||
|
}
|
||||||
|
|
||||||
return &cs, nil
|
return &cs, nil
|
||||||
|
|
||||||
case OP_UPDATECLAIM:
|
case OP_UPDATECLAIM:
|
||||||
|
|
||||||
// OP_UPDATECLAIM <Name> <ClaimID> <Value> OP_2DROP OP_2DROP <P2PKH>
|
// OP_UPDATECLAIM <Name> <ClaimID> <Value> OP_2DROP OP_2DROP <P2PKH>
|
||||||
if !tokenizer.Next() || len(tokenizer.Data()) > MaxClaimNameSize {
|
if !tokenizer.Next() || len(tokenizer.Data()) > MaxClaimNameSize {
|
||||||
str := fmt.Sprintf("name size %d exceeds limit %d", len(tokenizer.data), MaxClaimNameSize)
|
str := fmt.Sprintf("name size %d exceeds limit %d", len(tokenizer.data), MaxClaimNameSize)
|
||||||
|
@ -177,6 +185,11 @@ func ExtractClaimScript(script []byte) (*ClaimScript, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
cs.Size = int(tokenizer.ByteIndex())
|
cs.Size = int(tokenizer.ByteIndex())
|
||||||
|
if cs.Size > MaxClaimScriptSize {
|
||||||
|
str := fmt.Sprintf("script size %d exceeds limit %d", cs.Size, MaxClaimScriptSize)
|
||||||
|
return nil, claimScriptError(ErrInvalidClaimUpdateScript, str)
|
||||||
|
}
|
||||||
|
|
||||||
return &cs, nil
|
return &cs, nil
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -193,7 +206,7 @@ func StripClaimScriptPrefix(script []byte) []byte {
|
||||||
return script[cs.Size:]
|
return script[cs.Size:]
|
||||||
}
|
}
|
||||||
|
|
||||||
const illegalChars = "=&#:*$%?/;\\\b\n\t\r\x00"
|
const illegalChars = "=&#:$%?/;\\\b\n\t\r\x00"
|
||||||
|
|
||||||
func AllClaimsAreSane(script []byte, enforceSoftFork bool) error {
|
func AllClaimsAreSane(script []byte, enforceSoftFork bool) error {
|
||||||
cs, err := ExtractClaimScript(script)
|
cs, err := ExtractClaimScript(script)
|
||||||
|
@ -205,10 +218,11 @@ func AllClaimsAreSane(script []byte, enforceSoftFork bool) error {
|
||||||
}
|
}
|
||||||
if enforceSoftFork {
|
if enforceSoftFork {
|
||||||
if !utf8.Valid(cs.Name) {
|
if !utf8.Valid(cs.Name) {
|
||||||
return fmt.Errorf("claim name is not valid UTF-8")
|
return claimScriptError(ErrInvalidClaimName, "claim name is not valid UTF-8")
|
||||||
}
|
}
|
||||||
if bytes.ContainsAny(cs.Name, illegalChars) {
|
if bytes.ContainsAny(cs.Name, illegalChars) {
|
||||||
return fmt.Errorf("claim name has illegal chars; it should not contain any of these: %s", illegalChars)
|
str := fmt.Sprintf("claim name has illegal chars; it should not contain any of these: %s", illegalChars)
|
||||||
|
return claimScriptError(ErrInvalidClaimName, str)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,7 @@ overview to provide information on how to use the package.
|
||||||
This package provides data structures and functions to parse and execute
|
This package provides data structures and functions to parse and execute
|
||||||
bitcoin transaction scripts.
|
bitcoin transaction scripts.
|
||||||
|
|
||||||
Script Overview
|
# Script Overview
|
||||||
|
|
||||||
Bitcoin transaction scripts are written in a stack-base, FORTH-like language.
|
Bitcoin transaction scripts are written in a stack-base, FORTH-like language.
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@ is used to prove the the spender is authorized to perform the transaction.
|
||||||
One benefit of using a scripting language is added flexibility in specifying
|
One benefit of using a scripting language is added flexibility in specifying
|
||||||
what conditions must be met in order to spend bitcoins.
|
what conditions must be met in order to spend bitcoins.
|
||||||
|
|
||||||
Errors
|
# Errors
|
||||||
|
|
||||||
Errors returned by this package are of type txscript.Error. This allows the
|
Errors returned by this package are of type txscript.Error. This allows the
|
||||||
caller to programmatically determine the specific error by examining the
|
caller to programmatically determine the specific error by examining the
|
||||||
|
|
|
@ -427,10 +427,10 @@ func (e ErrorCode) String() string {
|
||||||
|
|
||||||
// Error identifies a script-related error. It is used to indicate three
|
// Error identifies a script-related error. It is used to indicate three
|
||||||
// classes of errors:
|
// classes of errors:
|
||||||
// 1) Script execution failures due to violating one of the many requirements
|
// 1. Script execution failures due to violating one of the many requirements
|
||||||
// imposed by the script engine or evaluating to false
|
// imposed by the script engine or evaluating to false
|
||||||
// 2) Improper API usage by callers
|
// 2. Improper API usage by callers
|
||||||
// 3) Internal consistency check failures
|
// 3. Internal consistency check failures
|
||||||
//
|
//
|
||||||
// The caller can use type assertions on the returned errors to access the
|
// The caller can use type assertions on the returned errors to access the
|
||||||
// ErrorCode field to ascertain the specific reason for the error. As an
|
// ErrorCode field to ascertain the specific reason for the error. As an
|
||||||
|
|
|
@ -37,16 +37,17 @@ func (e ErrScriptNotCanonical) Error() string {
|
||||||
// For example, the following would build a 2-of-3 multisig script for usage in
|
// For example, the following would build a 2-of-3 multisig script for usage in
|
||||||
// a pay-to-script-hash (although in this situation MultiSigScript() would be a
|
// a pay-to-script-hash (although in this situation MultiSigScript() would be a
|
||||||
// better choice to generate the script):
|
// better choice to generate the script):
|
||||||
// builder := txscript.NewScriptBuilder()
|
//
|
||||||
// builder.AddOp(txscript.OP_2).AddData(pubKey1).AddData(pubKey2)
|
// builder := txscript.NewScriptBuilder()
|
||||||
// builder.AddData(pubKey3).AddOp(txscript.OP_3)
|
// builder.AddOp(txscript.OP_2).AddData(pubKey1).AddData(pubKey2)
|
||||||
// builder.AddOp(txscript.OP_CHECKMULTISIG)
|
// builder.AddData(pubKey3).AddOp(txscript.OP_3)
|
||||||
// script, err := builder.Script()
|
// builder.AddOp(txscript.OP_CHECKMULTISIG)
|
||||||
// if err != nil {
|
// script, err := builder.Script()
|
||||||
// // Handle the error.
|
// if err != nil {
|
||||||
// return
|
// // Handle the error.
|
||||||
// }
|
// return
|
||||||
// fmt.Printf("Final multi-sig script: %x\n", script)
|
// }
|
||||||
|
// fmt.Printf("Final multi-sig script: %x\n", script)
|
||||||
type ScriptBuilder struct {
|
type ScriptBuilder struct {
|
||||||
script []byte
|
script []byte
|
||||||
err error
|
err error
|
||||||
|
|
|
@ -89,18 +89,19 @@ func checkMinimalDataEncoding(v []byte) error {
|
||||||
// Bytes returns the number serialized as a little endian with a sign bit.
|
// Bytes returns the number serialized as a little endian with a sign bit.
|
||||||
//
|
//
|
||||||
// Example encodings:
|
// Example encodings:
|
||||||
// 127 -> [0x7f]
|
//
|
||||||
// -127 -> [0xff]
|
// 127 -> [0x7f]
|
||||||
// 128 -> [0x80 0x00]
|
// -127 -> [0xff]
|
||||||
// -128 -> [0x80 0x80]
|
// 128 -> [0x80 0x00]
|
||||||
// 129 -> [0x81 0x00]
|
// -128 -> [0x80 0x80]
|
||||||
// -129 -> [0x81 0x80]
|
// 129 -> [0x81 0x00]
|
||||||
// 256 -> [0x00 0x01]
|
// -129 -> [0x81 0x80]
|
||||||
// -256 -> [0x00 0x81]
|
// 256 -> [0x00 0x01]
|
||||||
// 32767 -> [0xff 0x7f]
|
// -256 -> [0x00 0x81]
|
||||||
// -32767 -> [0xff 0xff]
|
// 32767 -> [0xff 0x7f]
|
||||||
// 32768 -> [0x00 0x80 0x00]
|
// -32767 -> [0xff 0xff]
|
||||||
// -32768 -> [0x00 0x80 0x80]
|
// 32768 -> [0x00 0x80 0x00]
|
||||||
|
// -32768 -> [0x00 0x80 0x80]
|
||||||
func (n scriptNum) Bytes() []byte {
|
func (n scriptNum) Bytes() []byte {
|
||||||
// Zero encodes as an empty byte slice.
|
// Zero encodes as an empty byte slice.
|
||||||
if n == 0 {
|
if n == 0 {
|
||||||
|
|
|
@ -139,7 +139,10 @@ func (v parsedVersion) buildmeta() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v parsedVersion) full() string {
|
func (v parsedVersion) full() string {
|
||||||
return fmt.Sprintf("%s-%s+%s", v.version, v.prerelease, v.buildmeta())
|
if len(v.prerelease) > 0 {
|
||||||
|
return fmt.Sprintf("%s-%s+%s", v.version, v.prerelease, v.buildmeta())
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s+%s", v.version, v.buildmeta())
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseTag(tag string) (version string, prerelease string, err error) {
|
func parseTag(tag string) (version string, prerelease string, err error) {
|
||||||
|
@ -148,7 +151,13 @@ func parseTag(tag string) (version string, prerelease string, err error) {
|
||||||
return "", "", fmt.Errorf("tag must be prefixed with v; %s", tag)
|
return "", "", fmt.Errorf("tag must be prefixed with v; %s", tag)
|
||||||
}
|
}
|
||||||
|
|
||||||
strs := strings.Split(tag[1:], "-")
|
tag = tag[1:]
|
||||||
|
|
||||||
|
if !strings.Contains(tag, "-") {
|
||||||
|
return tag, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
strs := strings.Split(tag, "-")
|
||||||
|
|
||||||
if len(strs) != 2 {
|
if len(strs) != 2 {
|
||||||
return "", "", fmt.Errorf("tag must be in the form of Version.Revision; %s", tag)
|
return "", "", fmt.Errorf("tag must be in the form of Version.Revision; %s", tag)
|
||||||
|
|
20
wire/doc.go
20
wire/doc.go
|
@ -14,7 +14,7 @@ supported bitcoin messages to and from the wire. This package does not deal
|
||||||
with the specifics of message handling such as what to do when a message is
|
with the specifics of message handling such as what to do when a message is
|
||||||
received. This provides the caller with a high level of flexibility.
|
received. This provides the caller with a high level of flexibility.
|
||||||
|
|
||||||
Bitcoin Message Overview
|
# Bitcoin Message Overview
|
||||||
|
|
||||||
The bitcoin protocol consists of exchanging messages between peers. Each
|
The bitcoin protocol consists of exchanging messages between peers. Each
|
||||||
message is preceded by a header which identifies information about it such as
|
message is preceded by a header which identifies information about it such as
|
||||||
|
@ -30,7 +30,7 @@ messages, all of the details of marshalling and unmarshalling to and from the
|
||||||
wire using bitcoin encoding are handled so the caller doesn't have to concern
|
wire using bitcoin encoding are handled so the caller doesn't have to concern
|
||||||
themselves with the specifics.
|
themselves with the specifics.
|
||||||
|
|
||||||
Message Interaction
|
# Message Interaction
|
||||||
|
|
||||||
The following provides a quick summary of how the bitcoin messages are intended
|
The following provides a quick summary of how the bitcoin messages are intended
|
||||||
to interact with one another. As stated above, these interactions are not
|
to interact with one another. As stated above, these interactions are not
|
||||||
|
@ -62,13 +62,13 @@ interactions in no particular order.
|
||||||
in BIP0031. The BIP0031Version constant can be used to detect a recent
|
in BIP0031. The BIP0031Version constant can be used to detect a recent
|
||||||
enough protocol version for this purpose (version > BIP0031Version).
|
enough protocol version for this purpose (version > BIP0031Version).
|
||||||
|
|
||||||
Common Parameters
|
# Common Parameters
|
||||||
|
|
||||||
There are several common parameters that arise when using this package to read
|
There are several common parameters that arise when using this package to read
|
||||||
and write bitcoin messages. The following sections provide a quick overview of
|
and write bitcoin messages. The following sections provide a quick overview of
|
||||||
these parameters so the next sections can build on them.
|
these parameters so the next sections can build on them.
|
||||||
|
|
||||||
Protocol Version
|
# Protocol Version
|
||||||
|
|
||||||
The protocol version should be negotiated with the remote peer at a higher
|
The protocol version should be negotiated with the remote peer at a higher
|
||||||
level than this package via the version (MsgVersion) message exchange, however,
|
level than this package via the version (MsgVersion) message exchange, however,
|
||||||
|
@ -77,7 +77,7 @@ latest protocol version this package supports and is typically the value to use
|
||||||
for all outbound connections before a potentially lower protocol version is
|
for all outbound connections before a potentially lower protocol version is
|
||||||
negotiated.
|
negotiated.
|
||||||
|
|
||||||
Bitcoin Network
|
# Bitcoin Network
|
||||||
|
|
||||||
The bitcoin network is a magic number which is used to identify the start of a
|
The bitcoin network is a magic number which is used to identify the start of a
|
||||||
message and which bitcoin network the message applies to. This package provides
|
message and which bitcoin network the message applies to. This package provides
|
||||||
|
@ -88,7 +88,7 @@ the following constants:
|
||||||
wire.TestNet3 (Test network version 3)
|
wire.TestNet3 (Test network version 3)
|
||||||
wire.SimNet (Simulation test network)
|
wire.SimNet (Simulation test network)
|
||||||
|
|
||||||
Determining Message Type
|
# Determining Message Type
|
||||||
|
|
||||||
As discussed in the bitcoin message overview section, this package reads
|
As discussed in the bitcoin message overview section, this package reads
|
||||||
and writes bitcoin messages using a generic interface named Message. In
|
and writes bitcoin messages using a generic interface named Message. In
|
||||||
|
@ -106,7 +106,7 @@ switch or type assertion. An example of a type switch follows:
|
||||||
fmt.Printf("Number of tx in block: %v", msg.Header.TxnCount)
|
fmt.Printf("Number of tx in block: %v", msg.Header.TxnCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
Reading Messages
|
# Reading Messages
|
||||||
|
|
||||||
In order to unmarshall bitcoin messages from the wire, use the ReadMessage
|
In order to unmarshall bitcoin messages from the wire, use the ReadMessage
|
||||||
function. It accepts any io.Reader, but typically this will be a net.Conn to
|
function. It accepts any io.Reader, but typically this will be a net.Conn to
|
||||||
|
@ -121,7 +121,7 @@ a remote node running a bitcoin peer. Example syntax is:
|
||||||
// Log and handle the error
|
// Log and handle the error
|
||||||
}
|
}
|
||||||
|
|
||||||
Writing Messages
|
# Writing Messages
|
||||||
|
|
||||||
In order to marshall bitcoin messages to the wire, use the WriteMessage
|
In order to marshall bitcoin messages to the wire, use the WriteMessage
|
||||||
function. It accepts any io.Writer, but typically this will be a net.Conn to
|
function. It accepts any io.Writer, but typically this will be a net.Conn to
|
||||||
|
@ -139,7 +139,7 @@ from a remote peer is:
|
||||||
// Log and handle the error
|
// Log and handle the error
|
||||||
}
|
}
|
||||||
|
|
||||||
Errors
|
# Errors
|
||||||
|
|
||||||
Errors returned by this package are either the raw errors provided by underlying
|
Errors returned by this package are either the raw errors provided by underlying
|
||||||
calls to read/write from streams such as io.EOF, io.ErrUnexpectedEOF, and
|
calls to read/write from streams such as io.EOF, io.ErrUnexpectedEOF, and
|
||||||
|
@ -147,7 +147,7 @@ io.ErrShortWrite, or of type wire.MessageError. This allows the caller to
|
||||||
differentiate between general IO errors and malformed messages through type
|
differentiate between general IO errors and malformed messages through type
|
||||||
assertions.
|
assertions.
|
||||||
|
|
||||||
Bitcoin Improvement Proposals
|
# Bitcoin Improvement Proposals
|
||||||
|
|
||||||
This package includes spec changes outlined by the following BIPs:
|
This package includes spec changes outlined by the following BIPs:
|
||||||
|
|
||||||
|
|
|
@ -114,16 +114,18 @@ const (
|
||||||
// transaction from one that would require a different parsing logic.
|
// transaction from one that would require a different parsing logic.
|
||||||
//
|
//
|
||||||
// Position of FLAG in a bitcoin tx message:
|
// Position of FLAG in a bitcoin tx message:
|
||||||
// ┌─────────┬────────────────────┬─────────────┬─────┐
|
//
|
||||||
// │ VERSION │ FLAG │ TX-IN-COUNT │ ... │
|
// ┌─────────┬────────────────────┬─────────────┬─────┐
|
||||||
// │ 4 bytes │ 2 bytes (optional) │ varint │ │
|
// │ VERSION │ FLAG │ TX-IN-COUNT │ ... │
|
||||||
// └─────────┴────────────────────┴─────────────┴─────┘
|
// │ 4 bytes │ 2 bytes (optional) │ varint │ │
|
||||||
|
// └─────────┴────────────────────┴─────────────┴─────┘
|
||||||
//
|
//
|
||||||
// Zooming into the FLAG field:
|
// Zooming into the FLAG field:
|
||||||
// ┌── FLAG ─────────────┬────────┐
|
//
|
||||||
// │ TxFlagMarker (0x00) │ TxFlag │
|
// ┌── FLAG ─────────────┬────────┐
|
||||||
// │ 1 byte │ 1 byte │
|
// │ TxFlagMarker (0x00) │ TxFlag │
|
||||||
// └─────────────────────┴────────┘
|
// │ 1 byte │ 1 byte │
|
||||||
|
// └─────────────────────┴────────┘
|
||||||
const TxFlagMarker = 0x00
|
const TxFlagMarker = 0x00
|
||||||
|
|
||||||
// TxFlag is the second byte of the FLAG field in a bitcoin tx message.
|
// TxFlag is the second byte of the FLAG field in a bitcoin tx message.
|
||||||
|
|
Loading…
Reference in a new issue