WIP: sync tests
parent
c5cb1e2e54
commit
5000c01f11
|
@ -6,6 +6,10 @@ import (
|
|||
"blockbook/bchain"
|
||||
)
|
||||
|
||||
func SetBlockChain(w *SyncWorker, chain bchain.BlockChain) {
|
||||
w.chain = chain
|
||||
}
|
||||
|
||||
func ConnectBlocks(w *SyncWorker, onNewBlock bchain.OnNewBlockFunc, initialSync bool) error {
|
||||
return w.connectBlocks(onNewBlock, initialSync)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,269 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/db"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testConnectBlocks(t *testing.T, h *TestHandler) {
|
||||
for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
|
||||
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
|
||||
upperHash, err := h.Chain.GetBlockHash(rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.ConnectBlocks(sw, func(hash string, height uint32) {
|
||||
if hash == upperHash {
|
||||
close(ch)
|
||||
}
|
||||
}, true)
|
||||
if err != nil {
|
||||
if err.Error() != fmt.Sprintf("connectBlocks interrupted at height %d", rng.Upper) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
height, hash, err := d.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if height != rng.Upper {
|
||||
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
|
||||
}
|
||||
if hash != upperHash {
|
||||
t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash)
|
||||
}
|
||||
|
||||
t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
|
||||
t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
|
||||
t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testConnectBlocksParallel(t *testing.T, h *TestHandler) {
|
||||
for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
|
||||
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
|
||||
upperHash, err := h.Chain.GetBlockHash(rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
height, hash, err := d.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if height != rng.Upper {
|
||||
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
|
||||
}
|
||||
if hash != upperHash {
|
||||
t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash)
|
||||
}
|
||||
|
||||
t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
|
||||
t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
|
||||
t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
|
||||
for height := rng.Lower; height <= rng.Upper; height++ {
|
||||
block, found := h.TestData.ConnectBlocks.Blocks[height]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
bi, err := d.GetBlockInfo(height)
|
||||
if err != nil {
|
||||
t.Errorf("GetBlockInfo(%d) error: %s", height, err)
|
||||
continue
|
||||
}
|
||||
if bi == nil {
|
||||
t.Errorf("GetBlockInfo(%d) returned nil", height)
|
||||
continue
|
||||
}
|
||||
|
||||
if bi.Hash != block.Hash {
|
||||
t.Errorf("Block hash mismatch: %s != %s", bi.Hash, block.Hash)
|
||||
}
|
||||
|
||||
if bi.Txs != block.NoTxs {
|
||||
t.Errorf("Number of transactions in block %s mismatch: %d != %d", bi.Hash, bi.Txs, block.NoTxs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
|
||||
type txInfo struct {
|
||||
txid string
|
||||
vout uint32
|
||||
isOutput bool
|
||||
}
|
||||
addr2txs := make(map[string][]txInfo)
|
||||
checkMap := make(map[string][]bool)
|
||||
|
||||
for height := rng.Lower; height <= rng.Upper; height++ {
|
||||
block, found := h.TestData.ConnectBlocks.Blocks[height]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tx := range block.TxDetails {
|
||||
for _, vin := range tx.Vin {
|
||||
for _, a := range vin.Addresses {
|
||||
addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vin.Vout, false})
|
||||
checkMap[a] = append(checkMap[a], false)
|
||||
}
|
||||
}
|
||||
for _, vout := range tx.Vout {
|
||||
for _, a := range vout.ScriptPubKey.Addresses {
|
||||
addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vout.N, true})
|
||||
checkMap[a] = append(checkMap[a], false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for addr, txs := range addr2txs {
|
||||
err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, vout uint32, isOutput bool) error {
|
||||
for i, tx := range txs {
|
||||
if txid == tx.txid && vout == tx.vout && isOutput == tx.isOutput {
|
||||
checkMap[addr][i] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for addr, txs := range addr2txs {
|
||||
for i, tx := range txs {
|
||||
if !checkMap[addr][i] {
|
||||
t.Errorf("%s: transaction not found %+v", addr, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
|
||||
parser := h.Chain.GetChainParser()
|
||||
|
||||
for height := rng.Lower; height <= rng.Upper; height++ {
|
||||
block, found := h.TestData.ConnectBlocks.Blocks[height]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tx := range block.TxDetails {
|
||||
ta, err := d.GetTxAddresses(tx.Txid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
txInfo := getTxInfo(tx)
|
||||
taInfo, err := getTaInfo(parser, ta)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ta.Height != height {
|
||||
t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, height)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(txInfo.inputs) > 0 && !reflect.DeepEqual(taInfo.inputs, txInfo.inputs) {
|
||||
t.Errorf("Tx %s: inputs mismatch: got %q, want %q", tx.Txid, taInfo.inputs, txInfo.inputs)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(taInfo.outputs, txInfo.outputs) {
|
||||
t.Errorf("Tx %s: outputs mismatch: got %q, want %q", tx.Txid, taInfo.outputs, txInfo.outputs)
|
||||
}
|
||||
|
||||
if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
|
||||
t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
|
||||
tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
|
||||
}
|
||||
|
||||
if len(txInfo.inputs) > 0 {
|
||||
treshold := "0.0001"
|
||||
fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
|
||||
if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
|
||||
t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
|
||||
tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type txInfo struct {
|
||||
inputs []string
|
||||
outputs []string
|
||||
valInSat big.Int
|
||||
valOutSat big.Int
|
||||
}
|
||||
|
||||
func getTxInfo(tx *bchain.Tx) *txInfo {
|
||||
info := &txInfo{inputs: []string{}, outputs: []string{}}
|
||||
|
||||
for _, vin := range tx.Vin {
|
||||
for _, a := range vin.Addresses {
|
||||
info.inputs = append(info.inputs, a)
|
||||
}
|
||||
}
|
||||
for _, vout := range tx.Vout {
|
||||
for _, a := range vout.ScriptPubKey.Addresses {
|
||||
info.outputs = append(info.outputs, a)
|
||||
}
|
||||
info.valOutSat.Add(&info.valOutSat, &vout.ValueSat)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, error) {
|
||||
info := &txInfo{inputs: []string{}, outputs: []string{}}
|
||||
|
||||
for i := range ta.Inputs {
|
||||
info.valInSat.Add(&info.valInSat, &ta.Inputs[i].ValueSat)
|
||||
addrs, _, err := ta.Inputs[i].Addresses(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if !strings.HasPrefix(a, "OP_") {
|
||||
info.inputs = append(info.inputs, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for i := range ta.Outputs {
|
||||
info.valOutSat.Add(&info.valOutSat, &ta.Outputs[i].ValueSat)
|
||||
addrs, _, err := ta.Outputs[i].Addresses(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, a := range addrs {
|
||||
if !strings.HasPrefix(a, "OP_") {
|
||||
info.outputs = append(info.outputs, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"errors"
|
||||
)
|
||||
|
||||
type fakeBlockChain struct {
|
||||
bchain.BlockChain
|
||||
returnFakes bool
|
||||
fakeBlocks map[uint32]BlockID
|
||||
fakeBestHeight uint32
|
||||
}
|
||||
|
||||
func (c *fakeBlockChain) GetBestBlockHash() (v string, err error) {
|
||||
if !c.returnFakes {
|
||||
return c.BlockChain.GetBestBlockHash()
|
||||
}
|
||||
if b, found := c.fakeBlocks[c.fakeBestHeight]; found {
|
||||
return b.Hash, nil
|
||||
} else {
|
||||
return "", errors.New("Not found")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *fakeBlockChain) GetBestBlockHeight() (v uint32, err error) {
|
||||
if !c.returnFakes {
|
||||
return c.BlockChain.GetBestBlockHeight()
|
||||
}
|
||||
return c.fakeBestHeight, nil
|
||||
}
|
||||
|
||||
func (c *fakeBlockChain) GetBlockHash(height uint32) (v string, err error) {
|
||||
if c.returnFakes {
|
||||
if b, found := c.fakeBlocks[height]; found {
|
||||
return b.Hash, nil
|
||||
}
|
||||
}
|
||||
return c.BlockChain.GetBlockHash(height)
|
||||
}
|
||||
|
||||
func (c *fakeBlockChain) GetBlock(hash string, height uint32) (*bchain.Block, error) {
|
||||
if c.returnFakes {
|
||||
if hash == "" && height > 0 {
|
||||
var err error
|
||||
hash, err = c.GetBlockHash(height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return c.BlockChain.GetBlock(hash, height)
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"blockbook/bchain"
|
||||
"blockbook/db"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func testHandleFork(t *testing.T, h *TestHandler) {
|
||||
for _, rng := range h.TestData.HandleFork.SyncRanges {
|
||||
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
|
||||
fakeBlocks := getFakeBlocks(h, rng)
|
||||
chain, err := makeFakeChain(h.Chain, fakeBlocks, rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
db.SetBlockChain(sw, chain)
|
||||
|
||||
sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
|
||||
|
||||
height, _, err := d.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if height != rng.Upper {
|
||||
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
|
||||
}
|
||||
|
||||
fakeTxs, err := getTxs(h, d, rng, fakeBlocks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
fakeAddr2txs := getAddr2TxsMap(fakeTxs)
|
||||
|
||||
verifyTransactionsXXX(t, d, rng, fakeAddr2txs, true)
|
||||
// verifyAddressesXXX(t, d, h.Chain, fakeBlocks)
|
||||
|
||||
chain.returnFakes = false
|
||||
|
||||
upperHash := fakeBlocks[len(fakeBlocks)-1].Hash
|
||||
db.HandleFork(sw, rng.Upper, upperHash, func(hash string, height uint32) {
|
||||
if hash == upperHash {
|
||||
close(ch)
|
||||
}
|
||||
}, true)
|
||||
|
||||
realBlocks := getRealBlocks(h, rng)
|
||||
realTxs, err := getTxs(h, d, rng, realBlocks)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
realAddr2txs := getAddr2TxsMap(realTxs)
|
||||
|
||||
verifyTransactionsXXX(t, d, rng, fakeAddr2txs, false)
|
||||
verifyTransactionsXXX(t, d, rng, realAddr2txs, true)
|
||||
// verifyAddressesXXX(t, d, h.Chain, realBlocks)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func verifyAddressesXXX(t *testing.T, d *db.RocksDB, chain bchain.BlockChain, blks []BlockID) {
|
||||
parser := chain.GetChainParser()
|
||||
|
||||
for _, b := range blks {
|
||||
txs, err := getBlockTxs(chain, b.Hash)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tx := range txs {
|
||||
ta, err := d.GetTxAddresses(tx.Txid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
txInfo := getTxInfo(&tx)
|
||||
taInfo, err := getTaInfo(parser, ta)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ta.Height != b.Height {
|
||||
t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, b.Height)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(txInfo.inputs) > 0 && !reflect.DeepEqual(taInfo.inputs, txInfo.inputs) {
|
||||
t.Errorf("Tx %s: inputs mismatch: got %q, want %q", tx.Txid, taInfo.inputs, txInfo.inputs)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(taInfo.outputs, txInfo.outputs) {
|
||||
t.Errorf("Tx %s: outputs mismatch: got %q, want %q", tx.Txid, taInfo.outputs, txInfo.outputs)
|
||||
}
|
||||
|
||||
if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
|
||||
t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
|
||||
tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
|
||||
}
|
||||
|
||||
if len(txInfo.inputs) > 0 {
|
||||
treshold := "0.0001"
|
||||
fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
|
||||
if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
|
||||
t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
|
||||
tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyTransactionsXXX(t *testing.T, d *db.RocksDB, rng Range, addr2txs map[string][]string, exist bool) {
|
||||
noErrs := 0
|
||||
for addr, txs := range addr2txs {
|
||||
checkMap := make(map[string]bool, len(txs))
|
||||
for _, txid := range txs {
|
||||
checkMap[txid] = false
|
||||
}
|
||||
|
||||
err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, vout uint32, isOutput bool) error {
|
||||
if isOutput {
|
||||
checkMap[txid] = true
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, txid := range txs {
|
||||
if checkMap[txid] != exist {
|
||||
auxverb := "wasn't"
|
||||
if !exist {
|
||||
auxverb = "was"
|
||||
}
|
||||
t.Errorf("%s: transaction %s %s found [expected = %t]", addr, txid, auxverb, exist)
|
||||
noErrs++
|
||||
if noErrs >= 10 {
|
||||
t.Fatal("Too many errors")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func getFakeBlocks(h *TestHandler, rng Range) []BlockID {
|
||||
blks := make([]BlockID, 0, rng.Upper-rng.Lower+1)
|
||||
for _, b := range h.TestData.HandleFork.FakeBlocks {
|
||||
if b.Height >= rng.Lower && b.Height <= rng.Upper {
|
||||
blks = append(blks, b)
|
||||
}
|
||||
}
|
||||
return blks
|
||||
}
|
||||
|
||||
func getRealBlocks(h *TestHandler, rng Range) []BlockID {
|
||||
blks := make([]BlockID, 0, rng.Upper-rng.Lower+1)
|
||||
for _, b := range h.TestData.HandleFork.RealBlocks {
|
||||
if b.Height >= rng.Lower && b.Height <= rng.Upper {
|
||||
blks = append(blks, b)
|
||||
}
|
||||
}
|
||||
return blks
|
||||
}
|
||||
|
||||
func makeFakeChain(chain bchain.BlockChain, blks []BlockID, upper uint32) (*fakeBlockChain, error) {
|
||||
if blks[len(blks)-1].Height != upper {
|
||||
return nil, errors.New("Range must end with fake block in order to emulate fork")
|
||||
}
|
||||
mBlks := make(map[uint32]BlockID, len(blks))
|
||||
for i := 0; i < len(blks); i++ {
|
||||
mBlks[blks[i].Height] = blks[i]
|
||||
}
|
||||
return &fakeBlockChain{
|
||||
BlockChain: chain,
|
||||
returnFakes: true,
|
||||
fakeBlocks: mBlks,
|
||||
fakeBestHeight: upper,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getTxs(h *TestHandler, d *db.RocksDB, rng Range, blks []BlockID) ([]bchain.Tx, error) {
|
||||
res := make([]bchain.Tx, 0, (rng.Upper-rng.Lower+1)*2000)
|
||||
|
||||
for _, b := range blks {
|
||||
bi, err := d.GetBlockInfo(b.Height)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bi.Hash != b.Hash {
|
||||
return nil, fmt.Errorf("Block hash mismatch: %s != %s", bi.Hash, b.Hash)
|
||||
}
|
||||
|
||||
txs, err := getBlockTxs(h.Chain, b.Hash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res = append(res, txs...)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func getBlockTxs(chain bchain.BlockChain, hash string) ([]bchain.Tx, error) {
|
||||
b, err := chain.GetBlock(hash, 0)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("GetBlock: %s", err)
|
||||
}
|
||||
parser := chain.GetChainParser()
|
||||
for i := 0; i < len(b.Txs); i++ {
|
||||
err := setTxAddresses(parser, &b.Txs[i])
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("setTxAddresses [%s]: %s", b.Txs[i].Txid, err)
|
||||
}
|
||||
}
|
||||
return b.Txs, nil
|
||||
}
|
||||
|
||||
func getAddr2TxsMap(txs []bchain.Tx) map[string][]string {
|
||||
addr2txs := make(map[string][]string)
|
||||
for i := 0; i < len(txs); i++ {
|
||||
for j := 0; j < len(txs[i].Vout); j++ {
|
||||
for k := 0; k < len(txs[i].Vout[j].ScriptPubKey.Addresses); k++ {
|
||||
addr := txs[i].Vout[j].ScriptPubKey.Addresses[k]
|
||||
txid := txs[i].Txid
|
||||
addr2txs[addr] = append(addr2txs[addr], txid)
|
||||
}
|
||||
}
|
||||
}
|
||||
return addr2txs
|
||||
}
|
|
@ -6,20 +6,16 @@ import (
|
|||
"blockbook/db"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testMap = map[string]func(t *testing.T, th *TestHandler){
|
||||
"ConnectBlocks": testConnectBlocks,
|
||||
"ConnectBlocksParallel": testConnectBlocksParallel,
|
||||
// "HandleFork": testHandleFork,
|
||||
"HandleFork": testHandleFork,
|
||||
}
|
||||
|
||||
type TestHandler struct {
|
||||
|
@ -127,18 +123,17 @@ func loadTestData(coin string, parser bchain.BlockChainParser) (*TestData, error
|
|||
}
|
||||
|
||||
func setTxAddresses(parser bchain.BlockChainParser, tx *bchain.Tx) error {
|
||||
// pack and unpack transaction in order to get addresses decoded - ugly but works
|
||||
var tmp *bchain.Tx
|
||||
b, err := parser.PackTx(tx, 0, 0)
|
||||
if err == nil {
|
||||
tmp, _, err = parser.UnpackTx(b)
|
||||
if err == nil {
|
||||
for i := 0; i < len(tx.Vout); i++ {
|
||||
tx.Vout[i].ScriptPubKey.Addresses = tmp.Vout[i].ScriptPubKey.Addresses
|
||||
}
|
||||
for i := 0; i < len(tx.Vout); i++ {
|
||||
ad, err := parser.GetAddrDescFromVout(&tx.Vout[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
a, s, err := parser.GetAddressesFromAddrDesc(ad)
|
||||
if err == nil && s {
|
||||
tx.Vout[i].ScriptPubKey.Addresses = a
|
||||
}
|
||||
}
|
||||
return err
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeRocksDB(parser bchain.BlockChainParser, m *common.Metrics, is *common.InternalState) (*db.RocksDB, func(), error) {
|
||||
|
@ -199,252 +194,3 @@ func withRocksDBAndSyncWorker(t *testing.T, h *TestHandler, startHeight uint32,
|
|||
|
||||
fn(d, sw, ch)
|
||||
}
|
||||
|
||||
func testConnectBlocks(t *testing.T, h *TestHandler) {
|
||||
for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
|
||||
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
|
||||
upperHash, err := h.Chain.GetBlockHash(rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = db.ConnectBlocks(sw, func(hash string, height uint32) {
|
||||
if hash == upperHash {
|
||||
close(ch)
|
||||
}
|
||||
}, true)
|
||||
if err != nil {
|
||||
if err.Error() != fmt.Sprintf("connectBlocks interrupted at height %d", rng.Upper) {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
height, hash, err := d.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if height != rng.Upper {
|
||||
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
|
||||
}
|
||||
if hash != upperHash {
|
||||
t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash)
|
||||
}
|
||||
|
||||
t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
|
||||
t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
|
||||
t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testConnectBlocksParallel(t *testing.T, h *TestHandler) {
|
||||
for _, rng := range h.TestData.ConnectBlocks.SyncRanges {
|
||||
withRocksDBAndSyncWorker(t, h, rng.Lower, func(d *db.RocksDB, sw *db.SyncWorker, ch chan os.Signal) {
|
||||
upperHash, err := h.Chain.GetBlockHash(rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = sw.ConnectBlocksParallel(rng.Lower, rng.Upper)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
height, hash, err := d.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if height != rng.Upper {
|
||||
t.Fatalf("Upper block height mismatch: %d != %d", height, rng.Upper)
|
||||
}
|
||||
if hash != upperHash {
|
||||
t.Fatalf("Upper block hash mismatch: %s != %s", hash, upperHash)
|
||||
}
|
||||
|
||||
t.Run("verifyBlockInfo", func(t *testing.T) { verifyBlockInfo(t, d, h, rng) })
|
||||
t.Run("verifyTransactions", func(t *testing.T) { verifyTransactions(t, d, h, rng) })
|
||||
t.Run("verifyAddresses", func(t *testing.T) { verifyAddresses(t, d, h, rng) })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func verifyBlockInfo(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
|
||||
for height := rng.Lower; height <= rng.Upper; height++ {
|
||||
block, found := h.TestData.ConnectBlocks.Blocks[height]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
bi, err := d.GetBlockInfo(height)
|
||||
if err != nil {
|
||||
t.Errorf("GetBlockInfo(%d) error: %s", height, err)
|
||||
continue
|
||||
}
|
||||
if bi == nil {
|
||||
t.Errorf("GetBlockInfo(%d) returned nil", height)
|
||||
continue
|
||||
}
|
||||
|
||||
if bi.Hash != block.Hash {
|
||||
t.Errorf("Block hash mismatch: %s != %s", bi.Hash, block.Hash)
|
||||
}
|
||||
|
||||
if bi.Txs != block.NoTxs {
|
||||
t.Errorf("Number of transactions in block %s mismatch: %d != %d", bi.Hash, bi.Txs, block.NoTxs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyTransactions(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
|
||||
type txInfo struct {
|
||||
txid string
|
||||
vout uint32
|
||||
isOutput bool
|
||||
}
|
||||
addr2txs := make(map[string][]txInfo)
|
||||
checkMap := make(map[string][]bool)
|
||||
|
||||
for height := rng.Lower; height <= rng.Upper; height++ {
|
||||
block, found := h.TestData.ConnectBlocks.Blocks[height]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tx := range block.TxDetails {
|
||||
for _, vin := range tx.Vin {
|
||||
for _, a := range vin.Addresses {
|
||||
addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vin.Vout, false})
|
||||
checkMap[a] = append(checkMap[a], false)
|
||||
}
|
||||
}
|
||||
for _, vout := range tx.Vout {
|
||||
for _, a := range vout.ScriptPubKey.Addresses {
|
||||
addr2txs[a] = append(addr2txs[a], txInfo{tx.Txid, vout.N, true})
|
||||
checkMap[a] = append(checkMap[a], false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for addr, txs := range addr2txs {
|
||||
err := d.GetTransactions(addr, rng.Lower, rng.Upper, func(txid string, vout uint32, isOutput bool) error {
|
||||
for i, tx := range txs {
|
||||
if txid == tx.txid && vout == tx.vout && isOutput == tx.isOutput {
|
||||
checkMap[addr][i] = true
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
for addr, txs := range addr2txs {
|
||||
for i, tx := range txs {
|
||||
if !checkMap[addr][i] {
|
||||
t.Errorf("%s: transaction not found %+v", addr, tx)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func verifyAddresses(t *testing.T, d *db.RocksDB, h *TestHandler, rng Range) {
|
||||
parser := h.Chain.GetChainParser()
|
||||
|
||||
for height := rng.Lower; height <= rng.Upper; height++ {
|
||||
block, found := h.TestData.ConnectBlocks.Blocks[height]
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, tx := range block.TxDetails {
|
||||
ta, err := d.GetTxAddresses(tx.Txid)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
txInfo := getTxInfo(tx)
|
||||
taInfo, err := getTaInfo(parser, ta)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if ta.Height != height {
|
||||
t.Errorf("Tx %s: block height mismatch: %d != %d", tx.Txid, ta.Height, height)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(txInfo.inputs) > 0 && !reflect.DeepEqual(taInfo.inputs, txInfo.inputs) {
|
||||
t.Errorf("Tx %s: inputs mismatch: got %q, want %q", tx.Txid, taInfo.inputs, txInfo.inputs)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(taInfo.outputs, txInfo.outputs) {
|
||||
t.Errorf("Tx %s: outputs mismatch: got %q, want %q", tx.Txid, taInfo.outputs, txInfo.outputs)
|
||||
}
|
||||
|
||||
if taInfo.valOutSat.Cmp(&txInfo.valOutSat) != 0 {
|
||||
t.Errorf("Tx %s: total output amount mismatch: got %s, want %s",
|
||||
tx.Txid, taInfo.valOutSat.String(), txInfo.valOutSat.String())
|
||||
}
|
||||
|
||||
if len(txInfo.inputs) > 0 {
|
||||
treshold := "0.0001"
|
||||
fee := new(big.Int).Sub(&taInfo.valInSat, &taInfo.valOutSat)
|
||||
if strings.Compare(parser.AmountToDecimalString(fee), treshold) > 0 {
|
||||
t.Errorf("Tx %s: suspicious amounts: input ∑ [%s] - output ∑ [%s] > %s",
|
||||
tx.Txid, taInfo.valInSat.String(), taInfo.valOutSat.String(), treshold)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type txInfo struct {
|
||||
inputs []string
|
||||
outputs []string
|
||||
valInSat big.Int
|
||||
valOutSat big.Int
|
||||
}
|
||||
|
||||
func getTxInfo(tx *bchain.Tx) *txInfo {
|
||||
info := &txInfo{inputs: []string{}, outputs: []string{}}
|
||||
|
||||
for _, vin := range tx.Vin {
|
||||
for _, a := range vin.Addresses {
|
||||
info.inputs = append(info.inputs, a)
|
||||
}
|
||||
}
|
||||
for _, vout := range tx.Vout {
|
||||
for _, a := range vout.ScriptPubKey.Addresses {
|
||||
info.outputs = append(info.outputs, a)
|
||||
}
|
||||
info.valOutSat.Add(&info.valOutSat, &vout.ValueSat)
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
func getTaInfo(parser bchain.BlockChainParser, ta *db.TxAddresses) (*txInfo, error) {
|
||||
info := &txInfo{inputs: []string{}, outputs: []string{}}
|
||||
|
||||
for i := range ta.Inputs {
|
||||
info.valInSat.Add(&info.valInSat, &ta.Inputs[i].ValueSat)
|
||||
addrs, _, err := ta.Inputs[i].Addresses(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.inputs = append(info.inputs, addrs...)
|
||||
}
|
||||
|
||||
for i := range ta.Outputs {
|
||||
info.valOutSat.Add(&info.valOutSat, &ta.Outputs[i].ValueSat)
|
||||
addrs, _, err := ta.Outputs[i].Addresses(parser)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
info.outputs = append(info.outputs, addrs...)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
|
|
@ -397,5 +397,63 @@
|
|||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"handleFork": {
|
||||
"syncRanges": [
|
||||
{"lower": 541224, "upper": 541255},
|
||||
{"lower": 542835, "upper": 542838}
|
||||
],
|
||||
"fakeBlocks": {
|
||||
"541253": {
|
||||
"height": 541253,
|
||||
"hash": "0000000000000000001af0987bd9f319c5a8105537b3deb54c423b299b133bb6"
|
||||
},
|
||||
"541254": {
|
||||
"height": 541254,
|
||||
"hash": "00000000000000000011eebe554e91cf26ba7897195b42997b03ec4f929b27d2"
|
||||
},
|
||||
"541255": {
|
||||
"height": 541255,
|
||||
"hash": "0000000000000000000feb2cbefe55cb4bb8cf045fac7ed4b4cc09e8f645a64c"
|
||||
},
|
||||
"542836": {
|
||||
"height": 542836,
|
||||
"hash": "000000000000000000218866194a2bc15b92d7dccca1d328a2fa6a9c0befb039"
|
||||
},
|
||||
"542837": {
|
||||
"height": 542837,
|
||||
"hash": "00000000000000000023936c5189062f9f9b05895d778c202e2f83f0d119a370"
|
||||
},
|
||||
"542838": {
|
||||
"height": 542838,
|
||||
"hash": "000000000000000000155f1289c445127d0cfc360b8cc9f2c46c5850de607307"
|
||||
}
|
||||
},
|
||||
"realBlocks": {
|
||||
"541253": {
|
||||
"height": 541253,
|
||||
"hash": "000000000000000000045d55641e32a86ff313cd9d8a557d0fe9d0ab4e7eae7f"
|
||||
},
|
||||
"541254": {
|
||||
"height": 541254,
|
||||
"hash": "0000000000000000000e08972d3e7e26f30c58313dcd15ce5817b09aef0e69b7"
|
||||
},
|
||||
"541255": {
|
||||
"height": 541255,
|
||||
"hash": "0000000000000000001375c3e88b4e53b687a70aba4b645ba81c93a4539d724d"
|
||||
},
|
||||
"542836": {
|
||||
"height": 542836,
|
||||
"hash": "0000000000000000000927c61a2a3174c64f2beee103ead0f62b30354f323456"
|
||||
},
|
||||
"542837": {
|
||||
"height": 542837,
|
||||
"hash": "000000000000000000158353b8c9865c2261dc96bd780ef7d0017b2c2798c5c7"
|
||||
},
|
||||
"542838": {
|
||||
"height": 542838,
|
||||
"hash": "00000000000000000001e973be0770d428d3494d4e1b661aafcd8924c892648d"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
{
|
||||
"handleFork": {
|
||||
"syncRanges": [
|
||||
{"lower": 1414526, "upper": 1414546}
|
||||
],
|
||||
"fakeBlocks": {
|
||||
"1414544": {
|
||||
"height": 1414544,
|
||||
"hash": "00000000aa4bbe624efc4cc0be98f1d49b03abca094a9785ad1e50c20e7634b1"
|
||||
},
|
||||
"1414545": {
|
||||
"height": 1414545,
|
||||
"hash": "0000000000035fd65c471e0645190d5392de4dc626358bc001c753ade3b8a44c"
|
||||
},
|
||||
"1414546": {
|
||||
"height": 1414546,
|
||||
"hash": "000000000003b4360cebb2c29d7798c22ae31b8ba83941ebed0c61fb56beb765"
|
||||
}
|
||||
},
|
||||
"realBlocks": {
|
||||
"1414544": {
|
||||
"height": 1414544,
|
||||
"hash": "0000000000012b55c84faf61e201a1c64c6a57e8be3d2b5e11868c39cab5027e"
|
||||
},
|
||||
"1414545": {
|
||||
"height": 1414545,
|
||||
"hash": "0000000017020556bdeac9d3bbd2361d11aba79ac39f8a161170ac22d225856d"
|
||||
},
|
||||
"1414546": {
|
||||
"height": 1414546,
|
||||
"hash": "0000000000000000b169d17aee62e9f5758a38c7bead2676fc4e4a22a51f17b9"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,11 +10,12 @@
|
|||
"bitcoin": {
|
||||
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",
|
||||
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],
|
||||
"sync": ["ConnectBlocksParallel", "ConnectBlocks"]
|
||||
"sync": ["ConnectBlocksParallel", "ConnectBlocks", "HandleFork"]
|
||||
},
|
||||
"bitcoin_testnet": {
|
||||
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",
|
||||
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"]
|
||||
"EstimateSmartFee", "EstimateFee", "GetBestBlockHash", "GetBestBlockHeight", "GetBlockHeader"],
|
||||
"sync": ["HandleFork"]
|
||||
},
|
||||
"dash": {
|
||||
"rpc": ["GetBlock", "GetBlockHash", "GetTransaction", "GetTransactionForMempool", "MempoolSync",
|
||||
|
|
Loading…
Reference in New Issue