Compare commits
80 commits
master
...
mem_pressu
Author | SHA1 | Date | |
---|---|---|---|
|
cb7aad11c6 | ||
|
531428d306 | ||
|
ccfa7af546 | ||
|
9aa4259382 | ||
|
ade593da33 | ||
|
132b93713d | ||
|
3c0e288e0f | ||
|
1a3f34c345 | ||
|
85a7f74f83 | ||
|
e8b2910b36 | ||
|
88dbf2267c | ||
|
0377a3e7ac | ||
|
26a4ffe2e3 | ||
|
2ead2539c0 | ||
|
424235655c | ||
|
c5b4662aa8 | ||
|
353d08bb91 | ||
|
15ded4ed0f | ||
|
a553f2e9c8 | ||
|
d4073bd18d | ||
|
cb7175bd70 | ||
|
ade6adb7dc | ||
|
b23710bc33 | ||
|
82d4b6657b | ||
|
0a01170422 | ||
|
1a65a6a19d | ||
|
ab852a6e9f | ||
|
d691ab7a9e | ||
|
9a177f3a9a | ||
|
d013bb7e72 | ||
|
9615516b51 | ||
|
ceb72948ec | ||
|
7dff7f9dd8 | ||
|
ac6a7ad121 | ||
|
26e4083f38 | ||
|
9937f66b6a | ||
|
b6cf5f2665 | ||
|
d74924992a | ||
|
a1631880be | ||
|
d46bedf5ef | ||
|
0518180508 | ||
|
a0469820a2 | ||
|
f829fb6206 | ||
|
d7f97ab750 | ||
|
2e75ce6583 | ||
|
6b55968ccd | ||
|
ceba136a70 | ||
|
9e5a717c39 | ||
|
f218c04488 | ||
|
328705f579 | ||
|
3c85e6e56a | ||
|
27c81de4e5 | ||
|
ccaa6dd816 | ||
|
2dcdb458e8 | ||
|
56c21c6bd6 | ||
|
87c3243bf1 | ||
|
4e68d1fb81 | ||
|
b0f1458ff7 | ||
|
e0b451a76a | ||
|
57b3f96f3f | ||
|
c4a5ae339c | ||
|
ced137f9e2 | ||
|
fc77c6db6a | ||
|
9b1c4fbc04 | ||
|
818ad52cdf | ||
|
35eaa76e42 | ||
|
42793ad871 | ||
|
f6450deacb | ||
|
3b9d3ab05f | ||
|
c84ced2f10 | ||
|
34bdf58303 | ||
|
3cf16aad88 | ||
|
be9fc27e6f | ||
|
e62432dc95 | ||
|
0636c889f5 | ||
|
4f422e29cf | ||
|
363cc18b31 | ||
|
860529321f | ||
|
43566e6f2b | ||
|
7ae6608f48 |
173 changed files with 10113 additions and 24690 deletions
|
@ -3,10 +3,10 @@ on: [push, pull_request]
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
name: Go CI
|
name: Go CI
|
||||||
runs-on: ubuntu-latest
|
runs-on: self-hosted
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
go: [1.14, 1.15]
|
go: [1.16]
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v2
|
uses: actions/setup-go@v2
|
52
.github/workflows/create-release.yml
vendored
Normal file
52
.github/workflows/create-release.yml
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
name: Create release
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
note:
|
||||||
|
description: 'Note'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: [1.16]
|
||||||
|
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Install coreutils, ICU for macOS
|
||||||
|
if: matrix.os == 'macos-latest'
|
||||||
|
run: brew install coreutils icu4c
|
||||||
|
- name: Build executables
|
||||||
|
env:
|
||||||
|
GO111MODULE: "on"
|
||||||
|
run: go build -trimpath -o artifacts/ --tags use_icu_normalization .
|
||||||
|
- name: SHA256 sum
|
||||||
|
run: sha256sum -b artifacts/* > artifacts/chain.sha256
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v2
|
||||||
|
with:
|
||||||
|
name: chain-${{ matrix.os }}
|
||||||
|
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
|
34
.github/workflows/full-sync-part-1.yml
vendored
Normal file
34
.github/workflows/full-sync-part-1.yml
vendored
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
name: Full Sync From 0
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
note:
|
||||||
|
description: 'Note'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Go CI
|
||||||
|
runs-on: self-hosted
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: [1.16]
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "Note ${{ github.event.inputs.note }}!"
|
||||||
|
- name: Setup Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Build chain
|
||||||
|
run: go build --tags use_icu_normalization .
|
||||||
|
- name: Create datadir
|
||||||
|
run: echo "TEMP_DATA_DIR=$(mktemp -d)" >> $GITHUB_ENV
|
||||||
|
- name: Run chain
|
||||||
|
run: ./chain --datadir=${{env.TEMP_DATA_DIR}}/data --logdir=${{env.TEMP_DATA_DIR}}/logs --connect=127.0.0.1 --norpc
|
||||||
|
- name: Remove datadir
|
||||||
|
run: rm -rf ${{env.TEMP_DATA_DIR}}
|
36
.github/workflows/full-sync-part-2.yml
vendored
Normal file
36
.github/workflows/full-sync-part-2.yml
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
name: Full Sync From 814k
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
note:
|
||||||
|
description: 'Note'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
name: Go CI
|
||||||
|
runs-on: self-hosted
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go: [1.16]
|
||||||
|
steps:
|
||||||
|
- run: |
|
||||||
|
echo "Note ${{ github.event.inputs.note }}!"
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go }}
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Build chain
|
||||||
|
run: go build --tags use_icu_normalization .
|
||||||
|
- name: Create datadir
|
||||||
|
run: echo "TEMP_DATA_DIR=$(mktemp -d)" >> $GITHUB_ENV
|
||||||
|
- name: Copy initial data
|
||||||
|
run: cp -r /home/lbry/chain_814k/* ${{env.TEMP_DATA_DIR}}
|
||||||
|
- name: Run chain
|
||||||
|
run: ./chain --datadir=${{env.TEMP_DATA_DIR}}/data --logdir=${{env.TEMP_DATA_DIR}}/logs --connect=127.0.0.1 --norpc
|
||||||
|
- name: Remove datadir
|
||||||
|
run: rm -rf ${{env.TEMP_DATA_DIR}}
|
12
.gitignore
vendored
12
.gitignore
vendored
|
@ -33,6 +33,18 @@ _testmain.go
|
||||||
|
|
||||||
*.exe
|
*.exe
|
||||||
|
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
# Code coverage files
|
# Code coverage files
|
||||||
profile.tmp
|
profile.tmp
|
||||||
profile.cov
|
profile.cov
|
||||||
|
|
||||||
|
# IDE
|
||||||
|
.idea
|
||||||
|
.vscode
|
||||||
|
|
||||||
|
# Binaries
|
||||||
|
btcd
|
||||||
|
btcctl
|
||||||
|
chain
|
||||||
|
chainctl
|
||||||
|
|
|
@ -38,4 +38,4 @@ VOLUME ["/root/.btcd"]
|
||||||
|
|
||||||
EXPOSE 8333 8334
|
EXPOSE 8333 8334
|
||||||
|
|
||||||
ENTRYPOINT ["btcd"]
|
ENTRYPOINT ["chain"]
|
||||||
|
|
|
@ -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/btcsuite/btcd/addrmgr"
|
|
||||||
"github.com/btcsuite/btcd/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.")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +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"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BenchmarkIsCoinBase performs a simple benchmark against the IsCoinBase
|
|
||||||
// function.
|
|
||||||
func BenchmarkIsCoinBase(b *testing.B) {
|
|
||||||
tx, _ := btcutil.NewBlock(&Block100000).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 := Block100000.Transactions[1]
|
|
||||||
b.ResetTimer()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
IsCoinBaseTx(tx)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -93,6 +93,7 @@ type blockNode struct {
|
||||||
nonce uint32
|
nonce uint32
|
||||||
timestamp int64
|
timestamp int64
|
||||||
merkleRoot chainhash.Hash
|
merkleRoot chainhash.Hash
|
||||||
|
claimTrie chainhash.Hash
|
||||||
|
|
||||||
// status is a bitfield representing the validation state of the block. The
|
// status is a bitfield representing the validation state of the block. The
|
||||||
// status field, unlike the other fields, may be written to and so should
|
// status field, unlike the other fields, may be written to and so should
|
||||||
|
@ -114,6 +115,7 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parent *block
|
||||||
nonce: blockHeader.Nonce,
|
nonce: blockHeader.Nonce,
|
||||||
timestamp: blockHeader.Timestamp.Unix(),
|
timestamp: blockHeader.Timestamp.Unix(),
|
||||||
merkleRoot: blockHeader.MerkleRoot,
|
merkleRoot: blockHeader.MerkleRoot,
|
||||||
|
claimTrie: blockHeader.ClaimTrie,
|
||||||
}
|
}
|
||||||
if parent != nil {
|
if parent != nil {
|
||||||
node.parent = parent
|
node.parent = parent
|
||||||
|
@ -144,6 +146,7 @@ func (node *blockNode) Header() wire.BlockHeader {
|
||||||
Version: node.version,
|
Version: node.version,
|
||||||
PrevBlock: *prevHash,
|
PrevBlock: *prevHash,
|
||||||
MerkleRoot: node.merkleRoot,
|
MerkleRoot: node.merkleRoot,
|
||||||
|
ClaimTrie: node.claimTrie,
|
||||||
Timestamp: time.Unix(node.timestamp, 0),
|
Timestamp: time.Unix(node.timestamp, 0),
|
||||||
Bits: node.bits,
|
Bits: node.bits,
|
||||||
Nonce: node.nonce,
|
Nonce: node.nonce,
|
||||||
|
|
|
@ -17,6 +17,8 @@ import (
|
||||||
"github.com/btcsuite/btcd/txscript"
|
"github.com/btcsuite/btcd/txscript"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -180,6 +182,8 @@ type BlockChain struct {
|
||||||
// certain blockchain events.
|
// certain blockchain events.
|
||||||
notificationsLock sync.RWMutex
|
notificationsLock sync.RWMutex
|
||||||
notifications []NotificationCallback
|
notifications []NotificationCallback
|
||||||
|
|
||||||
|
claimTrie *claimtrie.ClaimTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
// HaveBlock returns whether or not the chain instance has the block represented
|
// HaveBlock returns whether or not the chain instance has the block represented
|
||||||
|
@ -571,7 +575,8 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
|
||||||
}
|
}
|
||||||
|
|
||||||
// No warnings about unknown rules until the chain is current.
|
// No warnings about unknown rules until the chain is current.
|
||||||
if b.isCurrent() {
|
current := b.isCurrent()
|
||||||
|
if current {
|
||||||
// Warn if any unknown new rules are either about to activate or
|
// Warn if any unknown new rules are either about to activate or
|
||||||
// have already been activated.
|
// have already been activated.
|
||||||
if err := b.warnUnknownRuleActivations(node); err != nil {
|
if err := b.warnUnknownRuleActivations(node); err != nil {
|
||||||
|
@ -579,6 +584,13 @@ func (b *BlockChain) connectBlock(node *blockNode, block *btcutil.Block,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle LBRY Claim Scripts
|
||||||
|
if b.claimTrie != nil {
|
||||||
|
if err := b.ParseClaimScripts(block, node, view, false, current); err != nil {
|
||||||
|
return ruleError(ErrBadClaimTrie, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Write any block status changes to DB before updating best state.
|
// Write any block status changes to DB before updating best state.
|
||||||
err := b.index.flushToDB()
|
err := b.index.flushToDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -761,6 +773,10 @@ func (b *BlockChain) disconnectBlock(node *blockNode, block *btcutil.Block, view
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if err = b.claimTrie.ResetHeight(node.parent.height); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
// Prune fully spent entries and mark all entries in the view unmodified
|
// Prune fully spent entries and mark all entries in the view unmodified
|
||||||
// now that the modifications have been committed to the database.
|
// now that the modifications have been committed to the database.
|
||||||
view.commit()
|
view.commit()
|
||||||
|
@ -1208,7 +1224,7 @@ func (b *BlockChain) connectBestChain(node *blockNode, block *btcutil.Block, fla
|
||||||
// factors are used to guess, but the key factors that allow the chain to
|
// factors are used to guess, but the key factors that allow the chain to
|
||||||
// believe it is current are:
|
// believe it is current are:
|
||||||
// - Latest block height is after the latest checkpoint (if enabled)
|
// - Latest block height is after the latest checkpoint (if enabled)
|
||||||
// - Latest block has a timestamp newer than 24 hours ago
|
// - Latest block has a timestamp newer than ~6 hours ago (as LBRY block time is one fourth of bitcoin)
|
||||||
//
|
//
|
||||||
// This function MUST be called with the chain state lock held (for reads).
|
// This function MUST be called with the chain state lock held (for reads).
|
||||||
func (b *BlockChain) isCurrent() bool {
|
func (b *BlockChain) isCurrent() bool {
|
||||||
|
@ -1219,13 +1235,13 @@ func (b *BlockChain) isCurrent() bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Not current if the latest best block has a timestamp before 24 hours
|
// Not current if the latest best block has a timestamp before 7 hours
|
||||||
// ago.
|
// ago.
|
||||||
//
|
//
|
||||||
// The chain appears to be current if none of the checks reported
|
// The chain appears to be current if none of the checks reported
|
||||||
// otherwise.
|
// otherwise.
|
||||||
minus24Hours := b.timeSource.AdjustedTime().Add(-24 * time.Hour).Unix()
|
hours := b.timeSource.AdjustedTime().Add(-7 * time.Hour).Unix()
|
||||||
return b.bestChain.Tip().timestamp >= minus24Hours
|
return b.bestChain.Tip().timestamp >= hours
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsCurrent returns whether or not the chain believes it is current. Several
|
// IsCurrent returns whether or not the chain believes it is current. Several
|
||||||
|
@ -1614,6 +1630,11 @@ func (b *BlockChain) LocateHeaders(locator BlockLocator, hashStop *chainhash.Has
|
||||||
return headers
|
return headers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ClaimTrie returns the claimTrie associated wit hthe chain.
|
||||||
|
func (b *BlockChain) ClaimTrie() *claimtrie.ClaimTrie {
|
||||||
|
return b.claimTrie
|
||||||
|
}
|
||||||
|
|
||||||
// IndexManager provides a generic interface that the is called when blocks are
|
// IndexManager provides a generic interface that the is called when blocks are
|
||||||
// connected and disconnected to and from the tip of the main chain for the
|
// connected and disconnected to and from the tip of the main chain for the
|
||||||
// purpose of supporting optional indexes.
|
// purpose of supporting optional indexes.
|
||||||
|
@ -1700,6 +1721,8 @@ type Config struct {
|
||||||
// This field can be nil if the caller is not interested in using a
|
// This field can be nil if the caller is not interested in using a
|
||||||
// signature cache.
|
// signature cache.
|
||||||
HashCache *txscript.HashCache
|
HashCache *txscript.HashCache
|
||||||
|
|
||||||
|
ClaimTrie *claimtrie.ClaimTrie
|
||||||
}
|
}
|
||||||
|
|
||||||
// New returns a BlockChain instance using the provided configuration details.
|
// New returns a BlockChain instance using the provided configuration details.
|
||||||
|
@ -1736,7 +1759,6 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
params := config.ChainParams
|
params := config.ChainParams
|
||||||
targetTimespan := int64(params.TargetTimespan / time.Second)
|
targetTimespan := int64(params.TargetTimespan / time.Second)
|
||||||
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
|
targetTimePerBlock := int64(params.TargetTimePerBlock / time.Second)
|
||||||
adjustmentFactor := params.RetargetAdjustmentFactor
|
|
||||||
b := BlockChain{
|
b := BlockChain{
|
||||||
checkpoints: config.Checkpoints,
|
checkpoints: config.Checkpoints,
|
||||||
checkpointsByHeight: checkpointsByHeight,
|
checkpointsByHeight: checkpointsByHeight,
|
||||||
|
@ -1745,8 +1767,8 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
timeSource: config.TimeSource,
|
timeSource: config.TimeSource,
|
||||||
sigCache: config.SigCache,
|
sigCache: config.SigCache,
|
||||||
indexManager: config.IndexManager,
|
indexManager: config.IndexManager,
|
||||||
minRetargetTimespan: targetTimespan / adjustmentFactor,
|
minRetargetTimespan: targetTimespan - (targetTimespan / 8),
|
||||||
maxRetargetTimespan: targetTimespan * adjustmentFactor,
|
maxRetargetTimespan: targetTimespan + (targetTimespan / 2),
|
||||||
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
blocksPerRetarget: int32(targetTimespan / targetTimePerBlock),
|
||||||
index: newBlockIndex(config.DB, params),
|
index: newBlockIndex(config.DB, params),
|
||||||
hashCache: config.HashCache,
|
hashCache: config.HashCache,
|
||||||
|
@ -1755,6 +1777,7 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
|
prevOrphans: make(map[chainhash.Hash][]*orphanBlock),
|
||||||
warningCaches: newThresholdCaches(vbNumBits),
|
warningCaches: newThresholdCaches(vbNumBits),
|
||||||
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
|
deploymentCaches: newThresholdCaches(chaincfg.DefinedDeployments),
|
||||||
|
claimTrie: config.ClaimTrie,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the chain state from the passed database. When the db
|
// Initialize the chain state from the passed database. When the db
|
||||||
|
@ -1764,6 +1787,20 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper function to insert the output in genesis block in to the
|
||||||
|
// transaction database.
|
||||||
|
fn := func(dbTx database.Tx) error {
|
||||||
|
genesisBlock := btcutil.NewBlock(b.chainParams.GenesisBlock)
|
||||||
|
view := NewUtxoViewpoint()
|
||||||
|
if err := view.connectTransactions(genesisBlock, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return dbPutUtxoView(dbTx, view)
|
||||||
|
}
|
||||||
|
if err := b.db.Update(fn); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Perform any upgrades to the various chain-specific buckets as needed.
|
// Perform any upgrades to the various chain-specific buckets as needed.
|
||||||
if err := b.maybeUpgradeDbBuckets(config.Interrupt); err != nil {
|
if err := b.maybeUpgradeDbBuckets(config.Interrupt); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -1783,6 +1820,14 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if b.claimTrie != nil {
|
||||||
|
err := rebuildMissingClaimTrieData(&b, config.Interrupt)
|
||||||
|
if err != nil {
|
||||||
|
b.claimTrie.Close()
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bestNode := b.bestChain.Tip()
|
bestNode := b.bestChain.Tip()
|
||||||
log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)",
|
log.Infof("Chain state (height %d, hash %v, totaltx %d, work %v)",
|
||||||
bestNode.height, bestNode.hash, b.stateSnapshot.TotalTxns,
|
bestNode.height, bestNode.hash, b.stateSnapshot.TotalTxns,
|
||||||
|
@ -1790,3 +1835,63 @@ func New(config *Config) (*BlockChain, error) {
|
||||||
|
|
||||||
return &b, nil
|
return &b, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func rebuildMissingClaimTrieData(b *BlockChain, done <-chan struct{}) error {
|
||||||
|
target := b.bestChain.Height()
|
||||||
|
if b.claimTrie.Height() == target {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if b.claimTrie.Height() > target {
|
||||||
|
return b.claimTrie.ResetHeight(target)
|
||||||
|
}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
lastReport := time.Now()
|
||||||
|
// TODO: move this view inside the loop (or recreate it every 5 sec.)
|
||||||
|
// as accumulating all inputs has potential to use a huge amount of RAM
|
||||||
|
// but we need to get the spent inputs working for that to be possible
|
||||||
|
view := NewUtxoViewpoint()
|
||||||
|
for h := int32(0); h < target; h++ {
|
||||||
|
select {
|
||||||
|
case <-done:
|
||||||
|
return fmt.Errorf("rebuild unfinished at height %d", b.claimTrie.Height())
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
n := b.bestChain.NodeByHeight(h + 1)
|
||||||
|
|
||||||
|
var block *btcutil.Block
|
||||||
|
err := b.db.View(func(dbTx database.Tx) error {
|
||||||
|
var err error
|
||||||
|
block, err = dbFetchBlockByNode(dbTx, n)
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = view.fetchInputUtxos(b.db, block)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = view.connectTransactions(block, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h >= b.claimTrie.Height() {
|
||||||
|
err = b.ParseClaimScripts(block, n, view, true, false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if time.Since(lastReport) > time.Second*5 {
|
||||||
|
lastReport = time.Now()
|
||||||
|
log.Infof("Rebuilding claim trie data to %d. At: %d", target, h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Infof("Completed rebuilding claim trie data to %d. Took %s ",
|
||||||
|
b.claimTrie.Height(), time.Since(start))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -1,966 +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/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestHaveBlock tests the HaveBlock API to ensure proper functionality.
|
|
||||||
func TestHaveBlock(t *testing.T) {
|
|
||||||
// Load up blocks such that there is a side chain.
|
|
||||||
// (genesis block) -> 1 -> 2 -> 3 -> 4
|
|
||||||
// \-> 3a
|
|
||||||
testFiles := []string{
|
|
||||||
"blk_0_to_4.dat.bz2",
|
|
||||||
"blk_3A.dat.bz2",
|
|
||||||
}
|
|
||||||
|
|
||||||
var blocks []*btcutil.Block
|
|
||||||
for _, file := range testFiles {
|
|
||||||
blockTmp, err := loadBlocks(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Error loading file: %v\n", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
blocks = append(blocks, blockTmp...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new database and chain instance to run tests against.
|
|
||||||
chain, teardownFunc, err := chainSetup("haveblock",
|
|
||||||
&chaincfg.MainNetParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to setup chain instance: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer teardownFunc()
|
|
||||||
|
|
||||||
// Since we're not dealing with the real block chain, set the coinbase
|
|
||||||
// maturity to 1.
|
|
||||||
chain.TstSetCoinbaseMaturity(1)
|
|
||||||
|
|
||||||
for i := 1; i < len(blocks); i++ {
|
|
||||||
_, isOrphan, err := chain.ProcessBlock(blocks[i], BFNone)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("ProcessBlock fail on block %v: %v\n", i, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if isOrphan {
|
|
||||||
t.Errorf("ProcessBlock incorrectly returned block %v "+
|
|
||||||
"is an orphan\n", i)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Insert an orphan block.
|
|
||||||
_, isOrphan, err := chain.ProcessBlock(btcutil.NewBlock(&Block100000),
|
|
||||||
BFNone)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Unable to process block: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !isOrphan {
|
|
||||||
t.Errorf("ProcessBlock indicated block is an not orphan when " +
|
|
||||||
"it should be\n")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
hash string
|
|
||||||
want bool
|
|
||||||
}{
|
|
||||||
// Genesis block should be present (in the main chain).
|
|
||||||
{hash: chaincfg.MainNetParams.GenesisHash.String(), want: true},
|
|
||||||
|
|
||||||
// Block 3a should be present (on a side chain).
|
|
||||||
{hash: "00000000474284d20067a4d33f6a02284e6ef70764a3a26d6a5b9df52ef663dd", want: true},
|
|
||||||
|
|
||||||
// Block 100000 should be present (as an orphan).
|
|
||||||
{hash: "000000000003ba27aa200b1cecaad478d2b00432346c3f1f3986da1afd33e506", want: true},
|
|
||||||
|
|
||||||
// Random hashes should not be available.
|
|
||||||
{hash: "123", want: false},
|
|
||||||
}
|
|
||||||
|
|
||||||
for i, test := range tests {
|
|
||||||
hash, err := chainhash.NewHashFromStr(test.hash)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("NewHashFromStr: %v", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
result, err := chain.HaveBlock(hash)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("HaveBlock #%d unexpected error: %v", i, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if result != test.want {
|
|
||||||
t.Errorf("HaveBlock #%d got %v want %v", i, result,
|
|
||||||
test.want)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
178
blockchain/claimtrie.go
Normal file
178
blockchain/claimtrie.go
Normal file
|
@ -0,0 +1,178 @@
|
||||||
|
package blockchain
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (b *BlockChain) ParseClaimScripts(block *btcutil.Block, bn *blockNode, view *UtxoViewpoint,
|
||||||
|
failOnHashMiss bool, shouldFlush bool) error {
|
||||||
|
ht := block.Height()
|
||||||
|
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
h := handler{ht, tx, view, map[string][]byte{}}
|
||||||
|
if err := h.handleTxIns(b.claimTrie); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := h.handleTxOuts(b.claimTrie); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := b.claimTrie.AppendBlock()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "in append block")
|
||||||
|
}
|
||||||
|
|
||||||
|
if shouldFlush {
|
||||||
|
b.claimTrie.FlushToDisk()
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := b.claimTrie.MerkleHash()
|
||||||
|
if bn.claimTrie != *hash {
|
||||||
|
if failOnHashMiss {
|
||||||
|
return errors.Errorf("height: %d, ct.MerkleHash: %s != node.ClaimTrie: %s", ht, *hash, bn.claimTrie)
|
||||||
|
}
|
||||||
|
node.LogOnce(fmt.Sprintf("\n\nHeight: %d, ct.MerkleHash: %s != node.ClaimTrie: %s, Error: %s", ht, *hash, bn.claimTrie, err))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type handler struct {
|
||||||
|
ht int32
|
||||||
|
tx *btcutil.Tx
|
||||||
|
view *UtxoViewpoint
|
||||||
|
spent map[string][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *handler) handleTxIns(ct *claimtrie.ClaimTrie) error {
|
||||||
|
if IsCoinBase(h.tx) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for _, txIn := range h.tx.MsgTx().TxIn {
|
||||||
|
op := txIn.PreviousOutPoint
|
||||||
|
e := h.view.LookupEntry(op)
|
||||||
|
if e == nil {
|
||||||
|
return errors.Errorf("missing input in view for %s", op.String())
|
||||||
|
}
|
||||||
|
cs, closer, err := txscript.DecodeClaimScript(e.pkScript)
|
||||||
|
if err == txscript.ErrNotClaimScript {
|
||||||
|
closer()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
closer()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var id change.ClaimID
|
||||||
|
name := cs.Name() // name of the previous one (that we're now spending)
|
||||||
|
|
||||||
|
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()] = node.NormalizeIfNecessary(name, ct.Height())
|
||||||
|
err = ct.SpendClaim(name, op, id)
|
||||||
|
case txscript.OP_UPDATECLAIM:
|
||||||
|
copy(id[:], cs.ClaimID())
|
||||||
|
h.spent[id.Key()] = node.NormalizeIfNecessary(name, ct.Height())
|
||||||
|
err = ct.SpendClaim(name, op, id)
|
||||||
|
case txscript.OP_SUPPORTCLAIM:
|
||||||
|
copy(id[:], cs.ClaimID())
|
||||||
|
err = ct.SpendSupport(name, op, id)
|
||||||
|
}
|
||||||
|
closer()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "handleTxIns")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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, closer, err := txscript.DecodeClaimScript(txOut.PkScript)
|
||||||
|
if err == txscript.ErrNotClaimScript {
|
||||||
|
closer()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
closer()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var id change.ClaimID
|
||||||
|
name := cs.Name()
|
||||||
|
amt := txOut.Value
|
||||||
|
|
||||||
|
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())
|
||||||
|
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())
|
||||||
|
normName := node.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",
|
||||||
|
ct.Height(), normName, id.String()))
|
||||||
|
closer()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
delete(h.spent, id.Key())
|
||||||
|
err = ct.UpdateClaim(name, op, amt, id)
|
||||||
|
}
|
||||||
|
closer()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "handleTxOuts")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlockChain) GetNamesChangedInBlock(height int32) ([]string, error) {
|
||||||
|
b.chainLock.RLock()
|
||||||
|
defer b.chainLock.RUnlock()
|
||||||
|
|
||||||
|
return b.claimTrie.NamesChangedInBlock(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *BlockChain) GetClaimsForName(height int32, name string) (string, *node.Node, error) {
|
||||||
|
|
||||||
|
normalizedName := node.NormalizeIfNecessary([]byte(name), height)
|
||||||
|
|
||||||
|
b.chainLock.RLock()
|
||||||
|
defer b.chainLock.RUnlock()
|
||||||
|
|
||||||
|
n, err := b.claimTrie.NodeAt(height, normalizedName)
|
||||||
|
if err != nil {
|
||||||
|
if n != nil {
|
||||||
|
n.Close()
|
||||||
|
}
|
||||||
|
return string(normalizedName), nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if n == nil {
|
||||||
|
return string(normalizedName), nil, fmt.Errorf("name does not exist at height %d: %s", height, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
n.SortClaimsByBid()
|
||||||
|
return string(normalizedName), n, nil
|
||||||
|
}
|
|
@ -159,7 +159,6 @@ func CalcWork(bits uint32) *big.Int {
|
||||||
func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) uint32 {
|
func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration) uint32 {
|
||||||
// Convert types used in the calculations below.
|
// Convert types used in the calculations below.
|
||||||
durationVal := int64(duration / time.Second)
|
durationVal := int64(duration / time.Second)
|
||||||
adjustmentFactor := big.NewInt(b.chainParams.RetargetAdjustmentFactor)
|
|
||||||
|
|
||||||
// The test network rules allow minimum difficulty blocks after more
|
// The test network rules allow minimum difficulty blocks after more
|
||||||
// than twice the desired amount of time needed to generate a block has
|
// than twice the desired amount of time needed to generate a block has
|
||||||
|
@ -178,7 +177,8 @@ func (b *BlockChain) calcEasiestDifficulty(bits uint32, duration time.Duration)
|
||||||
// multiplied by the max adjustment factor.
|
// multiplied by the max adjustment factor.
|
||||||
newTarget := CompactToBig(bits)
|
newTarget := CompactToBig(bits)
|
||||||
for durationVal > 0 && newTarget.Cmp(b.chainParams.PowLimit) < 0 {
|
for durationVal > 0 && newTarget.Cmp(b.chainParams.PowLimit) < 0 {
|
||||||
newTarget.Mul(newTarget, adjustmentFactor)
|
adj := new(big.Int).Div(newTarget, big.NewInt(2))
|
||||||
|
newTarget.Add(newTarget, adj)
|
||||||
durationVal -= b.maxRetargetTimespan
|
durationVal -= b.maxRetargetTimespan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -224,47 +224,44 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
|
||||||
return b.chainParams.PowLimitBits, nil
|
return b.chainParams.PowLimitBits, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the previous block's difficulty requirements if this block
|
// For networks that support it, allow special reduction of the
|
||||||
// is not at a difficulty retarget interval.
|
// required difficulty once too much time has elapsed without
|
||||||
if (lastNode.height+1)%b.blocksPerRetarget != 0 {
|
// mining a block.
|
||||||
// For networks that support it, allow special reduction of the
|
if b.chainParams.ReduceMinDifficulty {
|
||||||
// required difficulty once too much time has elapsed without
|
// Return minimum difficulty when more than the desired
|
||||||
// mining a block.
|
// amount of time has elapsed without mining a block.
|
||||||
if b.chainParams.ReduceMinDifficulty {
|
reductionTime := int64(b.chainParams.MinDiffReductionTime /
|
||||||
// Return minimum difficulty when more than the desired
|
time.Second)
|
||||||
// amount of time has elapsed without mining a block.
|
allowMinTime := lastNode.timestamp + reductionTime
|
||||||
reductionTime := int64(b.chainParams.MinDiffReductionTime /
|
if newBlockTime.Unix() > allowMinTime {
|
||||||
time.Second)
|
return b.chainParams.PowLimitBits, nil
|
||||||
allowMinTime := lastNode.timestamp + reductionTime
|
|
||||||
if newBlockTime.Unix() > allowMinTime {
|
|
||||||
return b.chainParams.PowLimitBits, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// The block was mined within the desired timeframe, so
|
|
||||||
// return the difficulty for the last block which did
|
|
||||||
// not have the special minimum difficulty rule applied.
|
|
||||||
return b.findPrevTestNetDifficulty(lastNode), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// For the main network (or any unrecognized networks), simply
|
// The block was mined within the desired timeframe, so
|
||||||
// return the previous block's difficulty requirements.
|
// return the difficulty for the last block which did
|
||||||
return lastNode.bits, nil
|
// not have the special minimum difficulty rule applied.
|
||||||
|
return b.findPrevTestNetDifficulty(lastNode), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the block node at the previous retarget (targetTimespan days
|
// Get the block node at the previous retarget (targetTimespan days
|
||||||
// worth of blocks).
|
// worth of blocks).
|
||||||
firstNode := lastNode.RelativeAncestor(b.blocksPerRetarget - 1)
|
firstNode := lastNode.RelativeAncestor(b.blocksPerRetarget)
|
||||||
|
if lastNode.height == 0 {
|
||||||
|
firstNode = lastNode
|
||||||
|
}
|
||||||
if firstNode == nil {
|
if firstNode == nil {
|
||||||
return 0, AssertError("unable to obtain previous retarget block")
|
return 0, AssertError("unable to obtain previous retarget block")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
targetTimeSpan := int64(b.chainParams.TargetTimespan / time.Second)
|
||||||
|
|
||||||
// Limit the amount of adjustment that can occur to the previous
|
// Limit the amount of adjustment that can occur to the previous
|
||||||
// difficulty.
|
// difficulty.
|
||||||
actualTimespan := lastNode.timestamp - firstNode.timestamp
|
actualTimespan := lastNode.timestamp - firstNode.timestamp
|
||||||
adjustedTimespan := actualTimespan
|
adjustedTimespan := targetTimeSpan + (actualTimespan-targetTimeSpan)/8
|
||||||
if actualTimespan < b.minRetargetTimespan {
|
if adjustedTimespan < b.minRetargetTimespan {
|
||||||
adjustedTimespan = b.minRetargetTimespan
|
adjustedTimespan = b.minRetargetTimespan
|
||||||
} else if actualTimespan > b.maxRetargetTimespan {
|
} else if adjustedTimespan > b.maxRetargetTimespan {
|
||||||
adjustedTimespan = b.maxRetargetTimespan
|
adjustedTimespan = b.maxRetargetTimespan
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,7 +272,6 @@ func (b *BlockChain) calcNextRequiredDifficulty(lastNode *blockNode, newBlockTim
|
||||||
// result.
|
// result.
|
||||||
oldTarget := CompactToBig(lastNode.bits)
|
oldTarget := CompactToBig(lastNode.bits)
|
||||||
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
|
newTarget := new(big.Int).Mul(oldTarget, big.NewInt(adjustedTimespan))
|
||||||
targetTimeSpan := int64(b.chainParams.TargetTimespan / time.Second)
|
|
||||||
newTarget.Div(newTarget, big.NewInt(targetTimeSpan))
|
newTarget.Div(newTarget, big.NewInt(targetTimeSpan))
|
||||||
|
|
||||||
// Limit new value to the proof of work limit.
|
// Limit new value to the proof of work limit.
|
||||||
|
|
|
@ -220,6 +220,10 @@ const (
|
||||||
// current chain tip. This is not a block validation rule, but is required
|
// current chain tip. This is not a block validation rule, but is required
|
||||||
// for block proposals submitted via getblocktemplate RPC.
|
// for block proposals submitted via getblocktemplate RPC.
|
||||||
ErrPrevBlockNotBest
|
ErrPrevBlockNotBest
|
||||||
|
|
||||||
|
// ErrBadClaimTrie indicates the calculated ClaimTrie root does not match
|
||||||
|
// the expected value.
|
||||||
|
ErrBadClaimTrie
|
||||||
)
|
)
|
||||||
|
|
||||||
// Map of ErrorCode values back to their constant names for pretty printing.
|
// Map of ErrorCode values back to their constant names for pretty printing.
|
||||||
|
@ -267,6 +271,7 @@ var errorCodeStrings = map[ErrorCode]string{
|
||||||
ErrPreviousBlockUnknown: "ErrPreviousBlockUnknown",
|
ErrPreviousBlockUnknown: "ErrPreviousBlockUnknown",
|
||||||
ErrInvalidAncestorBlock: "ErrInvalidAncestorBlock",
|
ErrInvalidAncestorBlock: "ErrInvalidAncestorBlock",
|
||||||
ErrPrevBlockNotBest: "ErrPrevBlockNotBest",
|
ErrPrevBlockNotBest: "ErrPrevBlockNotBest",
|
||||||
|
ErrBadClaimTrie: "ErrBadClaimTrie",
|
||||||
}
|
}
|
||||||
|
|
||||||
// String returns the ErrorCode as a human-readable name.
|
// String returns the ErrorCode as a human-readable name.
|
||||||
|
|
|
@ -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/btcsuite/btcd/blockchain"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/database"
|
|
||||||
_ "github.com/btcsuite/btcd/database/ffldb"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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/btcsuite/btcd/blockchain"
|
|
||||||
"github.com/btcsuite/btcd/blockchain/fullblocktests"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcd/database"
|
|
||||||
_ "github.com/btcsuite/btcd/database/ffldb"
|
|
||||||
"github.com/btcsuite/btcd/txscript"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
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",
|
|
||||||
&chaincfg.RegressionNetParams)
|
|
||||||
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,11 +31,11 @@ const (
|
||||||
// Intentionally defined here rather than using constants from codebase
|
// Intentionally defined here rather than using constants from codebase
|
||||||
// to ensure consensus changes are detected.
|
// to ensure consensus changes are detected.
|
||||||
maxBlockSigOps = 20000
|
maxBlockSigOps = 20000
|
||||||
maxBlockSize = 1000000
|
maxBlockSize = 2000000
|
||||||
minCoinbaseScriptLen = 2
|
minCoinbaseScriptLen = 2
|
||||||
maxCoinbaseScriptLen = 100
|
maxCoinbaseScriptLen = 100
|
||||||
medianTimeBlocks = 11
|
medianTimeBlocks = 11
|
||||||
maxScriptElementSize = 520
|
maxScriptElementSize = 20000
|
||||||
|
|
||||||
// numLargeReorgBlocks is the number of blocks to use in the large block
|
// numLargeReorgBlocks is the number of blocks to use in the large block
|
||||||
// reorg test (when enabled). This is the equivalent of 1 week's worth
|
// reorg test (when enabled). This is the equivalent of 1 week's worth
|
||||||
|
@ -1875,7 +1875,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) {
|
||||||
//
|
//
|
||||||
// Comment assumptions:
|
// Comment assumptions:
|
||||||
// maxBlockSigOps = 20000
|
// maxBlockSigOps = 20000
|
||||||
// maxScriptElementSize = 520
|
// maxScriptElementSize = 20000
|
||||||
//
|
//
|
||||||
// [0-19999] : OP_CHECKSIG
|
// [0-19999] : OP_CHECKSIG
|
||||||
// [20000] : OP_PUSHDATA4
|
// [20000] : OP_PUSHDATA4
|
||||||
|
|
|
@ -230,8 +230,8 @@ func ValidateWitnessCommitment(blk *btcutil.Block) error {
|
||||||
coinbaseWitness := coinbaseTx.MsgTx().TxIn[0].Witness
|
coinbaseWitness := coinbaseTx.MsgTx().TxIn[0].Witness
|
||||||
if len(coinbaseWitness) != 1 {
|
if len(coinbaseWitness) != 1 {
|
||||||
str := fmt.Sprintf("the coinbase transaction has %d items in "+
|
str := fmt.Sprintf("the coinbase transaction has %d items in "+
|
||||||
"its witness stack when only one is allowed",
|
"its witness stack when only one is allowed. Height: %d",
|
||||||
len(coinbaseWitness))
|
len(coinbaseWitness), blk.Height())
|
||||||
return ruleError(ErrInvalidWitnessCommitment, str)
|
return ruleError(ErrInvalidWitnessCommitment, str)
|
||||||
}
|
}
|
||||||
witnessNonce := coinbaseWitness[0]
|
witnessNonce := coinbaseWitness[0]
|
||||||
|
|
|
@ -1,23 +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"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestMerkle tests the BuildMerkleTreeStore API.
|
|
||||||
func TestMerkle(t *testing.T) {
|
|
||||||
block := btcutil.NewBlock(&Block100000)
|
|
||||||
merkles := BuildMerkleTreeStore(block.Transactions(), false)
|
|
||||||
calculatedMerkleRoot := merkles[len(merkles)-1]
|
|
||||||
wantMerkle := &Block100000.Header.MerkleRoot
|
|
||||||
if !wantMerkle.IsEqual(calculatedMerkleRoot) {
|
|
||||||
t.Errorf("BuildMerkleTreeStore: merkle root mismatch - "+
|
|
||||||
"got %v, want %v", calculatedMerkleRoot, wantMerkle)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
// Copyright (c) 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"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestNotifications ensures that notification callbacks are fired on events.
|
|
||||||
func TestNotifications(t *testing.T) {
|
|
||||||
blocks, err := loadBlocks("blk_0_to_4.dat.bz2")
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error loading file: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new database and chain instance to run tests against.
|
|
||||||
chain, teardownFunc, err := chainSetup("notifications",
|
|
||||||
&chaincfg.MainNetParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Failed to setup chain instance: %v", err)
|
|
||||||
}
|
|
||||||
defer teardownFunc()
|
|
||||||
|
|
||||||
notificationCount := 0
|
|
||||||
callback := func(notification *Notification) {
|
|
||||||
if notification.Type == NTBlockAccepted {
|
|
||||||
notificationCount++
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register callback multiple times then assert it is called that many
|
|
||||||
// times.
|
|
||||||
const numSubscribers = 3
|
|
||||||
for i := 0; i < numSubscribers; i++ {
|
|
||||||
chain.Subscribe(callback)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, _, err = chain.ProcessBlock(blocks[1], BFNone)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("ProcessBlock fail on block 1: %v\n", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if notificationCount != numSubscribers {
|
|
||||||
t.Fatalf("Expected notification callback to be executed %d "+
|
|
||||||
"times, found %d", numSubscribers, notificationCount)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -86,6 +86,7 @@ out:
|
||||||
txIn.PreviousOutPoint, err, witness,
|
txIn.PreviousOutPoint, err, witness,
|
||||||
sigScript, pkScript)
|
sigScript, pkScript)
|
||||||
err := ruleError(ErrScriptMalformed, str)
|
err := ruleError(ErrScriptMalformed, str)
|
||||||
|
vm.Close()
|
||||||
v.sendResult(err)
|
v.sendResult(err)
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
@ -100,11 +101,13 @@ out:
|
||||||
txIn.PreviousOutPoint, err, witness,
|
txIn.PreviousOutPoint, err, witness,
|
||||||
sigScript, pkScript)
|
sigScript, pkScript)
|
||||||
err := ruleError(ErrScriptValidation, str)
|
err := ruleError(ErrScriptValidation, str)
|
||||||
|
vm.Close()
|
||||||
v.sendResult(err)
|
v.sendResult(err)
|
||||||
break out
|
break out
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation succeeded.
|
// Validation succeeded.
|
||||||
|
vm.Close()
|
||||||
v.sendResult(nil)
|
v.sendResult(nil)
|
||||||
|
|
||||||
case <-v.quitChan:
|
case <-v.quitChan:
|
||||||
|
|
|
@ -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/btcsuite/btcd/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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -302,6 +302,12 @@ func (b *BlockChain) deploymentState(prevNode *blockNode, deploymentID uint32) (
|
||||||
}
|
}
|
||||||
|
|
||||||
deployment := &b.chainParams.Deployments[deploymentID]
|
deployment := &b.chainParams.Deployments[deploymentID]
|
||||||
|
|
||||||
|
// added to mimic LBRYcrd:
|
||||||
|
if deployment.ForceActiveAt > 0 && prevNode != nil && prevNode.height+1 >= deployment.ForceActiveAt {
|
||||||
|
return ThresholdActive, nil
|
||||||
|
}
|
||||||
|
|
||||||
checker := deploymentChecker{deployment: deployment, chain: b}
|
checker := deploymentChecker{deployment: deployment, chain: b}
|
||||||
cache := &b.deploymentCaches[deploymentID]
|
cache := &b.deploymentCaches[deploymentID]
|
||||||
|
|
||||||
|
|
|
@ -40,7 +40,7 @@ const (
|
||||||
|
|
||||||
// baseSubsidy is the starting subsidy amount for mined blocks. This
|
// baseSubsidy is the starting subsidy amount for mined blocks. This
|
||||||
// value is halved every SubsidyHalvingInterval blocks.
|
// value is halved every SubsidyHalvingInterval blocks.
|
||||||
baseSubsidy = 50 * btcutil.SatoshiPerBitcoin
|
baseSubsidy = 500 * btcutil.SatoshiPerBitcoin
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -192,17 +192,44 @@ func isBIP0030Node(node *blockNode) bool {
|
||||||
// At the target block generation rate for the main network, this is
|
// At the target block generation rate for the main network, this is
|
||||||
// approximately every 4 years.
|
// approximately every 4 years.
|
||||||
func CalcBlockSubsidy(height int32, chainParams *chaincfg.Params) int64 {
|
func CalcBlockSubsidy(height int32, chainParams *chaincfg.Params) int64 {
|
||||||
if chainParams.SubsidyReductionInterval == 0 {
|
h := int64(height)
|
||||||
return baseSubsidy
|
if h == 0 {
|
||||||
|
return btcutil.SatoshiPerBitcoin * 4e8
|
||||||
|
}
|
||||||
|
if h <= 5100 {
|
||||||
|
return btcutil.SatoshiPerBitcoin
|
||||||
|
}
|
||||||
|
if h <= 55000 {
|
||||||
|
return btcutil.SatoshiPerBitcoin * (1 + (h-5001)/100)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Equivalent to: baseSubsidy / 2^(height/subsidyHalvingInterval)
|
lv := (h - 55001) / int64(chainParams.SubsidyReductionInterval)
|
||||||
return baseSubsidy >> uint(height/chainParams.SubsidyReductionInterval)
|
reduction := (int64(math.Sqrt((float64(8*lv))+1)) - 1) / 2
|
||||||
|
for !withinLevelBounds(reduction, lv) {
|
||||||
|
if ((reduction*reduction + reduction) >> 1) > lv {
|
||||||
|
reduction--
|
||||||
|
} else {
|
||||||
|
reduction++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
subsidyReduction := btcutil.SatoshiPerBitcoin * reduction
|
||||||
|
if subsidyReduction >= baseSubsidy {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return baseSubsidy - subsidyReduction
|
||||||
|
}
|
||||||
|
|
||||||
|
func withinLevelBounds(reduction int64, lv int64) bool {
|
||||||
|
if ((reduction*reduction + reduction) >> 1) > lv {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
reduction++
|
||||||
|
return ((reduction*reduction + reduction) >> 1) > lv
|
||||||
}
|
}
|
||||||
|
|
||||||
// CheckTransactionSanity performs some preliminary checks on a transaction to
|
// CheckTransactionSanity performs some preliminary checks on a transaction to
|
||||||
// ensure it is sane. These checks are context free.
|
// ensure it is sane.
|
||||||
func CheckTransactionSanity(tx *btcutil.Tx) error {
|
func CheckTransactionSanity(tx *btcutil.Tx, enforceSoftFork bool) error {
|
||||||
// A transaction must have at least one input.
|
// A transaction must have at least one input.
|
||||||
msgTx := tx.MsgTx()
|
msgTx := tx.MsgTx()
|
||||||
if len(msgTx.TxIn) == 0 {
|
if len(msgTx.TxIn) == 0 {
|
||||||
|
@ -261,6 +288,11 @@ func CheckTransactionSanity(tx *btcutil.Tx) error {
|
||||||
btcutil.MaxSatoshi)
|
btcutil.MaxSatoshi)
|
||||||
return ruleError(ErrBadTxOutValue, str)
|
return ruleError(ErrBadTxOutValue, str)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err := txscript.AllClaimsAreSane(txOut.PkScript, enforceSoftFork)
|
||||||
|
if err != nil {
|
||||||
|
return ruleError(ErrBadTxOutValue, err.Error())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for duplicate transaction inputs.
|
// Check for duplicate transaction inputs.
|
||||||
|
@ -324,7 +356,7 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio
|
||||||
// to avoid proof of work checks is set.
|
// to avoid proof of work checks is set.
|
||||||
if flags&BFNoPoWCheck != BFNoPoWCheck {
|
if flags&BFNoPoWCheck != BFNoPoWCheck {
|
||||||
// The block hash must be less than the claimed target.
|
// The block hash must be less than the claimed target.
|
||||||
hash := header.BlockHash()
|
hash := header.BlockPoWHash()
|
||||||
hashNum := HashToBig(&hash)
|
hashNum := HashToBig(&hash)
|
||||||
if hashNum.Cmp(target) > 0 {
|
if hashNum.Cmp(target) > 0 {
|
||||||
str := fmt.Sprintf("block hash of %064x is higher than "+
|
str := fmt.Sprintf("block hash of %064x is higher than "+
|
||||||
|
@ -515,7 +547,7 @@ func checkBlockSanity(block *btcutil.Block, powLimit *big.Int, timeSource Median
|
||||||
// Do some preliminary checks on each transaction to ensure they are
|
// Do some preliminary checks on each transaction to ensure they are
|
||||||
// sane before continuing.
|
// sane before continuing.
|
||||||
for _, tx := range transactions {
|
for _, tx := range transactions {
|
||||||
err := CheckTransactionSanity(tx)
|
err := CheckTransactionSanity(tx, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,487 +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 (
|
|
||||||
"math"
|
|
||||||
"reflect"
|
|
||||||
"testing"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestCheckConnectBlockTemplate tests the CheckConnectBlockTemplate function to
|
|
||||||
// ensure it fails.
|
|
||||||
func TestCheckConnectBlockTemplate(t *testing.T) {
|
|
||||||
// Create a new database and chain instance to run tests against.
|
|
||||||
chain, teardownFunc, err := chainSetup("checkconnectblocktemplate",
|
|
||||||
&chaincfg.MainNetParams)
|
|
||||||
if err != nil {
|
|
||||||
t.Errorf("Failed to setup chain instance: %v", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer teardownFunc()
|
|
||||||
|
|
||||||
// Since we're not dealing with the real block chain, set the coinbase
|
|
||||||
// maturity to 1.
|
|
||||||
chain.TstSetCoinbaseMaturity(1)
|
|
||||||
|
|
||||||
// Load up blocks such that there is a side chain.
|
|
||||||
// (genesis block) -> 1 -> 2 -> 3 -> 4
|
|
||||||
// \-> 3a
|
|
||||||
testFiles := []string{
|
|
||||||
"blk_0_to_4.dat.bz2",
|
|
||||||
"blk_3A.dat.bz2",
|
|
||||||
}
|
|
||||||
|
|
||||||
var blocks []*btcutil.Block
|
|
||||||
for _, file := range testFiles {
|
|
||||||
blockTmp, err := loadBlocks(file)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("Error loading file: %v\n", err)
|
|
||||||
}
|
|
||||||
blocks = append(blocks, blockTmp...)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 1; i <= 3; i++ {
|
|
||||||
isMainChain, _, err := chain.ProcessBlock(blocks[i], BFNone)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error "+
|
|
||||||
"processing block %d: %v", i, err)
|
|
||||||
}
|
|
||||||
if !isMainChain {
|
|
||||||
t.Fatalf("CheckConnectBlockTemplate: Expected block %d to connect "+
|
|
||||||
"to main chain", i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block 3 should fail to connect since it's already inserted.
|
|
||||||
err = chain.CheckConnectBlockTemplate(blocks[3])
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("CheckConnectBlockTemplate: Did not received expected error " +
|
|
||||||
"on block 3")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block 4 should connect successfully to tip of chain.
|
|
||||||
err = chain.CheckConnectBlockTemplate(blocks[4])
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error on "+
|
|
||||||
"block 4: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block 3a should fail to connect since does not build on chain tip.
|
|
||||||
err = chain.CheckConnectBlockTemplate(blocks[5])
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("CheckConnectBlockTemplate: Did not received expected error " +
|
|
||||||
"on block 3a")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block 4 should connect even if proof of work is invalid.
|
|
||||||
invalidPowBlock := *blocks[4].MsgBlock()
|
|
||||||
invalidPowBlock.Header.Nonce++
|
|
||||||
err = chain.CheckConnectBlockTemplate(btcutil.NewBlock(&invalidPowBlock))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("CheckConnectBlockTemplate: Received unexpected error on "+
|
|
||||||
"block 4 with bad nonce: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Invalid block building on chain tip should fail to connect.
|
|
||||||
invalidBlock := *blocks[4].MsgBlock()
|
|
||||||
invalidBlock.Header.Bits--
|
|
||||||
err = chain.CheckConnectBlockTemplate(btcutil.NewBlock(&invalidBlock))
|
|
||||||
if err == nil {
|
|
||||||
t.Fatal("CheckConnectBlockTemplate: Did not received expected error " +
|
|
||||||
"on block 4 with invalid difficulty bits")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// TestCheckBlockSanity tests the CheckBlockSanity function to ensure it works
|
|
||||||
// as expected.
|
|
||||||
func TestCheckBlockSanity(t *testing.T) {
|
|
||||||
powLimit := chaincfg.MainNetParams.PowLimit
|
|
||||||
block := btcutil.NewBlock(&Block100000)
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Block100000 defines block 100,000 of the block chain. It is used to
|
|
||||||
// test Block operations.
|
|
||||||
var Block100000 = wire.MsgBlock{
|
|
||||||
Header: wire.BlockHeader{
|
|
||||||
Version: 1,
|
|
||||||
PrevBlock: chainhash.Hash([32]byte{ // Make go vet happy.
|
|
||||||
0x50, 0x12, 0x01, 0x19, 0x17, 0x2a, 0x61, 0x04,
|
|
||||||
0x21, 0xa6, 0xc3, 0x01, 0x1d, 0xd3, 0x30, 0xd9,
|
|
||||||
0xdf, 0x07, 0xb6, 0x36, 0x16, 0xc2, 0xcc, 0x1f,
|
|
||||||
0x1c, 0xd0, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
}), // 000000000002d01c1fccc21636b607dfd930d31d01c3a62104612a1719011250
|
|
||||||
MerkleRoot: chainhash.Hash([32]byte{ // Make go vet happy.
|
|
||||||
0x66, 0x57, 0xa9, 0x25, 0x2a, 0xac, 0xd5, 0xc0,
|
|
||||||
0xb2, 0x94, 0x09, 0x96, 0xec, 0xff, 0x95, 0x22,
|
|
||||||
0x28, 0xc3, 0x06, 0x7c, 0xc3, 0x8d, 0x48, 0x85,
|
|
||||||
0xef, 0xb5, 0xa4, 0xac, 0x42, 0x47, 0xe9, 0xf3,
|
|
||||||
}), // f3e94742aca4b5ef85488dc37c06c3282295ffec960994b2c0d5ac2a25a95766
|
|
||||||
Timestamp: time.Unix(1293623863, 0), // 2010-12-29 11:57:43 +0000 UTC
|
|
||||||
Bits: 0x1b04864c, // 453281356
|
|
||||||
Nonce: 0x10572b0f, // 274148111
|
|
||||||
},
|
|
||||||
Transactions: []*wire.MsgTx{
|
|
||||||
{
|
|
||||||
Version: 1,
|
|
||||||
TxIn: []*wire.TxIn{
|
|
||||||
{
|
|
||||||
PreviousOutPoint: wire.OutPoint{
|
|
||||||
Hash: chainhash.Hash{},
|
|
||||||
Index: 0xffffffff,
|
|
||||||
},
|
|
||||||
SignatureScript: []byte{
|
|
||||||
0x04, 0x4c, 0x86, 0x04, 0x1b, 0x02, 0x06, 0x02,
|
|
||||||
},
|
|
||||||
Sequence: 0xffffffff,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TxOut: []*wire.TxOut{
|
|
||||||
{
|
|
||||||
Value: 0x12a05f200, // 5000000000
|
|
||||||
PkScript: []byte{
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0x1b, 0x0e, 0x8c, 0x25, 0x67, 0xc1, 0x25,
|
|
||||||
0x36, 0xaa, 0x13, 0x35, 0x7b, 0x79, 0xa0, 0x73,
|
|
||||||
0xdc, 0x44, 0x44, 0xac, 0xb8, 0x3c, 0x4e, 0xc7,
|
|
||||||
0xa0, 0xe2, 0xf9, 0x9d, 0xd7, 0x45, 0x75, 0x16,
|
|
||||||
0xc5, 0x81, 0x72, 0x42, 0xda, 0x79, 0x69, 0x24,
|
|
||||||
0xca, 0x4e, 0x99, 0x94, 0x7d, 0x08, 0x7f, 0xed,
|
|
||||||
0xf9, 0xce, 0x46, 0x7c, 0xb9, 0xf7, 0xc6, 0x28,
|
|
||||||
0x70, 0x78, 0xf8, 0x01, 0xdf, 0x27, 0x6f, 0xdf,
|
|
||||||
0x84, // 65-byte signature
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LockTime: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 1,
|
|
||||||
TxIn: []*wire.TxIn{
|
|
||||||
{
|
|
||||||
PreviousOutPoint: wire.OutPoint{
|
|
||||||
Hash: chainhash.Hash([32]byte{ // Make go vet happy.
|
|
||||||
0x03, 0x2e, 0x38, 0xe9, 0xc0, 0xa8, 0x4c, 0x60,
|
|
||||||
0x46, 0xd6, 0x87, 0xd1, 0x05, 0x56, 0xdc, 0xac,
|
|
||||||
0xc4, 0x1d, 0x27, 0x5e, 0xc5, 0x5f, 0xc0, 0x07,
|
|
||||||
0x79, 0xac, 0x88, 0xfd, 0xf3, 0x57, 0xa1, 0x87,
|
|
||||||
}), // 87a157f3fd88ac7907c05fc55e271dc4acdc5605d187d646604ca8c0e9382e03
|
|
||||||
Index: 0,
|
|
||||||
},
|
|
||||||
SignatureScript: []byte{
|
|
||||||
0x49, // OP_DATA_73
|
|
||||||
0x30, 0x46, 0x02, 0x21, 0x00, 0xc3, 0x52, 0xd3,
|
|
||||||
0xdd, 0x99, 0x3a, 0x98, 0x1b, 0xeb, 0xa4, 0xa6,
|
|
||||||
0x3a, 0xd1, 0x5c, 0x20, 0x92, 0x75, 0xca, 0x94,
|
|
||||||
0x70, 0xab, 0xfc, 0xd5, 0x7d, 0xa9, 0x3b, 0x58,
|
|
||||||
0xe4, 0xeb, 0x5d, 0xce, 0x82, 0x02, 0x21, 0x00,
|
|
||||||
0x84, 0x07, 0x92, 0xbc, 0x1f, 0x45, 0x60, 0x62,
|
|
||||||
0x81, 0x9f, 0x15, 0xd3, 0x3e, 0xe7, 0x05, 0x5c,
|
|
||||||
0xf7, 0xb5, 0xee, 0x1a, 0xf1, 0xeb, 0xcc, 0x60,
|
|
||||||
0x28, 0xd9, 0xcd, 0xb1, 0xc3, 0xaf, 0x77, 0x48,
|
|
||||||
0x01, // 73-byte signature
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0xf4, 0x6d, 0xb5, 0xe9, 0xd6, 0x1a, 0x9d,
|
|
||||||
0xc2, 0x7b, 0x8d, 0x64, 0xad, 0x23, 0xe7, 0x38,
|
|
||||||
0x3a, 0x4e, 0x6c, 0xa1, 0x64, 0x59, 0x3c, 0x25,
|
|
||||||
0x27, 0xc0, 0x38, 0xc0, 0x85, 0x7e, 0xb6, 0x7e,
|
|
||||||
0xe8, 0xe8, 0x25, 0xdc, 0xa6, 0x50, 0x46, 0xb8,
|
|
||||||
0x2c, 0x93, 0x31, 0x58, 0x6c, 0x82, 0xe0, 0xfd,
|
|
||||||
0x1f, 0x63, 0x3f, 0x25, 0xf8, 0x7c, 0x16, 0x1b,
|
|
||||||
0xc6, 0xf8, 0xa6, 0x30, 0x12, 0x1d, 0xf2, 0xb3,
|
|
||||||
0xd3, // 65-byte pubkey
|
|
||||||
},
|
|
||||||
Sequence: 0xffffffff,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TxOut: []*wire.TxOut{
|
|
||||||
{
|
|
||||||
Value: 0x2123e300, // 556000000
|
|
||||||
PkScript: []byte{
|
|
||||||
0x76, // OP_DUP
|
|
||||||
0xa9, // OP_HASH160
|
|
||||||
0x14, // OP_DATA_20
|
|
||||||
0xc3, 0x98, 0xef, 0xa9, 0xc3, 0x92, 0xba, 0x60,
|
|
||||||
0x13, 0xc5, 0xe0, 0x4e, 0xe7, 0x29, 0x75, 0x5e,
|
|
||||||
0xf7, 0xf5, 0x8b, 0x32,
|
|
||||||
0x88, // OP_EQUALVERIFY
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value: 0x108e20f00, // 4444000000
|
|
||||||
PkScript: []byte{
|
|
||||||
0x76, // OP_DUP
|
|
||||||
0xa9, // OP_HASH160
|
|
||||||
0x14, // OP_DATA_20
|
|
||||||
0x94, 0x8c, 0x76, 0x5a, 0x69, 0x14, 0xd4, 0x3f,
|
|
||||||
0x2a, 0x7a, 0xc1, 0x77, 0xda, 0x2c, 0x2f, 0x6b,
|
|
||||||
0x52, 0xde, 0x3d, 0x7c,
|
|
||||||
0x88, // OP_EQUALVERIFY
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LockTime: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 1,
|
|
||||||
TxIn: []*wire.TxIn{
|
|
||||||
{
|
|
||||||
PreviousOutPoint: wire.OutPoint{
|
|
||||||
Hash: chainhash.Hash([32]byte{ // Make go vet happy.
|
|
||||||
0xc3, 0x3e, 0xbf, 0xf2, 0xa7, 0x09, 0xf1, 0x3d,
|
|
||||||
0x9f, 0x9a, 0x75, 0x69, 0xab, 0x16, 0xa3, 0x27,
|
|
||||||
0x86, 0xaf, 0x7d, 0x7e, 0x2d, 0xe0, 0x92, 0x65,
|
|
||||||
0xe4, 0x1c, 0x61, 0xd0, 0x78, 0x29, 0x4e, 0xcf,
|
|
||||||
}), // cf4e2978d0611ce46592e02d7e7daf8627a316ab69759a9f3df109a7f2bf3ec3
|
|
||||||
Index: 1,
|
|
||||||
},
|
|
||||||
SignatureScript: []byte{
|
|
||||||
0x47, // OP_DATA_71
|
|
||||||
0x30, 0x44, 0x02, 0x20, 0x03, 0x2d, 0x30, 0xdf,
|
|
||||||
0x5e, 0xe6, 0xf5, 0x7f, 0xa4, 0x6c, 0xdd, 0xb5,
|
|
||||||
0xeb, 0x8d, 0x0d, 0x9f, 0xe8, 0xde, 0x6b, 0x34,
|
|
||||||
0x2d, 0x27, 0x94, 0x2a, 0xe9, 0x0a, 0x32, 0x31,
|
|
||||||
0xe0, 0xba, 0x33, 0x3e, 0x02, 0x20, 0x3d, 0xee,
|
|
||||||
0xe8, 0x06, 0x0f, 0xdc, 0x70, 0x23, 0x0a, 0x7f,
|
|
||||||
0x5b, 0x4a, 0xd7, 0xd7, 0xbc, 0x3e, 0x62, 0x8c,
|
|
||||||
0xbe, 0x21, 0x9a, 0x88, 0x6b, 0x84, 0x26, 0x9e,
|
|
||||||
0xae, 0xb8, 0x1e, 0x26, 0xb4, 0xfe, 0x01,
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0xae, 0x31, 0xc3, 0x1b, 0xf9, 0x12, 0x78,
|
|
||||||
0xd9, 0x9b, 0x83, 0x77, 0xa3, 0x5b, 0xbc, 0xe5,
|
|
||||||
0xb2, 0x7d, 0x9f, 0xff, 0x15, 0x45, 0x68, 0x39,
|
|
||||||
0xe9, 0x19, 0x45, 0x3f, 0xc7, 0xb3, 0xf7, 0x21,
|
|
||||||
0xf0, 0xba, 0x40, 0x3f, 0xf9, 0x6c, 0x9d, 0xee,
|
|
||||||
0xb6, 0x80, 0xe5, 0xfd, 0x34, 0x1c, 0x0f, 0xc3,
|
|
||||||
0xa7, 0xb9, 0x0d, 0xa4, 0x63, 0x1e, 0xe3, 0x95,
|
|
||||||
0x60, 0x63, 0x9d, 0xb4, 0x62, 0xe9, 0xcb, 0x85,
|
|
||||||
0x0f, // 65-byte pubkey
|
|
||||||
},
|
|
||||||
Sequence: 0xffffffff,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TxOut: []*wire.TxOut{
|
|
||||||
{
|
|
||||||
Value: 0xf4240, // 1000000
|
|
||||||
PkScript: []byte{
|
|
||||||
0x76, // OP_DUP
|
|
||||||
0xa9, // OP_HASH160
|
|
||||||
0x14, // OP_DATA_20
|
|
||||||
0xb0, 0xdc, 0xbf, 0x97, 0xea, 0xbf, 0x44, 0x04,
|
|
||||||
0xe3, 0x1d, 0x95, 0x24, 0x77, 0xce, 0x82, 0x2d,
|
|
||||||
0xad, 0xbe, 0x7e, 0x10,
|
|
||||||
0x88, // OP_EQUALVERIFY
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Value: 0x11d260c0, // 299000000
|
|
||||||
PkScript: []byte{
|
|
||||||
0x76, // OP_DUP
|
|
||||||
0xa9, // OP_HASH160
|
|
||||||
0x14, // OP_DATA_20
|
|
||||||
0x6b, 0x12, 0x81, 0xee, 0xc2, 0x5a, 0xb4, 0xe1,
|
|
||||||
0xe0, 0x79, 0x3f, 0xf4, 0xe0, 0x8a, 0xb1, 0xab,
|
|
||||||
0xb3, 0x40, 0x9c, 0xd9,
|
|
||||||
0x88, // OP_EQUALVERIFY
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LockTime: 0,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Version: 1,
|
|
||||||
TxIn: []*wire.TxIn{
|
|
||||||
{
|
|
||||||
PreviousOutPoint: wire.OutPoint{
|
|
||||||
Hash: chainhash.Hash([32]byte{ // Make go vet happy.
|
|
||||||
0x0b, 0x60, 0x72, 0xb3, 0x86, 0xd4, 0xa7, 0x73,
|
|
||||||
0x23, 0x52, 0x37, 0xf6, 0x4c, 0x11, 0x26, 0xac,
|
|
||||||
0x3b, 0x24, 0x0c, 0x84, 0xb9, 0x17, 0xa3, 0x90,
|
|
||||||
0x9b, 0xa1, 0xc4, 0x3d, 0xed, 0x5f, 0x51, 0xf4,
|
|
||||||
}), // f4515fed3dc4a19b90a317b9840c243bac26114cf637522373a7d486b372600b
|
|
||||||
Index: 0,
|
|
||||||
},
|
|
||||||
SignatureScript: []byte{
|
|
||||||
0x49, // OP_DATA_73
|
|
||||||
0x30, 0x46, 0x02, 0x21, 0x00, 0xbb, 0x1a, 0xd2,
|
|
||||||
0x6d, 0xf9, 0x30, 0xa5, 0x1c, 0xce, 0x11, 0x0c,
|
|
||||||
0xf4, 0x4f, 0x7a, 0x48, 0xc3, 0xc5, 0x61, 0xfd,
|
|
||||||
0x97, 0x75, 0x00, 0xb1, 0xae, 0x5d, 0x6b, 0x6f,
|
|
||||||
0xd1, 0x3d, 0x0b, 0x3f, 0x4a, 0x02, 0x21, 0x00,
|
|
||||||
0xc5, 0xb4, 0x29, 0x51, 0xac, 0xed, 0xff, 0x14,
|
|
||||||
0xab, 0xba, 0x27, 0x36, 0xfd, 0x57, 0x4b, 0xdb,
|
|
||||||
0x46, 0x5f, 0x3e, 0x6f, 0x8d, 0xa1, 0x2e, 0x2c,
|
|
||||||
0x53, 0x03, 0x95, 0x4a, 0xca, 0x7f, 0x78, 0xf3,
|
|
||||||
0x01, // 73-byte signature
|
|
||||||
0x41, // OP_DATA_65
|
|
||||||
0x04, 0xa7, 0x13, 0x5b, 0xfe, 0x82, 0x4c, 0x97,
|
|
||||||
0xec, 0xc0, 0x1e, 0xc7, 0xd7, 0xe3, 0x36, 0x18,
|
|
||||||
0x5c, 0x81, 0xe2, 0xaa, 0x2c, 0x41, 0xab, 0x17,
|
|
||||||
0x54, 0x07, 0xc0, 0x94, 0x84, 0xce, 0x96, 0x94,
|
|
||||||
0xb4, 0x49, 0x53, 0xfc, 0xb7, 0x51, 0x20, 0x65,
|
|
||||||
0x64, 0xa9, 0xc2, 0x4d, 0xd0, 0x94, 0xd4, 0x2f,
|
|
||||||
0xdb, 0xfd, 0xd5, 0xaa, 0xd3, 0xe0, 0x63, 0xce,
|
|
||||||
0x6a, 0xf4, 0xcf, 0xaa, 0xea, 0x4e, 0xa1, 0x4f,
|
|
||||||
0xbb, // 65-byte pubkey
|
|
||||||
},
|
|
||||||
Sequence: 0xffffffff,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
TxOut: []*wire.TxOut{
|
|
||||||
{
|
|
||||||
Value: 0xf4240, // 1000000
|
|
||||||
PkScript: []byte{
|
|
||||||
0x76, // OP_DUP
|
|
||||||
0xa9, // OP_HASH160
|
|
||||||
0x14, // OP_DATA_20
|
|
||||||
0x39, 0xaa, 0x3d, 0x56, 0x9e, 0x06, 0xa1, 0xd7,
|
|
||||||
0x92, 0x6d, 0xc4, 0xbe, 0x11, 0x93, 0xc9, 0x9b,
|
|
||||||
0xf2, 0xeb, 0x9e, 0xe0,
|
|
||||||
0x88, // OP_EQUALVERIFY
|
|
||||||
0xac, // OP_CHECKSIG
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
LockTime: 0,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
|
@ -195,6 +195,12 @@ func (b *BlockChain) calcNextBlockVersion(prevNode *blockNode) (int32, error) {
|
||||||
expectedVersion := uint32(vbTopBits)
|
expectedVersion := uint32(vbTopBits)
|
||||||
for id := 0; id < len(b.chainParams.Deployments); id++ {
|
for id := 0; id < len(b.chainParams.Deployments); id++ {
|
||||||
deployment := &b.chainParams.Deployments[id]
|
deployment := &b.chainParams.Deployments[id]
|
||||||
|
|
||||||
|
// added to mimic LBRYcrd:
|
||||||
|
if deployment.ForceActiveAt > 0 && prevNode != nil && prevNode.height+1 >= deployment.ForceActiveAt {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
cache := &b.deploymentCaches[id]
|
cache := &b.deploymentCaches[id]
|
||||||
checker := deploymentChecker{deployment: deployment, chain: b}
|
checker := deploymentChecker{deployment: deployment, chain: b}
|
||||||
state, err := b.thresholdState(prevNode, checker, cache)
|
state, err := b.thresholdState(prevNode, checker, cache)
|
||||||
|
|
|
@ -20,11 +20,11 @@ const (
|
||||||
// weight of a "base" byte is 4, while the weight of a witness byte is
|
// weight of a "base" byte is 4, while the weight of a witness byte is
|
||||||
// 1. As a result, for a block to be valid, the BlockWeight MUST be
|
// 1. As a result, for a block to be valid, the BlockWeight MUST be
|
||||||
// less than, or equal to MaxBlockWeight.
|
// less than, or equal to MaxBlockWeight.
|
||||||
MaxBlockWeight = 4000000
|
MaxBlockWeight = 8000000
|
||||||
|
|
||||||
// MaxBlockBaseSize is the maximum number of bytes within a block
|
// MaxBlockBaseSize is the maximum number of bytes within a block
|
||||||
// which can be allocated to non-witness data.
|
// which can be allocated to non-witness data.
|
||||||
MaxBlockBaseSize = 1000000
|
MaxBlockBaseSize = 8000000
|
||||||
|
|
||||||
// MaxBlockSigOpsCost is the maximum number of signature operations
|
// MaxBlockSigOpsCost is the maximum number of signature operations
|
||||||
// allowed for a block. It is calculated via a weighted algorithm which
|
// allowed for a block. It is calculated via a weighted algorithm which
|
||||||
|
|
12
btcd.go
12
btcd.go
|
@ -16,8 +16,11 @@ import (
|
||||||
"runtime/pprof"
|
"runtime/pprof"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/blockchain/indexers"
|
"github.com/btcsuite/btcd/blockchain/indexers"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
"github.com/btcsuite/btcd/database"
|
"github.com/btcsuite/btcd/database"
|
||||||
"github.com/btcsuite/btcd/limits"
|
"github.com/btcsuite/btcd/limits"
|
||||||
|
|
||||||
|
"github.com/felixge/fgprof"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -65,6 +68,7 @@ func btcdMain(serverChan chan<- *server) error {
|
||||||
|
|
||||||
// Enable http profiling server if requested.
|
// Enable http profiling server if requested.
|
||||||
if cfg.Profile != "" {
|
if cfg.Profile != "" {
|
||||||
|
http.DefaultServeMux.Handle("/debug/fgprof", fgprof.Handler())
|
||||||
go func() {
|
go func() {
|
||||||
listenAddr := net.JoinHostPort("", cfg.Profile)
|
listenAddr := net.JoinHostPort("", cfg.Profile)
|
||||||
btcdLog.Infof("Profile server listening on %s", listenAddr)
|
btcdLog.Infof("Profile server listening on %s", listenAddr)
|
||||||
|
@ -144,6 +148,10 @@ func btcdMain(serverChan chan<- *server) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
param.SetNetwork(activeNetParams.Params.Net) // prep the claimtrie params
|
||||||
|
|
||||||
|
go logMemoryUsage()
|
||||||
|
|
||||||
// Create server and start it.
|
// Create server and start it.
|
||||||
server, err := newServer(cfg.Listeners, cfg.AgentBlacklist,
|
server, err := newServer(cfg.Listeners, cfg.AgentBlacklist,
|
||||||
cfg.AgentWhitelist, db, activeNetParams.Params, interrupt)
|
cfg.AgentWhitelist, db, activeNetParams.Params, interrupt)
|
||||||
|
@ -158,6 +166,10 @@ func btcdMain(serverChan chan<- *server) error {
|
||||||
server.Stop()
|
server.Stop()
|
||||||
server.WaitForShutdown()
|
server.WaitForShutdown()
|
||||||
srvrLog.Infof("Server shutdown complete")
|
srvrLog.Infof("Server shutdown complete")
|
||||||
|
// TODO: tie into the sync manager for shutdown instead
|
||||||
|
if ct := server.chain.ClaimTrie(); ct != nil {
|
||||||
|
ct.Close()
|
||||||
|
}
|
||||||
}()
|
}()
|
||||||
server.Start()
|
server.Start()
|
||||||
if serverChan != nil {
|
if serverChan != nil {
|
||||||
|
|
|
@ -388,7 +388,7 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewGetBlockFilterCmd("0000afaf", nil)
|
return btcjson.NewGetBlockFilterCmd("0000afaf", nil)
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getblockfilter","params":["0000afaf"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getblockfilter","params":["0000afaf"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetBlockFilterCmd{"0000afaf", nil},
|
unmarshalled: &btcjson.GetBlockFilterCmd{BlockHash: "0000afaf", FilterType: nil},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "getblockfilter optional filtertype",
|
name: "getblockfilter optional filtertype",
|
||||||
|
@ -399,7 +399,7 @@ func TestChainSvrCmds(t *testing.T) {
|
||||||
return btcjson.NewGetBlockFilterCmd("0000afaf", btcjson.NewFilterTypeName(btcjson.FilterTypeBasic))
|
return btcjson.NewGetBlockFilterCmd("0000afaf", btcjson.NewFilterTypeName(btcjson.FilterTypeBasic))
|
||||||
},
|
},
|
||||||
marshalled: `{"jsonrpc":"1.0","method":"getblockfilter","params":["0000afaf","basic"],"id":1}`,
|
marshalled: `{"jsonrpc":"1.0","method":"getblockfilter","params":["0000afaf","basic"],"id":1}`,
|
||||||
unmarshalled: &btcjson.GetBlockFilterCmd{"0000afaf", btcjson.NewFilterTypeName(btcjson.FilterTypeBasic)},
|
unmarshalled: &btcjson.GetBlockFilterCmd{BlockHash: "0000afaf", FilterType: btcjson.NewFilterTypeName(btcjson.FilterTypeBasic)},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "getblockhash",
|
name: "getblockhash",
|
||||||
|
|
|
@ -25,6 +25,7 @@ type GetBlockHeaderVerboseResult struct {
|
||||||
Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
VersionHex string `json:"versionHex"`
|
VersionHex string `json:"versionHex"`
|
||||||
MerkleRoot string `json:"merkleroot"`
|
MerkleRoot string `json:"merkleroot"`
|
||||||
|
ClaimTrie string `json:"claimtrie"`
|
||||||
Time int64 `json:"time"`
|
Time int64 `json:"time"`
|
||||||
Nonce uint64 `json:"nonce"`
|
Nonce uint64 `json:"nonce"`
|
||||||
Bits string `json:"bits"`
|
Bits string `json:"bits"`
|
||||||
|
@ -81,6 +82,7 @@ type GetBlockVerboseResult struct {
|
||||||
Version int32 `json:"version"`
|
Version int32 `json:"version"`
|
||||||
VersionHex string `json:"versionHex"`
|
VersionHex string `json:"versionHex"`
|
||||||
MerkleRoot string `json:"merkleroot"`
|
MerkleRoot string `json:"merkleroot"`
|
||||||
|
ClaimTrie string `json:"claimTrie"`
|
||||||
Tx []string `json:"tx,omitempty"`
|
Tx []string `json:"tx,omitempty"`
|
||||||
RawTx []TxRawResult `json:"rawtx,omitempty"` // Note: this field is always empty when verbose != 2.
|
RawTx []TxRawResult `json:"rawtx,omitempty"` // Note: this field is always empty when verbose != 2.
|
||||||
Time int64 `json:"time"`
|
Time int64 `json:"time"`
|
||||||
|
@ -428,6 +430,9 @@ type ScriptPubKeyResult struct {
|
||||||
Hex string `json:"hex,omitempty"`
|
Hex string `json:"hex,omitempty"`
|
||||||
ReqSigs int32 `json:"reqSigs,omitempty"`
|
ReqSigs int32 `json:"reqSigs,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
|
SubType string `json:"subtype"`
|
||||||
|
IsClaim bool `json:"isclaim"`
|
||||||
|
IsSupport bool `json:"issupport"`
|
||||||
Addresses []string `json:"addresses,omitempty"`
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -586,6 +591,8 @@ func (v *Vin) MarshalJSON() ([]byte, error) {
|
||||||
type PrevOut struct {
|
type PrevOut struct {
|
||||||
Addresses []string `json:"addresses,omitempty"`
|
Addresses []string `json:"addresses,omitempty"`
|
||||||
Value float64 `json:"value"`
|
Value float64 `json:"value"`
|
||||||
|
IsClaim bool `json:"isclaim"`
|
||||||
|
IsSupport bool `json:"issupport"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// VinPrevOut is like Vin except it includes PrevOut. It is used by searchrawtransaction
|
// VinPrevOut is like Vin except it includes PrevOut. It is used by searchrawtransaction
|
||||||
|
|
|
@ -70,7 +70,7 @@ func TestChainSvrCustomResults(t *testing.T) {
|
||||||
},
|
},
|
||||||
Sequence: 4294967295,
|
Sequence: 4294967295,
|
||||||
},
|
},
|
||||||
expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"prevOut":{"addresses":["addr1"],"value":0},"sequence":4294967295}`,
|
expected: `{"txid":"123","vout":1,"scriptSig":{"asm":"0","hex":"00"},"prevOut":{"addresses":["addr1"],"value":0,"isclaim":false,"issupport":false},"sequence":4294967295}`,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
95
btcjson/claimcmds.go
Normal file
95
btcjson/claimcmds.go
Normal file
|
@ -0,0 +1,95 @@
|
||||||
|
package btcjson
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
// No special flags for commands in this file.
|
||||||
|
flags := UsageFlag(0)
|
||||||
|
|
||||||
|
MustRegisterCmd("getchangesinblock", (*GetChangesInBlockCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getclaimsforname", (*GetClaimsForNameCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getclaimsfornamebyid", (*GetClaimsForNameByIDCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getclaimsfornamebybid", (*GetClaimsForNameByBidCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("getclaimsfornamebyseq", (*GetClaimsForNameBySeqCmd)(nil), flags)
|
||||||
|
MustRegisterCmd("normalize", (*GetNormalizedCmd)(nil), flags)
|
||||||
|
}
|
||||||
|
|
||||||
|
// optional inputs are required to be pointers, but they support things like `jsonrpcdefault:"false"`
|
||||||
|
// optional inputs have to be at the bottom of the struct
|
||||||
|
// optional outputs require ",omitempty"
|
||||||
|
// traditional bitcoin fields are all lowercase
|
||||||
|
|
||||||
|
type GetChangesInBlockCmd struct {
|
||||||
|
HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetChangesInBlockResult struct {
|
||||||
|
Hash string `json:"hash"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
Names []string `json:"names"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetClaimsForNameCmd struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""`
|
||||||
|
IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetClaimsForNameByIDCmd struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
PartialClaimIDs []string `json:"partialclaimids"`
|
||||||
|
HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""`
|
||||||
|
IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetClaimsForNameByBidCmd struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Bids []int32 `json:"bids"`
|
||||||
|
HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""`
|
||||||
|
IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetClaimsForNameBySeqCmd struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Sequences []int32 `json:"sequences" jsonrpcusage:"[sequence,...]"`
|
||||||
|
HashOrHeight *string `json:"hashorheight" jsonrpcdefault:""`
|
||||||
|
IncludeValues *bool `json:"includevalues" jsonrpcdefault:"false"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetClaimsForNameResult struct {
|
||||||
|
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 {
|
||||||
|
TXID string `json:"txid"`
|
||||||
|
N uint32 `json:"n"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
ValidAtHeight int32 `json:"validatheight"`
|
||||||
|
Amount int64 `json:"amount"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClaimResult struct {
|
||||||
|
ClaimID string `json:"claimid"`
|
||||||
|
TXID string `json:"txid"`
|
||||||
|
N uint32 `json:"n"`
|
||||||
|
Bid int32 `json:"bid"`
|
||||||
|
Sequence int32 `json:"sequence"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
ValidAtHeight int32 `json:"validatheight"`
|
||||||
|
EffectiveAmount int64 `json:"effectiveamount"`
|
||||||
|
Supports []SupportResult `json:"supports,omitempty"`
|
||||||
|
Address string `json:"address,omitempty"`
|
||||||
|
Value string `json:"value,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNormalizedCmd struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GetNormalizedResult struct {
|
||||||
|
NormalizedName string `json:"normalizedname"`
|
||||||
|
}
|
|
@ -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/btcsuite/btcd/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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,593 +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/btcsuite/btcd/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")},
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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/btcsuite/btcd/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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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/btcsuite/btcd/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/btcsuite/btcd/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")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -5,7 +5,12 @@
|
||||||
|
|
||||||
package chainhash
|
package chainhash
|
||||||
|
|
||||||
import "crypto/sha256"
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
|
"crypto/sha512"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ripemd160"
|
||||||
|
)
|
||||||
|
|
||||||
// HashB calculates hash(b) and returns the resulting bytes.
|
// HashB calculates hash(b) and returns the resulting bytes.
|
||||||
func HashB(b []byte) []byte {
|
func HashB(b []byte) []byte {
|
||||||
|
@ -31,3 +36,26 @@ func DoubleHashH(b []byte) Hash {
|
||||||
first := sha256.Sum256(b)
|
first := sha256.Sum256(b)
|
||||||
return Hash(sha256.Sum256(first[:]))
|
return Hash(sha256.Sum256(first[:]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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))
|
||||||
|
func LbryPoWHashH(b []byte) Hash {
|
||||||
|
doubled := DoubleHashB(b)
|
||||||
|
expanded := sha512.Sum512(doubled)
|
||||||
|
|
||||||
|
r := ripemd160.New()
|
||||||
|
r.Reset()
|
||||||
|
r.Write(expanded[:sha256.Size])
|
||||||
|
left := r.Sum(nil)
|
||||||
|
|
||||||
|
r.Reset()
|
||||||
|
r.Write(expanded[sha256.Size:])
|
||||||
|
|
||||||
|
combined := r.Sum(left)
|
||||||
|
return DoubleHashH(combined)
|
||||||
|
}
|
||||||
|
|
|
@ -22,33 +22,22 @@ var genesisCoinbaseTx = wire.MsgTx{
|
||||||
Index: 0xffffffff,
|
Index: 0xffffffff,
|
||||||
},
|
},
|
||||||
SignatureScript: []byte{
|
SignatureScript: []byte{
|
||||||
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x45, /* |.......E| */
|
0x04, 0xff, 0xff, 0x00, 0x1d, 0x01, 0x04, 0x17,
|
||||||
0x54, 0x68, 0x65, 0x20, 0x54, 0x69, 0x6d, 0x65, /* |The Time| */
|
0x69, 0x6e, 0x73, 0x65, 0x72, 0x74, 0x20, 0x74,
|
||||||
0x73, 0x20, 0x30, 0x33, 0x2f, 0x4a, 0x61, 0x6e, /* |s 03/Jan| */
|
0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70,
|
||||||
0x2f, 0x32, 0x30, 0x30, 0x39, 0x20, 0x43, 0x68, /* |/2009 Ch| */
|
0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67,
|
||||||
0x61, 0x6e, 0x63, 0x65, 0x6c, 0x6c, 0x6f, 0x72, /* |ancellor| */
|
|
||||||
0x20, 0x6f, 0x6e, 0x20, 0x62, 0x72, 0x69, 0x6e, /* | on brin| */
|
|
||||||
0x6b, 0x20, 0x6f, 0x66, 0x20, 0x73, 0x65, 0x63, /* |k of sec|*/
|
|
||||||
0x6f, 0x6e, 0x64, 0x20, 0x62, 0x61, 0x69, 0x6c, /* |ond bail| */
|
|
||||||
0x6f, 0x75, 0x74, 0x20, 0x66, 0x6f, 0x72, 0x20, /* |out for |*/
|
|
||||||
0x62, 0x61, 0x6e, 0x6b, 0x73, /* |banks| */
|
|
||||||
},
|
},
|
||||||
Sequence: 0xffffffff,
|
Sequence: 0xffffffff,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
TxOut: []*wire.TxOut{
|
TxOut: []*wire.TxOut{
|
||||||
{
|
{
|
||||||
Value: 0x12a05f200,
|
Value: 0x8e1bc9bf040000, // 400000000 * COIN
|
||||||
PkScript: []byte{
|
PkScript: []byte{
|
||||||
0x41, 0x04, 0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, /* |A.g....U| */
|
0x76, 0xa9, 0x14, 0x34, 0x59, 0x91, 0xdb, 0xf5,
|
||||||
0x48, 0x27, 0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, /* |H'.g..q0| */
|
0x7b, 0xfb, 0x01, 0x4b, 0x87, 0x00, 0x6a, 0xcd,
|
||||||
0xb7, 0x10, 0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, /* |..\..(.9| */
|
0xfa, 0xfb, 0xfc, 0x5f, 0xe8, 0x29, 0x2f, 0x88,
|
||||||
0x09, 0xa6, 0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, /* |..yb...a| */
|
0xac,
|
||||||
0xde, 0xb6, 0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, /* |..I..?L.| */
|
|
||||||
0x38, 0xc4, 0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, /* |8..U....| */
|
|
||||||
0x12, 0xde, 0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, /* |..\8M...| */
|
|
||||||
0x8d, 0x57, 0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, /* |.W.Lp+k.| */
|
|
||||||
0x1d, 0x5f, 0xac, /* |._.| */
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -58,19 +47,28 @@ var genesisCoinbaseTx = wire.MsgTx{
|
||||||
// genesisHash is the hash of the first block in the block chain for the main
|
// genesisHash is the hash of the first block in the block chain for the main
|
||||||
// network (genesis block).
|
// network (genesis block).
|
||||||
var genesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
var genesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||||
0x6f, 0xe2, 0x8c, 0x0a, 0xb6, 0xf1, 0xb3, 0x72,
|
0x63, 0xf4, 0x34, 0x6a, 0x4d, 0xb3, 0x4f, 0xdf,
|
||||||
0xc1, 0xa6, 0xa2, 0x46, 0xae, 0x63, 0xf7, 0x4f,
|
0xce, 0x29, 0xa7, 0x0f, 0x5e, 0x8d, 0x11, 0xf0,
|
||||||
0x93, 0x1e, 0x83, 0x65, 0xe1, 0x5a, 0x08, 0x9c,
|
0x65, 0xf6, 0xb9, 0x16, 0x02, 0xb7, 0x03, 0x6c,
|
||||||
0x68, 0xd6, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00,
|
0x7f, 0x22, 0xf3, 0xa0, 0x3b, 0x28, 0x89, 0x9c,
|
||||||
})
|
})
|
||||||
|
|
||||||
// genesisMerkleRoot is the hash of the first transaction in the genesis block
|
// genesisMerkleRoot is the hash of the first transaction in the genesis block
|
||||||
// for the main network.
|
// for the main network.
|
||||||
var genesisMerkleRoot = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
var genesisMerkleRoot = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||||
0x3b, 0xa3, 0xed, 0xfd, 0x7a, 0x7b, 0x12, 0xb2,
|
0xcc, 0x59, 0xe5, 0x9f, 0xf9, 0x7a, 0xc0, 0x92,
|
||||||
0x7a, 0xc7, 0x2c, 0x3e, 0x67, 0x76, 0x8f, 0x61,
|
0xb5, 0x5e, 0x42, 0x3a, 0xa5, 0x49, 0x51, 0x51,
|
||||||
0x7f, 0xc8, 0x1b, 0xc3, 0x88, 0x8a, 0x51, 0x32,
|
0xed, 0x6f, 0xb8, 0x05, 0x70, 0xa5, 0xbb, 0x78,
|
||||||
0x3a, 0x9f, 0xb8, 0xaa, 0x4b, 0x1e, 0x5e, 0x4a,
|
0xcd, 0x5b, 0xd1, 0xc3, 0x82, 0x1c, 0x21, 0xb8,
|
||||||
|
})
|
||||||
|
|
||||||
|
// genesisClaimTrie is the hash of the first transaction in the genesis block
|
||||||
|
// for the main network.
|
||||||
|
var genesisClaimTrie = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||||
|
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||||
})
|
})
|
||||||
|
|
||||||
// genesisBlock defines the genesis block of the block chain which serves as the
|
// genesisBlock defines the genesis block of the block chain which serves as the
|
||||||
|
@ -79,10 +77,11 @@ var genesisBlock = wire.MsgBlock{
|
||||||
Header: wire.BlockHeader{
|
Header: wire.BlockHeader{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
||||||
MerkleRoot: genesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
|
MerkleRoot: genesisMerkleRoot, // b8211c82c3d15bcd78bba57005b86fed515149a53a425eb592c07af99fe559cc
|
||||||
Timestamp: time.Unix(0x495fab29, 0), // 2009-01-03 18:15:05 +0000 UTC
|
ClaimTrie: genesisClaimTrie, // 0000000000000000000000000000000000000000000000000000000000000001
|
||||||
Bits: 0x1d00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
|
Timestamp: time.Unix(1446058291, 0), // 28 Oct 2015 18:51:31 +0000 UTC
|
||||||
Nonce: 0x7c2bac1d, // 2083236893
|
Bits: 0x1f00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
|
||||||
|
Nonce: 0x00000507, // 1287
|
||||||
},
|
},
|
||||||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||||
}
|
}
|
||||||
|
@ -90,10 +89,10 @@ var genesisBlock = wire.MsgBlock{
|
||||||
// regTestGenesisHash is the hash of the first block in the block chain for the
|
// regTestGenesisHash is the hash of the first block in the block chain for the
|
||||||
// regression test network (genesis block).
|
// regression test network (genesis block).
|
||||||
var regTestGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
var regTestGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy.
|
||||||
0x06, 0x22, 0x6e, 0x46, 0x11, 0x1a, 0x0b, 0x59,
|
0x56, 0x75, 0x68, 0x69, 0x76, 0x67, 0x4f, 0x50,
|
||||||
0xca, 0xaf, 0x12, 0x60, 0x43, 0xeb, 0x5b, 0xbf,
|
0xa0, 0xa1, 0x95, 0x3d, 0x17, 0x2e, 0x9e, 0xcf,
|
||||||
0x28, 0xc3, 0x4f, 0x3a, 0x5e, 0x33, 0x2a, 0x1f,
|
0x4a, 0x4a, 0x62, 0x1d, 0xc9, 0xa4, 0xc3, 0x79,
|
||||||
0xc7, 0xb2, 0xb7, 0x3c, 0xf1, 0x88, 0x91, 0x0f,
|
0x5d, 0xec, 0xd4, 0x99, 0x12, 0xcf, 0x3f, 0x6e,
|
||||||
})
|
})
|
||||||
|
|
||||||
// regTestGenesisMerkleRoot is the hash of the first transaction in the genesis
|
// regTestGenesisMerkleRoot is the hash of the first transaction in the genesis
|
||||||
|
@ -107,10 +106,11 @@ var regTestGenesisBlock = wire.MsgBlock{
|
||||||
Header: wire.BlockHeader{
|
Header: wire.BlockHeader{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
||||||
MerkleRoot: regTestGenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
|
MerkleRoot: regTestGenesisMerkleRoot, // b8211c82c3d15bcd78bba57005b86fed515149a53a425eb592c07af99fe559cc
|
||||||
Timestamp: time.Unix(1296688602, 0), // 2011-02-02 23:16:42 +0000 UTC
|
ClaimTrie: genesisClaimTrie, // 0000000000000000000000000000000000000000000000000000000000000001
|
||||||
|
Timestamp: time.Unix(1446058291, 0), // 28 Oct 2015 18:51:31 +0000 UTC
|
||||||
Bits: 0x207fffff, // 545259519 [7fffff0000000000000000000000000000000000000000000000000000000000]
|
Bits: 0x207fffff, // 545259519 [7fffff0000000000000000000000000000000000000000000000000000000000]
|
||||||
Nonce: 2,
|
Nonce: 1,
|
||||||
},
|
},
|
||||||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||||
}
|
}
|
||||||
|
@ -135,10 +135,11 @@ var testNet3GenesisBlock = wire.MsgBlock{
|
||||||
Header: wire.BlockHeader{
|
Header: wire.BlockHeader{
|
||||||
Version: 1,
|
Version: 1,
|
||||||
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000
|
||||||
MerkleRoot: testNet3GenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b
|
MerkleRoot: testNet3GenesisMerkleRoot, // b8211c82c3d15bcd78bba57005b86fed515149a53a425eb592c07af99fe559cc
|
||||||
Timestamp: time.Unix(1296688602, 0), // 2011-02-02 23:16:42 +0000 UTC
|
ClaimTrie: genesisClaimTrie, // 0000000000000000000000000000000000000000000000000000000000000001
|
||||||
Bits: 0x1d00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
|
Timestamp: time.Unix(1446058291, 0), // 28 Oct 2015 18:51:31 +0000 UTC
|
||||||
Nonce: 0x18aea41a, // 414098458
|
Bits: 0x1f00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000]
|
||||||
|
Nonce: 0x00000507, // 1287
|
||||||
},
|
},
|
||||||
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
Transactions: []*wire.MsgTx{&genesisCoinbaseTx},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,351 +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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the encoded block matches the expected bytes.
|
|
||||||
if !bytes.Equal(buf.Bytes(), genesisBlockBytes) {
|
|
||||||
t.Fatalf("TestGenesisBlock: Genesis block does not appear valid - "+
|
|
||||||
"got %v, want %v", spew.Sdump(buf.Bytes()),
|
|
||||||
spew.Sdump(genesisBlockBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the encoded block matches the expected bytes.
|
|
||||||
if !bytes.Equal(buf.Bytes(), regTestGenesisBlockBytes) {
|
|
||||||
t.Fatalf("TestRegTestGenesisBlock: Genesis block does not "+
|
|
||||||
"appear valid - got %v, want %v",
|
|
||||||
spew.Sdump(buf.Bytes()),
|
|
||||||
spew.Sdump(regTestGenesisBlockBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the encoded block matches the expected bytes.
|
|
||||||
if !bytes.Equal(buf.Bytes(), testNet3GenesisBlockBytes) {
|
|
||||||
t.Fatalf("TestTestNet3GenesisBlock: Genesis block does not "+
|
|
||||||
"appear valid - got %v, want %v",
|
|
||||||
spew.Sdump(buf.Bytes()),
|
|
||||||
spew.Sdump(testNet3GenesisBlockBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the encoded block matches the expected bytes.
|
|
||||||
if !bytes.Equal(buf.Bytes(), simNetGenesisBlockBytes) {
|
|
||||||
t.Fatalf("TestSimNetGenesisBlock: Genesis block does not "+
|
|
||||||
"appear valid - got %v, want %v",
|
|
||||||
spew.Sdump(buf.Bytes()),
|
|
||||||
spew.Sdump(simNetGenesisBlockBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure the encoded block matches the expected bytes.
|
|
||||||
if !bytes.Equal(buf.Bytes(), sigNetGenesisBlockBytes) {
|
|
||||||
t.Fatalf("TestSigNetGenesisBlock: Genesis block does not "+
|
|
||||||
"appear valid - got %v, want %v",
|
|
||||||
spew.Sdump(buf.Bytes()),
|
|
||||||
spew.Sdump(sigNetGenesisBlockBytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// genesisBlockBytes are the wire encoded bytes for the genesis block of the
|
|
||||||
// main network as of protocol version 60002.
|
|
||||||
var genesisBlockBytes = []byte{
|
|
||||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |....;...| */
|
|
||||||
0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |z{..z.,>| */
|
|
||||||
0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |gv.a....| */
|
|
||||||
0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |..Q2:...| */
|
|
||||||
0x4b, 0x1e, 0x5e, 0x4a, 0x29, 0xab, 0x5f, 0x49, /* |K.^J)._I| */
|
|
||||||
0xff, 0xff, 0x00, 0x1d, 0x1d, 0xac, 0x2b, 0x7c, /* |......+|| */
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* |........| */
|
|
||||||
0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |..M.....| */
|
|
||||||
0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..EThe T| */
|
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |imes 03/| */
|
|
||||||
0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |Jan/2009| */
|
|
||||||
0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* | Chancel| */
|
|
||||||
0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* |lor on b| */
|
|
||||||
0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |rink of | */
|
|
||||||
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |second b| */
|
|
||||||
0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |ailout f| */
|
|
||||||
0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |or banks| */
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |........| */
|
|
||||||
0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |*....CA.| */
|
|
||||||
0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |g....UH'| */
|
|
||||||
0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |.g..q0..| */
|
|
||||||
0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |\..(.9..| */
|
|
||||||
0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |yb...a..| */
|
|
||||||
0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |I..?L.8.| */
|
|
||||||
0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |.U......| */
|
|
||||||
0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |\8M....W| */
|
|
||||||
0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |.Lp+k.._|*/
|
|
||||||
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
|
|
||||||
}
|
|
||||||
|
|
||||||
// regTestGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
|
||||||
// the regression test network as of protocol version 60002.
|
|
||||||
var regTestGenesisBlockBytes = []byte{
|
|
||||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |....;...| */
|
|
||||||
0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |z{..z.,>| */
|
|
||||||
0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |gv.a....| */
|
|
||||||
0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |..Q2:...| */
|
|
||||||
0x4b, 0x1e, 0x5e, 0x4a, 0xda, 0xe5, 0x49, 0x4d, /* |K.^J)._I| */
|
|
||||||
0xff, 0xff, 0x7f, 0x20, 0x02, 0x00, 0x00, 0x00, /* |......+|| */
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* |........| */
|
|
||||||
0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |..M.....| */
|
|
||||||
0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..EThe T| */
|
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |imes 03/| */
|
|
||||||
0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |Jan/2009| */
|
|
||||||
0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* | Chancel| */
|
|
||||||
0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* |lor on b| */
|
|
||||||
0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |rink of | */
|
|
||||||
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |second b| */
|
|
||||||
0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |ailout f| */
|
|
||||||
0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |or banks| */
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |........| */
|
|
||||||
0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |*....CA.| */
|
|
||||||
0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |g....UH'| */
|
|
||||||
0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |.g..q0..| */
|
|
||||||
0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |\..(.9..| */
|
|
||||||
0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |yb...a..| */
|
|
||||||
0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |I..?L.8.| */
|
|
||||||
0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |.U......| */
|
|
||||||
0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |\8M....W| */
|
|
||||||
0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |.Lp+k.._|*/
|
|
||||||
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
|
|
||||||
}
|
|
||||||
|
|
||||||
// testNet3GenesisBlockBytes are the wire encoded bytes for the genesis block of
|
|
||||||
// the test network (version 3) as of protocol version 60002.
|
|
||||||
var testNet3GenesisBlockBytes = []byte{
|
|
||||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |....;...| */
|
|
||||||
0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |z{..z.,>| */
|
|
||||||
0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |gv.a....| */
|
|
||||||
0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |..Q2:...| */
|
|
||||||
0x4b, 0x1e, 0x5e, 0x4a, 0xda, 0xe5, 0x49, 0x4d, /* |K.^J)._I| */
|
|
||||||
0xff, 0xff, 0x00, 0x1d, 0x1a, 0xa4, 0xae, 0x18, /* |......+|| */
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* |........| */
|
|
||||||
0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |..M.....| */
|
|
||||||
0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..EThe T| */
|
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |imes 03/| */
|
|
||||||
0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |Jan/2009| */
|
|
||||||
0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* | Chancel| */
|
|
||||||
0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* |lor on b| */
|
|
||||||
0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |rink of | */
|
|
||||||
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |second b| */
|
|
||||||
0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |ailout f| */
|
|
||||||
0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |or banks| */
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |........| */
|
|
||||||
0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |*....CA.| */
|
|
||||||
0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |g....UH'| */
|
|
||||||
0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |.g..q0..| */
|
|
||||||
0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |\..(.9..| */
|
|
||||||
0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |yb...a..| */
|
|
||||||
0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |I..?L.8.| */
|
|
||||||
0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |.U......| */
|
|
||||||
0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |\8M....W| */
|
|
||||||
0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |.Lp+k.._|*/
|
|
||||||
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
|
|
||||||
}
|
|
||||||
|
|
||||||
// simNetGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
|
||||||
// the simulation test network as of protocol version 70002.
|
|
||||||
var simNetGenesisBlockBytes = []byte{
|
|
||||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |....;...| */
|
|
||||||
0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |z{..z.,>| */
|
|
||||||
0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |gv.a....| */
|
|
||||||
0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |..Q2:...| */
|
|
||||||
0x4b, 0x1e, 0x5e, 0x4a, 0x45, 0x06, 0x86, 0x53, /* |K.^J)._I| */
|
|
||||||
0xff, 0xff, 0x7f, 0x20, 0x02, 0x00, 0x00, 0x00, /* |......+|| */
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* |........| */
|
|
||||||
0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |..M.....| */
|
|
||||||
0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..EThe T| */
|
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |imes 03/| */
|
|
||||||
0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |Jan/2009| */
|
|
||||||
0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* | Chancel| */
|
|
||||||
0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* |lor on b| */
|
|
||||||
0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |rink of | */
|
|
||||||
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |second b| */
|
|
||||||
0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |ailout f| */
|
|
||||||
0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |or banks| */
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |........| */
|
|
||||||
0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |*....CA.| */
|
|
||||||
0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |g....UH'| */
|
|
||||||
0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |.g..q0..| */
|
|
||||||
0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |\..(.9..| */
|
|
||||||
0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |yb...a..| */
|
|
||||||
0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |I..?L.8.| */
|
|
||||||
0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |.U......| */
|
|
||||||
0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |\8M....W| */
|
|
||||||
0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |.Lp+k.._|*/
|
|
||||||
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
|
|
||||||
}
|
|
||||||
|
|
||||||
// sigNetGenesisBlockBytes are the wire encoded bytes for the genesis block of
|
|
||||||
// the signet test network as of protocol version 70002.
|
|
||||||
var sigNetGenesisBlockBytes = []byte{
|
|
||||||
0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |...@....| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x3b, 0xa3, 0xed, 0xfd, /* |........| */
|
|
||||||
0x7a, 0x7b, 0x12, 0xb2, 0x7a, 0xc7, 0x2c, 0x3e, /* |....;...| */
|
|
||||||
0x67, 0x76, 0x8f, 0x61, 0x7f, 0xc8, 0x1b, 0xc3, /* |z{..z.,>| */
|
|
||||||
0x88, 0x8a, 0x51, 0x32, 0x3a, 0x9f, 0xb8, 0xaa, /* |gv.a....| */
|
|
||||||
0x4b, 0x1e, 0x5e, 0x4a, 0x00, 0x8f, 0x4d, 0x5f, /* |..Q2:...| */
|
|
||||||
0xae, 0x77, 0x03, 0x1e, 0x8a, 0xd2, 0x22, 0x03, /* |K.^J..M_| */
|
|
||||||
0x01, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, /* |.w....".| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, /* |........| */
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 0xff, /* |........| */
|
|
||||||
0xff, 0xff, 0x4d, 0x04, 0xff, 0xff, 0x00, 0x1d, /* |........| */
|
|
||||||
0x01, 0x04, 0x45, 0x54, 0x68, 0x65, 0x20, 0x54, /* |..M.....| */
|
|
||||||
0x69, 0x6d, 0x65, 0x73, 0x20, 0x30, 0x33, 0x2f, /* |..EThe T| */
|
|
||||||
0x4a, 0x61, 0x6e, 0x2f, 0x32, 0x30, 0x30, 0x39, /* |imes 03/| */
|
|
||||||
0x20, 0x43, 0x68, 0x61, 0x6e, 0x63, 0x65, 0x6c, /* |Jan/2009| */
|
|
||||||
0x6c, 0x6f, 0x72, 0x20, 0x6f, 0x6e, 0x20, 0x62, /* | Chancel| */
|
|
||||||
0x72, 0x69, 0x6e, 0x6b, 0x20, 0x6f, 0x66, 0x20, /* |lor on b| */
|
|
||||||
0x73, 0x65, 0x63, 0x6f, 0x6e, 0x64, 0x20, 0x62, /* |rink of| */
|
|
||||||
0x61, 0x69, 0x6c, 0x6f, 0x75, 0x74, 0x20, 0x66, /* |second b| */
|
|
||||||
0x6f, 0x72, 0x20, 0x62, 0x61, 0x6e, 0x6b, 0x73, /* |ailout f| */
|
|
||||||
0xff, 0xff, 0xff, 0xff, 0x01, 0x00, 0xf2, 0x05, /* |or banks| */
|
|
||||||
0x2a, 0x01, 0x00, 0x00, 0x00, 0x43, 0x41, 0x04, /* |........| */
|
|
||||||
0x67, 0x8a, 0xfd, 0xb0, 0xfe, 0x55, 0x48, 0x27, /* |*....CA.| */
|
|
||||||
0x19, 0x67, 0xf1, 0xa6, 0x71, 0x30, 0xb7, 0x10, /* |g....UH'| */
|
|
||||||
0x5c, 0xd6, 0xa8, 0x28, 0xe0, 0x39, 0x09, 0xa6, /* |.g..q0..| */
|
|
||||||
0x79, 0x62, 0xe0, 0xea, 0x1f, 0x61, 0xde, 0xb6, /* |\..(.9..| */
|
|
||||||
0x49, 0xf6, 0xbc, 0x3f, 0x4c, 0xef, 0x38, 0xc4, /* |yb...a..| */
|
|
||||||
0xf3, 0x55, 0x04, 0xe5, 0x1e, 0xc1, 0x12, 0xde, /* |I..?L.8.| */
|
|
||||||
0x5c, 0x38, 0x4d, 0xf7, 0xba, 0x0b, 0x8d, 0x57, /* |.U......| */
|
|
||||||
0x8a, 0x4c, 0x70, 0x2b, 0x6b, 0xf1, 0x1d, 0x5f, /* |\8M....W| */
|
|
||||||
0xac, 0x00, 0x00, 0x00, 0x00, /* |.....| */
|
|
||||||
}
|
|
|
@ -25,8 +25,8 @@ var (
|
||||||
bigOne = big.NewInt(1)
|
bigOne = big.NewInt(1)
|
||||||
|
|
||||||
// mainPowLimit is the highest proof of work value a Bitcoin block can
|
// mainPowLimit is the highest proof of work value a Bitcoin block can
|
||||||
// have for the main network. It is the value 2^224 - 1.
|
// have for the main network. It is the value 2^240 - 1.
|
||||||
mainPowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 224), bigOne)
|
mainPowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 240), bigOne)
|
||||||
|
|
||||||
// regressionPowLimit is the highest proof of work value a Bitcoin block
|
// regressionPowLimit is the highest proof of work value a Bitcoin block
|
||||||
// can have for the regression test network. It is the value 2^255 - 1.
|
// can have for the regression test network. It is the value 2^255 - 1.
|
||||||
|
@ -34,8 +34,8 @@ var (
|
||||||
|
|
||||||
// testNet3PowLimit is the highest proof of work value a Bitcoin block
|
// testNet3PowLimit is the highest proof of work value a Bitcoin block
|
||||||
// can have for the test network (version 3). It is the value
|
// can have for the test network (version 3). It is the value
|
||||||
// 2^224 - 1.
|
// 2^240 - 1.
|
||||||
testNet3PowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 224), bigOne)
|
testNet3PowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 240), bigOne)
|
||||||
|
|
||||||
// simNetPowLimit is the highest proof of work value a Bitcoin block
|
// simNetPowLimit is the highest proof of work value a Bitcoin block
|
||||||
// can have for the simulation test network. It is the value 2^255 - 1.
|
// can have for the simulation test network. It is the value 2^255 - 1.
|
||||||
|
@ -102,6 +102,9 @@ type ConsensusDeployment struct {
|
||||||
// ExpireTime is the median block time after which the attempted
|
// ExpireTime is the median block time after which the attempted
|
||||||
// deployment expires.
|
// deployment expires.
|
||||||
ExpireTime uint64
|
ExpireTime uint64
|
||||||
|
|
||||||
|
// ForceActiveAt is added by LBRY to bypass consensus. Features are activated via hard-fork instead.
|
||||||
|
ForceActiveAt int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Constants that define the deployment offset in the deployments field of the
|
// Constants that define the deployment offset in the deployments field of the
|
||||||
|
@ -237,11 +240,9 @@ type Params struct {
|
||||||
Bech32HRPSegwit string
|
Bech32HRPSegwit string
|
||||||
|
|
||||||
// Address encoding magics
|
// Address encoding magics
|
||||||
PubKeyHashAddrID byte // First byte of a P2PKH address
|
PubKeyHashAddrID byte // First byte of a P2PKH address
|
||||||
ScriptHashAddrID byte // First byte of a P2SH address
|
ScriptHashAddrID byte // First byte of a P2SH address
|
||||||
PrivateKeyID byte // First byte of a WIF private key
|
PrivateKeyID byte // First byte of a WIF private key
|
||||||
WitnessPubKeyHashAddrID byte // First byte of a P2WPKH address
|
|
||||||
WitnessScriptHashAddrID byte // First byte of a P2WSH address
|
|
||||||
|
|
||||||
// BIP32 hierarchical deterministic extended key magics
|
// BIP32 hierarchical deterministic extended key magics
|
||||||
HDPrivateKeyID [4]byte
|
HDPrivateKeyID [4]byte
|
||||||
|
@ -256,60 +257,58 @@ type Params struct {
|
||||||
var MainNetParams = Params{
|
var MainNetParams = Params{
|
||||||
Name: "mainnet",
|
Name: "mainnet",
|
||||||
Net: wire.MainNet,
|
Net: wire.MainNet,
|
||||||
DefaultPort: "8333",
|
DefaultPort: "9246",
|
||||||
DNSSeeds: []DNSSeed{
|
DNSSeeds: []DNSSeed{
|
||||||
{"seed.bitcoin.sipa.be", true},
|
{"dnsseed1.lbry.com", true},
|
||||||
{"dnsseed.bluematt.me", true},
|
{"dnsseed2.lbry.com", true},
|
||||||
{"dnsseed.bitcoin.dashjr.org", false},
|
{"dnsseed3.lbry.com", true},
|
||||||
{"seed.bitcoinstats.com", true},
|
{"seed.lbry.grin.io", true},
|
||||||
{"seed.bitnodes.io", false},
|
{"seed.allaboutlbc.com", true},
|
||||||
{"seed.bitcoin.jonasschnelli.ch", true},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Chain parameters
|
// Chain parameters
|
||||||
GenesisBlock: &genesisBlock,
|
GenesisBlock: &genesisBlock,
|
||||||
GenesisHash: &genesisHash,
|
GenesisHash: &genesisHash,
|
||||||
PowLimit: mainPowLimit,
|
PowLimit: mainPowLimit,
|
||||||
PowLimitBits: 0x1d00ffff,
|
PowLimitBits: 0x1f00ffff,
|
||||||
BIP0034Height: 227931, // 000000000000024b89b42a942fe0d9fea3bb44ab7bd1b19115dd6a759c0808b8
|
BIP0034Height: 1,
|
||||||
BIP0065Height: 388381, // 000000000000000004c2b624ed5d7756c508d90fd0da2c7c679febfa6c4735f0
|
BIP0065Height: 200000,
|
||||||
BIP0066Height: 363725, // 00000000000000000379eaa19dce8c9b722d46ae6a57c2f1a988119488b50931
|
BIP0066Height: 200000,
|
||||||
CoinbaseMaturity: 100,
|
CoinbaseMaturity: 100,
|
||||||
SubsidyReductionInterval: 210000,
|
SubsidyReductionInterval: 1 << 5,
|
||||||
TargetTimespan: time.Hour * 24 * 14, // 14 days
|
TargetTimespan: time.Second * 150, // retarget every block
|
||||||
TargetTimePerBlock: time.Minute * 10, // 10 minutes
|
TargetTimePerBlock: time.Second * 150, // 150 seconds
|
||||||
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
||||||
ReduceMinDifficulty: false,
|
ReduceMinDifficulty: false,
|
||||||
MinDiffReductionTime: 0,
|
MinDiffReductionTime: 0,
|
||||||
GenerateSupported: false,
|
GenerateSupported: false,
|
||||||
|
|
||||||
// Checkpoints ordered from oldest to newest.
|
// Checkpoints ordered from oldest to newest.
|
||||||
Checkpoints: []Checkpoint{
|
Checkpoints: []Checkpoint{
|
||||||
{11111, newHashFromStr("0000000069e244f73d78e8fd29ba2fd2ed618bd6fa2ee92559f542fdb26e7c1d")},
|
{40000, newHashFromStr("4c55584b068108b15c0066a010d11971aa92f46b0a73d479f1b7fa57df8b05f4")},
|
||||||
{33333, newHashFromStr("000000002dd5588a74784eaa7ab0507a18ad16a236e7b1ce69f00d7ddfb5d0a6")},
|
{80000, newHashFromStr("6e9facdfb87ba8394a46c61a7c093f7f00b1397a2dabc6a04f2911e0efdcf50a")},
|
||||||
{74000, newHashFromStr("0000000000573993a3c9e41ce34471c079dcf5f52a0e824a81e7f953b8661a20")},
|
{120000, newHashFromStr("6a9dba420ec544b927769765dccec8b29e214e6ca9f82b54a52bf20ca517b75a")},
|
||||||
{105000, newHashFromStr("00000000000291ce28027faea320c8d2b054b2e0fe44a773f3eefb151d6bdc97")},
|
{160000, newHashFromStr("87b2913a509d857401f7587903c90214db7847af1a1ad63a3b6f245936e3ae9d")},
|
||||||
{134444, newHashFromStr("00000000000005b12ffd4cd315cd34ffd4a594f430ac814c91184a0d42d2b0fe")},
|
{200000, newHashFromStr("0fe8ed6019a83028006435e47be4e37a0d3ed48019cde1dc7ede6562e5829839")},
|
||||||
{168000, newHashFromStr("000000000000099e61ea72015e79632f216fe6cb33d7899acb35b75c8303b763")},
|
{240000, newHashFromStr("cb3c2342afbe7291012f2288403a9d105f46987f78b279d516db2deb4d35b0b7")},
|
||||||
{193000, newHashFromStr("000000000000059f452a5f7340de6682a977387c17010ff6e6c3bd83ca8b1317")},
|
{280000, newHashFromStr("9835d03eb527ea4ce45c217350c68042926d497c21fb31413b2f7824ff6fc6c3")},
|
||||||
{210000, newHashFromStr("000000000000048b95347e83192f69cf0366076336c639f9b7228e9ba171342e")},
|
{320000, newHashFromStr("ad80c7cb91ca1d9c9b7bf68ca1b6d4ba217fe25ca5ded6a7e8acbaba663b143f")},
|
||||||
{216116, newHashFromStr("00000000000001b4f4b433e81ee46494af945cf96014816a4e2370f11b23df4e")},
|
{360000, newHashFromStr("f9fd013252439663c1e729a8afb27187a8b9cc63a253336060f867e3cfbe4dcb")},
|
||||||
{225430, newHashFromStr("00000000000001c108384350f74090433e7fcf79a606b8e797f065b130575932")},
|
{400000, newHashFromStr("f0e56e70782af63ccb49c76e852540688755869ba59ec68cac9c04a6b4d9f5ca")},
|
||||||
{250000, newHashFromStr("000000000000003887df1f29024b06fc2200b55f8af8f35453d7be294df2d214")},
|
{440000, newHashFromStr("52760e00c369b40781a2ced32836711fab82a720fafb121118c815bb46afd996")},
|
||||||
{267300, newHashFromStr("000000000000000a83fbd660e918f218bf37edd92b748ad940483c7c116179ac")},
|
{480000, newHashFromStr("cecacaf4d1a8d1ef60da39343540781115abb91f5f0c976bb08afc4d4e3218ac")},
|
||||||
{279000, newHashFromStr("0000000000000001ae8c72a0b0c301f67e3afca10e819efa9041e458e9bd7e40")},
|
{520000, newHashFromStr("fa5e9d6dcf9ad57ba60d8ba26fb05585741098d10f42ed9d5e6b5e90ebc278d6")},
|
||||||
{300255, newHashFromStr("0000000000000000162804527c6e9b9f0563a280525f9d08c12041def0a0f3b2")},
|
{560000, newHashFromStr("95c6229bd9b40f03a8426b2fec740026b3f06b1628cfb87527b0cbd0da328c0c")},
|
||||||
{319400, newHashFromStr("000000000000000021c6052e9becade189495d1c539aa37c58917305fd15f13b")},
|
{600000, newHashFromStr("532657a97d480feb2d0423bb736cbfd7400b3ac8311e81ac749a2f29103a6c6b")},
|
||||||
{343185, newHashFromStr("0000000000000000072b8bf361d01a6ba7d445dd024203fafc78768ed4368554")},
|
{640000, newHashFromStr("68b69e3e8765e1ddbac63cbfbbf12e1a920da994d242a26fd07624f067743080")},
|
||||||
{352940, newHashFromStr("000000000000000010755df42dba556bb72be6a32f3ce0b6941ce4430152c9ff")},
|
{680000, newHashFromStr("7b9f30c959405b5b96d0b0c2ba8fc7c5586cd0ce40df51427de4b8a217859c45")},
|
||||||
{382320, newHashFromStr("00000000000000000a8dc6ed5b133d0eb2fd6af56203e4159789b092defd8ab2")},
|
{720000, newHashFromStr("42084d5f88c71c0ae09b8677070969df9c3ef875c5f434133f552d863204f0cb")},
|
||||||
{400000, newHashFromStr("000000000000000004ec466ce4732fe6f1ed1cddc2ed4b328fff5224276e3f6f")},
|
{760000, newHashFromStr("1887cd8b50375a9ac0dc9686c98fa8ac69bca618eab6254310647057f6fe4fc9")},
|
||||||
{430000, newHashFromStr("000000000000000001868b2bb3a285f3cc6b33ea234eb70facf4dcdf22186b87")},
|
{800000, newHashFromStr("d34bb871b21e6fda4bd9d9e530ebf12e044814004007f088415035c651ecf322")},
|
||||||
{460000, newHashFromStr("000000000000000000ef751bbce8e744ad303c47ece06c8d863e4d417efc258c")},
|
{840000, newHashFromStr("d0e73c5ce3ad5d6fdb4483aa450f0b1cf7e4570987ee3a3806ace4ad2f7cc9af")},
|
||||||
{490000, newHashFromStr("000000000000000000de069137b17b8d5a3dfbd5b145b2dcfb203f15d0c4de90")},
|
{880000, newHashFromStr("806a95f26bab603f1d9132b5d4ea72aab9d1198ad55ae18dac1e149f6cb70ce4")},
|
||||||
{520000, newHashFromStr("0000000000000000000d26984c0229c9f6962dc74db0a6d525f2f1640396f69c")},
|
{920000, newHashFromStr("83bc84555105436c51728ab200e8da4d9b3a365fd3d1d47a60048ad0f977c55b")},
|
||||||
{550000, newHashFromStr("000000000000000000223b7a2298fb1c6c75fb0efc28a4c56853ff4112ec6bc9")},
|
{960000, newHashFromStr("60e37b1c2d1f8771290b7f84865cbadf22b5b89d3ce1201d454b09f0775b42c2")},
|
||||||
{560000, newHashFromStr("0000000000000000002c7b276daf6efb2b6aa68e2ce3be67ef925b3264ae7122")},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Consensus rule change deployments.
|
// Consensus rule change deployments.
|
||||||
|
@ -325,14 +324,16 @@ var MainNetParams = Params{
|
||||||
ExpireTime: 1230767999, // December 31, 2008 UTC
|
ExpireTime: 1230767999, // December 31, 2008 UTC
|
||||||
},
|
},
|
||||||
DeploymentCSV: {
|
DeploymentCSV: {
|
||||||
BitNumber: 0,
|
BitNumber: 0,
|
||||||
StartTime: 1462060800, // May 1st, 2016
|
StartTime: 1462060800, // May 1st, 2016
|
||||||
ExpireTime: 1493596800, // May 1st, 2017
|
ExpireTime: 1493596800, // May 1st, 2017
|
||||||
|
ForceActiveAt: 200000,
|
||||||
},
|
},
|
||||||
DeploymentSegwit: {
|
DeploymentSegwit: {
|
||||||
BitNumber: 1,
|
BitNumber: 1,
|
||||||
StartTime: 1479168000, // November 15, 2016 UTC
|
StartTime: 1547942400, // Jan 20, 2019
|
||||||
ExpireTime: 1510704000, // November 15, 2017 UTC.
|
ExpireTime: 1548288000, // Jan 24, 2019
|
||||||
|
ForceActiveAt: 680770,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -341,18 +342,16 @@ var MainNetParams = Params{
|
||||||
|
|
||||||
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
||||||
// BIP 173.
|
// BIP 173.
|
||||||
Bech32HRPSegwit: "bc", // always bc for main net
|
Bech32HRPSegwit: "lbc",
|
||||||
|
|
||||||
// Address encoding magics
|
// Address encoding magics
|
||||||
PubKeyHashAddrID: 0x00, // starts with 1
|
PubKeyHashAddrID: 0x55,
|
||||||
ScriptHashAddrID: 0x05, // starts with 3
|
ScriptHashAddrID: 0x7a,
|
||||||
PrivateKeyID: 0x80, // starts with 5 (uncompressed) or K (compressed)
|
PrivateKeyID: 0x1c,
|
||||||
WitnessPubKeyHashAddrID: 0x06, // starts with p2
|
|
||||||
WitnessScriptHashAddrID: 0x0A, // starts with 7Xh
|
|
||||||
|
|
||||||
// BIP32 hierarchical deterministic extended key magics
|
// BIP32 hierarchical deterministic extended key magics
|
||||||
HDPrivateKeyID: [4]byte{0x04, 0x88, 0xad, 0xe4}, // starts with xprv
|
HDPrivateKeyID: [4]byte{0x04, 0x88, 0xad, 0xe4},
|
||||||
HDPublicKeyID: [4]byte{0x04, 0x88, 0xb2, 0x1e}, // starts with xpub
|
HDPublicKeyID: [4]byte{0x04, 0x88, 0xb2, 0x1e},
|
||||||
|
|
||||||
// BIP44 coin type used in the hierarchical deterministic path for
|
// BIP44 coin type used in the hierarchical deterministic path for
|
||||||
// address generation.
|
// address generation.
|
||||||
|
@ -365,7 +364,7 @@ var MainNetParams = Params{
|
||||||
var RegressionNetParams = Params{
|
var RegressionNetParams = Params{
|
||||||
Name: "regtest",
|
Name: "regtest",
|
||||||
Net: wire.TestNet,
|
Net: wire.TestNet,
|
||||||
DefaultPort: "18444",
|
DefaultPort: "29246",
|
||||||
DNSSeeds: []DNSSeed{},
|
DNSSeeds: []DNSSeed{},
|
||||||
|
|
||||||
// Chain parameters
|
// Chain parameters
|
||||||
|
@ -374,15 +373,15 @@ var RegressionNetParams = Params{
|
||||||
PowLimit: regressionPowLimit,
|
PowLimit: regressionPowLimit,
|
||||||
PowLimitBits: 0x207fffff,
|
PowLimitBits: 0x207fffff,
|
||||||
CoinbaseMaturity: 100,
|
CoinbaseMaturity: 100,
|
||||||
BIP0034Height: 100000000, // Not active - Permit ver 1 blocks
|
BIP0034Height: 1000,
|
||||||
BIP0065Height: 1351, // Used by regression tests
|
BIP0065Height: 1351, // Used by regression tests
|
||||||
BIP0066Height: 1251, // Used by regression tests
|
BIP0066Height: 1251, // Used by regression tests
|
||||||
SubsidyReductionInterval: 150,
|
SubsidyReductionInterval: 1 << 5,
|
||||||
TargetTimespan: time.Hour * 24 * 14, // 14 days
|
TargetTimespan: time.Second,
|
||||||
TargetTimePerBlock: time.Minute * 10, // 10 minutes
|
TargetTimePerBlock: time.Second,
|
||||||
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
||||||
ReduceMinDifficulty: true,
|
ReduceMinDifficulty: false,
|
||||||
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
|
MinDiffReductionTime: 0,
|
||||||
GenerateSupported: true,
|
GenerateSupported: true,
|
||||||
|
|
||||||
// Checkpoints ordered from oldest to newest.
|
// Checkpoints ordered from oldest to newest.
|
||||||
|
@ -401,14 +400,16 @@ var RegressionNetParams = Params{
|
||||||
ExpireTime: math.MaxInt64, // Never expires
|
ExpireTime: math.MaxInt64, // Never expires
|
||||||
},
|
},
|
||||||
DeploymentCSV: {
|
DeploymentCSV: {
|
||||||
BitNumber: 0,
|
BitNumber: 0,
|
||||||
StartTime: 0, // Always available for vote
|
StartTime: 0, // Always available for vote
|
||||||
ExpireTime: math.MaxInt64, // Never expires
|
ExpireTime: math.MaxInt64, // Never expires
|
||||||
|
ForceActiveAt: 1,
|
||||||
},
|
},
|
||||||
DeploymentSegwit: {
|
DeploymentSegwit: {
|
||||||
BitNumber: 1,
|
BitNumber: 1,
|
||||||
StartTime: 0, // Always available for vote
|
StartTime: 0,
|
||||||
ExpireTime: math.MaxInt64, // Never expires.
|
ExpireTime: math.MaxInt64,
|
||||||
|
ForceActiveAt: 150,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -417,12 +418,12 @@ var RegressionNetParams = Params{
|
||||||
|
|
||||||
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
||||||
// BIP 173.
|
// BIP 173.
|
||||||
Bech32HRPSegwit: "bcrt", // always bcrt for reg test net
|
Bech32HRPSegwit: "rlbc",
|
||||||
|
|
||||||
// Address encoding magics
|
// Address encoding magics
|
||||||
PubKeyHashAddrID: 0x6f, // starts with m or n
|
PubKeyHashAddrID: 111, // starts with m or n
|
||||||
ScriptHashAddrID: 0xc4, // starts with 2
|
ScriptHashAddrID: 196, // starts with 2
|
||||||
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
|
PrivateKeyID: 239, // starts with 9 (uncompressed) or c (compressed)
|
||||||
|
|
||||||
// BIP32 hierarchical deterministic extended key magics
|
// BIP32 hierarchical deterministic extended key magics
|
||||||
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
||||||
|
@ -439,48 +440,31 @@ var RegressionNetParams = Params{
|
||||||
var TestNet3Params = Params{
|
var TestNet3Params = Params{
|
||||||
Name: "testnet3",
|
Name: "testnet3",
|
||||||
Net: wire.TestNet3,
|
Net: wire.TestNet3,
|
||||||
DefaultPort: "18333",
|
DefaultPort: "19246",
|
||||||
DNSSeeds: []DNSSeed{
|
DNSSeeds: []DNSSeed{
|
||||||
{"testnet-seed.bitcoin.jonasschnelli.ch", true},
|
{"testdnsseed1.lbry.com", true},
|
||||||
{"testnet-seed.bitcoin.schildbach.de", false},
|
{"testdnsseed2.lbry.com", true},
|
||||||
{"seed.tbtc.petertodd.org", true},
|
|
||||||
{"testnet-seed.bluematt.me", false},
|
|
||||||
},
|
},
|
||||||
|
|
||||||
// Chain parameters
|
// Chain parameters
|
||||||
GenesisBlock: &testNet3GenesisBlock,
|
GenesisBlock: &testNet3GenesisBlock,
|
||||||
GenesisHash: &testNet3GenesisHash,
|
GenesisHash: &testNet3GenesisHash,
|
||||||
PowLimit: testNet3PowLimit,
|
PowLimit: testNet3PowLimit,
|
||||||
PowLimitBits: 0x1d00ffff,
|
PowLimitBits: 0x1f00ffff,
|
||||||
BIP0034Height: 21111, // 0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
|
BIP0034Height: 21111, // 0x0000000023b3a96d3484e5abb3755c413e7d41500f8e2a5c3f0dd01299cd8ef8
|
||||||
BIP0065Height: 581885, // 00000000007f6655f22f98e72ed80d8b06dc761d5da09df0fa1dc4be4f861eb6
|
BIP0065Height: 1200000,
|
||||||
BIP0066Height: 330776, // 000000002104c8c45e99a8853285a3b592602a3ccde2b832481da85e9e4ba182
|
BIP0066Height: 1200000,
|
||||||
CoinbaseMaturity: 100,
|
CoinbaseMaturity: 100,
|
||||||
SubsidyReductionInterval: 210000,
|
SubsidyReductionInterval: 1 << 5,
|
||||||
TargetTimespan: time.Hour * 24 * 14, // 14 days
|
TargetTimespan: time.Second * 150, // retarget every block
|
||||||
TargetTimePerBlock: time.Minute * 10, // 10 minutes
|
TargetTimePerBlock: time.Second * 150, // 150 seconds
|
||||||
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
||||||
ReduceMinDifficulty: true,
|
ReduceMinDifficulty: false,
|
||||||
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
|
MinDiffReductionTime: 0,
|
||||||
GenerateSupported: false,
|
GenerateSupported: true,
|
||||||
|
|
||||||
// Checkpoints ordered from oldest to newest.
|
// Checkpoints ordered from oldest to newest.
|
||||||
Checkpoints: []Checkpoint{
|
Checkpoints: []Checkpoint{},
|
||||||
{546, newHashFromStr("000000002a936ca763904c3c35fce2f3556c559c0214345d31b1bcebf76acb70")},
|
|
||||||
{100000, newHashFromStr("00000000009e2958c15ff9290d571bf9459e93b19765c6801ddeccadbb160a1e")},
|
|
||||||
{200000, newHashFromStr("0000000000287bffd321963ef05feab753ebe274e1d78b2fd4e2bfe9ad3aa6f2")},
|
|
||||||
{300001, newHashFromStr("0000000000004829474748f3d1bc8fcf893c88be255e6d7f571c548aff57abf4")},
|
|
||||||
{400002, newHashFromStr("0000000005e2c73b8ecb82ae2dbc2e8274614ebad7172b53528aba7501f5a089")},
|
|
||||||
{500011, newHashFromStr("00000000000929f63977fbac92ff570a9bd9e7715401ee96f2848f7b07750b02")},
|
|
||||||
{600002, newHashFromStr("000000000001f471389afd6ee94dcace5ccc44adc18e8bff402443f034b07240")},
|
|
||||||
{700000, newHashFromStr("000000000000406178b12a4dea3b27e13b3c4fe4510994fd667d7c1e6a3f4dc1")},
|
|
||||||
{800010, newHashFromStr("000000000017ed35296433190b6829db01e657d80631d43f5983fa403bfdb4c1")},
|
|
||||||
{900000, newHashFromStr("0000000000356f8d8924556e765b7a94aaebc6b5c8685dcfa2b1ee8b41acd89b")},
|
|
||||||
{1000007, newHashFromStr("00000000001ccb893d8a1f25b70ad173ce955e5f50124261bbbc50379a612ddf")},
|
|
||||||
{1100007, newHashFromStr("00000000000abc7b2cd18768ab3dee20857326a818d1946ed6796f42d66dd1e8")},
|
|
||||||
{1200007, newHashFromStr("00000000000004f2dc41845771909db57e04191714ed8c963f7e56713a7b6cea")},
|
|
||||||
{1300007, newHashFromStr("0000000072eab69d54df75107c052b26b0395b44f77578184293bf1bb1dbd9fa")},
|
|
||||||
},
|
|
||||||
|
|
||||||
// Consensus rule change deployments.
|
// Consensus rule change deployments.
|
||||||
//
|
//
|
||||||
|
@ -500,9 +484,10 @@ var TestNet3Params = Params{
|
||||||
ExpireTime: 1493596800, // May 1st, 2017
|
ExpireTime: 1493596800, // May 1st, 2017
|
||||||
},
|
},
|
||||||
DeploymentSegwit: {
|
DeploymentSegwit: {
|
||||||
BitNumber: 1,
|
BitNumber: 1,
|
||||||
StartTime: 1462060800, // May 1, 2016 UTC
|
StartTime: 1462060800, // May 1st 2016
|
||||||
ExpireTime: 1493596800, // May 1, 2017 UTC.
|
ExpireTime: 1493596800, // May 1st 2017
|
||||||
|
ForceActiveAt: 1198600,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -511,14 +496,12 @@ var TestNet3Params = Params{
|
||||||
|
|
||||||
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
||||||
// BIP 173.
|
// BIP 173.
|
||||||
Bech32HRPSegwit: "tb", // always tb for test net
|
Bech32HRPSegwit: "tlbc",
|
||||||
|
|
||||||
// Address encoding magics
|
// Address encoding magics
|
||||||
PubKeyHashAddrID: 0x6f, // starts with m or n
|
PubKeyHashAddrID: 111,
|
||||||
ScriptHashAddrID: 0xc4, // starts with 2
|
ScriptHashAddrID: 196,
|
||||||
WitnessPubKeyHashAddrID: 0x03, // starts with QW
|
PrivateKeyID: 239,
|
||||||
WitnessScriptHashAddrID: 0x28, // starts with T7n
|
|
||||||
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
|
|
||||||
|
|
||||||
// BIP32 hierarchical deterministic extended key magics
|
// BIP32 hierarchical deterministic extended key magics
|
||||||
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
||||||
|
@ -552,11 +535,11 @@ var SimNetParams = Params{
|
||||||
BIP0066Height: 0, // Always active on simnet
|
BIP0066Height: 0, // Always active on simnet
|
||||||
CoinbaseMaturity: 100,
|
CoinbaseMaturity: 100,
|
||||||
SubsidyReductionInterval: 210000,
|
SubsidyReductionInterval: 210000,
|
||||||
TargetTimespan: time.Hour * 24 * 14, // 14 days
|
TargetTimespan: time.Second * 150,
|
||||||
TargetTimePerBlock: time.Minute * 10, // 10 minutes
|
TargetTimePerBlock: time.Second * 150,
|
||||||
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
RetargetAdjustmentFactor: 4, // 25% less, 400% more
|
||||||
ReduceMinDifficulty: true,
|
ReduceMinDifficulty: true,
|
||||||
MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2
|
MinDiffReductionTime: 0,
|
||||||
GenerateSupported: true,
|
GenerateSupported: true,
|
||||||
|
|
||||||
// Checkpoints ordered from oldest to newest.
|
// Checkpoints ordered from oldest to newest.
|
||||||
|
@ -581,8 +564,8 @@ var SimNetParams = Params{
|
||||||
},
|
},
|
||||||
DeploymentSegwit: {
|
DeploymentSegwit: {
|
||||||
BitNumber: 1,
|
BitNumber: 1,
|
||||||
StartTime: 0, // Always available for vote
|
StartTime: 0,
|
||||||
ExpireTime: math.MaxInt64, // Never expires.
|
ExpireTime: math.MaxInt64,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -591,14 +574,12 @@ var SimNetParams = Params{
|
||||||
|
|
||||||
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
||||||
// BIP 173.
|
// BIP 173.
|
||||||
Bech32HRPSegwit: "sb", // always sb for sim net
|
Bech32HRPSegwit: "slbc",
|
||||||
|
|
||||||
// Address encoding magics
|
// Address encoding magics
|
||||||
PubKeyHashAddrID: 0x3f, // starts with S
|
PubKeyHashAddrID: 111,
|
||||||
ScriptHashAddrID: 0x7b, // starts with s
|
ScriptHashAddrID: 196,
|
||||||
PrivateKeyID: 0x64, // starts with 4 (uncompressed) or F (compressed)
|
PrivateKeyID: 239,
|
||||||
WitnessPubKeyHashAddrID: 0x19, // starts with Gg
|
|
||||||
WitnessScriptHashAddrID: 0x28, // starts with ?
|
|
||||||
|
|
||||||
// BIP32 hierarchical deterministic extended key magics
|
// BIP32 hierarchical deterministic extended key magics
|
||||||
HDPrivateKeyID: [4]byte{0x04, 0x20, 0xb9, 0x00}, // starts with sprv
|
HDPrivateKeyID: [4]byte{0x04, 0x20, 0xb9, 0x00}, // starts with sprv
|
||||||
|
@ -691,14 +672,12 @@ func CustomSignetParams(challenge []byte, dnsSeeds []DNSSeed) Params {
|
||||||
|
|
||||||
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
// Human-readable part for Bech32 encoded segwit addresses, as defined in
|
||||||
// BIP 173.
|
// BIP 173.
|
||||||
Bech32HRPSegwit: "tb", // always tb for test net
|
Bech32HRPSegwit: "slbc",
|
||||||
|
|
||||||
// Address encoding magics
|
// Address encoding magics
|
||||||
PubKeyHashAddrID: 0x6f, // starts with m or n
|
PubKeyHashAddrID: 0x6f, // starts with m or n
|
||||||
ScriptHashAddrID: 0xc4, // starts with 2
|
ScriptHashAddrID: 0xc4, // starts with 2
|
||||||
WitnessPubKeyHashAddrID: 0x03, // starts with QW
|
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
|
||||||
WitnessScriptHashAddrID: 0x28, // starts with T7n
|
|
||||||
PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed)
|
|
||||||
|
|
||||||
// BIP32 hierarchical deterministic extended key magics
|
// BIP32 hierarchical deterministic extended key magics
|
||||||
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv
|
||||||
|
|
76
claimtrie/block/blockrepo/pebble.go
Normal file
76
claimtrie/block/blockrepo/pebble.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package blockrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pebble struct {
|
||||||
|
db *pebble.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPebble(path string) (*Pebble, error) {
|
||||||
|
|
||||||
|
db, err := pebble.Open(path, nil)
|
||||||
|
repo := &Pebble{db: db}
|
||||||
|
|
||||||
|
return repo, errors.Wrapf(err, "unable to open %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Load() (int32, error) {
|
||||||
|
|
||||||
|
iter := repo.db.NewIter(nil)
|
||||||
|
if !iter.Last() {
|
||||||
|
err := iter.Close()
|
||||||
|
return 0, errors.Wrap(err, "closing iterator with no last")
|
||||||
|
}
|
||||||
|
|
||||||
|
height := int32(binary.BigEndian.Uint32(iter.Key()))
|
||||||
|
err := iter.Close()
|
||||||
|
return height, errors.Wrap(err, "closing iterator")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Get(height int32) (*chainhash.Hash, error) {
|
||||||
|
|
||||||
|
key := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(key, uint32(height))
|
||||||
|
|
||||||
|
b, closer, err := repo.db.Get(key)
|
||||||
|
if closer != nil {
|
||||||
|
defer closer.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "in get")
|
||||||
|
}
|
||||||
|
hash, err := chainhash.NewHash(b)
|
||||||
|
return hash, errors.Wrap(err, "creating hash")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Set(height int32, hash *chainhash.Hash) error {
|
||||||
|
|
||||||
|
key := make([]byte, 4)
|
||||||
|
binary.BigEndian.PutUint32(key, uint32(height))
|
||||||
|
|
||||||
|
return errors.WithStack(repo.db.Set(key, hash[:], pebble.NoSync))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Close() error {
|
||||||
|
|
||||||
|
err := repo.db.Flush()
|
||||||
|
if err != nil {
|
||||||
|
// if we fail to close are we going to try again later?
|
||||||
|
return errors.Wrap(err, "on flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.db.Close()
|
||||||
|
return errors.Wrap(err, "on close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Flush() error {
|
||||||
|
_, err := repo.db.AsyncFlush()
|
||||||
|
return err
|
||||||
|
}
|
14
claimtrie/block/repo.go
Normal file
14
claimtrie/block/repo.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package block
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repo defines APIs for Block to access persistence layer.
|
||||||
|
type Repo interface {
|
||||||
|
Load() (int32, error)
|
||||||
|
Set(height int32, hash *chainhash.Hash) error
|
||||||
|
Get(height int32) (*chainhash.Hash, error)
|
||||||
|
Close() error
|
||||||
|
Flush() error
|
||||||
|
}
|
77
claimtrie/chain/chainrepo/pebble.go
Normal file
77
claimtrie/chain/chainrepo/pebble.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package chainrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/vmihailenco/msgpack/v5"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pebble struct {
|
||||||
|
db *pebble.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPebble(path string) (*Pebble, error) {
|
||||||
|
|
||||||
|
db, err := pebble.Open(path, &pebble.Options{BytesPerSync: 64 << 20})
|
||||||
|
repo := &Pebble{db: db}
|
||||||
|
|
||||||
|
return repo, errors.Wrapf(err, "open %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Save(height int32, changes []change.Change) error {
|
||||||
|
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var key [4]byte
|
||||||
|
binary.BigEndian.PutUint32(key[:], uint32(height))
|
||||||
|
|
||||||
|
value, err := msgpack.Marshal(changes)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "in marshaller")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.db.Set(key[:], value, pebble.NoSync)
|
||||||
|
return errors.Wrap(err, "in set")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Load(height int32) ([]change.Change, error) {
|
||||||
|
|
||||||
|
var key [4]byte
|
||||||
|
binary.BigEndian.PutUint32(key[:], uint32(height))
|
||||||
|
|
||||||
|
b, closer, err := repo.db.Get(key[:])
|
||||||
|
if closer != nil {
|
||||||
|
defer closer.Close()
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "in get")
|
||||||
|
}
|
||||||
|
|
||||||
|
var changes []change.Change
|
||||||
|
err = msgpack.Unmarshal(b, &changes)
|
||||||
|
return changes, errors.Wrap(err, "in unmarshaller")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Close() error {
|
||||||
|
|
||||||
|
err := repo.db.Flush()
|
||||||
|
if err != nil {
|
||||||
|
// if we fail to close are we going to try again later?
|
||||||
|
return errors.Wrap(err, "on flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.db.Close()
|
||||||
|
return errors.Wrap(err, "on close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Flush() error {
|
||||||
|
_, err := repo.db.AsyncFlush()
|
||||||
|
return err
|
||||||
|
}
|
10
claimtrie/chain/repo.go
Normal file
10
claimtrie/chain/repo.go
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
package chain
|
||||||
|
|
||||||
|
import "github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
|
||||||
|
type Repo interface {
|
||||||
|
Save(height int32, changes []change.Change) error
|
||||||
|
Load(height int32) ([]change.Change, error)
|
||||||
|
Close() error
|
||||||
|
Flush() error
|
||||||
|
}
|
112
claimtrie/change/change.go
Normal file
112
claimtrie/change/change.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package change
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ChangeType uint32
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddClaim ChangeType = iota
|
||||||
|
SpendClaim
|
||||||
|
UpdateClaim
|
||||||
|
AddSupport
|
||||||
|
SpendSupport
|
||||||
|
)
|
||||||
|
|
||||||
|
type Change struct {
|
||||||
|
Type ChangeType
|
||||||
|
Height int32
|
||||||
|
|
||||||
|
Name []byte `msg:"-"`
|
||||||
|
ClaimID ClaimID
|
||||||
|
OutPoint wire.OutPoint
|
||||||
|
Amount int64
|
||||||
|
|
||||||
|
ActiveHeight int32 // for normalization fork
|
||||||
|
VisibleHeight int32
|
||||||
|
|
||||||
|
SpentChildren map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChange(typ ChangeType) Change {
|
||||||
|
return Change{Type: typ}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Change) SetHeight(height int32) Change {
|
||||||
|
c.Height = height
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Change) SetName(name []byte) Change {
|
||||||
|
c.Name = name // need to clone it?
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Change) SetOutPoint(op *wire.OutPoint) Change {
|
||||||
|
c.OutPoint = *op
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c Change) SetAmount(amt int64) Change {
|
||||||
|
c.Amount = amt
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Change) MarshalTo(enc *bytes.Buffer) error {
|
||||||
|
enc.Write(c.ClaimID[:])
|
||||||
|
enc.Write(c.OutPoint.Hash[:])
|
||||||
|
var temp [8]byte
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], c.OutPoint.Index)
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], uint32(c.Type))
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], uint32(c.Height))
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], uint32(c.ActiveHeight))
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], uint32(c.VisibleHeight))
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
binary.BigEndian.PutUint64(temp[:], uint64(c.Amount))
|
||||||
|
enc.Write(temp[:])
|
||||||
|
|
||||||
|
if c.SpentChildren != nil {
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], uint32(len(c.SpentChildren)))
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
for key := range c.SpentChildren {
|
||||||
|
binary.BigEndian.PutUint16(temp[:2], uint16(len(key))) // technically limited to 255; not sure we trust it
|
||||||
|
enc.Write(temp[:2])
|
||||||
|
enc.WriteString(key)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binary.BigEndian.PutUint32(temp[:4], 0)
|
||||||
|
enc.Write(temp[:4])
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Change) UnmarshalFrom(dec *bytes.Buffer) error {
|
||||||
|
copy(c.ClaimID[:], dec.Next(ClaimIDSize))
|
||||||
|
copy(c.OutPoint.Hash[:], dec.Next(chainhash.HashSize))
|
||||||
|
c.OutPoint.Index = binary.BigEndian.Uint32(dec.Next(4))
|
||||||
|
c.Type = ChangeType(binary.BigEndian.Uint32(dec.Next(4)))
|
||||||
|
c.Height = int32(binary.BigEndian.Uint32(dec.Next(4)))
|
||||||
|
c.ActiveHeight = int32(binary.BigEndian.Uint32(dec.Next(4)))
|
||||||
|
c.VisibleHeight = int32(binary.BigEndian.Uint32(dec.Next(4)))
|
||||||
|
c.Amount = int64(binary.BigEndian.Uint64(dec.Next(8)))
|
||||||
|
keys := binary.BigEndian.Uint32(dec.Next(4))
|
||||||
|
if keys > 0 {
|
||||||
|
c.SpentChildren = map[string]bool{}
|
||||||
|
}
|
||||||
|
for keys > 0 {
|
||||||
|
keys--
|
||||||
|
keySize := int(binary.BigEndian.Uint16(dec.Next(2)))
|
||||||
|
key := string(dec.Next(keySize))
|
||||||
|
c.SpentChildren[key] = true
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
53
claimtrie/change/claimid.go
Normal file
53
claimtrie/change/claimid.go
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
package change
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClaimID represents a Claim's ClaimID.
|
||||||
|
const ClaimIDSize = 20
|
||||||
|
|
||||||
|
type ClaimID [ClaimIDSize]byte
|
||||||
|
|
||||||
|
// NewClaimID returns a Claim ID calculated from Ripemd160(Sha256(OUTPOINT).
|
||||||
|
func NewClaimID(op wire.OutPoint) (id ClaimID) {
|
||||||
|
|
||||||
|
var buffer [chainhash.HashSize + 4]byte // hoping for stack alloc
|
||||||
|
copy(buffer[:], op.Hash[:])
|
||||||
|
binary.BigEndian.PutUint32(buffer[chainhash.HashSize:], op.Index)
|
||||||
|
copy(id[:], btcutil.Hash160(buffer[:]))
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewIDFromString returns a Claim ID from a string.
|
||||||
|
func NewIDFromString(s string) (id ClaimID, err error) {
|
||||||
|
|
||||||
|
if len(s) == 40 {
|
||||||
|
_, err = hex.Decode(id[:], []byte(s))
|
||||||
|
} else {
|
||||||
|
copy(id[:], s)
|
||||||
|
}
|
||||||
|
for i, j := 0, len(id)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
id[i], id[j] = id[j], id[i]
|
||||||
|
}
|
||||||
|
return id, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key is for in-memory maps
|
||||||
|
func (id ClaimID) Key() string {
|
||||||
|
return string(id[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
// String is for anything written to a DB
|
||||||
|
func (id ClaimID) String() string {
|
||||||
|
|
||||||
|
for i, j := 0, len(id)-1; i < j; i, j = i+1, j-1 {
|
||||||
|
id[i], id[j] = id[j], id[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
return hex.EncodeToString(id[:])
|
||||||
|
}
|
399
claimtrie/claimtrie.go
Normal file
399
claimtrie/claimtrie.go
Normal file
|
@ -0,0 +1,399 @@
|
||||||
|
package claimtrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/block"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/block/blockrepo"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/config"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/merkletrie"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/merkletrie/merkletrierepo"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node/noderepo"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/temporal"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/temporal/temporalrepo"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ClaimTrie implements a Merkle Trie supporting linear history of commits.
|
||||||
|
type ClaimTrie struct {
|
||||||
|
|
||||||
|
// Repository for calculated block hashes.
|
||||||
|
blockRepo block.Repo
|
||||||
|
|
||||||
|
// Repository for storing temporal information of nodes at each block height.
|
||||||
|
// For example, which nodes (by name) should be refreshed at each block height
|
||||||
|
// due to stake expiration or delayed activation.
|
||||||
|
temporalRepo temporal.Repo
|
||||||
|
|
||||||
|
// Cache layer of Nodes.
|
||||||
|
nodeManager node.Manager
|
||||||
|
|
||||||
|
// Prefix tree (trie) that manages merkle hash of each node.
|
||||||
|
merkleTrie merkletrie.MerkleTrie
|
||||||
|
|
||||||
|
// Current block height, which is increased by one when AppendBlock() is called.
|
||||||
|
height int32
|
||||||
|
|
||||||
|
// Registrered cleanup functions which are invoked in the Close() in reverse order.
|
||||||
|
cleanups []func() error
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(cfg config.Config) (*ClaimTrie, error) {
|
||||||
|
|
||||||
|
var cleanups []func() error
|
||||||
|
|
||||||
|
// The passed in cfg.DataDir has been prepended with netname.
|
||||||
|
dataDir := filepath.Join(cfg.DataDir, "claim_dbs")
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, cfg.BlockRepoPebble.Path)
|
||||||
|
blockRepo, err := blockrepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "creating block repo")
|
||||||
|
}
|
||||||
|
cleanups = append(cleanups, blockRepo.Close)
|
||||||
|
|
||||||
|
dbPath = filepath.Join(dataDir, cfg.TemporalRepoPebble.Path)
|
||||||
|
temporalRepo, err := temporalrepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "creating temporal repo")
|
||||||
|
}
|
||||||
|
cleanups = append(cleanups, temporalRepo.Close)
|
||||||
|
|
||||||
|
// Initialize repository for changes to nodes.
|
||||||
|
// The cleanup is delegated to the Node Manager.
|
||||||
|
dbPath = filepath.Join(dataDir, cfg.NodeRepoPebble.Path)
|
||||||
|
nodeRepo, err := noderepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "creating node repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
baseManager, err := node.NewBaseManager(nodeRepo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "creating node base manager")
|
||||||
|
}
|
||||||
|
nodeManager := node.NewNormalizingManager(baseManager)
|
||||||
|
cleanups = append(cleanups, nodeManager.Close)
|
||||||
|
|
||||||
|
var trie merkletrie.MerkleTrie
|
||||||
|
if cfg.RamTrie {
|
||||||
|
trie = merkletrie.NewRamTrie(nodeManager)
|
||||||
|
} else {
|
||||||
|
|
||||||
|
// Initialize repository for MerkleTrie. The cleanup is delegated to MerkleTrie.
|
||||||
|
dbPath = filepath.Join(dataDir, cfg.MerkleTrieRepoPebble.Path)
|
||||||
|
trieRepo, err := merkletrierepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "creating trie repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
persistentTrie := merkletrie.NewPersistentTrie(nodeManager, trieRepo)
|
||||||
|
cleanups = append(cleanups, persistentTrie.Close)
|
||||||
|
trie = persistentTrie
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restore the last height.
|
||||||
|
previousHeight, err := blockRepo.Load()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "load block tip")
|
||||||
|
}
|
||||||
|
|
||||||
|
ct := &ClaimTrie{
|
||||||
|
blockRepo: blockRepo,
|
||||||
|
temporalRepo: temporalRepo,
|
||||||
|
|
||||||
|
nodeManager: nodeManager,
|
||||||
|
merkleTrie: trie,
|
||||||
|
|
||||||
|
height: previousHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
ct.cleanups = cleanups
|
||||||
|
|
||||||
|
if previousHeight > 0 {
|
||||||
|
hash, err := blockRepo.Get(previousHeight)
|
||||||
|
if err != nil {
|
||||||
|
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)
|
||||||
|
if err != nil {
|
||||||
|
ct.Close()
|
||||||
|
return nil, errors.Wrap(err, "increment height to")
|
||||||
|
}
|
||||||
|
// TODO: pass in the interrupt signal here:
|
||||||
|
trie.SetRoot(hash, nil) // keep this after IncrementHeightTo
|
||||||
|
|
||||||
|
if !ct.MerkleHash().IsEqual(hash) {
|
||||||
|
ct.Close()
|
||||||
|
return nil, errors.Errorf("unable to restore the claim hash to %s at height %d", hash.String(), previousHeight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ct, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddClaim adds a Claim to the ClaimTrie.
|
||||||
|
func (ct *ClaimTrie) AddClaim(name []byte, op wire.OutPoint, id change.ClaimID, amt int64) error {
|
||||||
|
|
||||||
|
chg := change.Change{
|
||||||
|
Type: change.AddClaim,
|
||||||
|
Name: name,
|
||||||
|
OutPoint: op,
|
||||||
|
Amount: amt,
|
||||||
|
ClaimID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ct.forwardNodeChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateClaim updates a Claim in the ClaimTrie.
|
||||||
|
func (ct *ClaimTrie) UpdateClaim(name []byte, op wire.OutPoint, amt int64, id change.ClaimID) error {
|
||||||
|
|
||||||
|
chg := change.Change{
|
||||||
|
Type: change.UpdateClaim,
|
||||||
|
Name: name,
|
||||||
|
OutPoint: op,
|
||||||
|
Amount: amt,
|
||||||
|
ClaimID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ct.forwardNodeChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpendClaim spends a Claim in the ClaimTrie.
|
||||||
|
func (ct *ClaimTrie) SpendClaim(name []byte, op wire.OutPoint, id change.ClaimID) error {
|
||||||
|
|
||||||
|
chg := change.Change{
|
||||||
|
Type: change.SpendClaim,
|
||||||
|
Name: name,
|
||||||
|
OutPoint: op,
|
||||||
|
ClaimID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ct.forwardNodeChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddSupport adds a Support to the ClaimTrie.
|
||||||
|
func (ct *ClaimTrie) AddSupport(name []byte, op wire.OutPoint, amt int64, id change.ClaimID) error {
|
||||||
|
|
||||||
|
chg := change.Change{
|
||||||
|
Type: change.AddSupport,
|
||||||
|
Name: name,
|
||||||
|
OutPoint: op,
|
||||||
|
Amount: amt,
|
||||||
|
ClaimID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ct.forwardNodeChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SpendSupport spends a Support in the ClaimTrie.
|
||||||
|
func (ct *ClaimTrie) SpendSupport(name []byte, op wire.OutPoint, id change.ClaimID) error {
|
||||||
|
|
||||||
|
chg := change.Change{
|
||||||
|
Type: change.SpendSupport,
|
||||||
|
Name: name,
|
||||||
|
OutPoint: op,
|
||||||
|
ClaimID: id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return ct.forwardNodeChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AppendBlock increases block by one.
|
||||||
|
func (ct *ClaimTrie) AppendBlock() error {
|
||||||
|
|
||||||
|
ct.height++
|
||||||
|
|
||||||
|
names, err := ct.nodeManager.IncrementHeightTo(ct.height)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "node manager increment")
|
||||||
|
}
|
||||||
|
|
||||||
|
expirations, err := ct.temporalRepo.NodesAt(ct.height)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "temporal repo get")
|
||||||
|
}
|
||||||
|
|
||||||
|
names = removeDuplicates(names) // comes out sorted
|
||||||
|
|
||||||
|
updateNames := make([][]byte, 0, len(names)+len(expirations))
|
||||||
|
updateHeights := make([]int32, 0, len(names)+len(expirations))
|
||||||
|
updateNames = append(updateNames, names...)
|
||||||
|
for range names { // log to the db that we updated a name at this height for rollback purposes
|
||||||
|
updateHeights = append(updateHeights, ct.height)
|
||||||
|
}
|
||||||
|
names = append(names, expirations...)
|
||||||
|
names = removeDuplicates(names)
|
||||||
|
|
||||||
|
for _, name := range names {
|
||||||
|
|
||||||
|
ct.merkleTrie.Update(name, true)
|
||||||
|
|
||||||
|
newName, nextUpdate := ct.nodeManager.NextUpdateHeightOfNode(name)
|
||||||
|
if nextUpdate <= 0 {
|
||||||
|
continue // some names are no longer there; that's not an error
|
||||||
|
}
|
||||||
|
updateNames = append(updateNames, newName) // TODO: make sure using the temporalRepo batch is actually faster
|
||||||
|
updateHeights = append(updateHeights, nextUpdate)
|
||||||
|
}
|
||||||
|
if len(updateNames) != 0 {
|
||||||
|
err = ct.temporalRepo.SetNodesAt(updateNames, updateHeights)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "temporal repo set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hitFork := ct.updateTrieForHashForkIfNecessary()
|
||||||
|
|
||||||
|
h := ct.MerkleHash()
|
||||||
|
ct.blockRepo.Set(ct.height, h)
|
||||||
|
|
||||||
|
if hitFork {
|
||||||
|
ct.merkleTrie.SetRoot(h, names) // for clearing the memory entirely
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ClaimTrie) updateTrieForHashForkIfNecessary() bool {
|
||||||
|
if ct.height != param.ActiveParams.AllClaimsInMerkleForkHeight {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
node.LogOnce("Marking all trie nodes as dirty for the hash fork...")
|
||||||
|
|
||||||
|
// invalidate all names because we have to recompute the hash on everything
|
||||||
|
ct.nodeManager.IterateNames(func(name []byte) bool {
|
||||||
|
ct.merkleTrie.Update(name, false)
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
node.LogOnce("Done. Now recomputing all hashes...")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeDuplicates(names [][]byte) [][]byte { // this might be too expensive; we'll have to profile it
|
||||||
|
sort.Slice(names, func(i, j int) bool { // put names in order so we can skip duplicates
|
||||||
|
return bytes.Compare(names[i], names[j]) < 0
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := len(names) - 2; i >= 0; i-- {
|
||||||
|
if bytes.Equal(names[i], names[i+1]) {
|
||||||
|
names = append(names[:i], names[i+1:]...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return names
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetHeight resets the ClaimTrie to a previous known height..
|
||||||
|
func (ct *ClaimTrie) ResetHeight(height int32) error {
|
||||||
|
|
||||||
|
names := make([][]byte, 0)
|
||||||
|
for h := height + 1; h <= ct.height; h++ {
|
||||||
|
results, err := ct.temporalRepo.NodesAt(h)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
names = append(names, results...)
|
||||||
|
}
|
||||||
|
err := ct.nodeManager.DecrementHeightTo(names, height)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
passedHashFork := ct.height >= param.ActiveParams.AllClaimsInMerkleForkHeight && height < param.ActiveParams.AllClaimsInMerkleForkHeight
|
||||||
|
ct.height = height
|
||||||
|
hash, err := ct.blockRepo.Get(height)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if passedHashFork {
|
||||||
|
names = nil // force them to reconsider all names
|
||||||
|
}
|
||||||
|
ct.merkleTrie.SetRoot(hash, names)
|
||||||
|
|
||||||
|
if !ct.MerkleHash().IsEqual(hash) {
|
||||||
|
return errors.Errorf("unable to restore the hash at height %d", height)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// MerkleHash returns the Merkle Hash of the claimTrie.
|
||||||
|
func (ct *ClaimTrie) MerkleHash() *chainhash.Hash {
|
||||||
|
if ct.height >= param.ActiveParams.AllClaimsInMerkleForkHeight {
|
||||||
|
return ct.merkleTrie.MerkleHashAllClaims()
|
||||||
|
}
|
||||||
|
return ct.merkleTrie.MerkleHash()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Height returns the current block height.
|
||||||
|
func (ct *ClaimTrie) Height() int32 {
|
||||||
|
return ct.height
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close persists states.
|
||||||
|
// Any calls to the ClaimTrie after Close() being called results undefined behaviour.
|
||||||
|
func (ct *ClaimTrie) Close() {
|
||||||
|
|
||||||
|
for i := len(ct.cleanups) - 1; i >= 0; i-- {
|
||||||
|
cleanup := ct.cleanups[i]
|
||||||
|
err := cleanup()
|
||||||
|
if err != nil { // it would be better to cleanup what we can than exit early
|
||||||
|
node.LogOnce("On cleanup: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ct.cleanups = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ClaimTrie) forwardNodeChange(chg change.Change) error {
|
||||||
|
|
||||||
|
chg.Height = ct.Height() + 1
|
||||||
|
|
||||||
|
err := ct.nodeManager.AppendChange(chg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("node manager handle change: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ClaimTrie) NodeAt(height int32, name []byte) (*node.Node, error) {
|
||||||
|
return ct.nodeManager.NodeAt(height, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ClaimTrie) NamesChangedInBlock(height int32) ([]string, error) {
|
||||||
|
hits, err := ct.temporalRepo.NodesAt(height)
|
||||||
|
r := make([]string, len(hits))
|
||||||
|
for i := range hits {
|
||||||
|
r[i] = string(hits[i])
|
||||||
|
}
|
||||||
|
return r, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ct *ClaimTrie) FlushToDisk() {
|
||||||
|
// maybe the user can fix the file lock shown in the warning before they shut down
|
||||||
|
if err := ct.nodeManager.Flush(); err != nil {
|
||||||
|
node.Warn("During nodeManager flush: " + err.Error())
|
||||||
|
}
|
||||||
|
if err := ct.temporalRepo.Flush(); err != nil {
|
||||||
|
node.Warn("During temporalRepo flush: " + err.Error())
|
||||||
|
}
|
||||||
|
if err := ct.merkleTrie.Flush(); err != nil {
|
||||||
|
node.Warn("During merkleTrie flush: " + err.Error())
|
||||||
|
}
|
||||||
|
if err := ct.blockRepo.Flush(); err != nil {
|
||||||
|
node.Warn("During blockRepo flush: " + err.Error())
|
||||||
|
}
|
||||||
|
}
|
987
claimtrie/claimtrie_test.go
Normal file
987
claimtrie/claimtrie_test.go
Normal file
|
@ -0,0 +1,987 @@
|
||||||
|
package claimtrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/config"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/merkletrie"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var cfg = config.DefaultConfig
|
||||||
|
|
||||||
|
func setup(t *testing.T) {
|
||||||
|
param.SetNetwork(wire.TestNet)
|
||||||
|
cfg.DataDir = t.TempDir()
|
||||||
|
}
|
||||||
|
|
||||||
|
func b(s string) []byte {
|
||||||
|
return []byte(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func buildTx(hash chainhash.Hash) *wire.MsgTx {
|
||||||
|
tx := wire.NewMsgTx(1)
|
||||||
|
txIn := wire.NewTxIn(wire.NewOutPoint(&hash, 0), nil, nil)
|
||||||
|
tx.AddTxIn(txIn)
|
||||||
|
tx.AddTxOut(wire.NewTxOut(0, nil))
|
||||||
|
return tx
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixedHashes(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
setup(t)
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
r.Equal(merkletrie.EmptyTrieHash[:], ct.MerkleHash()[:])
|
||||||
|
|
||||||
|
tx1 := buildTx(*merkletrie.EmptyTrieHash)
|
||||||
|
tx2 := buildTx(tx1.TxHash())
|
||||||
|
tx3 := buildTx(tx2.TxHash())
|
||||||
|
tx4 := buildTx(tx3.TxHash())
|
||||||
|
|
||||||
|
err = ct.AddClaim(b("test"), tx1.TxIn[0].PreviousOutPoint, change.NewClaimID(tx1.TxIn[0].PreviousOutPoint), 50)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AddClaim(b("test2"), tx2.TxIn[0].PreviousOutPoint, change.NewClaimID(tx2.TxIn[0].PreviousOutPoint), 50)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AddClaim(b("test"), tx3.TxIn[0].PreviousOutPoint, change.NewClaimID(tx3.TxIn[0].PreviousOutPoint), 50)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AddClaim(b("tes"), tx4.TxIn[0].PreviousOutPoint, change.NewClaimID(tx4.TxIn[0].PreviousOutPoint), 50)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
expected, err := chainhash.NewHashFromStr("938fb93364bf8184e0b649c799ae27274e8db5221f1723c99fb2acd3386cfb00")
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(expected[:], ct.MerkleHash()[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizationFork(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.NormalizedNameForkHeight = 2
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("AÑEJO"), o1, change.NewClaimID(o1), 10)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("AÑejo"), o2, change.NewClaimID(o2), 5)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
err = ct.AddClaim([]byte("あてはまる"), o3, change.NewClaimID(o3), 5)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o4 := wire.OutPoint{Hash: hash, Index: 4}
|
||||||
|
err = ct.AddClaim([]byte("Aḿlie"), o4, change.NewClaimID(o4), 5)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o5 := wire.OutPoint{Hash: hash, Index: 5}
|
||||||
|
err = ct.AddClaim([]byte("TEST"), o5, change.NewClaimID(o5), 5)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o6 := wire.OutPoint{Hash: hash, Index: 6}
|
||||||
|
err = ct.AddClaim([]byte("test"), o6, change.NewClaimID(o6), 7)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o7 := wire.OutPoint{Hash: hash, Index: 7}
|
||||||
|
err = ct.AddSupport([]byte("test"), o7, 11, change.NewClaimID(o6))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotEqual(merkletrie.EmptyTrieHash[:], ct.MerkleHash()[:])
|
||||||
|
|
||||||
|
n, err := ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("AÑEJO"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(n.BestClaim)
|
||||||
|
r.Equal(int32(1), n.TakenOverAt)
|
||||||
|
n.Close()
|
||||||
|
|
||||||
|
o8 := wire.OutPoint{Hash: hash, Index: 8}
|
||||||
|
err = ct.AddClaim([]byte("aÑEJO"), o8, change.NewClaimID(o8), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotEqual(merkletrie.EmptyTrieHash[:], ct.MerkleHash()[:])
|
||||||
|
|
||||||
|
n, err = ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("añejo"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(3, len(n.Claims))
|
||||||
|
r.Equal(uint32(1), n.BestClaim.OutPoint.Index)
|
||||||
|
r.Equal(int32(2), n.TakenOverAt)
|
||||||
|
|
||||||
|
n, err = ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(18), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
n.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestActivationsOnNormalizationFork(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.NormalizedNameForkHeight = 4
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
|
||||||
|
o7 := wire.OutPoint{Hash: hash, Index: 7}
|
||||||
|
err = ct.AddClaim([]byte("A"), o7, change.NewClaimID(o7), 1)
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
verifyBestIndex(t, ct, "A", 7, 1)
|
||||||
|
|
||||||
|
o8 := wire.OutPoint{Hash: hash, Index: 8}
|
||||||
|
err = ct.AddClaim([]byte("A"), o8, change.NewClaimID(o8), 2)
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
verifyBestIndex(t, ct, "a", 8, 2)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
verifyBestIndex(t, ct, "a", 8, 2)
|
||||||
|
|
||||||
|
err = ct.ResetHeight(3)
|
||||||
|
r.NoError(err)
|
||||||
|
verifyBestIndex(t, ct, "A", 7, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNormalizationSortOrder(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
// this was an unfortunate bug; the normalization fork should not have activated anything
|
||||||
|
// alas, it's now part of our history; we hereby test it to keep it that way
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.NormalizedNameForkHeight = 2
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("A"), o1, change.NewClaimID(o1), 1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("A"), o2, change.NewClaimID(o2), 2)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
err = ct.AddClaim([]byte("a"), o3, change.NewClaimID(o3), 3)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
verifyBestIndex(t, ct, "A", 2, 2)
|
||||||
|
verifyBestIndex(t, ct, "a", 3, 1)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
verifyBestIndex(t, ct, "a", 3, 3)
|
||||||
|
}
|
||||||
|
|
||||||
|
func verifyBestIndex(t *testing.T, ct *ClaimTrie, name string, idx uint32, claims int) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
n, err := ct.nodeManager.NodeAt(ct.nodeManager.Height(), []byte(name))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(claims, len(n.Claims))
|
||||||
|
if claims > 0 {
|
||||||
|
r.Equal(idx, n.BestClaim.OutPoint.Index)
|
||||||
|
}
|
||||||
|
n.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRebuild(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test1"), o1, change.NewClaimID(o1), 1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("test2"), o2, change.NewClaimID(o2), 2)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
m := ct.MerkleHash()
|
||||||
|
r.NotNil(m)
|
||||||
|
r.NotEqual(*merkletrie.EmptyTrieHash, *m)
|
||||||
|
|
||||||
|
ct.merkleTrie = merkletrie.NewRamTrie(ct.nodeManager)
|
||||||
|
ct.merkleTrie.SetRoot(m, nil)
|
||||||
|
|
||||||
|
m2 := ct.MerkleHash()
|
||||||
|
r.NotNil(m2)
|
||||||
|
r.Equal(*m, *m2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkClaimTrie_AppendBlock(b *testing.B) {
|
||||||
|
|
||||||
|
rand.Seed(42)
|
||||||
|
names := make([][]byte, 0, b.N)
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
names = append(names, randomName())
|
||||||
|
}
|
||||||
|
|
||||||
|
param.SetNetwork(wire.TestNet)
|
||||||
|
param.ActiveParams.OriginalClaimExpirationTime = 1000000
|
||||||
|
param.ActiveParams.ExtendedClaimExpirationTime = 1000000
|
||||||
|
cfg.DataDir = b.TempDir()
|
||||||
|
|
||||||
|
r := require.New(b)
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
defer ct.Close()
|
||||||
|
h1 := chainhash.Hash{100, 200}
|
||||||
|
|
||||||
|
start := time.Now()
|
||||||
|
b.ResetTimer()
|
||||||
|
|
||||||
|
c := 0
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
op := wire.OutPoint{Hash: h1, Index: uint32(i)}
|
||||||
|
id := change.NewClaimID(op)
|
||||||
|
err = ct.AddClaim(names[i], op, id, 500)
|
||||||
|
r.NoError(err)
|
||||||
|
if c++; (c & 0xff) == 0xff {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
op := wire.OutPoint{Hash: h1, Index: uint32(i)}
|
||||||
|
id := change.NewClaimID(op)
|
||||||
|
op.Hash[0] = 1
|
||||||
|
err = ct.UpdateClaim(names[i], op, 400, id)
|
||||||
|
r.NoError(err)
|
||||||
|
if c++; (c & 0xff) == 0xff {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
op := wire.OutPoint{Hash: h1, Index: uint32(i)}
|
||||||
|
id := change.NewClaimID(op)
|
||||||
|
op.Hash[0] = 2
|
||||||
|
err = ct.UpdateClaim(names[i], op, 300, id)
|
||||||
|
r.NoError(err)
|
||||||
|
if c++; (c & 0xff) == 0xff {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
op := wire.OutPoint{Hash: h1, Index: uint32(i)}
|
||||||
|
id := change.NewClaimID(op)
|
||||||
|
op.Hash[0] = 3
|
||||||
|
err = ct.SpendClaim(names[i], op, id)
|
||||||
|
r.NoError(err)
|
||||||
|
if c++; (c & 0xff) == 0xff {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
b.StopTimer()
|
||||||
|
ht := ct.height
|
||||||
|
h1 = *ct.MerkleHash()
|
||||||
|
ct.Close()
|
||||||
|
b.Logf("Running AppendBlock bench with %d names in %f sec. Height: %d, Hash: %s",
|
||||||
|
b.N, time.Since(start).Seconds(), ht, h1.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomName() []byte {
|
||||||
|
name := make([]byte, rand.Intn(30)+10)
|
||||||
|
rand.Read(name)
|
||||||
|
for i := range name {
|
||||||
|
name[i] %= 56
|
||||||
|
name[i] += 65
|
||||||
|
}
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClaimReplace(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("bass"), o1, change.NewClaimID(o1), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("basso"), o2, change.NewClaimID(o2), 10)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("bass"))
|
||||||
|
r.Equal(o1.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("bass"), o1, n.BestClaim.ClaimID)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o4 := wire.OutPoint{Hash: hash, Index: 4}
|
||||||
|
err = ct.AddClaim([]byte("bassfisher"), o4, change.NewClaimID(o4), 12)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("bass"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(n == nil || !n.HasActiveBestClaim())
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("bassfisher"))
|
||||||
|
r.Equal(o4.String(), n.BestClaim.OutPoint.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGeneralClaim(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.ResetHeight(ct.height - 1)
|
||||||
|
r.NoError(err)
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(n == nil || !n.HasActiveBestClaim())
|
||||||
|
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8)
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.ResetHeight(ct.height - 1)
|
||||||
|
r.NoError(err)
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(n == nil || !n.HasActiveBestClaim())
|
||||||
|
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.ResetHeight(ct.height - 2)
|
||||||
|
r.NoError(err)
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(n == nil || !n.HasActiveBestClaim())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClaimTakeover(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 18)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o1.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o2.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.ResetHeight(ct.height - 1)
|
||||||
|
r.NoError(err)
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o1.String(), n.BestClaim.OutPoint.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSpendClaim(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18)
|
||||||
|
r.NoError(err)
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o2.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.ResetHeight(ct.height - 1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
err = ct.AddClaim([]byte("test"), o3, change.NewClaimID(o3), 22)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
o4 := wire.OutPoint{Hash: hash, Index: 4}
|
||||||
|
err = ct.AddClaim([]byte("test"), o4, change.NewClaimID(o4), 28)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o3.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o3, n.BestClaim.ClaimID)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o4.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.SpendClaim([]byte("test"), o2, change.NewClaimID(o2))
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.SpendClaim([]byte("test"), o3, change.NewClaimID(o3))
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.SpendClaim([]byte("test"), o4, change.NewClaimID(o4))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(n == nil || !n.HasActiveBestClaim())
|
||||||
|
|
||||||
|
h := ct.MerkleHash()
|
||||||
|
r.Equal(merkletrie.EmptyTrieHash.String(), h.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupportDelay(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18)
|
||||||
|
r.NoError(err)
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.AddClaim([]byte("test"), o2, change.NewClaimID(o2), 8)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
err = ct.AddSupport([]byte("test"), o3, 18, change.NewClaimID(o3)) // using bad ClaimID on purpose
|
||||||
|
r.NoError(err)
|
||||||
|
o4 := wire.OutPoint{Hash: hash, Index: 4}
|
||||||
|
err = ct.AddSupport([]byte("test"), o4, 18, change.NewClaimID(o2))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o2.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
o5 := wire.OutPoint{Hash: hash, Index: 5}
|
||||||
|
err = ct.AddSupport([]byte("test"), o5, 18, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o2.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
for i := 0; i < 11; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o1.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
err = ct.ResetHeight(ct.height - 1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o2.String(), n.BestClaim.OutPoint.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupportSpending(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
err = ct.AddSupport([]byte("test"), o3, 18, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(n == nil || !n.HasActiveBestClaim())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupportOnUpdate(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o1, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
err = ct.UpdateClaim([]byte("test"), o2, 28, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(28), n.BestClaim.Amount)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o2, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
err = ct.UpdateClaim([]byte("test"), o3, 38, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o4 := wire.OutPoint{Hash: hash, Index: 4}
|
||||||
|
err = ct.AddSupport([]byte("test"), o4, 2, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
o5 := wire.OutPoint{Hash: hash, Index: 5}
|
||||||
|
err = ct.AddClaim([]byte("test"), o5, change.NewClaimID(o5), 39)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(40), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
|
||||||
|
err = ct.SpendSupport([]byte("test"), o4, n.BestClaim.ClaimID)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
// NOTE: LBRYcrd did not test that supports can trigger a takeover correctly (and it doesn't work here):
|
||||||
|
// n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
// r.NoError(err)
|
||||||
|
// r.Equal(int64(39), n.BestClaim.Amount + n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupportPreservation(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
o4 := wire.OutPoint{Hash: hash, Index: 4}
|
||||||
|
o5 := wire.OutPoint{Hash: hash, Index: 5}
|
||||||
|
|
||||||
|
err = ct.AddSupport([]byte("test"), o2, 10, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 18)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AddClaim([]byte("test"), o3, change.NewClaimID(o3), 7)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(28), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
|
||||||
|
err = ct.AddSupport([]byte("test"), o4, 10, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
err = ct.AddSupport([]byte("test"), o5, 100, change.NewClaimID(o3))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(38), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(int64(107), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInvalidClaimID(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
o2 := wire.OutPoint{Hash: hash, Index: 2}
|
||||||
|
o3 := wire.OutPoint{Hash: hash, Index: 3}
|
||||||
|
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 10)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.SpendClaim([]byte("test"), o3, change.NewClaimID(o1))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.UpdateClaim([]byte("test"), o2, 18, change.NewClaimID(o3))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
for i := 0; i < 12; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Len(n.Claims, 1)
|
||||||
|
r.Len(n.Supports, 0)
|
||||||
|
r.Equal(int64(10), n.BestClaim.Amount+n.SupportSums[n.BestClaim.ClaimID.Key()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStableTrieHash(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
param.ActiveParams.AllClaimsInMerkleForkHeight = 8 // changes on this one
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
hash := chainhash.HashH([]byte{1, 2, 3})
|
||||||
|
o1 := wire.OutPoint{Hash: hash, Index: 1}
|
||||||
|
|
||||||
|
err = ct.AddClaim([]byte("test"), o1, change.NewClaimID(o1), 1)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
h := ct.MerkleHash()
|
||||||
|
r.NotEqual(merkletrie.EmptyTrieHash.String(), h.String())
|
||||||
|
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(h.String(), ct.MerkleHash().String())
|
||||||
|
}
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
r.NotEqual(h.String(), ct.MerkleHash())
|
||||||
|
h = ct.MerkleHash()
|
||||||
|
|
||||||
|
for i := 0; i < 16; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(h.String(), ct.MerkleHash().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlock884431(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
setup(t)
|
||||||
|
param.ActiveParams.ActiveDelayFactor = 1
|
||||||
|
param.ActiveParams.MaxRemovalWorkaroundHeight = 0
|
||||||
|
param.ActiveParams.AllClaimsInMerkleForkHeight = 0
|
||||||
|
|
||||||
|
ct, err := New(cfg)
|
||||||
|
r.NoError(err)
|
||||||
|
r.NotNil(ct)
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
// in this block we have a scenario where we update all the child names
|
||||||
|
// which, in the old code, caused a trie vertex to be removed
|
||||||
|
// which, in turn, would trigger a premature takeover
|
||||||
|
|
||||||
|
c := byte(10)
|
||||||
|
|
||||||
|
add := func(s string, amt int64) wire.OutPoint {
|
||||||
|
h := chainhash.HashH([]byte{c})
|
||||||
|
c++
|
||||||
|
o := wire.OutPoint{Hash: h, Index: 1}
|
||||||
|
err := ct.AddClaim([]byte(s), o, change.NewClaimID(o), amt)
|
||||||
|
r.NoError(err)
|
||||||
|
return o
|
||||||
|
}
|
||||||
|
|
||||||
|
update := func(s string, o wire.OutPoint, amt int64) wire.OutPoint {
|
||||||
|
err = ct.SpendClaim([]byte(s), o, change.NewClaimID(o))
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
h := chainhash.HashH([]byte{c})
|
||||||
|
c++
|
||||||
|
o2 := wire.OutPoint{Hash: h, Index: 2}
|
||||||
|
|
||||||
|
err = ct.UpdateClaim([]byte(s), o2, amt, change.NewClaimID(o))
|
||||||
|
r.NoError(err)
|
||||||
|
return o2
|
||||||
|
}
|
||||||
|
|
||||||
|
o1a := add("go", 10)
|
||||||
|
o1b := add("go", 20)
|
||||||
|
o2 := add("goop", 10)
|
||||||
|
o3 := add("gog", 20)
|
||||||
|
|
||||||
|
o4a := add("test", 10)
|
||||||
|
o4b := add("test", 20)
|
||||||
|
o5 := add("tester", 10)
|
||||||
|
o6 := add("testing", 20)
|
||||||
|
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
n, err := ct.NodeAt(ct.height, []byte("go"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o1b.String(), n.BestClaim.OutPoint.String())
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o4b.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
update("go", o1b, 30)
|
||||||
|
o10 := update("go", o1a, 40)
|
||||||
|
update("gog", o3, 30)
|
||||||
|
update("goop", o2, 30)
|
||||||
|
|
||||||
|
update("testing", o6, 30)
|
||||||
|
o11 := update("test", o4b, 30)
|
||||||
|
update("test", o4a, 40)
|
||||||
|
update("tester", o5, 30)
|
||||||
|
|
||||||
|
err = ct.AppendBlock()
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("go"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o10.String(), n.BestClaim.OutPoint.String())
|
||||||
|
n, err = ct.NodeAt(ct.height, []byte("test"))
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(o11.String(), n.BestClaim.OutPoint.String())
|
||||||
|
}
|
98
claimtrie/cmd/cmd/block.go
Normal file
98
claimtrie/cmd/cmd/block.go
Normal file
|
@ -0,0 +1,98 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewBlocCommands())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBlocCommands() *cobra.Command {
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "block",
|
||||||
|
Short: "Block related commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(NewBlockBestCommand())
|
||||||
|
cmd.AddCommand(NewBlockListCommand())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBlockBestCommand() *cobra.Command {
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "best",
|
||||||
|
Short: "Show the height and hash of the best block",
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
db, err := loadBlocksDB()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load blocks database")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
chain, err := loadChain(db)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
state := chain.BestSnapshot()
|
||||||
|
fmt.Printf("Block %7d: %s\n", state.Height, state.Hash.String())
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBlockListCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var fromHeight int32
|
||||||
|
var toHeight int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "list",
|
||||||
|
Short: "List merkle hash of blocks between <from_height> <to_height>",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
db, err := loadBlocksDB()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load blocks database")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
chain, err := loadChain(db)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
if toHeight > chain.BestSnapshot().Height {
|
||||||
|
toHeight = chain.BestSnapshot().Height
|
||||||
|
}
|
||||||
|
|
||||||
|
for ht := fromHeight; ht <= toHeight; ht++ {
|
||||||
|
hash, err := chain.BlockHashByHeight(ht)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load hash for %d", ht)
|
||||||
|
}
|
||||||
|
fmt.Printf("Block %7d: %s\n", ht, hash.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Int32Var(&fromHeight, "from", 0, "From height (inclusive)")
|
||||||
|
cmd.Flags().Int32Var(&toHeight, "to", 0, "To height (inclusive)")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
439
claimtrie/cmd/cmd/chain.go
Normal file
439
claimtrie/cmd/cmd/chain.go
Normal file
|
@ -0,0 +1,439 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/chain"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/chain/chainrepo"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/config"
|
||||||
|
"github.com/btcsuite/btcd/database"
|
||||||
|
_ "github.com/btcsuite/btcd/database/ffldb"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewChainCommands())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChainCommands() *cobra.Command {
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "chain",
|
||||||
|
Short: "chain related command",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(NewChainDumpCommand())
|
||||||
|
cmd.AddCommand(NewChainReplayCommand())
|
||||||
|
cmd.AddCommand(NewChainConvertCommand())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChainDumpCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var chainRepoPath string
|
||||||
|
var fromHeight int32
|
||||||
|
var toHeight int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "dump",
|
||||||
|
Short: "Dump the chain changes between <fromHeight> and <toHeight>",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
dbPath := chainRepoPath
|
||||||
|
log.Debugf("Open chain repo: %q", dbPath)
|
||||||
|
chainRepo, err := chainrepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open chain repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
for height := fromHeight; height <= toHeight; height++ {
|
||||||
|
changes, err := chainRepo.Load(height)
|
||||||
|
if errors.Is(err, pebble.ErrNotFound) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load charnges for height: %d")
|
||||||
|
}
|
||||||
|
for _, chg := range changes {
|
||||||
|
showChange(chg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&chainRepoPath, "chaindb", "chain_db", "Claim operation database")
|
||||||
|
cmd.Flags().Int32Var(&fromHeight, "from", 0, "From height (inclusive)")
|
||||||
|
cmd.Flags().Int32Var(&toHeight, "to", 0, "To height (inclusive)")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChainReplayCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var chainRepoPath string
|
||||||
|
var fromHeight int32
|
||||||
|
var toHeight int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "replay",
|
||||||
|
Short: "Replay the chain changes between <fromHeight> and <toHeight>",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
for _, dbName := range []string{
|
||||||
|
cfg.BlockRepoPebble.Path,
|
||||||
|
cfg.NodeRepoPebble.Path,
|
||||||
|
cfg.MerkleTrieRepoPebble.Path,
|
||||||
|
cfg.TemporalRepoPebble.Path,
|
||||||
|
} {
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "claim_dbs", dbName)
|
||||||
|
log.Debugf("Delete repo: %q", dbPath)
|
||||||
|
err := os.RemoveAll(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "delete repo: %q", dbPath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("Open chain repo: %q", chainRepoPath)
|
||||||
|
chainRepo, err := chainrepo.NewPebble(chainRepoPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open chain repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := config.DefaultConfig
|
||||||
|
cfg.RamTrie = true
|
||||||
|
cfg.DataDir = filepath.Join(dataDir, netName)
|
||||||
|
|
||||||
|
ct, err := claimtrie.New(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "create claimtrie")
|
||||||
|
}
|
||||||
|
defer ct.Close()
|
||||||
|
|
||||||
|
db, err := loadBlocksDB()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load blocks database")
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, err := loadChain(db)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
for ht := fromHeight; ht < toHeight; ht++ {
|
||||||
|
|
||||||
|
changes, err := chainRepo.Load(ht + 1)
|
||||||
|
if errors.Is(err, pebble.ErrNotFound) {
|
||||||
|
// do nothing.
|
||||||
|
} else if err != nil {
|
||||||
|
return errors.Wrapf(err, "load changes for block %d", ht)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chg := range changes {
|
||||||
|
|
||||||
|
switch chg.Type {
|
||||||
|
case change.AddClaim:
|
||||||
|
err = ct.AddClaim(chg.Name, chg.OutPoint, chg.ClaimID, chg.Amount)
|
||||||
|
case change.UpdateClaim:
|
||||||
|
err = ct.UpdateClaim(chg.Name, chg.OutPoint, chg.Amount, chg.ClaimID)
|
||||||
|
case change.SpendClaim:
|
||||||
|
err = ct.SpendClaim(chg.Name, chg.OutPoint, chg.ClaimID)
|
||||||
|
case change.AddSupport:
|
||||||
|
err = ct.AddSupport(chg.Name, chg.OutPoint, chg.Amount, chg.ClaimID)
|
||||||
|
case change.SpendSupport:
|
||||||
|
err = ct.SpendSupport(chg.Name, chg.OutPoint, chg.ClaimID)
|
||||||
|
default:
|
||||||
|
err = errors.Errorf("invalid change type: %v", chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "execute change %v", chg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = appendBlock(ct, chain)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "appendBlock")
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Since(startTime) > 5*time.Second {
|
||||||
|
log.Infof("Block: %d", ct.Height())
|
||||||
|
startTime = time.Now()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&chainRepoPath, "chaindb", "chain_db", "Claim operation database")
|
||||||
|
cmd.Flags().Int32Var(&fromHeight, "from", 0, "From height")
|
||||||
|
cmd.Flags().Int32Var(&toHeight, "to", 0, "To height")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func appendBlock(ct *claimtrie.ClaimTrie, chain *blockchain.BlockChain) error {
|
||||||
|
|
||||||
|
err := ct.AppendBlock()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "append block: %w")
|
||||||
|
}
|
||||||
|
|
||||||
|
block, err := chain.BlockByHeight(ct.Height())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load from block repo: %w")
|
||||||
|
}
|
||||||
|
hash := block.MsgBlock().Header.ClaimTrie
|
||||||
|
|
||||||
|
if *ct.MerkleHash() != hash {
|
||||||
|
return errors.Errorf("hash mismatched at height %5d: exp: %s, got: %s", ct.Height(), hash, ct.MerkleHash())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewChainConvertCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var chainRepoPath string
|
||||||
|
var toHeight int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "convert",
|
||||||
|
Short: "convert changes from 0 to <toHeight>",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
db, err := loadBlocksDB()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load block db")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
chain, err := loadChain(db)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load block db")
|
||||||
|
}
|
||||||
|
|
||||||
|
if toHeight > chain.BestSnapshot().Height {
|
||||||
|
toHeight = chain.BestSnapshot().Height
|
||||||
|
}
|
||||||
|
|
||||||
|
chainRepo, err := chainrepo.NewPebble(chainRepoPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open chain repo: %v")
|
||||||
|
}
|
||||||
|
defer chainRepo.Close()
|
||||||
|
|
||||||
|
converter := chainConverter{
|
||||||
|
db: db,
|
||||||
|
chain: chain,
|
||||||
|
chainRepo: chainRepo,
|
||||||
|
toHeight: toHeight,
|
||||||
|
blockChan: make(chan *btcutil.Block, 1000),
|
||||||
|
changesChan: make(chan []change.Change, 1000),
|
||||||
|
wg: &sync.WaitGroup{},
|
||||||
|
stat: &stat{},
|
||||||
|
}
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
err = converter.start()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "start Converter")
|
||||||
|
}
|
||||||
|
|
||||||
|
converter.wait()
|
||||||
|
log.Infof("Convert chain: took %s", time.Since(startTime))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&chainRepoPath, "chaindb", "chain_db", "Claim operation database")
|
||||||
|
cmd.Flags().Int32Var(&toHeight, "to", 0, "toHeight")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
type stat struct {
|
||||||
|
blocksFetched int
|
||||||
|
blocksProcessed int
|
||||||
|
changesSaved int
|
||||||
|
}
|
||||||
|
|
||||||
|
type chainConverter struct {
|
||||||
|
db database.DB
|
||||||
|
chain *blockchain.BlockChain
|
||||||
|
chainRepo chain.Repo
|
||||||
|
toHeight int32
|
||||||
|
|
||||||
|
blockChan chan *btcutil.Block
|
||||||
|
changesChan chan []change.Change
|
||||||
|
|
||||||
|
wg *sync.WaitGroup
|
||||||
|
|
||||||
|
stat *stat
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cc *chainConverter) wait() {
|
||||||
|
cc.wg.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *chainConverter) start() error {
|
||||||
|
|
||||||
|
go cb.reportStats()
|
||||||
|
|
||||||
|
cb.wg.Add(3)
|
||||||
|
go cb.getBlock()
|
||||||
|
go cb.processBlock()
|
||||||
|
go cb.saveChanges()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *chainConverter) getBlock() {
|
||||||
|
defer cb.wg.Done()
|
||||||
|
defer close(cb.blockChan)
|
||||||
|
|
||||||
|
for ht := int32(0); ht < cb.toHeight; ht++ {
|
||||||
|
block, err := cb.chain.BlockByHeight(ht)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Cause(err).Error() == "too many open files" {
|
||||||
|
err = errors.WithHintf(err, "try ulimit -n 2048")
|
||||||
|
}
|
||||||
|
log.Errorf("load changes at %d: %s", ht, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb.stat.blocksFetched++
|
||||||
|
cb.blockChan <- block
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *chainConverter) processBlock() {
|
||||||
|
defer cb.wg.Done()
|
||||||
|
defer close(cb.changesChan)
|
||||||
|
|
||||||
|
utxoPubScripts := map[wire.OutPoint][]byte{}
|
||||||
|
for block := range cb.blockChan {
|
||||||
|
var changes []change.Change
|
||||||
|
for _, tx := range block.Transactions() {
|
||||||
|
|
||||||
|
if blockchain.IsCoinBase(tx) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, txIn := range tx.MsgTx().TxIn {
|
||||||
|
prevOutpoint := txIn.PreviousOutPoint
|
||||||
|
pkScript := utxoPubScripts[prevOutpoint]
|
||||||
|
cs, closer, err := txscript.DecodeClaimScript(pkScript)
|
||||||
|
if err == txscript.ErrNotClaimScript {
|
||||||
|
closer()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
log.Criticalf("Can't parse claim script: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
chg := change.Change{
|
||||||
|
Height: block.Height(),
|
||||||
|
Name: cs.Name(),
|
||||||
|
OutPoint: txIn.PreviousOutPoint,
|
||||||
|
}
|
||||||
|
delete(utxoPubScripts, prevOutpoint)
|
||||||
|
|
||||||
|
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())
|
||||||
|
case txscript.OP_SUPPORTCLAIM:
|
||||||
|
chg.Type = change.SpendSupport
|
||||||
|
copy(chg.ClaimID[:], cs.ClaimID())
|
||||||
|
}
|
||||||
|
|
||||||
|
changes = append(changes, chg)
|
||||||
|
closer()
|
||||||
|
}
|
||||||
|
|
||||||
|
op := *wire.NewOutPoint(tx.Hash(), 0)
|
||||||
|
for i, txOut := range tx.MsgTx().TxOut {
|
||||||
|
cs, closer, err := txscript.DecodeClaimScript(txOut.PkScript)
|
||||||
|
if err == txscript.ErrNotClaimScript {
|
||||||
|
closer()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
op.Index = uint32(i)
|
||||||
|
chg := change.Change{
|
||||||
|
Height: block.Height(),
|
||||||
|
Name: cs.Name(),
|
||||||
|
OutPoint: op,
|
||||||
|
Amount: txOut.Value,
|
||||||
|
}
|
||||||
|
utxoPubScripts[op] = txOut.PkScript
|
||||||
|
|
||||||
|
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())
|
||||||
|
case txscript.OP_UPDATECLAIM:
|
||||||
|
chg.Type = change.UpdateClaim
|
||||||
|
copy(chg.ClaimID[:], cs.ClaimID())
|
||||||
|
}
|
||||||
|
changes = append(changes, chg)
|
||||||
|
closer()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cb.stat.blocksProcessed++
|
||||||
|
|
||||||
|
if len(changes) != 0 {
|
||||||
|
cb.changesChan <- changes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *chainConverter) saveChanges() {
|
||||||
|
defer cb.wg.Done()
|
||||||
|
|
||||||
|
for changes := range cb.changesChan {
|
||||||
|
err := cb.chainRepo.Save(changes[0].Height, changes)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("save to chain repo: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
cb.stat.changesSaved++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cb *chainConverter) reportStats() {
|
||||||
|
stat := cb.stat
|
||||||
|
tick := time.NewTicker(5 * time.Second)
|
||||||
|
for range tick.C {
|
||||||
|
log.Infof("block : %7d / %7d, changes saved: %d",
|
||||||
|
stat.blocksFetched, stat.blocksProcessed, stat.changesSaved)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
62
claimtrie/cmd/cmd/helper.go
Normal file
62
claimtrie/cmd/cmd/helper.go
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/blockchain"
|
||||||
|
"github.com/btcsuite/btcd/chaincfg"
|
||||||
|
"github.com/btcsuite/btcd/database"
|
||||||
|
"github.com/btcsuite/btcd/txscript"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
func loadBlocksDB() (database.DB, error) {
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "blocks_ffldb")
|
||||||
|
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")
|
||||||
|
}
|
||||||
|
|
||||||
|
return db, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadChain(db database.DB) (*blockchain.BlockChain, error) {
|
||||||
|
paramsCopy := chaincfg.MainNetParams
|
||||||
|
|
||||||
|
log.Infof("Loading chain from database")
|
||||||
|
|
||||||
|
startTime := time.Now()
|
||||||
|
chain, err := blockchain.New(&blockchain.Config{
|
||||||
|
DB: db,
|
||||||
|
ChainParams: ¶msCopy,
|
||||||
|
TimeSource: blockchain.NewMedianTime(),
|
||||||
|
SigCache: txscript.NewSigCache(1000),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrapf(err, "create blockchain")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("Loaded chain from database (%s)", time.Since(startTime))
|
||||||
|
|
||||||
|
return chain, err
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func chainPramas() chaincfg.Params {
|
||||||
|
|
||||||
|
// Make a copy so the user won't modify the global instance.
|
||||||
|
params := chaincfg.MainNetParams
|
||||||
|
switch netName {
|
||||||
|
case "mainnet":
|
||||||
|
params = chaincfg.MainNetParams
|
||||||
|
case "testnet":
|
||||||
|
params = chaincfg.TestNet3Params
|
||||||
|
case "regtest":
|
||||||
|
params = chaincfg.RegressionNetParams
|
||||||
|
}
|
||||||
|
return params
|
||||||
|
}
|
105
claimtrie/cmd/cmd/merkletrie.go
Normal file
105
claimtrie/cmd/cmd/merkletrie.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/merkletrie"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/merkletrie/merkletrierepo"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/temporal/temporalrepo"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewTrieCommands())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrieCommands() *cobra.Command {
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "trie",
|
||||||
|
Short: "MerkleTrie related commands",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(NewTrieNameCommand())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTrieNameCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var height int32
|
||||||
|
var name string
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "name",
|
||||||
|
Short: "List the claim and child hashes at vertex name of block at height",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
db, err := loadBlocksDB()
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load blocks database")
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
chain, err := loadChain(db)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load chain")
|
||||||
|
}
|
||||||
|
|
||||||
|
state := chain.BestSnapshot()
|
||||||
|
fmt.Printf("Block %7d: %s\n", state.Height, state.Hash.String())
|
||||||
|
|
||||||
|
if height > state.Height {
|
||||||
|
return errors.New("requested height is unavailable")
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := state.Hash
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "claim_dbs", cfg.MerkleTrieRepoPebble.Path)
|
||||||
|
log.Debugf("Open merkletrie repo: %q", dbPath)
|
||||||
|
trieRepo, err := merkletrierepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open merkle trie repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
trie := merkletrie.NewPersistentTrie(nil, trieRepo)
|
||||||
|
defer trie.Close()
|
||||||
|
|
||||||
|
trie.SetRoot(&hash, nil)
|
||||||
|
|
||||||
|
if len(name) > 1 {
|
||||||
|
trie.Dump(name)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
dbPath = filepath.Join(dataDir, netName, "claim_dbs", cfg.TemporalRepoPebble.Path)
|
||||||
|
log.Debugf("Open temporal repo: %q", dbPath)
|
||||||
|
tmpRepo, err := temporalrepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open temporal repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes, err := tmpRepo.NodesAt(height)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "read temporal repo at %d", height)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range nodes {
|
||||||
|
fmt.Printf("Name: %s, ", string(name))
|
||||||
|
trie.Dump(string(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Int32Var(&height, "height", 0, "Height")
|
||||||
|
cmd.Flags().StringVar(&name, "name", "", "Name")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
155
claimtrie/cmd/cmd/node.go
Normal file
155
claimtrie/cmd/cmd/node.go
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node/noderepo"
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewNodeCommands())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNodeCommands() *cobra.Command {
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "node",
|
||||||
|
Short: "Replay the application of changes on a node up to certain height",
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.AddCommand(NewNodeDumpCommand())
|
||||||
|
cmd.AddCommand(NewNodeReplayCommand())
|
||||||
|
cmd.AddCommand(NewNodeChildrenCommand())
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNodeDumpCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var height int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "dump",
|
||||||
|
Short: "Replay the application of changes on a node up to certain height",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "claim_dbs", cfg.NodeRepoPebble.Path)
|
||||||
|
log.Debugf("Open node repo: %q", dbPath)
|
||||||
|
repo, err := noderepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open node repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
changes, err := repo.LoadChanges([]byte(name))
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "load commands")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chg := range changes {
|
||||||
|
if chg.Height > height {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
showChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&name, "name", "", "Name")
|
||||||
|
cmd.MarkFlagRequired("name")
|
||||||
|
cmd.Flags().Int32Var(&height, "height", math.MaxInt32, "Height")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNodeReplayCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var name string
|
||||||
|
var height int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "replay",
|
||||||
|
Short: "Replay the changes of <name> up to <height>",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "claim_dbs", cfg.NodeRepoPebble.Path)
|
||||||
|
log.Debugf("Open node repo: %q", dbPath)
|
||||||
|
repo, err := noderepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open node repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
bm, err := node.NewBaseManager(repo)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "create node manager")
|
||||||
|
}
|
||||||
|
|
||||||
|
nm := node.NewNormalizingManager(bm)
|
||||||
|
|
||||||
|
n, err := nm.NodeAt(height, []byte(name))
|
||||||
|
if err != nil || n == nil {
|
||||||
|
return errors.Wrapf(err, "get node: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
showNode(n)
|
||||||
|
n.Close()
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&name, "name", "", "Name")
|
||||||
|
cmd.MarkFlagRequired("name")
|
||||||
|
cmd.Flags().Int32Var(&height, "height", 0, "Height (inclusive)")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNodeChildrenCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var name string
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "children",
|
||||||
|
Short: "Show all the children names of a given node name",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "claim_dbs", cfg.NodeRepoPebble.Path)
|
||||||
|
log.Debugf("Open node repo: %q", dbPath)
|
||||||
|
repo, err := noderepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open node repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn := func(changes []change.Change) bool {
|
||||||
|
fmt.Printf("Name: %s, Height: %d, %d\n", changes[0].Name, changes[0].Height,
|
||||||
|
changes[len(changes)-1].Height)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.IterateChildren([]byte(name), fn)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "iterate children: %s", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().StringVar(&name, "name", "", "Name")
|
||||||
|
cmd.MarkFlagRequired("name")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
55
claimtrie/cmd/cmd/root.go
Normal file
55
claimtrie/cmd/cmd/root.go
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/config"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
log btclog.Logger
|
||||||
|
cfg = config.DefaultConfig
|
||||||
|
netName string
|
||||||
|
dataDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
var rootCmd = NewRootCommand()
|
||||||
|
|
||||||
|
func NewRootCommand() *cobra.Command {
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "claimtrie",
|
||||||
|
Short: "ClaimTrie Command Line Interface",
|
||||||
|
SilenceUsage: true,
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
switch netName {
|
||||||
|
case "mainnet":
|
||||||
|
param.SetNetwork(wire.MainNet)
|
||||||
|
case "testnet":
|
||||||
|
param.SetNetwork(wire.TestNet3)
|
||||||
|
case "regtest":
|
||||||
|
param.SetNetwork(wire.TestNet)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.PersistentFlags().StringVar(&netName, "netname", "mainnet", "Net name")
|
||||||
|
cmd.PersistentFlags().StringVarP(&dataDir, "datadir", "b", cfg.DataDir, "Data dir")
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
func Execute() {
|
||||||
|
|
||||||
|
backendLogger := btclog.NewBackend(os.Stdout)
|
||||||
|
defer os.Stdout.Sync()
|
||||||
|
log = backendLogger.Logger("CMDL")
|
||||||
|
log.SetLevel(btclog.LevelDebug)
|
||||||
|
|
||||||
|
rootCmd.Execute() // nolint : errchk
|
||||||
|
}
|
56
claimtrie/cmd/cmd/temporal.go
Normal file
56
claimtrie/cmd/cmd/temporal.go
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/temporal/temporalrepo"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/errors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
rootCmd.AddCommand(NewTemporalCommand())
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewTemporalCommand() *cobra.Command {
|
||||||
|
|
||||||
|
var fromHeight int32
|
||||||
|
var toHeight int32
|
||||||
|
|
||||||
|
cmd := &cobra.Command{
|
||||||
|
Use: "temporal",
|
||||||
|
Short: "List which nodes are update in a range of heights",
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
RunE: func(cmd *cobra.Command, args []string) error {
|
||||||
|
|
||||||
|
dbPath := filepath.Join(dataDir, netName, "claim_dbs", cfg.TemporalRepoPebble.Path)
|
||||||
|
log.Debugf("Open temporal repo: %s", dbPath)
|
||||||
|
repo, err := temporalrepo.NewPebble(dbPath)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "open temporal repo")
|
||||||
|
}
|
||||||
|
|
||||||
|
for ht := fromHeight; ht < toHeight; ht++ {
|
||||||
|
names, err := repo.NodesAt(ht)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "get node names from temporal")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(names) == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
showTemporalNames(ht, names)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.Flags().Int32Var(&fromHeight, "from", 0, "From height (inclusive)")
|
||||||
|
cmd.Flags().Int32Var(&toHeight, "to", 0, "To height (inclusive)")
|
||||||
|
cmd.Flags().SortFlags = false
|
||||||
|
|
||||||
|
return cmd
|
||||||
|
}
|
76
claimtrie/cmd/cmd/ui.go
Normal file
76
claimtrie/cmd/cmd/ui.go
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
package cmd
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
)
|
||||||
|
|
||||||
|
var status = map[node.Status]string{
|
||||||
|
node.Accepted: "Accepted",
|
||||||
|
node.Activated: "Activated",
|
||||||
|
node.Deactivated: "Deactivated",
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeType(c change.ChangeType) string {
|
||||||
|
switch c {
|
||||||
|
case change.AddClaim:
|
||||||
|
return "AddClaim"
|
||||||
|
case change.SpendClaim:
|
||||||
|
return "SpendClaim"
|
||||||
|
case change.UpdateClaim:
|
||||||
|
return "UpdateClaim"
|
||||||
|
case change.AddSupport:
|
||||||
|
return "AddSupport"
|
||||||
|
case change.SpendSupport:
|
||||||
|
return "SpendSupport"
|
||||||
|
}
|
||||||
|
return "Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
func showChange(chg change.Change) {
|
||||||
|
fmt.Printf(">>> Height: %6d: %s for %04s, %15d, %s - %s\n",
|
||||||
|
chg.Height, changeType(chg.Type), chg.ClaimID, chg.Amount, chg.OutPoint, chg.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showClaim(c *node.Claim, n *node.Node) {
|
||||||
|
mark := " "
|
||||||
|
if c == n.BestClaim {
|
||||||
|
mark = "*"
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("%s C ID: %s, TXO: %s\n %5d/%-5d, Status: %9s, Amount: %15d, Support Amount: %15d\n",
|
||||||
|
mark, c.ClaimID, c.OutPoint, c.AcceptedAt, c.ActiveAt, status[c.Status], c.Amount, n.SupportSums[c.ClaimID.Key()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func showSupport(c *node.Claim) {
|
||||||
|
fmt.Printf(" S id: %s, op: %s, %5d/%-5d, %9s, amt: %15d\n",
|
||||||
|
c.ClaimID, c.OutPoint, c.AcceptedAt, c.ActiveAt, status[c.Status], c.Amount)
|
||||||
|
}
|
||||||
|
|
||||||
|
func showNode(n *node.Node) {
|
||||||
|
|
||||||
|
fmt.Printf("%s\n", strings.Repeat("-", 200))
|
||||||
|
fmt.Printf("Last Node Takeover: %d\n\n", n.TakenOverAt)
|
||||||
|
n.SortClaimsByBid()
|
||||||
|
for _, c := range n.Claims {
|
||||||
|
showClaim(c, n)
|
||||||
|
for _, s := range n.Supports {
|
||||||
|
if s.ClaimID != c.ClaimID {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
showSupport(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fmt.Printf("\n\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func showTemporalNames(height int32, names [][]byte) {
|
||||||
|
fmt.Printf("%7d: %q", height, names[0])
|
||||||
|
for _, name := range names[1:] {
|
||||||
|
fmt.Printf(", %q ", name)
|
||||||
|
}
|
||||||
|
fmt.Printf("\n")
|
||||||
|
}
|
9
claimtrie/cmd/main.go
Normal file
9
claimtrie/cmd/main.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/cmd/cmd"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
47
claimtrie/config/config.go
Normal file
47
claimtrie/config/config.go
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"github.com/btcsuite/btcutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var DefaultConfig = Config{
|
||||||
|
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("chain", false), "data"),
|
||||||
|
|
||||||
|
BlockRepoPebble: pebbleConfig{
|
||||||
|
Path: "blocks_pebble_db",
|
||||||
|
},
|
||||||
|
NodeRepoPebble: pebbleConfig{
|
||||||
|
Path: "node_change_pebble_db",
|
||||||
|
},
|
||||||
|
TemporalRepoPebble: pebbleConfig{
|
||||||
|
Path: "temporal_pebble_db",
|
||||||
|
},
|
||||||
|
MerkleTrieRepoPebble: pebbleConfig{
|
||||||
|
Path: "merkletrie_pebble_db",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config is the container of all configurations.
|
||||||
|
type Config struct {
|
||||||
|
Params param.ClaimTrieParams
|
||||||
|
|
||||||
|
RamTrie bool
|
||||||
|
|
||||||
|
DataDir string
|
||||||
|
|
||||||
|
BlockRepoPebble pebbleConfig
|
||||||
|
NodeRepoPebble pebbleConfig
|
||||||
|
TemporalRepoPebble pebbleConfig
|
||||||
|
MerkleTrieRepoPebble pebbleConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
type pebbleConfig struct {
|
||||||
|
Path string
|
||||||
|
}
|
235
claimtrie/merkletrie/collapsedtrie.go
Normal file
235
claimtrie/merkletrie/collapsedtrie.go
Normal file
|
@ -0,0 +1,235 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
type KeyType []byte
|
||||||
|
|
||||||
|
type collapsedVertex struct { // implements sort.Interface
|
||||||
|
children []*collapsedVertex
|
||||||
|
key KeyType
|
||||||
|
merkleHash *chainhash.Hash
|
||||||
|
claimHash *chainhash.Hash
|
||||||
|
}
|
||||||
|
|
||||||
|
// insertAt inserts v into s at index i and returns the new slice.
|
||||||
|
// https://stackoverflow.com/questions/42746972/golang-insert-to-a-sorted-slice
|
||||||
|
func insertAt(data []*collapsedVertex, i int, v *collapsedVertex) []*collapsedVertex {
|
||||||
|
if i == len(data) {
|
||||||
|
// Insert at end is the easy case.
|
||||||
|
return append(data, v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make space for the inserted element by shifting
|
||||||
|
// values at the insertion index up one index. The call
|
||||||
|
// to append does not allocate memory when cap(data) is
|
||||||
|
// greater than len(data).
|
||||||
|
data = append(data[:i+1], data[i:]...)
|
||||||
|
data[i] = v
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ptn *collapsedVertex) Insert(value *collapsedVertex) *collapsedVertex {
|
||||||
|
// keep it sorted (and sort.Sort is too slow)
|
||||||
|
index := sortSearch(ptn.children, value.key[0])
|
||||||
|
ptn.children = insertAt(ptn.children, index, value)
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// this sort.Search is stolen shamelessly from search.go,
|
||||||
|
// and modified for performance to not need a closure
|
||||||
|
func sortSearch(nodes []*collapsedVertex, b byte) int {
|
||||||
|
i, j := 0, len(nodes)
|
||||||
|
for i < j {
|
||||||
|
h := int(uint(i+j) >> 1) // avoid overflow when computing h
|
||||||
|
// i ≤ h < j
|
||||||
|
if nodes[h].key[0] < b {
|
||||||
|
i = h + 1 // preserves f(i-1) == false
|
||||||
|
} else {
|
||||||
|
j = h // preserves f(j) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// i == j, f(i-1) == false, and f(j) (= f(i)) == true => answer is i.
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
index := sortSearch(ptn.children, key[0])
|
||||||
|
hits := ptn.children[index:]
|
||||||
|
if len(hits) > 0 {
|
||||||
|
return index, hits[0]
|
||||||
|
}
|
||||||
|
return -1, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type collapsedTrie struct {
|
||||||
|
Root *collapsedVertex
|
||||||
|
Nodes int
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewCollapsedTrie() *collapsedTrie {
|
||||||
|
// we never delete the Root node
|
||||||
|
return &collapsedTrie{Root: &collapsedVertex{key: make(KeyType, 0)}, Nodes: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *collapsedTrie) NodeCount() int {
|
||||||
|
return pt.Nodes
|
||||||
|
}
|
||||||
|
|
||||||
|
func matchLength(a, b KeyType) int {
|
||||||
|
minLen := len(a)
|
||||||
|
if len(b) < minLen {
|
||||||
|
minLen = len(b)
|
||||||
|
}
|
||||||
|
for i := 0; i < minLen; i++ {
|
||||||
|
if a[i] != b[i] {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minLen
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *collapsedTrie) insert(value KeyType, node *collapsedVertex) (bool, *collapsedVertex) {
|
||||||
|
index, child := node.findNearest(value)
|
||||||
|
match := 0
|
||||||
|
if index >= 0 { // if we found a child
|
||||||
|
child.merkleHash = nil
|
||||||
|
match = matchLength(value, child.key)
|
||||||
|
if len(value) == match && len(child.key) == match {
|
||||||
|
return false, child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if match <= 0 {
|
||||||
|
pt.Nodes++
|
||||||
|
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}
|
||||||
|
newChild := collapsedVertex{key: child.key[0:match], children: []*collapsedVertex{&grandChild}}
|
||||||
|
child = &newChild
|
||||||
|
node.children[index] = child
|
||||||
|
pt.Nodes++
|
||||||
|
if len(value) == match {
|
||||||
|
return true, child
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pt.insert(value[match:], child)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pt *collapsedTrie) InsertOrFind(value KeyType) (bool, *collapsedVertex) {
|
||||||
|
pt.Root.merkleHash = nil
|
||||||
|
if len(value) <= 0 {
|
||||||
|
return false, pt.Root
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
func find(value KeyType, node *collapsedVertex, pathIndexes *[]int, path *[]*collapsedVertex) *collapsedVertex {
|
||||||
|
index, child := node.findNearest(value)
|
||||||
|
if index < 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
match := matchLength(value, child.key)
|
||||||
|
if len(value) == match && len(child.key) == match {
|
||||||
|
if pathIndexes != nil {
|
||||||
|
*pathIndexes = append(*pathIndexes, index)
|
||||||
|
}
|
||||||
|
if path != nil {
|
||||||
|
*path = append(*path, child)
|
||||||
|
}
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
if match < len(child.key) || match == len(value) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if pathIndexes != nil {
|
||||||
|
*pathIndexes = append(*pathIndexes, index)
|
||||||
|
}
|
||||||
|
if path != nil {
|
||||||
|
*path = append(*path, child)
|
||||||
|
}
|
||||||
|
return find(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)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pathIndexes, path
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
if node == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
iterateFrom(start, node, handler)
|
||||||
|
}
|
||||||
|
|
||||||
|
func iterateFrom(name KeyType, node *collapsedVertex, handler func(name KeyType, value *collapsedVertex) bool) {
|
||||||
|
for handler(name, node) {
|
||||||
|
for _, child := range node.children {
|
||||||
|
iterateFrom(append(name, child.key...), child, handler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
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
|
||||||
|
for ; i > 0; i-- {
|
||||||
|
childCount := len(path[i].children)
|
||||||
|
noClaimData := path[i].claimHash == nil
|
||||||
|
path[i].merkleHash = nil
|
||||||
|
if childCount == 1 && noClaimData {
|
||||||
|
path[i].key = append(path[i].key, path[i].children[0].key...)
|
||||||
|
path[i].claimHash = path[i].children[0].claimHash
|
||||||
|
path[i].children = path[i].children[0].children
|
||||||
|
pt.Nodes--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if childCount == 0 && noClaimData {
|
||||||
|
index := indexes[i]
|
||||||
|
path[i-1].children = append(path[i-1].children[:index], path[i-1].children[index+1:]...)
|
||||||
|
pt.Nodes--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
for ; i >= 0; i-- {
|
||||||
|
path[i].merkleHash = nil
|
||||||
|
}
|
||||||
|
return nodes > pt.Nodes
|
||||||
|
}
|
112
claimtrie/merkletrie/collapsedtrie_test.go
Normal file
112
claimtrie/merkletrie/collapsedtrie_test.go
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func b(value string) []byte { return []byte(value) }
|
||||||
|
func eq(x []byte, y string) bool { return bytes.Equal(x, b(y)) }
|
||||||
|
|
||||||
|
func TestInsertAndErase(t *testing.T) {
|
||||||
|
trie := NewCollapsedTrie()
|
||||||
|
assert.True(t, trie.NodeCount() == 1)
|
||||||
|
inserted, node := trie.InsertOrFind(b("abc"))
|
||||||
|
assert.True(t, inserted)
|
||||||
|
assert.NotNil(t, node)
|
||||||
|
assert.Equal(t, 2, trie.NodeCount())
|
||||||
|
inserted, node = trie.InsertOrFind(b("abd"))
|
||||||
|
assert.True(t, inserted)
|
||||||
|
assert.Equal(t, 4, trie.NodeCount())
|
||||||
|
assert.NotNil(t, node)
|
||||||
|
hit := trie.Find(b("ab"))
|
||||||
|
assert.True(t, eq(hit.key, "ab"))
|
||||||
|
assert.Equal(t, 2, len(hit.children))
|
||||||
|
hit = trie.Find(b("abc"))
|
||||||
|
assert.True(t, eq(hit.key, "c"))
|
||||||
|
hit = trie.Find(b("abd"))
|
||||||
|
assert.True(t, eq(hit.key, "d"))
|
||||||
|
hit = trie.Find(b("a"))
|
||||||
|
assert.Nil(t, hit)
|
||||||
|
indexes, path := trie.FindPath(b("abd"))
|
||||||
|
assert.Equal(t, 3, len(indexes))
|
||||||
|
assert.True(t, eq(path[1].key, "ab"))
|
||||||
|
erased := trie.Erase(b("ab"))
|
||||||
|
assert.False(t, erased)
|
||||||
|
assert.Equal(t, 4, trie.NodeCount())
|
||||||
|
erased = trie.Erase(b("abc"))
|
||||||
|
assert.True(t, erased)
|
||||||
|
assert.Equal(t, 2, trie.NodeCount())
|
||||||
|
erased = trie.Erase(b("abd"))
|
||||||
|
assert.True(t, erased)
|
||||||
|
assert.Equal(t, 1, trie.NodeCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNilNameHandling(t *testing.T) {
|
||||||
|
trie := NewCollapsedTrie()
|
||||||
|
inserted, n := trie.InsertOrFind([]byte("test"))
|
||||||
|
assert.True(t, inserted)
|
||||||
|
n.claimHash = EmptyTrieHash
|
||||||
|
inserted, n = trie.InsertOrFind(nil)
|
||||||
|
assert.False(t, inserted)
|
||||||
|
n.claimHash = EmptyTrieHash
|
||||||
|
n.merkleHash = EmptyTrieHash
|
||||||
|
inserted, n = trie.InsertOrFind(nil)
|
||||||
|
assert.False(t, inserted)
|
||||||
|
assert.NotNil(t, n.claimHash)
|
||||||
|
assert.Nil(t, n.merkleHash)
|
||||||
|
nodeRemoved := trie.Erase(nil)
|
||||||
|
assert.False(t, nodeRemoved)
|
||||||
|
inserted, n = trie.InsertOrFind(nil)
|
||||||
|
assert.False(t, inserted)
|
||||||
|
assert.Nil(t, n.claimHash)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollapsedTriePerformance(t *testing.T) {
|
||||||
|
inserts := 10000 // increase this to 1M for more interesting results
|
||||||
|
data := make([][]byte, inserts)
|
||||||
|
rand.Seed(42)
|
||||||
|
for i := 0; i < inserts; i++ {
|
||||||
|
size := rand.Intn(70) + 4
|
||||||
|
data[i] = make([]byte, size)
|
||||||
|
rand.Read(data[i])
|
||||||
|
for j := 0; j < size; j++ {
|
||||||
|
data[i][j] %= byte(62) // shrink the range to match the old test
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trie := NewCollapsedTrie()
|
||||||
|
// doing my own timing because I couldn't get the B.Run method to work:
|
||||||
|
start := time.Now()
|
||||||
|
for i := 0; i < inserts; i++ {
|
||||||
|
_, node := trie.InsertOrFind(data[i])
|
||||||
|
assert.NotNil(t, node, "Failure at %d of %d", i, inserts)
|
||||||
|
}
|
||||||
|
t.Logf("Insertion in %f sec.", time.Since(start).Seconds())
|
||||||
|
|
||||||
|
start = time.Now()
|
||||||
|
for i := 0; i < inserts; i++ {
|
||||||
|
node := trie.Find(data[i])
|
||||||
|
assert.True(t, bytes.HasSuffix(data[i], node.key), "Failure on %d of %d", i, inserts)
|
||||||
|
}
|
||||||
|
t.Logf("Lookup in %f sec. on %d nodes.", time.Since(start).Seconds(), trie.NodeCount())
|
||||||
|
|
||||||
|
start = time.Now()
|
||||||
|
for i := 0; i < inserts; i++ {
|
||||||
|
indexes, path := trie.FindPath(data[i])
|
||||||
|
assert.True(t, len(indexes) == len(path))
|
||||||
|
assert.True(t, len(path) > 1)
|
||||||
|
assert.True(t, bytes.HasSuffix(data[i], path[len(path)-1].key))
|
||||||
|
}
|
||||||
|
t.Logf("Parents in %f sec.", time.Since(start).Seconds())
|
||||||
|
|
||||||
|
start = time.Now()
|
||||||
|
for i := 0; i < inserts; i++ {
|
||||||
|
trie.Erase(data[i])
|
||||||
|
}
|
||||||
|
t.Logf("Deletion in %f sec.", time.Since(start).Seconds())
|
||||||
|
assert.Equal(t, 1, trie.NodeCount())
|
||||||
|
}
|
278
claimtrie/merkletrie/merkletrie.go
Normal file
278
claimtrie/merkletrie/merkletrie.go
Normal file
|
@ -0,0 +1,278 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"runtime"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// EmptyTrieHash represents the Merkle Hash of an empty PersistentTrie.
|
||||||
|
// "0000000000000000000000000000000000000000000000000000000000000001"
|
||||||
|
EmptyTrieHash = &chainhash.Hash{1}
|
||||||
|
NoChildrenHash = &chainhash.Hash{2}
|
||||||
|
NoClaimsHash = &chainhash.Hash{3}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ValueStore enables PersistentTrie to query node values from different implementations.
|
||||||
|
type ValueStore interface {
|
||||||
|
Hash(name []byte) *chainhash.Hash
|
||||||
|
IterateNames(predicate func(name []byte) bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersistentTrie implements a 256-way prefix tree.
|
||||||
|
type PersistentTrie struct {
|
||||||
|
store ValueStore
|
||||||
|
repo Repo
|
||||||
|
|
||||||
|
root *vertex
|
||||||
|
bufs *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPersistentTrie returns a PersistentTrie.
|
||||||
|
func NewPersistentTrie(store ValueStore, repo Repo) *PersistentTrie {
|
||||||
|
|
||||||
|
tr := &PersistentTrie{
|
||||||
|
store: store,
|
||||||
|
repo: repo,
|
||||||
|
bufs: &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
root: newVertex(EmptyTrieHash),
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRoot drops all resolved nodes in the PersistentTrie, and set the Root with specified hash.
|
||||||
|
func (t *PersistentTrie) SetRoot(h *chainhash.Hash, names [][]byte) {
|
||||||
|
t.root = newVertex(h)
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the nodes along the path to the key.
|
||||||
|
// Each node is resolved or created with their Hash cleared.
|
||||||
|
func (t *PersistentTrie) Update(name []byte, restoreChildren bool) {
|
||||||
|
|
||||||
|
n := t.root
|
||||||
|
for i, ch := range name {
|
||||||
|
if restoreChildren && len(n.childLinks) == 0 {
|
||||||
|
t.resolveChildLinks(n, name[:i])
|
||||||
|
}
|
||||||
|
if n.childLinks[ch] == nil {
|
||||||
|
n.childLinks[ch] = newVertex(nil)
|
||||||
|
}
|
||||||
|
n.merkleHash = nil
|
||||||
|
n = n.childLinks[ch]
|
||||||
|
}
|
||||||
|
|
||||||
|
if restoreChildren && len(n.childLinks) == 0 {
|
||||||
|
t.resolveChildLinks(n, name)
|
||||||
|
}
|
||||||
|
n.hasValue = true
|
||||||
|
n.merkleHash = nil
|
||||||
|
n.claimsHash = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveChildLinks updates the links on n
|
||||||
|
func (t *PersistentTrie) resolveChildLinks(n *vertex, key []byte) {
|
||||||
|
|
||||||
|
if n.merkleHash == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
b := t.bufs.Get().(*bytes.Buffer)
|
||||||
|
defer t.bufs.Put(b)
|
||||||
|
b.Reset()
|
||||||
|
b.Write(key)
|
||||||
|
b.Write(n.merkleHash[:])
|
||||||
|
|
||||||
|
result, closer, err := t.repo.Get(b.Bytes())
|
||||||
|
if result == nil {
|
||||||
|
return
|
||||||
|
} else if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer closer.Close()
|
||||||
|
|
||||||
|
nb := nbuf(result)
|
||||||
|
n.hasValue, n.claimsHash = nb.hasValue()
|
||||||
|
for i := 0; i < nb.entries(); i++ {
|
||||||
|
p, h := nb.entry(i)
|
||||||
|
n.childLinks[p] = newVertex(h)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// MerkleHash returns the Merkle Hash of the PersistentTrie.
|
||||||
|
// All nodes must have been resolved before calling this function.
|
||||||
|
func (t *PersistentTrie) MerkleHash() *chainhash.Hash {
|
||||||
|
buf := make([]byte, 0, 256)
|
||||||
|
if h := t.merkle(buf, t.root); h == nil {
|
||||||
|
return EmptyTrieHash
|
||||||
|
}
|
||||||
|
return t.root.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
// merkle recursively resolves the hashes of the node.
|
||||||
|
// All nodes must have been resolved before calling this function.
|
||||||
|
func (t *PersistentTrie) merkle(prefix []byte, v *vertex) *chainhash.Hash {
|
||||||
|
if v.merkleHash != nil {
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
b := t.bufs.Get().(*bytes.Buffer)
|
||||||
|
defer t.bufs.Put(b)
|
||||||
|
b.Reset()
|
||||||
|
|
||||||
|
keys := keysInOrder(v)
|
||||||
|
|
||||||
|
for _, ch := range keys {
|
||||||
|
child := v.childLinks[ch]
|
||||||
|
if child == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := append(prefix, ch)
|
||||||
|
h := t.merkle(p, child)
|
||||||
|
if h != nil {
|
||||||
|
b.WriteByte(ch) // nolint : errchk
|
||||||
|
b.Write(h[:]) // nolint : errchk
|
||||||
|
}
|
||||||
|
if h == nil || len(prefix) > 4 { // TODO: determine the right number here
|
||||||
|
delete(v.childLinks, ch) // keep the RAM down (they get recreated on Update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.hasValue {
|
||||||
|
claimHash := v.claimsHash
|
||||||
|
if claimHash == nil {
|
||||||
|
claimHash = t.store.Hash(prefix)
|
||||||
|
v.claimsHash = claimHash
|
||||||
|
}
|
||||||
|
if claimHash != nil {
|
||||||
|
b.Write(claimHash[:])
|
||||||
|
} else {
|
||||||
|
v.hasValue = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Len() > 0 {
|
||||||
|
h := chainhash.DoubleHashH(b.Bytes())
|
||||||
|
v.merkleHash = &h
|
||||||
|
t.repo.Set(append(prefix, h[:]...), b.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func keysInOrder(v *vertex) []byte {
|
||||||
|
keys := make([]byte, 0, len(v.childLinks))
|
||||||
|
for key := range v.childLinks {
|
||||||
|
keys = append(keys, key)
|
||||||
|
}
|
||||||
|
sort.Slice(keys, func(i, j int) bool { return keys[i] < keys[j] })
|
||||||
|
return keys
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PersistentTrie) MerkleHashAllClaims() *chainhash.Hash {
|
||||||
|
buf := make([]byte, 0, 256)
|
||||||
|
if h := t.merkleAllClaims(buf, t.root); h == nil {
|
||||||
|
return EmptyTrieHash
|
||||||
|
}
|
||||||
|
return t.root.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PersistentTrie) merkleAllClaims(prefix []byte, v *vertex) *chainhash.Hash {
|
||||||
|
if v.merkleHash != nil {
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
b := t.bufs.Get().(*bytes.Buffer)
|
||||||
|
defer t.bufs.Put(b)
|
||||||
|
b.Reset()
|
||||||
|
|
||||||
|
keys := keysInOrder(v)
|
||||||
|
childHashes := make([]*chainhash.Hash, 0, len(keys))
|
||||||
|
for _, ch := range keys {
|
||||||
|
n := v.childLinks[ch]
|
||||||
|
if n == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
p := append(prefix, ch)
|
||||||
|
h := t.merkleAllClaims(p, n)
|
||||||
|
if h != nil {
|
||||||
|
childHashes = append(childHashes, h)
|
||||||
|
b.WriteByte(ch) // nolint : errchk
|
||||||
|
b.Write(h[:]) // nolint : errchk
|
||||||
|
}
|
||||||
|
if h == nil || len(prefix) > 4 { // TODO: determine the right number here
|
||||||
|
delete(v.childLinks, ch) // keep the RAM down (they get recreated on Update)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.hasValue {
|
||||||
|
if v.claimsHash == nil {
|
||||||
|
v.claimsHash = t.store.Hash(prefix)
|
||||||
|
v.hasValue = v.claimsHash != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(childHashes) > 1 || v.claimsHash != nil { // yeah, about that 1 there -- old code used the condensed trie
|
||||||
|
left := NoChildrenHash
|
||||||
|
if len(childHashes) > 0 {
|
||||||
|
left = node.ComputeMerkleRoot(childHashes)
|
||||||
|
}
|
||||||
|
right := NoClaimsHash
|
||||||
|
if v.claimsHash != nil {
|
||||||
|
b.Write(v.claimsHash[:]) // for Has Value, nolint : errchk
|
||||||
|
right = v.claimsHash
|
||||||
|
}
|
||||||
|
|
||||||
|
h := node.HashMerkleBranches(left, right)
|
||||||
|
v.merkleHash = h
|
||||||
|
t.repo.Set(append(prefix, h[:]...), b.Bytes())
|
||||||
|
} else if len(childHashes) == 1 {
|
||||||
|
v.merkleHash = childHashes[0] // pass it up the tree
|
||||||
|
t.repo.Set(append(prefix, v.merkleHash[:]...), b.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PersistentTrie) Close() error {
|
||||||
|
return errors.WithStack(t.repo.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PersistentTrie) Dump(s string) {
|
||||||
|
// TODO: this function is in the wrong spot; either it goes with its caller or it needs to be a generic iterator
|
||||||
|
// we don't want fmt used in here either way
|
||||||
|
|
||||||
|
v := t.root
|
||||||
|
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
t.resolveChildLinks(v, []byte(s[:i]))
|
||||||
|
ch := s[i]
|
||||||
|
v = v.childLinks[ch]
|
||||||
|
if v == nil {
|
||||||
|
fmt.Printf("Missing child at %s\n", s[:i+1])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.resolveChildLinks(v, []byte(s))
|
||||||
|
|
||||||
|
fmt.Printf("Node hash: %s, has value: %t\n", v.merkleHash.String(), v.hasValue)
|
||||||
|
|
||||||
|
for key, value := range v.childLinks {
|
||||||
|
fmt.Printf(" Child %s hash: %s\n", string(key), value.merkleHash.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *PersistentTrie) Flush() error {
|
||||||
|
return t.repo.Flush()
|
||||||
|
}
|
25
claimtrie/merkletrie/merkletrie_test.go
Normal file
25
claimtrie/merkletrie/merkletrie_test.go
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestName(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
target, _ := chainhash.NewHashFromStr("e9ffb584c62449f157c8be88257bd1eebb2d8ef824f5c86b43c4f8fd9e800d6a")
|
||||||
|
|
||||||
|
data := []*chainhash.Hash{EmptyTrieHash}
|
||||||
|
root := node.ComputeMerkleRoot(data)
|
||||||
|
r.True(EmptyTrieHash.IsEqual(root))
|
||||||
|
|
||||||
|
data = append(data, NoChildrenHash, NoClaimsHash)
|
||||||
|
root = node.ComputeMerkleRoot(data)
|
||||||
|
r.True(target.IsEqual(root))
|
||||||
|
}
|
66
claimtrie/merkletrie/merkletrierepo/pebble.go
Normal file
66
claimtrie/merkletrie/merkletrierepo/pebble.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package merkletrierepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pebble struct {
|
||||||
|
db *pebble.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPebble(path string) (*Pebble, error) {
|
||||||
|
|
||||||
|
cache := pebble.NewCache(512 << 20)
|
||||||
|
//defer cache.Unref()
|
||||||
|
//
|
||||||
|
//go func() {
|
||||||
|
// tick := time.NewTicker(60 * time.Second)
|
||||||
|
// for range tick.C {
|
||||||
|
//
|
||||||
|
// m := cache.Metrics()
|
||||||
|
// fmt.Printf("cnt: %s, objs: %s, hits: %s, miss: %s, hitrate: %.2f\n",
|
||||||
|
// humanize.Bytes(uint64(m.Size)),
|
||||||
|
// humanize.Comma(m.Count),
|
||||||
|
// humanize.Comma(m.Hits),
|
||||||
|
// humanize.Comma(m.Misses),
|
||||||
|
// float64(m.Hits)/float64(m.Hits+m.Misses))
|
||||||
|
//
|
||||||
|
// }
|
||||||
|
//}()
|
||||||
|
|
||||||
|
db, err := pebble.Open(path, &pebble.Options{Cache: cache, BytesPerSync: 32 << 20})
|
||||||
|
repo := &Pebble{db: db}
|
||||||
|
|
||||||
|
return repo, errors.Wrapf(err, "unable to open %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Get(key []byte) ([]byte, io.Closer, error) {
|
||||||
|
d, c, e := repo.db.Get(key)
|
||||||
|
if e == pebble.ErrNotFound {
|
||||||
|
return nil, c, nil
|
||||||
|
}
|
||||||
|
return d, c, e
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Set(key, value []byte) error {
|
||||||
|
return repo.db.Set(key, value, pebble.NoSync)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Close() error {
|
||||||
|
|
||||||
|
err := repo.db.Flush()
|
||||||
|
if err != nil {
|
||||||
|
// if we fail to close are we going to try again later?
|
||||||
|
return errors.Wrap(err, "on flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.db.Close()
|
||||||
|
return errors.Wrap(err, "on close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Flush() error {
|
||||||
|
_, err := repo.db.AsyncFlush()
|
||||||
|
return err
|
||||||
|
}
|
163
claimtrie/merkletrie/ramtrie.go
Normal file
163
claimtrie/merkletrie/ramtrie.go
Normal file
|
@ -0,0 +1,163 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"runtime"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
)
|
||||||
|
|
||||||
|
type MerkleTrie interface {
|
||||||
|
SetRoot(h *chainhash.Hash, names [][]byte)
|
||||||
|
Update(name []byte, restoreChildren bool)
|
||||||
|
MerkleHash() *chainhash.Hash
|
||||||
|
MerkleHashAllClaims() *chainhash.Hash
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type RamTrie struct {
|
||||||
|
collapsedTrie
|
||||||
|
store ValueStore
|
||||||
|
bufs *sync.Pool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRamTrie(s ValueStore) *RamTrie {
|
||||||
|
return &RamTrie{
|
||||||
|
store: s,
|
||||||
|
bufs: &sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return new(bytes.Buffer)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
collapsedTrie: collapsedTrie{Root: &collapsedVertex{}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) SetRoot(h *chainhash.Hash, names [][]byte) {
|
||||||
|
if rt.Root.merkleHash.IsEqual(h) {
|
||||||
|
runtime.GC()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// if names is nil then we need to query all names
|
||||||
|
if names == nil {
|
||||||
|
node.LogOnce("Building the entire claim trie in RAM...") // could put this in claimtrie.go
|
||||||
|
|
||||||
|
//should technically clear the old trie first:
|
||||||
|
if rt.Nodes > 1 {
|
||||||
|
rt.Root = &collapsedVertex{key: make(KeyType, 0)}
|
||||||
|
rt.Nodes = 1
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
c := 0
|
||||||
|
rt.store.IterateNames(func(name []byte) bool {
|
||||||
|
rt.Update(name, false)
|
||||||
|
c++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
node.LogOnce("Completed claim trie construction. Name count: " + strconv.Itoa(c))
|
||||||
|
} else {
|
||||||
|
for _, name := range names {
|
||||||
|
rt.Update(name, false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
runtime.GC()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) Update(name []byte, _ bool) {
|
||||||
|
h := rt.store.Hash(name)
|
||||||
|
if h == nil {
|
||||||
|
rt.Erase(name)
|
||||||
|
} else {
|
||||||
|
_, n := rt.InsertOrFind(name)
|
||||||
|
n.claimHash = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) MerkleHash() *chainhash.Hash {
|
||||||
|
if h := rt.merkleHash(rt.Root); h == nil {
|
||||||
|
return EmptyTrieHash
|
||||||
|
}
|
||||||
|
return rt.Root.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) merkleHash(v *collapsedVertex) *chainhash.Hash {
|
||||||
|
if v.merkleHash != nil {
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
b := rt.bufs.Get().(*bytes.Buffer)
|
||||||
|
defer rt.bufs.Put(b)
|
||||||
|
b.Reset()
|
||||||
|
|
||||||
|
for _, ch := range v.children {
|
||||||
|
h := rt.merkleHash(ch) // h is a pointer; don't destroy its data
|
||||||
|
b.WriteByte(ch.key[0]) // nolint : errchk
|
||||||
|
b.Write(rt.completeHash(h, ch.key)) // nolint : errchk
|
||||||
|
}
|
||||||
|
|
||||||
|
if v.claimHash != nil {
|
||||||
|
b.Write(v.claimHash[:])
|
||||||
|
}
|
||||||
|
|
||||||
|
if b.Len() > 0 {
|
||||||
|
h := chainhash.DoubleHashH(b.Bytes())
|
||||||
|
v.merkleHash = &h
|
||||||
|
}
|
||||||
|
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) completeHash(h *chainhash.Hash, childKey KeyType) []byte {
|
||||||
|
var data [chainhash.HashSize + 1]byte
|
||||||
|
copy(data[1:], h[:])
|
||||||
|
for i := len(childKey) - 1; i > 0; i-- {
|
||||||
|
data[0] = childKey[i]
|
||||||
|
copy(data[1:], chainhash.DoubleHashB(data[:]))
|
||||||
|
}
|
||||||
|
return data[1:]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) MerkleHashAllClaims() *chainhash.Hash {
|
||||||
|
if h := rt.merkleHashAllClaims(rt.Root); h == nil {
|
||||||
|
return EmptyTrieHash
|
||||||
|
}
|
||||||
|
return rt.Root.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) merkleHashAllClaims(v *collapsedVertex) *chainhash.Hash {
|
||||||
|
if v.merkleHash != nil {
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
childHashes := make([]*chainhash.Hash, 0, len(v.children))
|
||||||
|
for _, ch := range v.children {
|
||||||
|
h := rt.merkleHashAllClaims(ch)
|
||||||
|
childHashes = append(childHashes, h)
|
||||||
|
}
|
||||||
|
|
||||||
|
claimHash := NoClaimsHash
|
||||||
|
if v.claimHash != nil {
|
||||||
|
claimHash = v.claimHash
|
||||||
|
} else if len(childHashes) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
childHash := NoChildrenHash
|
||||||
|
if len(childHashes) > 0 {
|
||||||
|
// this shouldn't be referencing node; where else can we put this merkle root func?
|
||||||
|
childHash = node.ComputeMerkleRoot(childHashes)
|
||||||
|
}
|
||||||
|
|
||||||
|
v.merkleHash = node.HashMerkleBranches(childHash, claimHash)
|
||||||
|
return v.merkleHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rt *RamTrie) Flush() error {
|
||||||
|
return nil
|
||||||
|
}
|
13
claimtrie/merkletrie/repo.go
Normal file
13
claimtrie/merkletrie/repo.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repo defines APIs for PersistentTrie to access persistence layer.
|
||||||
|
type Repo interface {
|
||||||
|
Get(key []byte) ([]byte, io.Closer, error)
|
||||||
|
Set(key, value []byte) error
|
||||||
|
Close() error
|
||||||
|
Flush() error
|
||||||
|
}
|
44
claimtrie/merkletrie/vertex.go
Normal file
44
claimtrie/merkletrie/vertex.go
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
package merkletrie
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
)
|
||||||
|
|
||||||
|
type vertex struct {
|
||||||
|
merkleHash *chainhash.Hash
|
||||||
|
claimsHash *chainhash.Hash
|
||||||
|
childLinks map[byte]*vertex
|
||||||
|
hasValue bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func newVertex(hash *chainhash.Hash) *vertex {
|
||||||
|
return &vertex{childLinks: map[byte]*vertex{}, merkleHash: hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
type nbuf []byte
|
||||||
|
|
||||||
|
func (nb nbuf) entries() int {
|
||||||
|
return len(nb) / 33
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nb nbuf) entry(i int) (byte, *chainhash.Hash) {
|
||||||
|
h := chainhash.Hash{}
|
||||||
|
copy(h[:], nb[33*i+1:])
|
||||||
|
return nb[33*i], &h
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nb nbuf) hasValue() (bool, *chainhash.Hash) {
|
||||||
|
if len(nb)%33 == 0 {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
h := chainhash.Hash{}
|
||||||
|
copy(h[:], nb[len(nb)-32:])
|
||||||
|
return true, &h
|
||||||
|
}
|
92
claimtrie/node/claim.go
Normal file
92
claimtrie/node/claim.go
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Status int
|
||||||
|
|
||||||
|
const (
|
||||||
|
Accepted Status = iota
|
||||||
|
Activated
|
||||||
|
Deactivated
|
||||||
|
)
|
||||||
|
|
||||||
|
// Claim defines a structure of stake, which could be a Claim or Support.
|
||||||
|
type Claim struct {
|
||||||
|
OutPoint wire.OutPoint
|
||||||
|
ClaimID change.ClaimID
|
||||||
|
Amount int64
|
||||||
|
// CreatedAt int32 // the very first block, unused at present
|
||||||
|
AcceptedAt int32 // the latest update height
|
||||||
|
ActiveAt int32 // AcceptedAt + actual delay
|
||||||
|
VisibleAt int32
|
||||||
|
|
||||||
|
Status Status `msgpack:",omitempty"`
|
||||||
|
Sequence int32 `msgpack:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claim) setOutPoint(op wire.OutPoint) *Claim {
|
||||||
|
c.OutPoint = op
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claim) SetAmt(amt int64) *Claim {
|
||||||
|
c.Amount = amt
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claim) setAccepted(height int32) *Claim {
|
||||||
|
c.AcceptedAt = height
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claim) setActiveAt(height int32) *Claim {
|
||||||
|
c.ActiveAt = height
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Claim) setStatus(status Status) *Claim {
|
||||||
|
c.Status = status
|
||||||
|
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[:]); {
|
||||||
|
case cmp < 0:
|
||||||
|
return true
|
||||||
|
case cmp > 0:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return a.Index < b.Index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOutPointFromString(str string) *wire.OutPoint {
|
||||||
|
|
||||||
|
f := strings.Split(str, ":")
|
||||||
|
if len(f) != 2 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
hash, _ := chainhash.NewHashFromStr(f[0])
|
||||||
|
idx, _ := strconv.Atoi(f[1])
|
||||||
|
|
||||||
|
return wire.NewOutPoint(hash, uint32(idx))
|
||||||
|
}
|
33
claimtrie/node/claim_list.go
Normal file
33
claimtrie/node/claim_list.go
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ClaimList []*Claim
|
||||||
|
|
||||||
|
type comparator func(c *Claim) bool
|
||||||
|
|
||||||
|
func byID(id change.ClaimID) comparator {
|
||||||
|
return func(c *Claim) bool {
|
||||||
|
return c.ClaimID == id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func byOut(out wire.OutPoint) comparator {
|
||||||
|
return func(c *Claim) bool {
|
||||||
|
return c.OutPoint == out // assuming value comparison
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l ClaimList) find(cmp comparator) *Claim {
|
||||||
|
|
||||||
|
for i := range l {
|
||||||
|
if cmp(l[i]) {
|
||||||
|
return l[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
29
claimtrie/node/hashfunc.go
Normal file
29
claimtrie/node/hashfunc.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import "github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
|
||||||
|
func HashMerkleBranches(left *chainhash.Hash, right *chainhash.Hash) *chainhash.Hash {
|
||||||
|
// Concatenate the left and right nodes.
|
||||||
|
var hash [chainhash.HashSize * 2]byte
|
||||||
|
copy(hash[:chainhash.HashSize], left[:])
|
||||||
|
copy(hash[chainhash.HashSize:], right[:])
|
||||||
|
|
||||||
|
newHash := chainhash.DoubleHashH(hash[:])
|
||||||
|
return &newHash
|
||||||
|
}
|
||||||
|
|
||||||
|
func ComputeMerkleRoot(hashes []*chainhash.Hash) *chainhash.Hash {
|
||||||
|
if len(hashes) <= 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
for len(hashes) > 1 {
|
||||||
|
if (len(hashes) & 1) > 0 { // odd count
|
||||||
|
hashes = append(hashes, hashes[len(hashes)-1])
|
||||||
|
}
|
||||||
|
for i := 0; i < len(hashes); i += 2 { // TODO: parallelize this loop (or use a lib that does it)
|
||||||
|
hashes[i>>1] = HashMerkleBranches(hashes[i], hashes[i+1])
|
||||||
|
}
|
||||||
|
hashes = hashes[:len(hashes)>>1]
|
||||||
|
}
|
||||||
|
return hashes[0]
|
||||||
|
}
|
41
claimtrie/node/log.go
Normal file
41
claimtrie/node/log.go
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btclog"
|
||||||
|
)
|
||||||
|
|
||||||
|
// log is a logger that is initialized with no output filters. This
|
||||||
|
// means the package will not perform any logging by default until the caller
|
||||||
|
// requests it.
|
||||||
|
var log btclog.Logger
|
||||||
|
|
||||||
|
// The default amount of logging is none.
|
||||||
|
func init() {
|
||||||
|
DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output. Logging output is disabled
|
||||||
|
// by default until either UseLogger or SetLogWriter are called.
|
||||||
|
func DisableLog() {
|
||||||
|
log = btclog.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info.
|
||||||
|
// This should be used in preference to SetLogWriter if the caller is also
|
||||||
|
// using btclog.
|
||||||
|
func UseLogger(logger btclog.Logger) {
|
||||||
|
log = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
var loggedStrings = map[string]bool{} // is this gonna get too large?
|
||||||
|
func LogOnce(s string) {
|
||||||
|
if loggedStrings[s] {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loggedStrings[s] = true
|
||||||
|
log.Info(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Warn(s string) {
|
||||||
|
log.Warn(s)
|
||||||
|
}
|
514
claimtrie/node/manager.go
Normal file
514
claimtrie/node/manager.go
Normal file
|
@ -0,0 +1,514 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"container/list"
|
||||||
|
"crypto/sha256"
|
||||||
|
"encoding/binary"
|
||||||
|
"fmt"
|
||||||
|
"sort"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Manager interface {
|
||||||
|
AppendChange(chg change.Change) error
|
||||||
|
IncrementHeightTo(height int32) ([][]byte, error)
|
||||||
|
DecrementHeightTo(affectedNames [][]byte, height int32) error
|
||||||
|
Height() int32
|
||||||
|
Close() error
|
||||||
|
NodeAt(height int32, name []byte) (*Node, error)
|
||||||
|
NextUpdateHeightOfNode(name []byte) ([]byte, int32)
|
||||||
|
IterateNames(predicate func(name []byte) bool)
|
||||||
|
Hash(name []byte) *chainhash.Hash
|
||||||
|
Flush() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeCacheLeaf struct {
|
||||||
|
node *Node
|
||||||
|
key string
|
||||||
|
}
|
||||||
|
|
||||||
|
type nodeCache struct {
|
||||||
|
elements map[string]*list.Element
|
||||||
|
data *list.List
|
||||||
|
maxElements int
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNodeCache(size int) *nodeCache {
|
||||||
|
return &nodeCache{elements: make(map[string]*list.Element, size),
|
||||||
|
data: list.New(),
|
||||||
|
maxElements: size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *nodeCache) Get(key string) *Node {
|
||||||
|
element := nc.elements[key]
|
||||||
|
if element != nil {
|
||||||
|
nc.data.MoveToFront(element)
|
||||||
|
return element.Value.(nodeCacheLeaf).node
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *nodeCache) Put(key string, element *Node) {
|
||||||
|
existing := nc.elements[key]
|
||||||
|
if existing != nil {
|
||||||
|
existing.Value = nodeCacheLeaf{element, key}
|
||||||
|
nc.data.MoveToFront(existing)
|
||||||
|
} else if len(nc.elements) >= nc.maxElements {
|
||||||
|
existing = nc.data.Back()
|
||||||
|
delete(nc.elements, existing.Value.(nodeCacheLeaf).key)
|
||||||
|
existing.Value.(nodeCacheLeaf).node.Close()
|
||||||
|
existing.Value = nodeCacheLeaf{element, key}
|
||||||
|
nc.data.MoveToFront(existing)
|
||||||
|
nc.elements[key] = existing
|
||||||
|
} else {
|
||||||
|
nc.elements[key] = nc.data.PushFront(nodeCacheLeaf{element, key})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nc *nodeCache) Delete(key string) {
|
||||||
|
existing := nc.elements[key]
|
||||||
|
if existing != nil {
|
||||||
|
delete(nc.elements, key)
|
||||||
|
existing.Value.(nodeCacheLeaf).node.Close()
|
||||||
|
nc.data.Remove(existing)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type BaseManager struct {
|
||||||
|
repo Repo
|
||||||
|
|
||||||
|
height int32
|
||||||
|
cache *nodeCache
|
||||||
|
changes []change.Change
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBaseManager(repo Repo) (*BaseManager, error) {
|
||||||
|
|
||||||
|
nm := &BaseManager{
|
||||||
|
repo: repo,
|
||||||
|
cache: newNodeCache(param.ActiveParams.MaxNodeManagerCacheSize),
|
||||||
|
}
|
||||||
|
|
||||||
|
return nm, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) NodeAt(height int32, name []byte) (*Node, error) {
|
||||||
|
|
||||||
|
changes, err := nm.repo.LoadChanges(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "in load changes")
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := nm.newNodeFromChanges(changes, height)
|
||||||
|
if err != nil {
|
||||||
|
if n != nil {
|
||||||
|
n.Close()
|
||||||
|
}
|
||||||
|
return nil, errors.Wrap(err, "in new node")
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Node returns a node at the current height.
|
||||||
|
// The returned node may have pending changes.
|
||||||
|
func (nm *BaseManager) node(name []byte) (*Node, error) {
|
||||||
|
|
||||||
|
nameStr := string(name)
|
||||||
|
n := nm.cache.Get(nameStr)
|
||||||
|
if n != nil {
|
||||||
|
return n.AdjustTo(nm.height, -1, name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := nm.NodeAt(nm.height, name)
|
||||||
|
|
||||||
|
if n != nil && err == nil {
|
||||||
|
nm.cache.Put(nameStr, n)
|
||||||
|
}
|
||||||
|
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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()
|
||||||
|
previous := changes[0].Height
|
||||||
|
count := len(changes)
|
||||||
|
|
||||||
|
for i, chg := range changes {
|
||||||
|
if chg.Height < previous {
|
||||||
|
panic("expected the changes to be in order by height")
|
||||||
|
}
|
||||||
|
if chg.Height > height {
|
||||||
|
count = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if previous < chg.Height {
|
||||||
|
n.AdjustTo(previous, chg.Height-1, chg.Name) // update bids and activation
|
||||||
|
previous = chg.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := nm.getDelayForName(n, chg)
|
||||||
|
err := n.ApplyChange(chg, delay)
|
||||||
|
if err != nil {
|
||||||
|
n.Close()
|
||||||
|
return nil, errors.Wrap(err, "in apply change")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if count <= 0 {
|
||||||
|
n.Close()
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
lastChange := changes[count-1]
|
||||||
|
return n.AdjustTo(lastChange.Height, height, lastChange.Name), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) AppendChange(chg change.Change) error {
|
||||||
|
|
||||||
|
nm.cache.Delete(string(chg.Name))
|
||||||
|
nm.changes = append(nm.changes, chg)
|
||||||
|
|
||||||
|
// worth putting in this kind of thing pre-emptively?
|
||||||
|
// log.Debugf("CHG: %d, %s, %v, %s, %d", chg.Height, chg.Name, chg.Type, chg.ClaimID, chg.Amount)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectChildNames(changes []change.Change) {
|
||||||
|
// we need to determine which children (names that start with the same name) go with which change
|
||||||
|
// if we have the names in order then we can avoid iterating through all names in the change list
|
||||||
|
// and we can possibly reuse the previous list.
|
||||||
|
|
||||||
|
// what would happen in the old code:
|
||||||
|
// spending a claim (which happens before every update) could remove a node from the cached trie
|
||||||
|
// in which case we would fall back on the data from the previous block (where it obviously wasn't spent).
|
||||||
|
// It would only delete the node if it had no children, but have even some rare situations
|
||||||
|
// Where all of the children happen to be deleted first. That's what we must detect here.
|
||||||
|
|
||||||
|
// Algorithm:
|
||||||
|
// For each non-spend change
|
||||||
|
// Loop through all the spends before you and add them to your child list if they are your child
|
||||||
|
|
||||||
|
type pair struct {
|
||||||
|
name string
|
||||||
|
order int
|
||||||
|
}
|
||||||
|
|
||||||
|
spends := make([]pair, 0, len(changes))
|
||||||
|
for i := range changes {
|
||||||
|
t := changes[i].Type
|
||||||
|
if t != change.SpendClaim {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
spends = append(spends, pair{string(changes[i].Name), i})
|
||||||
|
}
|
||||||
|
sort.Slice(spends, func(i, j int) bool {
|
||||||
|
return spends[i].name < spends[j].name
|
||||||
|
})
|
||||||
|
|
||||||
|
for i := range changes {
|
||||||
|
t := changes[i].Type
|
||||||
|
if t == change.SpendClaim || t == change.SpendSupport {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
a := string(changes[i].Name)
|
||||||
|
sc := map[string]bool{}
|
||||||
|
idx := sort.Search(len(spends), func(k int) bool {
|
||||||
|
return spends[k].name > a
|
||||||
|
})
|
||||||
|
for idx < len(spends) {
|
||||||
|
b := spends[idx].name
|
||||||
|
if len(b) <= len(a) || a != b[:len(a)] {
|
||||||
|
break // since they're ordered alphabetically, we should be able to break out once we're past matches
|
||||||
|
}
|
||||||
|
if spends[idx].order < i {
|
||||||
|
sc[b] = true
|
||||||
|
}
|
||||||
|
idx++
|
||||||
|
}
|
||||||
|
changes[i].SpentChildren = sc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to understand the above function, it may be helpful to refer to the slower implementation:
|
||||||
|
//func collectChildNamesSlow(changes []change.Change) {
|
||||||
|
// for i := range changes {
|
||||||
|
// t := changes[i].Type
|
||||||
|
// if t == change.SpendClaim || t == change.SpendSupport {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// a := changes[i].Name
|
||||||
|
// sc := map[string]bool{}
|
||||||
|
// for j := 0; j < i; j++ {
|
||||||
|
// t = changes[j].Type
|
||||||
|
// if t != change.SpendClaim {
|
||||||
|
// continue
|
||||||
|
// }
|
||||||
|
// b := changes[j].Name
|
||||||
|
// if len(b) >= len(a) && bytes.Equal(a, b[:len(a)]) {
|
||||||
|
// sc[string(b)] = true
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// changes[i].SpentChildren = sc
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
func (nm *BaseManager) IncrementHeightTo(height int32) ([][]byte, error) {
|
||||||
|
|
||||||
|
if height <= nm.height {
|
||||||
|
panic("invalid height")
|
||||||
|
}
|
||||||
|
|
||||||
|
if height >= param.ActiveParams.MaxRemovalWorkaroundHeight {
|
||||||
|
// not technically needed until block 884430, but to be true to the arbitrary rollback length...
|
||||||
|
collectChildNames(nm.changes)
|
||||||
|
}
|
||||||
|
|
||||||
|
names := make([][]byte, 0, len(nm.changes))
|
||||||
|
for i := range nm.changes {
|
||||||
|
names = append(names, nm.changes[i].Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
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.
|
||||||
|
if len(nm.changes) > 1000 { // TODO: determine a good number here
|
||||||
|
nm.changes = nil // release the RAM
|
||||||
|
} else {
|
||||||
|
nm.changes = nm.changes[:0]
|
||||||
|
}
|
||||||
|
nm.height = height
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) DecrementHeightTo(affectedNames [][]byte, height int32) error {
|
||||||
|
if height >= nm.height {
|
||||||
|
return errors.Errorf("invalid height of %d for %d", height, nm.height)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, name := range affectedNames {
|
||||||
|
nm.cache.Delete(string(name))
|
||||||
|
if err := nm.repo.DropChanges(name, height); err != nil {
|
||||||
|
return errors.Wrap(err, "in drop changes")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nm.height = height
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) getDelayForName(n *Node, chg change.Change) int32 {
|
||||||
|
// Note: we don't consider the active status of BestClaim here on purpose.
|
||||||
|
// That's because we deactivate and reactivate as part of claim updates.
|
||||||
|
// However, the final status will be accounted for when we compute the takeover heights;
|
||||||
|
// claims may get activated early at that point.
|
||||||
|
|
||||||
|
hasBest := n.BestClaim != nil
|
||||||
|
if hasBest && n.BestClaim.ClaimID == chg.ClaimID {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if chg.ActiveHeight >= chg.Height { // ActiveHeight is usually unset (aka, zero)
|
||||||
|
return chg.ActiveHeight - chg.Height
|
||||||
|
}
|
||||||
|
if !hasBest {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
delay := calculateDelay(chg.Height, n.TakenOverAt)
|
||||||
|
if delay > 0 && nm.aWorkaroundIsNeeded(n, chg) {
|
||||||
|
if chg.Height >= nm.height {
|
||||||
|
LogOnce(fmt.Sprintf("Delay workaround applies to %s at %d, ClaimID: %s",
|
||||||
|
chg.Name, chg.Height, chg.ClaimID))
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasZeroActiveClaims(n *Node) bool {
|
||||||
|
// this isn't quite the same as having an active best (since that is only updated after all changes are processed)
|
||||||
|
for _, c := range n.Claims {
|
||||||
|
if c.Status == Activated {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// aWorkaroundIsNeeded handles bugs that existed in previous versions
|
||||||
|
func (nm *BaseManager) aWorkaroundIsNeeded(n *Node, chg change.Change) bool {
|
||||||
|
|
||||||
|
if chg.Type == change.SpendClaim || chg.Type == change.SpendSupport {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if chg.Height >= param.ActiveParams.MaxRemovalWorkaroundHeight {
|
||||||
|
// TODO: hard fork this out; it's a bug from previous versions:
|
||||||
|
|
||||||
|
// old 17.3 C++ code we're trying to mimic (where empty means no active claims):
|
||||||
|
// auto it = nodesToAddOrUpdate.find(name); // nodesToAddOrUpdate is the working changes, base is previous block
|
||||||
|
// auto answer = (it || (it = base->find(name))) && !it->empty() ? nNextHeight - it->nHeightOfLastTakeover : 0;
|
||||||
|
|
||||||
|
return hasZeroActiveClaims(n) && nm.hasChildren(chg.Name, chg.Height, chg.SpentChildren, 2)
|
||||||
|
} else if len(n.Claims) > 0 {
|
||||||
|
// NOTE: old code had a bug in it where nodes with no claims but with children would get left in the cache after removal.
|
||||||
|
// This would cause the getNumBlocksOfContinuousOwnership to return zero (causing incorrect takeover height calc).
|
||||||
|
w, ok := param.DelayWorkarounds[string(chg.Name)]
|
||||||
|
if ok {
|
||||||
|
for _, h := range w {
|
||||||
|
if chg.Height == h {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateDelay(curr, tookOver int32) int32 {
|
||||||
|
|
||||||
|
delay := (curr - tookOver) / param.ActiveParams.ActiveDelayFactor
|
||||||
|
if delay > param.ActiveParams.MaxActiveDelay {
|
||||||
|
return param.ActiveParams.MaxActiveDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
return delay
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm BaseManager) NextUpdateHeightOfNode(name []byte) ([]byte, int32) {
|
||||||
|
|
||||||
|
n, err := nm.node(name)
|
||||||
|
if err != nil || n == nil {
|
||||||
|
return name, 0
|
||||||
|
}
|
||||||
|
|
||||||
|
return name, n.NextUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) Height() int32 {
|
||||||
|
return nm.height
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) Close() error {
|
||||||
|
return errors.WithStack(nm.repo.Close())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) hasChildren(name []byte, height int32, spentChildren map[string]bool, required int) bool {
|
||||||
|
c := map[byte]bool{}
|
||||||
|
if spentChildren == nil {
|
||||||
|
spentChildren = map[string]bool{}
|
||||||
|
}
|
||||||
|
|
||||||
|
err := nm.repo.IterateChildren(name, func(changes []change.Change) bool {
|
||||||
|
// if the key is unseen, generate a node for it to height
|
||||||
|
// if that node is active then increase the count
|
||||||
|
if len(changes) == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if c[changes[0].Name[len(name)]] { // assuming all names here are longer than starter name
|
||||||
|
return true // we already checked a similar name
|
||||||
|
}
|
||||||
|
if spentChildren[string(changes[0].Name)] {
|
||||||
|
return true // children that are spent in the same block cannot count as active children
|
||||||
|
}
|
||||||
|
n, _ := nm.newNodeFromChanges(changes, height)
|
||||||
|
if n != nil {
|
||||||
|
defer n.Close()
|
||||||
|
if n.HasActiveBestClaim() {
|
||||||
|
c[changes[0].Name[len(name)]] = true
|
||||||
|
if len(c) >= required {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
return err == nil && len(c) >= required
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) IterateNames(predicate func(name []byte) bool) {
|
||||||
|
nm.repo.IterateAll(predicate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) claimHashes(name []byte) *chainhash.Hash {
|
||||||
|
|
||||||
|
n, err := nm.node(name)
|
||||||
|
if err != nil || n == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
n.SortClaimsByBid()
|
||||||
|
claimHashes := make([]*chainhash.Hash, 0, len(n.Claims))
|
||||||
|
for _, c := range n.Claims {
|
||||||
|
if c.Status == Activated { // TODO: unit test this line
|
||||||
|
claimHashes = append(claimHashes, calculateNodeHash(c.OutPoint, n.TakenOverAt))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(claimHashes) > 0 {
|
||||||
|
return ComputeMerkleRoot(claimHashes)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) Hash(name []byte) *chainhash.Hash {
|
||||||
|
|
||||||
|
if nm.height >= param.ActiveParams.AllClaimsInMerkleForkHeight {
|
||||||
|
return nm.claimHashes(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := nm.node(name)
|
||||||
|
if err != nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if n != nil && len(n.Claims) > 0 {
|
||||||
|
if n.BestClaim != nil && n.BestClaim.Status == Activated {
|
||||||
|
return calculateNodeHash(n.BestClaim.OutPoint, n.TakenOverAt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func calculateNodeHash(op wire.OutPoint, takeover int32) *chainhash.Hash {
|
||||||
|
|
||||||
|
txHash := chainhash.DoubleHashH(op.Hash[:])
|
||||||
|
|
||||||
|
nOut := []byte(strconv.Itoa(int(op.Index)))
|
||||||
|
nOutHash := chainhash.DoubleHashH(nOut)
|
||||||
|
|
||||||
|
buf := make([]byte, 8)
|
||||||
|
binary.BigEndian.PutUint64(buf, uint64(takeover))
|
||||||
|
heightHash := chainhash.DoubleHashH(buf)
|
||||||
|
|
||||||
|
h := make([]byte, 0, sha256.Size*3)
|
||||||
|
h = append(h, txHash[:]...)
|
||||||
|
h = append(h, nOutHash[:]...)
|
||||||
|
h = append(h, heightHash[:]...)
|
||||||
|
|
||||||
|
hh := chainhash.DoubleHashH(h)
|
||||||
|
|
||||||
|
return &hh
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *BaseManager) Flush() error {
|
||||||
|
return nm.repo.Flush()
|
||||||
|
}
|
258
claimtrie/node/manager_test.go
Normal file
258
claimtrie/node/manager_test.go
Normal file
|
@ -0,0 +1,258 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node/noderepo"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
out1 = NewOutPointFromString("0000000000000000000000000000000000000000000000000000000000000000:1")
|
||||||
|
out2 = NewOutPointFromString("0000000000000000000000000000000000000000000000000000000000000000:2")
|
||||||
|
out3 = NewOutPointFromString("0100000000000000000000000000000000000000000000000000000000000000:1")
|
||||||
|
out4 = NewOutPointFromString("0100000000000000000000000000000000000000000000000000000000000000:2")
|
||||||
|
name1 = []byte("name1")
|
||||||
|
name2 = []byte("name2")
|
||||||
|
)
|
||||||
|
|
||||||
|
// verify that we can round-trip bytes to strings
|
||||||
|
func TestStringRoundTrip(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
data := [][]byte{
|
||||||
|
{97, 98, 99, 0, 100, 255},
|
||||||
|
{0xc3, 0x28},
|
||||||
|
{0xa0, 0xa1},
|
||||||
|
{0xe2, 0x28, 0xa1},
|
||||||
|
{0xf0, 0x28, 0x8c, 0x28},
|
||||||
|
}
|
||||||
|
for _, d := range data {
|
||||||
|
s := string(d)
|
||||||
|
r.Equal(s, fmt.Sprintf("%s", d)) // nolint
|
||||||
|
d2 := []byte(s)
|
||||||
|
r.Equal(len(d), len(s))
|
||||||
|
r.Equal(d, d2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSimpleAddClaim(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)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg := change.NewChange(change.AddClaim).SetName(name1).SetOutPoint(out1).SetHeight(11)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
_, err = m.IncrementHeightTo(11)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg = chg.SetName(name2).SetOutPoint(out2).SetHeight(12)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
_, err = m.IncrementHeightTo(12)
|
||||||
|
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)))
|
||||||
|
|
||||||
|
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)
|
||||||
|
r.NoError(err)
|
||||||
|
n2, err = m.node(name1)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Nil(n2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSupportAmounts(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)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg := change.NewChange(change.AddClaim).SetName(name1).SetOutPoint(out1).SetHeight(11).SetAmount(3)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out1)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg = change.NewChange(change.AddClaim).SetName(name1).SetOutPoint(out2).SetHeight(11).SetAmount(4)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out2)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
_, err = m.IncrementHeightTo(11)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg = change.NewChange(change.AddSupport).SetName(name1).SetOutPoint(out3).SetHeight(12).SetAmount(2)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out1)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg = change.NewChange(change.AddSupport).SetName(name1).SetOutPoint(out4).SetHeight(12).SetAmount(2)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out2)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
chg = change.NewChange(change.SpendSupport).SetName(name1).SetOutPoint(out4).SetHeight(12).SetAmount(2)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out2)
|
||||||
|
err = m.AppendChange(chg)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
_, err = m.IncrementHeightTo(20)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
n1, err := m.node(name1)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equal(2, len(n1.Claims))
|
||||||
|
r.Equal(int64(5), n1.BestClaim.Amount+n1.SupportSums[n1.BestClaim.ClaimID.Key()])
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNodeSort(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
param.ActiveParams.ExtendedClaimExpirationTime = 1000
|
||||||
|
|
||||||
|
r.True(OutPointLess(*out1, *out2))
|
||||||
|
r.True(OutPointLess(*out1, *out3))
|
||||||
|
|
||||||
|
n := New()
|
||||||
|
defer n.Close()
|
||||||
|
n.Claims = append(n.Claims, &Claim{OutPoint: *out1, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{1}})
|
||||||
|
n.Claims = append(n.Claims, &Claim{OutPoint: *out2, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{2}})
|
||||||
|
n.handleExpiredAndActivated(3)
|
||||||
|
n.updateTakeoverHeight(3, []byte{}, true)
|
||||||
|
|
||||||
|
r.Equal(n.Claims.find(byOut(*out1)).OutPoint.String(), n.BestClaim.OutPoint.String())
|
||||||
|
|
||||||
|
n.Claims = append(n.Claims, &Claim{OutPoint: *out3, AcceptedAt: 3, Amount: 3, ClaimID: change.ClaimID{3}})
|
||||||
|
n.handleExpiredAndActivated(3)
|
||||||
|
n.updateTakeoverHeight(3, []byte{}, true)
|
||||||
|
r.Equal(n.Claims.find(byOut(*out1)).OutPoint.String(), n.BestClaim.OutPoint.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClaimSort(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
param.ActiveParams.ExtendedClaimExpirationTime = 1000
|
||||||
|
|
||||||
|
n := New()
|
||||||
|
defer n.Close()
|
||||||
|
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)
|
||||||
|
r.Equal(int64(3), n.Claims[1].Amount)
|
||||||
|
r.Equal(int64(2), n.Claims[2].Amount)
|
||||||
|
r.Equal(int32(4), n.Claims[3].AcceptedAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHasChildren(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()
|
||||||
|
|
||||||
|
chg := change.NewChange(change.AddClaim).SetName([]byte("a")).SetOutPoint(out1).SetHeight(1).SetAmount(2)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out1)
|
||||||
|
r.NoError(m.AppendChange(chg))
|
||||||
|
_, 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)
|
||||||
|
r.NoError(m.AppendChange(chg))
|
||||||
|
_, 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))
|
||||||
|
|
||||||
|
chg = change.NewChange(change.AddClaim).SetName([]byte("abc")).SetOutPoint(out3).SetHeight(3).SetAmount(2)
|
||||||
|
chg.ClaimID = change.NewClaimID(*out3)
|
||||||
|
r.NoError(m.AppendChange(chg))
|
||||||
|
_, 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)
|
||||||
|
r.NoError(m.AppendChange(chg))
|
||||||
|
_, err = m.IncrementHeightTo(4)
|
||||||
|
r.NoError(err)
|
||||||
|
r.True(m.hasChildren([]byte("a"), 4, nil, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCollectChildren(t *testing.T) {
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
c1 := change.Change{Name: []byte("ba"), Type: change.SpendClaim}
|
||||||
|
c2 := change.Change{Name: []byte("ba"), Type: change.UpdateClaim}
|
||||||
|
c3 := change.Change{Name: []byte("ac"), Type: change.SpendClaim}
|
||||||
|
c4 := change.Change{Name: []byte("ac"), Type: change.UpdateClaim}
|
||||||
|
c5 := change.Change{Name: []byte("a"), Type: change.SpendClaim}
|
||||||
|
c6 := change.Change{Name: []byte("a"), Type: change.UpdateClaim}
|
||||||
|
c7 := change.Change{Name: []byte("ab"), Type: change.SpendClaim}
|
||||||
|
c8 := change.Change{Name: []byte("ab"), Type: change.UpdateClaim}
|
||||||
|
c := []change.Change{c1, c2, c3, c4, c5, c6, c7, c8}
|
||||||
|
|
||||||
|
collectChildNames(c)
|
||||||
|
|
||||||
|
r.Empty(c[0].SpentChildren)
|
||||||
|
r.Empty(c[2].SpentChildren)
|
||||||
|
r.Empty(c[4].SpentChildren)
|
||||||
|
r.Empty(c[6].SpentChildren)
|
||||||
|
|
||||||
|
r.Len(c[1].SpentChildren, 0)
|
||||||
|
r.Len(c[3].SpentChildren, 0)
|
||||||
|
r.Len(c[5].SpentChildren, 1)
|
||||||
|
r.True(c[5].SpentChildren["ac"])
|
||||||
|
|
||||||
|
r.Len(c[7].SpentChildren, 0)
|
||||||
|
}
|
340
claimtrie/node/node.go
Normal file
340
claimtrie/node/node.go
Normal file
|
@ -0,0 +1,340 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Node struct {
|
||||||
|
BestClaim *Claim // The claim that has most effective amount at the current height.
|
||||||
|
TakenOverAt int32 // The height at when the current BestClaim took over.
|
||||||
|
Claims ClaimList // List of all Claims.
|
||||||
|
Supports ClaimList // List of all Supports, including orphaned ones.
|
||||||
|
SupportSums map[string]int64
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new node.
|
||||||
|
func New() *Node {
|
||||||
|
return &Node{SupportSums: map[string]int64{}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) HasActiveBestClaim() bool {
|
||||||
|
return n.BestClaim != nil && n.BestClaim.Status == Activated
|
||||||
|
}
|
||||||
|
|
||||||
|
var claimPool = sync.Pool{
|
||||||
|
New: func() interface{} {
|
||||||
|
return &Claim{}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) Close() {
|
||||||
|
n.BestClaim = nil
|
||||||
|
n.SupportSums = nil
|
||||||
|
|
||||||
|
for i := range n.Claims {
|
||||||
|
claimPool.Put(n.Claims[i])
|
||||||
|
}
|
||||||
|
n.Claims = nil
|
||||||
|
|
||||||
|
for i := range n.Supports {
|
||||||
|
claimPool.Put(n.Supports[i])
|
||||||
|
}
|
||||||
|
n.Supports = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) ApplyChange(chg change.Change, delay int32) error {
|
||||||
|
|
||||||
|
visibleAt := chg.VisibleHeight
|
||||||
|
if visibleAt <= 0 {
|
||||||
|
visibleAt = chg.Height
|
||||||
|
}
|
||||||
|
|
||||||
|
switch chg.Type {
|
||||||
|
case change.AddClaim:
|
||||||
|
c := claimPool.Get().(*Claim)
|
||||||
|
// set all 8 fields on c as they aren't initialized to 0:
|
||||||
|
c.Status = Accepted
|
||||||
|
c.OutPoint = chg.OutPoint
|
||||||
|
c.Amount = chg.Amount
|
||||||
|
c.ClaimID = chg.ClaimID
|
||||||
|
// CreatedAt: chg.Height,
|
||||||
|
c.AcceptedAt = chg.Height
|
||||||
|
c.ActiveAt = chg.Height + delay
|
||||||
|
c.VisibleAt = visibleAt
|
||||||
|
c.Sequence = int32(len(n.Claims))
|
||||||
|
// removed this after proving ResetHeight works:
|
||||||
|
// old := n.Claims.find(byOut(chg.OutPoint))
|
||||||
|
// if old != nil {
|
||||||
|
// return errors.Errorf("CONFLICT WITH EXISTING TXO! Name: %s, Height: %d", chg.Name, chg.Height)
|
||||||
|
// }
|
||||||
|
n.Claims = append(n.Claims, c)
|
||||||
|
|
||||||
|
case change.SpendClaim:
|
||||||
|
c := n.Claims.find(byOut(chg.OutPoint))
|
||||||
|
if c != nil {
|
||||||
|
c.setStatus(Deactivated)
|
||||||
|
} else {
|
||||||
|
LogOnce(fmt.Sprintf("Spending claim but missing existing claim with TXO %s, "+
|
||||||
|
"Name: %s, ID: %s", chg.OutPoint, chg.Name, chg.ClaimID))
|
||||||
|
}
|
||||||
|
// apparently it's legit to be absent in the map:
|
||||||
|
// 'two' at 481100, 36a719a156a1df178531f3c712b8b37f8e7cc3b36eea532df961229d936272a1:0
|
||||||
|
|
||||||
|
case change.UpdateClaim:
|
||||||
|
c := n.Claims.find(byID(chg.ClaimID))
|
||||||
|
if c != nil && c.Status == Deactivated {
|
||||||
|
|
||||||
|
// Keep its ID, which was generated from the spent claim.
|
||||||
|
// And update the rest of properties.
|
||||||
|
c.setOutPoint(chg.OutPoint).SetAmt(chg.Amount)
|
||||||
|
c.setStatus(Accepted) // it was Deactivated in the spend (but we only activate at the end of the block)
|
||||||
|
// that's because the old code would put all insertions into the "queue" that was processed at block's end
|
||||||
|
|
||||||
|
// This forces us to be newer, which may in an unintentional takeover if there's an older one.
|
||||||
|
// TODO: reconsider these updates in future hard forks.
|
||||||
|
c.setAccepted(chg.Height)
|
||||||
|
c.setActiveAt(chg.Height + delay)
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LogOnce(fmt.Sprintf("Updating claim but missing existing claim with ID %s", chg.ClaimID))
|
||||||
|
}
|
||||||
|
case change.AddSupport:
|
||||||
|
s := claimPool.Get().(*Claim)
|
||||||
|
// set all 8 fields on s:
|
||||||
|
s.Status = Accepted
|
||||||
|
s.OutPoint = chg.OutPoint
|
||||||
|
s.Amount = chg.Amount
|
||||||
|
s.ClaimID = chg.ClaimID
|
||||||
|
s.AcceptedAt = chg.Height
|
||||||
|
s.ActiveAt = chg.Height + delay
|
||||||
|
s.VisibleAt = visibleAt
|
||||||
|
s.Sequence = int32(len(n.Supports))
|
||||||
|
|
||||||
|
n.Supports = append(n.Supports, s)
|
||||||
|
|
||||||
|
case change.SpendSupport:
|
||||||
|
s := n.Supports.find(byOut(chg.OutPoint))
|
||||||
|
if s != nil {
|
||||||
|
if s.Status == Activated {
|
||||||
|
n.SupportSums[s.ClaimID.Key()] -= s.Amount
|
||||||
|
}
|
||||||
|
// TODO: we could do without this Deactivated flag if we set expiration instead
|
||||||
|
// That would eliminate the above Sum update.
|
||||||
|
// We would also need to track the update situation, though, but that could be done locally.
|
||||||
|
s.setStatus(Deactivated)
|
||||||
|
} else {
|
||||||
|
LogOnce(fmt.Sprintf("Spending support but missing existing claim with TXO %s, "+
|
||||||
|
"Name: %s, ID: %s", chg.OutPoint, chg.Name, chg.ClaimID))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdjustTo activates claims and computes takeovers until it reaches the specified height.
|
||||||
|
func (n *Node) AdjustTo(height, maxHeight int32, name []byte) *Node {
|
||||||
|
changed := n.handleExpiredAndActivated(height) > 0
|
||||||
|
n.updateTakeoverHeight(height, name, changed)
|
||||||
|
if maxHeight > height {
|
||||||
|
for h := n.NextUpdate(); h <= maxHeight; h = n.NextUpdate() {
|
||||||
|
changed = n.handleExpiredAndActivated(h) > 0
|
||||||
|
n.updateTakeoverHeight(h, name, changed)
|
||||||
|
height = h
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) updateTakeoverHeight(height int32, name []byte, refindBest bool) {
|
||||||
|
|
||||||
|
candidate := n.BestClaim
|
||||||
|
if refindBest {
|
||||||
|
candidate = n.findBestClaim() // so expensive...
|
||||||
|
}
|
||||||
|
|
||||||
|
hasCandidate := candidate != nil
|
||||||
|
hasCurrentWinner := n.HasActiveBestClaim()
|
||||||
|
|
||||||
|
takeoverHappening := !hasCandidate || !hasCurrentWinner || candidate.ClaimID != n.BestClaim.ClaimID
|
||||||
|
|
||||||
|
if takeoverHappening {
|
||||||
|
if n.activateAllClaims(height) > 0 {
|
||||||
|
candidate = n.findBestClaim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !takeoverHappening && height < param.ActiveParams.MaxRemovalWorkaroundHeight {
|
||||||
|
// This is a super ugly hack to work around bug in old code.
|
||||||
|
// The bug: un/support a name then update it. This will cause its takeover height to be reset to current.
|
||||||
|
// This is because the old code would add to the cache without setting block originals when dealing in supports.
|
||||||
|
_, takeoverHappening = param.TakeoverWorkarounds[fmt.Sprintf("%d_%s", height, name)] // TODO: ditch the fmt call
|
||||||
|
}
|
||||||
|
|
||||||
|
if takeoverHappening {
|
||||||
|
n.TakenOverAt = height
|
||||||
|
n.BestClaim = candidate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) handleExpiredAndActivated(height int32) int {
|
||||||
|
|
||||||
|
changes := 0
|
||||||
|
update := func(items ClaimList, sums map[string]int64) ClaimList {
|
||||||
|
for i := 0; i < len(items); i++ {
|
||||||
|
c := items[i]
|
||||||
|
if c.Status == Accepted && c.ActiveAt <= height && c.VisibleAt <= height {
|
||||||
|
c.setStatus(Activated)
|
||||||
|
changes++
|
||||||
|
if sums != nil {
|
||||||
|
sums[c.ClaimID.Key()] += c.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if c.ExpireAt() <= height || c.Status == Deactivated {
|
||||||
|
if i < len(items)-1 {
|
||||||
|
items[i] = items[len(items)-1]
|
||||||
|
i--
|
||||||
|
}
|
||||||
|
items = items[:len(items)-1]
|
||||||
|
changes++
|
||||||
|
if sums != nil && c.Status != Deactivated {
|
||||||
|
sums[c.ClaimID.Key()] -= c.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return items
|
||||||
|
}
|
||||||
|
n.Claims = update(n.Claims, nil)
|
||||||
|
n.Supports = update(n.Supports, n.SupportSums)
|
||||||
|
return changes
|
||||||
|
}
|
||||||
|
|
||||||
|
// NextUpdate returns the nearest height in the future that the node should
|
||||||
|
// be refreshed due to changes of claims or supports.
|
||||||
|
func (n Node) NextUpdate() int32 {
|
||||||
|
|
||||||
|
next := int32(math.MaxInt32)
|
||||||
|
|
||||||
|
for _, c := range n.Claims {
|
||||||
|
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 {
|
||||||
|
min := c.ActiveAt
|
||||||
|
if c.VisibleAt > min {
|
||||||
|
min = c.VisibleAt
|
||||||
|
}
|
||||||
|
if min < next {
|
||||||
|
next = min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range n.Supports {
|
||||||
|
if s.ExpireAt() < next {
|
||||||
|
next = s.ExpireAt()
|
||||||
|
}
|
||||||
|
if s.Status == Accepted {
|
||||||
|
min := s.ActiveAt
|
||||||
|
if s.VisibleAt > min {
|
||||||
|
min = s.VisibleAt
|
||||||
|
}
|
||||||
|
if min < next {
|
||||||
|
next = min
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n Node) findBestClaim() *Claim {
|
||||||
|
|
||||||
|
// WARNING: this method is called billions of times.
|
||||||
|
// if we just had some easy way to know that our best claim was the first one in the list...
|
||||||
|
// or it may be faster to cache effective amount in the db at some point.
|
||||||
|
|
||||||
|
var best *Claim
|
||||||
|
var bestAmount int64
|
||||||
|
for _, candidate := range n.Claims {
|
||||||
|
|
||||||
|
// not using switch here for performance reasons
|
||||||
|
if candidate.Status != Activated {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if best == nil {
|
||||||
|
best = candidate
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
candidateAmount := candidate.Amount + n.SupportSums[candidate.ClaimID.Key()]
|
||||||
|
if bestAmount <= 0 {
|
||||||
|
bestAmount = best.Amount + n.SupportSums[best.ClaimID.Key()]
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case candidateAmount > bestAmount:
|
||||||
|
best = candidate
|
||||||
|
bestAmount = candidateAmount
|
||||||
|
case candidateAmount < bestAmount:
|
||||||
|
continue
|
||||||
|
case candidate.AcceptedAt < best.AcceptedAt:
|
||||||
|
best = candidate
|
||||||
|
bestAmount = candidateAmount
|
||||||
|
case candidate.AcceptedAt > best.AcceptedAt:
|
||||||
|
continue
|
||||||
|
case OutPointLess(candidate.OutPoint, best.OutPoint):
|
||||||
|
best = candidate
|
||||||
|
bestAmount = candidateAmount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return best
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) activateAllClaims(height int32) int {
|
||||||
|
count := 0
|
||||||
|
for _, c := range n.Claims {
|
||||||
|
if c.Status == Accepted && c.ActiveAt > height && c.VisibleAt <= height {
|
||||||
|
c.setActiveAt(height) // don't necessarily need to change this number?
|
||||||
|
c.setStatus(Activated)
|
||||||
|
count++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range n.Supports {
|
||||||
|
if s.Status == Accepted && s.ActiveAt > height && s.VisibleAt <= height {
|
||||||
|
s.setActiveAt(height) // don't necessarily need to change this number?
|
||||||
|
s.setStatus(Activated)
|
||||||
|
count++
|
||||||
|
n.SupportSums[s.ClaimID.Key()] += s.Amount
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return count
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) SortClaimsByBid() {
|
||||||
|
|
||||||
|
// purposefully sorting by descent
|
||||||
|
sort.Slice(n.Claims, func(j, i int) bool {
|
||||||
|
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
|
||||||
|
case iAmount > jAmount:
|
||||||
|
return false
|
||||||
|
case n.Claims[i].AcceptedAt > n.Claims[j].AcceptedAt:
|
||||||
|
return true
|
||||||
|
case n.Claims[i].AcceptedAt < n.Claims[j].AcceptedAt:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return OutPointLess(n.Claims[j].OutPoint, n.Claims[i].OutPoint)
|
||||||
|
})
|
||||||
|
}
|
188
claimtrie/node/noderepo/noderepo_test.go
Normal file
188
claimtrie/node/noderepo/noderepo_test.go
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
package noderepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/node"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
out1 = node.NewOutPointFromString("0000000000000000000000000000000000000000000000000000000000000000:1")
|
||||||
|
testNodeName1 = []byte("name1")
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPebble(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
repo, err := NewPebble(t.TempDir())
|
||||||
|
r.NoError(err)
|
||||||
|
defer func() {
|
||||||
|
err := repo.Close()
|
||||||
|
r.NoError(err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
cleanup := func() {
|
||||||
|
lowerBound := testNodeName1
|
||||||
|
upperBound := append(testNodeName1, byte(0))
|
||||||
|
err := repo.db.DeleteRange(lowerBound, upperBound, nil)
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
testNodeRepo(t, repo, func() {}, cleanup)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNodeRepo(t *testing.T, repo node.Repo, setup, cleanup func()) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
chg := change.NewChange(change.AddClaim).SetName(testNodeName1).SetOutPoint(out1)
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
name string
|
||||||
|
height int32
|
||||||
|
changes []change.Change
|
||||||
|
expected []change.Change
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"test 1",
|
||||||
|
1,
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
[]change.Change{chg.SetHeight(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test 2",
|
||||||
|
2,
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
[]change.Change{chg.SetHeight(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test 3",
|
||||||
|
3,
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test 4",
|
||||||
|
4,
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test 5",
|
||||||
|
5,
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"test 6",
|
||||||
|
6,
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
[]change.Change{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testcases {
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
err := repo.AppendChanges(tt.changes)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
changes, err := repo.LoadChanges(testNodeName1)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equalf(tt.expected, changes[:len(tt.expected)], tt.name)
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
|
||||||
|
testcases2 := []struct {
|
||||||
|
name string
|
||||||
|
height int32
|
||||||
|
changes [][]change.Change
|
||||||
|
expected []change.Change
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
"Save in 2 batches, and load up to 1",
|
||||||
|
1,
|
||||||
|
[][]change.Change{
|
||||||
|
{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
{chg.SetHeight(6), chg.SetHeight(8), chg.SetHeight(9)},
|
||||||
|
},
|
||||||
|
[]change.Change{chg.SetHeight(1)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Save in 2 batches, and load up to 9",
|
||||||
|
9,
|
||||||
|
[][]change.Change{
|
||||||
|
{chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5)},
|
||||||
|
{chg.SetHeight(6), chg.SetHeight(8), chg.SetHeight(9)},
|
||||||
|
},
|
||||||
|
[]change.Change{
|
||||||
|
chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5),
|
||||||
|
chg.SetHeight(6), chg.SetHeight(8), chg.SetHeight(9),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Save in 3 batches, and load up to 8",
|
||||||
|
8,
|
||||||
|
[][]change.Change{
|
||||||
|
{chg.SetHeight(1), chg.SetHeight(3)},
|
||||||
|
{chg.SetHeight(5)},
|
||||||
|
{chg.SetHeight(6), chg.SetHeight(8), chg.SetHeight(9)},
|
||||||
|
},
|
||||||
|
[]change.Change{
|
||||||
|
chg.SetHeight(1), chg.SetHeight(3), chg.SetHeight(5),
|
||||||
|
chg.SetHeight(6), chg.SetHeight(8),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range testcases2 {
|
||||||
|
|
||||||
|
setup()
|
||||||
|
|
||||||
|
for _, changes := range tt.changes {
|
||||||
|
err := repo.AppendChanges(changes)
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
changes, err := repo.LoadChanges(testNodeName1)
|
||||||
|
r.NoError(err)
|
||||||
|
r.Equalf(tt.expected, changes[:len(tt.expected)], tt.name)
|
||||||
|
|
||||||
|
cleanup()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIterator(t *testing.T) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
repo, err := NewPebble(t.TempDir())
|
||||||
|
r.NoError(err)
|
||||||
|
defer func() {
|
||||||
|
err := repo.Close()
|
||||||
|
r.NoError(err)
|
||||||
|
}()
|
||||||
|
|
||||||
|
creation := []change.Change{
|
||||||
|
{Name: []byte("test\x00"), Height: 5},
|
||||||
|
{Name: []byte("test\x00\x00"), Height: 5},
|
||||||
|
{Name: []byte("test\x00b"), Height: 5},
|
||||||
|
{Name: []byte("test\x00\xFF"), Height: 5},
|
||||||
|
{Name: []byte("testa"), Height: 5},
|
||||||
|
}
|
||||||
|
err = repo.AppendChanges(creation)
|
||||||
|
r.NoError(err)
|
||||||
|
|
||||||
|
i := 0
|
||||||
|
repo.IterateChildren([]byte{}, func(changes []change.Change) bool {
|
||||||
|
r.Equal(creation[i], changes[0])
|
||||||
|
i++
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
219
claimtrie/node/noderepo/pebble.go
Normal file
219
claimtrie/node/noderepo/pebble.go
Normal file
|
@ -0,0 +1,219 @@
|
||||||
|
package noderepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"io"
|
||||||
|
"sort"
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pebble struct {
|
||||||
|
db *pebble.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
type pooledMerger struct {
|
||||||
|
pool *sync.Pool
|
||||||
|
buffer []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *pooledMerger) MergeNewer(value []byte) error {
|
||||||
|
if a.buffer == nil {
|
||||||
|
a.buffer = a.pool.Get().([]byte)
|
||||||
|
}
|
||||||
|
a.buffer = append(a.buffer, value...)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
func (a *pooledMerger) MergeOlder(value []byte) error {
|
||||||
|
if a.buffer == nil {
|
||||||
|
a.buffer = a.pool.Get().([]byte)
|
||||||
|
}
|
||||||
|
n := len(a.buffer)
|
||||||
|
if cap(a.buffer) >= len(value)+n {
|
||||||
|
a.buffer = a.buffer[0 : len(value)+n] // expand it
|
||||||
|
copy(a.buffer[len(value):], a.buffer[0:n]) // could overlap
|
||||||
|
copy(a.buffer, value)
|
||||||
|
} else {
|
||||||
|
existing := a.buffer
|
||||||
|
a.buffer = make([]byte, 0, len(value)+len(existing))
|
||||||
|
a.buffer = append(a.buffer, value...)
|
||||||
|
a.buffer = append(a.buffer, existing...)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *pooledMerger) Finish(_ bool) ([]byte, io.Closer, error) {
|
||||||
|
return a.buffer, a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *pooledMerger) Close() error {
|
||||||
|
a.pool.Put(a.buffer[:0])
|
||||||
|
a.buffer = nil
|
||||||
|
a.pool = nil
|
||||||
|
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(32 << 20),
|
||||||
|
BytesPerSync: 4 << 20,
|
||||||
|
})
|
||||||
|
|
||||||
|
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()
|
||||||
|
defer batch.Close()
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer(nil)
|
||||||
|
|
||||||
|
for _, chg := range changes {
|
||||||
|
buffer.Reset()
|
||||||
|
err := chg.MarshalTo(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "in marshaller")
|
||||||
|
}
|
||||||
|
|
||||||
|
// expecting this next line to make a copy of the buffer, not hold it
|
||||||
|
err = batch.Merge(chg.Name, buffer.Bytes(), pebble.NoSync)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "in merge")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Wrap(batch.Commit(pebble.NoSync), "in commit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) LoadChanges(name []byte) ([]change.Change, error) {
|
||||||
|
|
||||||
|
data, closer, err := repo.db.Get(name)
|
||||||
|
if err != nil && err != pebble.ErrNotFound {
|
||||||
|
return nil, errors.Wrapf(err, "in get %s", name) // does returning a name in an error expose too much?
|
||||||
|
}
|
||||||
|
if closer != nil {
|
||||||
|
defer closer.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return unmarshalChanges(name, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalChanges(name, data []byte) ([]change.Change, error) {
|
||||||
|
var changes []change.Change
|
||||||
|
|
||||||
|
buffer := bytes.NewBuffer(data)
|
||||||
|
for buffer.Len() > 0 {
|
||||||
|
var chg change.Change
|
||||||
|
err := chg.UnmarshalFrom(buffer)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "in decode")
|
||||||
|
}
|
||||||
|
chg.Name = name
|
||||||
|
changes = append(changes, chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) DropChanges(name []byte, finalHeight int32) error {
|
||||||
|
changes, err := repo.LoadChanges(name)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "in load changes for %s", name)
|
||||||
|
}
|
||||||
|
i := 0
|
||||||
|
for ; i < len(changes); i++ {
|
||||||
|
if changes[i].Height > finalHeight {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// making a performance assumption that DropChanges won't happen often:
|
||||||
|
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 {
|
||||||
|
start := make([]byte, len(name)+1) // zeros that last byte; need a constant len for stack alloc?
|
||||||
|
copy(start, name)
|
||||||
|
|
||||||
|
end := make([]byte, 256) // max name length is 255
|
||||||
|
copy(end, name)
|
||||||
|
for i := len(name); i < 256; i++ {
|
||||||
|
end[i] = 255
|
||||||
|
}
|
||||||
|
|
||||||
|
prefixIterOptions := &pebble.IterOptions{
|
||||||
|
LowerBound: start,
|
||||||
|
UpperBound: end,
|
||||||
|
}
|
||||||
|
|
||||||
|
iter := repo.db.NewIter(prefixIterOptions)
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
for iter.First(); iter.Valid(); iter.Next() {
|
||||||
|
// NOTE! iter.Key() is ephemeral!
|
||||||
|
changes, err := unmarshalChanges(iter.Key(), iter.Value())
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrapf(err, "from unmarshaller at %s", iter.Key())
|
||||||
|
}
|
||||||
|
if !f(changes) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) IterateAll(predicate func(name []byte) bool) {
|
||||||
|
iter := repo.db.NewIter(nil)
|
||||||
|
defer iter.Close()
|
||||||
|
|
||||||
|
for iter.First(); iter.Valid(); iter.Next() {
|
||||||
|
if !predicate(iter.Key()) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Close() error {
|
||||||
|
|
||||||
|
err := repo.db.Flush()
|
||||||
|
if err != nil {
|
||||||
|
// if we fail to close are we going to try again later?
|
||||||
|
return errors.Wrap(err, "on flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.db.Close()
|
||||||
|
return errors.Wrap(err, "on close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Flush() error {
|
||||||
|
_, err := repo.db.AsyncFlush()
|
||||||
|
return err
|
||||||
|
}
|
30
claimtrie/node/normalizer.go
Normal file
30
claimtrie/node/normalizer.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
"golang.org/x/text/cases"
|
||||||
|
"golang.org/x/text/unicode/norm"
|
||||||
|
)
|
||||||
|
|
||||||
|
//func init() {
|
||||||
|
// if cases.UnicodeVersion[:2] != "11" {
|
||||||
|
// panic("Wrong unicode version!")
|
||||||
|
// }
|
||||||
|
//}
|
||||||
|
|
||||||
|
var Normalize = normalizeGo
|
||||||
|
|
||||||
|
func NormalizeIfNecessary(name []byte, height int32) []byte {
|
||||||
|
if height < param.ActiveParams.NormalizedNameForkHeight {
|
||||||
|
return name
|
||||||
|
}
|
||||||
|
return Normalize(name)
|
||||||
|
}
|
||||||
|
|
||||||
|
var folder = cases.Fold()
|
||||||
|
|
||||||
|
func normalizeGo(value []byte) []byte {
|
||||||
|
|
||||||
|
normalized := norm.NFD.Bytes(value)
|
||||||
|
return folder.Bytes(normalized)
|
||||||
|
}
|
65
claimtrie/node/normalizer_icu.go
Normal file
65
claimtrie/node/normalizer_icu.go
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
// +build use_icu_normalization
|
||||||
|
|
||||||
|
package node
|
||||||
|
|
||||||
|
// #cgo CFLAGS: -O2
|
||||||
|
// #cgo LDFLAGS: -licuio -licui18n -licuuc -licudata
|
||||||
|
// #include <unicode/unorm2.h>
|
||||||
|
// #include <unicode/ustring.h>
|
||||||
|
// #include <unicode/uversion.h>
|
||||||
|
// int icu_version() {
|
||||||
|
// UVersionInfo info;
|
||||||
|
// u_getVersion(info);
|
||||||
|
// return ((int)(info[0]) << 16) + info[1];
|
||||||
|
// }
|
||||||
|
// int normalize(char* name, int length, char* result) {
|
||||||
|
// UErrorCode ec = U_ZERO_ERROR;
|
||||||
|
// static const UNormalizer2* normalizer = NULL;
|
||||||
|
// if (normalizer == NULL) normalizer = unorm2_getNFDInstance(&ec);
|
||||||
|
// UChar dest[256]; // maximum claim name size is 255; we won't have more UTF16 chars than bytes
|
||||||
|
// int dest_len;
|
||||||
|
// u_strFromUTF8(dest, 256, &dest_len, name, length, &ec);
|
||||||
|
// if (U_FAILURE(ec) || dest_len == 0) return 0;
|
||||||
|
// UChar normalized[256];
|
||||||
|
// dest_len = unorm2_normalize(normalizer, dest, dest_len, normalized, 256, &ec);
|
||||||
|
// if (U_FAILURE(ec) || dest_len == 0) return 0;
|
||||||
|
// dest_len = u_strFoldCase(dest, 256, normalized, dest_len, U_FOLD_CASE_DEFAULT, &ec);
|
||||||
|
// if (U_FAILURE(ec) || dest_len == 0) return 0;
|
||||||
|
// u_strToUTF8(result, 512, &dest_len, dest, dest_len, &ec);
|
||||||
|
// return dest_len;
|
||||||
|
// }
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"unsafe"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Normalize = normalizeICU
|
||||||
|
}
|
||||||
|
|
||||||
|
func IcuVersion() string {
|
||||||
|
// TODO: we probably need to explode if it's not 63.2 as it affects consensus
|
||||||
|
result := C.icu_version()
|
||||||
|
return fmt.Sprintf("%d.%d", result>>16, result&0xffff)
|
||||||
|
}
|
||||||
|
|
||||||
|
func normalizeICU(value []byte) []byte {
|
||||||
|
if len(value) <= 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
name := (*C.char)(unsafe.Pointer(&value[0]))
|
||||||
|
length := C.int(len(value))
|
||||||
|
|
||||||
|
// hopefully this is a stack alloc (but it may be a bit large for that):
|
||||||
|
var resultName [512]byte // inputs are restricted to 255 chars; it shouldn't expand too much past that
|
||||||
|
result := unsafe.Pointer(&resultName[0])
|
||||||
|
|
||||||
|
resultLength := C.normalize(name, length, (*C.char)(result))
|
||||||
|
if resultLength == 0 {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
|
||||||
|
// return resultName[0:resultLength] -- we want to shrink the result (not use a slice on 1024)
|
||||||
|
return C.GoBytes(result, resultLength)
|
||||||
|
}
|
23
claimtrie/node/normalizer_icu_test.go
Normal file
23
claimtrie/node/normalizer_icu_test.go
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
// +build use_icu_normalization
|
||||||
|
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalizationICU(t *testing.T) {
|
||||||
|
testNormalization(t, normalizeICU)
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNormalizeICU(b *testing.B) {
|
||||||
|
benchmarkNormalize(b, normalizeICU)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBlock760150(t *testing.T) {
|
||||||
|
test := "Ꮖ-Ꮩ-Ꭺ-N--------Ꭺ-N-Ꮹ-Ꭼ-Ꮮ-Ꭺ-on-Instagram_-“Our-next-destination-is-East-and-Southeast-Asia--selfie--asia”"
|
||||||
|
a := normalizeGo([]byte(test))
|
||||||
|
b := normalizeICU([]byte(test))
|
||||||
|
assert.Equal(t, a, b)
|
||||||
|
}
|
54
claimtrie/node/normalizer_test.go
Normal file
54
claimtrie/node/normalizer_test.go
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNormalizationGo(t *testing.T) {
|
||||||
|
testNormalization(t, normalizeGo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testNormalization(t *testing.T, normalize func(value []byte) []byte) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
r.Equal("test", string(normalize([]byte("TESt"))))
|
||||||
|
r.Equal("test 23", string(normalize([]byte("tesT 23"))))
|
||||||
|
r.Equal("\xFF", string(normalize([]byte("\xFF"))))
|
||||||
|
r.Equal("\xC3\x28", string(normalize([]byte("\xC3\x28"))))
|
||||||
|
r.Equal("\xCF\x89", string(normalize([]byte("\xE2\x84\xA6"))))
|
||||||
|
r.Equal("\xD1\x84", string(normalize([]byte("\xD0\xA4"))))
|
||||||
|
r.Equal("\xD5\xA2", string(normalize([]byte("\xD4\xB2"))))
|
||||||
|
r.Equal("\xE3\x81\xB5\xE3\x82\x99", string(normalize([]byte("\xE3\x81\xB6"))))
|
||||||
|
r.Equal("\xE1\x84\x81\xE1\x85\xAA\xE1\x86\xB0", string(normalize([]byte("\xEA\xBD\x91"))))
|
||||||
|
}
|
||||||
|
|
||||||
|
func randSeq(n int) []byte {
|
||||||
|
var alphabet = []rune("abcdefghijklmnopqrstuvwxyz̃ABCDEFGHIJKLMNOPQRSTUVWXYZ̃")
|
||||||
|
|
||||||
|
b := make([]rune, n)
|
||||||
|
for i := range b {
|
||||||
|
b[i] = alphabet[rand.Intn(len(alphabet))]
|
||||||
|
}
|
||||||
|
return []byte(string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
func BenchmarkNormalize(b *testing.B) {
|
||||||
|
benchmarkNormalize(b, normalizeGo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func benchmarkNormalize(b *testing.B, normalize func(value []byte) []byte) {
|
||||||
|
rand.Seed(42)
|
||||||
|
strings := make([][]byte, b.N)
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
strings[i] = randSeq(32)
|
||||||
|
}
|
||||||
|
b.ResetTimer()
|
||||||
|
for i := 0; i < b.N; i++ {
|
||||||
|
s := normalize(strings[i])
|
||||||
|
require.True(b, len(s) >= 8)
|
||||||
|
}
|
||||||
|
}
|
121
claimtrie/node/normalizing_manager.go
Normal file
121
claimtrie/node/normalizing_manager.go
Normal file
|
@ -0,0 +1,121 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/param"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NormalizingManager struct { // implements Manager
|
||||||
|
Manager
|
||||||
|
normalizedAt int32
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNormalizingManager(baseManager Manager) Manager {
|
||||||
|
return &NormalizingManager{
|
||||||
|
Manager: baseManager,
|
||||||
|
normalizedAt: -1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *NormalizingManager) AppendChange(chg change.Change) error {
|
||||||
|
chg.Name = NormalizeIfNecessary(chg.Name, chg.Height)
|
||||||
|
return nm.Manager.AppendChange(chg)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *NormalizingManager) IncrementHeightTo(height int32) ([][]byte, error) {
|
||||||
|
nm.addNormalizationForkChangesIfNecessary(height)
|
||||||
|
return nm.Manager.IncrementHeightTo(height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *NormalizingManager) DecrementHeightTo(affectedNames [][]byte, height int32) error {
|
||||||
|
if nm.normalizedAt > height {
|
||||||
|
nm.normalizedAt = -1
|
||||||
|
}
|
||||||
|
return nm.Manager.DecrementHeightTo(affectedNames, height)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *NormalizingManager) NextUpdateHeightOfNode(name []byte) ([]byte, int32) {
|
||||||
|
name, nextUpdate := nm.Manager.NextUpdateHeightOfNode(name)
|
||||||
|
if nextUpdate > param.ActiveParams.NormalizedNameForkHeight {
|
||||||
|
name = Normalize(name)
|
||||||
|
}
|
||||||
|
return name, nextUpdate
|
||||||
|
}
|
||||||
|
|
||||||
|
func (nm *NormalizingManager) addNormalizationForkChangesIfNecessary(height int32) {
|
||||||
|
|
||||||
|
if nm.Manager.Height()+1 != height {
|
||||||
|
// initialization phase
|
||||||
|
if height >= param.ActiveParams.NormalizedNameForkHeight {
|
||||||
|
nm.normalizedAt = param.ActiveParams.NormalizedNameForkHeight // eh, we don't really know that it happened there
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if nm.normalizedAt >= 0 || height != param.ActiveParams.NormalizedNameForkHeight {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
nm.normalizedAt = height
|
||||||
|
log.Info("Generating necessary changes for the normalization fork...")
|
||||||
|
|
||||||
|
// the original code had an unfortunate bug where many unnecessary takeovers
|
||||||
|
// were triggered at the normalization fork
|
||||||
|
predicate := func(name []byte) bool {
|
||||||
|
norm := Normalize(name)
|
||||||
|
eq := bytes.Equal(name, norm)
|
||||||
|
if eq {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
clone := make([]byte, len(name))
|
||||||
|
copy(clone, name) // iteration name buffer is reused on future loops
|
||||||
|
|
||||||
|
// by loading changes for norm here, you can determine if there will be a conflict
|
||||||
|
|
||||||
|
n, err := nm.Manager.NodeAt(nm.Manager.Height(), clone)
|
||||||
|
if err != nil || n == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
defer n.Close()
|
||||||
|
for _, c := range n.Claims {
|
||||||
|
nm.Manager.AppendChange(change.Change{
|
||||||
|
Type: change.AddClaim,
|
||||||
|
Name: norm,
|
||||||
|
Height: c.AcceptedAt,
|
||||||
|
OutPoint: c.OutPoint,
|
||||||
|
ClaimID: c.ClaimID,
|
||||||
|
Amount: c.Amount,
|
||||||
|
ActiveHeight: c.ActiveAt, // necessary to match the old hash
|
||||||
|
VisibleHeight: height, // necessary to match the old hash; it would have been much better without
|
||||||
|
})
|
||||||
|
nm.Manager.AppendChange(change.Change{
|
||||||
|
Type: change.SpendClaim,
|
||||||
|
Name: clone,
|
||||||
|
Height: height,
|
||||||
|
OutPoint: c.OutPoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
for _, c := range n.Supports {
|
||||||
|
nm.Manager.AppendChange(change.Change{
|
||||||
|
Type: change.AddSupport,
|
||||||
|
Name: norm,
|
||||||
|
Height: c.AcceptedAt,
|
||||||
|
OutPoint: c.OutPoint,
|
||||||
|
ClaimID: c.ClaimID,
|
||||||
|
Amount: c.Amount,
|
||||||
|
ActiveHeight: c.ActiveAt,
|
||||||
|
VisibleHeight: height,
|
||||||
|
})
|
||||||
|
nm.Manager.AppendChange(change.Change{
|
||||||
|
Type: change.SpendSupport,
|
||||||
|
Name: clone,
|
||||||
|
Height: height,
|
||||||
|
OutPoint: c.OutPoint,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
nm.Manager.IterateNames(predicate)
|
||||||
|
}
|
31
claimtrie/node/repo.go
Normal file
31
claimtrie/node/repo.go
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
package node
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/change"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Repo defines APIs for Node to access persistence layer.
|
||||||
|
type Repo interface {
|
||||||
|
// AppendChanges saves changes into the repo.
|
||||||
|
// The changes can belong to different nodes, but the chronological
|
||||||
|
// order must be preserved for the same node.
|
||||||
|
AppendChanges(changes []change.Change) error
|
||||||
|
|
||||||
|
// LoadChanges loads changes of a node up to (includes) the specified height.
|
||||||
|
// If no changes found, both returned slice and error will be nil.
|
||||||
|
LoadChanges(name []byte) ([]change.Change, error)
|
||||||
|
|
||||||
|
DropChanges(name []byte, finalHeight int32) error
|
||||||
|
|
||||||
|
// Close closes the repo.
|
||||||
|
Close() error
|
||||||
|
|
||||||
|
// IterateChildren returns change sets for each of name.+
|
||||||
|
// Return false on f to stop the iteration.
|
||||||
|
IterateChildren(name []byte, f func(changes []change.Change) bool) error
|
||||||
|
|
||||||
|
// IterateAll iterates keys until the predicate function returns false
|
||||||
|
IterateAll(predicate func(name []byte) bool)
|
||||||
|
|
||||||
|
Flush() error
|
||||||
|
}
|
285
claimtrie/param/delays.go
Normal file
285
claimtrie/param/delays.go
Normal file
|
@ -0,0 +1,285 @@
|
||||||
|
package param
|
||||||
|
|
||||||
|
var DelayWorkarounds = generateDelayWorkarounds() // called "removal workarounds" in previous versions
|
||||||
|
|
||||||
|
func generateDelayWorkarounds() map[string][]int32 {
|
||||||
|
return map[string][]int32{
|
||||||
|
"travtest01": {426898},
|
||||||
|
"gauntlet-invade-the-darkness-lvl-1-of": {583305},
|
||||||
|
"fr-let-s-play-software-inc-jay": {588308},
|
||||||
|
"fr-motorsport-manager-jay-s-racing": {588308},
|
||||||
|
"fr-crusader-kings-2-la-dynastie-6": {588318},
|
||||||
|
"fr-jurassic-world-evolution-let-s-play": {588318},
|
||||||
|
"calling-tech-support-scammers-live-3": {588683, 646584},
|
||||||
|
"let-s-play-jackbox-games": {589013},
|
||||||
|
"lets-play-jackbox-games-5": {589013},
|
||||||
|
"kabutothesnake-s-live-ps4-broadcast": {589538},
|
||||||
|
"no-eas-strong-thunderstorm-advisory": {589554},
|
||||||
|
"geometry-dash-level-requests": {589564},
|
||||||
|
"geometry-dash-level-requests-2": {589564},
|
||||||
|
"star-ocean-integrity-and-faithlessness": {589609},
|
||||||
|
"@pop": {589613},
|
||||||
|
"ullash": {589630},
|
||||||
|
"today-s-professionals-2018-winter-3": {589640},
|
||||||
|
"today-s-professionals-2018-winter-4": {589640},
|
||||||
|
"today-s-professionals-2018-winter-10": {589641},
|
||||||
|
"today-s-professionals-big-brother-6-13": {589641},
|
||||||
|
"today-s-professionals-big-brother-6-14": {589641},
|
||||||
|
"today-s-professionals-big-brother-6-26": {589641},
|
||||||
|
"today-s-professionals-big-brother-6-27": {589641},
|
||||||
|
"today-s-professionals-big-brother-6-28": {589641},
|
||||||
|
"today-s-professionals-big-brother-6-29": {589641},
|
||||||
|
"dark-souls-iii": {589697},
|
||||||
|
"bobby-blades": {589760},
|
||||||
|
"adrian": {589803},
|
||||||
|
"roblox-2": {589803, 597925},
|
||||||
|
"roblox-4": {589803},
|
||||||
|
"roblox-5": {589803},
|
||||||
|
"roblox-6": {589803},
|
||||||
|
"roblox-7": {589803},
|
||||||
|
"roblox-8": {589803},
|
||||||
|
"madden-17": {589809},
|
||||||
|
"madden-18-franchise": {589810},
|
||||||
|
"fifa-14-android-astrodude44-vs": {589831},
|
||||||
|
"gaming-with-silverwolf-live-stream-3": {589849},
|
||||||
|
"gaming-with-silverwolf-live-stream-4": {589849},
|
||||||
|
"gaming-with-silverwolf-live-stream-5": {589849},
|
||||||
|
"gaming-with-silverwolf-videos-live": {589849},
|
||||||
|
"gaming-with-silverwolf-live-stream-6": {589851},
|
||||||
|
"live-q-a": {589851},
|
||||||
|
"classic-sonic-games": {589870},
|
||||||
|
"gta": {589926},
|
||||||
|
"j-dog7973-s-fortnite-squad": {589926},
|
||||||
|
"wow-warlords-of-draenor-horde-side": {589967},
|
||||||
|
"minecraft-ps4-hardcore-survival-2-the-5": {589991},
|
||||||
|
"happy-new-year-2017": {590013},
|
||||||
|
"come-chill-with-rekzzey-2": {590020},
|
||||||
|
"counter-strike-global-offensive-funny": {590031},
|
||||||
|
"father-vs-son-stickfight-stickfight": {590178},
|
||||||
|
"little-t-playing-subnautica-livestream": {590178},
|
||||||
|
"today-s-professionals-big-brother-7-26-5": {590200},
|
||||||
|
"50585be4e3159a7-1": {590206},
|
||||||
|
"dark-souls-iii-soul-level-1-challenge": {590223},
|
||||||
|
"dark-souls-iii-soul-level-1-challenge-3": {590223},
|
||||||
|
"let-s-play-sniper-elite-4-authentic-2": {590225},
|
||||||
|
"skyrim-special-edition-ps4-platinum-4": {590225},
|
||||||
|
"let-s-play-final-fantasy-the-zodiac-2": {590226},
|
||||||
|
"let-s-play-final-fantasy-the-zodiac-3": {590226},
|
||||||
|
"ls-h-ppchen-halloween-stream-vom-31-10": {590401},
|
||||||
|
"a-new-stream": {590669},
|
||||||
|
"danganronpa-v3-killing-harmony-episode": {590708},
|
||||||
|
"danganronpa-v3-killing-harmony-episode-4": {590708},
|
||||||
|
"danganronpa-v3-killing-harmony-episode-6": {590708},
|
||||||
|
"danganronpa-v3-killing-harmony-episode-8": {590708},
|
||||||
|
"danganronpa-v3-killing-harmony-episode-9": {590708},
|
||||||
|
"call-of-duty-infinite-warfare-gameplay-2": {591982},
|
||||||
|
"destiny-the-taken-king-gameplay": {591982},
|
||||||
|
"horizon-zero-dawn-100-complete-4": {591983},
|
||||||
|
"ghost-recon-wildlands-100-complete-4": {591984},
|
||||||
|
"nier-automata-100-complete-gameplay-25": {591985},
|
||||||
|
"frustrert": {592291},
|
||||||
|
"call-of-duty-black-ops-3-multiplayer": {593504},
|
||||||
|
"rayman-legends-challenges-app-the": {593551},
|
||||||
|
"super-mario-sunshine-3-player-race-2": {593552},
|
||||||
|
"some-new-stuff-might-play-a-game": {593698},
|
||||||
|
"memory-techniques-1-000-people-system": {595537},
|
||||||
|
"propresenter-6-tutorials-new-features-4": {595559},
|
||||||
|
"rocket-league-live": {595559},
|
||||||
|
"fortnite-battle-royale": {595818},
|
||||||
|
"fortnite-battle-royale-2": {595818},
|
||||||
|
"ohare12345-s-live-ps4-broadcast": {595818},
|
||||||
|
"super-smash-bros-u-home-run-contest-13": {595838},
|
||||||
|
"super-smash-bros-u-home-run-contest-15": {595838},
|
||||||
|
"super-smash-bros-u-home-run-contest-2": {595838, 595844},
|
||||||
|
"super-smash-bros-u-home-run-contest-22": {595838, 595845},
|
||||||
|
"super-smash-bros-u-multi-man-smash-3": {595838},
|
||||||
|
"minecraft-survival-biedronka-i-czarny-2": {596828},
|
||||||
|
"gramy-minecraft-jasmc-pl": {596829},
|
||||||
|
"farcry-5-gameplay": {595818},
|
||||||
|
"my-channel-trailer": {595818},
|
||||||
|
"full-song-production-tutorial-aeternum": {596934},
|
||||||
|
"blackboxglobalreview-hd": {597091},
|
||||||
|
"tom-clancy-s-rainbow-six-siege": {597633},
|
||||||
|
"5-new-technology-innovations-in-5": {597635},
|
||||||
|
"5-new-technology-innovations-in-5-2": {597635},
|
||||||
|
"how-to-play-nothing-else-matters-on": {597637},
|
||||||
|
"rb6": {597639},
|
||||||
|
"borderlands-2-tiny-tina-s-assault-on": {597658},
|
||||||
|
"let-s-play-borderlands-the-pre-sequel": {597658},
|
||||||
|
"caveman-world-mountains-of-unga-boonga": {597660},
|
||||||
|
"for-honor-ps4-2": {597706},
|
||||||
|
"fortnite-episode-1": {597728},
|
||||||
|
"300-subscribers": {597750},
|
||||||
|
"viscera-cleanup-detail-santa-s-rampage": {597755},
|
||||||
|
"infinite-voxel-terrain-in-unity-update": {597777},
|
||||||
|
"let-s-play-pok-mon-light-platinum": {597783},
|
||||||
|
"video-2": {597785},
|
||||||
|
"video-8": {597785},
|
||||||
|
"finally": {597793},
|
||||||
|
"let-s-play-mario-party-luigi-s-engine": {597796},
|
||||||
|
"my-edited-video": {597799},
|
||||||
|
"we-need-to-talk": {597800},
|
||||||
|
"tf2-stream-2": {597811},
|
||||||
|
"royal-thumble-tuesday-night-thumbdown": {597814},
|
||||||
|
"beat-it-michael-jackson-cover": {597815},
|
||||||
|
"black-ops-3": {597816},
|
||||||
|
"call-of-duty-black-ops-3-campaign": {597819},
|
||||||
|
"skyrim-special-edition-silent-2": {597822},
|
||||||
|
"the-chainsmokers-everybody-hates-me": {597823},
|
||||||
|
"experiment-glowing-1000-degree-knife-vs": {597824},
|
||||||
|
"l1011widebody-friends-let-s-play-2": {597824},
|
||||||
|
"call-of-duty-black-ops-4": {597825},
|
||||||
|
"let-s-play-fallout-2-restoration-3": {597825},
|
||||||
|
"let-s-play-fallout-2-restoration-19": {597826},
|
||||||
|
"let-s-play-fallout-2-restoration-27": {597826},
|
||||||
|
"2015": {597828},
|
||||||
|
"payeer": {597829},
|
||||||
|
"youtube-3": {597829},
|
||||||
|
"bitcoin-5": {597830},
|
||||||
|
"2016": {597831},
|
||||||
|
"bitcoin-2": {597831},
|
||||||
|
"dreamtowards": {597831},
|
||||||
|
"surfearner": {597831},
|
||||||
|
"100-000": {597832},
|
||||||
|
"20000": {597833},
|
||||||
|
"remme": {597833},
|
||||||
|
"hycon": {597834},
|
||||||
|
"robocraft": {597834},
|
||||||
|
"saturday-night-baseball-with-37": {597834},
|
||||||
|
"let-s-play-command-conquer-red-alert-9": {597835},
|
||||||
|
"15-curiosidades-que-probablemente-ya": {597837},
|
||||||
|
"elder-scrolls-online-road-to-level-20": {597893},
|
||||||
|
"playerunknown-s-battlegrounds": {597894},
|
||||||
|
"black-ops-3-fun": {597897},
|
||||||
|
"mortal-kombat-xl-the-funniest": {597899},
|
||||||
|
"try-not-to-laugh-2": {597899},
|
||||||
|
"call-of-duty-advanced-warfare-domination": {597898},
|
||||||
|
"my-live-stream-with-du-recorder-5": {597900},
|
||||||
|
"ls-h-ppchen-halloween-stream-vom-31-10-2": {597904},
|
||||||
|
"ls-h-ppchen-halloween-stream-vom-31-10-3": {597904},
|
||||||
|
"how-it-feels-to-chew-5-gum-funny-8": {597905},
|
||||||
|
"live-stream-mu-club-america-3": {597918},
|
||||||
|
"black-death": {597927},
|
||||||
|
"lets-play-spore-with-3": {597929},
|
||||||
|
"true-mov-2": {597933},
|
||||||
|
"fortnite-w-pat-the-rat-pat-the-rat": {597935},
|
||||||
|
"jugando-pokemon-esmeralda-gba": {597935},
|
||||||
|
"talking-about-my-channel-and-much-more-4": {597936},
|
||||||
|
"-14": {597939},
|
||||||
|
"-15": {597939},
|
||||||
|
"-16": {597939},
|
||||||
|
"-17": {597939},
|
||||||
|
"-18": {597939},
|
||||||
|
"-20": {597939},
|
||||||
|
"-21": {597939},
|
||||||
|
"-24": {597939},
|
||||||
|
"-25": {597939},
|
||||||
|
"-26": {597939},
|
||||||
|
"-27": {597939},
|
||||||
|
"-28": {597939},
|
||||||
|
"-29": {597939},
|
||||||
|
"-31": {597941},
|
||||||
|
"-34": {597941},
|
||||||
|
"-6": {597939},
|
||||||
|
"-7": {597939},
|
||||||
|
"10-4": {612097},
|
||||||
|
"10-6": {612097},
|
||||||
|
"10-7": {612097},
|
||||||
|
"10-diy": {612097},
|
||||||
|
"10-twitch": {612097},
|
||||||
|
"100-5": {597909},
|
||||||
|
"189f2f04a378c02-1": {612097},
|
||||||
|
"2011-2": {597917},
|
||||||
|
"2011-3": {597917},
|
||||||
|
"2c61c818687ed09-1": {612097},
|
||||||
|
"5-diy-4": {612097},
|
||||||
|
"@andymcdandycdn": {640212},
|
||||||
|
"@lividjava": {651654},
|
||||||
|
"@mhx": {653957},
|
||||||
|
"@tipwhatyoulike": {599792},
|
||||||
|
"@wibbels": {612195},
|
||||||
|
"@yisraeldov": {647416},
|
||||||
|
"beyaz-hap-biseks-el-evlat": {657957},
|
||||||
|
"bilgisayar-al-t-rma-s-recinde-ya-ananlar": {657957},
|
||||||
|
"brave-como-ganhar-dinheiro-todos-os-dias": {598494},
|
||||||
|
"c81e728d9d4c2f6-1": {598178},
|
||||||
|
"call-of-duty-world-war-2": {597935},
|
||||||
|
"chain-reaction": {597940},
|
||||||
|
"commodore-64-an-lar-ve-oyunlar": {657957},
|
||||||
|
"counter-strike-global-offensive-gameplay": {597900},
|
||||||
|
"dead-island-riptide-co-op-walkthrough-2": {597904, 598105},
|
||||||
|
"diy-10": {612097},
|
||||||
|
"diy-11": {612097},
|
||||||
|
"diy-13": {612097},
|
||||||
|
"diy-14": {612097},
|
||||||
|
"diy-19": {612097},
|
||||||
|
"diy-4": {612097},
|
||||||
|
"diy-6": {612097},
|
||||||
|
"diy-7": {612097},
|
||||||
|
"diy-9": {612097},
|
||||||
|
"doktor-ve-patron-sahnesinin-haz-rl-k-ve": {657957},
|
||||||
|
"eat-the-street": {597910},
|
||||||
|
"fallout-4-modded": {597901},
|
||||||
|
"fallout-4-walkthrough": {597900},
|
||||||
|
"filmli-efecast-129-film-inde-film-inde": {657957},
|
||||||
|
"filmli-efecast-130-ger-ek-hayatta-anime": {657957},
|
||||||
|
"filmli-efecast-97-netflix-filmi-form-l": {657957},
|
||||||
|
"for-honor-2": {597932},
|
||||||
|
"for-honor-4": {597932},
|
||||||
|
"gta-5": {597902},
|
||||||
|
"gta-5-2": {597902},
|
||||||
|
"helldriver-g-n-n-ekstrem-filmi": {657957},
|
||||||
|
"hi-4": {597933},
|
||||||
|
"hi-5": {597933},
|
||||||
|
"hi-7": {597933},
|
||||||
|
"kizoa-movie-video-slideshow-maker": {597900, 597932},
|
||||||
|
"l1011widebody-friends-let-s-play-3": {598070},
|
||||||
|
"lbry": {608276},
|
||||||
|
"lets-play-spore-with": {597930},
|
||||||
|
"madants": {625032},
|
||||||
|
"mechwarrior-2-soundtrack-clan-jade": {598070},
|
||||||
|
"milo-forbidden-conversation": {655173},
|
||||||
|
"mobile-record": {597910},
|
||||||
|
"mouths": {607379},
|
||||||
|
"mp-aleyna-tilki-nin-zorla-seyrettirilen": {657957},
|
||||||
|
"mp-atat-rk-e-eytan-diyen-yunan-as-ll": {657957},
|
||||||
|
"mp-bah-eli-calan-avukatlar-yla-g-r-s-n": {657957},
|
||||||
|
"mp-bu-podcast-babalar-in": {657957},
|
||||||
|
"mp-bu-podcasti-akp-li-tan-d-klar-n-za": {657957},
|
||||||
|
"mp-gaziantep-te-tacizle-su-lan-p-dayak": {650409},
|
||||||
|
"mp-hatipo-lu-nun-ermeni-bir-ocu-u-canl": {657957},
|
||||||
|
"mp-k-rt-annelerin-hdp-ye-tepkisi": {657957},
|
||||||
|
"mp-kenan-sofuo-lu-nun-mamo-lu-na-destek": {657957},
|
||||||
|
"mp-mamo-lu-nun-muhafazakar-g-r-nmesi": {657957},
|
||||||
|
"mp-mhp-akp-gerginli-i": {657957},
|
||||||
|
"mp-otob-ste-t-rkle-meyin-diye-ba-ran-svi": {657957},
|
||||||
|
"mp-pace-i-kazand-m-diyip-21-bin-dolar": {657957},
|
||||||
|
"mp-rusya-da-kad-nlara-tecav-zc-s-n-ld": {657957},
|
||||||
|
"mp-s-n-rs-z-nafakan-n-kalkmas-adil-mi": {657957},
|
||||||
|
"mp-susamam-ark-s-ve-serkan-nci-nin-ark": {657957},
|
||||||
|
"mp-y-lmaz-zdil-in-kitap-paralar-yla-yard": {657957},
|
||||||
|
"mp-yang-n-u-aklar-pahal-diyen-orman": {657957},
|
||||||
|
"mp-yeni-zelanda-katliam-ndan-siyasi-rant": {657957},
|
||||||
|
"my-edited-video-4": {597932},
|
||||||
|
"my-live-stream-with-du-recorder": {597900},
|
||||||
|
"my-live-stream-with-du-recorder-3": {597900},
|
||||||
|
"new-channel-intro": {598235},
|
||||||
|
"paladins-3": {597900},
|
||||||
|
"popstar-sahnesi-kamera-arkas-g-r-nt-leri": {657957},
|
||||||
|
"retro-bilgisayar-bulu-mas": {657957},
|
||||||
|
"scp-t-rk-e-scp-002-canl-oda": {657957},
|
||||||
|
"steep": {597900},
|
||||||
|
"stephen-hicks-postmodernism-reprise": {655173},
|
||||||
|
"super-smash-bros-u-brawl-co-op-event": {595841},
|
||||||
|
"super-smash-bros-u-super-mario-u-smash": {595839},
|
||||||
|
"super-smash-bros-u-zelda-smash-series": {595841},
|
||||||
|
"superonline-fiber-den-efsane-kaz-k-yedim": {657957},
|
||||||
|
"talking-about-my-channel-and-much-more-5": {597936},
|
||||||
|
"test1337reflector356": {627814},
|
||||||
|
"the-last-of-us-remastered-2": {597915},
|
||||||
|
"tom-clancy-s-ghost-recon-wildlands-2": {597916},
|
||||||
|
"tom-clancy-s-rainbow-six-siege-3": {597935},
|
||||||
|
"wwe-2k18-with-that-guy-and-tricky": {597901},
|
||||||
|
"yay-nc-bob-afet-kamera-arkas": {657957},
|
||||||
|
}
|
||||||
|
}
|
74
claimtrie/param/general.go
Normal file
74
claimtrie/param/general.go
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
package param
|
||||||
|
|
||||||
|
import "github.com/btcsuite/btcd/wire"
|
||||||
|
|
||||||
|
type ClaimTrieParams struct {
|
||||||
|
MaxActiveDelay int32
|
||||||
|
ActiveDelayFactor int32
|
||||||
|
|
||||||
|
MaxNodeManagerCacheSize int
|
||||||
|
|
||||||
|
OriginalClaimExpirationTime int32
|
||||||
|
ExtendedClaimExpirationTime int32
|
||||||
|
ExtendedClaimExpirationForkHeight int32
|
||||||
|
|
||||||
|
MaxRemovalWorkaroundHeight int32
|
||||||
|
|
||||||
|
NormalizedNameForkHeight int32
|
||||||
|
AllClaimsInMerkleForkHeight int32
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
ActiveParams = MainNet
|
||||||
|
|
||||||
|
MainNet = ClaimTrieParams{
|
||||||
|
MaxActiveDelay: 4032,
|
||||||
|
ActiveDelayFactor: 32,
|
||||||
|
MaxNodeManagerCacheSize: 32000,
|
||||||
|
|
||||||
|
OriginalClaimExpirationTime: 262974,
|
||||||
|
ExtendedClaimExpirationTime: 2102400,
|
||||||
|
ExtendedClaimExpirationForkHeight: 400155, // https://lbry.io/news/hf1807
|
||||||
|
MaxRemovalWorkaroundHeight: 658300,
|
||||||
|
NormalizedNameForkHeight: 539940, // targeting 21 March 2019}, https://lbry.com/news/hf1903
|
||||||
|
AllClaimsInMerkleForkHeight: 658309, // targeting 30 Oct 2019}, https://lbry.com/news/hf1910
|
||||||
|
}
|
||||||
|
|
||||||
|
TestNet = ClaimTrieParams{
|
||||||
|
MaxActiveDelay: 4032,
|
||||||
|
ActiveDelayFactor: 32,
|
||||||
|
MaxNodeManagerCacheSize: 32000,
|
||||||
|
|
||||||
|
OriginalClaimExpirationTime: 262974,
|
||||||
|
ExtendedClaimExpirationTime: 2102400,
|
||||||
|
ExtendedClaimExpirationForkHeight: 278160,
|
||||||
|
MaxRemovalWorkaroundHeight: 1, // if you get a hash mismatch, come back to this
|
||||||
|
NormalizedNameForkHeight: 993380,
|
||||||
|
AllClaimsInMerkleForkHeight: 1198559,
|
||||||
|
}
|
||||||
|
|
||||||
|
Regtest = ClaimTrieParams{
|
||||||
|
MaxActiveDelay: 4032,
|
||||||
|
ActiveDelayFactor: 32,
|
||||||
|
MaxNodeManagerCacheSize: 32000,
|
||||||
|
|
||||||
|
OriginalClaimExpirationTime: 500,
|
||||||
|
ExtendedClaimExpirationTime: 600,
|
||||||
|
ExtendedClaimExpirationForkHeight: 800,
|
||||||
|
MaxRemovalWorkaroundHeight: -1,
|
||||||
|
NormalizedNameForkHeight: 250,
|
||||||
|
AllClaimsInMerkleForkHeight: 349,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
func SetNetwork(net wire.BitcoinNet) {
|
||||||
|
|
||||||
|
switch net {
|
||||||
|
case wire.MainNet:
|
||||||
|
ActiveParams = MainNet
|
||||||
|
case wire.TestNet3:
|
||||||
|
ActiveParams = TestNet
|
||||||
|
case wire.TestNet, wire.SimNet: // "regtest"
|
||||||
|
ActiveParams = Regtest
|
||||||
|
}
|
||||||
|
}
|
451
claimtrie/param/takeovers.go
Normal file
451
claimtrie/param/takeovers.go
Normal file
|
@ -0,0 +1,451 @@
|
||||||
|
package param
|
||||||
|
|
||||||
|
var TakeoverWorkarounds = generateTakeoverWorkarounds()
|
||||||
|
|
||||||
|
func generateTakeoverWorkarounds() map[string]int { // TODO: the values here are unused; bools would probably be better
|
||||||
|
return map[string]int{
|
||||||
|
"496856_HunterxHunterAMV": 496835,
|
||||||
|
"542978_namethattune1": 542429,
|
||||||
|
"543508_namethattune-5": 543306,
|
||||||
|
"546780_forecasts": 546624,
|
||||||
|
"548730_forecasts": 546780,
|
||||||
|
"551540_forecasts": 548730,
|
||||||
|
"552380_chicthinkingofyou": 550804,
|
||||||
|
"560363_takephotowithlbryteam": 559962,
|
||||||
|
"563710_test-img": 563700,
|
||||||
|
"566750_itila": 543261,
|
||||||
|
"567082_malabarismo-com-bolas-de-futebol-vs-chap": 563592,
|
||||||
|
"596860_180mphpullsthrougheurope": 596757,
|
||||||
|
"617743_vaccines": 572756,
|
||||||
|
"619609_copface-slamshandcuffedteengirlintoconcrete": 539940,
|
||||||
|
"620392_banker-exposes-satanic-elite": 597788,
|
||||||
|
"624997_direttiva-sulle-armi-ue-in-svizzera-di": 567908,
|
||||||
|
"624997_best-of-apex": 585580,
|
||||||
|
"629970_cannot-ignore-my-veins": 629914,
|
||||||
|
"633058_bio-waste-we-programmed-your-brain": 617185,
|
||||||
|
"633601_macrolauncher-overview-first-look": 633058,
|
||||||
|
"640186_its-up-to-you-and-i-2019": 639116,
|
||||||
|
"640241_tor-eas-3-20": 592645,
|
||||||
|
"640522_seadoxdark": 619531,
|
||||||
|
"640617_lbry-przewodnik-1-instalacja": 451186,
|
||||||
|
"640623_avxchange-2019-the-next-netflix-spotify": 606790,
|
||||||
|
"640684_algebra-introduction": 624152,
|
||||||
|
"640684_a-high-school-math-teacher-does-a": 600885,
|
||||||
|
"640684_another-random-life-update": 600884,
|
||||||
|
"640684_who-is-the-taylor-series-for": 600882,
|
||||||
|
"640684_tedx-talk-released": 612303,
|
||||||
|
"640730_e-mental": 615375,
|
||||||
|
"641143_amiga-1200-bespoke-virgin-cinema": 623542,
|
||||||
|
"641161_dreamscape-432-omega": 618894,
|
||||||
|
"641162_2019-topstone-carbon-force-etap-axs-bike": 639107,
|
||||||
|
"641186_arin-sings-big-floppy-penis-live-jazz-2": 638904,
|
||||||
|
"641421_edward-snowden-on-bitcoin-and-privacy": 522729,
|
||||||
|
"641421_what-is-libra-facebook-s-new": 598236,
|
||||||
|
"641421_what-are-stablecoins-counter-party-risk": 583508,
|
||||||
|
"641421_anthony-pomp-pompliano-discusses-crypto": 564416,
|
||||||
|
"641421_tim-draper-crypto-invest-summit-2019": 550329,
|
||||||
|
"641421_mass-adoption-and-what-will-it-take-to": 549781,
|
||||||
|
"641421_dragonwolftech-youtube-channel-trailer": 567128,
|
||||||
|
"641421_naomi-brockwell-s-weekly-crypto-recap": 540006,
|
||||||
|
"641421_blockchain-based-youtube-twitter": 580809,
|
||||||
|
"641421_andreas-antonopoulos-on-privacy-privacy": 533522,
|
||||||
|
"641817_mexico-submits-and-big-tech-worsens": 582977,
|
||||||
|
"641817_why-we-need-travel-bans": 581354,
|
||||||
|
"641880_censored-by-patreon-bitchute-shares": 482460,
|
||||||
|
"641880_crypto-wonderland": 485218,
|
||||||
|
"642168_1-diabolo-julio-cezar-16-cbmcp-freestyle": 374999,
|
||||||
|
"642314_tough-students": 615780,
|
||||||
|
"642697_gamercauldronep2": 642153,
|
||||||
|
"643406_the-most-fun-i-ve-had-in-a-long-time": 616506,
|
||||||
|
"643893_spitshine69-and-uk-freedom-audits": 616876,
|
||||||
|
"644480_my-mum-getting-attacked-a-duck": 567624,
|
||||||
|
"644486_the-cryptocurrency-experiment": 569189,
|
||||||
|
"644486_tag-you-re-it": 558316,
|
||||||
|
"644486_orange-county-mineral-society-rock-and": 397138,
|
||||||
|
"644486_sampling-with-the-gold-rush-nugget": 527960,
|
||||||
|
"644562_september-15-21-a-new-way-of-doing": 634792,
|
||||||
|
"644562_july-week-3-collective-frequency-general": 607942,
|
||||||
|
"644562_september-8-14-growing-up-general": 630977,
|
||||||
|
"644562_august-4-10-collective-frequency-general": 612307,
|
||||||
|
"644562_august-11-17-collective-frequency": 617279,
|
||||||
|
"644562_september-1-7-gentle-wake-up-call": 627104,
|
||||||
|
"644607_no-more-lol": 643497,
|
||||||
|
"644607_minion-masters-who-knew": 641313,
|
||||||
|
"645236_danganronpa-3-the-end-of-hope-s-peak": 644153,
|
||||||
|
"645348_captchabot-a-discord-bot-to-protect-your": 592810,
|
||||||
|
"645701_the-xero-hour-saint-greta-of-thunberg": 644081,
|
||||||
|
"645701_batman-v-superman-theological-notions": 590189,
|
||||||
|
"645918_emacs-is-great-ep-0-init-el-from-org": 575666,
|
||||||
|
"645918_emacs-is-great-ep-1-packages": 575666,
|
||||||
|
"645918_emacs-is-great-ep-40-pt-2-hebrew": 575668,
|
||||||
|
"645923_nasal-snuff-review-osp-batch-2": 575658,
|
||||||
|
"645923_why-bit-coin": 575658,
|
||||||
|
"645929_begin-quest": 598822,
|
||||||
|
"645929_filthy-foe": 588386,
|
||||||
|
"645929_unsanitary-snow": 588386,
|
||||||
|
"645929_famispam-1-music-box": 588386,
|
||||||
|
"645929_running-away": 598822,
|
||||||
|
"645931_my-beloved-chris-madsen": 589114,
|
||||||
|
"645931_space-is-consciousness-chris-madsen": 589116,
|
||||||
|
"645947_gasifier-rocket-stove-secondary-burn": 590595,
|
||||||
|
"645949_mouse-razer-abyssus-v2-e-mousepad": 591139,
|
||||||
|
"645949_pr-temporada-2018-league-of-legends": 591138,
|
||||||
|
"645949_windows-10-build-9901-pt-br": 591137,
|
||||||
|
"645949_abrindo-pacotes-do-festival-lunar-2018": 591139,
|
||||||
|
"645949_unboxing-camisetas-personalizadas-play-e": 591138,
|
||||||
|
"645949_abrindo-envelopes-do-festival-lunar-2017": 591138,
|
||||||
|
"645951_grub-my-grub-played-guruku-tersayang": 618033,
|
||||||
|
"645951_ismeeltimepiece": 618038,
|
||||||
|
"645951_thoughts-on-doom": 596485,
|
||||||
|
"645951_thoughts-on-god-of-war-about-as-deep-as": 596485,
|
||||||
|
"645956_linux-lite-3-6-see-what-s-new": 645195,
|
||||||
|
"646191_kahlil-gibran-the-prophet-part-1": 597637,
|
||||||
|
"646551_crypto-market-crash-should-you-sell-your": 442613,
|
||||||
|
"646551_live-crypto-trading-and-market-analysis": 442615,
|
||||||
|
"646551_5-reasons-trading-is-always-better-than": 500850,
|
||||||
|
"646551_digitex-futures-dump-panic-selling-or": 568065,
|
||||||
|
"646552_how-to-install-polarr-on-kali-linux-bynp": 466235,
|
||||||
|
"646586_electoral-college-kids-civics-lesson": 430818,
|
||||||
|
"646602_grapes-full-90-minute-watercolour": 537108,
|
||||||
|
"646602_meizu-mx4-the-second-ubuntu-phone": 537109,
|
||||||
|
"646609_how-to-set-up-the-ledger-nano-x": 569992,
|
||||||
|
"646609_how-to-buy-ethereum": 482354,
|
||||||
|
"646609_how-to-install-setup-the-exodus-multi": 482356,
|
||||||
|
"646609_how-to-manage-your-passwords-using": 531987,
|
||||||
|
"646609_cryptodad-s-live-q-a-friday-may-3rd-2019": 562303,
|
||||||
|
"646638_resident-evil-ada-chapter-5-final": 605612,
|
||||||
|
"646639_taurus-june-2019-career-love-tarot": 586910,
|
||||||
|
"646652_digital-bullpen-ep-5-building-a-digital": 589274,
|
||||||
|
"646661_sunlight": 591076,
|
||||||
|
"646661_grasp-lab-nasa-open-mct-series": 589414,
|
||||||
|
"646663_bunnula-s-creepers-tim-pool-s-beanie-a": 599669,
|
||||||
|
"646663_bunnula-music-hey-ya-by-outkast": 605685,
|
||||||
|
"646663_bunnula-tv-s-music-television-eunoia": 644437,
|
||||||
|
"646663_the-pussy-centipede-40-sneakers-and": 587265,
|
||||||
|
"646663_bunnula-reacts-ashton-titty-whitty": 596988,
|
||||||
|
"646677_filip-reviews-jeromes-dream-cataracts-so": 589751,
|
||||||
|
"646691_fascism-and-its-mobilizing-passions": 464342,
|
||||||
|
"646692_hsb-color-layers-action-for-adobe": 586533,
|
||||||
|
"646692_master-colorist-action-pack-extracting": 631830,
|
||||||
|
"646693_how-to-protect-your-garden-from-animals": 588476,
|
||||||
|
"646693_gardening-for-the-apocalypse-epic": 588472,
|
||||||
|
"646693_my-first-bee-hive-foundationless-natural": 588469,
|
||||||
|
"646693_dragon-fruit-and-passion-fruit-planting": 588470,
|
||||||
|
"646693_installing-my-first-foundationless": 588469,
|
||||||
|
"646705_first-naza-fpv": 590411,
|
||||||
|
"646717_first-burning-man-2019-detour-034": 630247,
|
||||||
|
"646717_why-bob-marley-was-an-idiot-test-driving": 477558,
|
||||||
|
"646717_we-are-addicted-to-gambling-ufc-207-w": 481398,
|
||||||
|
"646717_ghetto-swap-meet-selling-storage-lockers": 498291,
|
||||||
|
"646738_1-kings-chapter-7-summary-and-what-god": 586599,
|
||||||
|
"646814_brand-spanking-new-junior-high-school": 592378,
|
||||||
|
"646814_lupe-fiasco-freestyle-at-end-of-the-weak": 639535,
|
||||||
|
"646824_how-to-one-stroke-painting-doodles-mixed": 592404,
|
||||||
|
"646824_acrylic-pouring-landscape-with-a-tree": 592404,
|
||||||
|
"646824_how-to-make-a-diy-concrete-paste-planter": 595976,
|
||||||
|
"646824_how-to-make-a-rustic-sand-planter-sand": 592404,
|
||||||
|
"646833_3-day-festival-at-the-galilee-lake-and": 592842,
|
||||||
|
"646833_rainbow-circle-around-the-noon-sun-above": 592842,
|
||||||
|
"646833_energetic-self-control-demonstration": 623811,
|
||||||
|
"646833_bees-congregating": 592842,
|
||||||
|
"646856_formula-offroad-honefoss-sunday-track2": 592872,
|
||||||
|
"646862_h3video1-dc-vs-mb-1": 593237,
|
||||||
|
"646862_h3video1-iwasgoingto-load-up-gmod-but": 593237,
|
||||||
|
"646883_watch-this-game-developer-make-a-video": 592593,
|
||||||
|
"646883_how-to-write-secure-javascript": 592593,
|
||||||
|
"646883_blockchain-technology-explained-2-hour": 592593,
|
||||||
|
"646888_fl-studio-bits": 608155,
|
||||||
|
"646914_andy-s-shed-live-s03e02-the-longest": 592200,
|
||||||
|
"646914_gpo-telephone-776-phone-restoration": 592201,
|
||||||
|
"646916_toxic-studios-co-stream-pubg": 597126,
|
||||||
|
"646916_hyperlapse-of-prague-praha-from-inside": 597109,
|
||||||
|
"646933_videobits-1": 597378,
|
||||||
|
"646933_clouds-developing-daytime-8": 597378,
|
||||||
|
"646933_slechtvalk-in-watertoren-bodegraven": 597378,
|
||||||
|
"646933_timelapse-maansverduistering-16-juli": 605880,
|
||||||
|
"646933_startrails-27": 597378,
|
||||||
|
"646933_passing-clouds-daytime-3": 597378,
|
||||||
|
"646940_nerdgasm-unboxing-massive-playing-cards": 597421,
|
||||||
|
"646946_debunking-cops-volume-3-the-murder-of": 630570,
|
||||||
|
"646961_kingsong-ks16x-electric-unicycle-250km": 636725,
|
||||||
|
"646968_wild-mountain-goats-amazing-rock": 621940,
|
||||||
|
"646968_no-shelter-backcountry-camping-in": 621940,
|
||||||
|
"646968_can-i-live-in-this-through-winter-lets": 645750,
|
||||||
|
"646968_why-i-wear-a-chest-rig-backcountry-or": 621940,
|
||||||
|
"646989_marc-ivan-o-gorman-promo-producer-editor": 645656,
|
||||||
|
"647045_@moraltis": 646367,
|
||||||
|
"647045_moraltis-twitch-highlights-first-edit": 646368,
|
||||||
|
"647075_the-3-massive-tinder-convo-mistakes": 629464,
|
||||||
|
"647075_how-to-get-friend-zoned-via-text": 592298,
|
||||||
|
"647075_don-t-do-this-on-tinder": 624591,
|
||||||
|
"647322_world-of-tanks-7-kills": 609905,
|
||||||
|
"647322_the-tier-6-auto-loading-swedish-meatball": 591338,
|
||||||
|
"647416_hypnotic-soundscapes-garden-of-the": 596923,
|
||||||
|
"647416_hypnotic-soundscapes-the-cauldron-sacred": 596928,
|
||||||
|
"647416_schumann-resonance-to-theta-sweep": 596920,
|
||||||
|
"647416_conversational-indirect-hypnosis-why": 596913,
|
||||||
|
"647493_mimirs-brunnr": 590498,
|
||||||
|
"648143_live-ita-completiamo-the-evil-within-2": 646568,
|
||||||
|
"648203_why-we-love-people-that-hurt-us": 591128,
|
||||||
|
"648203_i-didn-t-like-my-baby-and-considered": 591128,
|
||||||
|
"648220_trade-talk-001-i-m-a-vlogger-now-fielder": 597303,
|
||||||
|
"648220_vise-restoration-record-no-6-vise": 597303,
|
||||||
|
"648540_amv-reign": 571863,
|
||||||
|
"648540_amv-virus": 571863,
|
||||||
|
"648588_audial-drift-(a-journey-into-sound)": 630217,
|
||||||
|
"648616_quick-zbrush-tip-transpose-master-scale": 463205,
|
||||||
|
"648616_how-to-create-3d-horns-maya-to-zbrush-2": 463205,
|
||||||
|
"648815_arduino-based-cartridge-game-handheld": 593252,
|
||||||
|
"648815_a-maze-update-3-new-game-modes-amazing": 593252,
|
||||||
|
"649209_denmark-trip": 591428,
|
||||||
|
"649209_stunning-4k-drone-footage": 591428,
|
||||||
|
"649215_how-to-create-a-channel-and-publish-a": 414908,
|
||||||
|
"649215_lbryclass-11-how-to-get-your-deposit": 632420,
|
||||||
|
"649543_spring-break-madness-at-universal": 599698,
|
||||||
|
"649921_navegador-brave-navegador-da-web-seguro": 649261,
|
||||||
|
"650191_stream-intro": 591301,
|
||||||
|
"650946_platelet-chan-fan-art": 584601,
|
||||||
|
"650946_aqua-fanart": 584601,
|
||||||
|
"650946_virginmedia-stores-password-in-plain": 619537,
|
||||||
|
"650946_running-linux-on-android-teaser": 604441,
|
||||||
|
"650946_hatsune-miku-ievan-polka": 600126,
|
||||||
|
"650946_digital-security-and-privacy-2-and-a-new": 600135,
|
||||||
|
"650993_my-editorial-comment-on-recent-youtube": 590305,
|
||||||
|
"650993_drive-7-18-2018": 590305,
|
||||||
|
"651011_old-world-put-on-realm-realms-gg": 591899,
|
||||||
|
"651011_make-your-own-soundboard-with-autohotkey": 591899,
|
||||||
|
"651011_ark-survival-https-discord-gg-ad26xa": 637680,
|
||||||
|
"651011_minecraft-featuring-seus-8-just-came-4": 596488,
|
||||||
|
"651057_found-footage-bikinis-at-the-beach-with": 593586,
|
||||||
|
"651057_found-footage-sexy-mom-a-mink-stole": 593586,
|
||||||
|
"651067_who-are-the-gentiles-gomer": 597094,
|
||||||
|
"651067_take-back-the-kingdom-ep-2-450-million": 597094,
|
||||||
|
"651067_mmxtac-implemented-footstep-sounds-and": 597094,
|
||||||
|
"651067_dynasoul-s-blender-to-unreal-animated": 597094,
|
||||||
|
"651103_calling-a-scammer-syntax-error": 612532,
|
||||||
|
"651103_quick-highlight-of-my-day": 647651,
|
||||||
|
"651103_calling-scammers-and-singing-christmas": 612531,
|
||||||
|
"651109_@livingtzm": 637322,
|
||||||
|
"651109_living-tzm-juuso-from-finland-september": 643412,
|
||||||
|
"651373_se-voc-rir-ou-sorrir-reinicie-o-v-deo": 649302,
|
||||||
|
"651476_what-is-pagan-online-polished-new-arpg": 592157,
|
||||||
|
"651476_must-have-elder-scrolls-online-addons": 592156,
|
||||||
|
"651476_who-should-play-albion-online": 592156,
|
||||||
|
"651730_person-detection-with-keras-tensorflow": 621276,
|
||||||
|
"651730_youtube-censorship-take-two": 587249,
|
||||||
|
"651730_new-red-tail-shark-and-two-silver-sharks": 587251,
|
||||||
|
"651730_around-auckland": 587250,
|
||||||
|
"651730_humanism-in-islam": 587250,
|
||||||
|
"651730_tigers-at-auckland-zoo": 587250,
|
||||||
|
"651730_gravity-demonstration": 587250,
|
||||||
|
"651730_copyright-question": 587249,
|
||||||
|
"651730_uberg33k-the-ultimate-software-developer": 599522,
|
||||||
|
"651730_chl-e-swarbrick-auckland-mayoral": 587250,
|
||||||
|
"651730_code-reviews": 587249,
|
||||||
|
"651730_raising-robots": 587251,
|
||||||
|
"651730_teaching-python": 587250,
|
||||||
|
"651730_kelly-tarlton-2016": 587250,
|
||||||
|
"652172_where-is-everything": 589491,
|
||||||
|
"652172_some-guy-and-his-camera": 617062,
|
||||||
|
"652172_practical-information-pt-1": 589491,
|
||||||
|
"652172_latent-vibrations": 589491,
|
||||||
|
"652172_maldek-compilation": 589491,
|
||||||
|
"652444_thank-you-etika-thank-you-desmond": 652121,
|
||||||
|
"652611_plants-vs-zombies-gw2-20190827183609": 624339,
|
||||||
|
"652611_wolfenstein-the-new-order-playthrough-6": 650299,
|
||||||
|
"652887_a-codeigniter-cms-open-source-download": 652737,
|
||||||
|
"652966_@pokesadventures": 632391,
|
||||||
|
"653009_flat-earth-uk-convention-is-a-bust": 585786,
|
||||||
|
"653009_flat-earth-reset-flat-earth-money-tree": 585786,
|
||||||
|
"653011_veil-of-thorns-dispirit-brutal-leech-3": 652475,
|
||||||
|
"653069_being-born-after-9-11": 632218,
|
||||||
|
"653069_8-years-on-youtube-what-it-has-done-for": 637130,
|
||||||
|
"653069_answering-questions-how-original": 521447,
|
||||||
|
"653069_talking-about-my-first-comedy-stand-up": 583450,
|
||||||
|
"653069_doing-push-ups-in-public": 650920,
|
||||||
|
"653069_vlog-extra": 465997,
|
||||||
|
"653069_crying-myself": 465997,
|
||||||
|
"653069_xbox-rejection": 465992,
|
||||||
|
"653354_msps-how-to-find-a-linux-job-where-no": 642537,
|
||||||
|
"653354_windows-is-better-than-linux-vlog-it-and": 646306,
|
||||||
|
"653354_luke-smith-is-wrong-about-everything": 507717,
|
||||||
|
"653354_advice-for-those-starting-out-in-tech": 612452,
|
||||||
|
"653354_treating-yourself-to-make-studying-more": 623561,
|
||||||
|
"653354_lpi-linux-essential-dns-tools-vlog-what": 559464,
|
||||||
|
"653354_is-learning-linux-worth-it-in-2019-vlog": 570886,
|
||||||
|
"653354_huawei-linux-and-cellphones-in-2019-vlog": 578501,
|
||||||
|
"653354_how-to-use-webmin-to-manage-linux": 511507,
|
||||||
|
"653354_latency-concurrency-and-the-best-value": 596857,
|
||||||
|
"653354_how-to-use-the-pomodoro-method-in-it": 506632,
|
||||||
|
"653354_negotiating-compensation-vlog-it-and": 542317,
|
||||||
|
"653354_procedural-goals-vs-outcome-goals-vlog": 626785,
|
||||||
|
"653354_intro-to-raid-understanding-how-raid": 529341,
|
||||||
|
"653354_smokeping": 574693,
|
||||||
|
"653354_richard-stallman-should-not-be-fired": 634928,
|
||||||
|
"653354_unusual-or-specialty-certifications-vlog": 620146,
|
||||||
|
"653354_gratitude-and-small-projects-vlog-it": 564900,
|
||||||
|
"653354_why-linux-on-the-smartphone-is-important": 649543,
|
||||||
|
"653354_opportunity-costs-vlog-it-devops-career": 549708,
|
||||||
|
"653354_double-giveaway-lpi-class-dates-and": 608129,
|
||||||
|
"653354_linux-on-the-smartphone-in-2019-librem": 530426,
|
||||||
|
"653524_celtic-folk-music-full-live-concert-mps": 589762,
|
||||||
|
"653745_aftermath-of-the-mac": 592768,
|
||||||
|
"653745_b-c-a-glock-17-threaded-barrel": 592770,
|
||||||
|
"653800_middle-earth-shadow-of-mordor-by": 590229,
|
||||||
|
"654079_tomand-jeremy-chirs45": 614296,
|
||||||
|
"654096_achamos-carteira-com-grana-olha-o-que": 466262,
|
||||||
|
"654096_viagem-bizarra-e-cansativa-ao-nordeste": 466263,
|
||||||
|
"654096_tedio-na-tailandia-limpeza-de-area": 466265,
|
||||||
|
"654425_schau-bung-2014-in-windischgarsten": 654410,
|
||||||
|
"654425_mitternachtseinlage-ball-rk": 654410,
|
||||||
|
"654425_zugabe-ball-rk-windischgarsten": 654412,
|
||||||
|
"654722_skytrain-in-korea": 463145,
|
||||||
|
"654722_luwak-coffee-the-shit-coffee": 463155,
|
||||||
|
"654722_puppet-show-in-bangkok-thailand": 462812,
|
||||||
|
"654722_kyaito-market-myanmar": 462813,
|
||||||
|
"654724_wipeout-zombies-bo3-custom-zombies-1st": 589569,
|
||||||
|
"654724_the-street-bo3-custom-zombies": 589544,
|
||||||
|
"654880_wwii-airsoft-pow": 586968,
|
||||||
|
"654880_dueling-geese-fight-to-the-death": 586968,
|
||||||
|
"654880_wwii-airsoft-torgau-raw-footage-part4": 586968,
|
||||||
|
"655173_april-2019-q-and-a": 554032,
|
||||||
|
"655173_the-meaning-and-reality-of-individual": 607892,
|
||||||
|
"655173_steven-pinker-progress-despite": 616984,
|
||||||
|
"655173_we-make-stories-out-of-totem-poles": 549090,
|
||||||
|
"655173_jamil-jivani-author-of-why-young-men": 542035,
|
||||||
|
"655173_commentaries-on-jb-peterson-rebel-wisdom": 528898,
|
||||||
|
"655173_auckland-clip-4-on-cain-and-abel": 629242,
|
||||||
|
"655173_peterson-vs-zizek-livestream-tickets": 545285,
|
||||||
|
"655173_auckland-clip-3-the-dawning-of-the-moral": 621154,
|
||||||
|
"655173_religious-belief-and-the-enlightenment": 606269,
|
||||||
|
"655173_auckland-lc-highlight-1-the-presumption": 565783,
|
||||||
|
"655173_q-a-sir-roger-scruton-dr-jordan-b": 544184,
|
||||||
|
"655173_cancellation-polish-national-foundation": 562529,
|
||||||
|
"655173_the-coddling-of-the-american-mind-haidt": 440185,
|
||||||
|
"655173_02-harris-weinstein-peterson-discussion": 430896,
|
||||||
|
"655173_jordan-peterson-threatens-everything-of": 519737,
|
||||||
|
"655173_on-claiming-belief-in-god-commentary": 581738,
|
||||||
|
"655173_how-to-make-the-world-better-really-with": 482317,
|
||||||
|
"655173_quillette-discussion-with-founder-editor": 413749,
|
||||||
|
"655173_jb-peterson-on-free-thought-and-speech": 462849,
|
||||||
|
"655173_marxism-zizek-peterson-official-video": 578453,
|
||||||
|
"655173_patreon-problem-solution-dave-rubin-dr": 490394,
|
||||||
|
"655173_next-week-st-louis-salt-lake-city": 445933,
|
||||||
|
"655173_conversations-with-john-anderson-jordan": 529981,
|
||||||
|
"655173_nz-australia-12-rules-tour-next-2-weeks": 518649,
|
||||||
|
"655173_a-call-to-rebellion-for-ontario-legal": 285451,
|
||||||
|
"655173_2016-personality-lecture-12": 578465,
|
||||||
|
"655173_on-the-vital-necessity-of-free-speech": 427404,
|
||||||
|
"655173_2017-01-23-social-justice-freedom-of": 578465,
|
||||||
|
"655173_discussion-sam-harris-the-idw-and-the": 423332,
|
||||||
|
"655173_march-2018-patreon-q-a": 413749,
|
||||||
|
"655173_take-aim-even-badly": 490395,
|
||||||
|
"655173_jp-f-wwbgo6a2w": 539940,
|
||||||
|
"655173_patreon-account-deletion": 503477,
|
||||||
|
"655173_canada-us-europe-tour-august-dec-2018": 413749,
|
||||||
|
"655173_leaders-myth-reality-general-stanley": 514333,
|
||||||
|
"655173_jp-ifi5kkxig3s": 539940,
|
||||||
|
"655173_documentary-a-glitch-in-the-matrix-david": 413749,
|
||||||
|
"655173_2017-08-14-patreon-q-and-a": 285451,
|
||||||
|
"655173_postmodernism-history-and-diagnosis": 285451,
|
||||||
|
"655173_23-minutes-from-maps-of-meaning-the": 413749,
|
||||||
|
"655173_milo-forbidden-conversation": 578493,
|
||||||
|
"655173_jp-wnjbasba-qw": 539940,
|
||||||
|
"655173_uk-12-rules-tour-october-and-november": 462849,
|
||||||
|
"655173_2015-maps-of-meaning-10-culture-anomaly": 578465,
|
||||||
|
"655173_ayaan-hirsi-ali-islam-mecca-vs-medina": 285452,
|
||||||
|
"655173_jp-f9393el2z1i": 539940,
|
||||||
|
"655173_campus-indoctrination-the-parasitization": 285453,
|
||||||
|
"655173_jp-owgc63khcl8": 539940,
|
||||||
|
"655173_the-death-and-resurrection-of-christ-a": 413749,
|
||||||
|
"655173_01-harris-weinstein-peterson-discussion": 430896,
|
||||||
|
"655173_enlightenment-now-steven-pinker-jb": 413749,
|
||||||
|
"655173_the-lindsay-shepherd-affair-update": 413749,
|
||||||
|
"655173_jp-g3fwumq5k8i": 539940,
|
||||||
|
"655173_jp-evvs3l-abv4": 539940,
|
||||||
|
"655173_former-australian-deputy-pm-john": 413750,
|
||||||
|
"655173_message-to-my-korean-readers-90-seconds": 477424,
|
||||||
|
"655173_jp--0xbomwjkgm": 539940,
|
||||||
|
"655173_ben-shapiro-jordan-peterson-and-a-12": 413749,
|
||||||
|
"655173_jp-91jwsb7zyhw": 539940,
|
||||||
|
"655173_deconstruction-the-lindsay-shepherd": 299272,
|
||||||
|
"655173_september-patreon-q-a": 285451,
|
||||||
|
"655173_jp-2c3m0tt5kce": 539940,
|
||||||
|
"655173_australia-s-john-anderson-dr-jordan-b": 413749,
|
||||||
|
"655173_jp-hdrlq7dpiws": 539940,
|
||||||
|
"655173_stephen-hicks-postmodernism-reprise": 578480,
|
||||||
|
"655173_october-patreon-q-a": 285451,
|
||||||
|
"655173_an-animated-intro-to-truth-order-and": 413749,
|
||||||
|
"655173_jp-bsh37-x5rny": 539940,
|
||||||
|
"655173_january-2019-q-a": 503477,
|
||||||
|
"655173_comedians-canaries-and-coalmines": 498586,
|
||||||
|
"655173_the-democrats-apology-and-promise": 465433,
|
||||||
|
"655173_jp-s4c-jodptn8": 539940,
|
||||||
|
"655173_2014-personality-lecture-16-extraversion": 578465,
|
||||||
|
"655173_dr-jordan-b-peterson-on-femsplainers": 490395,
|
||||||
|
"655173_higher-ed-our-cultural-inflection-point": 527291,
|
||||||
|
"655173_archetype-reality-friendship-and": 519736,
|
||||||
|
"655173_sir-roger-scruton-dr-jordan-b-peterson": 490395,
|
||||||
|
"655173_jp-cf2nqmqifxc": 539940,
|
||||||
|
"655173_penguin-uk-12-rules-for-life": 413749,
|
||||||
|
"655173_march-2019-q-and-a": 537138,
|
||||||
|
"655173_jp-ne5vbomsqjc": 539940,
|
||||||
|
"655173_dublin-london-harris-murray-new-usa-12": 413749,
|
||||||
|
"655173_12-rules-12-cities-tickets-now-available": 413749,
|
||||||
|
"655173_jp-j9j-bvdrgdi": 539940,
|
||||||
|
"655173_responsibility-conscience-and-meaning": 499123,
|
||||||
|
"655173_04-harris-murray-peterson-discussion": 436678,
|
||||||
|
"655173_jp-ayhaz9k008q": 539940,
|
||||||
|
"655173_with-jocko-willink-the-catastrophe-of": 490395,
|
||||||
|
"655173_interview-with-the-grievance-studies": 501296,
|
||||||
|
"655173_russell-brand-jordan-b-peterson-under": 413750,
|
||||||
|
"655173_goodbye-to-patreon": 496771,
|
||||||
|
"655173_revamped-podcast-announcement-with": 540943,
|
||||||
|
"655173_swedes-want-to-know": 285453,
|
||||||
|
"655173_auckland-clip-2-the-four-fundamental": 607892,
|
||||||
|
"655173_jp-dtirzqmgbdm": 539940,
|
||||||
|
"655173_political-correctness-a-force-for-good-a": 413750,
|
||||||
|
"655173_sean-plunket-full-interview-new-zealand": 597638,
|
||||||
|
"655173_q-a-the-meaning-and-reality-of": 616984,
|
||||||
|
"655173_lecture-and-q-a-with-jordan-peterson-the": 413749,
|
||||||
|
"655173_2017-personality-07-carl-jung-and-the": 578465,
|
||||||
|
"655173_nina-paley-animator-extraordinaire": 413750,
|
||||||
|
"655173_truth-as-the-antidote-to-suffering-with": 455127,
|
||||||
|
"655173_bishop-barron-word-on-fire": 599814,
|
||||||
|
"655173_zizek-vs-peterson-april-19": 527291,
|
||||||
|
"655173_revamped-podcast-with-westwood-one": 540943,
|
||||||
|
"655173_2016-11-19-university-of-toronto-free": 578465,
|
||||||
|
"655173_jp-1emrmtrj5jc": 539940,
|
||||||
|
"655173_who-is-joe-rogan-with-jordan-peterson": 585578,
|
||||||
|
"655173_who-dares-say-he-believes-in-god": 581738,
|
||||||
|
"655252_games-with-live2d": 589978,
|
||||||
|
"655252_kaenbyou-rin-live2d": 589978,
|
||||||
|
"655374_steam-groups-are-crazy": 607590,
|
||||||
|
"655379_asmr-captain-falcon-happily-beats-you-up": 644574,
|
||||||
|
"655379_pixel-art-series-5-link-holding-the": 442952,
|
||||||
|
"655379_who-can-cross-the-planck-length-the-hero": 610830,
|
||||||
|
"655379_ssbb-the-yoshi-grab-release-crash": 609747,
|
||||||
|
"655379_tas-captain-falcon-s-bizarre-adventure": 442958,
|
||||||
|
"655379_super-smash-bros-in-360-test": 442963,
|
||||||
|
"655379_what-if-luigi-was-b-u-f-f": 442971,
|
||||||
|
"655803_sun-time-lapse-test-7": 610634,
|
||||||
|
"655952_upper-build-complete": 591728,
|
||||||
|
"656758_cryptocurrency-awareness-adoption-the": 541770,
|
||||||
|
"656829_3d-printing-technologies-comparison": 462685,
|
||||||
|
"656829_3d-printing-for-everyone": 462685,
|
||||||
|
"657052_tni-punya-ilmu-kanuragan-gaya-baru": 657045,
|
||||||
|
"657052_papa-sunimah-nelpon-sri-utami-emon": 657045,
|
||||||
|
"657274_rapforlife-4-win": 656856,
|
||||||
|
"657274_bizzilion-proof-of-withdrawal": 656856,
|
||||||
|
"657420_quick-drawing-prince-tribute-colored": 605630,
|
||||||
|
"657453_white-boy-tom-mcdonald-facts": 597169,
|
||||||
|
"657453_is-it-ok-to-look-when-you-with-your-girl": 610508,
|
||||||
|
"657584_need-for-speed-ryzen-5-1600-gtx-1050-ti": 657161,
|
||||||
|
"657584_quantum-break-ryzen-5-1600-gtx-1050-ti-4": 657161,
|
||||||
|
"657584_nightcore-legends-never-die": 657161,
|
||||||
|
"657706_mtb-enduro-ferragosto-2019-sestri": 638904,
|
||||||
|
"657706_warface-free-for-all": 638908,
|
||||||
|
"657782_nick-warren-at-loveland-but-not-really": 444299,
|
||||||
|
"658098_le-temps-nous-glisse-entre-les-doigts": 600099,
|
||||||
|
}
|
||||||
|
}
|
9
claimtrie/temporal/repo.go
Normal file
9
claimtrie/temporal/repo.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package temporal
|
||||||
|
|
||||||
|
// Repo defines APIs for Temporal to access persistence layer.
|
||||||
|
type Repo interface {
|
||||||
|
SetNodesAt(names [][]byte, heights []int32) error
|
||||||
|
NodesAt(height int32) ([][]byte, error)
|
||||||
|
Close() error
|
||||||
|
Flush() error
|
||||||
|
}
|
45
claimtrie/temporal/temporalrepo/memory.go
Normal file
45
claimtrie/temporal/temporalrepo/memory.go
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
package temporalrepo
|
||||||
|
|
||||||
|
type Memory struct {
|
||||||
|
cache map[int32]map[string]bool
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewMemory() *Memory {
|
||||||
|
return &Memory{
|
||||||
|
cache: map[int32]map[string]bool{},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Memory) SetNodesAt(names [][]byte, heights []int32) error {
|
||||||
|
|
||||||
|
for i, height := range heights {
|
||||||
|
c, ok := repo.cache[height]
|
||||||
|
if !ok {
|
||||||
|
c = map[string]bool{}
|
||||||
|
repo.cache[height] = c
|
||||||
|
}
|
||||||
|
name := string(names[i])
|
||||||
|
c[name] = true
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Memory) NodesAt(height int32) ([][]byte, error) {
|
||||||
|
|
||||||
|
var names [][]byte
|
||||||
|
|
||||||
|
for name := range repo.cache[height] {
|
||||||
|
names = append(names, []byte(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Memory) Close() error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Memory) Flush() error {
|
||||||
|
return nil
|
||||||
|
}
|
87
claimtrie/temporal/temporalrepo/pebble.go
Normal file
87
claimtrie/temporal/temporalrepo/pebble.go
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
package temporalrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/cockroachdb/pebble"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Pebble struct {
|
||||||
|
db *pebble.DB
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewPebble(path string) (*Pebble, error) {
|
||||||
|
|
||||||
|
db, err := pebble.Open(path, &pebble.Options{Cache: pebble.NewCache(16 << 20)})
|
||||||
|
repo := &Pebble{db: db}
|
||||||
|
|
||||||
|
return repo, errors.Wrapf(err, "unable to open %s", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) SetNodesAt(name [][]byte, heights []int32) error {
|
||||||
|
|
||||||
|
// key format: height(4B) + 0(1B) + name(varable length)
|
||||||
|
key := bytes.NewBuffer(nil)
|
||||||
|
batch := repo.db.NewBatch()
|
||||||
|
defer batch.Close()
|
||||||
|
for i, name := range name {
|
||||||
|
key.Reset()
|
||||||
|
binary.Write(key, binary.BigEndian, heights[i])
|
||||||
|
binary.Write(key, binary.BigEndian, byte(0))
|
||||||
|
key.Write(name)
|
||||||
|
|
||||||
|
err := batch.Set(key.Bytes(), nil, pebble.NoSync)
|
||||||
|
if err != nil {
|
||||||
|
return errors.Wrap(err, "in set")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return errors.Wrap(batch.Commit(pebble.NoSync), "in commit")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) NodesAt(height int32) ([][]byte, error) {
|
||||||
|
|
||||||
|
prefix := bytes.NewBuffer(nil)
|
||||||
|
binary.Write(prefix, binary.BigEndian, height)
|
||||||
|
binary.Write(prefix, binary.BigEndian, byte(0))
|
||||||
|
|
||||||
|
end := bytes.NewBuffer(nil)
|
||||||
|
binary.Write(end, binary.BigEndian, height)
|
||||||
|
binary.Write(end, binary.BigEndian, byte(1))
|
||||||
|
|
||||||
|
prefixIterOptions := &pebble.IterOptions{
|
||||||
|
LowerBound: prefix.Bytes(),
|
||||||
|
UpperBound: end.Bytes(),
|
||||||
|
}
|
||||||
|
|
||||||
|
var names [][]byte
|
||||||
|
|
||||||
|
iter := repo.db.NewIter(prefixIterOptions)
|
||||||
|
for iter.First(); iter.Valid(); iter.Next() {
|
||||||
|
// Skipping the first 5 bytes (height and a null byte), we get the name.
|
||||||
|
name := make([]byte, len(iter.Key())-5)
|
||||||
|
copy(name, iter.Key()[5:]) // iter.Key() reuses its buffer
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return names, errors.Wrap(iter.Close(), "in close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Close() error {
|
||||||
|
|
||||||
|
err := repo.db.Flush()
|
||||||
|
if err != nil {
|
||||||
|
// if we fail to close are we going to try again later?
|
||||||
|
return errors.Wrap(err, "on flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = repo.db.Close()
|
||||||
|
return errors.Wrap(err, "on close")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (repo *Pebble) Flush() error {
|
||||||
|
_, err := repo.db.AsyncFlush()
|
||||||
|
return err
|
||||||
|
}
|
80
claimtrie/temporal/temporalrepo/temporalrepo_test.go
Normal file
80
claimtrie/temporal/temporalrepo/temporalrepo_test.go
Normal file
|
@ -0,0 +1,80 @@
|
||||||
|
package temporalrepo
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/btcsuite/btcd/claimtrie/temporal"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMemory(t *testing.T) {
|
||||||
|
|
||||||
|
repo := NewMemory()
|
||||||
|
testTemporalRepo(t, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPebble(t *testing.T) {
|
||||||
|
|
||||||
|
repo, err := NewPebble(t.TempDir())
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testTemporalRepo(t, repo)
|
||||||
|
}
|
||||||
|
|
||||||
|
func testTemporalRepo(t *testing.T, repo temporal.Repo) {
|
||||||
|
|
||||||
|
r := require.New(t)
|
||||||
|
|
||||||
|
nameA := []byte("a")
|
||||||
|
nameB := []byte("b")
|
||||||
|
nameC := []byte("c")
|
||||||
|
|
||||||
|
testcases := []struct {
|
||||||
|
name []byte
|
||||||
|
heights []int32
|
||||||
|
}{
|
||||||
|
{nameA, []int32{1, 3, 2}},
|
||||||
|
{nameA, []int32{2, 3}},
|
||||||
|
{nameB, []int32{5, 4}},
|
||||||
|
{nameB, []int32{5, 1}},
|
||||||
|
{nameC, []int32{4, 3, 8}},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, i := range testcases {
|
||||||
|
names := make([][]byte, 0, len(i.heights))
|
||||||
|
for range i.heights {
|
||||||
|
names = append(names, i.name)
|
||||||
|
}
|
||||||
|
err := repo.SetNodesAt(names, i.heights)
|
||||||
|
r.NoError(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// a: 1, 2, 3
|
||||||
|
// b: 1, 5, 4
|
||||||
|
// c: 4, 3, 8
|
||||||
|
|
||||||
|
names, err := repo.NodesAt(2)
|
||||||
|
r.NoError(err)
|
||||||
|
r.ElementsMatch([][]byte{nameA}, names)
|
||||||
|
|
||||||
|
names, err = repo.NodesAt(5)
|
||||||
|
r.NoError(err)
|
||||||
|
r.ElementsMatch([][]byte{nameB}, names)
|
||||||
|
|
||||||
|
names, err = repo.NodesAt(8)
|
||||||
|
r.NoError(err)
|
||||||
|
r.ElementsMatch([][]byte{nameC}, names)
|
||||||
|
|
||||||
|
names, err = repo.NodesAt(1)
|
||||||
|
r.NoError(err)
|
||||||
|
r.ElementsMatch([][]byte{nameA, nameB}, names)
|
||||||
|
|
||||||
|
names, err = repo.NodesAt(4)
|
||||||
|
r.NoError(err)
|
||||||
|
r.ElementsMatch([][]byte{nameB, nameC}, names)
|
||||||
|
|
||||||
|
names, err = repo.NodesAt(3)
|
||||||
|
r.NoError(err)
|
||||||
|
r.ElementsMatch([][]byte{nameA, nameC}, names)
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
btcdHomeDir = btcutil.AppDataDir("chain", false)
|
||||||
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
||||||
knownDbTypes = database.SupportedDrivers()
|
knownDbTypes = database.SupportedDrivers()
|
||||||
activeNetParams = &chaincfg.MainNetParams
|
activeNetParams = &chaincfg.MainNetParams
|
||||||
|
|
|
@ -27,10 +27,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
btcdHomeDir = btcutil.AppDataDir("chain", false)
|
||||||
btcctlHomeDir = btcutil.AppDataDir("btcctl", false)
|
btcctlHomeDir = btcutil.AppDataDir("chainctl", false)
|
||||||
btcwalletHomeDir = btcutil.AppDataDir("btcwallet", false)
|
btcwalletHomeDir = btcutil.AppDataDir("chainwallet", false)
|
||||||
defaultConfigFile = filepath.Join(btcctlHomeDir, "btcctl.conf")
|
defaultConfigFile = filepath.Join(btcctlHomeDir, "chainctl.conf")
|
||||||
defaultRPCServer = "localhost"
|
defaultRPCServer = "localhost"
|
||||||
defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
defaultRPCCertFile = filepath.Join(btcdHomeDir, "rpc.cert")
|
||||||
defaultWalletCertFile = filepath.Join(btcwalletHomeDir, "rpc.cert")
|
defaultWalletCertFile = filepath.Join(btcwalletHomeDir, "rpc.cert")
|
||||||
|
@ -123,7 +123,7 @@ func normalizeAddress(addr string, chain *chaincfg.Params, useWallet bool) (stri
|
||||||
if useWallet {
|
if useWallet {
|
||||||
defaultPort = "18332"
|
defaultPort = "18332"
|
||||||
} else {
|
} else {
|
||||||
defaultPort = "18334"
|
defaultPort = "19245"
|
||||||
}
|
}
|
||||||
case &chaincfg.SimNetParams:
|
case &chaincfg.SimNetParams:
|
||||||
if useWallet {
|
if useWallet {
|
||||||
|
@ -149,7 +149,7 @@ func normalizeAddress(addr string, chain *chaincfg.Params, useWallet bool) (stri
|
||||||
if useWallet {
|
if useWallet {
|
||||||
defaultPort = "8332"
|
defaultPort = "8332"
|
||||||
} else {
|
} else {
|
||||||
defaultPort = "8334"
|
defaultPort = "9245"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -231,9 +231,9 @@ func loadConfig() (*config, []string, error) {
|
||||||
// Use config file for RPC server to create default btcctl config
|
// Use config file for RPC server to create default btcctl config
|
||||||
var serverConfigPath string
|
var serverConfigPath string
|
||||||
if preCfg.Wallet {
|
if preCfg.Wallet {
|
||||||
serverConfigPath = filepath.Join(btcwalletHomeDir, "btcwallet.conf")
|
serverConfigPath = filepath.Join(btcwalletHomeDir, "chainwallet.conf")
|
||||||
} else {
|
} else {
|
||||||
serverConfigPath = filepath.Join(btcdHomeDir, "btcd.conf")
|
serverConfigPath = filepath.Join(btcdHomeDir, "chain.conf")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := createDefaultConfigFile(preCfg.ConfigFile, serverConfigPath)
|
err := createDefaultConfigFile(preCfg.ConfigFile, serverConfigPath)
|
||||||
|
|
|
@ -25,7 +25,7 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
btcdHomeDir = btcutil.AppDataDir("chain", false)
|
||||||
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
defaultDataDir = filepath.Join(btcdHomeDir, "data")
|
||||||
knownDbTypes = database.SupportedDrivers()
|
knownDbTypes = database.SupportedDrivers()
|
||||||
activeNetParams = &chaincfg.MainNetParams
|
activeNetParams = &chaincfg.MainNetParams
|
||||||
|
|
11
config.go
11
config.go
|
@ -35,11 +35,11 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
defaultConfigFilename = "btcd.conf"
|
defaultConfigFilename = "chain.conf"
|
||||||
defaultDataDirname = "data"
|
defaultDataDirname = "data"
|
||||||
defaultLogLevel = "info"
|
defaultLogLevel = "info"
|
||||||
defaultLogDirname = "logs"
|
defaultLogDirname = "logs"
|
||||||
defaultLogFilename = "btcd.log"
|
defaultLogFilename = "chain.log"
|
||||||
defaultMaxPeers = 125
|
defaultMaxPeers = 125
|
||||||
defaultBanDuration = time.Hour * 24
|
defaultBanDuration = time.Hour * 24
|
||||||
defaultBanThreshold = 100
|
defaultBanThreshold = 100
|
||||||
|
@ -62,13 +62,13 @@ const (
|
||||||
defaultMaxOrphanTransactions = 100
|
defaultMaxOrphanTransactions = 100
|
||||||
defaultMaxOrphanTxSize = 100000
|
defaultMaxOrphanTxSize = 100000
|
||||||
defaultSigCacheMaxSize = 100000
|
defaultSigCacheMaxSize = 100000
|
||||||
sampleConfigFilename = "sample-btcd.conf"
|
sampleConfigFilename = "sample-chain.conf"
|
||||||
defaultTxIndex = false
|
defaultTxIndex = false
|
||||||
defaultAddrIndex = false
|
defaultAddrIndex = false
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
defaultHomeDir = btcutil.AppDataDir("btcd", false)
|
defaultHomeDir = btcutil.AppDataDir("chain", false)
|
||||||
defaultConfigFile = filepath.Join(defaultHomeDir, defaultConfigFilename)
|
defaultConfigFile = filepath.Join(defaultHomeDir, defaultConfigFilename)
|
||||||
defaultDataDir = filepath.Join(defaultHomeDir, defaultDataDirname)
|
defaultDataDir = filepath.Join(defaultHomeDir, defaultDataDirname)
|
||||||
knownDbTypes = database.SupportedDrivers()
|
knownDbTypes = database.SupportedDrivers()
|
||||||
|
@ -108,6 +108,9 @@ type config struct {
|
||||||
BlockPrioritySize uint32 `long:"blockprioritysize" description:"Size in bytes for high-priority/low-fee transactions when creating a block"`
|
BlockPrioritySize uint32 `long:"blockprioritysize" description:"Size in bytes for high-priority/low-fee transactions when creating a block"`
|
||||||
BlocksOnly bool `long:"blocksonly" description:"Do not accept transactions from remote peers."`
|
BlocksOnly bool `long:"blocksonly" description:"Do not accept transactions from remote peers."`
|
||||||
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
ConfigFile string `short:"C" long:"configfile" description:"Path to configuration file"`
|
||||||
|
ClaimTrieImpl string `long:"clmtimpl" description:"Implementation of ClaimTrie"`
|
||||||
|
ClaimTrieRecord bool `long:"clmtrecord" description:"Record claim operations made to ClaimTrie"`
|
||||||
|
ClaimTrieHeight uint32 `long:"clmtheight" description:"Reset height of ClaimTrie"`
|
||||||
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
|
ConnectPeers []string `long:"connect" description:"Connect only to the specified peers at startup"`
|
||||||
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
|
CPUProfile string `long:"cpuprofile" description:"Write CPU profile to the specified file"`
|
||||||
DataDir string `short:"b" long:"datadir" description:"Directory to store data"`
|
DataDir string `short:"b" long:"datadir" description:"Directory to store data"`
|
||||||
|
|
|
@ -20,16 +20,16 @@ func TestCreateDefaultConfigFile(t *testing.T) {
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Fatalf("Failed finding config file path")
|
t.Fatalf("Failed finding config file path")
|
||||||
}
|
}
|
||||||
sampleConfigFile := filepath.Join(filepath.Dir(path), "sample-btcd.conf")
|
sampleConfigFile := filepath.Join(filepath.Dir(path), "sample-chain.conf")
|
||||||
|
|
||||||
// Setup a temporary directory
|
// Setup a temporary directory
|
||||||
tmpDir, err := ioutil.TempDir("", "btcd")
|
tmpDir, err := ioutil.TempDir("", "chain")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed creating a temporary directory: %v", err)
|
t.Fatalf("Failed creating a temporary directory: %v", err)
|
||||||
}
|
}
|
||||||
testpath := filepath.Join(tmpDir, "test.conf")
|
testpath := filepath.Join(tmpDir, "test.conf")
|
||||||
|
|
||||||
// copy config file to location of btcd binary
|
// copy config file to location of chain binary
|
||||||
data, err := ioutil.ReadFile(sampleConfigFile)
|
data, err := ioutil.ReadFile(sampleConfigFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed reading sample config file: %v", err)
|
t.Fatalf("Failed reading sample config file: %v", err)
|
||||||
|
@ -38,7 +38,7 @@ func TestCreateDefaultConfigFile(t *testing.T) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed obtaining app path: %v", err)
|
t.Fatalf("Failed obtaining app path: %v", err)
|
||||||
}
|
}
|
||||||
tmpConfigFile := filepath.Join(appPath, "sample-btcd.conf")
|
tmpConfigFile := filepath.Join(appPath, "sample-chain.conf")
|
||||||
err = ioutil.WriteFile(tmpConfigFile, data, 0644)
|
err = ioutil.WriteFile(tmpConfigFile, data, 0644)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed copying sample config file: %v", err)
|
t.Fatalf("Failed copying sample config file: %v", err)
|
||||||
|
|
|
@ -18,7 +18,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
btcdHomeDir = btcutil.AppDataDir("btcd", false)
|
btcdHomeDir = btcutil.AppDataDir("chain", false)
|
||||||
knownDbTypes = database.SupportedDrivers()
|
knownDbTypes = database.SupportedDrivers()
|
||||||
activeNetParams = &chaincfg.MainNetParams
|
activeNetParams = &chaincfg.MainNetParams
|
||||||
|
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package database_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/database"
|
|
||||||
)
|
|
||||||
|
|
||||||
// TestErrorCodeStringer tests the stringized output for the ErrorCode type.
|
|
||||||
func TestErrorCodeStringer(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
in database.ErrorCode
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{database.ErrDbTypeRegistered, "ErrDbTypeRegistered"},
|
|
||||||
{database.ErrDbUnknownType, "ErrDbUnknownType"},
|
|
||||||
{database.ErrDbDoesNotExist, "ErrDbDoesNotExist"},
|
|
||||||
{database.ErrDbExists, "ErrDbExists"},
|
|
||||||
{database.ErrDbNotOpen, "ErrDbNotOpen"},
|
|
||||||
{database.ErrDbAlreadyOpen, "ErrDbAlreadyOpen"},
|
|
||||||
{database.ErrInvalid, "ErrInvalid"},
|
|
||||||
{database.ErrCorruption, "ErrCorruption"},
|
|
||||||
{database.ErrTxClosed, "ErrTxClosed"},
|
|
||||||
{database.ErrTxNotWritable, "ErrTxNotWritable"},
|
|
||||||
{database.ErrBucketNotFound, "ErrBucketNotFound"},
|
|
||||||
{database.ErrBucketExists, "ErrBucketExists"},
|
|
||||||
{database.ErrBucketNameRequired, "ErrBucketNameRequired"},
|
|
||||||
{database.ErrKeyRequired, "ErrKeyRequired"},
|
|
||||||
{database.ErrKeyTooLarge, "ErrKeyTooLarge"},
|
|
||||||
{database.ErrValueTooLarge, "ErrValueTooLarge"},
|
|
||||||
{database.ErrIncompatibleValue, "ErrIncompatibleValue"},
|
|
||||||
{database.ErrBlockNotFound, "ErrBlockNotFound"},
|
|
||||||
{database.ErrBlockExists, "ErrBlockExists"},
|
|
||||||
{database.ErrBlockRegionInvalid, "ErrBlockRegionInvalid"},
|
|
||||||
{database.ErrDriverSpecific, "ErrDriverSpecific"},
|
|
||||||
|
|
||||||
{0xffff, "Unknown ErrorCode (65535)"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect additional error codes that don't have the stringer added.
|
|
||||||
if len(tests)-1 != int(database.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\ngot: %s\nwant: %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 database.Error
|
|
||||||
want string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
database.Error{Description: "some error"},
|
|
||||||
"some error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
database.Error{Description: "human-readable error"},
|
|
||||||
"human-readable error",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
database.Error{
|
|
||||||
ErrorCode: database.ErrDriverSpecific,
|
|
||||||
Description: "some error",
|
|
||||||
Err: errors.New("driver-specific error"),
|
|
||||||
},
|
|
||||||
"some error: driver-specific 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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,177 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package database_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/database"
|
|
||||||
_ "github.com/btcsuite/btcd/database/ffldb"
|
|
||||||
"github.com/btcsuite/btcd/wire"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// This example demonstrates creating a new database.
|
|
||||||
func ExampleCreate() {
|
|
||||||
// This example assumes the ffldb driver is imported.
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "github.com/btcsuite/btcd/database"
|
|
||||||
// _ "github.com/btcsuite/btcd/database/ffldb"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Create a database and schedule it to be closed and removed on exit.
|
|
||||||
// Typically you wouldn't want to remove the database right away like
|
|
||||||
// this, nor put it in the temp directory, but it's done here to ensure
|
|
||||||
// the example cleans up after itself.
|
|
||||||
dbPath := filepath.Join(os.TempDir(), "examplecreate")
|
|
||||||
db, err := database.Create("ffldb", dbPath, wire.MainNet)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dbPath)
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates creating a new database and using a managed
|
|
||||||
// read-write transaction to store and retrieve metadata.
|
|
||||||
func Example_basicUsage() {
|
|
||||||
// This example assumes the ffldb driver is imported.
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "github.com/btcsuite/btcd/database"
|
|
||||||
// _ "github.com/btcsuite/btcd/database/ffldb"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Create a database and schedule it to be closed and removed on exit.
|
|
||||||
// Typically you wouldn't want to remove the database right away like
|
|
||||||
// this, nor put it in the temp directory, but it's done here to ensure
|
|
||||||
// the example cleans up after itself.
|
|
||||||
dbPath := filepath.Join(os.TempDir(), "exampleusage")
|
|
||||||
db, err := database.Create("ffldb", dbPath, wire.MainNet)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dbPath)
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Use the Update function of the database to perform a managed
|
|
||||||
// read-write transaction. The transaction will automatically be rolled
|
|
||||||
// back if the supplied inner function returns a non-nil error.
|
|
||||||
err = db.Update(func(tx database.Tx) error {
|
|
||||||
// Store a key/value pair directly in the metadata bucket.
|
|
||||||
// Typically a nested bucket would be used for a given feature,
|
|
||||||
// but this example is using the metadata bucket directly for
|
|
||||||
// simplicity.
|
|
||||||
key := []byte("mykey")
|
|
||||||
value := []byte("myvalue")
|
|
||||||
if err := tx.Metadata().Put(key, value); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Read the key back and ensure it matches.
|
|
||||||
if !bytes.Equal(tx.Metadata().Get(key), value) {
|
|
||||||
return fmt.Errorf("unexpected value for key '%s'", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new nested bucket under the metadata bucket.
|
|
||||||
nestedBucketKey := []byte("mybucket")
|
|
||||||
nestedBucket, err := tx.Metadata().CreateBucket(nestedBucketKey)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The key from above that was set in the metadata bucket does
|
|
||||||
// not exist in this new nested bucket.
|
|
||||||
if nestedBucket.Get(key) != nil {
|
|
||||||
return fmt.Errorf("key '%s' is not expected nil", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
}
|
|
||||||
|
|
||||||
// This example demonstrates creating a new database, using a managed read-write
|
|
||||||
// transaction to store a block, and using a managed read-only transaction to
|
|
||||||
// fetch the block.
|
|
||||||
func Example_blockStorageAndRetrieval() {
|
|
||||||
// This example assumes the ffldb driver is imported.
|
|
||||||
//
|
|
||||||
// import (
|
|
||||||
// "github.com/btcsuite/btcd/database"
|
|
||||||
// _ "github.com/btcsuite/btcd/database/ffldb"
|
|
||||||
// )
|
|
||||||
|
|
||||||
// Create a database and schedule it to be closed and removed on exit.
|
|
||||||
// Typically you wouldn't want to remove the database right away like
|
|
||||||
// this, nor put it in the temp directory, but it's done here to ensure
|
|
||||||
// the example cleans up after itself.
|
|
||||||
dbPath := filepath.Join(os.TempDir(), "exampleblkstorage")
|
|
||||||
db, err := database.Create("ffldb", dbPath, wire.MainNet)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dbPath)
|
|
||||||
defer db.Close()
|
|
||||||
|
|
||||||
// Use the Update function of the database to perform a managed
|
|
||||||
// read-write transaction and store a genesis block in the database as
|
|
||||||
// and example.
|
|
||||||
err = db.Update(func(tx database.Tx) error {
|
|
||||||
genesisBlock := chaincfg.MainNetParams.GenesisBlock
|
|
||||||
return tx.StoreBlock(btcutil.NewBlock(genesisBlock))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use the View function of the database to perform a managed read-only
|
|
||||||
// transaction and fetch the block stored above.
|
|
||||||
var loadedBlockBytes []byte
|
|
||||||
err = db.Update(func(tx database.Tx) error {
|
|
||||||
genesisHash := chaincfg.MainNetParams.GenesisHash
|
|
||||||
blockBytes, err := tx.FetchBlock(genesisHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// As documented, all data fetched from the database is only
|
|
||||||
// valid during a database transaction in order to support
|
|
||||||
// zero-copy backends. Thus, make a copy of the data so it
|
|
||||||
// can be used outside of the transaction.
|
|
||||||
loadedBlockBytes = make([]byte, len(blockBytes))
|
|
||||||
copy(loadedBlockBytes, blockBytes)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Typically at this point, the block could be deserialized via the
|
|
||||||
// wire.MsgBlock.Deserialize function or used in its serialized form
|
|
||||||
// depending on need. However, for this example, just display the
|
|
||||||
// number of serialized bytes to show it was loaded as expected.
|
|
||||||
fmt.Printf("Serialized block size: %d bytes\n", len(loadedBlockBytes))
|
|
||||||
|
|
||||||
// Output:
|
|
||||||
// Serialized block size: 285 bytes
|
|
||||||
}
|
|
|
@ -1,97 +0,0 @@
|
||||||
// Copyright (c) 2015-2016 The btcsuite developers
|
|
||||||
// Use of this source code is governed by an ISC
|
|
||||||
// license that can be found in the LICENSE file.
|
|
||||||
|
|
||||||
package ffldb
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg"
|
|
||||||
"github.com/btcsuite/btcd/database"
|
|
||||||
"github.com/btcsuite/btcutil"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BenchmarkBlockHeader benchmarks how long it takes to load the mainnet genesis
|
|
||||||
// block header.
|
|
||||||
func BenchmarkBlockHeader(b *testing.B) {
|
|
||||||
// Start by creating a new database and populating it with the mainnet
|
|
||||||
// genesis block.
|
|
||||||
dbPath := filepath.Join(os.TempDir(), "ffldb-benchblkhdr")
|
|
||||||
_ = os.RemoveAll(dbPath)
|
|
||||||
db, err := database.Create("ffldb", dbPath, blockDataNet)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dbPath)
|
|
||||||
defer db.Close()
|
|
||||||
err = db.Update(func(tx database.Tx) error {
|
|
||||||
block := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
|
|
||||||
return tx.StoreBlock(block)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
err = db.View(func(tx database.Tx) error {
|
|
||||||
blockHash := chaincfg.MainNetParams.GenesisHash
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := tx.FetchBlockHeader(blockHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't benchmark teardown.
|
|
||||||
b.StopTimer()
|
|
||||||
}
|
|
||||||
|
|
||||||
// BenchmarkBlockHeader benchmarks how long it takes to load the mainnet genesis
|
|
||||||
// block.
|
|
||||||
func BenchmarkBlock(b *testing.B) {
|
|
||||||
// Start by creating a new database and populating it with the mainnet
|
|
||||||
// genesis block.
|
|
||||||
dbPath := filepath.Join(os.TempDir(), "ffldb-benchblk")
|
|
||||||
_ = os.RemoveAll(dbPath)
|
|
||||||
db, err := database.Create("ffldb", dbPath, blockDataNet)
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
defer os.RemoveAll(dbPath)
|
|
||||||
defer db.Close()
|
|
||||||
err = db.Update(func(tx database.Tx) error {
|
|
||||||
block := btcutil.NewBlock(chaincfg.MainNetParams.GenesisBlock)
|
|
||||||
return tx.StoreBlock(block)
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
b.ReportAllocs()
|
|
||||||
b.ResetTimer()
|
|
||||||
err = db.View(func(tx database.Tx) error {
|
|
||||||
blockHash := chaincfg.MainNetParams.GenesisHash
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_, err := tx.FetchBlock(blockHash)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
b.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't benchmark teardown.
|
|
||||||
b.StopTimer()
|
|
||||||
}
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
"github.com/btcsuite/btcd/chaincfg/chainhash"
|
||||||
"github.com/btcsuite/btcd/database"
|
"github.com/btcsuite/btcd/database"
|
||||||
"github.com/btcsuite/btcd/database/internal/treap"
|
"github.com/btcsuite/btcd/database/ffldb/treap"
|
||||||
"github.com/btcsuite/btcd/wire"
|
"github.com/btcsuite/btcd/wire"
|
||||||
"github.com/btcsuite/btcutil"
|
"github.com/btcsuite/btcutil"
|
||||||
"github.com/btcsuite/goleveldb/leveldb"
|
"github.com/btcsuite/goleveldb/leveldb"
|
||||||
|
|
|
@ -10,7 +10,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/btcsuite/btcd/database/internal/treap"
|
"github.com/btcsuite/btcd/database/ffldb/treap"
|
||||||
"github.com/btcsuite/goleveldb/leveldb"
|
"github.com/btcsuite/goleveldb/leveldb"
|
||||||
"github.com/btcsuite/goleveldb/leveldb/iterator"
|
"github.com/btcsuite/goleveldb/leveldb/iterator"
|
||||||
"github.com/btcsuite/goleveldb/leveldb/util"
|
"github.com/btcsuite/goleveldb/leveldb/util"
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue