Compare commits
88 commits
master
...
pruned_ram
Author | SHA1 | Date | |
---|---|---|---|
|
e056f8ac23 | ||
|
742881a29a | ||
|
55abc9d25c | ||
|
dc230888ef | ||
|
ba05456830 | ||
|
07d25ba7ce | ||
|
d884f96553 | ||
|
eb165fb359 | ||
|
52f41459d5 | ||
|
3910319e80 | ||
|
db25b9b11b | ||
|
6009f9048c | ||
|
fa9239e0c5 | ||
|
edfffd979d | ||
|
02f76b2e8e | ||
|
9ebe440325 | ||
|
a4cc516896 | ||
|
690954d843 | ||
|
035ff2a799 | ||
|
79d7e65a35 | ||
|
103ab86904 | ||
|
cee79f6635 | ||
|
96f3d421df | ||
|
3a046d153c | ||
|
a56581d967 | ||
|
3c4900275c | ||
|
9d3279f215 | ||
|
40fe74adb8 | ||
|
ac1fdda8a0 | ||
|
79e392b85d | ||
|
5588af7d5b | ||
|
83b6a8dc18 | ||
|
1ba1a10e59 | ||
|
a51be72029 | ||
|
79987722bb | ||
|
1834d95b43 | ||
|
9c9bd3e3a5 | ||
|
8a5e74ebe6 | ||
|
7154e57d49 | ||
|
bf86d03f84 | ||
|
d2f8ad7657 | ||
|
ca18f3e8a2 | ||
|
0be18205c8 | ||
|
61de064575 | ||
|
e4c637b02a | ||
|
d83eaa4fed | ||
|
c7285883a6 | ||
|
fe1ce376d8 | ||
|
d19bcd60db | ||
|
7414ad7b54 | ||
|
c8cd97fe4d | ||
|
a06875fe46 | ||
|
5f872b37cf | ||
|
9af0566433 | ||
|
aa518c59bb | ||
|
1402e61807 | ||
|
a061c31208 | ||
|
983e4c6000 | ||
|
a310f8b598 | ||
|
ed2c764c9d | ||
|
4b8bd4c238 | ||
|
9080abc2c6 | ||
|
0224bf295b | ||
|
8e059c14d7 | ||
|
9d0dfbe87e | ||
|
62479740ae | ||
|
1895c9069f | ||
|
9caab1e2e1 | ||
|
0a22e00498 | ||
|
866b0cb610 | ||
|
3d9d9b1912 | ||
|
27b01f77b3 | ||
|
7d412b4e2f | ||
|
a6189cb439 | ||
|
1264d4581d | ||
|
ba7266c20c | ||
|
dce6d65452 | ||
|
43bed18649 | ||
|
b704fef973 | ||
|
5b72ef6719 | ||
|
f535c88f4a | ||
|
eb3bde09f2 | ||
|
c65dd82284 | ||
|
b7d3e11250 | ||
|
31ce21deb6 | ||
|
0e5bf1c24f | ||
|
61c0f0df99 | ||
|
82141c408c |
260 changed files with 6497 additions and 52268 deletions
7
.github/workflows/basic-check.yml
vendored
7
.github/workflows/basic-check.yml
vendored
|
@ -2,14 +2,11 @@ name: Build and Test
|
|||
on: [push, pull_request]
|
||||
jobs:
|
||||
build:
|
||||
# https://github.blog/changelog/2021-04-20-github-actions-control-permissions-for-github_token/
|
||||
permissions:
|
||||
contents: read
|
||||
name: Go CI
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.19]
|
||||
go: [1.16]
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
|
|
58
.github/workflows/create-release.yml
vendored
Normal file
58
.github/workflows/create-release.yml
vendored
Normal file
|
@ -0,0 +1,58 @@
|
|||
name: Create release
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
note:
|
||||
description: 'Note'
|
||||
required: false
|
||||
default: ''
|
||||
|
||||
jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.16]
|
||||
os: [linux, darwin, windows]
|
||||
ar: [amd64, arm64]
|
||||
exclude:
|
||||
- go: 1.16
|
||||
os: windows
|
||||
ar: arm64
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v2
|
||||
- name: Build executables
|
||||
env:
|
||||
GOOS: ${{ matrix.os }}
|
||||
GOARCH: ${{ matrix.ar }}
|
||||
CGO_ENABLED: 0
|
||||
run: |
|
||||
go build -trimpath -ldflags="-s -w -buildid=" -v -o artifacts/ .
|
||||
go build -trimpath -ldflags="-s -w -buildid=" -v -o artifacts/ ./cmd/lbcctl/
|
||||
- name: SHA256 sum
|
||||
run: sha256sum -b artifacts/* > artifacts/lbcd.sha256
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lbcd-${{ matrix.os }}-${{ matrix.ar }}
|
||||
path: artifacts/*
|
||||
|
||||
|
||||
# for releases see https://trstringer.com/github-actions-create-release-upload-artifacts/
|
||||
|
||||
# AWS S3 support:
|
||||
# - name: Upload to Amazon S3
|
||||
# uses: ItsKarma/aws-cli@v1.70.0
|
||||
# with:
|
||||
# args: s3 sync .release s3://my-bucket-name
|
||||
# env:
|
||||
# # Make sure to add the secrets in the repo settings page
|
||||
# # AWS_REGION is set to us-east-1 by default
|
||||
# AWS_ACCESS_KEY_ID: $
|
||||
# AWS_SECRET_ACCESS_KEY: $
|
||||
# AWS_REGION: us-east-1
|
4
.github/workflows/full-sync-part-1.yml
vendored
4
.github/workflows/full-sync-part-1.yml
vendored
|
@ -14,7 +14,7 @@ jobs:
|
|||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.19]
|
||||
go: [1.16]
|
||||
steps:
|
||||
- run: |
|
||||
echo "Note ${{ github.event.inputs.note }}!"
|
||||
|
@ -29,7 +29,7 @@ jobs:
|
|||
- name: Create datadir
|
||||
run: echo "TEMP_DATA_DIR=$(mktemp -d)" >> $GITHUB_ENV
|
||||
- name: Run lbcd
|
||||
run: ./lbcd --datadir=${{env.TEMP_DATA_DIR}}/data --logdir=${{env.TEMP_DATA_DIR}}/logs --nolisten --norpc
|
||||
run: ./lbcd --datadir=${{env.TEMP_DATA_DIR}}/data --logdir=${{env.TEMP_DATA_DIR}}/logs --connect=127.0.0.1 --norpc
|
||||
- name: Remove datadir
|
||||
if: always()
|
||||
run: rm -rf ${{env.TEMP_DATA_DIR}}
|
||||
|
|
6
.github/workflows/full-sync-part-2.yml
vendored
6
.github/workflows/full-sync-part-2.yml
vendored
|
@ -14,11 +14,11 @@ jobs:
|
|||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
go: [1.19]
|
||||
go: [1.16]
|
||||
steps:
|
||||
- run: |
|
||||
echo "Note ${{ github.event.inputs.note }}!"
|
||||
- name: Setup Go
|
||||
- name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: ${{ matrix.go }}
|
||||
|
@ -31,7 +31,7 @@ jobs:
|
|||
- name: Copy initial data
|
||||
run: cp -r /home/lbry/lbcd_814k/* ${{env.TEMP_DATA_DIR}}
|
||||
- name: Run lbcd
|
||||
run: ./lbcd --datadir=${{env.TEMP_DATA_DIR}}/data --logdir=${{env.TEMP_DATA_DIR}}/logs --nolisten --norpc
|
||||
run: ./lbcd --datadir=${{env.TEMP_DATA_DIR}}/data --logdir=${{env.TEMP_DATA_DIR}}/logs --connect=127.0.0.1 --norpc
|
||||
- name: Remove datadir
|
||||
if: always()
|
||||
run: rm -rf ${{env.TEMP_DATA_DIR}}
|
||||
|
|
2
.github/workflows/golangci-lint.yml
vendored
2
.github/workflows/golangci-lint.yml
vendored
|
@ -4,7 +4,7 @@ env:
|
|||
# go needs absolute directories, using the $HOME variable doesn't work here.
|
||||
GOCACHE: /home/runner/work/go/pkg/build
|
||||
GOPATH: /home/runner/work/go
|
||||
GO_VERSION: '^1.19'
|
||||
GO_VERSION: '^1.17.0'
|
||||
|
||||
on:
|
||||
push:
|
||||
|
|
57
.github/workflows/release.yml
vendored
57
.github/workflows/release.yml
vendored
|
@ -1,57 +0,0 @@
|
|||
name: goreleaser
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
note:
|
||||
description: 'Note'
|
||||
required: false
|
||||
default: ''
|
||||
pull_request:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
-
|
||||
name: Set up Go
|
||||
uses: actions/setup-go@v2
|
||||
with:
|
||||
go-version: 1.19
|
||||
|
||||
# Login against a Docker registry except on PR
|
||||
# https://github.com/docker/login-action
|
||||
- name: Log into registry docker.io
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_HUB_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
|
||||
-
|
||||
name: Run GoReleaser
|
||||
uses: goreleaser/goreleaser-action@v2
|
||||
with:
|
||||
distribution: goreleaser
|
||||
version: latest
|
||||
args: release --rm-dist
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Upload artifacts
|
||||
uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: lbcd-${{ github.sha }}
|
||||
path: |
|
||||
dist/checksums.txt
|
||||
dist/*.tar.gz
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -49,7 +49,3 @@ btcd
|
|||
btcctl
|
||||
lbcd
|
||||
lbcctl
|
||||
|
||||
# CI artifacts
|
||||
dist
|
||||
debug
|
||||
|
|
|
@ -1,68 +0,0 @@
|
|||
# This is an example .goreleaser.yml file with some sensible defaults.
|
||||
# Make sure to check the documentation at https://goreleaser.com
|
||||
before:
|
||||
hooks:
|
||||
# You may remove this if you don't use go modules.
|
||||
- go mod tidy
|
||||
# you may remove this if you don't need go generate
|
||||
- go generate ./...
|
||||
builds:
|
||||
-
|
||||
main: .
|
||||
id: "lbcd"
|
||||
binary: "lbcd"
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -buildid=
|
||||
- -X github.com/lbryio/lbcd/version.appTag={{ .Tag }}
|
||||
targets:
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- darwin_amd64
|
||||
- darwin_arm64
|
||||
- windows_amd64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
-
|
||||
main: ./cmd/lbcctl
|
||||
id: "lbcctl"
|
||||
binary: "lbcctl"
|
||||
flags:
|
||||
- -trimpath
|
||||
ldflags:
|
||||
- -s -w
|
||||
- -buildid=
|
||||
- -X github.com/lbryio/lbcd/version.appTag={{ .Tag }}
|
||||
env:
|
||||
- CGO_ENABLED=0
|
||||
targets:
|
||||
- linux_amd64
|
||||
- linux_arm64
|
||||
- darwin_amd64
|
||||
- darwin_arm64
|
||||
- windows_amd64
|
||||
mod_timestamp: '{{ .CommitTimestamp }}'
|
||||
checksum:
|
||||
name_template: 'checksums.txt'
|
||||
snapshot:
|
||||
name_template: "{{ .Version }}+{{ .Commit }}"
|
||||
changelog:
|
||||
sort: asc
|
||||
filters:
|
||||
exclude:
|
||||
- '^docs:'
|
||||
- '^test:'
|
||||
|
||||
dockers:
|
||||
- use: buildx
|
||||
dockerfile: Dockerfile.goreleaser
|
||||
image_templates:
|
||||
- "docker.io/lbry/lbcd:{{ .Tag }}"
|
||||
- "docker.io/lbry/lbcd:latest"
|
||||
|
||||
release:
|
||||
draft: true
|
||||
prerelease: auto
|
18
Dockerfile
18
Dockerfile
|
@ -1,35 +1,35 @@
|
|||
# This Dockerfile builds lbcd from source and creates a small (55 MB) docker container based on alpine linux.
|
||||
# This Dockerfile builds btcd from source and creates a small (55 MB) docker container based on alpine linux.
|
||||
#
|
||||
# Clone this repository and run the following command to build and tag a fresh lbcd amd64 container:
|
||||
# Clone this repository and run the following command to build and tag a fresh btcd amd64 container:
|
||||
#
|
||||
# docker build . -t yourregistry/lbcd
|
||||
# docker build . -t yourregistry/btcd
|
||||
#
|
||||
# You can use the following command to buid an arm64v8 container:
|
||||
#
|
||||
# docker build . -t yourregistry/lbcd --build-arg ARCH=arm64v8
|
||||
# docker build . -t yourregistry/btcd --build-arg ARCH=arm64v8
|
||||
#
|
||||
# For more information how to use this docker image visit:
|
||||
# https://github.com/lbryio/lbcd/tree/master/docs
|
||||
#
|
||||
# 9246 Mainnet LBRY peer-to-peer port
|
||||
# 9245 Mainet RPC port
|
||||
# 8333 Mainnet Bitcoin peer-to-peer port
|
||||
# 8334 Mainet RPC port
|
||||
|
||||
ARG ARCH=amd64
|
||||
|
||||
FROM golang:1.19 AS build-container
|
||||
FROM golang:1.16-alpine3.14 AS build-container
|
||||
|
||||
ARG ARCH
|
||||
ENV GO111MODULE=on
|
||||
|
||||
ADD . /app
|
||||
WORKDIR /app
|
||||
RUN set -ex \
|
||||
&& if [ "${ARCH}" = "amd64" ]; then export GOARCH=amd64; fi \
|
||||
&& if [ "${ARCH}" = "arm32v7" ]; then export GOARCH=arm; fi \
|
||||
&& if [ "${ARCH}" = "arm64v8" ]; then export GOARCH=arm64; fi \
|
||||
&& echo "Compiling for $GOARCH" \
|
||||
&& go install -v . ./cmd/...
|
||||
|
||||
FROM $ARCH/debian:bullseye-20220418-slim
|
||||
FROM $ARCH/alpine:3.14
|
||||
|
||||
COPY --from=build-container /go/bin /bin
|
||||
|
||||
|
|
|
@ -1,9 +0,0 @@
|
|||
FROM debian:bullseye-20220418-slim
|
||||
|
||||
COPY lbcd lbcctl /bin/
|
||||
|
||||
VOLUME ["/root/.lbcd"]
|
||||
|
||||
EXPOSE 9245 9246
|
||||
|
||||
ENTRYPOINT ["lbcd"]
|
349
README.md
349
README.md
|
@ -1,321 +1,66 @@
|
|||
# lbcd
|
||||
lbcd
|
||||
====
|
||||
|
||||
[](https://github.com/lbryio/lbcd/actions)
|
||||
[](https://coveralls.io/github/lbryio/lbcd?branch=master)
|
||||
[](http://copyfree.org)
|
||||
<!--[](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).
|
||||
|
||||
Software stack developed by LBRY teams has been all migrated to **lbcd**.
|
||||
This project is currently under active development and is in a Beta state while 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.
|
||||
|
||||
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
|
||||
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).
|
||||
|
||||
## 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
|
||||
|
||||
We take security seriously. Please contact [security](mailto:security@lbry.com) regarding any security issues.
|
||||
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,
|
||||
and soft/hard forks. To join, visit [fork list](https://lbry.com/forklist)
|
||||
and soft/hard forks. To join, visit 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 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).
|
||||
- Use your favorite git tool to acquire the lbcd source.
|
||||
- lbcd has no non-Go dependencies; it can be built by simply running `go build .`
|
||||
- lbcctl can be built similarly:
|
||||
|
||||
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 `~/.lbcd/`
|
||||
|
||||
To enable RPC access a username and password is required. Example:
|
||||
```
|
||||
./lbcd --notls --rpcuser=x --rpcpass=y --txindex &
|
||||
./lbcctl --notls --rpcuser=x --rpcpass=y getblocktemplate
|
||||
```
|
||||
<!-- TODO: explain how to use TLS certificates. -->
|
||||
|
||||
## Contributing
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ type AddrManager struct {
|
|||
nTried int
|
||||
nNew int
|
||||
lamtx sync.Mutex
|
||||
localAddresses map[string]*LocalAddress
|
||||
localAddresses map[string]*localAddress
|
||||
version int
|
||||
}
|
||||
|
||||
|
@ -69,9 +69,9 @@ type serializedAddrManager struct {
|
|||
TriedBuckets [triedBucketCount][]string
|
||||
}
|
||||
|
||||
type LocalAddress struct {
|
||||
NA *wire.NetAddress
|
||||
Score AddressPriority
|
||||
type localAddress struct {
|
||||
na *wire.NetAddress
|
||||
score AddressPriority
|
||||
}
|
||||
|
||||
// AddressPriority type is used to describe the hierarchy of local address
|
||||
|
@ -176,9 +176,9 @@ func (a *AddrManager) updateAddress(netAddr, srcAddr *wire.NetAddress) {
|
|||
// TODO: only update addresses periodically.
|
||||
// Update the last seen time and services.
|
||||
// note that to prevent causing excess garbage on getaddr
|
||||
// messages the netaddresses in addrmanager are *immutable*,
|
||||
// messages the netaddresses in addrmaanger are *immutable*,
|
||||
// if we need to change them then we replace the pointer with a
|
||||
// new copy so that we don't have to copy every NA for getaddr.
|
||||
// new copy so that we don't have to copy every na for getaddr.
|
||||
if netAddr.Timestamp.After(ka.na.Timestamp) ||
|
||||
(ka.na.Services&netAddr.Services) !=
|
||||
netAddr.Services {
|
||||
|
@ -186,9 +186,7 @@ func (a *AddrManager) updateAddress(netAddr, srcAddr *wire.NetAddress) {
|
|||
naCopy := *ka.na
|
||||
naCopy.Timestamp = netAddr.Timestamp
|
||||
naCopy.AddService(netAddr.Services)
|
||||
ka.mtx.Lock()
|
||||
ka.na = &naCopy
|
||||
ka.mtx.Unlock()
|
||||
}
|
||||
|
||||
// If already in tried, we have nothing to do here.
|
||||
|
@ -755,7 +753,7 @@ func (a *AddrManager) HostToNetAddress(host string, port uint16, services wire.S
|
|||
// the relevant .onion address.
|
||||
func ipString(na *wire.NetAddress) string {
|
||||
if IsOnionCatTor(na) {
|
||||
// We know now that NA.IP is long enough.
|
||||
// We know now that na.IP is long enough.
|
||||
base32 := base32.StdEncoding.EncodeToString(na.IP[6:])
|
||||
return strings.ToLower(base32) + ".onion"
|
||||
}
|
||||
|
@ -859,11 +857,8 @@ func (a *AddrManager) Attempt(addr *wire.NetAddress) {
|
|||
return
|
||||
}
|
||||
// set last tried time to now
|
||||
now := time.Now()
|
||||
ka.mtx.Lock()
|
||||
ka.attempts++
|
||||
ka.lastattempt = now
|
||||
ka.mtx.Unlock()
|
||||
ka.lastattempt = time.Now()
|
||||
}
|
||||
|
||||
// Connected Marks the given address as currently connected and working at the
|
||||
|
@ -882,12 +877,10 @@ func (a *AddrManager) Connected(addr *wire.NetAddress) {
|
|||
// so.
|
||||
now := time.Now()
|
||||
if now.After(ka.na.Timestamp.Add(time.Minute * 20)) {
|
||||
// ka.NA is immutable, so replace it.
|
||||
// ka.na is immutable, so replace it.
|
||||
naCopy := *ka.na
|
||||
naCopy.Timestamp = time.Now()
|
||||
ka.mtx.Lock()
|
||||
ka.na = &naCopy
|
||||
ka.mtx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -906,13 +899,11 @@ func (a *AddrManager) Good(addr *wire.NetAddress) {
|
|||
// ka.Timestamp is not updated here to avoid leaking information
|
||||
// about currently connected peers.
|
||||
now := time.Now()
|
||||
ka.mtx.Lock()
|
||||
ka.lastsuccess = now
|
||||
ka.lastattempt = now
|
||||
ka.attempts = 0
|
||||
ka.mtx.Unlock() // tried and refs synchronized via a.mtx
|
||||
|
||||
// move to tried set, optionally evicting other addresses if need.
|
||||
// move to tried set, optionally evicting other addresses if neeed.
|
||||
if ka.tried {
|
||||
return
|
||||
}
|
||||
|
@ -994,16 +985,14 @@ func (a *AddrManager) SetServices(addr *wire.NetAddress, services wire.ServiceFl
|
|||
|
||||
// Update the services if needed.
|
||||
if ka.na.Services != services {
|
||||
// ka.NA is immutable, so replace it.
|
||||
// ka.na is immutable, so replace it.
|
||||
naCopy := *ka.na
|
||||
naCopy.Services = services
|
||||
ka.mtx.Lock()
|
||||
ka.na = &naCopy
|
||||
ka.mtx.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// AddLocalAddress adds NA to the list of known local addresses to advertise
|
||||
// AddLocalAddress adds na to the list of known local addresses to advertise
|
||||
// with the given priority.
|
||||
func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPriority) error {
|
||||
if !IsRoutable(na) {
|
||||
|
@ -1015,13 +1004,13 @@ func (a *AddrManager) AddLocalAddress(na *wire.NetAddress, priority AddressPrior
|
|||
|
||||
key := NetAddressKey(na)
|
||||
la, ok := a.localAddresses[key]
|
||||
if !ok || la.Score < priority {
|
||||
if !ok || la.score < priority {
|
||||
if ok {
|
||||
la.Score = priority + 1
|
||||
la.score = priority + 1
|
||||
} else {
|
||||
a.localAddresses[key] = &LocalAddress{
|
||||
NA: na,
|
||||
Score: priority,
|
||||
a.localAddresses[key] = &localAddress{
|
||||
na: na,
|
||||
score: priority,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1117,12 +1106,12 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.Net
|
|||
var bestscore AddressPriority
|
||||
var bestAddress *wire.NetAddress
|
||||
for _, la := range a.localAddresses {
|
||||
reach := getReachabilityFrom(la.NA, remoteAddr)
|
||||
reach := getReachabilityFrom(la.na, remoteAddr)
|
||||
if reach > bestreach ||
|
||||
(reach == bestreach && la.Score > bestscore) {
|
||||
(reach == bestreach && la.score > bestscore) {
|
||||
bestreach = reach
|
||||
bestscore = la.Score
|
||||
bestAddress = la.NA
|
||||
bestscore = la.score
|
||||
bestAddress = la.na
|
||||
}
|
||||
}
|
||||
if bestAddress != nil {
|
||||
|
@ -1146,15 +1135,6 @@ func (a *AddrManager) GetBestLocalAddress(remoteAddr *wire.NetAddress) *wire.Net
|
|||
return bestAddress
|
||||
}
|
||||
|
||||
// LocalAddresses returns the list of local addresses for our node.
|
||||
func (a *AddrManager) LocalAddresses() []*LocalAddress {
|
||||
var addrs []*LocalAddress
|
||||
for _, addr := range a.localAddresses {
|
||||
addrs = append(addrs, addr)
|
||||
}
|
||||
return addrs
|
||||
}
|
||||
|
||||
// New returns a new bitcoin address manager.
|
||||
// Use Start to begin processing asynchronous address updates.
|
||||
func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager {
|
||||
|
@ -1163,7 +1143,7 @@ func New(dataDir string, lookupFunc func(string) ([]net.IP, error)) *AddrManager
|
|||
lookupFunc: lookupFunc,
|
||||
rand: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||
quit: make(chan struct{}),
|
||||
localAddresses: make(map[string]*LocalAddress),
|
||||
localAddresses: make(map[string]*localAddress),
|
||||
version: serialisationVersion,
|
||||
}
|
||||
am.reset()
|
||||
|
|
|
@ -34,61 +34,61 @@ var someIP = "173.194.115.66"
|
|||
func addNaTests() {
|
||||
// IPv4
|
||||
// Localhost
|
||||
addNaTest("127.0.0.1", 9244, "127.0.0.1:9244")
|
||||
addNaTest("127.0.0.1", 9245, "127.0.0.1:9245")
|
||||
addNaTest("127.0.0.1", 8333, "127.0.0.1:8333")
|
||||
addNaTest("127.0.0.1", 8334, "127.0.0.1:8334")
|
||||
|
||||
// Class A
|
||||
addNaTest("1.0.0.1", 9244, "1.0.0.1:9244")
|
||||
addNaTest("2.2.2.2", 9245, "2.2.2.2:9245")
|
||||
addNaTest("27.253.252.251", 9246, "27.253.252.251:9246")
|
||||
addNaTest("123.3.2.1", 9247, "123.3.2.1:9247")
|
||||
addNaTest("1.0.0.1", 8333, "1.0.0.1:8333")
|
||||
addNaTest("2.2.2.2", 8334, "2.2.2.2:8334")
|
||||
addNaTest("27.253.252.251", 8335, "27.253.252.251:8335")
|
||||
addNaTest("123.3.2.1", 8336, "123.3.2.1:8336")
|
||||
|
||||
// Private Class A
|
||||
addNaTest("10.0.0.1", 9244, "10.0.0.1:9244")
|
||||
addNaTest("10.1.1.1", 9245, "10.1.1.1:9245")
|
||||
addNaTest("10.2.2.2", 9246, "10.2.2.2:9246")
|
||||
addNaTest("10.10.10.10", 9247, "10.10.10.10:9247")
|
||||
addNaTest("10.0.0.1", 8333, "10.0.0.1:8333")
|
||||
addNaTest("10.1.1.1", 8334, "10.1.1.1:8334")
|
||||
addNaTest("10.2.2.2", 8335, "10.2.2.2:8335")
|
||||
addNaTest("10.10.10.10", 8336, "10.10.10.10:8336")
|
||||
|
||||
// Class B
|
||||
addNaTest("128.0.0.1", 9244, "128.0.0.1:9244")
|
||||
addNaTest("129.1.1.1", 9245, "129.1.1.1:9245")
|
||||
addNaTest("180.2.2.2", 9246, "180.2.2.2:9246")
|
||||
addNaTest("191.10.10.10", 9247, "191.10.10.10:9247")
|
||||
addNaTest("128.0.0.1", 8333, "128.0.0.1:8333")
|
||||
addNaTest("129.1.1.1", 8334, "129.1.1.1:8334")
|
||||
addNaTest("180.2.2.2", 8335, "180.2.2.2:8335")
|
||||
addNaTest("191.10.10.10", 8336, "191.10.10.10:8336")
|
||||
|
||||
// Private Class B
|
||||
addNaTest("172.16.0.1", 9244, "172.16.0.1:9244")
|
||||
addNaTest("172.16.1.1", 9245, "172.16.1.1:9245")
|
||||
addNaTest("172.16.2.2", 9246, "172.16.2.2:9246")
|
||||
addNaTest("172.16.172.172", 9247, "172.16.172.172:9247")
|
||||
addNaTest("172.16.0.1", 8333, "172.16.0.1:8333")
|
||||
addNaTest("172.16.1.1", 8334, "172.16.1.1:8334")
|
||||
addNaTest("172.16.2.2", 8335, "172.16.2.2:8335")
|
||||
addNaTest("172.16.172.172", 8336, "172.16.172.172:8336")
|
||||
|
||||
// Class C
|
||||
addNaTest("193.0.0.1", 9244, "193.0.0.1:9244")
|
||||
addNaTest("200.1.1.1", 9245, "200.1.1.1:9245")
|
||||
addNaTest("205.2.2.2", 9246, "205.2.2.2:9246")
|
||||
addNaTest("223.10.10.10", 9247, "223.10.10.10:9247")
|
||||
addNaTest("193.0.0.1", 8333, "193.0.0.1:8333")
|
||||
addNaTest("200.1.1.1", 8334, "200.1.1.1:8334")
|
||||
addNaTest("205.2.2.2", 8335, "205.2.2.2:8335")
|
||||
addNaTest("223.10.10.10", 8336, "223.10.10.10:8336")
|
||||
|
||||
// Private Class C
|
||||
addNaTest("192.168.0.1", 9244, "192.168.0.1:9244")
|
||||
addNaTest("192.168.1.1", 9245, "192.168.1.1:9245")
|
||||
addNaTest("192.168.2.2", 9246, "192.168.2.2:9246")
|
||||
addNaTest("192.168.192.192", 9247, "192.168.192.192:9247")
|
||||
addNaTest("192.168.0.1", 8333, "192.168.0.1:8333")
|
||||
addNaTest("192.168.1.1", 8334, "192.168.1.1:8334")
|
||||
addNaTest("192.168.2.2", 8335, "192.168.2.2:8335")
|
||||
addNaTest("192.168.192.192", 8336, "192.168.192.192:8336")
|
||||
|
||||
// IPv6
|
||||
// Localhost
|
||||
addNaTest("::1", 9244, "[::1]:9244")
|
||||
addNaTest("fe80::1", 9245, "[fe80::1]:9245")
|
||||
addNaTest("::1", 8333, "[::1]:8333")
|
||||
addNaTest("fe80::1", 8334, "[fe80::1]:8334")
|
||||
|
||||
// Link-local
|
||||
addNaTest("fe80::1:1", 9244, "[fe80::1:1]:9244")
|
||||
addNaTest("fe91::2:2", 9245, "[fe91::2:2]:9245")
|
||||
addNaTest("fea2::3:3", 9246, "[fea2::3:3]:9246")
|
||||
addNaTest("feb3::4:4", 9247, "[feb3::4:4]:9247")
|
||||
addNaTest("fe80::1:1", 8333, "[fe80::1:1]:8333")
|
||||
addNaTest("fe91::2:2", 8334, "[fe91::2:2]:8334")
|
||||
addNaTest("fea2::3:3", 8335, "[fea2::3:3]:8335")
|
||||
addNaTest("feb3::4:4", 8336, "[feb3::4:4]:8336")
|
||||
|
||||
// Site-local
|
||||
addNaTest("fec0::1:1", 9244, "[fec0::1:1]:9244")
|
||||
addNaTest("fed1::2:2", 9245, "[fed1::2:2]:9245")
|
||||
addNaTest("fee2::3:3", 9246, "[fee2::3:3]:9246")
|
||||
addNaTest("fef3::4:4", 9247, "[fef3::4:4]:9247")
|
||||
addNaTest("fec0::1:1", 8333, "[fec0::1:1]:8333")
|
||||
addNaTest("fed1::2:2", 8334, "[fed1::2:2]:8334")
|
||||
addNaTest("fee2::3:3", 8335, "[fee2::3:3]:8335")
|
||||
addNaTest("fef3::4:4", 8336, "[fef3::4:4]:8336")
|
||||
}
|
||||
|
||||
func addNaTest(ip string, port uint16, want string) {
|
||||
|
@ -119,7 +119,7 @@ func TestAddAddressByIP(t *testing.T) {
|
|||
err error
|
||||
}{
|
||||
{
|
||||
someIP + ":9244",
|
||||
someIP + ":8333",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
|
@ -127,7 +127,7 @@ func TestAddAddressByIP(t *testing.T) {
|
|||
addrErr,
|
||||
},
|
||||
{
|
||||
someIP[:12] + ":9244",
|
||||
someIP[:12] + ":8333",
|
||||
fmtErr,
|
||||
},
|
||||
{
|
||||
|
@ -212,7 +212,7 @@ func TestAttempt(t *testing.T) {
|
|||
n := addrmgr.New("testattempt", lookupFunc)
|
||||
|
||||
// Add a new address and get it
|
||||
err := n.AddAddressByIP(someIP + ":9244")
|
||||
err := n.AddAddressByIP(someIP + ":8333")
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
|
@ -234,7 +234,7 @@ func TestConnected(t *testing.T) {
|
|||
n := addrmgr.New("testconnected", lookupFunc)
|
||||
|
||||
// Add a new address and get it
|
||||
err := n.AddAddressByIP(someIP + ":9244")
|
||||
err := n.AddAddressByIP(someIP + ":8333")
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
|
@ -261,14 +261,14 @@ func TestNeedMoreAddresses(t *testing.T) {
|
|||
|
||||
var err error
|
||||
for i := 0; i < addrsToAdd; i++ {
|
||||
s := fmt.Sprintf("%d.%d.173.147:9244", i/128+60, i%128+60)
|
||||
s := fmt.Sprintf("%d.%d.173.147:8333", i/128+60, i%128+60)
|
||||
addrs[i], err = n.DeserializeNetAddress(s, wire.SFNodeNetwork)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 9244, 0)
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
n.AddAddresses(addrs, srcAddr)
|
||||
numAddrs := n.NumAddresses()
|
||||
|
@ -289,14 +289,14 @@ func TestGood(t *testing.T) {
|
|||
|
||||
var err error
|
||||
for i := 0; i < addrsToAdd; i++ {
|
||||
s := fmt.Sprintf("%d.173.147.%d:9244", i/64+60, i%64+60)
|
||||
s := fmt.Sprintf("%d.173.147.%d:8333", i/64+60, i%64+60)
|
||||
addrs[i], err = n.DeserializeNetAddress(s, wire.SFNodeNetwork)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to turn %s into an address: %v", s, err)
|
||||
}
|
||||
}
|
||||
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 9244, 0)
|
||||
srcAddr := wire.NewNetAddressIPPort(net.IPv4(173, 144, 173, 111), 8333, 0)
|
||||
|
||||
n.AddAddresses(addrs, srcAddr)
|
||||
for _, addr := range addrs {
|
||||
|
@ -323,7 +323,7 @@ func TestGetAddress(t *testing.T) {
|
|||
}
|
||||
|
||||
// Add a new address and get it
|
||||
err := n.AddAddressByIP(someIP + ":9244")
|
||||
err := n.AddAddressByIP(someIP + ":8333")
|
||||
if err != nil {
|
||||
t.Fatalf("Adding address failed: %v", err)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
/*
|
||||
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
|
||||
of addresses to connect to as nodes come and go. The Bitcoin protocol provides
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package addrmgr
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
|
@ -14,7 +13,6 @@ import (
|
|||
// KnownAddress tracks information about a known network address that is used
|
||||
// to determine how viable an address is.
|
||||
type KnownAddress struct {
|
||||
mtx sync.RWMutex // na and lastattempt
|
||||
na *wire.NetAddress
|
||||
srcAddr *wire.NetAddress
|
||||
attempts int
|
||||
|
@ -27,28 +25,19 @@ type KnownAddress struct {
|
|||
// NetAddress returns the underlying wire.NetAddress associated with the
|
||||
// known address.
|
||||
func (ka *KnownAddress) NetAddress() *wire.NetAddress {
|
||||
ka.mtx.RLock()
|
||||
defer ka.mtx.RUnlock()
|
||||
return ka.na
|
||||
}
|
||||
|
||||
// LastAttempt returns the last time the known address was attempted.
|
||||
func (ka *KnownAddress) LastAttempt() time.Time {
|
||||
ka.mtx.RLock()
|
||||
defer ka.mtx.RUnlock()
|
||||
return ka.lastattempt
|
||||
}
|
||||
|
||||
// Services returns the services supported by the peer with the known address.
|
||||
func (ka *KnownAddress) Services() wire.ServiceFlag {
|
||||
ka.mtx.RLock()
|
||||
defer ka.mtx.RUnlock()
|
||||
return ka.na.Services
|
||||
}
|
||||
|
||||
// The unexported methods, chance and isBad, are used from within AddrManager
|
||||
// where KnownAddress field access is synchronized via it's own Mutex.
|
||||
|
||||
// chance returns the selection probability for a known address. The priority
|
||||
// depends upon how recently the address has been seen, how recently it was last
|
||||
// attempted and how often attempts to connect to it have failed.
|
||||
|
|
|
@ -1,114 +0,0 @@
|
|||
// Copyright (c) 2013-2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package addrmgr_test
|
||||
|
||||
import (
|
||||
"math"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lbryio/lbcd/addrmgr"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
)
|
||||
|
||||
func TestChance(t *testing.T) {
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
var tests = []struct {
|
||||
addr *addrmgr.KnownAddress
|
||||
expected float64
|
||||
}{
|
||||
{
|
||||
//Test normal case
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1.0,
|
||||
}, {
|
||||
//Test case in which lastseen < 0
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(20 * time.Second)},
|
||||
0, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1.0,
|
||||
}, {
|
||||
//Test case in which lastattempt < 0
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(30*time.Minute), time.Now(), false, 0),
|
||||
1.0 * .01,
|
||||
}, {
|
||||
//Test case in which lastattempt < ten minutes
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
0, time.Now().Add(-5*time.Minute), time.Now(), false, 0),
|
||||
1.0 * .01,
|
||||
}, {
|
||||
//Test case with several failed attempts.
|
||||
addrmgr.TstNewKnownAddress(&wire.NetAddress{Timestamp: now.Add(-35 * time.Second)},
|
||||
2, time.Now().Add(-30*time.Minute), time.Now(), false, 0),
|
||||
1 / 1.5 / 1.5,
|
||||
},
|
||||
}
|
||||
|
||||
err := .0001
|
||||
for i, test := range tests {
|
||||
chance := addrmgr.TstKnownAddressChance(test.addr)
|
||||
if math.Abs(test.expected-chance) >= err {
|
||||
t.Errorf("case %d: got %f, expected %f", i, chance, test.expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIsBad(t *testing.T) {
|
||||
now := time.Unix(time.Now().Unix(), 0)
|
||||
future := now.Add(35 * time.Minute)
|
||||
monthOld := now.Add(-43 * time.Hour * 24)
|
||||
secondsOld := now.Add(-2 * time.Second)
|
||||
minutesOld := now.Add(-27 * time.Minute)
|
||||
hoursOld := now.Add(-5 * time.Hour)
|
||||
zeroTime := time.Time{}
|
||||
|
||||
futureNa := &wire.NetAddress{Timestamp: future}
|
||||
minutesOldNa := &wire.NetAddress{Timestamp: minutesOld}
|
||||
monthOldNa := &wire.NetAddress{Timestamp: monthOld}
|
||||
currentNa := &wire.NetAddress{Timestamp: secondsOld}
|
||||
|
||||
//Test addresses that have been tried in the last minute.
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 1: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 2: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, zeroTime, false, 0)) {
|
||||
t.Errorf("test case 3: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 3, secondsOld, monthOld, true, 0)) {
|
||||
t.Errorf("test case 4: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(currentNa, 2, secondsOld, secondsOld, true, 0)) {
|
||||
t.Errorf("test case 5: addresses that have been tried in the last minute are not bad.")
|
||||
}
|
||||
|
||||
//Test address that claims to be from the future.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(futureNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 6: addresses that claim to be from the future are bad.")
|
||||
}
|
||||
|
||||
//Test address that has not been seen in over a month.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(monthOldNa, 0, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 7: addresses more than a month old are bad.")
|
||||
}
|
||||
|
||||
//It has failed at least three times and never succeeded.
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 3, minutesOld, zeroTime, true, 0)) {
|
||||
t.Errorf("test case 8: addresses that have never succeeded are bad.")
|
||||
}
|
||||
|
||||
//It has failed ten times in the last week
|
||||
if !addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 10, minutesOld, monthOld, true, 0)) {
|
||||
t.Errorf("test case 9: addresses that have not succeeded in too long are bad.")
|
||||
}
|
||||
|
||||
//Test an address that should work.
|
||||
if addrmgr.TstKnownAddressIsBad(addrmgr.TstNewKnownAddress(minutesOldNa, 2, minutesOld, hoursOld, true, 0)) {
|
||||
t.Errorf("test case 10: This should be a valid address.")
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ func TestIPTypes(t *testing.T) {
|
|||
rfc4193, rfc4380, rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598,
|
||||
local, valid, routable bool) ipTest {
|
||||
nip := net.ParseIP(ip)
|
||||
na := *wire.NewNetAddressIPPort(nip, 9246, wire.SFNodeNetwork)
|
||||
na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork)
|
||||
test := ipTest{na, rfc1918, rfc2544, rfc3849, rfc3927, rfc3964, rfc4193, rfc4380,
|
||||
rfc4843, rfc4862, rfc5737, rfc6052, rfc6145, rfc6598, local, valid, routable}
|
||||
return test
|
||||
|
@ -192,7 +192,7 @@ func TestGroupKey(t *testing.T) {
|
|||
|
||||
for i, test := range tests {
|
||||
nip := net.ParseIP(test.ip)
|
||||
na := *wire.NewNetAddressIPPort(nip, 9246, wire.SFNodeNetwork)
|
||||
na := *wire.NewNetAddressIPPort(nip, 8333, wire.SFNodeNetwork)
|
||||
if key := addrmgr.GroupKey(&na); key != test.expected {
|
||||
t.Errorf("TestGroupKey #%d (%s): unexpected group key "+
|
||||
"- got '%s', want '%s'", i, test.name,
|
||||
|
|
|
@ -84,11 +84,9 @@ func (b *BlockChain) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags)
|
|||
// Notify the caller that the new block was accepted into the block
|
||||
// chain. The caller would typically want to react by relaying the
|
||||
// inventory to other peers.
|
||||
b.notificationSendLock.Lock()
|
||||
defer b.notificationSendLock.Unlock()
|
||||
b.chainLock.Unlock()
|
||||
defer b.chainLock.Lock()
|
||||
b.sendNotification(NTBlockAccepted, block)
|
||||
b.chainLock.Lock()
|
||||
|
||||
return isMainChain, nil
|
||||
}
|
||||
|
|
|
@ -1,29 +0,0 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// BenchmarkIsCoinBase performs a simple benchmark against the IsCoinBase
|
||||
// function.
|
||||
func BenchmarkIsCoinBase(b *testing.B) {
|
||||
tx, _ := GetBlock100000().Tx(1)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsCoinBase(tx)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkIsCoinBaseTx performs a simple benchmark against the IsCoinBaseTx
|
||||
// function.
|
||||
func BenchmarkIsCoinBaseTx(b *testing.B) {
|
||||
tx, _ := GetBlock100000().Tx(1)
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
IsCoinBaseTx(tx.MsgTx())
|
||||
}
|
||||
}
|
|
@ -8,7 +8,6 @@ package blockchain
|
|||
import (
|
||||
"container/list"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
|
@ -37,9 +36,8 @@ const (
|
|||
// from the block being located.
|
||||
//
|
||||
// For example, assume a block chain with a side chain as depicted below:
|
||||
//
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a
|
||||
//
|
||||
// The block locator for block 17a would be the hashes of blocks:
|
||||
// [17a 16a 15 14 13 12 11 10 9 8 7 6 4 genesis]
|
||||
|
@ -118,12 +116,6 @@ type BlockChain struct {
|
|||
// fields in this struct below this point.
|
||||
chainLock sync.RWMutex
|
||||
|
||||
// notificationSendLock helps us only process one block at a time.
|
||||
// It's definitely a hack. DCRD has much better structure in this regard.
|
||||
// Without this you will get an error if you invalidate a block and then generate more right after.
|
||||
// Taken from https://github.com/gcash/bchd/pull/308
|
||||
notificationSendLock sync.Mutex
|
||||
|
||||
// These fields are related to the memory block index. They both have
|
||||
// their own locks, however they are often also protected by the chain
|
||||
// lock to help prevent logic races when blocks are being processed.
|
||||
|
@ -207,15 +199,6 @@ func (b *BlockChain) HaveBlock(hash *chainhash.Hash) (bool, error) {
|
|||
return exists || b.IsKnownOrphan(hash), nil
|
||||
}
|
||||
|
||||
// GetWarnings returns a bool for whether unknownRules
|
||||
// has been warned.
|
||||
func (b *BlockChain) GetWarnings() bool {
|
||||
b.chainLock.RLock()
|
||||
defer b.chainLock.RUnlock()
|
||||
|
||||
return b.unknownRulesWarned
|
||||
}
|
||||
|
||||
// IsKnownOrphan returns whether the passed hash is currently a known orphan.
|
||||
// Keep in mind that only a limited number of orphans are held onto for a
|
||||
// limited amount of time, so this function must not be used as an absolute
|
||||
|
@ -489,7 +472,7 @@ func (b *BlockChain) calcSequenceLock(node *blockNode, tx *btcutil.Tx, utxoView
|
|||
// LockTimeToSequence converts the passed relative locktime to a sequence
|
||||
// number in accordance to BIP-68.
|
||||
// See: https://github.com/bitcoin/bips/blob/master/bip-0068.mediawiki
|
||||
// - (Compatibility)
|
||||
// * (Compatibility)
|
||||
func LockTimeToSequence(isSeconds bool, locktime uint32) uint32 {
|
||||
// If we're expressing the relative lock time in blocks, then the
|
||||
// corresponding sequence number is simply the desired input age.
|
||||
|
@ -603,8 +586,7 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
|
|||
|
||||
// Handle LBRY Claim Scripts
|
||||
if b.claimTrie != nil {
|
||||
shouldFlush := current && b.chainParams.Net != wire.TestNet
|
||||
if err := b.ParseClaimScripts(block, node, view, shouldFlush); err != nil {
|
||||
if err := b.ParseClaimScripts(block, node, view, current); err != nil {
|
||||
return ruleError(ErrBadClaimTrie, err.Error())
|
||||
}
|
||||
}
|
||||
|
@ -691,11 +673,9 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
|
|||
// Notify the caller that the block was connected to the main chain.
|
||||
// The caller would typically want to react with actions such as
|
||||
// updating wallets.
|
||||
b.notificationSendLock.Lock()
|
||||
defer b.notificationSendLock.Unlock()
|
||||
b.chainLock.Unlock()
|
||||
defer b.chainLock.Lock()
|
||||
b.sendNotification(NTBlockConnected, block)
|
||||
b.chainLock.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -818,11 +798,9 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
|
|||
// Notify the caller that the block was disconnected from the main
|
||||
// chain. The caller would typically want to react with actions such as
|
||||
// updating wallets.
|
||||
b.notificationSendLock.Lock()
|
||||
defer b.notificationSendLock.Unlock()
|
||||
b.chainLock.Unlock()
|
||||
defer b.chainLock.Lock()
|
||||
b.sendNotification(NTBlockDisconnected, block)
|
||||
b.chainLock.Lock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -1006,7 +984,6 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
|||
err = b.checkConnectBlock(n, block, view, nil)
|
||||
if err != nil {
|
||||
if _, ok := err.(RuleError); ok {
|
||||
b.index.UnsetStatusFlags(n, statusValid)
|
||||
b.index.SetStatusFlags(n, statusValidateFailed)
|
||||
for de := e.Next(); de != nil; de = de.Next() {
|
||||
dn := de.Value.(*blockNode)
|
||||
|
@ -1108,8 +1085,8 @@ func (b *BlockChain) reorganizeChain(detachNodes, attachNodes *list.List) error
|
|||
// a reorganization to become the main chain).
|
||||
//
|
||||
// The flags modify the behavior of this function as follows:
|
||||
// - BFFastAdd: Avoids several expensive transaction validation operations.
|
||||
// This is useful when using checkpoints.
|
||||
// - BFFastAdd: Avoids several expensive transaction validation operations.
|
||||
// This is useful when using checkpoints.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, flags BehaviorFlags) (bool, error) {
|
||||
|
@ -1144,7 +1121,6 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
|||
if err == nil {
|
||||
b.index.SetStatusFlags(node, statusValid)
|
||||
} else if _, ok := err.(RuleError); ok {
|
||||
b.index.UnsetStatusFlags(node, statusValid)
|
||||
b.index.SetStatusFlags(node, statusValidateFailed)
|
||||
} else {
|
||||
return false, err
|
||||
|
@ -1179,7 +1155,6 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
|||
// that status of the block as invalid and flush the
|
||||
// index state to disk before returning with the error.
|
||||
if _, ok := err.(RuleError); ok {
|
||||
b.index.UnsetStatusFlags(node, statusValid)
|
||||
b.index.SetStatusFlags(
|
||||
node, statusValidateFailed,
|
||||
)
|
||||
|
@ -1250,8 +1225,8 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
|||
// isCurrent returns whether or not the chain believes it is current. Several
|
||||
// factors are used to guess, but the key factors that allow the chain to
|
||||
// believe it is current are:
|
||||
// - Latest block height is after the latest checkpoint (if enabled)
|
||||
// - Latest block has a timestamp newer than ~6 hours ago (as LBRY block time is one fourth of bitcoin)
|
||||
// - Latest block height is after the latest checkpoint (if enabled)
|
||||
// - Latest block has a timestamp newer than ~6 hours ago (as LBRY block time is one fourth of bitcoin)
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for reads).
|
||||
func (b *BlockChain) isCurrent() bool {
|
||||
|
@ -1274,8 +1249,8 @@ func (b *BlockChain) isCurrent() bool {
|
|||
// IsCurrent returns whether or not the chain believes it is current. Several
|
||||
// factors are used to guess, but the key factors that allow the chain to
|
||||
// believe it is current are:
|
||||
// - Latest block height is after the latest checkpoint (if enabled)
|
||||
// - Latest block has a timestamp newer than 24 hours ago
|
||||
// - Latest block height is after the latest checkpoint (if enabled)
|
||||
// - Latest block has a timestamp newer than 24 hours ago
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) IsCurrent() bool {
|
||||
|
@ -1375,57 +1350,6 @@ func (b *BlockChain) BlockHashByHeight(blockHeight int32) (*chainhash.Hash, erro
|
|||
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
|
||||
// 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.
|
||||
|
@ -1561,11 +1485,11 @@ func (b *BlockChain) IntervalBlockHashes(endHash *chainhash.Hash, interval int,
|
|||
//
|
||||
// In addition, there are two special cases:
|
||||
//
|
||||
// - When no locators are provided, the stop hash is treated as a request for
|
||||
// that block, so it will either return the node associated with the stop hash
|
||||
// if it is known, or nil if it is unknown
|
||||
// - When locators are provided, but none of them are known, nodes starting
|
||||
// after the genesis block will be returned
|
||||
// - When no locators are provided, the stop hash is treated as a request for
|
||||
// that block, so it will either return the node associated with the stop hash
|
||||
// if it is known, or nil if it is unknown
|
||||
// - When locators are provided, but none of them are known, nodes starting
|
||||
// after the genesis block will be returned
|
||||
//
|
||||
// This is primarily a helper function for the locateBlocks and locateHeaders
|
||||
// functions.
|
||||
|
@ -1649,11 +1573,11 @@ func (b *BlockChain) locateBlocks(locator BlockLocator, hashStop *chainhash.Hash
|
|||
//
|
||||
// In addition, there are two special cases:
|
||||
//
|
||||
// - When no locators are provided, the stop hash is treated as a request for
|
||||
// that block, so it will either return the stop hash itself if it is known,
|
||||
// or nil if it is unknown
|
||||
// - When locators are provided, but none of them are known, hashes starting
|
||||
// after the genesis block will be returned
|
||||
// - When no locators are provided, the stop hash is treated as a request for
|
||||
// that block, so it will either return the stop hash itself if it is known,
|
||||
// or nil if it is unknown
|
||||
// - When locators are provided, but none of them are known, hashes starting
|
||||
// after the genesis block will be returned
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) LocateBlocks(locator BlockLocator, hashStop *chainhash.Hash, maxHashes uint32) []chainhash.Hash {
|
||||
|
@ -1694,11 +1618,11 @@ func (b *BlockChain) locateHeaders(locator BlockLocator, hashStop *chainhash.Has
|
|||
//
|
||||
// In addition, there are two special cases:
|
||||
//
|
||||
// - When no locators are provided, the stop hash is treated as a request for
|
||||
// that header, so it will either return the header for the stop hash itself
|
||||
// if it is known, or nil if it is unknown
|
||||
// - When locators are provided, but none of them are known, headers starting
|
||||
// after the genesis block will be returned
|
||||
// - When no locators are provided, the stop hash is treated as a request for
|
||||
// that header, so it will either return the header for the stop hash itself
|
||||
// if it is known, or nil if it is unknown
|
||||
// - When locators are provided, but none of them are known, headers starting
|
||||
// after the genesis block will be returned
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Hash) []wire.BlockHeader {
|
||||
|
@ -1708,116 +1632,6 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has
|
|||
return headers
|
||||
}
|
||||
|
||||
// InvalidateBlock takes a block hash and invalidates it.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) InvalidateBlock(hash *chainhash.Hash) error {
|
||||
b.chainLock.Lock()
|
||||
defer b.chainLock.Unlock()
|
||||
return b.invalidateBlock(hash)
|
||||
}
|
||||
|
||||
// invalidateBlock takes a block hash and invalidates it.
|
||||
func (b *BlockChain) invalidateBlock(hash *chainhash.Hash) error {
|
||||
node := b.index.LookupNode(hash)
|
||||
if node == nil {
|
||||
err := fmt.Errorf("block %s is not known", hash)
|
||||
return err
|
||||
}
|
||||
|
||||
// No need to invalidate if its already invalid.
|
||||
if node.status.KnownInvalid() {
|
||||
err := fmt.Errorf("block %s is already invalid", hash)
|
||||
return err
|
||||
}
|
||||
|
||||
if node.parent == nil {
|
||||
err := fmt.Errorf("block %s has no parent", hash)
|
||||
return err
|
||||
}
|
||||
|
||||
b.index.SetStatusFlags(node, statusValidateFailed)
|
||||
b.index.UnsetStatusFlags(node, statusValid)
|
||||
|
||||
detachNodes, attachNodes := b.getReorganizeNodes(node.parent)
|
||||
|
||||
err := b.reorganizeChain(detachNodes, attachNodes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for i, e := 0, detachNodes.Front(); e != nil; i, e = i+1, e.Next() {
|
||||
n := e.Value.(*blockNode)
|
||||
|
||||
b.index.SetStatusFlags(n, statusInvalidAncestor)
|
||||
b.index.UnsetStatusFlags(n, statusValid)
|
||||
}
|
||||
|
||||
if writeErr := b.index.flushToDB(); writeErr != nil {
|
||||
log.Warnf("Error flushing block index changes to disk: %v", writeErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReconsiderBlock takes a block hash and allows it to be revalidated.
|
||||
//
|
||||
// This function is safe for concurrent access.
|
||||
func (b *BlockChain) ReconsiderBlock(hash *chainhash.Hash) error {
|
||||
return b.reconsiderBlock(hash)
|
||||
}
|
||||
|
||||
// reconsiderBlock takes a block hash and allows it to be revalidated.
|
||||
func (b *BlockChain) reconsiderBlock(hash *chainhash.Hash) error {
|
||||
node := b.index.LookupNode(hash)
|
||||
if node == nil {
|
||||
err := fmt.Errorf("block %s is not known", hash)
|
||||
return err
|
||||
}
|
||||
|
||||
// No need to reconsider, it is already valid.
|
||||
if node.status.KnownValid() && !node.status.KnownInvalid() { // second clause works around old bug
|
||||
err := fmt.Errorf("block %s is already valid", hash)
|
||||
return err
|
||||
}
|
||||
|
||||
// Keep a reference to the first node in the chain of invalid
|
||||
// blocks so we can reprocess after status flags are updated.
|
||||
firstNode := node
|
||||
|
||||
// Find previous node to the point where the blocks are valid again.
|
||||
for n := node; n.status.KnownInvalid(); n = n.parent {
|
||||
b.index.UnsetStatusFlags(n, statusInvalidAncestor)
|
||||
b.index.UnsetStatusFlags(n, statusValidateFailed)
|
||||
|
||||
firstNode = n
|
||||
}
|
||||
|
||||
// do we need an rlock on chainstate for this section?
|
||||
var blk *btcutil.Block
|
||||
err := b.db.View(func(dbTx database.Tx) error {
|
||||
var err error
|
||||
blk, err = dbFetchBlockByNode(dbTx, firstNode)
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Process it all again. This will take care of the
|
||||
// orphans as well.
|
||||
_, _, err = b.ProcessBlock(blk, BFNoDupBlockCheck)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if writeErr := b.index.flushToDB(); writeErr != nil {
|
||||
log.Warnf("Error flushing block index changes to disk: %v", writeErr)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ClaimTrie returns the claimTrie associated wit hthe chain.
|
||||
func (b *BlockChain) ClaimTrie() *claimtrie.ClaimTrie {
|
||||
return b.claimTrie
|
||||
|
|
|
@ -1,870 +0,0 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lbryio/lbcd/chaincfg"
|
||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
btcutil "github.com/lbryio/lbcutil"
|
||||
)
|
||||
|
||||
// TestCalcSequenceLock tests the LockTimeToSequence function, and the
|
||||
// CalcSequenceLock method of a Chain instance. The tests exercise several
|
||||
// combinations of inputs to the CalcSequenceLock function in order to ensure
|
||||
// the returned SequenceLocks are correct for each test instance.
|
||||
func TestCalcSequenceLock(t *testing.T) {
|
||||
netParams := &chaincfg.SimNetParams
|
||||
|
||||
// We need to activate CSV in order to test the processing logic, so
|
||||
// manually craft the block version that's used to signal the soft-fork
|
||||
// activation.
|
||||
csvBit := netParams.Deployments[chaincfg.DeploymentCSV].BitNumber
|
||||
blockVersion := int32(0x20000000 | (uint32(1) << csvBit))
|
||||
|
||||
// Generate enough synthetic blocks to activate CSV.
|
||||
chain := newFakeChain(netParams)
|
||||
node := chain.bestChain.Tip()
|
||||
blockTime := node.Header().Timestamp
|
||||
numBlocksToActivate := (netParams.MinerConfirmationWindow * 3)
|
||||
for i := uint32(0); i < numBlocksToActivate; i++ {
|
||||
blockTime = blockTime.Add(time.Second)
|
||||
node = newFakeNode(node, blockVersion, 0, blockTime)
|
||||
chain.index.AddNode(node)
|
||||
chain.bestChain.SetTip(node)
|
||||
}
|
||||
|
||||
// Create a utxo view with a fake utxo for the inputs used in the
|
||||
// transactions created below. This utxo is added such that it has an
|
||||
// age of 4 blocks.
|
||||
targetTx := btcutil.NewTx(&wire.MsgTx{
|
||||
TxOut: []*wire.TxOut{{
|
||||
PkScript: nil,
|
||||
Value: 10,
|
||||
}},
|
||||
})
|
||||
utxoView := NewUtxoViewpoint()
|
||||
utxoView.AddTxOuts(targetTx, int32(numBlocksToActivate)-4)
|
||||
utxoView.SetBestHash(&node.hash)
|
||||
|
||||
// Create a utxo that spends the fake utxo created above for use in the
|
||||
// transactions created in the tests. It has an age of 4 blocks. Note
|
||||
// that the sequence lock heights are always calculated from the same
|
||||
// point of view that they were originally calculated from for a given
|
||||
// utxo. That is to say, the height prior to it.
|
||||
utxo := wire.OutPoint{
|
||||
Hash: *targetTx.Hash(),
|
||||
Index: 0,
|
||||
}
|
||||
prevUtxoHeight := int32(numBlocksToActivate) - 4
|
||||
|
||||
// Obtain the median time past from the PoV of the input created above.
|
||||
// The MTP for the input is the MTP from the PoV of the block *prior*
|
||||
// to the one that included it.
|
||||
medianTime := node.RelativeAncestor(5).CalcPastMedianTime().Unix()
|
||||
|
||||
// The median time calculated from the PoV of the best block in the
|
||||
// test chain. For unconfirmed inputs, this value will be used since
|
||||
// the MTP will be calculated from the PoV of the yet-to-be-mined
|
||||
// block.
|
||||
nextMedianTime := node.CalcPastMedianTime().Unix()
|
||||
nextBlockHeight := int32(numBlocksToActivate) + 1
|
||||
|
||||
// Add an additional transaction which will serve as our unconfirmed
|
||||
// output.
|
||||
unConfTx := &wire.MsgTx{
|
||||
TxOut: []*wire.TxOut{{
|
||||
PkScript: nil,
|
||||
Value: 5,
|
||||
}},
|
||||
}
|
||||
unConfUtxo := wire.OutPoint{
|
||||
Hash: unConfTx.TxHash(),
|
||||
Index: 0,
|
||||
}
|
||||
|
||||
// Adding a utxo with a height of 0x7fffffff indicates that the output
|
||||
// is currently unmined.
|
||||
utxoView.AddTxOuts(btcutil.NewTx(unConfTx), 0x7fffffff)
|
||||
|
||||
tests := []struct {
|
||||
tx *wire.MsgTx
|
||||
view *UtxoViewpoint
|
||||
mempool bool
|
||||
want *SequenceLock
|
||||
}{
|
||||
// A transaction of version one should disable sequence locks
|
||||
// as the new sequence number semantics only apply to
|
||||
// transactions version 2 or higher.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 1,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 3),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
BlockHeight: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with a single input with max sequence number.
|
||||
// This sequence number has the high bit set, so sequence locks
|
||||
// should be disabled.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: wire.MaxTxInSequenceNum,
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
BlockHeight: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with a single input whose lock time is
|
||||
// expressed in seconds. However, the specified lock time is
|
||||
// below the required floor for time based lock times since
|
||||
// they have time granularity of 512 seconds. As a result, the
|
||||
// seconds lock-time should be just before the median time of
|
||||
// the targeted block.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime - 1,
|
||||
BlockHeight: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with a single input whose lock time is
|
||||
// expressed in seconds. The number of seconds should be 1023
|
||||
// seconds after the median past time of the last block in the
|
||||
// chain.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 1024),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + 1023,
|
||||
BlockHeight: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with multiple inputs. The first input has a
|
||||
// lock time expressed in seconds. The second input has a
|
||||
// sequence lock in blocks with a value of 4. The last input
|
||||
// has a sequence number with a value of 5, but has the disable
|
||||
// bit set. So the first lock should be selected as it's the
|
||||
// latest lock that isn't disabled.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2560),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 4),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 5) |
|
||||
wire.SequenceLockTimeDisabled,
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + (5 << wire.SequenceLockTimeGranularity) - 1,
|
||||
BlockHeight: prevUtxoHeight + 3,
|
||||
},
|
||||
},
|
||||
// Transaction with a single input. The input's sequence number
|
||||
// encodes a relative lock-time in blocks (3 blocks). The
|
||||
// sequence lock should have a value of -1 for seconds, but a
|
||||
// height of 2 meaning it can be included at height 3.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 3),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
BlockHeight: prevUtxoHeight + 2,
|
||||
},
|
||||
},
|
||||
// A transaction with two inputs with lock times expressed in
|
||||
// seconds. The selected sequence lock value for seconds should
|
||||
// be the time further in the future.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 5120),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2560),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + (10 << wire.SequenceLockTimeGranularity) - 1,
|
||||
BlockHeight: -1,
|
||||
},
|
||||
},
|
||||
// A transaction with two inputs with lock times expressed in
|
||||
// blocks. The selected sequence lock value for blocks should
|
||||
// be the height further in the future, so a height of 10
|
||||
// indicating it can be included at height 11.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 1),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 11),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
BlockHeight: prevUtxoHeight + 10,
|
||||
},
|
||||
},
|
||||
// A transaction with multiple inputs. Two inputs are time
|
||||
// based, and the other two are block based. The lock lying
|
||||
// further into the future for both inputs should be chosen.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 2560),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(true, 6656),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 3),
|
||||
}, {
|
||||
PreviousOutPoint: utxo,
|
||||
Sequence: LockTimeToSequence(false, 9),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
want: &SequenceLock{
|
||||
Seconds: medianTime + (13 << wire.SequenceLockTimeGranularity) - 1,
|
||||
BlockHeight: prevUtxoHeight + 8,
|
||||
},
|
||||
},
|
||||
// A transaction with a single unconfirmed input. As the input
|
||||
// is confirmed, the height of the input should be interpreted
|
||||
// as the height of the *next* block. So, a 2 block relative
|
||||
// lock means the sequence lock should be for 1 block after the
|
||||
// *next* block height, indicating it can be included 2 blocks
|
||||
// after that.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: unConfUtxo,
|
||||
Sequence: LockTimeToSequence(false, 2),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
mempool: true,
|
||||
want: &SequenceLock{
|
||||
Seconds: -1,
|
||||
BlockHeight: nextBlockHeight + 1,
|
||||
},
|
||||
},
|
||||
// A transaction with a single unconfirmed input. The input has
|
||||
// a time based lock, so the lock time should be based off the
|
||||
// MTP of the *next* block.
|
||||
{
|
||||
tx: &wire.MsgTx{
|
||||
Version: 2,
|
||||
TxIn: []*wire.TxIn{{
|
||||
PreviousOutPoint: unConfUtxo,
|
||||
Sequence: LockTimeToSequence(true, 1024),
|
||||
}},
|
||||
},
|
||||
view: utxoView,
|
||||
mempool: true,
|
||||
want: &SequenceLock{
|
||||
Seconds: nextMedianTime + 1023,
|
||||
BlockHeight: -1,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %v SequenceLock tests", len(tests))
|
||||
for i, test := range tests {
|
||||
utilTx := btcutil.NewTx(test.tx)
|
||||
seqLock, err := chain.CalcSequenceLock(utilTx, test.view, test.mempool)
|
||||
if err != nil {
|
||||
t.Fatalf("test #%d, unable to calc sequence lock: %v", i, err)
|
||||
}
|
||||
|
||||
if seqLock.Seconds != test.want.Seconds {
|
||||
t.Fatalf("test #%d got %v seconds want %v seconds",
|
||||
i, seqLock.Seconds, test.want.Seconds)
|
||||
}
|
||||
if seqLock.BlockHeight != test.want.BlockHeight {
|
||||
t.Fatalf("test #%d got height of %v want height of %v ",
|
||||
i, seqLock.BlockHeight, test.want.BlockHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// nodeHashes is a convenience function that returns the hashes for all of the
|
||||
// passed indexes of the provided nodes. It is used to construct expected hash
|
||||
// slices in the tests.
|
||||
func nodeHashes(nodes []*blockNode, indexes ...int) []chainhash.Hash {
|
||||
hashes := make([]chainhash.Hash, 0, len(indexes))
|
||||
for _, idx := range indexes {
|
||||
hashes = append(hashes, nodes[idx].hash)
|
||||
}
|
||||
return hashes
|
||||
}
|
||||
|
||||
// nodeHeaders is a convenience function that returns the headers for all of
|
||||
// the passed indexes of the provided nodes. It is used to construct expected
|
||||
// located headers in the tests.
|
||||
func nodeHeaders(nodes []*blockNode, indexes ...int) []wire.BlockHeader {
|
||||
headers := make([]wire.BlockHeader, 0, len(indexes))
|
||||
for _, idx := range indexes {
|
||||
headers = append(headers, nodes[idx].Header())
|
||||
}
|
||||
return headers
|
||||
}
|
||||
|
||||
// TestLocateInventory ensures that locating inventory via the LocateHeaders and
|
||||
// LocateBlocks functions behaves as expected.
|
||||
func TestLocateInventory(t *testing.T) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
|
||||
branch1Nodes := chainedNodes(branch0Nodes[14], 2)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
for _, node := range branch1Nodes {
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
// Create chain views for different branches of the overall chain to
|
||||
// simulate a local and remote node on different parts of the chain.
|
||||
localView := newChainView(tip(branch0Nodes))
|
||||
remoteView := newChainView(tip(branch1Nodes))
|
||||
|
||||
// Create a chain view for a completely unrelated block chain to
|
||||
// simulate a remote node on a totally different chain.
|
||||
unrelatedBranchNodes := chainedNodes(nil, 5)
|
||||
unrelatedView := newChainView(tip(unrelatedBranchNodes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
locator BlockLocator // locator for requested inventory
|
||||
hashStop chainhash.Hash // stop hash for locator
|
||||
maxAllowed uint32 // max to locate, 0 = wire const
|
||||
headers []wire.BlockHeader // expected located headers
|
||||
hashes []chainhash.Hash // expected located hashes
|
||||
}{
|
||||
{
|
||||
// Empty block locators and unknown stop hash. No
|
||||
// inventory should be located.
|
||||
name: "no locators, no stop",
|
||||
locator: nil,
|
||||
hashStop: chainhash.Hash{},
|
||||
headers: nil,
|
||||
hashes: nil,
|
||||
},
|
||||
{
|
||||
// Empty block locators and stop hash in side chain.
|
||||
// The expected result is the requested block.
|
||||
name: "no locators, stop in side",
|
||||
locator: nil,
|
||||
hashStop: tip(branch1Nodes).hash,
|
||||
headers: nodeHeaders(branch1Nodes, 1),
|
||||
hashes: nodeHashes(branch1Nodes, 1),
|
||||
},
|
||||
{
|
||||
// Empty block locators and stop hash in main chain.
|
||||
// The expected result is the requested block.
|
||||
name: "no locators, stop in main",
|
||||
locator: nil,
|
||||
hashStop: branch0Nodes[12].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 12),
|
||||
hashes: nodeHashes(branch0Nodes, 12),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on side chain and a
|
||||
// stop hash local node doesn't know about. The
|
||||
// expected result is the blocks after the fork point in
|
||||
// the main chain and the stop hash has no effect.
|
||||
name: "remote side chain, unknown stop",
|
||||
locator: remoteView.BlockLocator(nil),
|
||||
hashStop: chainhash.Hash{0x01},
|
||||
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on side chain and a
|
||||
// stop hash in side chain. The expected result is the
|
||||
// blocks after the fork point in the main chain and the
|
||||
// stop hash has no effect.
|
||||
name: "remote side chain, stop in side",
|
||||
locator: remoteView.BlockLocator(nil),
|
||||
hashStop: tip(branch1Nodes).hash,
|
||||
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on side chain and a
|
||||
// stop hash in main chain, but before fork point. The
|
||||
// expected result is the blocks after the fork point in
|
||||
// the main chain and the stop hash has no effect.
|
||||
name: "remote side chain, stop in main before",
|
||||
locator: remoteView.BlockLocator(nil),
|
||||
hashStop: branch0Nodes[13].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on side chain and a
|
||||
// stop hash in main chain, but exactly at the fork
|
||||
// point. The expected result is the blocks after the
|
||||
// fork point in the main chain and the stop hash has no
|
||||
// effect.
|
||||
name: "remote side chain, stop in main exact",
|
||||
locator: remoteView.BlockLocator(nil),
|
||||
hashStop: branch0Nodes[14].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on side chain and a
|
||||
// stop hash in main chain just after the fork point.
|
||||
// The expected result is the blocks after the fork
|
||||
// point in the main chain up to and including the stop
|
||||
// hash.
|
||||
name: "remote side chain, stop in main after",
|
||||
locator: remoteView.BlockLocator(nil),
|
||||
hashStop: branch0Nodes[15].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 15),
|
||||
hashes: nodeHashes(branch0Nodes, 15),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on side chain and a
|
||||
// stop hash in main chain some time after the fork
|
||||
// point. The expected result is the blocks after the
|
||||
// fork point in the main chain up to and including the
|
||||
// stop hash.
|
||||
name: "remote side chain, stop in main after more",
|
||||
locator: remoteView.BlockLocator(nil),
|
||||
hashStop: branch0Nodes[16].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 15, 16),
|
||||
hashes: nodeHashes(branch0Nodes, 15, 16),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on main chain in the
|
||||
// past and a stop hash local node doesn't know about.
|
||||
// The expected result is the blocks after the known
|
||||
// point in the main chain and the stop hash has no
|
||||
// effect.
|
||||
name: "remote main chain past, unknown stop",
|
||||
locator: localView.BlockLocator(branch0Nodes[12]),
|
||||
hashStop: chainhash.Hash{0x01},
|
||||
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on main chain in the
|
||||
// past and a stop hash in a side chain. The expected
|
||||
// result is the blocks after the known point in the
|
||||
// main chain and the stop hash has no effect.
|
||||
name: "remote main chain past, stop in side",
|
||||
locator: localView.BlockLocator(branch0Nodes[12]),
|
||||
hashStop: tip(branch1Nodes).hash,
|
||||
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on main chain in the
|
||||
// past and a stop hash in the main chain before that
|
||||
// point. The expected result is the blocks after the
|
||||
// known point in the main chain and the stop hash has
|
||||
// no effect.
|
||||
name: "remote main chain past, stop in main before",
|
||||
locator: localView.BlockLocator(branch0Nodes[12]),
|
||||
hashStop: branch0Nodes[11].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on main chain in the
|
||||
// past and a stop hash in the main chain exactly at that
|
||||
// point. The expected result is the blocks after the
|
||||
// known point in the main chain and the stop hash has
|
||||
// no effect.
|
||||
name: "remote main chain past, stop in main exact",
|
||||
locator: localView.BlockLocator(branch0Nodes[12]),
|
||||
hashStop: branch0Nodes[12].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on main chain in the
|
||||
// past and a stop hash in the main chain just after
|
||||
// that point. The expected result is the blocks after
|
||||
// the known point in the main chain and the stop hash
|
||||
// has no effect.
|
||||
name: "remote main chain past, stop in main after",
|
||||
locator: localView.BlockLocator(branch0Nodes[12]),
|
||||
hashStop: branch0Nodes[13].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 13),
|
||||
hashes: nodeHashes(branch0Nodes, 13),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being on main chain in the
|
||||
// past and a stop hash in the main chain some time
|
||||
// after that point. The expected result is the blocks
|
||||
// after the known point in the main chain and the stop
|
||||
// hash has no effect.
|
||||
name: "remote main chain past, stop in main after more",
|
||||
locator: localView.BlockLocator(branch0Nodes[12]),
|
||||
hashStop: branch0Nodes[15].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 13, 14, 15),
|
||||
hashes: nodeHashes(branch0Nodes, 13, 14, 15),
|
||||
},
|
||||
{
|
||||
// Locators based on remote being at exactly the same
|
||||
// point in the main chain and a stop hash local node
|
||||
// doesn't know about. The expected result is no
|
||||
// located inventory.
|
||||
name: "remote main chain same, unknown stop",
|
||||
locator: localView.BlockLocator(nil),
|
||||
hashStop: chainhash.Hash{0x01},
|
||||
headers: nil,
|
||||
hashes: nil,
|
||||
},
|
||||
{
|
||||
// Locators based on remote being at exactly the same
|
||||
// point in the main chain and a stop hash at exactly
|
||||
// the same point. The expected result is no located
|
||||
// inventory.
|
||||
name: "remote main chain same, stop same point",
|
||||
locator: localView.BlockLocator(nil),
|
||||
hashStop: tip(branch0Nodes).hash,
|
||||
headers: nil,
|
||||
hashes: nil,
|
||||
},
|
||||
{
|
||||
// Locators from remote that don't include any blocks
|
||||
// the local node knows. This would happen if the
|
||||
// remote node is on a completely separate chain that
|
||||
// isn't rooted with the same genesis block. The
|
||||
// expected result is the blocks after the genesis
|
||||
// block.
|
||||
name: "remote unrelated chain",
|
||||
locator: unrelatedView.BlockLocator(nil),
|
||||
hashStop: chainhash.Hash{},
|
||||
headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Locators from remote for second block in main chain
|
||||
// and no stop hash, but with an overridden max limit.
|
||||
// The expected result is the blocks after the second
|
||||
// block limited by the max.
|
||||
name: "remote genesis",
|
||||
locator: locatorHashes(branch0Nodes, 0),
|
||||
hashStop: chainhash.Hash{},
|
||||
maxAllowed: 3,
|
||||
headers: nodeHeaders(branch0Nodes, 1, 2, 3),
|
||||
hashes: nodeHashes(branch0Nodes, 1, 2, 3),
|
||||
},
|
||||
{
|
||||
// Poorly formed locator.
|
||||
//
|
||||
// Locator from remote that only includes a single
|
||||
// block on a side chain the local node knows. The
|
||||
// expected result is the blocks after the genesis
|
||||
// block since even though the block is known, it is on
|
||||
// a side chain and there are no more locators to find
|
||||
// the fork point.
|
||||
name: "weak locator, single known side block",
|
||||
locator: locatorHashes(branch1Nodes, 1),
|
||||
hashStop: chainhash.Hash{},
|
||||
headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Poorly formed locator.
|
||||
//
|
||||
// Locator from remote that only includes multiple
|
||||
// blocks on a side chain the local node knows however
|
||||
// none in the main chain. The expected result is the
|
||||
// blocks after the genesis block since even though the
|
||||
// blocks are known, they are all on a side chain and
|
||||
// there are no more locators to find the fork point.
|
||||
name: "weak locator, multiple known side blocks",
|
||||
locator: locatorHashes(branch1Nodes, 1),
|
||||
hashStop: chainhash.Hash{},
|
||||
headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
|
||||
hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5, 6,
|
||||
7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
// Poorly formed locator.
|
||||
//
|
||||
// Locator from remote that only includes multiple
|
||||
// blocks on a side chain the local node knows however
|
||||
// none in the main chain but includes a stop hash in
|
||||
// the main chain. The expected result is the blocks
|
||||
// after the genesis block up to the stop hash since
|
||||
// even though the blocks are known, they are all on a
|
||||
// side chain and there are no more locators to find the
|
||||
// fork point.
|
||||
name: "weak locator, multiple known side blocks, stop in main",
|
||||
locator: locatorHashes(branch1Nodes, 1),
|
||||
hashStop: branch0Nodes[5].hash,
|
||||
headers: nodeHeaders(branch0Nodes, 0, 1, 2, 3, 4, 5),
|
||||
hashes: nodeHashes(branch0Nodes, 0, 1, 2, 3, 4, 5),
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
// Ensure the expected headers are located.
|
||||
var headers []wire.BlockHeader
|
||||
if test.maxAllowed != 0 {
|
||||
// Need to use the unexported function to override the
|
||||
// max allowed for headers.
|
||||
chain.chainLock.RLock()
|
||||
headers = chain.locateHeaders(test.locator,
|
||||
&test.hashStop, test.maxAllowed)
|
||||
chain.chainLock.RUnlock()
|
||||
} else {
|
||||
headers = chain.LocateHeaders(test.locator,
|
||||
&test.hashStop)
|
||||
}
|
||||
if !reflect.DeepEqual(headers, test.headers) {
|
||||
t.Errorf("%s: unxpected headers -- got %v, want %v",
|
||||
test.name, headers, test.headers)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the expected block hashes are located.
|
||||
maxAllowed := uint32(wire.MaxBlocksPerMsg)
|
||||
if test.maxAllowed != 0 {
|
||||
maxAllowed = test.maxAllowed
|
||||
}
|
||||
hashes := chain.LocateBlocks(test.locator, &test.hashStop,
|
||||
maxAllowed)
|
||||
if !reflect.DeepEqual(hashes, test.hashes) {
|
||||
t.Errorf("%s: unxpected hashes -- got %v, want %v",
|
||||
test.name, hashes, test.hashes)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHeightToHashRange ensures that fetching a range of block hashes by start
|
||||
// height and end hash works as expected.
|
||||
func TestHeightToHashRange(t *testing.T) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a -> 18a (unvalidated)
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
|
||||
branch1Nodes := chainedNodes(branch0Nodes[14], 3)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
for _, node := range branch1Nodes {
|
||||
if node.height < 18 {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
}
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
startHeight int32 // locator for requested inventory
|
||||
endHash chainhash.Hash // stop hash for locator
|
||||
maxResults int // max to locate, 0 = wire const
|
||||
hashes []chainhash.Hash // expected located hashes
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "blocks below tip",
|
||||
startHeight: 11,
|
||||
endHash: branch0Nodes[14].hash,
|
||||
maxResults: 10,
|
||||
hashes: nodeHashes(branch0Nodes, 10, 11, 12, 13, 14),
|
||||
},
|
||||
{
|
||||
name: "blocks on main chain",
|
||||
startHeight: 15,
|
||||
endHash: branch0Nodes[17].hash,
|
||||
maxResults: 10,
|
||||
hashes: nodeHashes(branch0Nodes, 14, 15, 16, 17),
|
||||
},
|
||||
{
|
||||
name: "blocks on stale chain",
|
||||
startHeight: 15,
|
||||
endHash: branch1Nodes[1].hash,
|
||||
maxResults: 10,
|
||||
hashes: append(nodeHashes(branch0Nodes, 14),
|
||||
nodeHashes(branch1Nodes, 0, 1)...),
|
||||
},
|
||||
{
|
||||
name: "invalid start height",
|
||||
startHeight: 19,
|
||||
endHash: branch0Nodes[17].hash,
|
||||
maxResults: 10,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "too many results",
|
||||
startHeight: 1,
|
||||
endHash: branch0Nodes[17].hash,
|
||||
maxResults: 10,
|
||||
expectError: true,
|
||||
},
|
||||
{
|
||||
name: "unvalidated block",
|
||||
startHeight: 15,
|
||||
endHash: branch1Nodes[2].hash,
|
||||
maxResults: 10,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash,
|
||||
test.maxResults)
|
||||
if err != nil {
|
||||
if !test.expectError {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(hashes, test.hashes) {
|
||||
t.Errorf("%s: unxpected hashes -- got %v, want %v",
|
||||
test.name, hashes, test.hashes)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestIntervalBlockHashes ensures that fetching block hashes at specified
|
||||
// intervals by end hash works as expected.
|
||||
func TestIntervalBlockHashes(t *testing.T) {
|
||||
// Construct a synthetic block chain with a block index consisting of
|
||||
// the following structure.
|
||||
// genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18
|
||||
// \-> 16a -> 17a -> 18a (unvalidated)
|
||||
tip := tstTip
|
||||
chain := newFakeChain(&chaincfg.MainNetParams)
|
||||
branch0Nodes := chainedNodes(chain.bestChain.Genesis(), 18)
|
||||
branch1Nodes := chainedNodes(branch0Nodes[14], 3)
|
||||
for _, node := range branch0Nodes {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
for _, node := range branch1Nodes {
|
||||
if node.height < 18 {
|
||||
chain.index.SetStatusFlags(node, statusValid)
|
||||
}
|
||||
chain.index.AddNode(node)
|
||||
}
|
||||
chain.bestChain.SetTip(tip(branch0Nodes))
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
endHash chainhash.Hash
|
||||
interval int
|
||||
hashes []chainhash.Hash
|
||||
expectError bool
|
||||
}{
|
||||
{
|
||||
name: "blocks on main chain",
|
||||
endHash: branch0Nodes[17].hash,
|
||||
interval: 8,
|
||||
hashes: nodeHashes(branch0Nodes, 7, 15),
|
||||
},
|
||||
{
|
||||
name: "blocks on stale chain",
|
||||
endHash: branch1Nodes[1].hash,
|
||||
interval: 8,
|
||||
hashes: append(nodeHashes(branch0Nodes, 7),
|
||||
nodeHashes(branch1Nodes, 0)...),
|
||||
},
|
||||
{
|
||||
name: "no results",
|
||||
endHash: branch0Nodes[17].hash,
|
||||
interval: 20,
|
||||
hashes: []chainhash.Hash{},
|
||||
},
|
||||
{
|
||||
name: "unvalidated block",
|
||||
endHash: branch1Nodes[2].hash,
|
||||
interval: 8,
|
||||
expectError: true,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
hashes, err := chain.IntervalBlockHashes(&test.endHash, test.interval)
|
||||
if err != nil {
|
||||
if !test.expectError {
|
||||
t.Errorf("%s: unexpected error: %v", test.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(hashes, test.hashes) {
|
||||
t.Errorf("%s: unxpected hashes -- got %v, want %v",
|
||||
test.name, hashes, test.hashes)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
btcutil "github.com/lbryio/lbcutil"
|
||||
)
|
||||
|
||||
type ChainTip struct { // duplicate of btcjson.GetChainTipsResult to avoid circular reference
|
||||
Height int64
|
||||
Hash string
|
||||
BranchLen int64
|
||||
Status string
|
||||
}
|
||||
|
||||
// nodeHeightSorter implements sort.Interface to allow a slice of nodes to
|
||||
// be sorted by height in ascending order.
|
||||
type nodeHeightSorter []ChainTip
|
||||
|
||||
// Len returns the number of nodes in the slice. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s nodeHeightSorter) Len() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
// Swap swaps the nodes at the passed indices. It is part of the
|
||||
// sort.Interface implementation.
|
||||
func (s nodeHeightSorter) Swap(i, j int) {
|
||||
s[i], s[j] = s[j], s[i]
|
||||
}
|
||||
|
||||
// Less returns whether the node with index i should sort before the node with
|
||||
// index j. It is part of the sort.Interface implementation.
|
||||
func (s nodeHeightSorter) Less(i, j int) bool {
|
||||
// To ensure stable order when the heights are the same, fall back to
|
||||
// sorting based on hash.
|
||||
if s[i].Height == s[j].Height {
|
||||
return strings.Compare(s[i].Hash, s[j].Hash) < 0
|
||||
}
|
||||
return s[i].Height < s[j].Height
|
||||
}
|
||||
|
||||
// ChainTips returns information, in JSON-RPC format, about all the currently
|
||||
// known chain tips in the block index.
|
||||
func (b *BlockChain) ChainTips() []ChainTip {
|
||||
// we need our current tip
|
||||
// we also need all of our orphans that aren't in the prevOrphans
|
||||
var results []ChainTip
|
||||
|
||||
tip := b.bestChain.Tip()
|
||||
results = append(results, ChainTip{
|
||||
Height: int64(tip.height),
|
||||
Hash: tip.hash.String(),
|
||||
BranchLen: 0,
|
||||
Status: "active",
|
||||
})
|
||||
|
||||
b.orphanLock.RLock()
|
||||
defer b.orphanLock.RUnlock()
|
||||
|
||||
notInBestChain := func(block *btcutil.Block) bool {
|
||||
node := b.bestChain.NodeByHeight(block.Height())
|
||||
if node == nil {
|
||||
return false
|
||||
}
|
||||
return node.hash.IsEqual(block.Hash())
|
||||
}
|
||||
|
||||
for hash, orphan := range b.orphans {
|
||||
if len(b.prevOrphans[hash]) > 0 {
|
||||
continue
|
||||
}
|
||||
fork := orphan.block
|
||||
for fork != nil && notInBestChain(fork) {
|
||||
fork = b.orphans[*fork.Hash()].block
|
||||
}
|
||||
|
||||
result := ChainTip{
|
||||
Height: int64(orphan.block.Height()),
|
||||
Hash: hash.String(),
|
||||
BranchLen: int64(orphan.block.Height() - fork.Height()),
|
||||
}
|
||||
|
||||
// Determine the status of the chain tip.
|
||||
//
|
||||
// active:
|
||||
// The current best chain tip.
|
||||
//
|
||||
// invalid:
|
||||
// The block or one of its ancestors is invalid.
|
||||
//
|
||||
// headers-only:
|
||||
// The block or one of its ancestors does not have the full block data
|
||||
// available which also means the block can't be validated or
|
||||
// connected.
|
||||
//
|
||||
// valid-fork:
|
||||
// The block is fully validated which implies it was probably part of
|
||||
// main chain at one point and was reorganized.
|
||||
//
|
||||
// valid-headers:
|
||||
// The full block data is available and the header is valid, but the
|
||||
// block was never validated which implies it was probably never part
|
||||
// of the main chain.
|
||||
tipStatus := b.index.LookupNode(&hash).status
|
||||
if tipStatus.KnownInvalid() {
|
||||
result.Status = "invalid"
|
||||
} else if !tipStatus.HaveData() {
|
||||
result.Status = "headers-only"
|
||||
} else if tipStatus.KnownValid() {
|
||||
result.Status = "valid-fork"
|
||||
} else {
|
||||
result.Status = "valid-headers"
|
||||
}
|
||||
|
||||
results = append(results, result)
|
||||
}
|
||||
|
||||
// Generate the results sorted by descending height.
|
||||
sort.Sort(sort.Reverse(nodeHeightSorter(results)))
|
||||
return results
|
||||
}
|
|
@ -36,13 +36,11 @@ func fastLog2Floor(n uint32) uint8 {
|
|||
// for comparing chains.
|
||||
//
|
||||
// For example, assume a block chain with a side chain as depicted below:
|
||||
//
|
||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||
// \-> 4a -> 5a -> 6a
|
||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||
// \-> 4a -> 5a -> 6a
|
||||
//
|
||||
// The chain view for the branch ending in 6a consists of:
|
||||
//
|
||||
// genesis -> 1 -> 2 -> 3 -> 4a -> 5a -> 6a
|
||||
// genesis -> 1 -> 2 -> 3 -> 4a -> 5a -> 6a
|
||||
type chainView struct {
|
||||
mtx sync.Mutex
|
||||
nodes []*blockNode
|
||||
|
@ -260,14 +258,12 @@ func (c *chainView) next(node *blockNode) *blockNode {
|
|||
// view.
|
||||
//
|
||||
// For example, assume a block chain with a side chain as depicted below:
|
||||
//
|
||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||
// \-> 4a -> 5a -> 6a
|
||||
// genesis -> 1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8
|
||||
// \-> 4a -> 5a -> 6a
|
||||
//
|
||||
// Further, assume the view is for the longer chain depicted above. That is to
|
||||
// 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 it with block node 5a would return nil since that node is not part
|
||||
|
@ -325,14 +321,12 @@ func (c *chainView) findFork(node *blockNode) *blockNode {
|
|||
// 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:
|
||||
//
|
||||
// genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8
|
||||
// \-> 6a -> 7a
|
||||
// genesis -> 1 -> 2 -> ... -> 5 -> 6 -> 7 -> 8
|
||||
// \-> 6a -> 7a
|
||||
//
|
||||
// Further, assume the view is for the longer chain depicted above. That is to
|
||||
// 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 it with block node 7 would return itself since it is already part of
|
||||
|
|
|
@ -172,8 +172,7 @@ func (b *BlockChain) findPreviousCheckpoint() (*blockNode, error) {
|
|||
func isNonstandardTransaction(tx *btcutil.Tx) bool {
|
||||
// Check all of the output public key scripts for non-standard scripts.
|
||||
for _, txOut := range tx.MsgTx().TxOut {
|
||||
stripped := txscript.StripClaimScriptPrefix(txOut.PkScript)
|
||||
scriptClass := txscript.GetScriptClass(stripped)
|
||||
scriptClass := txscript.GetScriptClass(txOut.PkScript)
|
||||
if scriptClass == txscript.NonStandardTy {
|
||||
return true
|
||||
}
|
||||
|
@ -185,14 +184,14 @@ func isNonstandardTransaction(tx *btcutil.Tx) bool {
|
|||
// checkpoint candidate.
|
||||
//
|
||||
// The factors used to determine a good checkpoint are:
|
||||
// - The block must be in the main chain
|
||||
// - The block must be at least 'CheckpointConfirmations' blocks prior to the
|
||||
// current end of the main chain
|
||||
// - The timestamps for the blocks before and after the checkpoint must have
|
||||
// timestamps which are also before and after the checkpoint, respectively
|
||||
// (due to the median time allowance this is not always the case)
|
||||
// - The block must not contain any strange transaction such as those with
|
||||
// nonstandard scripts
|
||||
// - The block must be in the main chain
|
||||
// - The block must be at least 'CheckpointConfirmations' blocks prior to the
|
||||
// current end of the main chain
|
||||
// - The timestamps for the blocks before and after the checkpoint must have
|
||||
// timestamps which are also before and after the checkpoint, respectively
|
||||
// (due to the median time allowance this is not always the case)
|
||||
// - The block must not contain any strange transaction such as those with
|
||||
// nonstandard scripts
|
||||
//
|
||||
// The intent is that candidates are reviewed by a developer to make the final
|
||||
// decision and then manually added to the list of checkpoints for a network.
|
||||
|
|
|
@ -44,7 +44,7 @@ func (b *BlockChain) ParseClaimScripts(block *btcutil.Block, bn *blockNode, view
|
|||
}
|
||||
}
|
||||
|
||||
err := b.claimTrie.AppendBlock(bn == nil)
|
||||
err := b.claimTrie.AppendBlock()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "in append block")
|
||||
}
|
||||
|
@ -80,8 +80,8 @@ func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error {
|
|||
if e == nil {
|
||||
return errors.Errorf("missing input in view for %s", op.String())
|
||||
}
|
||||
cs, err := txscript.ExtractClaimScript(e.pkScript)
|
||||
if txscript.IsErrorCode(err, txscript.ErrNotClaimScript) {
|
||||
cs, err := txscript.DecodeClaimScript(e.pkScript)
|
||||
if err == txscript.ErrNotClaimScript {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -89,19 +89,19 @@ func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error {
|
|||
}
|
||||
|
||||
var id change.ClaimID
|
||||
name := cs.Name // name of the previous one (that we're now spending)
|
||||
name := cs.Name() // name of the previous one (that we're now spending)
|
||||
|
||||
switch cs.Opcode {
|
||||
switch cs.Opcode() {
|
||||
case txscript.OP_CLAIMNAME: // OP code from previous transaction
|
||||
id = change.NewClaimID(op) // claimID of the previous item now being spent
|
||||
h.spent[id.Key()] = normalization.NormalizeIfNecessary(name, ct.Height())
|
||||
err = ct.SpendClaim(name, op, id)
|
||||
case txscript.OP_UPDATECLAIM:
|
||||
copy(id[:], cs.ClaimID)
|
||||
copy(id[:], cs.ClaimID())
|
||||
h.spent[id.Key()] = normalization.NormalizeIfNecessary(name, ct.Height())
|
||||
err = ct.SpendClaim(name, op, id)
|
||||
case txscript.OP_SUPPORTCLAIM:
|
||||
copy(id[:], cs.ClaimID)
|
||||
copy(id[:], cs.ClaimID())
|
||||
err = ct.SpendSupport(name, op, id)
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -114,8 +114,8 @@ func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error {
|
|||
func (h *handler) handleTxOuts(ct *claimtrie.ClaimTrie) error {
|
||||
for i, txOut := range h.tx.MsgTx().TxOut {
|
||||
op := *wire.NewOutPoint(h.tx.Hash(), uint32(i))
|
||||
cs, err := txscript.ExtractClaimScript(txOut.PkScript)
|
||||
if txscript.IsErrorCode(err, txscript.ErrNotClaimScript) {
|
||||
cs, err := txscript.DecodeClaimScript(txOut.PkScript)
|
||||
if err == txscript.ErrNotClaimScript {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -123,21 +123,21 @@ func (h *handler) handleTxOuts(ct *claimtrie.ClaimTrie) error {
|
|||
}
|
||||
|
||||
var id change.ClaimID
|
||||
name := cs.Name
|
||||
name := cs.Name()
|
||||
amt := txOut.Value
|
||||
|
||||
switch cs.Opcode {
|
||||
switch cs.Opcode() {
|
||||
case txscript.OP_CLAIMNAME:
|
||||
id = change.NewClaimID(op)
|
||||
err = ct.AddClaim(name, op, id, amt)
|
||||
case txscript.OP_SUPPORTCLAIM:
|
||||
copy(id[:], cs.ClaimID)
|
||||
copy(id[:], cs.ClaimID())
|
||||
err = ct.AddSupport(name, op, amt, id)
|
||||
case txscript.OP_UPDATECLAIM:
|
||||
// old code wouldn't run the update if name or claimID didn't match existing data
|
||||
// that was a safety feature, but it should have rejected the transaction instead
|
||||
// TODO: reject transactions with invalid update commands
|
||||
copy(id[:], cs.ClaimID)
|
||||
copy(id[:], cs.ClaimID())
|
||||
normName := normalization.NormalizeIfNecessary(name, ct.Height())
|
||||
if !bytes.Equal(h.spent[id.Key()], normName) {
|
||||
node.LogOnce(fmt.Sprintf("Invalid update operation: name or ID mismatch at %d for: %s, %s",
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
package blockchain
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"compress/bzip2"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
@ -64,7 +63,7 @@ func isSupportedDbType(dbType string) bool {
|
|||
func loadBlocks(filename string) (blocks []*btcutil.Block, err error) {
|
||||
filename = filepath.Join("testdata/", filename)
|
||||
|
||||
var network = 0xd9b4bef9 // bitcoin's network ID
|
||||
var network = wire.MainNet
|
||||
var dr io.Reader
|
||||
var fi io.ReadCloser
|
||||
|
||||
|
@ -96,7 +95,7 @@ func loadBlocks(filename string) (blocks []*btcutil.Block, err error) {
|
|||
break
|
||||
}
|
||||
if rintbuf != uint32(network) {
|
||||
continue
|
||||
break
|
||||
}
|
||||
err = binary.Read(dr, binary.LittleEndian, &rintbuf)
|
||||
blocklen := rintbuf
|
||||
|
@ -106,12 +105,6 @@ func loadBlocks(filename string) (blocks []*btcutil.Block, err error) {
|
|||
// read block
|
||||
dr.Read(rbytes)
|
||||
|
||||
// inject claimtrie:
|
||||
tail := make([]byte, len(rbytes)-68)
|
||||
copy(tail, rbytes[68:])
|
||||
rbytes = append(rbytes[:68], bytes.Repeat([]byte{23}, chainhash.HashSize)...)
|
||||
rbytes = append(rbytes, tail...)
|
||||
|
||||
block, err = btcutil.NewBlockFromBytes(rbytes)
|
||||
if err != nil {
|
||||
return blocks, err
|
||||
|
|
|
@ -42,21 +42,18 @@ func HashToBig(hash *chainhash.Hash) *big.Int {
|
|||
// Like IEEE754 floating point, there are three basic components: the sign,
|
||||
// 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 |
|
||||
// -------------------------------------------------
|
||||
// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] |
|
||||
// -------------------------------------------------
|
||||
// -------------------------------------------------
|
||||
// | Exponent | Sign | Mantissa |
|
||||
// -------------------------------------------------
|
||||
// | 8 bits [31-24] | 1 bit [23] | 23 bits [22-00] |
|
||||
// -------------------------------------------------
|
||||
//
|
||||
// The formula to calculate N is:
|
||||
//
|
||||
// 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
|
||||
// which represent difficulty targets, thus there really is not a need for a
|
||||
|
|
|
@ -26,42 +26,42 @@ caller a high level of flexibility in how they want to react to certain events
|
|||
such as orphan blocks which need their parents requested and newly connected
|
||||
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
|
||||
series of validation rules. The following list serves as a general outline of
|
||||
those rules to provide some intuition into what is going on under the hood, but
|
||||
is by no means exhaustive:
|
||||
|
||||
- Reject duplicate blocks
|
||||
- Perform a series of sanity checks on the block and its transactions such as
|
||||
verifying proof of work, timestamps, number and character of transactions,
|
||||
transaction amounts, script complexity, and merkle root calculations
|
||||
- Compare the block against predetermined checkpoints for expected timestamps
|
||||
and difficulty based on elapsed time since the checkpoint
|
||||
- Save the most recent orphan blocks for a limited time in case their parent
|
||||
blocks become available
|
||||
- Stop processing if the block is an orphan as the rest of the processing
|
||||
depends on the block's position within the block chain
|
||||
- Perform a series of more thorough checks that depend on the block's position
|
||||
within the block chain such as verifying block difficulties adhere to
|
||||
difficulty retarget rules, timestamps are after the median of the last
|
||||
several blocks, all transactions are finalized, checkpoint blocks match, and
|
||||
block versions are in line with the previous blocks
|
||||
- Determine how the block fits into the chain and perform different actions
|
||||
accordingly in order to ensure any side chains which have higher difficulty
|
||||
than the main chain become the new main chain
|
||||
- When a block is being connected to the main chain (either through
|
||||
reorganization of a side chain to the main chain or just extending the
|
||||
main chain), perform further checks on the block's transactions such as
|
||||
verifying transaction duplicates, script complexity for the combination of
|
||||
connected scripts, coinbase maturity, double spends, and connected
|
||||
transaction values
|
||||
- Run the transaction scripts to verify the spender is allowed to spend the
|
||||
coins
|
||||
- Insert the block into the block database
|
||||
- Reject duplicate blocks
|
||||
- Perform a series of sanity checks on the block and its transactions such as
|
||||
verifying proof of work, timestamps, number and character of transactions,
|
||||
transaction amounts, script complexity, and merkle root calculations
|
||||
- Compare the block against predetermined checkpoints for expected timestamps
|
||||
and difficulty based on elapsed time since the checkpoint
|
||||
- Save the most recent orphan blocks for a limited time in case their parent
|
||||
blocks become available
|
||||
- Stop processing if the block is an orphan as the rest of the processing
|
||||
depends on the block's position within the block chain
|
||||
- Perform a series of more thorough checks that depend on the block's position
|
||||
within the block chain such as verifying block difficulties adhere to
|
||||
difficulty retarget rules, timestamps are after the median of the last
|
||||
several blocks, all transactions are finalized, checkpoint blocks match, and
|
||||
block versions are in line with the previous blocks
|
||||
- Determine how the block fits into the chain and perform different actions
|
||||
accordingly in order to ensure any side chains which have higher difficulty
|
||||
than the main chain become the new main chain
|
||||
- When a block is being connected to the main chain (either through
|
||||
reorganization of a side chain to the main chain or just extending the
|
||||
main chain), perform further checks on the block's transactions such as
|
||||
verifying transaction duplicates, script complexity for the combination of
|
||||
connected scripts, coinbase maturity, double spends, and connected
|
||||
transaction values
|
||||
- Run the transaction scripts to verify the spender is allowed to spend the
|
||||
coins
|
||||
- Insert the block into the block database
|
||||
|
||||
# Errors
|
||||
Errors
|
||||
|
||||
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
|
||||
|
@ -70,12 +70,12 @@ violations through type assertions. In addition, callers can programmatically
|
|||
determine the specific rule violation by examining the ErrorCode field of the
|
||||
type asserted blockchain.RuleError.
|
||||
|
||||
# Bitcoin Improvement Proposals
|
||||
Bitcoin Improvement Proposals
|
||||
|
||||
This package includes spec changes outlined by the following BIPs:
|
||||
|
||||
BIP0016 (https://en.bitcoin.it/wiki/BIP_0016)
|
||||
BIP0030 (https://en.bitcoin.it/wiki/BIP_0030)
|
||||
BIP0034 (https://en.bitcoin.it/wiki/BIP_0034)
|
||||
BIP0016 (https://en.bitcoin.it/wiki/BIP_0016)
|
||||
BIP0030 (https://en.bitcoin.it/wiki/BIP_0030)
|
||||
BIP0034 (https://en.bitcoin.it/wiki/BIP_0034)
|
||||
*/
|
||||
package blockchain
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
// Copyright (c) 2014-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/lbryio/lbcd/blockchain"
|
||||
"github.com/lbryio/lbcd/chaincfg"
|
||||
"github.com/lbryio/lbcd/database"
|
||||
_ "github.com/lbryio/lbcd/database/ffldb"
|
||||
btcutil "github.com/lbryio/lbcutil"
|
||||
)
|
||||
|
||||
// This example demonstrates how to create a new chain instance and use
|
||||
// ProcessBlock to attempt to add a block to the chain. As the package
|
||||
// overview documentation describes, this includes all of the Bitcoin consensus
|
||||
// rules. This example intentionally attempts to insert a duplicate genesis
|
||||
// block to illustrate how an invalid block is handled.
|
||||
func ExampleBlockChain_ProcessBlock() {
|
||||
// Create a new database to store the accepted blocks into. Typically
|
||||
// this would be opening an existing database and would not be deleting
|
||||
// and creating a new database like this, but it is done here so this is
|
||||
// a complete working example and does not leave temporary files laying
|
||||
// around.
|
||||
dbPath := filepath.Join(os.TempDir(), "exampleprocessblock")
|
||||
_ = os.RemoveAll(dbPath)
|
||||
db, err := database.Create("ffldb", dbPath, chaincfg.MainNetParams.Net)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create database: %v\n", err)
|
||||
return
|
||||
}
|
||||
defer os.RemoveAll(dbPath)
|
||||
defer db.Close()
|
||||
|
||||
// Create a new BlockChain instance using the underlying database for
|
||||
// the main bitcoin network. This example does not demonstrate some
|
||||
// of the other available configuration options such as specifying a
|
||||
// notification callback and signature cache. Also, the caller would
|
||||
// ordinarily keep a reference to the median time source and add time
|
||||
// values obtained from other peers on the network so the local time is
|
||||
// adjusted to be in agreement with other peers.
|
||||
chain, err := blockchain.New(&blockchain.Config{
|
||||
DB: db,
|
||||
ChainParams: &chaincfg.MainNetParams,
|
||||
TimeSource: blockchain.NewMedianTime(),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to create chain instance: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Process a block. For this example, we are going to intentionally
|
||||
// cause an error by trying to process the genesis block which already
|
||||
// exists.
|
||||
genesisBlock := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
|
||||
isMainChain, isOrphan, err := chain.ProcessBlock(genesisBlock,
|
||||
blockchain.BFNone)
|
||||
if err != nil {
|
||||
fmt.Printf("Failed to process block: %v\n", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("Block accepted. Is it on the main chain?: %v", isMainChain)
|
||||
fmt.Printf("Block accepted. Is it an orphan?: %v", isOrphan)
|
||||
|
||||
// Output:
|
||||
// Failed to process block: already have block 9c89283ba0f3227f6c03b70216b9f665f0118d5e0fa729cedf4fb34d6a34f463
|
||||
}
|
||||
|
||||
// This example demonstrates how to convert the compact "bits" in a block header
|
||||
// which represent the target difficulty to a big integer and display it using
|
||||
// the typical hex notation.
|
||||
func ExampleCompactToBig() {
|
||||
// Convert the bits from block 300000 in the main block chain.
|
||||
bits := uint32(419465580)
|
||||
targetDifficulty := blockchain.CompactToBig(bits)
|
||||
|
||||
// Display it in hex.
|
||||
fmt.Printf("%064x\n", targetDifficulty.Bytes())
|
||||
|
||||
// Output:
|
||||
// 0000000000000000896c00000000000000000000000000000000000000000000
|
||||
}
|
||||
|
||||
// This example demonstrates how to convert a target difficulty into the compact
|
||||
// "bits" in a block header which represent that target difficulty .
|
||||
func ExampleBigToCompact() {
|
||||
// Convert the target difficulty from block 300000 in the main block
|
||||
// chain to compact form.
|
||||
t := "0000000000000000896c00000000000000000000000000000000000000000000"
|
||||
targetDifficulty, success := new(big.Int).SetString(t, 16)
|
||||
if !success {
|
||||
fmt.Println("invalid target difficulty")
|
||||
return
|
||||
}
|
||||
bits := blockchain.BigToCompact(targetDifficulty)
|
||||
|
||||
fmt.Println(bits)
|
||||
|
||||
// Output:
|
||||
// 419465580
|
||||
}
|
|
@ -1,310 +0,0 @@
|
|||
// Copyright (c) 2016 The Decred developers
|
||||
// Copyright (c) 2016-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/blockchain"
|
||||
"github.com/lbryio/lbcd/blockchain/fullblocktests"
|
||||
"github.com/lbryio/lbcd/chaincfg"
|
||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||
"github.com/lbryio/lbcd/database"
|
||||
_ "github.com/lbryio/lbcd/database/ffldb"
|
||||
"github.com/lbryio/lbcd/txscript"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
btcutil "github.com/lbryio/lbcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
// testDbType is the database backend type to use for the tests.
|
||||
testDbType = "ffldb"
|
||||
|
||||
// testDbRoot is the root directory used to create all test databases.
|
||||
testDbRoot = "testdbs"
|
||||
|
||||
// blockDataNet is the expected network in the test block data.
|
||||
blockDataNet = wire.MainNet
|
||||
)
|
||||
|
||||
// filesExists returns whether or not the named file or directory exists.
|
||||
func fileExists(name string) bool {
|
||||
if _, err := os.Stat(name); err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// isSupportedDbType returns whether or not the passed database type is
|
||||
// currently supported.
|
||||
func isSupportedDbType(dbType string) bool {
|
||||
supportedDrivers := database.SupportedDrivers()
|
||||
for _, driver := range supportedDrivers {
|
||||
if dbType == driver {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// chainSetup is used to create a new db and chain instance with the genesis
|
||||
// block already inserted. In addition to the new chain instance, it returns
|
||||
// a teardown function the caller should invoke when done testing to clean up.
|
||||
func chainSetup(dbName string, params *chaincfg.Params) (*blockchain.BlockChain, func(), error) {
|
||||
if !isSupportedDbType(testDbType) {
|
||||
return nil, nil, fmt.Errorf("unsupported db type %v", testDbType)
|
||||
}
|
||||
|
||||
// Handle memory database specially since it doesn't need the disk
|
||||
// specific handling.
|
||||
var db database.DB
|
||||
var teardown func()
|
||||
if testDbType == "memdb" {
|
||||
ndb, err := database.Create(testDbType)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating db: %v", err)
|
||||
}
|
||||
db = ndb
|
||||
|
||||
// Setup a teardown function for cleaning up. This function is
|
||||
// returned to the caller to be invoked when it is done testing.
|
||||
teardown = func() {
|
||||
db.Close()
|
||||
}
|
||||
} else {
|
||||
// Create the root directory for test databases.
|
||||
if !fileExists(testDbRoot) {
|
||||
if err := os.MkdirAll(testDbRoot, 0700); err != nil {
|
||||
err := fmt.Errorf("unable to create test db "+
|
||||
"root: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create a new database to store the accepted blocks into.
|
||||
dbPath := filepath.Join(testDbRoot, dbName)
|
||||
_ = os.RemoveAll(dbPath)
|
||||
ndb, err := database.Create(testDbType, dbPath, blockDataNet)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("error creating db: %v", err)
|
||||
}
|
||||
db = ndb
|
||||
|
||||
// Setup a teardown function for cleaning up. This function is
|
||||
// returned to the caller to be invoked when it is done testing.
|
||||
teardown = func() {
|
||||
db.Close()
|
||||
os.RemoveAll(dbPath)
|
||||
os.RemoveAll(testDbRoot)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the chain params to ensure any modifications the tests do to
|
||||
// the chain parameters do not affect the global instance.
|
||||
paramsCopy := *params
|
||||
|
||||
// Create the main chain instance.
|
||||
chain, err := blockchain.New(&blockchain.Config{
|
||||
DB: db,
|
||||
ChainParams: ¶msCopy,
|
||||
Checkpoints: nil,
|
||||
TimeSource: blockchain.NewMedianTime(),
|
||||
SigCache: txscript.NewSigCache(1000),
|
||||
})
|
||||
if err != nil {
|
||||
teardown()
|
||||
err := fmt.Errorf("failed to create chain instance: %v", err)
|
||||
return nil, nil, err
|
||||
}
|
||||
return chain, teardown, nil
|
||||
}
|
||||
|
||||
// TestFullBlocks ensures all tests generated by the fullblocktests package
|
||||
// have the expected result when processed via ProcessBlock.
|
||||
func TestFullBlocks(t *testing.T) {
|
||||
tests, err := fullblocktests.Generate(false)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to generate tests: %v", err)
|
||||
}
|
||||
|
||||
// Create a new database and chain instance to run tests against.
|
||||
chain, teardownFunc, err := chainSetup("fullblocktest",
|
||||
fullblocktests.FbRegressionNetParams)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to setup chain instance: %v", err)
|
||||
return
|
||||
}
|
||||
defer teardownFunc()
|
||||
|
||||
// testAcceptedBlock attempts to process the block in the provided test
|
||||
// instance and ensures that it was accepted according to the flags
|
||||
// specified in the test.
|
||||
testAcceptedBlock := func(item fullblocktests.AcceptedBlock) {
|
||||
blockHeight := item.Height
|
||||
block := btcutil.NewBlock(item.Block)
|
||||
block.SetHeight(blockHeight)
|
||||
t.Logf("Testing block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
isMainChain, isOrphan, err := chain.ProcessBlock(block,
|
||||
blockchain.BFNone)
|
||||
if err != nil {
|
||||
t.Fatalf("block %q (hash %s, height %d) should "+
|
||||
"have been accepted: %v", item.Name,
|
||||
block.Hash(), blockHeight, err)
|
||||
}
|
||||
|
||||
// Ensure the main chain and orphan flags match the values
|
||||
// specified in the test.
|
||||
if isMainChain != item.IsMainChain {
|
||||
t.Fatalf("block %q (hash %s, height %d) unexpected main "+
|
||||
"chain flag -- got %v, want %v", item.Name,
|
||||
block.Hash(), blockHeight, isMainChain,
|
||||
item.IsMainChain)
|
||||
}
|
||||
if isOrphan != item.IsOrphan {
|
||||
t.Fatalf("block %q (hash %s, height %d) unexpected "+
|
||||
"orphan flag -- got %v, want %v", item.Name,
|
||||
block.Hash(), blockHeight, isOrphan,
|
||||
item.IsOrphan)
|
||||
}
|
||||
}
|
||||
|
||||
// testRejectedBlock attempts to process the block in the provided test
|
||||
// instance and ensures that it was rejected with the reject code
|
||||
// specified in the test.
|
||||
testRejectedBlock := func(item fullblocktests.RejectedBlock) {
|
||||
blockHeight := item.Height
|
||||
block := btcutil.NewBlock(item.Block)
|
||||
block.SetHeight(blockHeight)
|
||||
t.Logf("Testing block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
_, _, err := chain.ProcessBlock(block, blockchain.BFNone)
|
||||
if err == nil {
|
||||
t.Fatalf("block %q (hash %s, height %d) should not "+
|
||||
"have been accepted", item.Name, block.Hash(),
|
||||
blockHeight)
|
||||
}
|
||||
|
||||
// Ensure the error code is of the expected type and the reject
|
||||
// code matches the value specified in the test instance.
|
||||
rerr, ok := err.(blockchain.RuleError)
|
||||
if !ok {
|
||||
t.Fatalf("block %q (hash %s, height %d) returned "+
|
||||
"unexpected error type -- got %T, want "+
|
||||
"blockchain.RuleError", item.Name, block.Hash(),
|
||||
blockHeight, err)
|
||||
}
|
||||
if rerr.ErrorCode != item.RejectCode {
|
||||
t.Fatalf("block %q (hash %s, height %d) does not have "+
|
||||
"expected reject code -- got %v, want %v",
|
||||
item.Name, block.Hash(), blockHeight,
|
||||
rerr.ErrorCode, item.RejectCode)
|
||||
}
|
||||
}
|
||||
|
||||
// testRejectedNonCanonicalBlock attempts to decode the block in the
|
||||
// provided test instance and ensures that it failed to decode with a
|
||||
// message error.
|
||||
testRejectedNonCanonicalBlock := func(item fullblocktests.RejectedNonCanonicalBlock) {
|
||||
headerLen := len(item.RawBlock)
|
||||
if headerLen > 80 {
|
||||
headerLen = 80
|
||||
}
|
||||
blockHash := chainhash.DoubleHashH(item.RawBlock[0:headerLen])
|
||||
blockHeight := item.Height
|
||||
t.Logf("Testing block %s (hash %s, height %d)", item.Name,
|
||||
blockHash, blockHeight)
|
||||
|
||||
// Ensure there is an error due to deserializing the block.
|
||||
var msgBlock wire.MsgBlock
|
||||
err := msgBlock.BtcDecode(bytes.NewReader(item.RawBlock), 0, wire.BaseEncoding)
|
||||
if _, ok := err.(*wire.MessageError); !ok {
|
||||
t.Fatalf("block %q (hash %s, height %d) should have "+
|
||||
"failed to decode", item.Name, blockHash,
|
||||
blockHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// testOrphanOrRejectedBlock attempts to process the block in the
|
||||
// provided test instance and ensures that it was either accepted as an
|
||||
// orphan or rejected with a rule violation.
|
||||
testOrphanOrRejectedBlock := func(item fullblocktests.OrphanOrRejectedBlock) {
|
||||
blockHeight := item.Height
|
||||
block := btcutil.NewBlock(item.Block)
|
||||
block.SetHeight(blockHeight)
|
||||
t.Logf("Testing block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
_, isOrphan, err := chain.ProcessBlock(block, blockchain.BFNone)
|
||||
if err != nil {
|
||||
// Ensure the error code is of the expected type.
|
||||
if _, ok := err.(blockchain.RuleError); !ok {
|
||||
t.Fatalf("block %q (hash %s, height %d) "+
|
||||
"returned unexpected error type -- "+
|
||||
"got %T, want blockchain.RuleError",
|
||||
item.Name, block.Hash(), blockHeight,
|
||||
err)
|
||||
}
|
||||
}
|
||||
|
||||
if !isOrphan {
|
||||
t.Fatalf("block %q (hash %s, height %d) was accepted, "+
|
||||
"but is not considered an orphan", item.Name,
|
||||
block.Hash(), blockHeight)
|
||||
}
|
||||
}
|
||||
|
||||
// testExpectedTip ensures the current tip of the blockchain is the
|
||||
// block specified in the provided test instance.
|
||||
testExpectedTip := func(item fullblocktests.ExpectedTip) {
|
||||
blockHeight := item.Height
|
||||
block := btcutil.NewBlock(item.Block)
|
||||
block.SetHeight(blockHeight)
|
||||
t.Logf("Testing tip for block %s (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight)
|
||||
|
||||
// Ensure hash and height match.
|
||||
best := chain.BestSnapshot()
|
||||
if best.Hash != item.Block.BlockHash() ||
|
||||
best.Height != blockHeight {
|
||||
|
||||
t.Fatalf("block %q (hash %s, height %d) should be "+
|
||||
"the current tip -- got (hash %s, height %d)",
|
||||
item.Name, block.Hash(), blockHeight, best.Hash,
|
||||
best.Height)
|
||||
}
|
||||
}
|
||||
|
||||
for testNum, test := range tests {
|
||||
for itemNum, item := range test {
|
||||
switch item := item.(type) {
|
||||
case fullblocktests.AcceptedBlock:
|
||||
testAcceptedBlock(item)
|
||||
case fullblocktests.RejectedBlock:
|
||||
testRejectedBlock(item)
|
||||
case fullblocktests.RejectedNonCanonicalBlock:
|
||||
testRejectedNonCanonicalBlock(item)
|
||||
case fullblocktests.OrphanOrRejectedBlock:
|
||||
testOrphanOrRejectedBlock(item)
|
||||
case fullblocktests.ExpectedTip:
|
||||
testExpectedTip(item)
|
||||
default:
|
||||
t.Fatalf("test #%d, item #%d is not one of "+
|
||||
"the supported test instance types -- "+
|
||||
"got type: %T", testNum, itemNum, item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -31,7 +31,7 @@ const (
|
|||
// Intentionally defined here rather than using constants from codebase
|
||||
// to ensure consensus changes are detected.
|
||||
maxBlockSigOps = 20000
|
||||
maxBlockSize = 8000000
|
||||
maxBlockSize = 2000000
|
||||
minCoinbaseScriptLen = 2
|
||||
maxCoinbaseScriptLen = 100
|
||||
medianTimeBlocks = 11
|
||||
|
@ -342,8 +342,10 @@ func solveBlock(header *wire.BlockHeader) bool {
|
|||
return
|
||||
default:
|
||||
hdr.Nonce = i
|
||||
hash := hdr.BlockPoWHash()
|
||||
if blockchain.HashToBig(&hash).Cmp(targetDifficulty) <= 0 {
|
||||
hash := hdr.BlockHash()
|
||||
if blockchain.HashToBig(&hash).Cmp(
|
||||
targetDifficulty) <= 0 {
|
||||
|
||||
results <- sbResult{true, i}
|
||||
return
|
||||
}
|
||||
|
@ -464,9 +466,9 @@ func createSpendTxForTx(tx *wire.MsgTx, fee btcutil.Amount) *wire.MsgTx {
|
|||
// - A coinbase that pays the required subsidy to an OP_TRUE script
|
||||
// - When a spendable output is provided:
|
||||
// - A transaction that spends from the provided output the following outputs:
|
||||
// - One that pays the inputs amount minus 1 atom to an OP_TRUE script
|
||||
// - One that contains an OP_RETURN output with a random uint64 in order to
|
||||
// ensure the transaction has a unique hash
|
||||
// - One that pays the inputs amount minus 1 atom to an OP_TRUE script
|
||||
// - One that contains an OP_RETURN output with a random uint64 in order to
|
||||
// ensure the transaction has a unique hash
|
||||
//
|
||||
// Additionally, if one or more munge functions are specified, they will be
|
||||
// invoked with the block prior to solving it. This provides callers with the
|
||||
|
@ -809,7 +811,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
|
|||
|
||||
// Create a test generator instance initialized with the genesis block
|
||||
// as the tip.
|
||||
g, err := makeTestGenerator(FbRegressionNetParams)
|
||||
g, err := makeTestGenerator(regressionNetParams)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1442,7 +1444,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
|
|||
// Keep incrementing the nonce until the hash treated as
|
||||
// a uint256 is higher than the limit.
|
||||
b46.Header.Nonce++
|
||||
blockHash := b46.Header.BlockPoWHash()
|
||||
blockHash := b46.BlockHash()
|
||||
hashNum := blockchain.HashToBig(&blockHash)
|
||||
if hashNum.Cmp(g.params.PowLimit) >= 0 {
|
||||
break
|
||||
|
|
|
@ -54,7 +54,6 @@ var (
|
|||
Version: 1,
|
||||
PrevBlock: *newHashFromStr("0000000000000000000000000000000000000000000000000000000000000000"),
|
||||
MerkleRoot: *newHashFromStr("4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b"),
|
||||
ClaimTrie: chainhash.Hash{1}, // EmptyTrieHash
|
||||
Timestamp: time.Unix(1296688602, 0), // 2011-02-02 23:16:42 +0000 UTC
|
||||
Bits: 0x207fffff, // 545259519 [7fffff0000000000000000000000000000000000000000000000000000000000]
|
||||
Nonce: 2,
|
||||
|
@ -84,25 +83,23 @@ var (
|
|||
LockTime: 0,
|
||||
}},
|
||||
}
|
||||
|
||||
regTestGenesisBlockHash = regTestGenesisBlock.BlockHash()
|
||||
)
|
||||
|
||||
// FbRegressionNetParams defines the network parameters for the regression test
|
||||
// regressionNetParams defines the network parameters for the regression test
|
||||
// network.
|
||||
//
|
||||
// NOTE: The test generator intentionally does not use the existing definitions
|
||||
// in the chaincfg package since the intent is to be able to generate known
|
||||
// good tests which exercise that code. Using the chaincfg parameters would
|
||||
// allow them to change out from under the tests potentially invalidating them.
|
||||
var FbRegressionNetParams = &chaincfg.Params{
|
||||
var regressionNetParams = &chaincfg.Params{
|
||||
Name: "regtest",
|
||||
Net: wire.TestNet,
|
||||
DefaultPort: "18444",
|
||||
|
||||
// Chain parameters
|
||||
GenesisBlock: ®TestGenesisBlock,
|
||||
GenesisHash: ®TestGenesisBlockHash,
|
||||
GenesisHash: newHashFromStr("5bec7567af40504e0994db3b573c186fffcc4edefe096ff2e58d00523bd7e8a6"),
|
||||
PowLimit: regressionPowLimit,
|
||||
PowLimitBits: 0x207fffff,
|
||||
CoinbaseMaturity: 100,
|
||||
|
@ -116,7 +113,6 @@ var FbRegressionNetParams = &chaincfg.Params{
|
|||
ReduceMinDifficulty: true,
|
||||
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
|
||||
GenerateSupported: true,
|
||||
MinerConfirmationWindow: 1,
|
||||
|
||||
// Checkpoints ordered from oldest to newest.
|
||||
Checkpoints: nil,
|
||||
|
|
|
@ -27,9 +27,8 @@ type blockProgressLogger struct {
|
|||
|
||||
// newBlockProgressLogger returns a new block progress logger.
|
||||
// The progress message is templated as follows:
|
||||
//
|
||||
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
||||
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
||||
// {progressAction} {numProcessed} {blocks|block} in the last {timePeriod}
|
||||
// ({numTxs}, height {lastBlockHeight}, {lastBlockTimeStamp})
|
||||
func newBlockProgressLogger(progressMessage string, logger btclog.Logger) *blockProgressLogger {
|
||||
return &blockProgressLogger{
|
||||
lastBlockLogTime: time.Now(),
|
||||
|
|
|
@ -11,7 +11,6 @@ import (
|
|||
|
||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||
"github.com/lbryio/lbcd/txscript"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
btcutil "github.com/lbryio/lbcutil"
|
||||
)
|
||||
|
||||
|
@ -87,7 +86,7 @@ func HashMerkleBranches(left *chainhash.Hash, right *chainhash.Hash) *chainhash.
|
|||
//
|
||||
// The above stored as a linear array is as follows:
|
||||
//
|
||||
// [h1 h2 h3 h4 h12 h34 root]
|
||||
// [h1 h2 h3 h4 h12 h34 root]
|
||||
//
|
||||
// As the above shows, the merkle root is always the last element in the array.
|
||||
//
|
||||
|
@ -228,20 +227,6 @@ func ValidateWitnessCommitment(blk *btcutil.Block) error {
|
|||
// coinbase transaction MUST have exactly one witness element within
|
||||
// its witness data and that element must be exactly
|
||||
// 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
|
||||
if len(coinbaseWitness) != 1 {
|
||||
str := fmt.Sprintf("the coinbase transaction has %d items in "+
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestMerkle tests the BuildMerkleTreeStore API.
|
||||
func TestMerkle(t *testing.T) {
|
||||
block := GetBlock100000()
|
||||
merkles := BuildMerkleTreeStore(block.Transactions(), false)
|
||||
calculatedMerkleRoot := merkles[len(merkles)-1]
|
||||
wantMerkle := block.MsgBlock().Header.MerkleRoot
|
||||
if !wantMerkle.IsEqual(calculatedMerkleRoot) {
|
||||
t.Errorf("BuildMerkleTreeStore: merkle root mismatch - "+
|
||||
"got %v, want %v", calculatedMerkleRoot, wantMerkle)
|
||||
}
|
||||
}
|
|
@ -50,9 +50,9 @@ func (n NotificationType) String() string {
|
|||
// Notification defines notification that is sent to the caller via the callback
|
||||
// function provided during the call to New and consists of a notification type
|
||||
// as well as associated data that depends on the type as follows:
|
||||
// - NTBlockAccepted: *btcutil.Block
|
||||
// - NTBlockConnected: *btcutil.Block
|
||||
// - NTBlockDisconnected: *btcutil.Block
|
||||
// - NTBlockAccepted: *btcutil.Block
|
||||
// - NTBlockConnected: *btcutil.Block
|
||||
// - NTBlockDisconnected: *btcutil.Block
|
||||
type Notification struct {
|
||||
Type NotificationType
|
||||
Data interface{}
|
||||
|
|
|
@ -29,10 +29,6 @@ const (
|
|||
// not be performed.
|
||||
BFNoPoWCheck
|
||||
|
||||
// BFNoDupBlockCheck signals if the block should skip existence
|
||||
// checks.
|
||||
BFNoDupBlockCheck
|
||||
|
||||
// BFNone is a convenience value to specifically indicate no flags.
|
||||
BFNone BehaviorFlags = 0
|
||||
)
|
||||
|
@ -152,26 +148,24 @@ func (b *BlockChain) ProcessBlock(block *btcutil.Block, flags BehaviorFlags) (bo
|
|||
blockHash := block.Hash()
|
||||
log.Tracef("Processing block %v", blockHash)
|
||||
|
||||
if flags&BFNoDupBlockCheck != BFNoDupBlockCheck {
|
||||
// The block must not already exist in the main chain or side chains.
|
||||
exists, err := b.blockExists(blockHash)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
if exists {
|
||||
str := fmt.Sprintf("already have block %v", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
// The block must not already exist in the main chain or side chains.
|
||||
exists, err := b.blockExists(blockHash)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
if exists {
|
||||
str := fmt.Sprintf("already have block %v", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
// The block must not already exist as an orphan.
|
||||
if _, exists := b.orphans[*blockHash]; exists {
|
||||
str := fmt.Sprintf("already have block (orphan) %v", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
// The block must not already exist as an orphan.
|
||||
if _, exists := b.orphans[*blockHash]; exists {
|
||||
str := fmt.Sprintf("already have block (orphan) %v", blockHash)
|
||||
return false, false, ruleError(ErrDuplicateBlock, str)
|
||||
}
|
||||
|
||||
// Perform preliminary sanity checks on the block and its transactions.
|
||||
err := checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags)
|
||||
err = checkBlockSanity(block, b.chainParams.PowLimit, b.timeSource, flags)
|
||||
if err != nil {
|
||||
return false, false, err
|
||||
}
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/txscript"
|
||||
)
|
||||
|
||||
// TestCheckBlockScripts ensures that validating the all of the scripts in a
|
||||
// known-good block doesn't return an error.
|
||||
func TestCheckBlockScripts(t *testing.T) {
|
||||
testBlockNum := 277647
|
||||
blockDataFile := fmt.Sprintf("%d.dat.bz2", testBlockNum)
|
||||
blocks, err := loadBlocks(blockDataFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error loading file: %v\n", err)
|
||||
return
|
||||
}
|
||||
if len(blocks) > 1 {
|
||||
t.Errorf("The test block file must only have one block in it")
|
||||
return
|
||||
}
|
||||
if len(blocks) == 0 {
|
||||
t.Errorf("The test block file may not be empty")
|
||||
return
|
||||
}
|
||||
|
||||
storeDataFile := fmt.Sprintf("%d.utxostore.bz2", testBlockNum)
|
||||
view, err := loadUtxoView(storeDataFile)
|
||||
if err != nil {
|
||||
t.Errorf("Error loading txstore: %v\n", err)
|
||||
return
|
||||
}
|
||||
|
||||
scriptFlags := txscript.ScriptBip16
|
||||
err = checkBlockScripts(blocks[0], view, scriptFlags, nil, nil)
|
||||
if err != nil {
|
||||
t.Errorf("Transaction script validation failed: %v\n", err)
|
||||
return
|
||||
}
|
||||
}
|
BIN
blockchain/testdata/blk_0_to_4.dat.bz2
vendored
Normal file
BIN
blockchain/testdata/blk_0_to_4.dat.bz2
vendored
Normal file
Binary file not shown.
BIN
blockchain/testdata/blk_3A.dat.bz2
vendored
Normal file
BIN
blockchain/testdata/blk_3A.dat.bz2
vendored
Normal file
Binary file not shown.
BIN
blockchain/testdata/blk_4A.dat.bz2
vendored
Normal file
BIN
blockchain/testdata/blk_4A.dat.bz2
vendored
Normal file
Binary file not shown.
BIN
blockchain/testdata/blk_5A.dat.bz2
vendored
Normal file
BIN
blockchain/testdata/blk_5A.dat.bz2
vendored
Normal file
Binary file not shown.
180
blockchain/testdata/reorgtest.hex
vendored
Normal file
180
blockchain/testdata/reorgtest.hex
vendored
Normal file
|
@ -0,0 +1,180 @@
|
|||
File path: reorgTest/blk_0_to_4.dat
|
||||
|
||||
Block 0:
|
||||
f9beb4d9
|
||||
1d010000
|
||||
|
||||
01000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 3ba3edfd 7a7b12b2 7ac72c3e 67768f61 7fc81bc3 888a5132 3a9fb8aa
|
||||
4b1e5e4a 29ab5f49 ffff001d 1dac2b7c
|
||||
01
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff4d04ff ff001d01 04455468 65205469 6d657320 30332f4a
|
||||
616e2f32 30303920 4368616e 63656c6c 6f72206f 6e206272 696e6b20 6f662073
|
||||
65636f6e 64206261 696c6f75 7420666f 72206261 6e6b73ff ffffff01 00f2052a
|
||||
01000000 43410467 8afdb0fe 55482719 67f1a671 30b7105c d6a828e0 3909a679
|
||||
62e0ea1f 61deb649 f6bc3f4c ef38c4f3 5504e51e c112de5c 384df7ba 0b8d578a
|
||||
4c702b6b f11d5fac 00000000
|
||||
Block 1:
|
||||
f9beb4d9
|
||||
d4000000
|
||||
|
||||
01000000 6fe28c0a b6f1b372 c1a6a246 ae63f74f 931e8365 e15a089c 68d61900
|
||||
00000000 3bbd67ad e98fbbb7 0718cd80 f9e9acf9 3b5fae91 7bb2b41d 4c3bb82c
|
||||
77725ca5 81ad5f49 ffff001d 44e69904
|
||||
01
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff04722f 2e2bffff ffff0100 f2052a01 00000043 41046868
|
||||
0737c76d abb801cb 2204f57d be4e4579 e4f710cd 67dc1b42 27592c81 e9b5cf02
|
||||
b5ac9e8b 4c9f49be 5251056b 6a6d011e 4c37f6b6 d17ede6b 55faa235 19e2ac00
|
||||
000000
|
||||
Block 2:
|
||||
f9beb4d9
|
||||
95010000
|
||||
|
||||
01000000 13ca7940 4c11c63e ca906bbd f190b751 2872b857 1b5143ae e8cb5737
|
||||
00000000 fc07c983 d7391736 0aeda657 29d0d4d3 2533eb84 76ee9d64 aa27538f
|
||||
9b4fc00a d9af5f49 ffff001d 630bea22
|
||||
02
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff04eb96 14e5ffff ffff0100 f2052a01 00000043 41046868
|
||||
0737c76d abb801cb 2204f57d be4e4579 e4f710cd 67dc1b42 27592c81 e9b5cf02
|
||||
b5ac9e8b 4c9f49be 5251056b 6a6d011e 4c37f6b6 d17ede6b 55faa235 19e2ac00
|
||||
000000
|
||||
|
||||
01000000 0163451d 1002611c 1388d5ba 4ddfdf99 196a86b5 990fb5b0 dc786207
|
||||
4fdcb8ee d2000000 004a4930 46022100 3dde52c6 5e339f45 7fe1015e 70eed208
|
||||
872eb71e dd484c07 206b190e cb2ec3f8 02210011 c78dcfd0 3d43fa63 61242a33
|
||||
6291ba2a 8c1ef5bc d5472126 2468f2bf 8dee4d01 ffffffff 0200ca9a 3b000000
|
||||
001976a9 14cb2abd e8bccacc 32e893df 3a054b9e f7f227a4 ce88ac00 286bee00
|
||||
00000019 76a914ee 26c56fc1 d942be8d 7a24b2a1 001dd894 69398088 ac000000
|
||||
00
|
||||
Block 3:
|
||||
f9beb4d9
|
||||
96020000
|
||||
|
||||
01000000 7d338254 0506faab 0d4cf179 45dda023 49db51f9 6233f24c 28002258
|
||||
00000000 4806fe80 bf85931b 882ea645 77ca5a03 22bb8af2 3f277b20 55f160cd
|
||||
972c8e8b 31b25f49 ffff001d e8f0c653
|
||||
03
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff044abd 8159ffff ffff0100 f2052a01 00000043 4104b95c
|
||||
249d84f4 17e3e395 a1274254 28b54067 1cc15881 eb828c17 b722a53f c599e21c
|
||||
a5e56c90 f340988d 3933acc7 6beb832f d64cab07 8ddf3ce7 32923031 d1a8ac00
|
||||
000000
|
||||
|
||||
01000000 01f287b5 e067e1cf 80f7da8a f89917b5 505094db d82412d9 35b665eb
|
||||
bad253d3 77010000 008c4930 46022100 96ee0d02 b35fd61e 4960b44f f396f67e
|
||||
01fe17f9 de4e0c17 b6a963bd ab2b50a6 02210034 920d4daa 7e9f8abe 5675c931
|
||||
495809f9 0b9c1189 d05fbaf1 dd6696a5 b0d8f301 41046868 0737c76d abb801cb
|
||||
2204f57d be4e4579 e4f710cd 67dc1b42 27592c81 e9b5cf02 b5ac9e8b 4c9f49be
|
||||
5251056b 6a6d011e 4c37f6b6 d17ede6b 55faa235 19e2ffff ffff0100 286bee00
|
||||
00000019 76a914c5 22664fb0 e55cdc5c 0cea73b4 aad97ec8 34323288 ac000000
|
||||
00
|
||||
|
||||
01000000 01f287b5 e067e1cf 80f7da8a f89917b5 505094db d82412d9 35b665eb
|
||||
bad253d3 77000000 008c4930 46022100 b08b922a c4bde411 1c229f92 9fe6eb6a
|
||||
50161f98 1f4cf47e a9214d35 bf74d380 022100d2 f6640327 e677a1e1 cc474991
|
||||
b9a48ba5 bd1e0c94 d1c8df49 f7b0193b 7ea4fa01 4104b95c 249d84f4 17e3e395
|
||||
a1274254 28b54067 1cc15881 eb828c17 b722a53f c599e21c a5e56c90 f340988d
|
||||
3933acc7 6beb832f d64cab07 8ddf3ce7 32923031 d1a8ffff ffff0100 ca9a3b00
|
||||
00000019 76a914c5 22664fb0 e55cdc5c 0cea73b4 aad97ec8 34323288 ac000000
|
||||
00
|
||||
|
||||
Block 4:
|
||||
f9beb4d9
|
||||
73010000
|
||||
|
||||
01000000 5da36499 06f35e09 9be42a1d 87b6dd42 11bc1400 6c220694 0807eaae
|
||||
00000000 48eeeaed 2d9d8522 e6201173 743823fd 4b87cd8a ca8e6408 ec75ca38
|
||||
302c2ff0 89b45f49 ffff001d 00530839
|
||||
02
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff04d41d 2213ffff ffff0100 f2052a01 00000043 4104678a
|
||||
fdb0fe55 48271967 f1a67130 b7105cd6 a828e039 09a67962 e0ea1f61 deb649f6
|
||||
bc3f4cef 38c4f355 04e51ec1 12de5c38 4df7ba0b 8d578a4c 702b6bf1 1d5fac00
|
||||
000000
|
||||
|
||||
01000000 0163451d 1002611c 1388d5ba 4ddfdf99 196a86b5 990fb5b0 dc786207
|
||||
4fdcb8ee d2000000 004a4930 46022100 8c8fd57b 48762135 8d8f3e69 19f33e08
|
||||
804736ff 83db47aa 248512e2 6df9b8ba 022100b0 c59e5ee7 bfcbfcd1 a4d83da9
|
||||
55fb260e fda7f42a 25522625 a3d6f2d9 1174a701 ffffffff 0100f205 2a010000
|
||||
001976a9 14c52266 4fb0e55c dc5c0cea 73b4aad9 7ec83432 3288ac00 000000
|
||||
|
||||
File path: reorgTest/blk_3A.dat
|
||||
Block 3A:
|
||||
f9beb4d9
|
||||
96020000
|
||||
|
||||
01000000 7d338254 0506faab 0d4cf179 45dda023 49db51f9 6233f24c 28002258
|
||||
00000000 5a15f573 1177a353 bdca7aab 20e16624 dfe90adc 70accadc 68016732
|
||||
302c20a7 31b25f49 ffff001d 6a901440
|
||||
03
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff04ad1b e7d5ffff ffff0100 f2052a01 00000043 4104ed83
|
||||
704c95d8 29046f1a c2780621 1132102c 34e9ac7f fa1b7111 0658e5b9 d1bdedc4
|
||||
16f5cefc 1db0625c d0c75de8 192d2b59 2d7e3b00 bcfb4a0e 860d880f d1fcac00
|
||||
000000
|
||||
|
||||
01000000 01f287b5 e067e1cf 80f7da8a f89917b5 505094db d82412d9 35b665eb
|
||||
bad253d3 77010000 008c4930 46022100 96ee0d02 b35fd61e 4960b44f f396f67e
|
||||
01fe17f9 de4e0c17 b6a963bd ab2b50a6 02210034 920d4daa 7e9f8abe 5675c931
|
||||
495809f9 0b9c1189 d05fbaf1 dd6696a5 b0d8f301 41046868 0737c76d abb801cb
|
||||
2204f57d be4e4579 e4f710cd 67dc1b42 27592c81 e9b5cf02 b5ac9e8b 4c9f49be
|
||||
5251056b 6a6d011e 4c37f6b6 d17ede6b 55faa235 19e2ffff ffff0100 286bee00
|
||||
00000019 76a914c5 22664fb0 e55cdc5c 0cea73b4 aad97ec8 34323288 ac000000
|
||||
00
|
||||
|
||||
01000000 01f287b5 e067e1cf 80f7da8a f89917b5 505094db d82412d9 35b665eb
|
||||
bad253d3 77000000 008c4930 46022100 9cc67ddd aa6f592a 6b2babd4 d6ff954f
|
||||
25a784cf 4fe4bb13 afb9f49b 08955119 022100a2 d99545b7 94080757 fcf2b563
|
||||
f2e91287 86332f46 0ec6b90f f085fb28 41a69701 4104b95c 249d84f4 17e3e395
|
||||
a1274254 28b54067 1cc15881 eb828c17 b722a53f c599e21c a5e56c90 f340988d
|
||||
3933acc7 6beb832f d64cab07 8ddf3ce7 32923031 d1a8ffff ffff0100 ca9a3b00
|
||||
00000019 76a914ee 26c56fc1 d942be8d 7a24b2a1 001dd894 69398088 ac000000
|
||||
00
|
||||
|
||||
File path: reorgTest/blk_4A.dat
|
||||
Block 4A:
|
||||
f9beb4d9
|
||||
d4000000
|
||||
|
||||
01000000 aae77468 2205667d 4f413a58 47cc8fe8 9795f1d5 645d5b24 1daf3c92
|
||||
00000000 361c9cde a09637a0 d0c05c3b 4e7a5d91 9edb184a 0a4c7633 d92e2ddd
|
||||
f04cb854 89b45f49 ffff001d 9e9aa1e8
|
||||
01
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff0401b8 f3eaffff ffff0100 f2052a01 00000043 4104678a
|
||||
fdb0fe55 48271967 f1a67130 b7105cd6 a828e039 09a67962 e0ea1f61 deb649f6
|
||||
bc3f4cef 38c4f355 04e51ec1 12de5c38 4df7ba0b 8d578a4c 702b6bf1 1d5fac00
|
||||
000000
|
||||
|
||||
File path: reorgTest/blk_5A.dat
|
||||
Block 5A:
|
||||
f9beb4d9
|
||||
73010000
|
||||
|
||||
01000000 ebc7d0de 9c31a71b 7f41d275 2c080ba4 11e1854b d45cb2cf 8c1e4624
|
||||
00000000 a607774b 79b8eb50 b52a5a32 c1754281 ec67f626 9561df28 57d1fe6a
|
||||
ea82c696 e1b65f49 ffff001d 4a263577
|
||||
02
|
||||
|
||||
01000000 01000000 00000000 00000000 00000000 00000000 00000000 00000000
|
||||
00000000 00ffffff ff049971 0c7dffff ffff0100 f2052a01 00000043 4104678a
|
||||
fdb0fe55 48271967 f1a67130 b7105cd6 a828e039 09a67962 e0ea1f61 deb649f6
|
||||
bc3f4cef 38c4f355 04e51ec1 12de5c38 4df7ba0b 8d578a4c 702b6bf1 1d5fac00
|
||||
000000
|
||||
|
||||
01000000 0163451d 1002611c 1388d5ba 4ddfdf99 196a86b5 990fb5b0 dc786207
|
||||
4fdcb8ee d2000000 004a4930 46022100 8c8fd57b 48762135 8d8f3e69 19f33e08
|
||||
804736ff 83db47aa 248512e2 6df9b8ba 022100b0 c59e5ee7 bfcbfcd1 a4d83da9
|
||||
55fb260e fda7f42a 25522625 a3d6f2d9 1174a701 ffffffff 0100f205 2a010000
|
||||
001976a9 14c52266 4fb0e55c dc5c0cea 73b4aad9 7ec83432 3288ac00 000000
|
||||
|
|
@ -232,25 +232,24 @@ func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, t
|
|||
//
|
||||
// The legacy format is as follows:
|
||||
//
|
||||
// <version><height><header code><unspentness bitmap>[<compressed txouts>,...]
|
||||
// <version><height><header code><unspentness bitmap>[<compressed txouts>,...]
|
||||
//
|
||||
// Field Type Size
|
||||
// version VLQ variable
|
||||
// block height VLQ variable
|
||||
// header code VLQ variable
|
||||
// unspentness bitmap []byte variable
|
||||
// compressed txouts
|
||||
// compressed amount VLQ variable
|
||||
// compressed script []byte variable
|
||||
// Field Type Size
|
||||
// version VLQ variable
|
||||
// block height VLQ variable
|
||||
// header code VLQ variable
|
||||
// unspentness bitmap []byte variable
|
||||
// compressed txouts
|
||||
// compressed amount VLQ variable
|
||||
// compressed script []byte variable
|
||||
//
|
||||
// The serialized header code format is:
|
||||
//
|
||||
// bit 0 - containing transaction is a coinbase
|
||||
// bit 1 - output zero is unspent
|
||||
// bit 2 - output one is unspent
|
||||
// bits 3-x - number of bytes in unspentness bitmap. When both bits 1 and 2
|
||||
// are unset, it encodes N-1 since there must be at least one unspent
|
||||
// output.
|
||||
// bit 0 - containing transaction is a coinbase
|
||||
// bit 1 - output zero is unspent
|
||||
// bit 2 - output one is unspent
|
||||
// bits 3-x - number of bytes in unspentness bitmap. When both bits 1 and 2
|
||||
// are unset, it encodes N-1 since there must be at least one unspent
|
||||
// output.
|
||||
//
|
||||
// The rationale for the header code scheme is as follows:
|
||||
// - Transactions which only pay to a single output and a change output are
|
||||
|
@ -270,65 +269,65 @@ func determineMainChainBlocks(blocksMap map[chainhash.Hash]*blockChainContext, t
|
|||
// From tx in main blockchain:
|
||||
// Blk 1, 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098
|
||||
//
|
||||
// 010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52
|
||||
// <><><><------------------------------------------------------------------>
|
||||
// | | \--------\ |
|
||||
// | height | compressed txout 0
|
||||
// version header code
|
||||
// 010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52
|
||||
// <><><><------------------------------------------------------------------>
|
||||
// | | \--------\ |
|
||||
// | height | compressed txout 0
|
||||
// version header code
|
||||
//
|
||||
// - version: 1
|
||||
// - height: 1
|
||||
// - header code: 0x03 (coinbase, output zero unspent, 0 bytes of unspentness)
|
||||
// - unspentness: Nothing since it is zero bytes
|
||||
// - compressed txout 0:
|
||||
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 BTC)
|
||||
// - 0x04: special script type pay-to-pubkey
|
||||
// - 0x96...52: x-coordinate of the pubkey
|
||||
// - version: 1
|
||||
// - height: 1
|
||||
// - header code: 0x03 (coinbase, output zero unspent, 0 bytes of unspentness)
|
||||
// - unspentness: Nothing since it is zero bytes
|
||||
// - compressed txout 0:
|
||||
// - 0x32: VLQ-encoded compressed amount for 5000000000 (50 BTC)
|
||||
// - 0x04: special script type pay-to-pubkey
|
||||
// - 0x96...52: x-coordinate of the pubkey
|
||||
//
|
||||
// Example 2:
|
||||
// From tx in main blockchain:
|
||||
// Blk 113931, 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f
|
||||
//
|
||||
// 0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58
|
||||
// <><----><><><------------------------------------------><-------------------------------------------->
|
||||
// | | | \-------------------\ | |
|
||||
// version | \--------\ unspentness | compressed txout 2
|
||||
// height header code compressed txout 0
|
||||
// 0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58
|
||||
// <><----><><><------------------------------------------><-------------------------------------------->
|
||||
// | | | \-------------------\ | |
|
||||
// version | \--------\ unspentness | compressed txout 2
|
||||
// height header code compressed txout 0
|
||||
//
|
||||
// - version: 1
|
||||
// - height: 113931
|
||||
// - header code: 0x0a (output zero unspent, 1 byte in unspentness bitmap)
|
||||
// - unspentness: [0x01] (bit 0 is set, so output 0+2 = 2 is unspent)
|
||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||
// - compressed txout 0:
|
||||
// - 0x12: VLQ-encoded compressed amount for 20000000 (0.2 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xe2...8a: pubkey hash
|
||||
// - compressed txout 2:
|
||||
// - 0x8009: VLQ-encoded compressed amount for 15000000 (0.15 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xb8...58: pubkey hash
|
||||
// - version: 1
|
||||
// - height: 113931
|
||||
// - header code: 0x0a (output zero unspent, 1 byte in unspentness bitmap)
|
||||
// - unspentness: [0x01] (bit 0 is set, so output 0+2 = 2 is unspent)
|
||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||
// - compressed txout 0:
|
||||
// - 0x12: VLQ-encoded compressed amount for 20000000 (0.2 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xe2...8a: pubkey hash
|
||||
// - compressed txout 2:
|
||||
// - 0x8009: VLQ-encoded compressed amount for 15000000 (0.15 BTC)
|
||||
// - 0x00: special script type pay-to-pubkey-hash
|
||||
// - 0xb8...58: pubkey hash
|
||||
//
|
||||
// Example 3:
|
||||
// From tx in main blockchain:
|
||||
// Blk 338156, 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620
|
||||
//
|
||||
// 0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6
|
||||
// <><----><><----><-------------------------------------------------->
|
||||
// | | | \-----------------\ |
|
||||
// version | \--------\ unspentness |
|
||||
// height header code compressed txout 22
|
||||
// 0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6
|
||||
// <><----><><----><-------------------------------------------------->
|
||||
// | | | \-----------------\ |
|
||||
// version | \--------\ unspentness |
|
||||
// height header code compressed txout 22
|
||||
//
|
||||
// - version: 1
|
||||
// - height: 338156
|
||||
// - header code: 0x10 (2+1 = 3 bytes in unspentness bitmap)
|
||||
// NOTE: It's +1 since neither bit 1 nor 2 are set, so N-1 is encoded.
|
||||
// - unspentness: [0x00 0x00 0x10] (bit 20 is set, so output 20+2 = 22 is unspent)
|
||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||
// - compressed txout 22:
|
||||
// - 0x8ba5b9e763: VLQ-encoded compressed amount for 366875659 (3.66875659 BTC)
|
||||
// - 0x01: special script type pay-to-script-hash
|
||||
// - 0x1d...e6: script hash
|
||||
// - version: 1
|
||||
// - height: 338156
|
||||
// - header code: 0x10 (2+1 = 3 bytes in unspentness bitmap)
|
||||
// NOTE: It's +1 since neither bit 1 nor 2 are set, so N-1 is encoded.
|
||||
// - unspentness: [0x00 0x00 0x10] (bit 20 is set, so output 20+2 = 22 is unspent)
|
||||
// NOTE: It's +2 since the first two outputs are encoded in the header code
|
||||
// - compressed txout 22:
|
||||
// - 0x8ba5b9e763: VLQ-encoded compressed amount for 366875659 (3.66875659 BTC)
|
||||
// - 0x01: special script type pay-to-script-hash
|
||||
// - 0x1d...e6: script hash
|
||||
func deserializeUtxoEntryV0(serialized []byte) (map[uint32]*UtxoEntry, error) {
|
||||
// Deserialize the version.
|
||||
//
|
||||
|
|
|
@ -334,8 +334,8 @@ func CheckTransactionSanity(tx *btcutil.Tx, enforceSoftFork bool) error {
|
|||
// target difficulty as claimed.
|
||||
//
|
||||
// The flags modify the behavior of this function as follows:
|
||||
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
|
||||
// difficulty is not performed.
|
||||
// - BFNoPoWCheck: The check to ensure the block hash is less than the target
|
||||
// difficulty is not performed.
|
||||
func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags BehaviorFlags) error {
|
||||
// The target difficulty must be larger than zero.
|
||||
target := CompactToBig(header.Bits)
|
||||
|
@ -669,8 +669,8 @@ func checkSerializedHeight(coinbaseTx *btcutil.Tx, wantHeight int32) error {
|
|||
// which depend on its position within the block chain.
|
||||
//
|
||||
// The flags modify the behavior of this function as follows:
|
||||
// - BFFastAdd: All checks except those involving comparing the header against
|
||||
// the checkpoints are not performed.
|
||||
// - BFFastAdd: All checks except those involving comparing the header against
|
||||
// the checkpoints are not performed.
|
||||
//
|
||||
// This function MUST be called with the chain state lock held (for writes).
|
||||
func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode *blockNode, flags BehaviorFlags) error {
|
||||
|
@ -748,8 +748,8 @@ func (b *BlockChain) checkBlockHeaderContext(header *wire.BlockHeader, prevNode
|
|||
// on its position within the block chain.
|
||||
//
|
||||
// The flags modify the behavior of this function as follows:
|
||||
// - BFFastAdd: The transaction are not checked to see if they are finalized
|
||||
// and the somewhat expensive BIP0034 validation is not performed.
|
||||
// - BFFastAdd: The transaction are not checked to see if they are finalized
|
||||
// and the somewhat expensive BIP0034 validation is not performed.
|
||||
//
|
||||
// The flags are also passed to checkBlockHeaderContext. See its documentation
|
||||
// for how the flags modify its behavior.
|
||||
|
|
|
@ -1,161 +0,0 @@
|
|||
// Copyright (c) 2013-2017 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package blockchain
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/lbryio/lbcd/chaincfg"
|
||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
btcutil "github.com/lbryio/lbcutil"
|
||||
)
|
||||
|
||||
// TestSequenceLocksActive tests the SequenceLockActive function to ensure it
|
||||
// works as expected in all possible combinations/scenarios.
|
||||
func TestSequenceLocksActive(t *testing.T) {
|
||||
seqLock := func(h int32, s int64) *SequenceLock {
|
||||
return &SequenceLock{
|
||||
Seconds: s,
|
||||
BlockHeight: h,
|
||||
}
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
seqLock *SequenceLock
|
||||
blockHeight int32
|
||||
mtp time.Time
|
||||
|
||||
want bool
|
||||
}{
|
||||
// Block based sequence lock with equal block height.
|
||||
{seqLock: seqLock(1000, -1), blockHeight: 1001, mtp: time.Unix(9, 0), want: true},
|
||||
|
||||
// Time based sequence lock with mtp past the absolute time.
|
||||
{seqLock: seqLock(-1, 30), blockHeight: 2, mtp: time.Unix(31, 0), want: true},
|
||||
|
||||
// Block based sequence lock with current height below seq lock block height.
|
||||
{seqLock: seqLock(1000, -1), blockHeight: 90, mtp: time.Unix(9, 0), want: false},
|
||||
|
||||
// Time based sequence lock with current time before lock time.
|
||||
{seqLock: seqLock(-1, 30), blockHeight: 2, mtp: time.Unix(29, 0), want: false},
|
||||
|
||||
// Block based sequence lock at the same height, so shouldn't yet be active.
|
||||
{seqLock: seqLock(1000, -1), blockHeight: 1000, mtp: time.Unix(9, 0), want: false},
|
||||
|
||||
// Time based sequence lock with current time equal to lock time, so shouldn't yet be active.
|
||||
{seqLock: seqLock(-1, 30), blockHeight: 2, mtp: time.Unix(30, 0), want: false},
|
||||
}
|
||||
|
||||
t.Logf("Running %d sequence locks tests", len(tests))
|
||||
for i, test := range tests {
|
||||
got := SequenceLockActive(test.seqLock,
|
||||
test.blockHeight, test.mtp)
|
||||
if got != test.want {
|
||||
t.Fatalf("SequenceLockActive #%d got %v want %v", i,
|
||||
got, test.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckBlockSanity tests the CheckBlockSanity function to ensure it works
|
||||
// as expected.
|
||||
func TestCheckBlockSanity(t *testing.T) {
|
||||
powLimit := chaincfg.MainNetParams.PowLimit
|
||||
block := GetBlock100000()
|
||||
timeSource := NewMedianTime()
|
||||
err := CheckBlockSanity(block, powLimit, timeSource)
|
||||
if err != nil {
|
||||
t.Errorf("CheckBlockSanity: %v", err)
|
||||
}
|
||||
|
||||
// Ensure a block that has a timestamp with a precision higher than one
|
||||
// second fails.
|
||||
timestamp := block.MsgBlock().Header.Timestamp
|
||||
block.MsgBlock().Header.Timestamp = timestamp.Add(time.Nanosecond)
|
||||
err = CheckBlockSanity(block, powLimit, timeSource)
|
||||
if err == nil {
|
||||
t.Errorf("CheckBlockSanity: error is nil when it shouldn't be")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCheckSerializedHeight tests the checkSerializedHeight function with
|
||||
// various serialized heights and also does negative tests to ensure errors
|
||||
// and handled properly.
|
||||
func TestCheckSerializedHeight(t *testing.T) {
|
||||
// Create an empty coinbase template to be used in the tests below.
|
||||
coinbaseOutpoint := wire.NewOutPoint(&chainhash.Hash{}, math.MaxUint32)
|
||||
coinbaseTx := wire.NewMsgTx(1)
|
||||
coinbaseTx.AddTxIn(wire.NewTxIn(coinbaseOutpoint, nil, nil))
|
||||
|
||||
// Expected rule errors.
|
||||
missingHeightError := RuleError{
|
||||
ErrorCode: ErrMissingCoinbaseHeight,
|
||||
}
|
||||
badHeightError := RuleError{
|
||||
ErrorCode: ErrBadCoinbaseHeight,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
sigScript []byte // Serialized data
|
||||
wantHeight int32 // Expected height
|
||||
err error // Expected error type
|
||||
}{
|
||||
// No serialized height length.
|
||||
{[]byte{}, 0, missingHeightError},
|
||||
// Serialized height length with no height bytes.
|
||||
{[]byte{0x02}, 0, missingHeightError},
|
||||
// Serialized height length with too few height bytes.
|
||||
{[]byte{0x02, 0x4a}, 0, missingHeightError},
|
||||
// Serialized height that needs 2 bytes to encode.
|
||||
{[]byte{0x02, 0x4a, 0x52}, 21066, nil},
|
||||
// Serialized height that needs 2 bytes to encode, but backwards
|
||||
// endianness.
|
||||
{[]byte{0x02, 0x4a, 0x52}, 19026, badHeightError},
|
||||
// Serialized height that needs 3 bytes to encode.
|
||||
{[]byte{0x03, 0x40, 0x0d, 0x03}, 200000, nil},
|
||||
// Serialized height that needs 3 bytes to encode, but backwards
|
||||
// endianness.
|
||||
{[]byte{0x03, 0x40, 0x0d, 0x03}, 1074594560, badHeightError},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
msgTx := coinbaseTx.Copy()
|
||||
msgTx.TxIn[0].SignatureScript = test.sigScript
|
||||
tx := btcutil.NewTx(msgTx)
|
||||
|
||||
err := checkSerializedHeight(tx, test.wantHeight)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("checkSerializedHeight #%d wrong error type "+
|
||||
"got: %v <%T>, want: %T", i, err, err, test.err)
|
||||
continue
|
||||
}
|
||||
|
||||
if rerr, ok := err.(RuleError); ok {
|
||||
trerr := test.err.(RuleError)
|
||||
if rerr.ErrorCode != trerr.ErrorCode {
|
||||
t.Errorf("checkSerializedHeight #%d wrong "+
|
||||
"error code got: %v, want: %v", i,
|
||||
rerr.ErrorCode, trerr.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var block100000Hex = "0000002024cbdc8644ee3983e66b003a0733891c069ca74c114c034c7b3e2e7ad7a12cd67e95e0555c0e056f6f2af538268ff9d21b420e529750d08eacb25c40f1322936637109b8a051157604c1c163cd39237687f6244b4e6d2b3a94e9d816babaecbb10c56058c811041b2b9c43000701000000010000000000000000000000000000000000000000000000000000000000000000ffffffff2003a086010410c56058081011314abf0100000d2f6e6f64655374726174756d2f000000000180354a6e0a0000001976a914b5e74e7cc9e1f480a6599895c92aab1401f870f188ac000000000100000002f1b75decc2c1c59c2178852822de412f574ad9796b65ac9092a79630d8109aaf000000006a47304402202f78ed3bf8dcadb6c17e289cd06e9c96b02c6f23aa1f446a4a7896a31cfd1e4702202862261e2eb59475ac91092c620b3cac5a831372bafc446d5ee450866040b532012103db4f3785354d84311fab7624c52784a61e3046d8f364463d327bdd96281b5b90feffffff987ee6b4bf95548d01e443683261dd0ffdcb2eb335b2f7119df0d41b60756b92010000006a47304402200c422c7560b6418d45443138bb957ec44eb293a639f4b2235a622205ca6cac370220759f15d5dc2543fd1aef80104c93427fcb025847bf895669025d1d82c62fbf6801210201864b998db5436990a0029fc3fb153c09e8c2689214b91c8ed68784995c8da0feffffff022bccfedd000000001976a914738f16132942a01d00cb9699bd058c4925aada3288ac1f4d030c000000001976a914c4292e757f5ff6a27c2f0a87d3a4aea5e46c275a88ac9f86010001000000015fbb26ad6d818186032baeef4d3f475dfe165c6da2d93411b8ec5f9f523cf1a4000000006a4730440220356999ad5a9f6f09b676f17dd4a2617a0af234722d68a50edc3768c983c0623d022056b4e5531608aeb0205fde9c44f741158da3bba1f4c3788c9fe79d36d43ea355012103509893a2a7c765d49ac9ff70126cb1af54871d70baba2c7e39ec9b4096289a9bfeffffff02389332fa080000001976a914f85e054405fbcedc2341cf8bf41ea989090587a288acf9321a41000000001976a914e85e90c048fdfbe1c2117a7132856ff4b39b470188ac9f86010001000000013508189b9bb61ac2aa536b905447b58f6c58c17cdef305240f566caa689d760a010000006a4730440220669a2b86e5abe58bae54829d3c271669540a9ad965c2fb79e8cc1fb609c0b60002202f958403d979138075cb53d8cb5ff6bb14e18d66dfdb6701c7d43a8ceeed0fa80121029935a602205a3fb72446064f3bc3a55ab9cd2e3459bf2ffdf80a48ab029d4b42feffffff02523c2f13000000001976a914c5b2ae398496f0f9ceaf81b83c28a27ddc890e3588ac211958f2000000001976a914ac65f1d16e5a2af37408b5d65406000a7ea143ca88ac9f8601000100000001bdd724322c555a21d5eb62d4aadbdc051663bcd4ec03f8d9005992f299783c21000000006a47304402205448141a2a788f73310025123bd24f5bee01dd8f48f18d7abc62d7c26465008902207ab46e6ddf6ba416decf3fbb97b6946a1428ea0a7c25a55cab47c47110d8e9ce0121029d6ff3b1235f2a08560b23dd4a08b14cc415b544801b885365736ea8ab1d3470feffffff029d104ccf000000001976a914999d5b0e3d5efcf601c711127b91841afbf5c37a88ace5c5a07f070000001976a9144aade372298eb387da9b6ac69d215a213e822f3f88ac9f86010001000000011658304d4ce796cd450228a10fdf647c6ea42295c9f5e1663df11481af1c884d010000006b483045022100a35d5d3ccde85b41559047d976ae6210b8e6ba5653c53aae1adc90048de0761002200d6bd6ebc6d73f97855f435c6fd595009ee71d23bb34870ab83ad53f67eeb22b012102d2f681ebfd1a570416d602986a47ca4254d8dedf2935b3f8c2ba55dcee8e98f4feffffff025ee913e6020000001976a91468076c9380d3c6b468ad1d6109c36770fb181e8f88acb165394f000000001976a9147ae970e81b3657cbb59df26517e372165807be0088ac9f86010001000000018f285109f78524a88ff328a4f94de2ac03224c50984b11c68adda192e8f78efa010000006b483045022100d77f2ac32dd6a3015f02f7115a13f617c57952fc5d53a33a87dc3fc00ffe1864022006455f74cff864b10424e445c530a59243f86d309dc92c5010ec5709e38471ab012102fdac7335b41edcd2846fc7e2166bb48312ee583ed6ff70fb5c27bcb2addaad86feffffff028b7a6d5c000000001976a914c65106d2e7ea4ec6aa8aa30ba4d11cfd1143123388ac5934c228000000001976a914d1c4d190b07edb972b91f33c36c6568b80358dd488ac9f860100"
|
||||
|
||||
// GetBlock100000 defines block 100,000 of the block chain. It is used to
|
||||
// test Block operations.
|
||||
func GetBlock100000() *btcutil.Block {
|
||||
var block100000Bytes, _ = hex.DecodeString(block100000Hex)
|
||||
var results, _ = btcutil.NewBlockFromBytes(block100000Bytes)
|
||||
return results
|
||||
}
|
|
@ -19,7 +19,6 @@ import (
|
|||
"github.com/lbryio/lbcd/claimtrie/param"
|
||||
"github.com/lbryio/lbcd/database"
|
||||
"github.com/lbryio/lbcd/limits"
|
||||
"github.com/lbryio/lbcd/version"
|
||||
|
||||
"github.com/felixge/fgprof"
|
||||
)
|
||||
|
@ -65,7 +64,7 @@ func btcdMain(serverChan chan<- *server) error {
|
|||
defer btcdLog.Info("Shutdown complete")
|
||||
|
||||
// Show version at startup.
|
||||
btcdLog.Infof("Version %s", version.Full())
|
||||
btcdLog.Infof("Version %s", version())
|
||||
|
||||
// Enable http profiling server if requested.
|
||||
if cfg.Profile != "" {
|
||||
|
@ -92,25 +91,6 @@ func btcdMain(serverChan chan<- *server) error {
|
|||
defer pprof.StopCPUProfile()
|
||||
}
|
||||
|
||||
// Write memory profile if requested.
|
||||
if cfg.MemProfile != "" {
|
||||
f, err := os.Create(cfg.MemProfile + ".heap")
|
||||
if err != nil {
|
||||
btcdLog.Errorf("Unable to create mem profile: %v", err)
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
defer pprof.Lookup("heap").WriteTo(f, 0)
|
||||
|
||||
f, err = os.Create(cfg.MemProfile + ".allocs")
|
||||
if err != nil {
|
||||
btcdLog.Errorf("Unable to create mem profile: %v", err)
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
defer pprof.Lookup("allocs").WriteTo(f, 0)
|
||||
}
|
||||
|
||||
// Perform upgrades to btcd as new versions require it.
|
||||
if err := doUpgrades(); err != nil {
|
||||
btcdLog.Errorf("%v", err)
|
||||
|
@ -203,6 +183,41 @@ func btcdMain(serverChan chan<- *server) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// removeRegressionDB removes the existing regression test database if running
|
||||
// in regression test mode and it already exists.
|
||||
func removeRegressionDB(dbPath string) error {
|
||||
// Don't do anything if not in regression test mode.
|
||||
if !cfg.RegressionTest {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove the old regression test database if it already exists.
|
||||
fi, err := os.Stat(dbPath)
|
||||
if err == nil {
|
||||
btcdLog.Infof("Removing regression test database from '%s'", dbPath)
|
||||
if fi.IsDir() {
|
||||
err := os.RemoveAll(dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
err := os.Remove(dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dbPath = filepath.Join(cfg.DataDir, activeNetParams.Name, "claim_dbs")
|
||||
btcdLog.Infof("Removing regression test claim databases from '%s'", dbPath)
|
||||
err = os.RemoveAll(dbPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// dbPath returns the path to the block database given a database type.
|
||||
func blockDbPath(dbType string) string {
|
||||
// The database name is based on the database type.
|
||||
|
@ -269,6 +284,11 @@ func loadBlockDB() (database.DB, error) {
|
|||
|
||||
// The database name is based on the database type.
|
||||
dbPath := blockDbPath(cfg.DbType)
|
||||
|
||||
// The regression test is special in that it needs a clean database for
|
||||
// each run, so remove it now if it already exists.
|
||||
removeRegressionDB(dbPath)
|
||||
|
||||
btcdLog.Infof("Loading block database from '%s'", dbPath)
|
||||
db, err := database.Open(cfg.DbType, dbPath, activeNetParams.Net)
|
||||
if err != nil {
|
||||
|
@ -300,9 +320,7 @@ func main() {
|
|||
// limits the garbage collector from excessively overallocating during
|
||||
// bursts. This value was arrived at with the help of profiling live
|
||||
// usage.
|
||||
if _, ok := os.LookupEnv("GOGC"); !ok {
|
||||
debug.SetGCPercent(10)
|
||||
}
|
||||
debug.SetGCPercent(10)
|
||||
|
||||
// Up some limits.
|
||||
if err := limits.SetLimits(); err != nil {
|
|
@ -527,7 +527,7 @@ type baseMultTest struct {
|
|||
x, y string
|
||||
}
|
||||
|
||||
// TODO: add more test vectors
|
||||
//TODO: add more test vectors
|
||||
var s256BaseMultTests = []baseMultTest{
|
||||
{
|
||||
"AA5E28D6A97A2479A65527F7290311A3624D4CC0FA1578598EE3C2613BF99522",
|
||||
|
@ -556,7 +556,7 @@ var s256BaseMultTests = []baseMultTest{
|
|||
},
|
||||
}
|
||||
|
||||
// TODO: test different curves as well?
|
||||
//TODO: test different curves as well?
|
||||
func TestBaseMult(t *testing.T) {
|
||||
s256 := S256()
|
||||
for i, e := range s256BaseMultTests {
|
||||
|
|
|
@ -125,30 +125,27 @@ var (
|
|||
// the arithmetic needed for elliptic curve operations.
|
||||
//
|
||||
// The following depicts the internal representation:
|
||||
//
|
||||
// -----------------------------------------------------------------
|
||||
// | n[9] | n[8] | ... | n[0] |
|
||||
// | 32 bits available | 32 bits available | ... | 32 bits available |
|
||||
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
|
||||
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
|
||||
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
|
||||
// -----------------------------------------------------------------
|
||||
// -----------------------------------------------------------------
|
||||
// | n[9] | n[8] | ... | n[0] |
|
||||
// | 32 bits available | 32 bits available | ... | 32 bits available |
|
||||
// | 22 bits for value | 26 bits for value | ... | 26 bits for value |
|
||||
// | 10 bits overflow | 6 bits overflow | ... | 6 bits overflow |
|
||||
// | Mult: 2^(26*9) | Mult: 2^(26*8) | ... | Mult: 2^(26*0) |
|
||||
// -----------------------------------------------------------------
|
||||
//
|
||||
// For example, consider the number 2^49 + 1. It would be represented as:
|
||||
//
|
||||
// n[0] = 1
|
||||
// n[1] = 2^23
|
||||
// n[2..9] = 0
|
||||
// n[0] = 1
|
||||
// n[1] = 2^23
|
||||
// n[2..9] = 0
|
||||
//
|
||||
// The full 256-bit value is then calculated by looping i from 9..0 and
|
||||
// doing sum(n[i] * 2^(26i)) like so:
|
||||
//
|
||||
// n[9] * 2^(26*9) = 0 * 2^234 = 0
|
||||
// n[8] * 2^(26*8) = 0 * 2^208 = 0
|
||||
// ...
|
||||
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
|
||||
// n[0] * 2^(26*0) = 1 * 2^0 = 1
|
||||
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
|
||||
// n[9] * 2^(26*9) = 0 * 2^234 = 0
|
||||
// n[8] * 2^(26*8) = 0 * 2^208 = 0
|
||||
// ...
|
||||
// n[1] * 2^(26*1) = 2^23 * 2^26 = 2^49
|
||||
// n[0] * 2^(26*0) = 1 * 2^0 = 1
|
||||
// Sum: 0 + 0 + ... + 2^49 + 1 = 2^49 + 1
|
||||
type fieldVal struct {
|
||||
n [10]uint32
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
//go:rm -f gensecp256k1.go; generate go run -tags gensecp256k1 genprecomps.go
|
||||
//go:generate go run -tags gensecp256k1 genprecomps.go
|
||||
|
||||
// loadS256BytePoints decompresses and deserializes the pre-computed byte points
|
||||
// used to accelerate scalar base multiplication for the secp256k1 curve. This
|
||||
|
|
|
@ -353,10 +353,6 @@ func recoverKeyFromSignature(curve *KoblitzCurve, sig *Signature, msg []byte,
|
|||
// step to prevent the jacobian conversion back and forth.
|
||||
Qx, Qy := curve.Add(sRx, sRy, minuseGx, minuseGy)
|
||||
|
||||
if Qx.Sign() == 0 && Qy.Sign() == 0 {
|
||||
return nil, errors.New("point (Qx, Qy) equals the point at infinity")
|
||||
}
|
||||
|
||||
return &PublicKey{
|
||||
Curve: curve,
|
||||
X: Qx,
|
||||
|
|
|
@ -549,12 +549,6 @@ var recoveryTests = []struct {
|
|||
sig: "0100b1693892219d736caba55bdb67216e485557ea6b6af75f37096c9aa6a5a75f00b940b1d03b21e36b0e47e79769f095fe2ab855bd91e3a38756b7d75a9c4549",
|
||||
err: fmt.Errorf("invalid square root"),
|
||||
},
|
||||
{
|
||||
// Point at infinity recovered
|
||||
msg: "6b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9",
|
||||
sig: "0079be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f817986b8d2c81b11b2d699528dde488dbdf2f94293d0d33c32e347f255fa4a6c1f0a9",
|
||||
err: fmt.Errorf("point (Qx, Qy) equals the point at infinity"),
|
||||
},
|
||||
{
|
||||
// Low R and S values.
|
||||
msg: "ba09edc1275a285fb27bfe82c4eea240a907a0dbaf9e55764b8f318c37d5974f",
|
||||
|
|
|
@ -48,15 +48,6 @@ 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
|
||||
// transaction hash and output number pair.
|
||||
type TransactionInput struct {
|
||||
|
@ -67,7 +58,7 @@ type TransactionInput struct {
|
|||
// CreateRawTransactionCmd defines the createrawtransaction JSON-RPC command.
|
||||
type CreateRawTransactionCmd struct {
|
||||
Inputs []TransactionInput
|
||||
Outputs map[string]interface{} `jsonrpcusage:"{\"address\":amount, \"data\":\"hex\", ...}"`
|
||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||
LockTime *int64
|
||||
}
|
||||
|
||||
|
@ -76,7 +67,7 @@ type CreateRawTransactionCmd struct {
|
|||
//
|
||||
// Amounts are in BTC. Passing in nil and the empty slice as inputs is equivalent,
|
||||
// both gets interpreted as the empty slice.
|
||||
func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]interface{},
|
||||
func NewCreateRawTransactionCmd(inputs []TransactionInput, amounts map[string]float64,
|
||||
lockTime *int64) *CreateRawTransactionCmd {
|
||||
// to make sure we're serializing this to the empty list and not null, we
|
||||
// explicitly initialize the list
|
||||
|
@ -85,7 +76,7 @@ func NewCreateRawTransactionCmd(inputs []TransactionInput, outputs map[string]in
|
|||
}
|
||||
return &CreateRawTransactionCmd{
|
||||
Inputs: inputs,
|
||||
Outputs: outputs,
|
||||
Amounts: amounts,
|
||||
LockTime: lockTime,
|
||||
}
|
||||
}
|
||||
|
@ -659,7 +650,7 @@ func NewGetRawMempoolCmd(verbose *bool) *GetRawMempoolCmd {
|
|||
// Core even though it really should be a bool.
|
||||
type GetRawTransactionCmd struct {
|
||||
Txid string
|
||||
Verbose *bool `jsonrpcdefault:"false"`
|
||||
Verbose *int `jsonrpcdefault:"0"`
|
||||
}
|
||||
|
||||
// NewGetRawTransactionCmd returns a new instance which can be used to issue a
|
||||
|
@ -667,7 +658,7 @@ type GetRawTransactionCmd struct {
|
|||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetRawTransactionCmd(txHash string, verbose *bool) *GetRawTransactionCmd {
|
||||
func NewGetRawTransactionCmd(txHash string, verbose *int) *GetRawTransactionCmd {
|
||||
return &GetRawTransactionCmd{
|
||||
Txid: txHash,
|
||||
Verbose: verbose,
|
||||
|
@ -766,15 +757,6 @@ 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.
|
||||
type PingCmd struct{}
|
||||
|
||||
|
@ -921,39 +903,6 @@ 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.
|
||||
type SetGenerateCmd struct {
|
||||
Generate bool
|
||||
|
@ -1131,9 +1080,6 @@ func init() {
|
|||
MustRegisterCmd("getnetworkhashps", (*GetNetworkHashPSCmd)(nil), flags)
|
||||
MustRegisterCmd("getnodeaddresses", (*GetNodeAddressesCmd)(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("getrawtransaction", (*GetRawTransactionCmd)(nil), flags)
|
||||
MustRegisterCmd("gettxout", (*GetTxOutCmd)(nil), flags)
|
||||
|
|
|
@ -52,13 +52,13 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
txInputs := []btcjson.TransactionInput{
|
||||
{Txid: "123", Vout: 1},
|
||||
}
|
||||
txOutputs := map[string]interface{}{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, nil)
|
||||
amounts := map[string]float64{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123}],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||
Outputs: map[string]interface{}{"456": .0123},
|
||||
Amounts: map[string]float64{"456": .0123},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -67,13 +67,13 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("createrawtransaction", `[]`, `{"456":0.0123}`)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
txOutputs := map[string]interface{}{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(nil, txOutputs, nil)
|
||||
amounts := map[string]float64{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(nil, amounts, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[],{"456":0.0123}],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||
Inputs: []btcjson.TransactionInput{},
|
||||
Outputs: map[string]interface{}{"456": .0123},
|
||||
Amounts: map[string]float64{"456": .0123},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -86,35 +86,16 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
txInputs := []btcjson.TransactionInput{
|
||||
{Txid: "123", Vout: 1},
|
||||
}
|
||||
txOutputs := map[string]interface{}{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, txOutputs, btcjson.Int64(12312333333))
|
||||
amounts := map[string]float64{"456": .0123}
|
||||
return btcjson.NewCreateRawTransactionCmd(txInputs, amounts, btcjson.Int64(12312333333))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"createrawtransaction","params":[[{"txid":"123","vout":1}],{"456":0.0123},12312333333],"id":1}`,
|
||||
unmarshalled: &btcjson.CreateRawTransactionCmd{
|
||||
Inputs: []btcjson.TransactionInput{{Txid: "123", Vout: 1}},
|
||||
Outputs: map[string]interface{}{"456": .0123},
|
||||
Amounts: map[string]float64{"456": .0123},
|
||||
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",
|
||||
newCmd: func() (i interface{}, e error) {
|
||||
|
@ -891,21 +872,21 @@ func TestChainSvrCmds(t *testing.T) {
|
|||
marshalled: `{"jsonrpc":"1.0","method":"getrawtransaction","params":["123"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetRawTransactionCmd{
|
||||
Txid: "123",
|
||||
Verbose: btcjson.Bool(false),
|
||||
Verbose: btcjson.Int(0),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "getrawtransaction optional",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("getrawtransaction", "123", true)
|
||||
return btcjson.NewCmd("getrawtransaction", "123", 1)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetRawTransactionCmd("123", btcjson.Bool(true))
|
||||
return btcjson.NewGetRawTransactionCmd("123", btcjson.Int(1))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getrawtransaction","params":["123",true],"id":1}`,
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getrawtransaction","params":["123",1],"id":1}`,
|
||||
unmarshalled: &btcjson.GetRawTransactionCmd{
|
||||
Txid: "123",
|
||||
Verbose: btcjson.Bool(true),
|
||||
Verbose: btcjson.Int(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -25,7 +25,7 @@ type GetBlockHeaderVerboseResult struct {
|
|||
Version int32 `json:"version"`
|
||||
VersionHex string `json:"versionHex"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
ClaimTrie string `json:"nameclaimroot,omitempty"`
|
||||
ClaimTrie string `json:"claimtrie,omitempty"`
|
||||
Time int64 `json:"time"`
|
||||
Nonce uint64 `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
|
@ -35,37 +35,35 @@ type GetBlockHeaderVerboseResult struct {
|
|||
}
|
||||
|
||||
// GetBlockStatsResult models the data from the getblockstats command.
|
||||
// Pointers are used instead of values to allow for optional fields.
|
||||
type GetBlockStatsResult struct {
|
||||
AverageFee *int64 `json:"avgfee,omitempty"`
|
||||
AverageFeeRate *int64 `json:"avgfeerate,omitempty"`
|
||||
AverageTxSize *int64 `json:"avgtxsize,omitempty"`
|
||||
FeeratePercentiles *[]int64 `json:"feerate_percentiles,omitempty"`
|
||||
Hash *string `json:"blockhash,omitempty"`
|
||||
Height *int64 `json:"height,omitempty"`
|
||||
Ins *int64 `json:"ins,omitempty"`
|
||||
MaxFee *int64 `json:"maxfee,omitempty"`
|
||||
MaxFeeRate *int64 `json:"maxfeerate,omitempty"`
|
||||
MaxTxSize *int64 `json:"maxtxsize,omitempty"`
|
||||
MedianFee *int64 `json:"medianfee,omitempty"`
|
||||
MedianTime *int64 `json:"mediantime,omitempty"`
|
||||
MedianTxSize *int64 `json:"mediantxsize,omitempty"`
|
||||
MinFee *int64 `json:"minfee,omitempty"`
|
||||
MinFeeRate *int64 `json:"minfeerate,omitempty"`
|
||||
MinTxSize *int64 `json:"mintxsize,omitempty"`
|
||||
Outs *int64 `json:"outs,omitempty"`
|
||||
SegWitTotalSize *int64 `json:"swtotal_size,omitempty"`
|
||||
SegWitTotalWeight *int64 `json:"swtotal_weight,omitempty"`
|
||||
SegWitTxs *int64 `json:"swtxs,omitempty"`
|
||||
Subsidy *int64 `json:"subsidy,omitempty"`
|
||||
Time *int64 `json:"time,omitempty"`
|
||||
TotalOut *int64 `json:"total_out,omitempty"`
|
||||
TotalSize *int64 `json:"total_size,omitempty"`
|
||||
TotalWeight *int64 `json:"total_weight,omitempty"`
|
||||
TotalFee *int64 `json:"totalfee,omitempty"`
|
||||
Txs *int64 `json:"txs,omitempty"`
|
||||
UTXOIncrease *int64 `json:"utxo_increase,omitempty"`
|
||||
UTXOSizeIncrease *int64 `json:"utxo_size_inc,omitempty"`
|
||||
AverageFee int64 `json:"avgfee"`
|
||||
AverageFeeRate int64 `json:"avgfeerate"`
|
||||
AverageTxSize int64 `json:"avgtxsize"`
|
||||
FeeratePercentiles []int64 `json:"feerate_percentiles"`
|
||||
Hash string `json:"blockhash"`
|
||||
Height int64 `json:"height"`
|
||||
Ins int64 `json:"ins"`
|
||||
MaxFee int64 `json:"maxfee"`
|
||||
MaxFeeRate int64 `json:"maxfeerate"`
|
||||
MaxTxSize int64 `json:"maxtxsize"`
|
||||
MedianFee int64 `json:"medianfee"`
|
||||
MedianTime int64 `json:"mediantime"`
|
||||
MedianTxSize int64 `json:"mediantxsize"`
|
||||
MinFee int64 `json:"minfee"`
|
||||
MinFeeRate int64 `json:"minfeerate"`
|
||||
MinTxSize int64 `json:"mintxsize"`
|
||||
Outs int64 `json:"outs"`
|
||||
SegWitTotalSize int64 `json:"swtotal_size"`
|
||||
SegWitTotalWeight int64 `json:"swtotal_weight"`
|
||||
SegWitTxs int64 `json:"swtxs"`
|
||||
Subsidy int64 `json:"subsidy"`
|
||||
Time int64 `json:"time"`
|
||||
TotalOut int64 `json:"total_out"`
|
||||
TotalSize int64 `json:"total_size"`
|
||||
TotalWeight int64 `json:"total_weight"`
|
||||
Txs int64 `json:"txs"`
|
||||
UTXOIncrease int64 `json:"utxo_increase"`
|
||||
UTXOSizeIncrease int64 `json:"utxo_size_inc"`
|
||||
}
|
||||
|
||||
type GetBlockVerboseResultBase struct {
|
||||
|
@ -79,15 +77,13 @@ type GetBlockVerboseResultBase struct {
|
|||
VersionHex string `json:"versionHex"`
|
||||
MerkleRoot string `json:"merkleroot"`
|
||||
Time int64 `json:"time"`
|
||||
MedianTime int64 `json:"mediantime"`
|
||||
Nonce uint32 `json:"nonce"`
|
||||
Bits string `json:"bits"`
|
||||
Difficulty float64 `json:"difficulty"`
|
||||
ChainWork string `json:"chainwork"`
|
||||
PreviousHash string `json:"previousblockhash,omitempty"`
|
||||
NextHash string `json:"nextblockhash,omitempty"`
|
||||
|
||||
ClaimTrie string `json:"nameclaimroot,omitempty"`
|
||||
ClaimTrie string `json:"claimTrie,omitempty"`
|
||||
TxCount int `json:"nTx"` // For backwards compatibility only
|
||||
}
|
||||
|
||||
|
@ -327,27 +323,13 @@ type GetMempoolEntryResult struct {
|
|||
WTxId string `json:"wtxid"`
|
||||
Fees MempoolFees `json:"fees"`
|
||||
Depends []string `json:"depends"`
|
||||
SpentBy []string `json:"spentby"`
|
||||
}
|
||||
|
||||
// GetChainTipsResult models the data returns from the getchaintips command.
|
||||
type GetChainTipsResult struct {
|
||||
Height int64 `json:"height"`
|
||||
Hash string `json:"hash"`
|
||||
BranchLen int64 `json:"branchlen"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// GetMempoolInfoResult models the data returned from the getmempoolinfo
|
||||
// command.
|
||||
type GetMempoolInfoResult struct {
|
||||
Size int64 `json:"size"` // Current tx count
|
||||
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
|
||||
Size int64 `json:"size"`
|
||||
Bytes int64 `json:"bytes"`
|
||||
}
|
||||
|
||||
// NetworksResult models the networks data from the getnetworkinfo command.
|
||||
|
@ -723,15 +705,6 @@ type InfoChainResult struct {
|
|||
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.
|
||||
type TxRawResult struct {
|
||||
Hex string `json:"hex"`
|
||||
|
|
|
@ -55,12 +55,11 @@ type GetClaimsForNameBySeqCmd struct {
|
|||
}
|
||||
|
||||
type GetClaimsForNameResult struct {
|
||||
Hash string `json:"hash"`
|
||||
Height int32 `json:"height"`
|
||||
LastTakeoverHeight int32 `json:"lasttakeoverheight"`
|
||||
NormalizedName string `json:"normalizedname"`
|
||||
Claims []ClaimResult `json:"claims"`
|
||||
// UnclaimedSupports []SupportResult `json:"supportswithoutclaim"` how would this work with other constraints?
|
||||
Hash string `json:"hash"`
|
||||
Height int32 `json:"height"`
|
||||
NormalizedName string `json:"normalizedname"`
|
||||
Claims []ClaimResult `json:"claims"`
|
||||
// UnclaimedSupports []SupportResult `json:"unclaimedSupports"` how would this work with other constraints?
|
||||
}
|
||||
|
||||
type SupportResult struct {
|
||||
|
@ -81,7 +80,6 @@ type ClaimResult struct {
|
|||
Sequence int32 `json:"sequence"`
|
||||
Height int32 `json:"height"`
|
||||
ValidAtHeight int32 `json:"validatheight"`
|
||||
Amount int64 `json:"amount"`
|
||||
EffectiveAmount int64 `json:"effectiveamount"`
|
||||
Supports []SupportResult `json:"supports,omitempty"`
|
||||
Address string `json:"address,omitempty"`
|
||||
|
|
|
@ -1,430 +0,0 @@
|
|||
// Copyright (c) 2015 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/btcjson"
|
||||
)
|
||||
|
||||
// TestCmdMethod tests the CmdMethod function to ensure it retunrs the expected
|
||||
// methods and errors.
|
||||
func TestCmdMethod(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
cmd interface{}
|
||||
method string
|
||||
err error
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
cmd: (*int)(nil),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "nil pointer of registered type",
|
||||
cmd: (*btcjson.GetBlockCmd)(nil),
|
||||
method: "getblock",
|
||||
},
|
||||
{
|
||||
name: "nil instance of registered type",
|
||||
cmd: &btcjson.GetBlockCountCmd{},
|
||||
method: "getblockcount",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
method, err := btcjson.CmdMethod(test.cmd)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||
"- got %v (%v), want %v", i, test.name,
|
||||
gotErrorCode, err,
|
||||
test.err.(btcjson.Error).ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure method matches the expected value.
|
||||
if method != test.method {
|
||||
t.Errorf("Test #%d (%s) mismatched method - got %v, "+
|
||||
"want %v", i, test.name, method, test.method)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMethodUsageFlags tests the MethodUsage function ensure it returns the
|
||||
// expected flags and errors.
|
||||
func TestMethodUsageFlags(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
err error
|
||||
flags btcjson.UsageFlag
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
method: "bogusmethod",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "getblock",
|
||||
method: "getblock",
|
||||
flags: 0,
|
||||
},
|
||||
{
|
||||
name: "walletpassphrase",
|
||||
method: "walletpassphrase",
|
||||
flags: btcjson.UFWalletOnly,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
flags, err := btcjson.MethodUsageFlags(test.method)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||
"- got %v (%v), want %v", i, test.name,
|
||||
gotErrorCode, err,
|
||||
test.err.(btcjson.Error).ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure flags match the expected value.
|
||||
if flags != test.flags {
|
||||
t.Errorf("Test #%d (%s) mismatched flags - got %v, "+
|
||||
"want %v", i, test.name, flags, test.flags)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMethodUsageText tests the MethodUsageText function ensure it returns the
|
||||
// expected text.
|
||||
func TestMethodUsageText(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
err error
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
method: "bogusmethod",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "getblockcount",
|
||||
method: "getblockcount",
|
||||
expected: "getblockcount",
|
||||
},
|
||||
{
|
||||
name: "getblock",
|
||||
method: "getblock",
|
||||
expected: `getblock "hash" (verbosity=1)`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
usage, err := btcjson.MethodUsageText(test.method)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.(btcjson.Error).ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code "+
|
||||
"- got %v (%v), want %v", i, test.name,
|
||||
gotErrorCode, err,
|
||||
test.err.(btcjson.Error).ErrorCode)
|
||||
continue
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure usage matches the expected value.
|
||||
if usage != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||
"want %v", i, test.name, usage, test.expected)
|
||||
continue
|
||||
}
|
||||
|
||||
// Get the usage again to exercise caching.
|
||||
usage, err = btcjson.MethodUsageText(test.method)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure usage still matches the expected value.
|
||||
if usage != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||
"want %v", i, test.name, usage, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestFieldUsage tests the internal fieldUsage function ensure it returns the
|
||||
// expected text.
|
||||
func TestFieldUsage(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
field reflect.StructField
|
||||
defValue *reflect.Value
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "jsonrpcusage tag override",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test int `jsonrpcusage:"testvalue"`
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: "testvalue",
|
||||
},
|
||||
{
|
||||
name: "generic interface",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test interface{}
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `test`,
|
||||
},
|
||||
{
|
||||
name: "string without default value",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `"test"`,
|
||||
},
|
||||
{
|
||||
name: "string with default value",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: func() *reflect.Value {
|
||||
value := "default"
|
||||
rv := reflect.ValueOf(&value)
|
||||
return &rv
|
||||
}(),
|
||||
expected: `test="default"`,
|
||||
},
|
||||
{
|
||||
name: "array of strings",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["test",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of strings with plural field name 1",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Keys []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["key",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of strings with plural field name 2",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Addresses []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["address",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of strings with plural field name 3",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Capabilities []string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `["capability",...]`,
|
||||
},
|
||||
{
|
||||
name: "array of structs",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Txid string
|
||||
}
|
||||
type s struct {
|
||||
Capabilities []s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `[{"txid":"value"},...]`,
|
||||
},
|
||||
{
|
||||
name: "array of ints",
|
||||
field: func() reflect.StructField {
|
||||
type s struct {
|
||||
Test []int
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `[test,...]`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with jsonrpcusage tag override",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Test string `jsonrpcusage:"testusage"`
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{testusage}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with string",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Txid string
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"txid":"value"}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with int",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Vout int
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"vout":n}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with float",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Amount float64
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"amount":n.nnn}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with sub struct",
|
||||
field: func() reflect.StructField {
|
||||
type s3 struct {
|
||||
Amount float64
|
||||
}
|
||||
type s2 struct {
|
||||
Template s3
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"template":{"amount":n.nnn}}`,
|
||||
},
|
||||
{
|
||||
name: "sub struct with slice",
|
||||
field: func() reflect.StructField {
|
||||
type s2 struct {
|
||||
Capabilities []string
|
||||
}
|
||||
type s struct {
|
||||
Test s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil)).Elem().Field(0)
|
||||
}(),
|
||||
defValue: nil,
|
||||
expected: `{"capabilities":["capability",...]}`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Ensure usage matches the expected value.
|
||||
usage := btcjson.TstFieldUsage(test.field, test.defValue)
|
||||
if usage != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched usage - got %v, "+
|
||||
"want %v", i, test.name, usage, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -134,16 +134,6 @@ func UnmarshalCmd(r *Request) (interface{}, error) {
|
|||
// Unmarshal the parameter into the struct field.
|
||||
concreteVal := rvf.Addr().Interface()
|
||||
if err := json.Unmarshal(r.Params[i], &concreteVal); err != nil {
|
||||
// Parse Integer into Bool for compatibility with lbrycrd.
|
||||
if rvf.Kind() == reflect.Ptr &&
|
||||
rvf.Elem().Type().Kind() == reflect.Bool {
|
||||
boolInt, errBoolInt := strconv.Atoi(string(r.Params[i]))
|
||||
if errBoolInt == nil {
|
||||
rvf.Elem().SetBool(boolInt != 0)
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// The most common error is the wrong type, so
|
||||
// explicitly detect that error and make it nicer.
|
||||
fieldName := strings.ToLower(rt.Field(i).Name)
|
||||
|
|
|
@ -1,674 +0,0 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/btcjson"
|
||||
)
|
||||
|
||||
// TestAssignField tests the assignField function handles supported combinations
|
||||
// properly.
|
||||
func TestAssignField(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dest interface{}
|
||||
src interface{}
|
||||
expected interface{}
|
||||
}{
|
||||
{
|
||||
name: "same types",
|
||||
dest: int8(0),
|
||||
src: int8(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "same types - more source pointers",
|
||||
dest: int8(0),
|
||||
src: func() interface{} {
|
||||
i := int8(100)
|
||||
return &i
|
||||
}(),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "same types - more dest pointers",
|
||||
dest: func() interface{} {
|
||||
i := int8(0)
|
||||
return &i
|
||||
}(),
|
||||
src: int8(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - more source pointers",
|
||||
dest: int16(0),
|
||||
src: func() interface{} {
|
||||
i := int8(100)
|
||||
return &i
|
||||
}(),
|
||||
expected: int16(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - both pointers",
|
||||
dest: func() interface{} {
|
||||
i := int8(0)
|
||||
return &i
|
||||
}(),
|
||||
src: func() interface{} {
|
||||
i := int16(100)
|
||||
return &i
|
||||
}(),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - int16 -> int8",
|
||||
dest: int8(0),
|
||||
src: int16(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - int16 -> uint8",
|
||||
dest: uint8(0),
|
||||
src: int16(100),
|
||||
expected: uint8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - uint16 -> int8",
|
||||
dest: int8(0),
|
||||
src: uint16(100),
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - uint16 -> uint8",
|
||||
dest: uint8(0),
|
||||
src: uint16(100),
|
||||
expected: uint8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - float32 -> float64",
|
||||
dest: float64(0),
|
||||
src: float32(1.5),
|
||||
expected: float64(1.5),
|
||||
},
|
||||
{
|
||||
name: "convertible types - float64 -> float32",
|
||||
dest: float32(0),
|
||||
src: float64(1.5),
|
||||
expected: float32(1.5),
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> bool",
|
||||
dest: false,
|
||||
src: "true",
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> int8",
|
||||
dest: int8(0),
|
||||
src: "100",
|
||||
expected: int8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> uint8",
|
||||
dest: uint8(0),
|
||||
src: "100",
|
||||
expected: uint8(100),
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> float32",
|
||||
dest: float32(0),
|
||||
src: "1.5",
|
||||
expected: float32(1.5),
|
||||
},
|
||||
{
|
||||
name: "convertible types - typecase string -> string",
|
||||
dest: "",
|
||||
src: func() interface{} {
|
||||
type foo string
|
||||
return foo("foo")
|
||||
}(),
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> array",
|
||||
dest: [2]string{},
|
||||
src: `["test","test2"]`,
|
||||
expected: [2]string{"test", "test2"},
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> slice",
|
||||
dest: []string{},
|
||||
src: `["test","test2"]`,
|
||||
expected: []string{"test", "test2"},
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> struct",
|
||||
dest: struct{ A int }{},
|
||||
src: `{"A":100}`,
|
||||
expected: struct{ A int }{100},
|
||||
},
|
||||
{
|
||||
name: "convertible types - string -> map",
|
||||
dest: map[string]float64{},
|
||||
src: `{"1Address":1.5}`,
|
||||
expected: map[string]float64{"1Address": 1.5},
|
||||
},
|
||||
{
|
||||
name: `null optional field - "null" -> *int32`,
|
||||
dest: btcjson.Int32(0),
|
||||
src: "null",
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: `null optional field - "null" -> *string`,
|
||||
dest: btcjson.String(""),
|
||||
src: "null",
|
||||
expected: nil,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
|
||||
src := reflect.ValueOf(test.src)
|
||||
err := btcjson.TstAssignField(1, "testField", dst, src)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) unexpected error: %v", i,
|
||||
test.name, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Check case where null string is used on optional field
|
||||
if dst.Kind() == reflect.Ptr && test.src == "null" {
|
||||
if !dst.IsNil() {
|
||||
t.Errorf("Test #%d (%s) unexpected value - got %v, "+
|
||||
"want nil", i, test.name, dst.Interface())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Inidirect through to the base types to ensure their values
|
||||
// are the same.
|
||||
for dst.Kind() == reflect.Ptr {
|
||||
dst = dst.Elem()
|
||||
}
|
||||
if !reflect.DeepEqual(dst.Interface(), test.expected) {
|
||||
t.Errorf("Test #%d (%s) unexpected value - got %v, "+
|
||||
"want %v", i, test.name, dst.Interface(),
|
||||
test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestAssignFieldErrors tests the assignField function error paths.
|
||||
func TestAssignFieldErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
dest interface{}
|
||||
src interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "general incompatible int -> string",
|
||||
dest: "\x00",
|
||||
src: int(0),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source int -> dest int",
|
||||
dest: int8(0),
|
||||
src: int(128),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source int -> dest uint",
|
||||
dest: uint8(0),
|
||||
src: int(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "int -> float",
|
||||
dest: float32(0),
|
||||
src: int(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source uint64 -> dest int64",
|
||||
dest: int64(0),
|
||||
src: uint64(1 << 63),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source uint -> dest int",
|
||||
dest: int8(0),
|
||||
src: uint(128),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow source uint -> dest uint",
|
||||
dest: uint8(0),
|
||||
src: uint(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "uint -> float",
|
||||
dest: float32(0),
|
||||
src: uint(256),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "float -> int",
|
||||
dest: int(0),
|
||||
src: float32(1.0),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow float64 -> float32",
|
||||
dest: float32(0),
|
||||
src: float64(math.MaxFloat64),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> bool",
|
||||
dest: true,
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> int",
|
||||
dest: int8(0),
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow string -> int",
|
||||
dest: int8(0),
|
||||
src: "128",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> uint",
|
||||
dest: uint8(0),
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow string -> uint",
|
||||
dest: uint8(0),
|
||||
src: "256",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> float",
|
||||
dest: float32(0),
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "overflow string -> float",
|
||||
dest: float32(0),
|
||||
src: "1.7976931348623157e+308",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> array",
|
||||
dest: [3]int{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> slice",
|
||||
dest: []int{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> struct",
|
||||
dest: struct{ A int }{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid string -> map",
|
||||
dest: map[string]int{},
|
||||
src: "foo",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
dst := reflect.New(reflect.TypeOf(test.dest)).Elem()
|
||||
src := reflect.ValueOf(test.src)
|
||||
err := btcjson.TstAssignField(1, "testField", dst, src)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%[3]v), "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestNewCmdErrors ensures the error paths of NewCmd behave as expected.
|
||||
func TestNewCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
args []interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered command",
|
||||
method: "boguscommand",
|
||||
args: []interface{}{},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "too few parameters to command with required + optional",
|
||||
method: "getblock",
|
||||
args: []interface{}{},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||
},
|
||||
{
|
||||
name: "too many parameters to command with no optional",
|
||||
method: "getblockcount",
|
||||
args: []interface{}{"123"},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||
},
|
||||
{
|
||||
name: "incorrect parameter type",
|
||||
method: "getblock",
|
||||
args: []interface{}{1},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.NewCmd(test.method, test.args...)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
||||
"want %T", i, test.name, err, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarshalCmd tests the MarshalCmd function.
|
||||
func TestMarshalCmd(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id interface{}
|
||||
cmd interface{}
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "include all parameters",
|
||||
id: 1,
|
||||
cmd: btcjson.NewGetNetworkHashPSCmd(btcjson.Int(100), btcjson.Int(2000)),
|
||||
expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[100,2000],"id":1}`,
|
||||
},
|
||||
{
|
||||
name: "include padding null parameter",
|
||||
id: 1,
|
||||
cmd: btcjson.NewGetNetworkHashPSCmd(nil, btcjson.Int(2000)),
|
||||
expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[null,2000],"id":1}`,
|
||||
},
|
||||
{
|
||||
name: "omit single unnecessary null parameter",
|
||||
id: 1,
|
||||
cmd: btcjson.NewGetNetworkHashPSCmd(btcjson.Int(100), nil),
|
||||
expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[100],"id":1}`,
|
||||
},
|
||||
{
|
||||
name: "omit unnecessary null parameters",
|
||||
id: 1,
|
||||
cmd: btcjson.NewGetNetworkHashPSCmd(nil, nil),
|
||||
expected: `{"jsonrpc":"1.0","method":"getnetworkhashps","params":[],"id":1}`,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
bytes, err := btcjson.MarshalCmd(btcjson.RpcVersion1, test.id, test.cmd)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v)",
|
||||
i, test.name, err, err)
|
||||
continue
|
||||
}
|
||||
marshalled := string(bytes)
|
||||
if marshalled != test.expected {
|
||||
t.Errorf("Test #%d (%s) mismatched marshall result - got "+
|
||||
"%v, want %v", i, test.name, marshalled, test.expected)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMarshalCmdErrors tests the error paths of the MarshalCmd function.
|
||||
func TestMarshalCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id interface{}
|
||||
cmd interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
id: 1,
|
||||
cmd: (*int)(nil),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "nil instance of registered type",
|
||||
id: 1,
|
||||
cmd: (*btcjson.GetBlockCmd)(nil),
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "nil instance of registered type",
|
||||
id: []int{0, 1},
|
||||
cmd: &btcjson.GetBlockCountCmd{},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.MarshalCmd(btcjson.RpcVersion1, test.id, test.cmd)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
||||
"want %T", i, test.name, err, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnmarshalCmdErrors tests the error paths of the UnmarshalCmd function.
|
||||
func TestUnmarshalCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
request btcjson.Request
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered type",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "bogusmethod",
|
||||
Params: nil,
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "incorrect number of params",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getblockcount",
|
||||
Params: []json.RawMessage{[]byte(`"bogusparam"`)},
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNumParams},
|
||||
},
|
||||
{
|
||||
name: "invalid type for a parameter",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getblock",
|
||||
Params: []json.RawMessage{[]byte("1.0")},
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid JSON for a parameter",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getblock",
|
||||
Params: []json.RawMessage{[]byte(`"1`)},
|
||||
ID: nil,
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.UnmarshalCmd(&test.request)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
||||
"want %T", i, test.name, err, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestUnmarshalCmdBoolParams tests the parsing of boolean paramers of the UnmarshalCmd function.
|
||||
func TestUnmarshalCmdBoolParams(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
txid := []byte(`"ab91c149aff2b37a4a1856e9935ea623c973f47886d032ed7511ad8ca37855bb"`)
|
||||
tests := []struct {
|
||||
name string
|
||||
request btcjson.Request
|
||||
expect bool
|
||||
}{
|
||||
{
|
||||
name: "parse true",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getrawtransaction",
|
||||
Params: []json.RawMessage{txid, []byte("true")},
|
||||
ID: nil,
|
||||
},
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "parse false",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getrawtransaction",
|
||||
Params: []json.RawMessage{txid, []byte("false")},
|
||||
ID: nil,
|
||||
},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "parse integer 0 to false",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getrawtransaction",
|
||||
Params: []json.RawMessage{txid, []byte("0")},
|
||||
ID: nil,
|
||||
},
|
||||
expect: false,
|
||||
},
|
||||
{
|
||||
name: "parse integer 1 to true",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getrawtransaction",
|
||||
Params: []json.RawMessage{txid, []byte("1")},
|
||||
ID: nil,
|
||||
},
|
||||
expect: true,
|
||||
},
|
||||
{
|
||||
name: "parse integer 100 to true",
|
||||
request: btcjson.Request{
|
||||
Jsonrpc: btcjson.RpcVersion1,
|
||||
Method: "getrawtransaction",
|
||||
Params: []json.RawMessage{txid, []byte("100")},
|
||||
ID: nil,
|
||||
},
|
||||
expect: true,
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
cmd, err := btcjson.UnmarshalCmd(&test.request)
|
||||
if err != nil {
|
||||
t.Errorf("Test #%d (%s) error - got %T (%v)", i, test.name,
|
||||
err, err)
|
||||
continue
|
||||
}
|
||||
cc := cmd.(*btcjson.GetRawTransactionCmd)
|
||||
verbose := *cc.Verbose
|
||||
if verbose != test.expect {
|
||||
t.Errorf("Test #%d (%s) got %t, want %v", i, test.name,
|
||||
verbose, test.expect)
|
||||
continue
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
/*
|
||||
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
|
||||
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
|
||||
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
|
||||
provide some intuition into what the marshalling and unmarshalling that is
|
||||
|
@ -47,39 +47,39 @@ with it) doesn't always follow the spec and will sometimes return an error
|
|||
string in the result field with a null error for certain commands. However,
|
||||
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
|
||||
package map into the required parts of the protocol
|
||||
|
||||
- Request Objects (type Request)
|
||||
- Commands (type <Foo>Cmd)
|
||||
- Notifications (type <Foo>Ntfn)
|
||||
- Commands (type <Foo>Cmd)
|
||||
- Notifications (type <Foo>Ntfn)
|
||||
- Response Objects (type Response)
|
||||
- Result (type <Foo>Result)
|
||||
- Result (type <Foo>Result)
|
||||
|
||||
To simplify the marshalling of the requests and responses, the MarshalCmd and
|
||||
MarshalResponse functions are provided. They return the raw bytes ready to be
|
||||
sent across the wire.
|
||||
|
||||
Unmarshalling a received Request object is a two step process:
|
||||
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
|
||||
a concrete command or notification instance with all struct fields set
|
||||
accordingly
|
||||
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
|
||||
a concrete command or notification instance with all struct fields set
|
||||
accordingly
|
||||
|
||||
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.
|
||||
|
||||
Unmarshalling a received Response object is also a two step process:
|
||||
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
|
||||
Response to create a concrete type instance
|
||||
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
|
||||
Response to create a concrete type instance
|
||||
|
||||
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.
|
||||
|
||||
# Command Creation
|
||||
Command Creation
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
that are intentionally dynamic.
|
||||
|
||||
# Custom Command Registration
|
||||
Custom Command Registration
|
||||
|
||||
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
|
||||
|
@ -104,7 +104,7 @@ function for this purpose.
|
|||
A list of all registered methods can be obtained with the RegisteredCmdMethods
|
||||
function.
|
||||
|
||||
# Command Inspection
|
||||
Command Inspection
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
CmdMethod function.
|
||||
|
||||
# Help Generation
|
||||
Help Generation
|
||||
|
||||
To facilitate providing consistent help to users of the RPC server, this package
|
||||
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
|
||||
one-line usage for registered commands and notifications using reflection.
|
||||
|
||||
# Errors
|
||||
Errors
|
||||
|
||||
There are 2 distinct type of errors supported by this package:
|
||||
|
||||
|
|
|
@ -1,80 +0,0 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/btcjson"
|
||||
)
|
||||
|
||||
// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
|
||||
func TestErrorCodeStringer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in btcjson.ErrorCode
|
||||
want string
|
||||
}{
|
||||
{btcjson.ErrDuplicateMethod, "ErrDuplicateMethod"},
|
||||
{btcjson.ErrInvalidUsageFlags, "ErrInvalidUsageFlags"},
|
||||
{btcjson.ErrInvalidType, "ErrInvalidType"},
|
||||
{btcjson.ErrEmbeddedType, "ErrEmbeddedType"},
|
||||
{btcjson.ErrUnexportedField, "ErrUnexportedField"},
|
||||
{btcjson.ErrUnsupportedFieldType, "ErrUnsupportedFieldType"},
|
||||
{btcjson.ErrNonOptionalField, "ErrNonOptionalField"},
|
||||
{btcjson.ErrNonOptionalDefault, "ErrNonOptionalDefault"},
|
||||
{btcjson.ErrMismatchedDefault, "ErrMismatchedDefault"},
|
||||
{btcjson.ErrUnregisteredMethod, "ErrUnregisteredMethod"},
|
||||
{btcjson.ErrNumParams, "ErrNumParams"},
|
||||
{btcjson.ErrMissingDescription, "ErrMissingDescription"},
|
||||
{0xffff, "Unknown ErrorCode (65535)"},
|
||||
}
|
||||
|
||||
// Detect additional error codes that don't have the stringer added.
|
||||
if len(tests)-1 != int(btcjson.TstNumErrorCodes) {
|
||||
t.Errorf("It appears an error code was added without adding an " +
|
||||
"associated stringer test")
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.String()
|
||||
if result != test.want {
|
||||
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestError tests the error output for the Error type.
|
||||
func TestError(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in btcjson.Error
|
||||
want string
|
||||
}{
|
||||
{
|
||||
btcjson.Error{Description: "some error"},
|
||||
"some error",
|
||||
},
|
||||
{
|
||||
btcjson.Error{Description: "human-readable error"},
|
||||
"human-readable error",
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.Error()
|
||||
if result != test.want {
|
||||
t.Errorf("Error #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
|
@ -476,12 +476,11 @@ func isValidResultType(kind reflect.Kind) bool {
|
|||
// an error will use the key in place of the description.
|
||||
//
|
||||
// The following outlines the required keys:
|
||||
//
|
||||
// "<method>--synopsis" Synopsis for the command
|
||||
// "<method>-<lowerfieldname>" Description for each command argument
|
||||
// "<typename>-<lowerfieldname>" Description for each object field
|
||||
// "<method>--condition<#>" Description for each result condition
|
||||
// "<method>--result<#>" Description for each primitive result num
|
||||
// "<method>--synopsis" Synopsis for the command
|
||||
// "<method>-<lowerfieldname>" Description for each command argument
|
||||
// "<typename>-<lowerfieldname>" Description for each object field
|
||||
// "<method>--condition<#>" Description for each result condition
|
||||
// "<method>--result<#>" Description for each primitive result num
|
||||
//
|
||||
// Notice that the "special" keys synopsis, condition<#>, and result<#> are
|
||||
// preceded by a double dash to ensure they don't conflict with field names.
|
||||
|
@ -493,17 +492,16 @@ func isValidResultType(kind reflect.Kind) bool {
|
|||
// For example, consider the 'help' command itself. There are two possible
|
||||
// returns depending on the provided parameters. So, the help would be
|
||||
// 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:
|
||||
//
|
||||
// "help--synopsis": "Returns a list of all commands or help for ...."
|
||||
// "help-command": "The command to retrieve help for",
|
||||
// "help--condition0": "no command provided"
|
||||
// "help--condition1": "command specified"
|
||||
// "help--result0": "List of commands"
|
||||
// "help--result1": "Help for specified command"
|
||||
// "help--synopsis": "Returns a list of all commands or help for ...."
|
||||
// "help-command": "The command to retrieve help for",
|
||||
// "help--condition0": "no command provided"
|
||||
// "help--condition1": "command specified"
|
||||
// "help--result0": "List of commands"
|
||||
// "help--result1": "Help for specified command"
|
||||
func GenerateHelp(method string, descs map[string]string, resultTypes ...interface{}) (string, error) {
|
||||
// Look up details about the provided method and error out if not
|
||||
// registered.
|
||||
|
@ -549,12 +547,6 @@ func GenerateHelp(method string, descs map[string]string, resultTypes ...interfa
|
|||
return desc
|
||||
}
|
||||
|
||||
if strings.Contains(key, "base-") {
|
||||
if desc, ok := descs[strings.ReplaceAll(key, "base-", "-")]; ok {
|
||||
return desc
|
||||
}
|
||||
}
|
||||
|
||||
missingKey = key
|
||||
return key
|
||||
}
|
||||
|
|
|
@ -1,737 +0,0 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/btcjson"
|
||||
)
|
||||
|
||||
// TestHelpReflectInternals ensures the various help functions which deal with
|
||||
// reflect types work as expected for various Go types.
|
||||
func TestHelpReflectInternals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reflectType reflect.Type
|
||||
indentLevel int
|
||||
key string
|
||||
examples []string
|
||||
isComplex bool
|
||||
help string
|
||||
isInvalid bool
|
||||
}{
|
||||
{
|
||||
name: "int",
|
||||
reflectType: reflect.TypeOf(int(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "*int",
|
||||
reflectType: reflect.TypeOf((*int)(nil)),
|
||||
key: "json-type-value",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-value) fdk",
|
||||
isInvalid: true,
|
||||
},
|
||||
{
|
||||
name: "int8",
|
||||
reflectType: reflect.TypeOf(int8(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "int16",
|
||||
reflectType: reflect.TypeOf(int16(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "int32",
|
||||
reflectType: reflect.TypeOf(int32(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "int64",
|
||||
reflectType: reflect.TypeOf(int64(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint",
|
||||
reflectType: reflect.TypeOf(uint(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint8",
|
||||
reflectType: reflect.TypeOf(uint8(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint16",
|
||||
reflectType: reflect.TypeOf(uint16(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint32",
|
||||
reflectType: reflect.TypeOf(uint32(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "uint64",
|
||||
reflectType: reflect.TypeOf(uint64(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n"},
|
||||
help: "n (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "float32",
|
||||
reflectType: reflect.TypeOf(float32(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n.nnn"},
|
||||
help: "n.nnn (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "float64",
|
||||
reflectType: reflect.TypeOf(float64(0)),
|
||||
key: "json-type-numeric",
|
||||
examples: []string{"n.nnn"},
|
||||
help: "n.nnn (json-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "string",
|
||||
reflectType: reflect.TypeOf(""),
|
||||
key: "json-type-string",
|
||||
examples: []string{`"json-example-string"`},
|
||||
help: "\"json-example-string\" (json-type-string) fdk",
|
||||
},
|
||||
{
|
||||
name: "bool",
|
||||
reflectType: reflect.TypeOf(true),
|
||||
key: "json-type-bool",
|
||||
examples: []string{"json-example-bool"},
|
||||
help: "json-example-bool (json-type-bool) fdk",
|
||||
},
|
||||
{
|
||||
name: "array of int",
|
||||
reflectType: reflect.TypeOf([1]int{0}),
|
||||
key: "json-type-arrayjson-type-numeric",
|
||||
examples: []string{"[n,...]"},
|
||||
help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "slice of int",
|
||||
reflectType: reflect.TypeOf([]int{0}),
|
||||
key: "json-type-arrayjson-type-numeric",
|
||||
examples: []string{"[n,...]"},
|
||||
help: "[n,...] (json-type-arrayjson-type-numeric) fdk",
|
||||
},
|
||||
{
|
||||
name: "struct",
|
||||
reflectType: reflect.TypeOf(struct{}{}),
|
||||
key: "json-type-object",
|
||||
examples: []string{"{", "}\t\t"},
|
||||
isComplex: true,
|
||||
help: "{\n} ",
|
||||
},
|
||||
{
|
||||
name: "struct indent level 1",
|
||||
reflectType: reflect.TypeOf(struct{ field int }{}),
|
||||
indentLevel: 1,
|
||||
key: "json-type-object",
|
||||
examples: []string{
|
||||
" \"field\": n,\t(json-type-numeric)\t-field",
|
||||
" },\t\t",
|
||||
},
|
||||
help: "{\n" +
|
||||
" \"field\": n, (json-type-numeric) -field\n" +
|
||||
"} ",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "array of struct indent level 0",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field int
|
||||
}
|
||||
return reflect.TypeOf([]s{})
|
||||
}(),
|
||||
key: "json-type-arrayjson-type-object",
|
||||
examples: []string{
|
||||
"[{",
|
||||
" \"field\": n,\t(json-type-numeric)\ts-field",
|
||||
"},...]",
|
||||
},
|
||||
help: "[{\n" +
|
||||
" \"field\": n, (json-type-numeric) s-field\n" +
|
||||
"},...]",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "array of struct indent level 1",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field int
|
||||
}
|
||||
return reflect.TypeOf([]s{})
|
||||
}(),
|
||||
indentLevel: 1,
|
||||
key: "json-type-arrayjson-type-object",
|
||||
examples: []string{
|
||||
" \"field\": n,\t(json-type-numeric)\ts-field",
|
||||
" },...],\t\t",
|
||||
},
|
||||
help: "[{\n" +
|
||||
" \"field\": n, (json-type-numeric) s-field\n" +
|
||||
"},...]",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "map",
|
||||
reflectType: reflect.TypeOf(map[string]string{}),
|
||||
key: "json-type-object",
|
||||
examples: []string{"{",
|
||||
" \"fdk--key\": fdk--value, (json-type-object) fdk--desc",
|
||||
" ...", "}",
|
||||
},
|
||||
help: "{\n" +
|
||||
" \"fdk--key\": fdk--value, (json-type-object) fdk--desc\n" +
|
||||
" ...\n" +
|
||||
"}",
|
||||
isComplex: true,
|
||||
},
|
||||
{
|
||||
name: "complex",
|
||||
reflectType: reflect.TypeOf(complex64(0)),
|
||||
key: "json-type-value",
|
||||
examples: []string{"json-example-unknown"},
|
||||
help: "json-example-unknown (json-type-value) fdk",
|
||||
isInvalid: true,
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
// Ensure the description key is the expected value.
|
||||
key := btcjson.TstReflectTypeToJSONType(xT, test.reflectType)
|
||||
if key != test.key {
|
||||
t.Errorf("Test #%d (%s) unexpected key - got: %v, "+
|
||||
"want: %v", i, test.name, key, test.key)
|
||||
continue
|
||||
}
|
||||
|
||||
// Ensure the generated example is as expected.
|
||||
examples, isComplex := btcjson.TstReflectTypeToJSONExample(xT,
|
||||
test.reflectType, test.indentLevel, "fdk")
|
||||
if isComplex != test.isComplex {
|
||||
t.Errorf("Test #%d (%s) unexpected isComplex - got: %v, "+
|
||||
"want: %v", i, test.name, isComplex,
|
||||
test.isComplex)
|
||||
continue
|
||||
}
|
||||
if len(examples) != len(test.examples) {
|
||||
t.Errorf("Test #%d (%s) unexpected result length - "+
|
||||
"got: %v, want: %v", i, test.name, len(examples),
|
||||
len(test.examples))
|
||||
continue
|
||||
}
|
||||
for j, example := range examples {
|
||||
if example != test.examples[j] {
|
||||
t.Errorf("Test #%d (%s) example #%d unexpected "+
|
||||
"example - got: %v, want: %v", i,
|
||||
test.name, j, example, test.examples[j])
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure the generated result type help is as expected.
|
||||
helpText := btcjson.TstResultTypeHelp(xT, test.reflectType, "fdk")
|
||||
if helpText != test.help {
|
||||
t.Errorf("Test #%d (%s) unexpected result help - "+
|
||||
"got: %v, want: %v", i, test.name, helpText,
|
||||
test.help)
|
||||
continue
|
||||
}
|
||||
|
||||
isValid := btcjson.TstIsValidResultType(test.reflectType.Kind())
|
||||
if isValid != !test.isInvalid {
|
||||
t.Errorf("Test #%d (%s) unexpected result type validity "+
|
||||
"- got: %v", i, test.name, isValid)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestResultStructHelp ensures the expected help text format is returned for
|
||||
// various Go struct types.
|
||||
func TestResultStructHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
reflectType reflect.Type
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty struct",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "struct with primitive field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field int
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": n,\t(json-type-numeric)\ts-field",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with primitive field and json tag",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field int `json:"f"`
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"f\": n,\t(json-type-numeric)\ts-f",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with array of primitive field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
field []int
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": [n,...],\t(json-type-arrayjson-type-numeric)\ts-field",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with sub-struct field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
subField int
|
||||
}
|
||||
type s struct {
|
||||
field s2
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": {\t(json-type-object)\ts-field",
|
||||
"{",
|
||||
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||
"}\t\t",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with sub-struct field pointer",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
subField int
|
||||
}
|
||||
type s struct {
|
||||
field *s2
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": {\t(json-type-object)\ts-field",
|
||||
"{",
|
||||
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||
"}\t\t",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "struct with array of structs field",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
subField int
|
||||
}
|
||||
type s struct {
|
||||
field []s2
|
||||
}
|
||||
return reflect.TypeOf(s{})
|
||||
}(),
|
||||
expected: []string{
|
||||
"\"field\": [{\t(json-type-arrayjson-type-object)\ts-field",
|
||||
"[{",
|
||||
" \"subfield\": n,\t(json-type-numeric)\ts2-subfield",
|
||||
"},...]",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
results := btcjson.TstResultStructHelp(xT, test.reflectType, 0)
|
||||
if len(results) != len(test.expected) {
|
||||
t.Errorf("Test #%d (%s) unexpected result length - "+
|
||||
"got: %v, want: %v", i, test.name, len(results),
|
||||
len(test.expected))
|
||||
continue
|
||||
}
|
||||
for j, result := range results {
|
||||
if result != test.expected[j] {
|
||||
t.Errorf("Test #%d (%s) result #%d unexpected "+
|
||||
"result - got: %v, want: %v", i,
|
||||
test.name, j, result, test.expected[j])
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestHelpArgInternals ensures the various help functions which deal with
|
||||
// arguments work as expected for various argument types.
|
||||
func TestHelpArgInternals(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
reflectType reflect.Type
|
||||
defaults map[int]reflect.Value
|
||||
help string
|
||||
}{
|
||||
{
|
||||
name: "command with no args",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "",
|
||||
},
|
||||
{
|
||||
name: "command with one required arg",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field int
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-numeric, help-required) test-field\n",
|
||||
},
|
||||
{
|
||||
name: "command with one optional arg, no default",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Optional *int
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. optional (json-type-numeric, help-optional) test-optional\n",
|
||||
},
|
||||
{
|
||||
name: "command with one optional arg with default",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Optional *string
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: func() map[int]reflect.Value {
|
||||
defVal := "test"
|
||||
return map[int]reflect.Value{
|
||||
0: reflect.ValueOf(&defVal),
|
||||
}
|
||||
}(),
|
||||
help: "1. optional (json-type-string, help-optional, help-default=\"test\") test-optional\n",
|
||||
},
|
||||
{
|
||||
name: "command with struct field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
F int8
|
||||
}
|
||||
type s struct {
|
||||
Field s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-object, help-required) test-field\n" +
|
||||
"{\n" +
|
||||
" \"f\": n, (json-type-numeric) s2-f\n" +
|
||||
"} \n",
|
||||
},
|
||||
{
|
||||
name: "command with map field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field map[string]float64
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-object, help-required) test-field\n" +
|
||||
"{\n" +
|
||||
" \"test-field--key\": test-field--value, (json-type-object) test-field--desc\n" +
|
||||
" ...\n" +
|
||||
"}\n",
|
||||
},
|
||||
{
|
||||
name: "command with slice of primitives field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field []int64
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-arrayjson-type-numeric, help-required) test-field\n",
|
||||
},
|
||||
{
|
||||
name: "command with slice of structs field",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s2 struct {
|
||||
F int64
|
||||
}
|
||||
type s struct {
|
||||
Field []s2
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
defaults: nil,
|
||||
help: "1. field (json-type-arrayjson-type-object, help-required) test-field\n" +
|
||||
"[{\n" +
|
||||
" \"f\": n, (json-type-numeric) s2-f\n" +
|
||||
"},...]\n",
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
help := btcjson.TstArgHelp(xT, test.reflectType, test.defaults,
|
||||
test.method)
|
||||
if help != test.help {
|
||||
t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
|
||||
"want:\n%v", i, test.name, help, test.help)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMethodHelp ensures the method help function works as expected for various
|
||||
// command structs.
|
||||
func TestMethodHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
reflectType reflect.Type
|
||||
defaults map[int]reflect.Value
|
||||
resultTypes []interface{}
|
||||
help string
|
||||
}{
|
||||
{
|
||||
name: "command with no args or results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
help: "test\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||
"help-result:\nhelp-result-nothing\n",
|
||||
},
|
||||
{
|
||||
name: "command with no args and one primitive result",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
resultTypes: []interface{}{(*int64)(nil)},
|
||||
help: "test\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||
"help-result:\nn (json-type-numeric) test--result0\n",
|
||||
},
|
||||
{
|
||||
name: "command with no args and two results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct{}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
resultTypes: []interface{}{(*int64)(nil), nil},
|
||||
help: "test\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\nhelp-arguments-none\n\n" +
|
||||
"help-result (test--condition0):\nn (json-type-numeric) test--result0\n\n" +
|
||||
"help-result (test--condition1):\nhelp-result-nothing\n",
|
||||
},
|
||||
{
|
||||
name: "command with primitive arg and no results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field bool
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
help: "test field\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\n1. field (json-type-bool, help-required) test-field\n\n" +
|
||||
"help-result:\nhelp-result-nothing\n",
|
||||
},
|
||||
{
|
||||
name: "command with primitive optional and no results",
|
||||
method: "test",
|
||||
reflectType: func() reflect.Type {
|
||||
type s struct {
|
||||
Field *bool
|
||||
}
|
||||
return reflect.TypeOf((*s)(nil))
|
||||
}(),
|
||||
help: "test (field)\n\ntest--synopsis\n\n" +
|
||||
"help-arguments:\n1. field (json-type-bool, help-optional) test-field\n\n" +
|
||||
"help-result:\nhelp-result-nothing\n",
|
||||
},
|
||||
}
|
||||
|
||||
xT := func(key string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
help := btcjson.TestMethodHelp(xT, test.reflectType,
|
||||
test.defaults, test.method, test.resultTypes)
|
||||
if help != test.help {
|
||||
t.Errorf("Test #%d (%s) unexpected help - got:\n%v\n"+
|
||||
"want:\n%v", i, test.name, help, test.help)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateHelpErrors ensures the GenerateHelp function returns the expected
|
||||
// errors.
|
||||
func TestGenerateHelpErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
resultTypes []interface{}
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "unregistered command",
|
||||
method: "boguscommand",
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnregisteredMethod},
|
||||
},
|
||||
{
|
||||
name: "non-pointer result type",
|
||||
method: "help",
|
||||
resultTypes: []interface{}{0},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid result type",
|
||||
method: "help",
|
||||
resultTypes: []interface{}{(*complex64)(nil)},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "missing description",
|
||||
method: "help",
|
||||
resultTypes: []interface{}{(*string)(nil), nil},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrMissingDescription},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
_, err := btcjson.GenerateHelp(test.method, nil,
|
||||
test.resultTypes...)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T (%v), "+
|
||||
"want %T", i, test.name, err, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v (%v), want %v", i, test.name, gotErrorCode,
|
||||
err, test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestGenerateHelp performs a very basic test to ensure GenerateHelp is working
|
||||
// as expected. The internal are testd much more thoroughly in other tests, so
|
||||
// there is no need to add more tests here.
|
||||
func TestGenerateHelp(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
descs := map[string]string{
|
||||
"help--synopsis": "test",
|
||||
"help-command": "test",
|
||||
}
|
||||
help, err := btcjson.GenerateHelp("help", descs)
|
||||
if err != nil {
|
||||
t.Fatalf("GenerateHelp: unexpected error: %v", err)
|
||||
}
|
||||
wantHelp := "help (\"command\")\n\n" +
|
||||
"test\n\nArguments:\n1. command (string, optional) test\n\n" +
|
||||
"Result:\nNothing\n"
|
||||
if help != wantHelp {
|
||||
t.Fatalf("GenerateHelp: unexpected help - got\n%v\nwant\n%v",
|
||||
help, wantHelp)
|
||||
}
|
||||
}
|
|
@ -1,263 +0,0 @@
|
|||
// Copyright (c) 2014 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package btcjson_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/lbryio/lbcd/btcjson"
|
||||
)
|
||||
|
||||
// TestUsageFlagStringer tests the stringized output for the UsageFlag type.
|
||||
func TestUsageFlagStringer(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
in btcjson.UsageFlag
|
||||
want string
|
||||
}{
|
||||
{0, "0x0"},
|
||||
{btcjson.UFWalletOnly, "UFWalletOnly"},
|
||||
{btcjson.UFWebsocketOnly, "UFWebsocketOnly"},
|
||||
{btcjson.UFNotification, "UFNotification"},
|
||||
{btcjson.UFWalletOnly | btcjson.UFWebsocketOnly,
|
||||
"UFWalletOnly|UFWebsocketOnly"},
|
||||
{btcjson.UFWalletOnly | btcjson.UFWebsocketOnly | (1 << 31),
|
||||
"UFWalletOnly|UFWebsocketOnly|0x80000000"},
|
||||
}
|
||||
|
||||
// Detect additional usage flags that don't have the stringer added.
|
||||
numUsageFlags := 0
|
||||
highestUsageFlagBit := btcjson.TstHighestUsageFlagBit
|
||||
for highestUsageFlagBit > 1 {
|
||||
numUsageFlags++
|
||||
highestUsageFlagBit >>= 1
|
||||
}
|
||||
if len(tests)-3 != numUsageFlags {
|
||||
t.Errorf("It appears a usage flag was added without adding " +
|
||||
"an associated stringer test")
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
result := test.in.String()
|
||||
if result != test.want {
|
||||
t.Errorf("String #%d\n got: %s want: %s", i, result,
|
||||
test.want)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegisterCmdErrors ensures the RegisterCmd function returns the expected
|
||||
// error when provided with invalid types.
|
||||
func TestRegisterCmdErrors(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
method string
|
||||
cmdFunc func() interface{}
|
||||
flags btcjson.UsageFlag
|
||||
err btcjson.Error
|
||||
}{
|
||||
{
|
||||
name: "duplicate method",
|
||||
method: "getblock",
|
||||
cmdFunc: func() interface{} {
|
||||
return struct{}{}
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrDuplicateMethod},
|
||||
},
|
||||
{
|
||||
name: "invalid usage flags",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
return 0
|
||||
},
|
||||
flags: btcjson.TstHighestUsageFlagBit,
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidUsageFlags},
|
||||
},
|
||||
{
|
||||
name: "invalid type",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
return 0
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "invalid type 2",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
return &[]string{}
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrInvalidType},
|
||||
},
|
||||
{
|
||||
name: "embedded field",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrEmbeddedType},
|
||||
},
|
||||
{
|
||||
name: "unexported field",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ a int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnexportedField},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 1",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A **int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 2",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A chan int }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 3",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A complex64 }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 4",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A complex128 }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 5",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A func() }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "unsupported field type 6",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct{ A interface{} }
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrUnsupportedFieldType},
|
||||
},
|
||||
{
|
||||
name: "required after optional",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct {
|
||||
A *int
|
||||
B int
|
||||
}
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalField},
|
||||
},
|
||||
{
|
||||
name: "non-optional with default",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct {
|
||||
A int `jsonrpcdefault:"1"`
|
||||
}
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrNonOptionalDefault},
|
||||
},
|
||||
{
|
||||
name: "mismatched default",
|
||||
method: "registertestcmd",
|
||||
cmdFunc: func() interface{} {
|
||||
type test struct {
|
||||
A *int `jsonrpcdefault:"1.7"`
|
||||
}
|
||||
return (*test)(nil)
|
||||
},
|
||||
err: btcjson.Error{ErrorCode: btcjson.ErrMismatchedDefault},
|
||||
},
|
||||
}
|
||||
|
||||
t.Logf("Running %d tests", len(tests))
|
||||
for i, test := range tests {
|
||||
err := btcjson.RegisterCmd(test.method, test.cmdFunc(),
|
||||
test.flags)
|
||||
if reflect.TypeOf(err) != reflect.TypeOf(test.err) {
|
||||
t.Errorf("Test #%d (%s) wrong error - got %T, "+
|
||||
"want %T", i, test.name, err, test.err)
|
||||
continue
|
||||
}
|
||||
gotErrorCode := err.(btcjson.Error).ErrorCode
|
||||
if gotErrorCode != test.err.ErrorCode {
|
||||
t.Errorf("Test #%d (%s) mismatched error code - got "+
|
||||
"%v, want %v", i, test.name, gotErrorCode,
|
||||
test.err.ErrorCode)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestMustRegisterCmdPanic ensures the MustRegisterCmd function panics when
|
||||
// used to register an invalid type.
|
||||
func TestMustRegisterCmdPanic(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Setup a defer to catch the expected panic to ensure it actually
|
||||
// paniced.
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Error("MustRegisterCmd did not panic as expected")
|
||||
}
|
||||
}()
|
||||
|
||||
// Intentionally try to register an invalid type to force a panic.
|
||||
btcjson.MustRegisterCmd("panicme", 0, 0)
|
||||
}
|
||||
|
||||
// TestRegisteredCmdMethods tests the RegisteredCmdMethods function ensure it
|
||||
// works as expected.
|
||||
func TestRegisteredCmdMethods(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Ensure the registered methods are returned.
|
||||
methods := btcjson.RegisteredCmdMethods()
|
||||
if len(methods) == 0 {
|
||||
t.Fatal("RegisteredCmdMethods: no methods")
|
||||
}
|
||||
|
||||
// Ensure the returned methods are sorted.
|
||||
sortedMethods := make([]string, len(methods))
|
||||
copy(sortedMethods, methods)
|
||||
sort.Strings(sortedMethods)
|
||||
if !reflect.DeepEqual(sortedMethods, methods) {
|
||||
t.Fatal("RegisteredCmdMethods: methods are not sorted")
|
||||
}
|
||||
}
|
|
@ -176,13 +176,12 @@ func NewGetAccountCmd(address string) *GetAccountCmd {
|
|||
|
||||
// GetAccountAddressCmd defines the getaccountaddress JSON-RPC command.
|
||||
type GetAccountAddressCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewGetAccountAddressCmd returns a new instance which can be used to issue a
|
||||
// getaccountaddress JSON-RPC command.
|
||||
func NewGetAccountAddressCmd(account *string) *GetAccountAddressCmd {
|
||||
func NewGetAccountAddressCmd(account string) *GetAccountAddressCmd {
|
||||
return &GetAccountAddressCmd{
|
||||
Account: account,
|
||||
}
|
||||
|
@ -190,13 +189,12 @@ func NewGetAccountAddressCmd(account *string) *GetAccountAddressCmd {
|
|||
|
||||
// GetAddressesByAccountCmd defines the getaddressesbyaccount JSON-RPC command.
|
||||
type GetAddressesByAccountCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewGetAddressesByAccountCmd returns a new instance which can be used to issue
|
||||
// a getaddressesbyaccount JSON-RPC command.
|
||||
func NewGetAddressesByAccountCmd(account *string) *GetAddressesByAccountCmd {
|
||||
func NewGetAddressesByAccountCmd(account string) *GetAddressesByAccountCmd {
|
||||
return &GetAddressesByAccountCmd{
|
||||
Account: account,
|
||||
}
|
||||
|
@ -217,9 +215,8 @@ func NewGetAddressInfoCmd(address string) *GetAddressInfoCmd {
|
|||
|
||||
// GetBalanceCmd defines the getbalance JSON-RPC command.
|
||||
type GetBalanceCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||
Account *string
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewGetBalanceCmd returns a new instance which can be used to issue a
|
||||
|
@ -245,8 +242,7 @@ func NewGetBalancesCmd() *GetBalancesCmd {
|
|||
|
||||
// GetNewAddressCmd defines the getnewaddress JSON-RPC command.
|
||||
type GetNewAddressCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewGetNewAddressCmd returns a new instance which can be used to issue a
|
||||
|
@ -262,8 +258,7 @@ func NewGetNewAddressCmd(account *string) *GetNewAddressCmd {
|
|||
|
||||
// GetRawChangeAddressCmd defines the getrawchangeaddress JSON-RPC command.
|
||||
type GetRawChangeAddressCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
AddressType *string `jsonrpcdefault:"\"legacy\""`
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewGetRawChangeAddressCmd returns a new instance which can be used to issue a
|
||||
|
@ -279,8 +274,8 @@ func NewGetRawChangeAddressCmd(account *string) *GetRawChangeAddressCmd {
|
|||
|
||||
// GetReceivedByAccountCmd defines the getreceivedbyaccount JSON-RPC command.
|
||||
type GetReceivedByAccountCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
Account string
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewGetReceivedByAccountCmd returns a new instance which can be used to issue
|
||||
|
@ -288,7 +283,7 @@ type GetReceivedByAccountCmd struct {
|
|||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewGetReceivedByAccountCmd(account *string, minConf *int) *GetReceivedByAccountCmd {
|
||||
func NewGetReceivedByAccountCmd(account string, minConf *int) *GetReceivedByAccountCmd {
|
||||
return &GetReceivedByAccountCmd{
|
||||
Account: account,
|
||||
MinConf: minConf,
|
||||
|
@ -411,8 +406,7 @@ func NewKeyPoolRefillCmd(newSize *uint) *KeyPoolRefillCmd {
|
|||
|
||||
// ListAccountsCmd defines the listaccounts JSON-RPC command.
|
||||
type ListAccountsCmd struct {
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
}
|
||||
|
||||
// NewListAccountsCmd returns a new instance which can be used to issue a
|
||||
|
@ -506,10 +500,10 @@ func NewListSinceBlockCmd(blockHash *string, targetConfirms *int, includeWatchOn
|
|||
|
||||
// ListTransactionsCmd defines the listtransactions JSON-RPC command.
|
||||
type ListTransactionsCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
Count *int `jsonrpcdefault:"10"`
|
||||
From *int `jsonrpcdefault:"0"`
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
Account *string
|
||||
Count *int `jsonrpcdefault:"10"`
|
||||
From *int `jsonrpcdefault:"0"`
|
||||
IncludeWatchOnly *bool `jsonrpcdefault:"false"`
|
||||
}
|
||||
|
||||
// NewListTransactionsCmd returns a new instance which can be used to issue a
|
||||
|
@ -561,13 +555,36 @@ 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.
|
||||
type SendFromCmd struct {
|
||||
FromAccount string
|
||||
ToAddress string
|
||||
Amount float64 // In BTC
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||
Comment *string
|
||||
CommentTo *string
|
||||
}
|
||||
|
@ -577,15 +594,12 @@ type SendFromCmd struct {
|
|||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendFromCmd(fromAccount, toAddress string, amount float64,
|
||||
minConf *int, addrType *string, comment, commentTo *string) *SendFromCmd {
|
||||
|
||||
func NewSendFromCmd(fromAccount, toAddress string, amount float64, minConf *int, comment, commentTo *string) *SendFromCmd {
|
||||
return &SendFromCmd{
|
||||
FromAccount: fromAccount,
|
||||
ToAddress: toAddress,
|
||||
Amount: amount,
|
||||
MinConf: minConf,
|
||||
AddressType: addrType,
|
||||
Comment: comment,
|
||||
CommentTo: commentTo,
|
||||
}
|
||||
|
@ -596,7 +610,6 @@ type SendManyCmd struct {
|
|||
FromAccount string
|
||||
Amounts map[string]float64 `jsonrpcusage:"{\"address\":amount,...}"` // In BTC
|
||||
MinConf *int `jsonrpcdefault:"1"`
|
||||
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||
Comment *string
|
||||
}
|
||||
|
||||
|
@ -605,24 +618,21 @@ type SendManyCmd struct {
|
|||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendManyCmd(fromAccount string, amounts map[string]float64,
|
||||
minConf *int, addrType *string, comment *string) *SendManyCmd {
|
||||
func NewSendManyCmd(fromAccount string, amounts map[string]float64, minConf *int, comment *string) *SendManyCmd {
|
||||
return &SendManyCmd{
|
||||
FromAccount: fromAccount,
|
||||
Amounts: amounts,
|
||||
MinConf: minConf,
|
||||
AddressType: addrType,
|
||||
Comment: comment,
|
||||
}
|
||||
}
|
||||
|
||||
// SendToAddressCmd defines the sendtoaddress JSON-RPC command.
|
||||
type SendToAddressCmd struct {
|
||||
Address string
|
||||
Amount float64
|
||||
AddressType *string `jsonrpcdefault:"\"*\""`
|
||||
Comment *string
|
||||
CommentTo *string
|
||||
Address string
|
||||
Amount float64
|
||||
Comment *string
|
||||
CommentTo *string
|
||||
}
|
||||
|
||||
// NewSendToAddressCmd returns a new instance which can be used to issue a
|
||||
|
@ -630,14 +640,27 @@ type SendToAddressCmd struct {
|
|||
//
|
||||
// The parameters which are pointers indicate they are optional. Passing nil
|
||||
// for optional parameters will use the default value.
|
||||
func NewSendToAddressCmd(address string, amount float64, addrType *string,
|
||||
comment, commentTo *string) *SendToAddressCmd {
|
||||
func NewSendToAddressCmd(address string, amount float64, comment, commentTo *string) *SendToAddressCmd {
|
||||
return &SendToAddressCmd{
|
||||
Address: address,
|
||||
Amount: amount,
|
||||
AddressType: addrType,
|
||||
Comment: comment,
|
||||
CommentTo: commentTo,
|
||||
Address: address,
|
||||
Amount: amount,
|
||||
Comment: comment,
|
||||
CommentTo: commentTo,
|
||||
}
|
||||
}
|
||||
|
||||
// SetAccountCmd defines the setaccount JSON-RPC command.
|
||||
type SetAccountCmd struct {
|
||||
Address string
|
||||
Account string
|
||||
}
|
||||
|
||||
// NewSetAccountCmd returns a new instance which can be used to issue a
|
||||
// setaccount JSON-RPC command.
|
||||
func NewSetAccountCmd(address, account string) *SetAccountCmd {
|
||||
return &SetAccountCmd{
|
||||
Address: address,
|
||||
Account: account,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -848,8 +871,7 @@ func (s *ScriptPubKey) UnmarshalJSON(data []byte) error {
|
|||
//
|
||||
// Descriptors are typically ranged when specified in the form of generic HD
|
||||
// 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
|
||||
// itself, as []int{begin, end}.
|
||||
|
@ -975,24 +997,6 @@ 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
|
||||
// WalletCreateFundedPsbtCmd command.
|
||||
type PsbtInput struct {
|
||||
|
@ -1114,10 +1118,11 @@ func init() {
|
|||
MustRegisterCmd("listunspent", (*ListUnspentCmd)(nil), flags)
|
||||
MustRegisterCmd("loadwallet", (*LoadWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("lockunspent", (*LockUnspentCmd)(nil), flags)
|
||||
MustRegisterCmd("rescanblockchain", (*RescanBlockchainCmd)(nil), flags)
|
||||
MustRegisterCmd("move", (*MoveCmd)(nil), flags)
|
||||
MustRegisterCmd("sendfrom", (*SendFromCmd)(nil), flags)
|
||||
MustRegisterCmd("sendmany", (*SendManyCmd)(nil), flags)
|
||||
MustRegisterCmd("sendtoaddress", (*SendToAddressCmd)(nil), flags)
|
||||
MustRegisterCmd("setaccount", (*SetAccountCmd)(nil), flags)
|
||||
MustRegisterCmd("settxfee", (*SetTxFeeCmd)(nil), flags)
|
||||
MustRegisterCmd("signmessage", (*SignMessageCmd)(nil), flags)
|
||||
MustRegisterCmd("signrawtransaction", (*SignRawTransactionCmd)(nil), flags)
|
||||
|
|
|
@ -287,12 +287,11 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("getaccountaddress", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetAccountAddressCmd(btcjson.String("acct"))
|
||||
return btcjson.NewGetAccountAddressCmd("acct")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getaccountaddress","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetAccountAddressCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Account: "acct",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -301,12 +300,11 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("getaddressesbyaccount", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetAddressesByAccountCmd(btcjson.String("acct"))
|
||||
return btcjson.NewGetAddressesByAccountCmd("acct")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getaddressesbyaccount","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetAddressesByAccountCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
AddressType: btcjson.String("*"),
|
||||
Account: "acct",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -332,9 +330,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetBalanceCmd{
|
||||
Account: btcjson.String("default"),
|
||||
MinConf: btcjson.Int(1),
|
||||
AddressType: btcjson.String("*"),
|
||||
Account: nil,
|
||||
MinConf: btcjson.Int(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -347,9 +344,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetBalanceCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
MinConf: btcjson.Int(1),
|
||||
AddressType: btcjson.String("*"),
|
||||
Account: btcjson.String("acct"),
|
||||
MinConf: btcjson.Int(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -362,9 +358,8 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getbalance","params":["acct",6],"id":1}`,
|
||||
unmarshalled: &btcjson.GetBalanceCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
MinConf: btcjson.Int(6),
|
||||
AddressType: btcjson.String("*"),
|
||||
Account: btcjson.String("acct"),
|
||||
MinConf: btcjson.Int(6),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -388,8 +383,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetNewAddressCmd{
|
||||
Account: btcjson.String("default"),
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -402,8 +396,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getnewaddress","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetNewAddressCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Account: btcjson.String("acct"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -416,8 +409,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetRawChangeAddressCmd{
|
||||
Account: btcjson.String("default"),
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -430,8 +422,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getrawchangeaddress","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetRawChangeAddressCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Account: btcjson.String("acct"),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -440,11 +431,11 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("getreceivedbyaccount", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("acct"), nil)
|
||||
return btcjson.NewGetReceivedByAccountCmd("acct", nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.GetReceivedByAccountCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
Account: "acct",
|
||||
MinConf: btcjson.Int(1),
|
||||
},
|
||||
},
|
||||
|
@ -454,11 +445,11 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("getreceivedbyaccount", "acct", 6)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewGetReceivedByAccountCmd(btcjson.String("acct"), btcjson.Int(6))
|
||||
return btcjson.NewGetReceivedByAccountCmd("acct", btcjson.Int(6))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getreceivedbyaccount","params":["acct",6],"id":1}`,
|
||||
unmarshalled: &btcjson.GetReceivedByAccountCmd{
|
||||
Account: btcjson.String("acct"),
|
||||
Account: "acct",
|
||||
MinConf: btcjson.Int(6),
|
||||
},
|
||||
},
|
||||
|
@ -610,8 +601,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAccountsCmd{
|
||||
MinConf: btcjson.Int(1),
|
||||
AddressType: btcjson.String("*"),
|
||||
MinConf: btcjson.Int(1),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -624,8 +614,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listaccounts","params":[6],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAccountsCmd{
|
||||
MinConf: btcjson.Int(6),
|
||||
AddressType: btcjson.String("*"),
|
||||
MinConf: btcjson.Int(6),
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -855,7 +844,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listtransactions","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.ListTransactionsCmd{
|
||||
Account: btcjson.String("default"),
|
||||
Account: nil,
|
||||
Count: btcjson.Int(10),
|
||||
From: btcjson.Int(0),
|
||||
IncludeWatchOnly: btcjson.Bool(false),
|
||||
|
@ -1007,13 +996,64 @@ 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",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil, nil)
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, nil, nil, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5],"id":1}`,
|
||||
unmarshalled: &btcjson.SendFromCmd{
|
||||
|
@ -1021,7 +1061,6 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
ToAddress: "1Address",
|
||||
Amount: 0.5,
|
||||
MinConf: btcjson.Int(1),
|
||||
AddressType: btcjson.String("*"),
|
||||
Comment: nil,
|
||||
CommentTo: nil,
|
||||
},
|
||||
|
@ -1032,7 +1071,7 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil, nil)
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), nil, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6],"id":1}`,
|
||||
unmarshalled: &btcjson.SendFromCmd{
|
||||
|
@ -1040,7 +1079,6 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
ToAddress: "1Address",
|
||||
Amount: 0.5,
|
||||
MinConf: btcjson.Int(6),
|
||||
AddressType: btcjson.String("*"),
|
||||
Comment: nil,
|
||||
CommentTo: nil,
|
||||
},
|
||||
|
@ -1048,59 +1086,37 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
{
|
||||
name: "sendfrom optional2",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy")
|
||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||
nil, nil)
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
|
||||
btcjson.String("comment"), nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy"],"id":1}`,
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"comment"],"id":1}`,
|
||||
unmarshalled: &btcjson.SendFromCmd{
|
||||
FromAccount: "from",
|
||||
ToAddress: "1Address",
|
||||
Amount: 0.5,
|
||||
MinConf: btcjson.Int(6),
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Comment: nil,
|
||||
Comment: btcjson.String("comment"),
|
||||
CommentTo: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sendfrom optional3",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "legacy", "comment")
|
||||
return btcjson.NewCmd("sendfrom", "from", "1Address", 0.5, 6, "comment", "commentto")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6), btcjson.String("legacy"),
|
||||
btcjson.String("comment"), nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment"],"id":1}`,
|
||||
unmarshalled: &btcjson.SendFromCmd{
|
||||
FromAccount: "from",
|
||||
ToAddress: "1Address",
|
||||
Amount: 0.5,
|
||||
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"),
|
||||
return btcjson.NewSendFromCmd("from", "1Address", 0.5, btcjson.Int(6),
|
||||
btcjson.String("comment"), btcjson.String("commentto"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"legacy","comment","commentto"],"id":1}`,
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendfrom","params":["from","1Address",0.5,6,"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"),
|
||||
CommentTo: btcjson.String("commentto"),
|
||||
},
|
||||
|
@ -1112,14 +1128,13 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
staticCmd: func() interface{} {
|
||||
amounts := map[string]float64{"1Address": 0.5}
|
||||
return btcjson.NewSendManyCmd("from", amounts, nil, nil, nil)
|
||||
return btcjson.NewSendManyCmd("from", amounts, nil, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5}],"id":1}`,
|
||||
unmarshalled: &btcjson.SendManyCmd{
|
||||
FromAccount: "from",
|
||||
Amounts: map[string]float64{"1Address": 0.5},
|
||||
MinConf: btcjson.Int(1),
|
||||
AddressType: btcjson.String("*"),
|
||||
Comment: nil,
|
||||
},
|
||||
},
|
||||
|
@ -1130,50 +1145,30 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
},
|
||||
staticCmd: func() interface{} {
|
||||
amounts := map[string]float64{"1Address": 0.5}
|
||||
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil, nil)
|
||||
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6],"id":1}`,
|
||||
unmarshalled: &btcjson.SendManyCmd{
|
||||
FromAccount: "from",
|
||||
Amounts: map[string]float64{"1Address": 0.5},
|
||||
MinConf: btcjson.Int(6),
|
||||
AddressType: btcjson.String("*"),
|
||||
Comment: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sendmany optional2",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "legacy")
|
||||
return btcjson.NewCmd("sendmany", "from", `{"1Address":0.5}`, 6, "comment")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
amounts := map[string]float64{"1Address": 0.5}
|
||||
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("legacy"), nil)
|
||||
return btcjson.NewSendManyCmd("from", amounts, btcjson.Int(6), btcjson.String("comment"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"legacy"],"id":1}`,
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendmany","params":["from",{"1Address":0.5},6,"comment"],"id":1}`,
|
||||
unmarshalled: &btcjson.SendManyCmd{
|
||||
FromAccount: "from",
|
||||
Amounts: map[string]float64{"1Address": 0.5},
|
||||
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"),
|
||||
},
|
||||
},
|
||||
|
@ -1183,50 +1178,45 @@ func TestWalletSvrCmds(t *testing.T) {
|
|||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil, nil)
|
||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, nil, nil)
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5],"id":1}`,
|
||||
unmarshalled: &btcjson.SendToAddressCmd{
|
||||
Address: "1Address",
|
||||
Amount: 0.5,
|
||||
AddressType: btcjson.String("*"),
|
||||
Comment: nil,
|
||||
CommentTo: nil,
|
||||
Address: "1Address",
|
||||
Amount: 0.5,
|
||||
Comment: nil,
|
||||
CommentTo: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sendtoaddress optional1",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy")
|
||||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "comment", "commentto")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), nil, nil)
|
||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("comment"),
|
||||
btcjson.String("commentto"))
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy"],"id":1}`,
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"comment","commentto"],"id":1}`,
|
||||
unmarshalled: &btcjson.SendToAddressCmd{
|
||||
Address: "1Address",
|
||||
Amount: 0.5,
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Comment: nil,
|
||||
CommentTo: nil,
|
||||
Address: "1Address",
|
||||
Amount: 0.5,
|
||||
Comment: btcjson.String("comment"),
|
||||
CommentTo: btcjson.String("commentto"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "sendtoaddress optional2",
|
||||
name: "setaccount",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("sendtoaddress", "1Address", 0.5, "legacy", "comment", "commentto")
|
||||
return btcjson.NewCmd("setaccount", "1Address", "acct")
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewSendToAddressCmd("1Address", 0.5, btcjson.String("legacy"), btcjson.String("comment"),
|
||||
btcjson.String("commentto"))
|
||||
return btcjson.NewSetAccountCmd("1Address", "acct")
|
||||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"sendtoaddress","params":["1Address",0.5,"legacy","comment","commentto"],"id":1}`,
|
||||
unmarshalled: &btcjson.SendToAddressCmd{
|
||||
Address: "1Address",
|
||||
Amount: 0.5,
|
||||
AddressType: btcjson.String("legacy"),
|
||||
Comment: btcjson.String("comment"),
|
||||
CommentTo: btcjson.String("commentto"),
|
||||
marshalled: `{"jsonrpc":"1.0","method":"setaccount","params":["1Address","acct"],"id":1}`,
|
||||
unmarshalled: &btcjson.SetAccountCmd{
|
||||
Address: "1Address",
|
||||
Account: "acct",
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -25,6 +25,7 @@ type CreateWalletResult struct {
|
|||
type embeddedAddressInfo struct {
|
||||
Address string `json:"address"`
|
||||
ScriptPubKey string `json:"scriptPubKey"`
|
||||
Solvable bool `json:"solvable"`
|
||||
Descriptor *string `json:"desc,omitempty"`
|
||||
IsScript bool `json:"isscript"`
|
||||
IsChange bool `json:"ischange"`
|
||||
|
@ -47,32 +48,13 @@ type embeddedAddressInfo struct {
|
|||
// Reference: https://bitcoincore.org/en/doc/0.20.0/rpc/wallet/getaddressinfo
|
||||
//
|
||||
// The GetAddressInfoResult has three segments:
|
||||
// 1. General information about the address.
|
||||
// 2. Metadata (Timestamp, HDKeyPath, HDSeedID) and wallet fields
|
||||
// (IsMine, IsWatchOnly).
|
||||
// 3. Information about the embedded address in case of P2SH or P2WSH.
|
||||
// Same structure as (1).
|
||||
// 1. General information about the address.
|
||||
// 2. Metadata (Timestamp, HDKeyPath, HDSeedID) and wallet fields
|
||||
// (IsMine, IsWatchOnly).
|
||||
// 3. Information about the embedded address in case of P2SH or P2WSH.
|
||||
// Same structure as (1).
|
||||
type GetAddressInfoResult struct {
|
||||
// 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"`
|
||||
|
||||
embeddedAddressInfo
|
||||
IsMine bool `json:"ismine"`
|
||||
IsWatchOnly bool `json:"iswatchonly"`
|
||||
Timestamp *int `json:"timestamp,omitempty"`
|
||||
|
@ -174,7 +156,6 @@ type GetTransactionResult struct {
|
|||
TimeReceived int64 `json:"timereceived"`
|
||||
Details []GetTransactionDetailsResult `json:"details"`
|
||||
Hex string `json:"hex"`
|
||||
Generated bool `json:"generated"`
|
||||
}
|
||||
|
||||
type ScanningOrFalse struct {
|
||||
|
@ -248,7 +229,6 @@ type InfoWalletResult struct {
|
|||
PaytxFee float64 `json:"paytxfee"`
|
||||
RelayFee float64 `json:"relayfee"`
|
||||
Errors string `json:"errors"`
|
||||
Staked float64 `json:"staked"`
|
||||
}
|
||||
|
||||
// ListTransactionsResult models the data from the listtransactions command.
|
||||
|
@ -289,6 +269,7 @@ type ListReceivedByAccountResult struct {
|
|||
// ListReceivedByAddressResult models the data from the listreceivedbyaddress
|
||||
// command.
|
||||
type ListReceivedByAddressResult struct {
|
||||
Account string `json:"account"`
|
||||
Address string `json:"address"`
|
||||
Amount float64 `json:"amount"`
|
||||
Confirmations uint64 `json:"confirmations"`
|
||||
|
@ -312,15 +293,7 @@ type ListUnspentResult struct {
|
|||
RedeemScript string `json:"redeemScript,omitempty"`
|
||||
Amount float64 `json:"amount"`
|
||||
Confirmations int64 `json:"confirmations"`
|
||||
Solvable bool `json:"solvable"`
|
||||
Spendable bool `json:"spendable"`
|
||||
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
|
||||
|
|
|
@ -37,8 +37,10 @@ func TestGetAddressInfoResult(t *testing.T) {
|
|||
name: "GetAddressInfoResult - ScriptType",
|
||||
result: `{"script":"nonstandard","address":"1abc"}`,
|
||||
want: GetAddressInfoResult{
|
||||
Address: "1abc",
|
||||
ScriptType: nonStandard,
|
||||
embeddedAddressInfo: embeddedAddressInfo{
|
||||
Address: "1abc",
|
||||
ScriptType: nonStandard,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -40,7 +40,7 @@ func NewExportWatchingWalletCmd(account *string, download *bool) *ExportWatching
|
|||
|
||||
// GetUnconfirmedBalanceCmd defines the getunconfirmedbalance JSON-RPC command.
|
||||
type GetUnconfirmedBalanceCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewGetUnconfirmedBalanceCmd returns a new instance which can be used to issue
|
||||
|
@ -58,7 +58,7 @@ func NewGetUnconfirmedBalanceCmd(account *string) *GetUnconfirmedBalanceCmd {
|
|||
// command.
|
||||
type ListAddressTransactionsCmd struct {
|
||||
Addresses []string
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
Account *string
|
||||
}
|
||||
|
||||
// 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.
|
||||
type ListAllTransactionsCmd struct {
|
||||
Account *string `jsonrpcdefault:"\"default\""`
|
||||
Account *string
|
||||
}
|
||||
|
||||
// NewListAllTransactionsCmd returns a new instance which can be used to issue a
|
||||
|
@ -114,8 +114,9 @@ func NewWalletIsLockedCmd() *WalletIsLockedCmd {
|
|||
}
|
||||
|
||||
func init() {
|
||||
// The commands in this file are only usable with a wallet server.
|
||||
flags := UFWalletOnly
|
||||
// The commands in this file are only usable with a wallet server via
|
||||
// websockets.
|
||||
flags := UFWalletOnly | UFWebsocketOnly
|
||||
|
||||
MustRegisterCmd("createencryptedwallet", (*CreateEncryptedWalletCmd)(nil), flags)
|
||||
MustRegisterCmd("exportwatchingwallet", (*ExportWatchingWalletCmd)(nil), flags)
|
||||
|
|
|
@ -71,7 +71,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
|||
{
|
||||
name: "exportwatchingwallet optional2",
|
||||
newCmd: func() (interface{}, error) {
|
||||
return btcjson.NewCmd("exportwatchingwallet", btcjson.String("acct"), true)
|
||||
return btcjson.NewCmd("exportwatchingwallet", "acct", true)
|
||||
},
|
||||
staticCmd: func() interface{} {
|
||||
return btcjson.NewExportWatchingWalletCmd(btcjson.String("acct"),
|
||||
|
@ -93,7 +93,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"getunconfirmedbalance","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.GetUnconfirmedBalanceCmd{
|
||||
Account: btcjson.String("default"),
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -120,7 +120,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
|||
marshalled: `{"jsonrpc":"1.0","method":"listaddresstransactions","params":[["1Address"]],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAddressTransactionsCmd{
|
||||
Addresses: []string{"1Address"},
|
||||
Account: btcjson.String("default"),
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -148,7 +148,7 @@ func TestWalletSvrWsCmds(t *testing.T) {
|
|||
},
|
||||
marshalled: `{"jsonrpc":"1.0","method":"listalltransactions","params":[],"id":1}`,
|
||||
unmarshalled: &btcjson.ListAllTransactionsCmd{
|
||||
Account: btcjson.String("default"),
|
||||
Account: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -39,11 +39,11 @@ func DoubleHashH(b []byte) Hash {
|
|||
|
||||
// LbryPoWHashH calculates returns the PoW Hash.
|
||||
//
|
||||
// doubled := SHA256(SHA256(b))
|
||||
// expanded := SHA512(doubled)
|
||||
// left := RIPEMD160(expanded[0:32])
|
||||
// right := RIPEMD160(expanded[32:64])
|
||||
// result := SHA256(SHA256(left||right))
|
||||
// doubled := SHA256(SHA256(b))
|
||||
// expanded := SHA512(doubled)
|
||||
// left := RIPEMD160(expanded[0:32])
|
||||
// right := RIPEMD160(expanded[32:64])
|
||||
// result := SHA256(SHA256(left||right))
|
||||
func LbryPoWHashH(b []byte) Hash {
|
||||
doubled := DoubleHashB(b)
|
||||
expanded := sha512.Sum512(doubled)
|
||||
|
|
|
@ -18,40 +18,40 @@
|
|||
// When a network parameter is needed, it may then be looked up through this
|
||||
// variable (either directly, or hidden in a library call).
|
||||
//
|
||||
// package main
|
||||
// package main
|
||||
//
|
||||
// import (
|
||||
// "flag"
|
||||
// "fmt"
|
||||
// "log"
|
||||
// import (
|
||||
// "flag"
|
||||
// "fmt"
|
||||
// "log"
|
||||
//
|
||||
// btcutil "github.com/lbryio/lbcutil"
|
||||
// "github.com/lbryio/lbcd/chaincfg"
|
||||
// )
|
||||
// btcutil "github.com/lbryio/lbcutil"
|
||||
// "github.com/lbryio/lbcd/chaincfg"
|
||||
// )
|
||||
//
|
||||
// var testnet = flag.Bool("testnet", false, "operate on the testnet Bitcoin network")
|
||||
// var testnet = flag.Bool("testnet", false, "operate on the testnet Bitcoin network")
|
||||
//
|
||||
// // By default (without -testnet), use mainnet.
|
||||
// var chainParams = &chaincfg.MainNetParams
|
||||
// // By default (without -testnet), use mainnet.
|
||||
// var chainParams = &chaincfg.MainNetParams
|
||||
//
|
||||
// func main() {
|
||||
// flag.Parse()
|
||||
// func main() {
|
||||
// flag.Parse()
|
||||
//
|
||||
// // Modify active network parameters if operating on testnet.
|
||||
// if *testnet {
|
||||
// chainParams = &chaincfg.TestNet3Params
|
||||
// }
|
||||
// // Modify active network parameters if operating on testnet.
|
||||
// if *testnet {
|
||||
// chainParams = &chaincfg.TestNet3Params
|
||||
// }
|
||||
//
|
||||
// // later...
|
||||
// // later...
|
||||
//
|
||||
// // Create and print new payment address, specific to the active network.
|
||||
// pubKeyHash := make([]byte, 20)
|
||||
// addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, chainParams)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// fmt.Println(addr)
|
||||
// }
|
||||
// // Create and print new payment address, specific to the active network.
|
||||
// pubKeyHash := make([]byte, 20)
|
||||
// addr, err := btcutil.NewAddressPubKeyHash(pubKeyHash, chainParams)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// fmt.Println(addr)
|
||||
// }
|
||||
//
|
||||
// If an application does not use one of the three standard Bitcoin networks,
|
||||
// a new Params struct may be created which defines the parameters for the
|
||||
|
|
|
@ -86,6 +86,15 @@ var genesisBlock = wire.MsgBlock{
|
|||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||
}
|
||||
|
||||
// regTestGenesisHash is the hash of the first block in the block chain for the
|
||||
// regression test network (genesis block).
|
||||
var regTestGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||
0x56, 0x75, 0x68, 0x69, 0x76, 0x67, 0x4f, 0x50,
|
||||
0xa0, 0xa1, 0x95, 0x3d, 0x17, 0x2e, 0x9e, 0xcf,
|
||||
0x4a, 0x4a, 0x62, 0x1d, 0xc9, 0xa4, 0xc3, 0x79,
|
||||
0x5d, 0xec, 0xd4, 0x99, 0x12, 0xcf, 0x3f, 0x6e,
|
||||
})
|
||||
|
||||
// regTestGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||
// block for the regression test network. It is the same as the merkle root for
|
||||
// the main network.
|
||||
|
@ -106,9 +115,14 @@ var regTestGenesisBlock = wire.MsgBlock{
|
|||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||
}
|
||||
|
||||
// regTestGenesisHash is the hash of the first block in the block chain for the
|
||||
// regression test network (genesis block).
|
||||
var regTestGenesisHash = regTestGenesisBlock.BlockHash()
|
||||
// testNet3GenesisHash is the hash of the first block in the block chain for the
|
||||
// test network (version 3).
|
||||
var testNet3GenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||
0x43, 0x49, 0x7f, 0xd7, 0xf8, 0x26, 0x95, 0x71,
|
||||
0x08, 0xf4, 0xa3, 0x0f, 0xd9, 0xce, 0xc3, 0xae,
|
||||
0xba, 0x79, 0x97, 0x20, 0x84, 0xe9, 0x0e, 0xad,
|
||||
0x01, 0xea, 0x33, 0x09, 0x00, 0x00, 0x00, 0x00,
|
||||
})
|
||||
|
||||
// testNet3GenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||
// block for the test network (version 3). It is the same as the merkle root
|
||||
|
@ -130,9 +144,14 @@ var testNet3GenesisBlock = wire.MsgBlock{
|
|||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||
}
|
||||
|
||||
// testNet3GenesisHash is the hash of the first block in the block chain for the
|
||||
// test network (version 3).
|
||||
var testNet3GenesisHash = testNet3GenesisBlock.BlockHash()
|
||||
// simNetGenesisHash is the hash of the first block in the block chain for the
|
||||
// simulation test network.
|
||||
var simNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||
0xf6, 0x7a, 0xd7, 0x69, 0x5d, 0x9b, 0x66, 0x2a,
|
||||
0x72, 0xff, 0x3d, 0x8e, 0xdb, 0xbb, 0x2d, 0xe0,
|
||||
0xbf, 0xa6, 0x7b, 0x13, 0x97, 0x4b, 0xb9, 0x91,
|
||||
0x0d, 0x11, 0x6d, 0x5c, 0xbd, 0x86, 0x3e, 0x68,
|
||||
})
|
||||
|
||||
// simNetGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||
// block for the simulation test network. It is the same as the merkle root for
|
||||
|
@ -153,9 +172,14 @@ var simNetGenesisBlock = wire.MsgBlock{
|
|||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||
}
|
||||
|
||||
// simNetGenesisHash is the hash of the first block in the block chain for the
|
||||
// simulation test network.
|
||||
var simNetGenesisHash = simNetGenesisBlock.BlockHash()
|
||||
// sigNetGenesisHash is the hash of the first block in the block chain for the
|
||||
// signet test network.
|
||||
var sigNetGenesisHash = chainhash.Hash{
|
||||
0xf6, 0x1e, 0xee, 0x3b, 0x63, 0xa3, 0x80, 0xa4,
|
||||
0x77, 0xa0, 0x63, 0xaf, 0x32, 0xb2, 0xbb, 0xc9,
|
||||
0x7c, 0x9f, 0xf9, 0xf0, 0x1f, 0x2c, 0x42, 0x25,
|
||||
0xe9, 0x73, 0x98, 0x81, 0x08, 0x00, 0x00, 0x00,
|
||||
}
|
||||
|
||||
// sigNetGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||
// block for the signet test network. It is the same as the merkle root for
|
||||
|
@ -175,7 +199,3 @@ var sigNetGenesisBlock = wire.MsgBlock{
|
|||
},
|
||||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||
}
|
||||
|
||||
// sigNetGenesisHash is the hash of the first block in the block chain for the
|
||||
// signet test network.
|
||||
var sigNetGenesisHash = sigNetGenesisBlock.BlockHash()
|
||||
|
|
|
@ -1,107 +0,0 @@
|
|||
// Copyright (c) 2014-2016 The btcsuite developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package chaincfg
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"testing"
|
||||
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// TestGenesisBlock tests the genesis block of the main network for validity by
|
||||
// checking the encoded bytes and hashes.
|
||||
func TestGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := MainNetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := MainNetParams.GenesisBlock.BlockHash()
|
||||
if !MainNetParams.GenesisHash.IsEqual(&hash) {
|
||||
t.Fatalf("TestGenesisBlock: Genesis block hash does not "+
|
||||
"appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(MainNetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestRegTestGenesisBlock tests the genesis block of the regression test
|
||||
// network for validity by checking the encoded bytes and hashes.
|
||||
func TestRegTestGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := RegressionNetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestRegTestGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := RegressionNetParams.GenesisBlock.BlockHash()
|
||||
if !RegressionNetParams.GenesisHash.IsEqual(&hash) {
|
||||
t.Fatalf("TestRegTestGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(RegressionNetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestTestNet3GenesisBlock tests the genesis block of the test network (version
|
||||
// 3) for validity by checking the encoded bytes and hashes.
|
||||
func TestTestNet3GenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := TestNet3Params.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestTestNet3GenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := TestNet3Params.GenesisBlock.BlockHash()
|
||||
if !TestNet3Params.GenesisHash.IsEqual(&hash) {
|
||||
t.Fatalf("TestTestNet3GenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(TestNet3Params.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSimNetGenesisBlock tests the genesis block of the simulation test network
|
||||
// for validity by checking the encoded bytes and hashes.
|
||||
func TestSimNetGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := SimNetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestSimNetGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := SimNetParams.GenesisBlock.BlockHash()
|
||||
if !SimNetParams.GenesisHash.IsEqual(&hash) {
|
||||
t.Fatalf("TestSimNetGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(SimNetParams.GenesisHash))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSigNetGenesisBlock tests the genesis block of the signet test network for
|
||||
// validity by checking the encoded bytes and hashes.
|
||||
func TestSigNetGenesisBlock(t *testing.T) {
|
||||
// Encode the genesis block to raw bytes.
|
||||
var buf bytes.Buffer
|
||||
err := SigNetParams.GenesisBlock.Serialize(&buf)
|
||||
if err != nil {
|
||||
t.Fatalf("TestSigNetGenesisBlock: %v", err)
|
||||
}
|
||||
|
||||
// Check hash of the block against expected hash.
|
||||
hash := SigNetParams.GenesisBlock.BlockHash()
|
||||
if !SigNetParams.GenesisHash.IsEqual(&hash) {
|
||||
t.Fatalf("TestSigNetGenesisBlock: Genesis block hash does "+
|
||||
"not appear valid - got %v, want %v", spew.Sdump(hash),
|
||||
spew.Sdump(SigNetParams.GenesisHash))
|
||||
}
|
||||
}
|
|
@ -62,7 +62,7 @@ var (
|
|||
DefaultSignetDNSSeeds = []DNSSeed{
|
||||
{"178.128.221.177", false},
|
||||
{"2a01:7c8:d005:390::5", false},
|
||||
{"v7ajjeirttkbnt32wpy3c6w3emwnfr3fkla7hpxcfokr3ysd3kqtzmqd.onion:39246", false},
|
||||
{"v7ajjeirttkbnt32wpy3c6w3emwnfr3fkla7hpxcfokr3ysd3kqtzmqd.onion:38333", false},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -522,7 +522,7 @@ var TestNet3Params = Params{
|
|||
var SimNetParams = Params{
|
||||
Name: "simnet",
|
||||
Net: wire.SimNet,
|
||||
DefaultPort: "39246",
|
||||
DefaultPort: "18555",
|
||||
DNSSeeds: []DNSSeed{}, // NOTE: There must NOT be any seeds.
|
||||
|
||||
// Chain parameters
|
||||
|
@ -615,7 +615,7 @@ func CustomSignetParams(challenge []byte, dnsSeeds []DNSSeed) Params {
|
|||
return Params{
|
||||
Name: "signet",
|
||||
Net: wire.BitcoinNet(net),
|
||||
DefaultPort: "39246",
|
||||
DefaultPort: "38333",
|
||||
DNSSeeds: dnsSeeds,
|
||||
|
||||
// Chain parameters
|
||||
|
@ -794,9 +794,8 @@ func IsBech32SegwitPrefix(prefix string) bool {
|
|||
// ErrInvalidHDKeyID error will be returned.
|
||||
//
|
||||
// Reference:
|
||||
//
|
||||
// SLIP-0132 : Registered HD version bytes for BIP-0032
|
||||
// https://github.com/satoshilabs/slips/blob/master/slip-0132.md
|
||||
// SLIP-0132 : Registered HD version bytes for BIP-0032
|
||||
// https://github.com/satoshilabs/slips/blob/master/slip-0132.md
|
||||
func RegisterHDKeyID(hdPublicKeyID []byte, hdPrivateKeyID []byte) error {
|
||||
if len(hdPublicKeyID) != 4 || len(hdPrivateKeyID) != 4 {
|
||||
return ErrInvalidHDKeyID
|
||||
|
|
|
@ -59,16 +59,6 @@ func (repo *Pebble) Set(height int32, hash *chainhash.Hash) error {
|
|||
return errors.WithStack(repo.db.Set(key, hash[:], pebble.NoSync))
|
||||
}
|
||||
|
||||
func (repo *Pebble) Delete(heightMin, heightMax int32) error {
|
||||
lower := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(lower, uint32(heightMin))
|
||||
|
||||
upper := make([]byte, 4)
|
||||
binary.BigEndian.PutUint32(upper, uint32(heightMax)+1)
|
||||
|
||||
return errors.Wrap(repo.db.DeleteRange(lower, upper, pebble.NoSync), "on range delete")
|
||||
}
|
||||
|
||||
func (repo *Pebble) Close() error {
|
||||
|
||||
err := repo.db.Flush()
|
||||
|
|
|
@ -11,5 +11,4 @@ type Repo interface {
|
|||
Get(height int32) (*chainhash.Hash, error)
|
||||
Close() error
|
||||
Flush() error
|
||||
Delete(heightMin, heightMax int32) error
|
||||
}
|
||||
|
|
|
@ -78,10 +78,9 @@ func (c *Change) Marshal(enc *bytes.Buffer) error {
|
|||
binary.BigEndian.PutUint32(temp[:4], uint32(len(c.SpentChildren)))
|
||||
enc.Write(temp[:4])
|
||||
for key := range c.SpentChildren {
|
||||
keySize := uint16(len(key))
|
||||
binary.BigEndian.PutUint16(temp[:2], keySize) // technically limited to 255; not sure we trust it
|
||||
binary.BigEndian.PutUint16(temp[:2], uint16(len(key))) // technically limited to 255; not sure we trust it
|
||||
enc.Write(temp[:2])
|
||||
enc.WriteString(key[:keySize])
|
||||
enc.WriteString(key)
|
||||
}
|
||||
} else {
|
||||
binary.BigEndian.PutUint32(temp[:4], 0)
|
||||
|
|
|
@ -4,7 +4,9 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
|
@ -47,9 +49,6 @@ type ClaimTrie struct {
|
|||
|
||||
// Registrered cleanup functions which are invoked in the Close() in reverse order.
|
||||
cleanups []func() error
|
||||
|
||||
// claimLogger communicates progress of claimtrie rebuild.
|
||||
claimLogger *claimProgressLogger
|
||||
}
|
||||
|
||||
func New(cfg config.Config) (*ClaimTrie, error) {
|
||||
|
@ -95,7 +94,23 @@ func New(cfg config.Config) (*ClaimTrie, error) {
|
|||
|
||||
var trie merkletrie.MerkleTrie
|
||||
if cfg.RamTrie {
|
||||
trie = merkletrie.NewRamTrie()
|
||||
f := func(prefix merkletrie.KeyType) ([]merkletrie.KeyType, []*chainhash.Hash) {
|
||||
var names []merkletrie.KeyType
|
||||
var hashes []*chainhash.Hash
|
||||
_ = nodeRepo.IterateChildren(prefix, func(changes []change.Change) bool {
|
||||
if len(changes) <= 0 {
|
||||
return true
|
||||
}
|
||||
childName := make(merkletrie.KeyType, len(changes[0].Name))
|
||||
copy(childName, changes[0].Name)
|
||||
names = append(names, childName)
|
||||
hash, _ := nodeManager.Hash(childName)
|
||||
hashes = append(hashes, hash)
|
||||
return true
|
||||
})
|
||||
return names, hashes
|
||||
}
|
||||
trie = merkletrie.NewRamTrie(f)
|
||||
} else {
|
||||
|
||||
// Initialize repository for MerkleTrie. The cleanup is delegated to MerkleTrie.
|
||||
|
@ -134,7 +149,7 @@ func New(cfg config.Config) (*ClaimTrie, error) {
|
|||
ct.Close() // TODO: the cleanups aren't run when we exit with an err above here (but should be)
|
||||
return nil, errors.Wrap(err, "block repo get")
|
||||
}
|
||||
_, err = nodeManager.IncrementHeightTo(previousHeight, false)
|
||||
_, err = nodeManager.IncrementHeightTo(previousHeight)
|
||||
if err != nil {
|
||||
ct.Close()
|
||||
return nil, errors.Wrap(err, "increment height to")
|
||||
|
@ -222,11 +237,11 @@ func (ct *ClaimTrie) SpendSupport(name []byte, op wire.OutPoint, id change.Claim
|
|||
}
|
||||
|
||||
// AppendBlock increases block by one.
|
||||
func (ct *ClaimTrie) AppendBlock(temporary bool) error {
|
||||
func (ct *ClaimTrie) AppendBlock() error {
|
||||
|
||||
ct.height++
|
||||
|
||||
names, err := ct.nodeManager.IncrementHeightTo(ct.height, temporary)
|
||||
names, err := ct.nodeManager.IncrementHeightTo(ct.height)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "node manager increment")
|
||||
}
|
||||
|
@ -247,19 +262,19 @@ func (ct *ClaimTrie) AppendBlock(temporary bool) error {
|
|||
names = append(names, expirations...)
|
||||
names = removeDuplicates(names)
|
||||
|
||||
for _, name := range names {
|
||||
nhns := ct.makeNameHashNext(names, false, nil)
|
||||
for nhn := range nhns {
|
||||
|
||||
hash, next := ct.nodeManager.Hash(name)
|
||||
ct.merkleTrie.Update(name, hash, true)
|
||||
if next <= 0 {
|
||||
ct.merkleTrie.Update(nhn.Name, nhn.Hash, true)
|
||||
if nhn.Next <= 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
newName := normalization.NormalizeIfNecessary(name, next)
|
||||
newName := normalization.NormalizeIfNecessary(nhn.Name, nhn.Next)
|
||||
updateNames = append(updateNames, newName)
|
||||
updateHeights = append(updateHeights, next)
|
||||
updateHeights = append(updateHeights, nhn.Next)
|
||||
}
|
||||
if !temporary && len(updateNames) > 0 {
|
||||
if len(updateNames) != 0 {
|
||||
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "temporal repo set")
|
||||
|
@ -267,11 +282,9 @@ func (ct *ClaimTrie) AppendBlock(temporary bool) error {
|
|||
}
|
||||
|
||||
hitFork := ct.updateTrieForHashForkIfNecessary()
|
||||
h := ct.MerkleHash()
|
||||
|
||||
if !temporary {
|
||||
ct.blockRepo.Set(ct.height, h)
|
||||
}
|
||||
h := ct.MerkleHash()
|
||||
ct.blockRepo.Set(ct.height, h)
|
||||
|
||||
if hitFork {
|
||||
err = ct.merkleTrie.SetRoot(h) // for clearing the memory entirely
|
||||
|
@ -314,7 +327,7 @@ func (ct *ClaimTrie) ResetHeight(height int32) error {
|
|||
}
|
||||
names = append(names, results...)
|
||||
}
|
||||
names, err := ct.nodeManager.DecrementHeightTo(names, height)
|
||||
err := ct.nodeManager.DecrementHeightTo(names, height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -325,58 +338,35 @@ func (ct *ClaimTrie) ResetHeight(height int32) error {
|
|||
return err
|
||||
}
|
||||
|
||||
oldHeight := ct.height
|
||||
ct.height = height // keep this before the rebuild
|
||||
|
||||
if passedHashFork {
|
||||
names = nil // force them to reconsider all names
|
||||
}
|
||||
|
||||
var fullRebuildRequired bool
|
||||
|
||||
err = ct.merkleTrie.SetRoot(hash)
|
||||
if err == merkletrie.ErrFullRebuildRequired {
|
||||
fullRebuildRequired = true
|
||||
} else if err != nil {
|
||||
return errors.Wrapf(err, "setRoot")
|
||||
}
|
||||
|
||||
if fullRebuildRequired {
|
||||
ct.runFullTrieRebuild(names, nil)
|
||||
}
|
||||
|
||||
if !ct.MerkleHash().IsEqual(hash) {
|
||||
return errors.Errorf("unable to restore the hash at height %d"+
|
||||
" (fullTriedRebuilt: %t)", height, fullRebuildRequired)
|
||||
return errors.Errorf("unable to restore the hash at height %d", height)
|
||||
}
|
||||
|
||||
return errors.WithStack(ct.blockRepo.Delete(height+1, oldHeight))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ct *ClaimTrie) runFullTrieRebuild(names [][]byte, interrupt <-chan struct{}) {
|
||||
var nhns chan NameHashNext
|
||||
if names == nil {
|
||||
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
|
||||
})
|
||||
node.LogOnce("Building the entire claim trie in RAM...")
|
||||
|
||||
nhns = ct.makeNameHashNext(nil, true, interrupt)
|
||||
} else {
|
||||
for _, name := range names {
|
||||
hash, _ := ct.nodeManager.Hash(name)
|
||||
ct.merkleTrie.Update(name, hash, false)
|
||||
}
|
||||
nhns = ct.makeNameHashNext(names, false, interrupt)
|
||||
}
|
||||
|
||||
for nhn := range nhns {
|
||||
ct.merkleTrie.Update(nhn.Name, nhn.Hash, false)
|
||||
}
|
||||
}
|
||||
|
||||
// MerkleHash returns the Merkle Hash of the claimTrie.
|
||||
|
@ -442,6 +432,12 @@ func (ct *ClaimTrie) FlushToDisk() {
|
|||
}
|
||||
}
|
||||
|
||||
type NameHashNext struct {
|
||||
Name []byte
|
||||
Hash *chainhash.Hash
|
||||
Next int32
|
||||
}
|
||||
|
||||
func interruptRequested(interrupted <-chan struct{}) bool {
|
||||
select {
|
||||
case <-interrupted: // should never block on nil
|
||||
|
@ -451,3 +447,53 @@ func interruptRequested(interrupted <-chan struct{}) bool {
|
|||
|
||||
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
|
||||
}
|
||||
|
|
|
@ -70,22 +70,6 @@ func TestFixedHashes(t *testing.T) {
|
|||
r.Equal(expected[:], ct.MerkleHash()[:])
|
||||
}
|
||||
|
||||
func TestEmptyHashFork(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
setup(t)
|
||||
param.ActiveParams.AllClaimsInMerkleForkHeight = 2
|
||||
ct, err := New(cfg)
|
||||
r.NoError(err)
|
||||
r.NotNil(ct)
|
||||
defer ct.Close()
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
err := ct.AppendBlock(false)
|
||||
r.NoError(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizationFork(t *testing.T) {
|
||||
r := require.New(t)
|
||||
|
||||
|
@ -255,7 +239,10 @@ func TestRebuild(t *testing.T) {
|
|||
r.NotNil(m)
|
||||
r.NotEqual(*merkletrie.EmptyTrieHash, *m)
|
||||
|
||||
ct.merkleTrie = merkletrie.NewRamTrie()
|
||||
ct.merkleTrie = merkletrie.NewRamTrie(func(name merkletrie.KeyType) ([]merkletrie.KeyType, []*chainhash.Hash) {
|
||||
r.Fail("Unexpected entrance on %s", string(name))
|
||||
return nil, nil
|
||||
})
|
||||
ct.runFullTrieRebuild(nil, nil)
|
||||
|
||||
m2 := ct.MerkleHash()
|
||||
|
@ -378,7 +365,7 @@ func incrementBlock(r *require.Assertions, ct *ClaimTrie, c int32) {
|
|||
r.NoError(err)
|
||||
} else {
|
||||
for ; c > 0; c-- {
|
||||
err := ct.AppendBlock(false)
|
||||
err := ct.AppendBlock()
|
||||
r.NoError(err)
|
||||
}
|
||||
}
|
||||
|
@ -996,7 +983,7 @@ func TestBlock884431(t *testing.T) {
|
|||
o6 := add("testing", 20)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
err = ct.AppendBlock(false)
|
||||
err = ct.AppendBlock()
|
||||
r.NoError(err)
|
||||
}
|
||||
n, err := ct.NodeAt(ct.height, []byte("go"))
|
||||
|
|
|
@ -23,14 +23,12 @@ func NewBlocCommands() *cobra.Command {
|
|||
|
||||
return cmd
|
||||
}
|
||||
func NewBlockBestCommand() *cobra.Command {
|
||||
|
||||
var showHash bool
|
||||
var showHeight bool
|
||||
func NewBlockBestCommand() *cobra.Command {
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "best",
|
||||
Short: "Show the block hash and height of the best block",
|
||||
Short: "Show the height and hash of the best block",
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
db, err := loadBlocksDB()
|
||||
|
@ -45,23 +43,12 @@ func NewBlockBestCommand() *cobra.Command {
|
|||
}
|
||||
|
||||
state := chain.BestSnapshot()
|
||||
|
||||
switch {
|
||||
case showHeight && showHash:
|
||||
fmt.Printf("%s:%d\n", state.Hash, state.Height)
|
||||
case !showHeight && showHash:
|
||||
fmt.Printf("%s\n", state.Hash)
|
||||
case showHeight && !showHash:
|
||||
fmt.Printf("%d\n", state.Height)
|
||||
}
|
||||
fmt.Printf("Block %7d: %s\n", state.Height, state.Hash.String())
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().BoolVar(&showHeight, "showheight", true, "Display block height")
|
||||
cmd.Flags().BoolVar(&showHash, "showhash", true, "Display block hash")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
@ -69,12 +56,10 @@ func NewBlockListCommand() *cobra.Command {
|
|||
|
||||
var fromHeight int32
|
||||
var toHeight int32
|
||||
var showHash bool
|
||||
var showHeight bool
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "list",
|
||||
Short: "List block hash and height between blocks <from_height> <to_height>",
|
||||
Short: "List merkle hash of blocks between <from_height> <to_height>",
|
||||
Args: cobra.NoArgs,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
|
||||
|
@ -98,14 +83,7 @@ func NewBlockListCommand() *cobra.Command {
|
|||
if err != nil {
|
||||
return errors.Wrapf(err, "load hash for %d", ht)
|
||||
}
|
||||
switch {
|
||||
case showHeight && showHash:
|
||||
fmt.Printf("%s:%d\n", hash, ht)
|
||||
case !showHeight && showHash:
|
||||
fmt.Printf("%s\n", hash)
|
||||
case showHeight && !showHash:
|
||||
fmt.Printf("%d\n", ht)
|
||||
}
|
||||
fmt.Printf("Block %7d: %s\n", ht, hash.String())
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -114,8 +92,6 @@ func NewBlockListCommand() *cobra.Command {
|
|||
|
||||
cmd.Flags().Int32Var(&fromHeight, "from", 0, "From height (inclusive)")
|
||||
cmd.Flags().Int32Var(&toHeight, "to", 0, "To height (inclusive)")
|
||||
cmd.Flags().BoolVar(&showHeight, "showheight", true, "Display block height")
|
||||
cmd.Flags().BoolVar(&showHash, "showhash", true, "Display block hash")
|
||||
cmd.Flags().SortFlags = false
|
||||
|
||||
return cmd
|
||||
|
|
|
@ -193,7 +193,7 @@ func NewChainReplayCommand() *cobra.Command {
|
|||
|
||||
func appendBlock(ct *claimtrie.ClaimTrie, chain *blockchain.BlockChain) error {
|
||||
|
||||
err := ct.AppendBlock(false)
|
||||
err := ct.AppendBlock()
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "append block: %w")
|
||||
}
|
||||
|
@ -349,8 +349,8 @@ func (cb *chainConverter) processBlock() {
|
|||
for _, txIn := range tx.MsgTx().TxIn {
|
||||
prevOutpoint := txIn.PreviousOutPoint
|
||||
pkScript := utxoPubScripts[prevOutpoint]
|
||||
cs, err := txscript.ExtractClaimScript(pkScript)
|
||||
if txscript.IsErrorCode(err, txscript.ErrNotClaimScript) {
|
||||
cs, err := txscript.DecodeClaimScript(pkScript)
|
||||
if err == txscript.ErrNotClaimScript {
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
|
@ -359,21 +359,21 @@ func (cb *chainConverter) processBlock() {
|
|||
|
||||
chg := change.Change{
|
||||
Height: block.Height(),
|
||||
Name: cs.Name,
|
||||
Name: cs.Name(),
|
||||
OutPoint: txIn.PreviousOutPoint,
|
||||
}
|
||||
delete(utxoPubScripts, prevOutpoint)
|
||||
|
||||
switch cs.Opcode {
|
||||
switch cs.Opcode() {
|
||||
case txscript.OP_CLAIMNAME:
|
||||
chg.Type = change.SpendClaim
|
||||
chg.ClaimID = change.NewClaimID(chg.OutPoint)
|
||||
case txscript.OP_UPDATECLAIM:
|
||||
chg.Type = change.SpendClaim
|
||||
copy(chg.ClaimID[:], cs.ClaimID)
|
||||
copy(chg.ClaimID[:], cs.ClaimID())
|
||||
case txscript.OP_SUPPORTCLAIM:
|
||||
chg.Type = change.SpendSupport
|
||||
copy(chg.ClaimID[:], cs.ClaimID)
|
||||
copy(chg.ClaimID[:], cs.ClaimID())
|
||||
}
|
||||
|
||||
changes = append(changes, chg)
|
||||
|
@ -381,30 +381,30 @@ func (cb *chainConverter) processBlock() {
|
|||
|
||||
op := *wire.NewOutPoint(tx.Hash(), 0)
|
||||
for i, txOut := range tx.MsgTx().TxOut {
|
||||
cs, err := txscript.ExtractClaimScript(txOut.PkScript)
|
||||
if txscript.IsErrorCode(err, txscript.ErrNotClaimScript) {
|
||||
cs, err := txscript.DecodeClaimScript(txOut.PkScript)
|
||||
if err == txscript.ErrNotClaimScript {
|
||||
continue
|
||||
}
|
||||
|
||||
op.Index = uint32(i)
|
||||
chg := change.Change{
|
||||
Height: block.Height(),
|
||||
Name: cs.Name,
|
||||
Name: cs.Name(),
|
||||
OutPoint: op,
|
||||
Amount: txOut.Value,
|
||||
}
|
||||
utxoPubScripts[op] = txOut.PkScript
|
||||
|
||||
switch cs.Opcode {
|
||||
switch cs.Opcode() {
|
||||
case txscript.OP_CLAIMNAME:
|
||||
chg.Type = change.AddClaim
|
||||
chg.ClaimID = change.NewClaimID(op)
|
||||
case txscript.OP_SUPPORTCLAIM:
|
||||
chg.Type = change.AddSupport
|
||||
copy(chg.ClaimID[:], cs.ClaimID)
|
||||
copy(chg.ClaimID[:], cs.ClaimID())
|
||||
case txscript.OP_UPDATECLAIM:
|
||||
chg.Type = change.UpdateClaim
|
||||
copy(chg.ClaimID[:], cs.ClaimID)
|
||||
copy(chg.ClaimID[:], cs.ClaimID())
|
||||
}
|
||||
changes = append(changes, chg)
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
func loadBlocksDB() (database.DB, error) {
|
||||
|
||||
dbPath := filepath.Join(dataDir, netName, "blocks_ffldb")
|
||||
log.Debugf("Loading blocks database: %s", dbPath)
|
||||
log.Infof("Loading blocks database: %s", dbPath)
|
||||
db, err := database.Open("ffldb", dbPath, chainPramas().Net)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "open blocks database")
|
||||
|
@ -27,7 +27,7 @@ func loadBlocksDB() (database.DB, error) {
|
|||
func loadChain(db database.DB) (*blockchain.BlockChain, error) {
|
||||
paramsCopy := chaincfg.MainNetParams
|
||||
|
||||
log.Debugf("Loading chain from database")
|
||||
log.Infof("Loading chain from database")
|
||||
|
||||
startTime := time.Now()
|
||||
chain, err := blockchain.New(&blockchain.Config{
|
||||
|
@ -40,7 +40,7 @@ func loadChain(db database.DB) (*blockchain.BlockChain, error) {
|
|||
return nil, errors.Wrapf(err, "create blockchain")
|
||||
}
|
||||
|
||||
log.Debugf("Loaded chain from database (%s)", time.Since(startTime))
|
||||
log.Infof("Loaded chain from database (%s)", time.Since(startTime))
|
||||
|
||||
return chain, err
|
||||
|
||||
|
|
|
@ -176,6 +176,8 @@ func NewNodeStatsCommand() *cobra.Command {
|
|||
|
||||
n := 0
|
||||
c := 0
|
||||
withUpdates := 0
|
||||
changeGap := 0
|
||||
err = repo.IterateChildren([]byte{}, func(changes []change.Change) bool {
|
||||
c += len(changes)
|
||||
n++
|
||||
|
@ -183,9 +185,14 @@ func NewNodeStatsCommand() *cobra.Command {
|
|||
fmt.Printf("Name: %s, Hex: %s, Changes: %d\n", string(changes[0].Name),
|
||||
hex.EncodeToString(changes[0].Name), len(changes))
|
||||
}
|
||||
if len(changes) > 3 {
|
||||
withUpdates++
|
||||
changeGap += int(changes[3].Height - changes[0].Height)
|
||||
}
|
||||
return true
|
||||
})
|
||||
fmt.Printf("\nNames: %d, Average changes: %.2f\n", n, float64(c)/float64(n))
|
||||
fmt.Printf("\nNames with updates: %d, Average 1st change gap: %.2f\n", withUpdates, float64(changeGap)/float64(withUpdates))
|
||||
return errors.Wrapf(err, "iterate node repo")
|
||||
},
|
||||
}
|
||||
|
|
|
@ -3,21 +3,20 @@ package cmd
|
|||
import (
|
||||
"os"
|
||||
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/lbryio/lbcd/claimtrie/config"
|
||||
"github.com/lbryio/lbcd/claimtrie/param"
|
||||
"github.com/lbryio/lbcd/limits"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
|
||||
"github.com/btcsuite/btclog"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var (
|
||||
log = btclog.NewBackend(os.Stdout).Logger("CMDL")
|
||||
cfg = config.DefaultConfig
|
||||
netName string
|
||||
dataDir string
|
||||
debugLevel string
|
||||
log btclog.Logger
|
||||
cfg = config.DefaultConfig
|
||||
netName string
|
||||
dataDir string
|
||||
)
|
||||
|
||||
var rootCmd = NewRootCommand()
|
||||
|
@ -29,9 +28,6 @@ func NewRootCommand() *cobra.Command {
|
|||
Short: "ClaimTrie Command Line Interface",
|
||||
SilenceUsage: true,
|
||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||
level, _ := btclog.LevelFromString(debugLevel)
|
||||
log.SetLevel(level)
|
||||
|
||||
switch netName {
|
||||
case "mainnet":
|
||||
param.SetNetwork(wire.MainNet)
|
||||
|
@ -41,20 +37,21 @@ func NewRootCommand() *cobra.Command {
|
|||
param.SetNetwork(wire.TestNet)
|
||||
}
|
||||
},
|
||||
PersistentPostRun: func(cmd *cobra.Command, args []string) {
|
||||
os.Stdout.Sync()
|
||||
},
|
||||
}
|
||||
|
||||
cmd.PersistentFlags().StringVar(&netName, "netname", "mainnet", "Net name")
|
||||
cmd.PersistentFlags().StringVarP(&dataDir, "datadir", "b", cfg.DataDir, "Data dir")
|
||||
cmd.PersistentFlags().StringVarP(&debugLevel, "debuglevel", "d", cfg.DebugLevel, "Debug level")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
|
||||
backendLogger := btclog.NewBackend(os.Stdout)
|
||||
defer os.Stdout.Sync()
|
||||
log = backendLogger.Logger("CMDL")
|
||||
log.SetLevel(btclog.LevelDebug)
|
||||
|
||||
// Up some limits.
|
||||
if err := limits.SetLimits(); err != nil {
|
||||
log.Errorf("failed to set limits: %v\n", err)
|
||||
|
|
|
@ -8,10 +8,11 @@ import (
|
|||
)
|
||||
|
||||
var DefaultConfig = Config{
|
||||
Params: param.MainNet,
|
||||
RamTrie: true, // as it stands the other trie uses more RAM, more time, and 40GB+ of disk space
|
||||
DebugLevel: "info",
|
||||
DataDir: filepath.Join(btcutil.AppDataDir("lbcd", false), "data"),
|
||||
Params: param.MainNet,
|
||||
|
||||
RamTrie: true, // as it stands the other trie uses more RAM, more time, and 40GB+ of disk space
|
||||
|
||||
DataDir: filepath.Join(btcutil.AppDataDir("lbcd", false), "data"),
|
||||
|
||||
BlockRepoPebble: pebbleConfig{
|
||||
Path: "blocks_pebble_db",
|
||||
|
@ -29,10 +30,11 @@ var DefaultConfig = Config{
|
|||
|
||||
// Config is the container of all configurations.
|
||||
type Config struct {
|
||||
Params param.ClaimTrieParams
|
||||
RamTrie bool
|
||||
DataDir string
|
||||
DebugLevel string
|
||||
Params param.ClaimTrieParams
|
||||
|
||||
RamTrie bool
|
||||
|
||||
DataDir string
|
||||
|
||||
BlockRepoPebble pebbleConfig
|
||||
NodeRepoPebble pebbleConfig
|
||||
|
|
|
@ -1,73 +0,0 @@
|
|||
// 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
|
||||
}
|
|
@ -1,16 +1,17 @@
|
|||
package merkletrie
|
||||
|
||||
import (
|
||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||
)
|
||||
|
||||
type KeyType []byte
|
||||
|
||||
type VertexPayload interface {
|
||||
clear()
|
||||
childModified()
|
||||
isEmpty() bool
|
||||
}
|
||||
|
||||
type collapsedVertex struct {
|
||||
children []*collapsedVertex
|
||||
key KeyType
|
||||
merkleHash *chainhash.Hash
|
||||
claimHash *chainhash.Hash
|
||||
children []*collapsedVertex
|
||||
key KeyType
|
||||
payload VertexPayload // TODO: remove all stupid null checks on this once we have generics
|
||||
}
|
||||
|
||||
// insertAt inserts v into s at index i and returns the new slice.
|
||||
|
@ -56,7 +57,7 @@ func sortSearch(nodes []*collapsedVertex, b byte) int {
|
|||
}
|
||||
|
||||
func (ptn *collapsedVertex) findNearest(key KeyType) (int, *collapsedVertex) {
|
||||
// none of the children overlap on the first char or we would have a parent node with that char
|
||||
// none of the children overlap on the first char, or we would have a parent node with that char
|
||||
index := sortSearch(ptn.children, key[0])
|
||||
hits := ptn.children[index:]
|
||||
if len(hits) > 0 {
|
||||
|
@ -66,13 +67,18 @@ func (ptn *collapsedVertex) findNearest(key KeyType) (int, *collapsedVertex) {
|
|||
}
|
||||
|
||||
type collapsedTrie struct {
|
||||
Root *collapsedVertex
|
||||
Nodes int
|
||||
Root *collapsedVertex
|
||||
Nodes int
|
||||
Reload func(prefix KeyType, child *collapsedVertex)
|
||||
}
|
||||
|
||||
func NewCollapsedTrie() *collapsedTrie {
|
||||
// we never delete the Root node
|
||||
return &collapsedTrie{Root: &collapsedVertex{key: make(KeyType, 0)}, Nodes: 1}
|
||||
return &collapsedTrie{
|
||||
Root: &collapsedVertex{key: make(KeyType, 0)},
|
||||
Nodes: 1,
|
||||
Reload: func(_ KeyType, child *collapsedVertex) {},
|
||||
}
|
||||
}
|
||||
|
||||
func (pt *collapsedTrie) NodeCount() int {
|
||||
|
@ -92,11 +98,14 @@ func matchLength(a, b KeyType) int {
|
|||
return minLen
|
||||
}
|
||||
|
||||
func (pt *collapsedTrie) insert(value KeyType, node *collapsedVertex) (bool, *collapsedVertex) {
|
||||
func (pt *collapsedTrie) insert(master, value KeyType, node *collapsedVertex) (bool, *collapsedVertex) {
|
||||
pt.Reload(master[0:len(master)-len(value)], node)
|
||||
index, child := node.findNearest(value)
|
||||
match := 0
|
||||
if index >= 0 { // if we found a child
|
||||
child.merkleHash = nil
|
||||
if child.payload != nil {
|
||||
child.payload.childModified()
|
||||
}
|
||||
match = matchLength(value, child.key)
|
||||
if len(value) == match && len(child.key) == match {
|
||||
return false, child
|
||||
|
@ -107,8 +116,7 @@ func (pt *collapsedTrie) insert(value KeyType, node *collapsedVertex) (bool, *co
|
|||
return true, node.Insert(&collapsedVertex{key: value})
|
||||
}
|
||||
if match < len(child.key) {
|
||||
grandChild := collapsedVertex{key: child.key[match:], children: child.children,
|
||||
claimHash: child.claimHash, merkleHash: child.merkleHash}
|
||||
grandChild := collapsedVertex{key: child.key[match:], children: child.children, payload: child.payload}
|
||||
newChild := collapsedVertex{key: child.key[0:match], children: []*collapsedVertex{&grandChild}}
|
||||
child = &newChild
|
||||
node.children[index] = child
|
||||
|
@ -117,23 +125,26 @@ func (pt *collapsedTrie) insert(value KeyType, node *collapsedVertex) (bool, *co
|
|||
return true, child
|
||||
}
|
||||
}
|
||||
return pt.insert(value[match:], child)
|
||||
return pt.insert(master, value[match:], child)
|
||||
}
|
||||
|
||||
func (pt *collapsedTrie) InsertOrFind(value KeyType) (bool, *collapsedVertex) {
|
||||
pt.Root.merkleHash = nil
|
||||
if pt.Root.payload != nil {
|
||||
pt.Root.payload.childModified()
|
||||
}
|
||||
if len(value) <= 0 {
|
||||
return false, pt.Root
|
||||
}
|
||||
|
||||
// we store the name so we need to make our own copy of it
|
||||
// we store the name, so we need to make our own copy of it.
|
||||
// this avoids errors where this function is called via the DB iterator
|
||||
v2 := make([]byte, len(value))
|
||||
copy(v2, value)
|
||||
return pt.insert(v2, pt.Root)
|
||||
return pt.insert(v2, v2, pt.Root)
|
||||
}
|
||||
|
||||
func find(value KeyType, node *collapsedVertex, pathIndexes *[]int, path *[]*collapsedVertex) *collapsedVertex {
|
||||
func (pt *collapsedTrie) find(master, value KeyType, node *collapsedVertex, pathIndexes *[]int, path *[]*collapsedVertex) *collapsedVertex {
|
||||
pt.Reload(master[0:len(master)-len(value)], node)
|
||||
index, child := node.findNearest(value)
|
||||
if index < 0 {
|
||||
return nil
|
||||
|
@ -157,22 +168,22 @@ func find(value KeyType, node *collapsedVertex, pathIndexes *[]int, path *[]*col
|
|||
if path != nil {
|
||||
*path = append(*path, child)
|
||||
}
|
||||
return find(value[match:], child, pathIndexes, path)
|
||||
return pt.find(master, value[match:], child, pathIndexes, path)
|
||||
}
|
||||
|
||||
func (pt *collapsedTrie) Find(value KeyType) *collapsedVertex {
|
||||
if len(value) <= 0 {
|
||||
return pt.Root
|
||||
}
|
||||
return find(value, pt.Root, nil, nil)
|
||||
return pt.find(value, value, pt.Root, nil, nil)
|
||||
}
|
||||
|
||||
func (pt *collapsedTrie) FindPath(value KeyType) ([]int, []*collapsedVertex) {
|
||||
pathIndexes := []int{-1}
|
||||
path := []*collapsedVertex{pt.Root}
|
||||
if len(value) > 0 {
|
||||
result := find(value, pt.Root, &pathIndexes, &path)
|
||||
if result == nil { // not sure I want this line
|
||||
result := pt.find(value, value, pt.Root, &pathIndexes, &path)
|
||||
if result == nil { // not sure that I want this line
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
@ -182,7 +193,7 @@ func (pt *collapsedTrie) FindPath(value KeyType) ([]int, []*collapsedVertex) {
|
|||
// IterateFrom can be used to find a value and run a function on that value.
|
||||
// If the handler returns true it continues to iterate through the children of value.
|
||||
func (pt *collapsedTrie) IterateFrom(start KeyType, handler func(name KeyType, value *collapsedVertex) bool) {
|
||||
node := find(start, pt.Root, nil, nil)
|
||||
node := pt.find(start, start, pt.Root, nil, nil)
|
||||
if node == nil {
|
||||
return
|
||||
}
|
||||
|
@ -200,27 +211,30 @@ func iterateFrom(name KeyType, node *collapsedVertex, handler func(name KeyType,
|
|||
func (pt *collapsedTrie) Erase(value KeyType) bool {
|
||||
indexes, path := pt.FindPath(value)
|
||||
if path == nil || len(path) <= 1 {
|
||||
if len(path) == 1 {
|
||||
path[0].merkleHash = nil
|
||||
path[0].claimHash = nil
|
||||
if len(path) == 1 && path[0].payload != nil {
|
||||
path[0].payload.clear()
|
||||
}
|
||||
return false
|
||||
}
|
||||
nodes := pt.Nodes
|
||||
i := len(path) - 1
|
||||
path[i].claimHash = nil // this is the thing we are erasing; the rest is book-keeping
|
||||
if path[i].payload != nil {
|
||||
path[i].payload.clear() // this is the thing we are erasing; the rest is book-keeping
|
||||
}
|
||||
for ; i > 0; i-- {
|
||||
childCount := len(path[i].children)
|
||||
noClaimData := path[i].claimHash == nil
|
||||
path[i].merkleHash = nil
|
||||
if childCount == 1 && noClaimData {
|
||||
emptyPayload := path[i].payload == nil || path[i].payload.isEmpty()
|
||||
if path[i].payload != nil {
|
||||
path[i].payload.childModified()
|
||||
}
|
||||
if childCount == 1 && emptyPayload {
|
||||
path[i].key = append(path[i].key, path[i].children[0].key...)
|
||||
path[i].claimHash = path[i].children[0].claimHash
|
||||
path[i].payload = path[i].children[0].payload
|
||||
path[i].children = path[i].children[0].children
|
||||
pt.Nodes--
|
||||
continue
|
||||
}
|
||||
if childCount == 0 && noClaimData {
|
||||
if childCount == 0 && emptyPayload {
|
||||
index := indexes[i]
|
||||
path[i-1].children = append(path[i-1].children[:index], path[i-1].children[index+1:]...)
|
||||
pt.Nodes--
|
||||
|
@ -229,7 +243,9 @@ func (pt *collapsedTrie) Erase(value KeyType) bool {
|
|||
break
|
||||
}
|
||||
for ; i >= 0; i-- {
|
||||
path[i].merkleHash = nil
|
||||
if path[i].payload != nil {
|
||||
path[i].payload.childModified()
|
||||
}
|
||||
}
|
||||
return nodes > pt.Nodes
|
||||
}
|
||||
|
|
|
@ -46,24 +46,43 @@ func TestInsertAndErase(t *testing.T) {
|
|||
assert.Equal(t, 1, trie.NodeCount())
|
||||
}
|
||||
|
||||
type testPayload struct {
|
||||
modifies int
|
||||
full bool
|
||||
}
|
||||
|
||||
func (t *testPayload) clear() {
|
||||
t.full = false
|
||||
}
|
||||
|
||||
func (t *testPayload) childModified() {
|
||||
t.modifies++
|
||||
}
|
||||
|
||||
func (t *testPayload) isEmpty() bool {
|
||||
return !t.full
|
||||
}
|
||||
|
||||
var _ VertexPayload = &testPayload{}
|
||||
|
||||
func TestNilNameHandling(t *testing.T) {
|
||||
trie := NewCollapsedTrie()
|
||||
inserted, n := trie.InsertOrFind([]byte("test"))
|
||||
assert.True(t, inserted)
|
||||
n.claimHash = EmptyTrieHash
|
||||
p := testPayload{}
|
||||
inserted, n = trie.InsertOrFind(nil)
|
||||
assert.False(t, inserted)
|
||||
n.claimHash = EmptyTrieHash
|
||||
n.merkleHash = EmptyTrieHash
|
||||
n.payload = &p
|
||||
p.full = true
|
||||
inserted, n = trie.InsertOrFind(nil)
|
||||
assert.False(t, inserted)
|
||||
assert.NotNil(t, n.claimHash)
|
||||
assert.Nil(t, n.merkleHash)
|
||||
assert.NotNil(t, n.payload)
|
||||
assert.True(t, p.modifies > 0)
|
||||
nodeRemoved := trie.Erase(nil)
|
||||
assert.False(t, nodeRemoved)
|
||||
inserted, n = trie.InsertOrFind(nil)
|
||||
assert.False(t, inserted)
|
||||
assert.Nil(t, n.claimHash)
|
||||
assert.True(t, n.payload.isEmpty())
|
||||
}
|
||||
|
||||
func TestCollapsedTriePerformance(t *testing.T) {
|
||||
|
|
|
@ -23,21 +23,79 @@ type RamTrie struct {
|
|||
bufs *sync.Pool
|
||||
}
|
||||
|
||||
func NewRamTrie() *RamTrie {
|
||||
func NewRamTrie(f func(name KeyType) ([]KeyType, []*chainhash.Hash)) *RamTrie {
|
||||
return &RamTrie{
|
||||
bufs: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return new(bytes.Buffer)
|
||||
},
|
||||
},
|
||||
collapsedTrie: collapsedTrie{Root: &collapsedVertex{merkleHash: EmptyTrieHash}},
|
||||
collapsedTrie: collapsedTrie{
|
||||
Root: &collapsedVertex{},
|
||||
Nodes: 1,
|
||||
Reload: func(prefix KeyType, v *collapsedVertex) {
|
||||
// Reload is always called before the node is used
|
||||
if len(prefix) <= 0 {
|
||||
return
|
||||
}
|
||||
p := getOrMakePayload(v)
|
||||
if p.pruned { // it's been pruned
|
||||
names, hashes := f(prefix)
|
||||
for i, name := range names {
|
||||
cv := &collapsedVertex{
|
||||
children: nil,
|
||||
key: name[len(prefix):],
|
||||
payload: &ramTriePayload{
|
||||
merkleHash: nil,
|
||||
claimHash: hashes[i],
|
||||
hit: 1,
|
||||
},
|
||||
}
|
||||
// assuming that names come out in order:
|
||||
v.children = append(v.children, cv)
|
||||
}
|
||||
p.pruned = false
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type ramTriePayload struct {
|
||||
merkleHash *chainhash.Hash
|
||||
claimHash *chainhash.Hash
|
||||
hit int32
|
||||
pruned bool
|
||||
}
|
||||
|
||||
func (r *ramTriePayload) clear() {
|
||||
r.claimHash = nil
|
||||
r.merkleHash = nil
|
||||
}
|
||||
|
||||
func (r *ramTriePayload) childModified() {
|
||||
r.merkleHash = nil
|
||||
}
|
||||
|
||||
func (r *ramTriePayload) isEmpty() bool {
|
||||
return r.claimHash == nil
|
||||
}
|
||||
|
||||
func getOrMakePayload(v *collapsedVertex) *ramTriePayload {
|
||||
if v.payload == nil {
|
||||
r := &ramTriePayload{}
|
||||
v.payload = r
|
||||
return r
|
||||
}
|
||||
return v.payload.(*ramTriePayload)
|
||||
}
|
||||
|
||||
var _ VertexPayload = &ramTriePayload{}
|
||||
|
||||
var ErrFullRebuildRequired = errors.New("a full rebuild is required")
|
||||
|
||||
func (rt *RamTrie) SetRoot(h *chainhash.Hash) error {
|
||||
if rt.Root.merkleHash.IsEqual(h) {
|
||||
if getOrMakePayload(rt.Root).merkleHash.IsEqual(h) {
|
||||
runtime.GC()
|
||||
return nil
|
||||
}
|
||||
|
@ -51,21 +109,25 @@ func (rt *RamTrie) Update(name []byte, h *chainhash.Hash, _ bool) {
|
|||
rt.Erase(name)
|
||||
} else {
|
||||
_, n := rt.InsertOrFind(name)
|
||||
n.claimHash = h
|
||||
getOrMakePayload(n).claimHash = h
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *RamTrie) MerkleHash() *chainhash.Hash {
|
||||
rootHashCalls++
|
||||
if h := rt.merkleHash(rt.Root); h == nil {
|
||||
return EmptyTrieHash
|
||||
}
|
||||
return rt.Root.merkleHash
|
||||
return getOrMakePayload(rt.Root).merkleHash
|
||||
}
|
||||
|
||||
func (rt *RamTrie) merkleHash(v *collapsedVertex) *chainhash.Hash {
|
||||
if v.merkleHash != nil {
|
||||
return v.merkleHash
|
||||
p := getOrMakePayload(v)
|
||||
if p.merkleHash != nil {
|
||||
runCleanup(v)
|
||||
return p.merkleHash
|
||||
}
|
||||
p.hit++
|
||||
|
||||
b := rt.bufs.Get().(*bytes.Buffer)
|
||||
defer rt.bufs.Put(b)
|
||||
|
@ -77,16 +139,16 @@ func (rt *RamTrie) merkleHash(v *collapsedVertex) *chainhash.Hash {
|
|||
b.Write(rt.completeHash(h, ch.key)) // nolint : errchk
|
||||
}
|
||||
|
||||
if v.claimHash != nil {
|
||||
b.Write(v.claimHash[:])
|
||||
if p.claimHash != nil {
|
||||
b.Write(p.claimHash[:])
|
||||
}
|
||||
|
||||
if b.Len() > 0 {
|
||||
h := chainhash.DoubleHashH(b.Bytes())
|
||||
v.merkleHash = &h
|
||||
p.merkleHash = &h
|
||||
}
|
||||
|
||||
return v.merkleHash
|
||||
return p.merkleHash
|
||||
}
|
||||
|
||||
func (rt *RamTrie) completeHash(h *chainhash.Hash, childKey KeyType) []byte {
|
||||
|
@ -100,16 +162,37 @@ func (rt *RamTrie) completeHash(h *chainhash.Hash, childKey KeyType) []byte {
|
|||
}
|
||||
|
||||
func (rt *RamTrie) MerkleHashAllClaims() *chainhash.Hash {
|
||||
rootHashCalls++
|
||||
if h := rt.merkleHashAllClaims(rt.Root); h == nil {
|
||||
return EmptyTrieHash
|
||||
}
|
||||
return rt.Root.merkleHash
|
||||
return getOrMakePayload(rt.Root).merkleHash
|
||||
}
|
||||
|
||||
var rootHashCalls = 0 // TODO: put this in rt
|
||||
const cleanupMod = 1024
|
||||
const threshold = 42 // have to be used 4% of time to stay in cache
|
||||
|
||||
func runCleanup(v *collapsedVertex) {
|
||||
if (rootHashCalls % cleanupMod) == cleanupMod-1 {
|
||||
for _, c := range v.children {
|
||||
if c.payload != nil && c.payload.(*ramTriePayload).hit > threshold {
|
||||
return
|
||||
}
|
||||
}
|
||||
v.children = nil
|
||||
p := getOrMakePayload(v)
|
||||
p.pruned = true
|
||||
}
|
||||
}
|
||||
|
||||
func (rt *RamTrie) merkleHashAllClaims(v *collapsedVertex) *chainhash.Hash {
|
||||
if v.merkleHash != nil {
|
||||
return v.merkleHash
|
||||
p := getOrMakePayload(v)
|
||||
if p.merkleHash != nil {
|
||||
runCleanup(v)
|
||||
return p.merkleHash
|
||||
}
|
||||
p.hit++
|
||||
|
||||
childHashes := make([]*chainhash.Hash, 0, len(v.children))
|
||||
for _, ch := range v.children {
|
||||
|
@ -118,8 +201,8 @@ func (rt *RamTrie) merkleHashAllClaims(v *collapsedVertex) *chainhash.Hash {
|
|||
}
|
||||
|
||||
claimHash := NoClaimsHash
|
||||
if v.claimHash != nil {
|
||||
claimHash = v.claimHash
|
||||
if p.claimHash != nil {
|
||||
claimHash = p.claimHash
|
||||
} else if len(childHashes) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
@ -130,8 +213,8 @@ func (rt *RamTrie) merkleHashAllClaims(v *collapsedVertex) *chainhash.Hash {
|
|||
childHash = node.ComputeMerkleRoot(childHashes)
|
||||
}
|
||||
|
||||
v.merkleHash = node.HashMerkleBranches(childHash, claimHash)
|
||||
return v.merkleHash
|
||||
p.merkleHash = node.HashMerkleBranches(childHash, claimHash)
|
||||
return p.merkleHash
|
||||
}
|
||||
|
||||
func (rt *RamTrie) Flush() error {
|
||||
|
|
|
@ -17,11 +17,10 @@ func newVertex(hash *chainhash.Hash) *vertex {
|
|||
// TODO: more professional to use msgpack here?
|
||||
|
||||
// nbuf decodes the on-disk format of a node, which has the following form:
|
||||
//
|
||||
// ch(1B) hash(32B)
|
||||
// ...
|
||||
// ch(1B) hash(32B)
|
||||
// vhash(32B)
|
||||
// ch(1B) hash(32B)
|
||||
// ...
|
||||
// ch(1B) hash(32B)
|
||||
// vhash(32B)
|
||||
type nbuf []byte
|
||||
|
||||
func (nb nbuf) entries() int {
|
||||
|
|
|
@ -1,85 +0,0 @@
|
|||
package node
|
||||
|
||||
import (
|
||||
"container/list"
|
||||
|
||||
"github.com/lbryio/lbcd/claimtrie/change"
|
||||
)
|
||||
|
||||
type cacheLeaf struct {
|
||||
node *Node
|
||||
element *list.Element
|
||||
changes []change.Change
|
||||
height int32
|
||||
}
|
||||
|
||||
type Cache struct {
|
||||
nodes map[string]*cacheLeaf
|
||||
order *list.List
|
||||
limit int
|
||||
}
|
||||
|
||||
func (nc *Cache) insert(name []byte, n *Node, height int32) {
|
||||
key := string(name)
|
||||
|
||||
existing := nc.nodes[key]
|
||||
if existing != nil {
|
||||
existing.node = n
|
||||
existing.height = height
|
||||
existing.changes = nil
|
||||
nc.order.MoveToFront(existing.element)
|
||||
return
|
||||
}
|
||||
|
||||
for nc.order.Len() >= nc.limit {
|
||||
// TODO: maybe ensure that we don't remove nodes that have a lot of changes?
|
||||
delete(nc.nodes, nc.order.Back().Value.(string))
|
||||
nc.order.Remove(nc.order.Back())
|
||||
}
|
||||
|
||||
element := nc.order.PushFront(key)
|
||||
nc.nodes[key] = &cacheLeaf{node: n, element: element, height: height}
|
||||
}
|
||||
|
||||
func (nc *Cache) fetch(name []byte, height int32) (*Node, []change.Change, int32) {
|
||||
key := string(name)
|
||||
|
||||
existing := nc.nodes[key]
|
||||
if existing != nil && existing.height <= height {
|
||||
nc.order.MoveToFront(existing.element)
|
||||
return existing.node, existing.changes, existing.height
|
||||
}
|
||||
return nil, nil, -1
|
||||
}
|
||||
|
||||
func (nc *Cache) addChanges(changes []change.Change, height int32) {
|
||||
for _, c := range changes {
|
||||
key := string(c.Name)
|
||||
existing := nc.nodes[key]
|
||||
if existing != nil && existing.height <= height {
|
||||
existing.changes = append(existing.changes, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (nc *Cache) drop(names [][]byte) {
|
||||
for _, name := range names {
|
||||
key := string(name)
|
||||
existing := nc.nodes[key]
|
||||
if existing != nil {
|
||||
// we can't roll it backwards because we don't know its previous height value; just toast it
|
||||
delete(nc.nodes, key)
|
||||
nc.order.Remove(existing.element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (nc *Cache) clear() {
|
||||
nc.nodes = map[string]*cacheLeaf{}
|
||||
nc.order = list.New()
|
||||
// we'll let the GC sort out the remains...
|
||||
}
|
||||
|
||||
func NewCache(limit int) *Cache {
|
||||
return &Cache{limit: limit, nodes: map[string]*cacheLeaf{}, order: list.New()}
|
||||
}
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/lbryio/lbcd/chaincfg/chainhash"
|
||||
"github.com/lbryio/lbcd/claimtrie/change"
|
||||
"github.com/lbryio/lbcd/claimtrie/param"
|
||||
"github.com/lbryio/lbcd/wire"
|
||||
)
|
||||
|
||||
|
@ -57,6 +58,15 @@ func (c *Claim) setStatus(status Status) *Claim {
|
|||
return c
|
||||
}
|
||||
|
||||
func (c *Claim) ExpireAt() int32 {
|
||||
|
||||
if c.AcceptedAt+param.ActiveParams.OriginalClaimExpirationTime > param.ActiveParams.ExtendedClaimExpirationForkHeight {
|
||||
return c.AcceptedAt + param.ActiveParams.ExtendedClaimExpirationTime
|
||||
}
|
||||
|
||||
return c.AcceptedAt + param.ActiveParams.OriginalClaimExpirationTime
|
||||
}
|
||||
|
||||
func OutPointLess(a, b wire.OutPoint) bool {
|
||||
|
||||
switch cmp := bytes.Compare(a.Hash[:], b.Hash[:]); {
|
||||
|
|
|
@ -29,10 +29,6 @@ func UseLogger(logger btclog.Logger) {
|
|||
log = logger
|
||||
}
|
||||
|
||||
func GetLogger() btclog.Logger {
|
||||
return log
|
||||
}
|
||||
|
||||
var loggedStrings = map[string]bool{} // is this gonna get too large?
|
||||
var loggedStringsMutex sync.Mutex
|
||||
|
||||
|
@ -46,10 +42,6 @@ func LogOnce(s string) {
|
|||
log.Info(s)
|
||||
}
|
||||
|
||||
func Log(s string) {
|
||||
log.Info(s)
|
||||
}
|
||||
|
||||
func Warn(s string) {
|
||||
log.Warn(s)
|
||||
}
|
||||
|
|
|
@ -13,15 +13,14 @@ import (
|
|||
|
||||
type Manager interface {
|
||||
AppendChange(chg change.Change)
|
||||
IncrementHeightTo(height int32, temporary bool) ([][]byte, error)
|
||||
DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error)
|
||||
IncrementHeightTo(height int32) ([][]byte, error)
|
||||
DecrementHeightTo(affectedNames [][]byte, height int32) error
|
||||
Height() int32
|
||||
Close() error
|
||||
NodeAt(height int32, name []byte) (*Node, error)
|
||||
IterateNames(predicate func(name []byte) bool)
|
||||
Hash(name []byte) (*chainhash.Hash, int32)
|
||||
Flush() error
|
||||
ClearCache()
|
||||
}
|
||||
|
||||
type BaseManager struct {
|
||||
|
@ -29,64 +28,27 @@ type BaseManager struct {
|
|||
|
||||
height int32
|
||||
changes []change.Change
|
||||
|
||||
tempChanges map[string][]change.Change
|
||||
|
||||
cache *Cache
|
||||
}
|
||||
|
||||
func NewBaseManager(repo Repo) (*BaseManager, error) {
|
||||
|
||||
nm := &BaseManager{
|
||||
repo: repo,
|
||||
cache: NewCache(10000), // TODO: how many should we cache?
|
||||
repo: repo,
|
||||
}
|
||||
|
||||
return nm, nil
|
||||
}
|
||||
|
||||
func (nm *BaseManager) ClearCache() {
|
||||
nm.cache.clear()
|
||||
}
|
||||
|
||||
func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) {
|
||||
|
||||
n, changes, oldHeight := nm.cache.fetch(name, height)
|
||||
if n == nil {
|
||||
changes, err := nm.repo.LoadChanges(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "in load changes")
|
||||
}
|
||||
changes, err := nm.repo.LoadChanges(name)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "in load changes")
|
||||
}
|
||||
|
||||
if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block
|
||||
changes = append(changes, nm.tempChanges[string(name)]...)
|
||||
}
|
||||
|
||||
n, err = nm.newNodeFromChanges(changes, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "in new node")
|
||||
}
|
||||
// TODO: how can we tell what needs to be cached?
|
||||
if nm.tempChanges == nil && height == nm.height && n != nil && (len(changes) > 4 || len(name) < 12) {
|
||||
nm.cache.insert(name, n, height)
|
||||
}
|
||||
} else {
|
||||
if nm.tempChanges != nil { // making an assumption that we only ever have tempChanges for a single block
|
||||
changes = append(changes, nm.tempChanges[string(name)]...)
|
||||
n = n.Clone()
|
||||
} else if height != nm.height {
|
||||
n = n.Clone()
|
||||
}
|
||||
updated, err := nm.updateFromChanges(n, changes, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "in update from changes")
|
||||
}
|
||||
if !updated {
|
||||
n.AdjustTo(oldHeight, height, name)
|
||||
}
|
||||
if nm.tempChanges == nil && height == nm.height {
|
||||
nm.cache.insert(name, n, height)
|
||||
}
|
||||
n, err := nm.newNodeFromChanges(changes, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "in new node")
|
||||
}
|
||||
|
||||
return n, nil
|
||||
|
@ -98,13 +60,17 @@ func (nm *BaseManager) node(name []byte) (*Node, error) {
|
|||
return nm.NodeAt(nm.height, name)
|
||||
}
|
||||
|
||||
func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, height int32) (bool, error) {
|
||||
// newNodeFromChanges returns a new Node constructed from the changes.
|
||||
// The changes must preserve their order received.
|
||||
func (nm *BaseManager) newNodeFromChanges(changes []change.Change, height int32) (*Node, error) {
|
||||
|
||||
count := len(changes)
|
||||
if count == 0 {
|
||||
return false, nil
|
||||
if len(changes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
n := New()
|
||||
previous := changes[0].Height
|
||||
count := len(changes)
|
||||
|
||||
for i, chg := range changes {
|
||||
if chg.Height < previous {
|
||||
|
@ -123,37 +89,15 @@ func (nm *BaseManager) updateFromChanges(n *Node, changes []change.Change, heigh
|
|||
delay := nm.getDelayForName(n, chg)
|
||||
err := n.ApplyChange(chg, delay)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "in apply change")
|
||||
return nil, errors.Wrap(err, "in apply change")
|
||||
}
|
||||
}
|
||||
|
||||
if count <= 0 {
|
||||
// we applied no changes, which means we shouldn't exist if we had all the changes
|
||||
// or might mean nothing significant if we are applying a partial changeset
|
||||
return false, nil
|
||||
}
|
||||
lastChange := changes[count-1]
|
||||
n.AdjustTo(lastChange.Height, height, lastChange.Name)
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// newNodeFromChanges returns a new Node constructed from the changes.
|
||||
// The changes must preserve their order received.
|
||||
func (nm *BaseManager) newNodeFromChanges(changes []change.Change, height int32) (*Node, error) {
|
||||
|
||||
if len(changes) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
n := New()
|
||||
updated, err := nm.updateFromChanges(n, changes, height)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "in update from changes")
|
||||
}
|
||||
if updated {
|
||||
return n, nil
|
||||
}
|
||||
return nil, nil
|
||||
lastChange := changes[count-1]
|
||||
return n.AdjustTo(lastChange.Height, height, lastChange.Name), nil
|
||||
}
|
||||
|
||||
func (nm *BaseManager) AppendChange(chg change.Change) {
|
||||
|
@ -243,7 +187,7 @@ func collectChildNames(changes []change.Change) {
|
|||
// }
|
||||
//}
|
||||
|
||||
func (nm *BaseManager) IncrementHeightTo(height int32, temporary bool) ([][]byte, error) {
|
||||
func (nm *BaseManager) IncrementHeightTo(height int32) ([][]byte, error) {
|
||||
|
||||
if height <= nm.height {
|
||||
panic("invalid height")
|
||||
|
@ -254,26 +198,13 @@ func (nm *BaseManager) IncrementHeightTo(height int32, temporary bool) ([][]byte
|
|||
collectChildNames(nm.changes)
|
||||
}
|
||||
|
||||
if temporary {
|
||||
if nm.tempChanges != nil {
|
||||
return nil, errors.Errorf("expected nil temporary changes")
|
||||
}
|
||||
nm.tempChanges = map[string][]change.Change{}
|
||||
}
|
||||
names := make([][]byte, 0, len(nm.changes))
|
||||
for i := range nm.changes {
|
||||
names = append(names, nm.changes[i].Name)
|
||||
if temporary {
|
||||
name := string(nm.changes[i].Name)
|
||||
nm.tempChanges[name] = append(nm.tempChanges[name], nm.changes[i])
|
||||
}
|
||||
}
|
||||
|
||||
if !temporary {
|
||||
nm.cache.addChanges(nm.changes, height)
|
||||
if err := nm.repo.AppendChanges(nm.changes); err != nil { // destroys names
|
||||
return nil, errors.Wrap(err, "in append changes")
|
||||
}
|
||||
if err := nm.repo.AppendChanges(nm.changes); err != nil { // destroys names
|
||||
return nil, errors.Wrap(err, "in append changes")
|
||||
}
|
||||
|
||||
// Truncate the buffer size to zero.
|
||||
|
@ -287,31 +218,20 @@ func (nm *BaseManager) IncrementHeightTo(height int32, temporary bool) ([][]byte
|
|||
return names, nil
|
||||
}
|
||||
|
||||
func (nm *BaseManager) DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error) {
|
||||
func (nm *BaseManager) DecrementHeightTo(affectedNames [][]byte, height int32) error {
|
||||
if height >= nm.height {
|
||||
return affectedNames, errors.Errorf("invalid height of %d for %d", height, nm.height)
|
||||
return errors.Errorf("invalid height of %d for %d", height, nm.height)
|
||||
}
|
||||
|
||||
if nm.tempChanges != nil {
|
||||
if height != nm.height-1 {
|
||||
return affectedNames, errors.Errorf("invalid temporary rollback at %d to %d", height, nm.height)
|
||||
for _, name := range affectedNames {
|
||||
if err := nm.repo.DropChanges(name, height); err != nil {
|
||||
return errors.Wrap(err, "in drop changes")
|
||||
}
|
||||
for key := range nm.tempChanges {
|
||||
affectedNames = append(affectedNames, []byte(key))
|
||||
}
|
||||
nm.tempChanges = nil
|
||||
} else {
|
||||
for _, name := range affectedNames {
|
||||
if err := nm.repo.DropChanges(name, height); err != nil {
|
||||
return affectedNames, errors.Wrap(err, "in drop changes")
|
||||
}
|
||||
}
|
||||
|
||||
nm.cache.drop(affectedNames)
|
||||
}
|
||||
|
||||
nm.height = height
|
||||
|
||||
return affectedNames, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (nm *BaseManager) getDelayForName(n *Node, chg change.Change) int32 {
|
||||
|
|
|
@ -54,17 +54,17 @@ func TestSimpleAddClaim(t *testing.T) {
|
|||
r.NoError(err)
|
||||
defer m.Close()
|
||||
|
||||
_, err = m.IncrementHeightTo(10, false)
|
||||
_, err = m.IncrementHeightTo(10)
|
||||
r.NoError(err)
|
||||
|
||||
chg := change.NewChange(change.AddClaim).SetName(name1).SetOutPoint(out1).SetHeight(11)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(11, false)
|
||||
_, err = m.IncrementHeightTo(11)
|
||||
r.NoError(err)
|
||||
|
||||
chg = chg.SetName(name2).SetOutPoint(out2).SetHeight(12)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(12, false)
|
||||
_, err = m.IncrementHeightTo(12)
|
||||
r.NoError(err)
|
||||
|
||||
n1, err := m.node(name1)
|
||||
|
@ -77,13 +77,13 @@ func TestSimpleAddClaim(t *testing.T) {
|
|||
r.Equal(1, len(n2.Claims))
|
||||
r.NotNil(n2.Claims.find(byOut(*out2)))
|
||||
|
||||
_, err = m.DecrementHeightTo([][]byte{name2}, 11)
|
||||
err = m.DecrementHeightTo([][]byte{name2}, 11)
|
||||
r.NoError(err)
|
||||
n2, err = m.node(name2)
|
||||
r.NoError(err)
|
||||
r.Nil(n2)
|
||||
|
||||
_, err = m.DecrementHeightTo([][]byte{name1}, 1)
|
||||
err = m.DecrementHeightTo([][]byte{name1}, 1)
|
||||
r.NoError(err)
|
||||
n2, err = m.node(name1)
|
||||
r.NoError(err)
|
||||
|
@ -102,7 +102,7 @@ func TestSupportAmounts(t *testing.T) {
|
|||
r.NoError(err)
|
||||
defer m.Close()
|
||||
|
||||
_, err = m.IncrementHeightTo(10, false)
|
||||
_, err = m.IncrementHeightTo(10)
|
||||
r.NoError(err)
|
||||
|
||||
chg := change.NewChange(change.AddClaim).SetName(name1).SetOutPoint(out1).SetHeight(11).SetAmount(3)
|
||||
|
@ -113,7 +113,7 @@ func TestSupportAmounts(t *testing.T) {
|
|||
chg.ClaimID = change.NewClaimID(*out2)
|
||||
m.AppendChange(chg)
|
||||
|
||||
_, err = m.IncrementHeightTo(11, false)
|
||||
_, err = m.IncrementHeightTo(11)
|
||||
r.NoError(err)
|
||||
|
||||
chg = change.NewChange(change.AddSupport).SetName(name1).SetOutPoint(out3).SetHeight(12).SetAmount(2)
|
||||
|
@ -128,7 +128,7 @@ func TestSupportAmounts(t *testing.T) {
|
|||
chg.ClaimID = change.NewClaimID(*out2)
|
||||
m.AppendChange(chg)
|
||||
|
||||
_, err = m.IncrementHeightTo(20, false)
|
||||
_, err = m.IncrementHeightTo(20)
|
||||
r.NoError(err)
|
||||
|
||||
n1, err := m.node(name1)
|
||||
|
@ -167,11 +167,10 @@ func TestClaimSort(t *testing.T) {
|
|||
param.ActiveParams.ExtendedClaimExpirationTime = 1000
|
||||
|
||||
n := New()
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out2, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{2}, Status: Activated})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 3, Amount: 2, ClaimID: change.ClaimID{3}, Status: Activated})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 4, Amount: 2, ClaimID: change.ClaimID{4}, Status: Activated})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out1, AcceptedAt: 3, Amount: 4, ClaimID: change.ClaimID{1}, Status: Activated})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out1, AcceptedAt: 1, Amount: 9, ClaimID: change.ClaimID{5}, Status: Accepted})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out2, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{2}})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 3, Amount: 2, ClaimID: change.ClaimID{3}})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 4, Amount: 2, ClaimID: change.ClaimID{4}})
|
||||
n.Claims = append(n.Claims, &Claim{OutPoint: *out1, AcceptedAt: 3, Amount: 4, ClaimID: change.ClaimID{1}})
|
||||
n.SortClaimsByBid()
|
||||
|
||||
r.Equal(int64(4), n.Claims[0].Amount)
|
||||
|
@ -194,14 +193,14 @@ func TestHasChildren(t *testing.T) {
|
|||
chg := change.NewChange(change.AddClaim).SetName([]byte("a")).SetOutPoint(out1).SetHeight(1).SetAmount(2)
|
||||
chg.ClaimID = change.NewClaimID(*out1)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(1, false)
|
||||
_, err = m.IncrementHeightTo(1)
|
||||
r.NoError(err)
|
||||
r.False(m.hasChildren([]byte("a"), 1, nil, 1))
|
||||
|
||||
chg = change.NewChange(change.AddClaim).SetName([]byte("ab")).SetOutPoint(out2).SetHeight(2).SetAmount(2)
|
||||
chg.ClaimID = change.NewClaimID(*out2)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(2, false)
|
||||
_, err = m.IncrementHeightTo(2)
|
||||
r.NoError(err)
|
||||
r.False(m.hasChildren([]byte("a"), 2, nil, 2))
|
||||
r.True(m.hasChildren([]byte("a"), 2, nil, 1))
|
||||
|
@ -209,14 +208,14 @@ func TestHasChildren(t *testing.T) {
|
|||
chg = change.NewChange(change.AddClaim).SetName([]byte("abc")).SetOutPoint(out3).SetHeight(3).SetAmount(2)
|
||||
chg.ClaimID = change.NewClaimID(*out3)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(3, false)
|
||||
_, err = m.IncrementHeightTo(3)
|
||||
r.NoError(err)
|
||||
r.False(m.hasChildren([]byte("a"), 3, nil, 2))
|
||||
|
||||
chg = change.NewChange(change.AddClaim).SetName([]byte("ac")).SetOutPoint(out1).SetHeight(4).SetAmount(2)
|
||||
chg.ClaimID = change.NewClaimID(*out4)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(4, false)
|
||||
_, err = m.IncrementHeightTo(4)
|
||||
r.NoError(err)
|
||||
r.True(m.hasChildren([]byte("a"), 4, nil, 2))
|
||||
}
|
||||
|
@ -248,52 +247,3 @@ func TestCollectChildren(t *testing.T) {
|
|||
|
||||
r.Len(c[7].SpentChildren, 0)
|
||||
}
|
||||
|
||||
func TestTemporaryAddClaim(t *testing.T) {
|
||||
|
||||
r := require.New(t)
|
||||
|
||||
param.SetNetwork(wire.TestNet)
|
||||
repo, err := noderepo.NewPebble(t.TempDir())
|
||||
r.NoError(err)
|
||||
|
||||
m, err := NewBaseManager(repo)
|
||||
r.NoError(err)
|
||||
defer m.Close()
|
||||
|
||||
_, err = m.IncrementHeightTo(10, false)
|
||||
r.NoError(err)
|
||||
|
||||
chg := change.NewChange(change.AddClaim).SetName(name1).SetOutPoint(out1).SetHeight(11)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(11, false)
|
||||
r.NoError(err)
|
||||
|
||||
chg = chg.SetName(name2).SetOutPoint(out2).SetHeight(12)
|
||||
m.AppendChange(chg)
|
||||
_, err = m.IncrementHeightTo(12, true)
|
||||
r.NoError(err)
|
||||
|
||||
n1, err := m.node(name1)
|
||||
r.NoError(err)
|
||||
r.Equal(1, len(n1.Claims))
|
||||
r.NotNil(n1.Claims.find(byOut(*out1)))
|
||||
|
||||
n2, err := m.node(name2)
|
||||
r.NoError(err)
|
||||
r.Equal(1, len(n2.Claims))
|
||||
r.NotNil(n2.Claims.find(byOut(*out2)))
|
||||
|
||||
names, err := m.DecrementHeightTo([][]byte{name2}, 11)
|
||||
r.Equal(names[0], name2)
|
||||
r.NoError(err)
|
||||
n2, err = m.node(name2)
|
||||
r.NoError(err)
|
||||
r.Nil(n2)
|
||||
|
||||
_, err = m.DecrementHeightTo([][]byte{name1}, 1)
|
||||
r.NoError(err)
|
||||
n2, err = m.node(name1)
|
||||
r.NoError(err)
|
||||
r.Nil(n2)
|
||||
}
|
||||
|
|
|
@ -110,7 +110,7 @@ func (n *Node) ApplyChange(chg change.Change, delay int32) error {
|
|||
}
|
||||
|
||||
// AdjustTo activates claims and computes takeovers until it reaches the specified height.
|
||||
func (n *Node) AdjustTo(height, maxHeight int32, name []byte) {
|
||||
func (n *Node) AdjustTo(height, maxHeight int32, name []byte) *Node {
|
||||
changed := n.handleExpiredAndActivated(height) > 0
|
||||
n.updateTakeoverHeight(height, name, changed)
|
||||
if maxHeight > height {
|
||||
|
@ -120,6 +120,7 @@ func (n *Node) AdjustTo(height, maxHeight int32, name []byte) {
|
|||
height = h
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool) {
|
||||
|
@ -155,16 +156,6 @@ func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool)
|
|||
|
||||
func (n *Node) handleExpiredAndActivated(height int32) int {
|
||||
|
||||
ot := param.ActiveParams.OriginalClaimExpirationTime
|
||||
et := param.ActiveParams.ExtendedClaimExpirationTime
|
||||
fk := param.ActiveParams.ExtendedClaimExpirationForkHeight
|
||||
expiresAt := func(c *Claim) int32 {
|
||||
if c.AcceptedAt+ot > fk {
|
||||
return c.AcceptedAt + et
|
||||
}
|
||||
return c.AcceptedAt + ot
|
||||
}
|
||||
|
||||
changes := 0
|
||||
update := func(items ClaimList, sums map[string]int64) ClaimList {
|
||||
for i := 0; i < len(items); i++ {
|
||||
|
@ -176,7 +167,7 @@ func (n *Node) handleExpiredAndActivated(height int32) int {
|
|||
sums[c.ClaimID.Key()] += c.Amount
|
||||
}
|
||||
}
|
||||
if c.Status == Deactivated || expiresAt(c) <= height {
|
||||
if c.ExpireAt() <= height || c.Status == Deactivated {
|
||||
if i < len(items)-1 {
|
||||
items[i] = items[len(items)-1]
|
||||
i--
|
||||
|
@ -199,22 +190,11 @@ func (n *Node) handleExpiredAndActivated(height int32) int {
|
|||
// be refreshed due to changes of claims or supports.
|
||||
func (n Node) NextUpdate() int32 {
|
||||
|
||||
ot := param.ActiveParams.OriginalClaimExpirationTime
|
||||
et := param.ActiveParams.ExtendedClaimExpirationTime
|
||||
fk := param.ActiveParams.ExtendedClaimExpirationForkHeight
|
||||
expiresAt := func(c *Claim) int32 {
|
||||
if c.AcceptedAt+ot > fk {
|
||||
return c.AcceptedAt + et
|
||||
}
|
||||
return c.AcceptedAt + ot
|
||||
}
|
||||
|
||||
next := int32(math.MaxInt32)
|
||||
|
||||
for _, c := range n.Claims {
|
||||
ea := expiresAt(c)
|
||||
if ea < next {
|
||||
next = ea
|
||||
if c.ExpireAt() < next {
|
||||
next = c.ExpireAt()
|
||||
}
|
||||
// if we're not active, we need to go to activeAt unless we're still invisible there
|
||||
if c.Status == Accepted {
|
||||
|
@ -229,9 +209,8 @@ func (n Node) NextUpdate() int32 {
|
|||
}
|
||||
|
||||
for _, s := range n.Supports {
|
||||
es := expiresAt(s)
|
||||
if es < next {
|
||||
next = es
|
||||
if s.ExpireAt() < next {
|
||||
next = s.ExpireAt()
|
||||
}
|
||||
if s.Status == Accepted {
|
||||
min := s.ActiveAt
|
||||
|
@ -315,17 +294,10 @@ func (n *Node) activateAllClaims(height int32) int {
|
|||
|
||||
func (n *Node) SortClaimsByBid() {
|
||||
|
||||
// purposefully sorting by descent via func parameter order:
|
||||
// purposefully sorting by descent
|
||||
sort.Slice(n.Claims, func(j, i int) bool {
|
||||
// SupportSums only include active values; do the same for amount. No active claim will have a zero amount
|
||||
iAmount := n.SupportSums[n.Claims[i].ClaimID.Key()]
|
||||
if n.Claims[i].Status == Activated {
|
||||
iAmount += n.Claims[i].Amount
|
||||
}
|
||||
jAmount := n.SupportSums[n.Claims[j].ClaimID.Key()]
|
||||
if n.Claims[j].Status == Activated {
|
||||
jAmount += n.Claims[j].Amount
|
||||
}
|
||||
iAmount := n.Claims[i].Amount + n.SupportSums[n.Claims[i].ClaimID.Key()]
|
||||
jAmount := n.Claims[j].Amount + n.SupportSums[n.Claims[j].ClaimID.Key()]
|
||||
switch {
|
||||
case iAmount < jAmount:
|
||||
return true
|
||||
|
@ -339,28 +311,3 @@ func (n *Node) SortClaimsByBid() {
|
|||
return OutPointLess(n.Claims[j].OutPoint, n.Claims[i].OutPoint)
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Node) Clone() *Node {
|
||||
clone := New()
|
||||
if n.SupportSums != nil {
|
||||
clone.SupportSums = map[string]int64{}
|
||||
for key, value := range n.SupportSums {
|
||||
clone.SupportSums[key] = value
|
||||
}
|
||||
}
|
||||
clone.Supports = make(ClaimList, len(n.Supports))
|
||||
for i, support := range n.Supports {
|
||||
clone.Supports[i] = &Claim{}
|
||||
*clone.Supports[i] = *support
|
||||
}
|
||||
clone.Claims = make(ClaimList, len(n.Claims))
|
||||
for i, claim := range n.Claims {
|
||||
clone.Claims[i] = &Claim{}
|
||||
*clone.Claims[i] = *claim
|
||||
}
|
||||
clone.TakenOverAt = n.TakenOverAt
|
||||
if n.BestClaim != nil {
|
||||
clone.BestClaim = clone.Claims.find(byID(n.BestClaim.ClaimID))
|
||||
}
|
||||
return clone
|
||||
}
|
||||
|
|
|
@ -2,9 +2,7 @@ package noderepo
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/cockroachdb/pebble"
|
||||
"github.com/lbryio/lbcd/claimtrie/change"
|
||||
|
@ -15,81 +13,15 @@ type Pebble struct {
|
|||
db *pebble.DB
|
||||
}
|
||||
|
||||
type pooledMerger struct {
|
||||
values [][]byte
|
||||
index []int
|
||||
pool *sync.Pool
|
||||
buffer []byte
|
||||
}
|
||||
|
||||
func (a *pooledMerger) Len() int { return len(a.index) }
|
||||
func (a *pooledMerger) Less(i, j int) bool { return a.index[i] < a.index[j] }
|
||||
func (a *pooledMerger) Swap(i, j int) {
|
||||
a.index[i], a.index[j] = a.index[j], a.index[i]
|
||||
a.values[i], a.values[j] = a.values[j], a.values[i]
|
||||
}
|
||||
|
||||
func (a *pooledMerger) MergeNewer(value []byte) error {
|
||||
vc := a.pool.Get().([]byte)[:0]
|
||||
vc = append(vc, value...)
|
||||
a.values = append(a.values, vc)
|
||||
a.index = append(a.index, len(a.values))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *pooledMerger) MergeOlder(value []byte) error {
|
||||
vc := a.pool.Get().([]byte)[:0]
|
||||
vc = append(vc, value...)
|
||||
a.values = append(a.values, vc)
|
||||
a.index = append(a.index, -len(a.values))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *pooledMerger) Finish(includesBase bool) ([]byte, io.Closer, error) {
|
||||
sort.Sort(a)
|
||||
|
||||
a.buffer = a.pool.Get().([]byte)[:0]
|
||||
for i := range a.values {
|
||||
a.buffer = append(a.buffer, a.values[i]...)
|
||||
}
|
||||
|
||||
return a.buffer, a, nil
|
||||
}
|
||||
|
||||
func (a *pooledMerger) Close() error {
|
||||
for i := range a.values {
|
||||
a.pool.Put(a.values[i])
|
||||
}
|
||||
a.pool.Put(a.buffer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewPebble(path string) (*Pebble, error) {
|
||||
|
||||
mp := &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return make([]byte, 0, 256)
|
||||
},
|
||||
}
|
||||
|
||||
db, err := pebble.Open(path, &pebble.Options{
|
||||
Merger: &pebble.Merger{
|
||||
Merge: func(key, value []byte) (pebble.ValueMerger, error) {
|
||||
p := &pooledMerger{pool: mp}
|
||||
return p, p.MergeNewer(value)
|
||||
},
|
||||
Name: pebble.DefaultMerger.Name, // yes, it's a lie
|
||||
},
|
||||
Cache: pebble.NewCache(64 << 20),
|
||||
BytesPerSync: 8 << 20,
|
||||
MaxOpenFiles: 2000,
|
||||
})
|
||||
|
||||
db, err := pebble.Open(path, &pebble.Options{Cache: pebble.NewCache(64 << 20), BytesPerSync: 8 << 20, MaxOpenFiles: 2000})
|
||||
repo := &Pebble{db: db}
|
||||
|
||||
return repo, errors.Wrapf(err, "unable to open %s", path)
|
||||
}
|
||||
|
||||
// AppendChanges makes an assumption that anything you pass to it is newer than what was saved before.
|
||||
func (repo *Pebble) AppendChanges(changes []change.Change) error {
|
||||
|
||||
batch := repo.db.NewBatch()
|
||||
|
@ -130,7 +62,6 @@ func unmarshalChanges(name, data []byte) ([]change.Change, error) {
|
|||
changes := make([]change.Change, 0, len(data)/84+1) // average is 5.1 changes
|
||||
|
||||
buffer := bytes.NewBuffer(data)
|
||||
sortNeeded := false
|
||||
for buffer.Len() > 0 {
|
||||
var chg change.Change
|
||||
err := chg.Unmarshal(buffer)
|
||||
|
@ -138,18 +69,14 @@ func unmarshalChanges(name, data []byte) ([]change.Change, error) {
|
|||
return nil, errors.Wrap(err, "in decode")
|
||||
}
|
||||
chg.Name = name
|
||||
if len(changes) > 0 && chg.Height < changes[len(changes)-1].Height {
|
||||
sortNeeded = true // alternately: sortNeeded || chg.Height != chg.VisibleHeight
|
||||
}
|
||||
changes = append(changes, chg)
|
||||
}
|
||||
|
||||
if sortNeeded {
|
||||
// this was required for the normalization stuff:
|
||||
sort.SliceStable(changes, func(i, j int) bool {
|
||||
return changes[i].Height < changes[j].Height
|
||||
})
|
||||
}
|
||||
// this was required for the normalization stuff:
|
||||
sort.SliceStable(changes, func(i, j int) bool {
|
||||
return changes[i].Height < changes[j].Height
|
||||
})
|
||||
|
||||
return changes, nil
|
||||
}
|
||||
|
||||
|
@ -158,24 +85,22 @@ func (repo *Pebble) DropChanges(name []byte, finalHeight int32) error {
|
|||
if err != nil {
|
||||
return errors.Wrapf(err, "in load changes for %s", name)
|
||||
}
|
||||
buffer := bytes.NewBuffer(nil)
|
||||
for i := 0; i < len(changes); i++ { // assuming changes are ordered by height
|
||||
i := 0
|
||||
for ; i < len(changes); i++ { // assuming changes are ordered by height
|
||||
if changes[i].Height > finalHeight {
|
||||
break
|
||||
}
|
||||
if changes[i].VisibleHeight > finalHeight { // created after this height has to be skipped
|
||||
continue
|
||||
}
|
||||
// having to sort the changes really messes up performance here. It would be better to not remarshal
|
||||
err := changes[i].Marshal(buffer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "in marshaller")
|
||||
if changes[i].VisibleHeight > finalHeight { // created after this height has to be deleted
|
||||
changes = append(changes[:i], changes[i+1:]...)
|
||||
i--
|
||||
}
|
||||
}
|
||||
|
||||
// making a performance assumption that DropChanges won't happen often:
|
||||
err = repo.db.Set(name, buffer.Bytes(), pebble.NoSync)
|
||||
return errors.Wrapf(err, "in set at %s", name)
|
||||
err = repo.db.Set(name, []byte{}, pebble.NoSync)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "in set at %s", name)
|
||||
}
|
||||
return repo.AppendChanges(changes[:i])
|
||||
}
|
||||
|
||||
func (repo *Pebble) IterateChildren(name []byte, f func(changes []change.Change) bool) error {
|
||||
|
|
|
@ -26,15 +26,14 @@ func (nm *NormalizingManager) AppendChange(chg change.Change) {
|
|||
nm.Manager.AppendChange(chg)
|
||||
}
|
||||
|
||||
func (nm *NormalizingManager) IncrementHeightTo(height int32, temporary bool) ([][]byte, error) {
|
||||
func (nm *NormalizingManager) IncrementHeightTo(height int32) ([][]byte, error) {
|
||||
nm.addNormalizationForkChangesIfNecessary(height)
|
||||
return nm.Manager.IncrementHeightTo(height, temporary)
|
||||
return nm.Manager.IncrementHeightTo(height)
|
||||
}
|
||||
|
||||
func (nm *NormalizingManager) DecrementHeightTo(affectedNames [][]byte, height int32) ([][]byte, error) {
|
||||
func (nm *NormalizingManager) DecrementHeightTo(affectedNames [][]byte, height int32) error {
|
||||
if nm.normalizedAt > height {
|
||||
nm.normalizedAt = -1
|
||||
nm.ClearCache()
|
||||
}
|
||||
return nm.Manager.DecrementHeightTo(affectedNames, height)
|
||||
}
|
||||
|
@ -111,7 +110,5 @@ func (nm *NormalizingManager) addNormalizationForkChangesIfNecessary(height int3
|
|||
|
||||
return true
|
||||
}
|
||||
|
||||
nm.Manager.ClearCache()
|
||||
nm.Manager.IterateNames(predicate)
|
||||
}
|
||||
|
|
1584
claimtrie/normalization/CaseFolding_v13.txt
Normal file
1584
claimtrie/normalization/CaseFolding_v13.txt
Normal file
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue