Compare commits

...

46 commits

Author SHA1 Message Date
Roy Lee
a0ff51b84a claimtrie: allows '*' in claim name 2022-11-23 08:50:17 -08:00
Roy Lee
4c39a9842c rpcclient: update rescanblockchain support 2022-10-31 00:23:46 -07:00
Roy Lee
f513fca6a7 lbcdblocknotify: reorganize the code with a few updates
1. Fixed a bug, which reads certs even TLS is disabled

2. Persists Stratum TCP connection with auto-reconnect.
   (retry backoff increases from 1s to 60s maximum)

3. Stratum update 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.
2022-10-17 00:03:13 -07:00
Alex Grintsvayg
6728bf4b08 error properly when lbcd fails to connect in HTTP POST mode
in the case where you're e.g. trying to connect to an
invalid address, the err vars in handleSendPostMessage()
were being shadowed inside the for loop. if c.httpClient.Do()
returned an error, that error never got returned upstream.
then ioutil.ReadAll(httpResponse.Body) would get a nil pointer
dereference. this fixes that case.
2022-10-14 11:40:46 -07:00
Roy Lee
979d643594 [lbry] claimtrie: created node cache 2022-09-29 16:45:42 -07:00
Roy Lee
cbc4d489e8 lbcctl: support --timed, --quiet options 2022-09-29 16:45:42 -07:00
Roy Lee
987a533423 rpc: update rpc cmd requests to support multi-account
Most of the updates add optional arguments with default
values.
2022-09-26 11:08:19 -07:00
Roy Lee
6bc9a2b4dd mining: always returns .coinbasevalue in getblocktemplate
Although the BIPs specify that coinbasetxn and coinbasevalue are
mutually exclusive, both the latest bitcoind (22.0.0) and lbrycrd
(0.17.3) return .coinbasevalue regardeless if 'coinbasetxn' is
specified in the capabilities.

We'll make lbcd behave the same for compatibility.
2022-09-25 18:48:59 -07:00
Roy Lee
9bcd3d0591 cotrib: add a helper script to show miner of a bkock 2022-09-23 17:49:01 -07:00
Roy Lee
2adfcd211d rpcclient: add -quiet option to the lbcdblocknotify example 2022-09-23 17:48:05 -07:00
Roy Lee
81ec217899 rpcserver: fix up getblockstats 2022-09-20 23:59:57 -07:00
Guilherme de Paula
5acfa4c81b rpcserver: add GetBlockStats 2022-09-20 23:59:57 -07:00
Roy Lee
c5193e74ac rpc: support hex data output for createrawtransaction 2022-09-14 18:41:04 -07:00
Roy Lee
8a80f0683a [lbry] policy: relax dust thrashold to 1000 dewies/kB
An 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, which
has a default rate of 1000 satoshis/kb

bitcoind refactored dust threshold calculation, which removed the
multiply factor of 3 from the code, but increased the DUST_RELAY_TX_FEE
from 1000 to 3000 (satoshi/kb).

lbrycrd adopted the refactored code but also kept the rate to
1000 dewies/kB, which means:

    An output is considered dust if the cost to the network to spend the
    coins is more than the minimum free transaction relay fee.
2022-09-01 15:28:07 -07:00
Roy Lee
5d7a219e35 rpc: make getblock return orphan blocks with confirmation=-1 2022-08-31 18:32:49 -07:00
Roy Lee
2d04d31894 rpc: implement rescanblockchain rpcclient 2022-08-31 18:32:49 -07:00
Roy Lee
ce37025d5a txscript: validate claimscript size 2022-08-30 15:30:07 -07:00
Roy Lee
98e5771989 rpc: implement setban, lisnbanned, clearbanned RPCs 2022-08-14 21:26:27 -07:00
Roy Lee
ff324e0fdb doc: update snapshot related instructions 2022-08-14 14:17:41 -07:00
Roy Lee
be0d7de8da mining: accomodate pre-BIP0141 coinbase structure
Some popular pool software, yiimp for example, constructs coinbase
in pre-BIP0141 style, which results in rejection of submitblock.
2022-08-12 10:39:26 -07:00
Roy Lee
fcfb2af76f netsync: revert base/segwit encoding hack 2022-08-12 10:39:26 -07:00
Roy Lee
78bed14956 go mod: bump lbcutil to v1.0.202 2022-08-12 10:39:26 -07:00
Roy Lee
fdedbf86f8 mining: include 'segwit' rule when no segwit txns in GBT
According to the BIP0009, all active softfork deployment should
be included in the rules.

We add the '!' to indicate the enforcement if the template has
any segwit transactions in it. Otherwise, plain `segwit` is fine.
2022-08-08 00:49:16 -07:00
Roy Lee
a9351b3e3a lbcdblocknotify: support --run to execute custom command 2022-08-07 23:55:10 -07:00
Roy Lee
e323751218 ci: gofmt with go 1.19
Go 1.19 introduces various updates to gofmt.
2022-08-07 23:40:53 -07:00
Roy Lee
66c8567a27 ci: bump to Go 1.19 2022-08-07 23:40:17 -07:00
Roy Lee
6b0e7592c6 btcjson: remove WebsocketOnly for wallet extension RPCs 2022-07-29 12:10:53 -07:00
Roy Lee
05f52c11a1 docs: update README.md 2022-07-28 17:23:39 -07:00
Roy Lee
ea63a44c7b [lbry] rpcclient: fix stratum update_block format for blocknotify 2022-07-28 08:32:09 -07:00
Jonathan Moody
daa3137dc4 [rpc blockchain] Add support for mediantime, chainwork to RPC getblock. 2022-07-27 10:41:24 -07:00
Roy Lee
b147fe2a5b Revert "[lbry] claimtrie: created node cache"
This reverts commit 8f95946b17.
2022-07-27 10:18:35 -07:00
Jonathan Moody
7f9fe4b970 [rpc mempool] More tweaks to dynamicMemUsage(). Add toggleable assertions for max depth and switch completness. Toggle them when running in mempool_test.go. Drop support for reflect.Map, as it's not needed at this time. 2022-07-18 17:17:56 -07:00
Jonathan Moody
eefb1260eb [rpc mempool] Correct comment BTC -> LBC. 2022-07-18 17:17:56 -07:00
Jonathan Moody
a8a44aa988 [rpc mempool] Hide debugging functionality of dynamicMemUsage(). 2022-07-18 17:17:56 -07:00
Jonathan Moody
abb1b8b388 [rpc mempool] Add support for unbroadcastcount to RPC getmempoolinfo. 2022-07-18 17:17:56 -07:00
Jonathan Moody
13e31d033a [rpc mempool] Add support for usage, total_fee, mempoolminfee, minrelaytxfee to RPC getmempoolinfo. 2022-07-18 17:17:56 -07:00
Roy Lee
5499a2c1b3 [lbry] claimtrie: more verbose error message in ResetHeight 2022-07-17 11:32:33 -07:00
Roy Lee
fae4063046 rpc: remove deprecated and unimplemented 'move' 2022-07-14 15:45:06 -07:00
Roy Lee
8d1005706b rpc: remove deprecated and unimplemented 'setaccount' 2022-07-14 15:43:35 -07:00
Roy Lee
bb93a49349 [lbry] config: allow non-localhost connections with TLS disabled 2022-07-11 16:52:38 -07:00
Roy Lee
d5922cd725 [lbry] version: fix version string handling 2022-07-06 20:44:22 -07:00
Roy Lee
3a179a0eee [lbry] rpc: un-embedded attributes in getaddressinfo result
lbcwallet failed to re-generate RPC help message.

The help message generator doesn't handle embedded fields properly.
2022-07-05 20:12:27 -07:00
Jonathan Moody
ca9b4e5529 Rename nameProgressLogger -> claimProgressLogger and tweak log message. 2022-06-14 11:27:58 -07:00
Jonathan Moody
2b7f065855 Adjust and rename blockProgressLogger -> nameProgressLogger. Use it in makeNameHashNext() to track progress. 2022-06-14 11:27:58 -07:00
Jonathan Moody
b859832907 Copy netsync/blocklogger.go to claimtrie/logger.go. 2022-06-14 11:27:58 -07:00
Jonathan Moody
70852905e0 Allow environment var GOMAXPROCS=<N> to override NumCPU(). 2022-06-14 11:03:27 -07:00
94 changed files with 3446 additions and 1453 deletions

View file

@ -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

View file

@ -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 }}!"

View file

@ -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 }}!"

View file

@ -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:

View file

@ -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

View file

@ -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
View file

@ -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.

View file

@ -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

View file

@ -8,6 +8,7 @@ package blockchain
import ( import (
"container/list" "container/list"
"fmt" "fmt"
"math/big"
"sync" "sync"
"time" "time"
@ -36,6 +37,7 @@ 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 // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
// \-> 16a -> 17a // \-> 16a -> 17a
// //
@ -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.
@ -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.

View file

@ -36,10 +36,12 @@ 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 // genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
// \-> 4a -> 5a -> 6a // \-> 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
@ -258,11 +260,13 @@ 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 // genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
// \-> 4a -> 5a -> 6a // \-> 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
@ -321,11 +325,13 @@ 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 // genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8
// \-> 6a -> 7a // \-> 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

View file

@ -42,9 +42,11 @@ 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
//
// - the least significant 23 bits represent the mantissa
// //
// ------------------------------------------------- // -------------------------------------------------
// | Exponent | Sign | Mantissa | // | Exponent | Sign | Mantissa |
@ -53,6 +55,7 @@ func HashToBig(hash *chainhash.Hash) *big.Int {
// ------------------------------------------------- // -------------------------------------------------
// //
// 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

View file

@ -26,7 +26,7 @@ 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
@ -61,7 +61,7 @@ is by no means exhaustive:
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,7 +70,7 @@ 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:

View file

@ -27,6 +27,7 @@ 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} // {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp}) // ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger { func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {

View file

@ -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"
) )
@ -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 "+

View file

@ -244,6 +244,7 @@ func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, t
// 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 0 - containing transaction is a coinbase
// bit 1 - output zero is unspent // bit 1 - output zero is unspent
// bit 2 - output one is unspent // bit 2 - output one is unspent

View file

@ -125,6 +125,7 @@ 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] | // | n[9] | n[8] | ... | n[0] |
// | 32 bits available | 32 bits available | ... | 32 bits available | // | 32 bits available | 32 bits available | ... | 32 bits available |
@ -134,12 +135,14 @@ var (
// ----------------------------------------------------------------- // -----------------------------------------------------------------
// //
// 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[0] = 1
// n[1] = 2^23 // n[1] = 2^23
// n[2..9] = 0 // 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[9] * 2^(26*9) = 0 * 2^234 = 0
// n[8] * 2^(26*8) = 0 * 2^208 = 0 // n[8] * 2^(26*8) = 0 * 2^208 = 0
// ... // ...

View file

@ -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)

View file

@ -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) {

View file

@ -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"`

View file

@ -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,7 +47,7 @@ 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
@ -63,8 +63,8 @@ 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
@ -72,14 +72,14 @@ 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:

View file

@ -476,6 +476,7 @@ 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>--synopsis" Synopsis for the command
// "<method>-<lowerfieldname>" Description for each command argument // "<method>-<lowerfieldname>" Description for each command argument
// "<typename>-<lowerfieldname>" Description for each object field // "<typename>-<lowerfieldname>" Description for each object field
@ -492,6 +493,7 @@ 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:

View file

@ -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,7 +279,7 @@ 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"`
} }
@ -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,
@ -408,6 +412,7 @@ 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,7 +506,7 @@ 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"`
@ -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,11 +605,13 @@ 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,
} }
} }
@ -632,6 +620,7 @@ func NewSendManyCmd(fromAccount string, amounts map[string]float64, minConf *int
type SendToAddressCmd struct { type SendToAddressCmd struct {
Address string Address string
Amount float64 Amount float64
AddressType *string `jsonrpcdefault:"\"*\""`
Comment *string Comment *string
CommentTo *string CommentTo *string
} }
@ -641,30 +630,17 @@ 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,
AddressType: addrType,
Comment: comment, Comment: comment,
CommentTo: commentTo, 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,
}
}
// SetTxFeeCmd defines the settxfee JSON-RPC command. // SetTxFeeCmd defines the settxfee JSON-RPC command.
type SetTxFeeCmd struct { type SetTxFeeCmd struct {
Amount float64 // In BTC Amount float64 // In BTC
@ -872,6 +848,7 @@ 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
@ -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)

View file

@ -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("*"),
}, },
}, },
{ {
@ -346,6 +349,7 @@ func TestWalletSvrCmds(t *testing.T) {
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("*"),
}, },
}, },
{ {
@ -360,6 +364,7 @@ func TestWalletSvrCmds(t *testing.T) {
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"),
}, },
}, },
{ {
@ -397,6 +403,7 @@ 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"),
}, },
}, },
{ {
@ -423,6 +431,7 @@ 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),
}, },
}, },
@ -602,6 +611,7 @@ 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("*"),
}, },
}, },
{ {
@ -615,6 +625,7 @@ 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,12 +1183,13 @@ 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,
AddressType: btcjson.String("*"),
Comment: nil, Comment: nil,
CommentTo: nil, CommentTo: nil,
}, },
@ -1191,32 +1197,36 @@ func TestWalletSvrCmds(t *testing.T) {
{ {
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"),
}, },
}, },
{ {

View file

@ -53,7 +53,26 @@ type embeddedAddressInfo struct {
// 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 {

View file

@ -37,12 +37,10 @@ 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,
}, },
}, },
},
{ {
name: "GetAddressInfoResult - embedded ScriptType", name: "GetAddressInfoResult - embedded ScriptType",
result: `{"embedded": {"script":"nonstandard","address":"121313"}}`, result: `{"embedded": {"script":"nonstandard","address":"121313"}}`,

View file

@ -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)

View file

@ -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"),
}, },
}, },
{ {

View file

@ -794,6 +794,7 @@ 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 // SLIP-0132 : Registered HD version bytes for BIP-0032
// https://github.com/satoshilabs/slips/blob/master/slip-0132.md // https://github.com/satoshilabs/slips/blob/master/slip-0132.md
func RegisterHDKeyID(hdPublicKeyID []byte, hdPrivateKeyID []byte) error { func RegisterHDKeyID(hdPublicKeyID []byte, hdPrivateKeyID []byte) error {

View file

@ -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
View 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
}

View file

@ -17,6 +17,7 @@ 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) // ch(1B) hash(32B)

View file

@ -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...

View file

@ -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)
} }

View file

@ -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() n = n.Clone()
} else if height != nm.height {
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)
} }
} }

View file

@ -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

View file

@ -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)
} }
} }

View file

@ -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
}
} }
} }

View file

@ -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"`

View file

@ -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
View 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}

View file

@ -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).
@ -26,7 +26,7 @@ A quick overview of the features database provides are as follows:
- 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

View file

@ -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

View file

@ -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

View file

@ -318,6 +318,7 @@ func (iter *Iterator) ForceReseek() {
// unexpected keys and/or values. // unexpected keys and/or values.
// //
// For example: // For example:
//
// iter := t.Iterator(nil, nil) // iter := t.Iterator(nil, nil)
// for iter.Next() { // for iter.Next() {
// if someCondition { // if someCondition {

8
doc.go
View file

@ -18,9 +18,11 @@ 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: --addcheckpoint= Add a custom checkpoint. Format:
'<height>:<hash>' '<height>:<hash>'
-a, --addpeer= Add a peer to connect with at startup -a, --addpeer= Add a peer to connect with at startup
@ -102,9 +104,7 @@ Application Options:
server is disabled by default if no server is disabled by default if no
rpcuser/rpcpass or rpclimituser/rpclimitpass is rpcuser/rpcpass or rpclimituser/rpclimitpass is
specified specified
--notls Disable TLS for the RPC server -- NOTE: This is --notls Disable TLS for the RPC server
only allowed if the RPC server is bound to
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
@ -155,7 +155,7 @@ Application Options:
(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

View file

@ -11,7 +11,7 @@ two main goals:
(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
@ -72,13 +72,13 @@ Estimation stage:
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
View file

@ -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
View file

@ -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=

View file

@ -282,6 +282,7 @@ 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
@ -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)

View file

@ -95,15 +95,20 @@ func makeTestOutput(r *rpctest.Harness, t *testing.T,
// them. // them.
// //
// Overview: // Overview:
//
// - Pre soft-fork: // - Pre soft-fork:
//
// - Transactions with non-final lock-times from the PoV of MTP should be // - Transactions with non-final lock-times from the PoV of MTP should be
// rejected from the mempool. // rejected from the mempool.
//
// - Transactions within non-final MTP based lock-times should be accepted // - Transactions within non-final MTP based lock-times should be accepted
// in valid blocks. // in valid blocks.
// //
// - Post soft-fork: // - Post soft-fork:
//
// - Transactions with non-final lock-times from the PoV of MTP should be // - Transactions with non-final lock-times from the PoV of MTP should be
// rejected from the mempool and when found within otherwise valid blocks. // rejected from the mempool and when found within otherwise valid blocks.
//
// - Transactions with final lock-times from the PoV of MTP should be // - Transactions with final lock-times from the PoV of MTP should be
// accepted to the mempool and mined in future block. // accepted to the mempool and mined in future block.
func TestBIP0113Activation(t *testing.T) { func TestBIP0113Activation(t *testing.T) {

View file

@ -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, "",
) )

View file

@ -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

View file

@ -31,7 +31,7 @@ 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.
@ -64,7 +64,7 @@ be an exhaustive list.
- 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

View file

@ -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),
} }
} }

View file

@ -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
View 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
}

View file

@ -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

View file

@ -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,
}, },

View file

@ -27,6 +27,7 @@ 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} // {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp}) // ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger { func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {

View file

@ -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)
} }
} }

View file

@ -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
@ -50,7 +50,7 @@ A quick overview of the major features peer provides are as follows:
- 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)

View file

@ -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.
// //

View file

@ -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:
@ -159,7 +159,7 @@ detect if a command is unimplemented by the remote RPC server:
// 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:

View file

@ -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.

View 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}
}

View 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()
}

View 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
}

View file

@ -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) )
}
conn, err := net.DialTCP("tcp", nil, addr)
if err != nil {
return fmt.Errorf("can't dial tcp: %w", err)
}
defer conn.Close()
msg := fmt.Sprintf(`{"id":1,"method":"mining.update_block","params":[%q,%q,%q]}`,
stratumPass, coinid, blockHash)
_, err = conn.Write([]byte(msg))
if err != nil {
return fmt.Errorf("can't write message: %w", err)
}
return nil
}
func main() {
var ( var (
coinid = flag.String("coinid", "1425", "Coin ID") coinid = flag.String("coinid", "1425", "Coin ID")
stratum = flag.String("stratum", "lbrypool.net:3334", "Stratum server") stratumServer = flag.String("stratum", "", "Stratum server")
stratumPass = flag.String("stratumpass", "password", "Stratum server password") stratumPass = flag.String("stratumpass", "", "Stratum server password")
rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server") rpcserver = flag.String("rpcserver", "localhost:9245", "LBCD RPC server")
rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username") rpcuser = flag.String("rpcuser", "rpcuser", "LBCD RPC username")
rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password") rpcpass = flag.String("rpcpass", "rpcpass", "LBCD RPC password")
rpccert = flag.String("rpccert", defaultCert, "LBCD RPC certificate")
notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled") notls = flag.Bool("notls", false, "Connect to LBCD with TLS disabled")
run = flag.String("run", "", "Run custom shell command")
quiet = flag.Bool("quiet", false, "Do not print logs")
) )
func main() {
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)
}
},
}
// Connect to local lbcd RPC server using websockets.
lbcdHomeDir := lbcutil.AppDataDir("lbcd", false)
certs, err := ioutil.ReadFile(filepath.Join(lbcdHomeDir, "rpc.cert"))
if err != nil { if err != nil {
log.Fatalf("can't read lbcd certificate: %s", err) log.Fatalf("ERROR: %s not found: %s", cmd, err)
} }
connCfg := &rpcclient.ConnConfig{ b.customCmd = *run
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. // Start the eventt handler.
if err = client.NotifyBlocks(); err != nil { go b.start()
log.Fatalf("can't register block notification: %s", err)
}
log.Printf("NotifyBlocks: Registration Complete")
// Get the current block count. // Adaptater receives lbcd notifications, and emit events.
blockCount, err := client.GetBlockCount() adpt := adapter{b}
if err != nil {
log.Fatalf("can't get block count: %s", err) client := newLbcdClient(*rpcserver, *rpcuser, *rpcpass, *notls, adpt)
}
log.Printf("Block count: %d", blockCount) go func() {
err := <-b.errorc
log.Fatalf("ERROR: %s", err)
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).

View 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)
}

View file

@ -56,6 +56,7 @@ 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
// ************************ // ************************

View file

@ -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 {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCType,
Message: "Invalid amount",
}
}
// Decode the provided address. // Decode the provided address.
addr, err := btcutil.DecodeAddress(encodedAddr, params)
if err != nil {
return nil, &btcjson.RPCError{
Code: btcjson.ErrRPCInvalidAddressOrKey,
Message: "Invalid address or key: " + err.Error(),
}
}
// Ensure the address is one of the supported types and that // Ensure the address is one of the supported types and that
// the network encoded with the address matches the network the // the network encoded with the address matches the network the
// server is currently on. // 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{
Code: btcjson.ErrRPCType,
Message: "invalid amount",
}
}
addr, err := btcutil.DecodeAddress(encodedAddr, params)
if err != nil {
return nil, rpcInvalidAddressOrKeyError(encodedAddr,
"invalid address or key")
}
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

View file

@ -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",
@ -455,6 +497,11 @@ var helpDescsEnUS = map[string]string{
// 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)},

View file

@ -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),
} }

View file

@ -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)
} }
} }

View file

@ -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

View file

@ -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

View file

@ -37,6 +37,7 @@ 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 := txscript.NewScriptBuilder()
// builder.AddOp(txscript.OP_2).AddData(pubKey1).AddData(pubKey2) // builder.AddOp(txscript.OP_2).AddData(pubKey1).AddData(pubKey2)
// builder.AddData(pubKey3).AddOp(txscript.OP_3) // builder.AddData(pubKey3).AddOp(txscript.OP_3)

View file

@ -89,6 +89,7 @@ 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 -> [0x7f]
// -127 -> [0xff] // -127 -> [0xff]
// 128 -> [0x80 0x00] // 128 -> [0x80 0x00]

View file

@ -139,8 +139,11 @@ func (v parsedVersion) buildmeta() string {
} }
func (v parsedVersion) full() string { func (v parsedVersion) full() string {
if len(v.prerelease) > 0 {
return fmt.Sprintf("%s-%s+%s", v.version, v.prerelease, v.buildmeta()) 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)

View file

@ -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:

View file

@ -114,12 +114,14 @@ 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 │ ... │ // │ VERSION │ FLAG │ TX-IN-COUNT │ ... │
// │ 4 bytes │ 2 bytes (optional) │ varint │ │ // │ 4 bytes │ 2 bytes (optional) │ varint │ │
// └─────────┴────────────────────┴─────────────┴─────┘ // └─────────┴────────────────────┴─────────────┴─────┘
// //
// Zooming into the FLAG field: // Zooming into the FLAG field:
//
// ┌── FLAG ─────────────┬────────┐ // ┌── FLAG ─────────────┬────────┐
// │ TxFlagMarker (0x00) │ TxFlag │ // │ TxFlagMarker (0x00) │ TxFlag │
// │ 1 byte │ 1 byte │ // │ 1 byte │ 1 byte │