Implement index v3 for ethereum type coin
parent
eb524c2226
commit
8886256d0b
|
@ -235,6 +235,7 @@ func (p *EthereumParser) PackTx(tx *bchain.Tx, height uint32, blockTime int64) (
|
|||
if pt.Tx.AccountNonce, err = hexutil.DecodeUint64(r.Tx.AccountNonce); err != nil {
|
||||
return nil, errors.Annotatef(err, "AccountNonce %v", r.Tx.AccountNonce)
|
||||
}
|
||||
// pt.BlockNumber = height
|
||||
if n, err = hexutil.DecodeUint64(r.Tx.BlockNumber); err != nil {
|
||||
return nil, errors.Annotatef(err, "BlockNumber %v", r.Tx.BlockNumber)
|
||||
}
|
||||
|
|
|
@ -150,9 +150,7 @@ func (is *InternalState) GetDBColumnStatValues(c int) (int64, int64, int64) {
|
|||
func (is *InternalState) GetAllDBColumnStats() []InternalStateColumn {
|
||||
is.mux.Lock()
|
||||
defer is.mux.Unlock()
|
||||
rv := make([]InternalStateColumn, len(is.DbColumns))
|
||||
copy(rv, is.DbColumns)
|
||||
return rv
|
||||
return append(is.DbColumns[:0:0], is.DbColumns...)
|
||||
}
|
||||
|
||||
// DBSizeTotal sums the computed sizes of all columns
|
||||
|
|
|
@ -273,39 +273,20 @@ const (
|
|||
|
||||
// ConnectBlock indexes addresses in the block and stores them in db
|
||||
func (d *RocksDB) ConnectBlock(block *bchain.Block) error {
|
||||
return d.writeBlock(block, opInsert)
|
||||
}
|
||||
|
||||
// DisconnectBlock removes addresses in the block from the db
|
||||
func (d *RocksDB) DisconnectBlock(block *bchain.Block) error {
|
||||
return d.writeBlock(block, opDelete)
|
||||
}
|
||||
|
||||
func (d *RocksDB) writeBlock(block *bchain.Block, op int) error {
|
||||
wb := gorocksdb.NewWriteBatch()
|
||||
defer wb.Destroy()
|
||||
|
||||
if glog.V(2) {
|
||||
switch op {
|
||||
case opInsert:
|
||||
glog.Infof("rocksdb: insert %d %s", block.Height, block.Hash)
|
||||
case opDelete:
|
||||
glog.Infof("rocksdb: delete %d %s", block.Height, block.Hash)
|
||||
}
|
||||
glog.Infof("rocksdb: insert %d %s", block.Height, block.Hash)
|
||||
}
|
||||
|
||||
chainType := d.chainParser.GetChainType()
|
||||
|
||||
if err := d.writeHeightFromBlock(wb, block, op); err != nil {
|
||||
if err := d.writeHeightFromBlock(wb, block, opInsert); err != nil {
|
||||
return err
|
||||
}
|
||||
addresses := make(map[string][]outpoint)
|
||||
if chainType == bchain.ChainBitcoinType {
|
||||
if op == opDelete {
|
||||
// block does not contain mapping tx-> input address, which is necessary to recreate
|
||||
// unspentTxs; therefore it is not possible to DisconnectBlocks this way
|
||||
return errors.New("DisconnectBlock is not supported for BitcoinType chains")
|
||||
}
|
||||
txAddressesMap := make(map[string]*TxAddresses)
|
||||
balances := make(map[string]*AddrBalance)
|
||||
if err := d.processAddressesBitcoinType(block, addresses, txAddressesMap, balances); err != nil {
|
||||
|
@ -680,8 +661,7 @@ func (d *RocksDB) getBlockTxs(height uint32) ([]blockTxs, error) {
|
|||
glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf))
|
||||
return nil, errors.New("Inconsistent data in blockTxs")
|
||||
}
|
||||
txid := make([]byte, pl)
|
||||
copy(txid, buf[i:])
|
||||
txid := append([]byte(nil), buf[i:i+pl]...)
|
||||
i += pl
|
||||
o, ol, err := d.unpackNOutpoints(buf[i:])
|
||||
if err != nil {
|
||||
|
@ -812,8 +792,7 @@ func unpackTxAddresses(buf []byte) (*TxAddresses, error) {
|
|||
|
||||
func unpackTxInput(ti *TxInput, buf []byte) int {
|
||||
al, l := unpackVaruint(buf)
|
||||
ti.AddrDesc = make([]byte, al)
|
||||
copy(ti.AddrDesc, buf[l:l+int(al)])
|
||||
ti.AddrDesc = append([]byte(nil), buf[l:l+int(al)]...)
|
||||
al += uint(l)
|
||||
ti.ValueSat, l = unpackBigint(buf[al:])
|
||||
return l + int(al)
|
||||
|
@ -825,8 +804,7 @@ func unpackTxOutput(to *TxOutput, buf []byte) int {
|
|||
to.Spent = true
|
||||
al = ^al
|
||||
}
|
||||
to.AddrDesc = make([]byte, al)
|
||||
copy(to.AddrDesc, buf[l:l+al])
|
||||
to.AddrDesc = append([]byte(nil), buf[l:l+al]...)
|
||||
al += l
|
||||
to.ValueSat, l = unpackBigint(buf[al:])
|
||||
return l + al
|
||||
|
@ -1004,51 +982,6 @@ func (d *RocksDB) writeHeight(wb *gorocksdb.WriteBatch, height uint32, bi *Block
|
|||
|
||||
// Disconnect blocks
|
||||
|
||||
func (d *RocksDB) allAddressesScan(lower uint32, higher uint32) ([][]byte, [][]byte, error) {
|
||||
glog.Infof("db: doing full scan of addresses column")
|
||||
addrKeys := [][]byte{}
|
||||
addrValues := [][]byte{}
|
||||
var totalOutputs, count uint64
|
||||
var seekKey []byte
|
||||
for {
|
||||
var key []byte
|
||||
it := d.db.NewIteratorCF(d.ro, d.cfh[cfAddresses])
|
||||
if totalOutputs == 0 {
|
||||
it.SeekToFirst()
|
||||
} else {
|
||||
it.Seek(seekKey)
|
||||
it.Next()
|
||||
}
|
||||
for count = 0; it.Valid() && count < refreshIterator; it.Next() {
|
||||
totalOutputs++
|
||||
count++
|
||||
key = it.Key().Data()
|
||||
l := len(key)
|
||||
if l > packedHeightBytes {
|
||||
height := unpackUint(key[l-packedHeightBytes : l])
|
||||
if height >= lower && height <= higher {
|
||||
addrKey := make([]byte, len(key))
|
||||
copy(addrKey, key)
|
||||
addrKeys = append(addrKeys, addrKey)
|
||||
value := it.Value().Data()
|
||||
addrValue := make([]byte, len(value))
|
||||
copy(addrValue, value)
|
||||
addrValues = append(addrValues, addrValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
seekKey = make([]byte, len(key))
|
||||
copy(seekKey, key)
|
||||
valid := it.Valid()
|
||||
it.Close()
|
||||
if !valid {
|
||||
break
|
||||
}
|
||||
}
|
||||
glog.Infof("rocksdb: scanned %d addresses, found %d to disconnect", totalOutputs, len(addrKeys))
|
||||
return addrKeys, addrValues, nil
|
||||
}
|
||||
|
||||
func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32, txid string, inputs []outpoint, txa *TxAddresses,
|
||||
txAddressesToUpdate map[string]*TxAddresses, balances map[string]*AddrBalance) error {
|
||||
addresses := make(map[string]struct{})
|
||||
|
@ -1136,9 +1069,8 @@ func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32,
|
|||
}
|
||||
|
||||
// DisconnectBlockRangeBitcoinType removes all data belonging to blocks in range lower-higher
|
||||
// if they are in the range kept in the cfBlockTxids column
|
||||
// it is able to disconnect only blocks for which there are data in the blockTxs column
|
||||
func (d *RocksDB) DisconnectBlockRangeBitcoinType(lower uint32, higher uint32) error {
|
||||
glog.Infof("db: disconnecting blocks %d-%d", lower, higher)
|
||||
blocks := make([][]blockTxs, higher-lower+1)
|
||||
for height := lower; height <= higher; height++ {
|
||||
blockTxs, err := d.getBlockTxs(height)
|
||||
|
|
|
@ -65,8 +65,7 @@ func (d *RocksDB) GetAddrDescContracts(addrDesc bchain.AddressDescriptor) (*Addr
|
|||
return nil, errors.New("Invalid data stored in cfAddressContracts for AddrDesc " + addrDesc.String())
|
||||
}
|
||||
txs, l := unpackVaruint(buf[eth.EthereumTypeAddressDescriptorLen:])
|
||||
contract := make(bchain.AddressDescriptor, eth.EthereumTypeAddressDescriptorLen)
|
||||
copy(contract, buf[:eth.EthereumTypeAddressDescriptorLen])
|
||||
contract := append(bchain.AddressDescriptor(nil), buf[:eth.EthereumTypeAddressDescriptorLen]...)
|
||||
c = append(c, AddrContract{
|
||||
Contract: contract,
|
||||
Txs: txs,
|
||||
|
@ -78,6 +77,15 @@ func (d *RocksDB) GetAddrDescContracts(addrDesc bchain.AddressDescriptor) (*Addr
|
|||
Contracts: c}, nil
|
||||
}
|
||||
|
||||
func findContractInAddressContracts(contract bchain.AddressDescriptor, contracts []AddrContract) (int, bool) {
|
||||
for i := range contracts {
|
||||
if bytes.Equal(contract, contracts[i].Contract) {
|
||||
return i, true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.AddressDescriptor, btxID []byte, index int32, contract bchain.AddressDescriptor, addresses map[string][]outpoint, addressContracts map[string]*AddrContracts) error {
|
||||
var err error
|
||||
strAddrDesc := string(addrDesc)
|
||||
|
@ -99,14 +107,7 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address
|
|||
ac.EthTxs++
|
||||
} else {
|
||||
// locate the contract and set i to the index in the array of contracts
|
||||
var i int
|
||||
var found bool
|
||||
for i = range ac.Contracts {
|
||||
if bytes.Equal(contract, ac.Contracts[i].Contract) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
i, found := findContractInAddressContracts(contract, ac.Contracts)
|
||||
if !found {
|
||||
i = len(ac.Contracts)
|
||||
ac.Contracts = append(ac.Contracts, AddrContract{Contract: contract})
|
||||
|
@ -240,31 +241,159 @@ func (d *RocksDB) storeAndCleanupBlockTxsEthereumType(wb *gorocksdb.WriteBatch,
|
|||
return d.cleanupBlockTxs(wb, block)
|
||||
}
|
||||
|
||||
// DisconnectBlockRangeNonUTXO performs full range scan to remove a range of blocks
|
||||
// it is very slow operation
|
||||
func (d *RocksDB) DisconnectBlockRangeNonUTXO(lower uint32, higher uint32) error {
|
||||
glog.Infof("db: disconnecting blocks %d-%d", lower, higher)
|
||||
addrKeys, _, err := d.allAddressesScan(lower, higher)
|
||||
func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) {
|
||||
pl := d.chainParser.PackedTxidLen()
|
||||
val, err := d.db.GetCF(d.ro, d.cfh[cfBlockTxs], packUint(height))
|
||||
if err != nil {
|
||||
return err
|
||||
return nil, err
|
||||
}
|
||||
defer val.Free()
|
||||
buf := val.Data()
|
||||
bt := make([]ethBlockTx, 0, 8)
|
||||
getAddress := func(i int) (bchain.AddressDescriptor, int, error) {
|
||||
if len(buf)-i < eth.EthereumTypeAddressDescriptorLen {
|
||||
glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf))
|
||||
return nil, 0, errors.New("Inconsistent data in blockTxs")
|
||||
}
|
||||
a := append(bchain.AddressDescriptor(nil), buf[i:i+eth.EthereumTypeAddressDescriptorLen]...)
|
||||
// return null addresses as nil
|
||||
for _, b := range a {
|
||||
if b != 0 {
|
||||
return a, i + eth.EthereumTypeAddressDescriptorLen, nil
|
||||
}
|
||||
}
|
||||
return nil, i + eth.EthereumTypeAddressDescriptorLen, nil
|
||||
}
|
||||
var from, to bchain.AddressDescriptor
|
||||
for i := 0; i < len(buf); {
|
||||
if len(buf)-i < pl {
|
||||
glog.Error("rocksdb: Inconsistent data in blockTxs ", hex.EncodeToString(buf))
|
||||
return nil, errors.New("Inconsistent data in blockTxs")
|
||||
}
|
||||
txid := append([]byte(nil), buf[i:i+pl]...)
|
||||
i += pl
|
||||
from, i, err = getAddress(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
to, i, err = getAddress(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cc, l := unpackVaruint(buf[i:])
|
||||
i += l
|
||||
contracts := make([]ethBlockTxContract, cc)
|
||||
for j := range contracts {
|
||||
contracts[j].addr, i, err = getAddress(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
contracts[j].contract, i, err = getAddress(i)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
bt = append(bt, ethBlockTx{
|
||||
btxID: txid,
|
||||
from: from,
|
||||
to: to,
|
||||
contracts: contracts,
|
||||
})
|
||||
}
|
||||
return bt, nil
|
||||
}
|
||||
|
||||
func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, height uint32, blockTxs []ethBlockTx, contracts map[string]*AddrContracts) error {
|
||||
glog.Info("Disconnecting block ", height, " containing ", len(blockTxs), " transactions")
|
||||
addresses := make(map[string]struct{})
|
||||
disconnectAddress := func(addrDesc, contract bchain.AddressDescriptor) error {
|
||||
var err error
|
||||
s := string(addrDesc)
|
||||
addresses[s] = struct{}{}
|
||||
c, fc := contracts[s]
|
||||
if !fc {
|
||||
c, err = d.GetAddrDescContracts(addrDesc)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
contracts[s] = c
|
||||
}
|
||||
if c != nil {
|
||||
if contract == nil {
|
||||
if c.EthTxs > 0 {
|
||||
c.EthTxs--
|
||||
} else {
|
||||
glog.Warning("AddressContracts ", addrDesc, ", EthTxs would be negative")
|
||||
}
|
||||
} else {
|
||||
i, found := findContractInAddressContracts(contract, c.Contracts)
|
||||
if found {
|
||||
if c.Contracts[i].Txs > 0 {
|
||||
c.Contracts[i].Txs--
|
||||
if c.Contracts[i].Txs == 0 {
|
||||
c.Contracts = append(c.Contracts[:i], c.Contracts[i+1:]...)
|
||||
}
|
||||
} else {
|
||||
glog.Warning("AddressContracts ", addrDesc, ", contract ", i, " Txs would be negative")
|
||||
}
|
||||
} else {
|
||||
glog.Warning("AddressContracts ", addrDesc, ", contract ", contract, " not found")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glog.Warning("AddressContracts ", addrDesc, " not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for i := range blockTxs {
|
||||
blockTx := &blockTxs[i]
|
||||
if err := disconnectAddress(blockTx.from, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := disconnectAddress(blockTx.to, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, c := range blockTx.contracts {
|
||||
if err := disconnectAddress(c.addr, c.contract); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
wb.DeleteCF(d.cfh[cfTransactions], blockTx.btxID)
|
||||
}
|
||||
for a := range addresses {
|
||||
key := packAddressKey([]byte(a), height)
|
||||
wb.DeleteCF(d.cfh[cfAddresses], key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DisconnectBlockRangeEthereumType removes all data belonging to blocks in range lower-higher
|
||||
// it is able to disconnect only blocks for which there are data in the blockTxs column
|
||||
func (d *RocksDB) DisconnectBlockRangeEthereumType(lower uint32, higher uint32) error {
|
||||
blocks := make([][]ethBlockTx, higher-lower+1)
|
||||
for height := lower; height <= higher; height++ {
|
||||
blockTxs, err := d.getBlockTxsEthereumType(height)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(blockTxs) == 0 {
|
||||
return errors.Errorf("Cannot disconnect blocks with height %v and lower. It is necessary to rebuild index.", height)
|
||||
}
|
||||
blocks[height-lower] = blockTxs
|
||||
}
|
||||
glog.Infof("rocksdb: about to disconnect %d addresses ", len(addrKeys))
|
||||
wb := gorocksdb.NewWriteBatch()
|
||||
defer wb.Destroy()
|
||||
for _, addrKey := range addrKeys {
|
||||
if glog.V(2) {
|
||||
glog.Info("address ", hex.EncodeToString(addrKey))
|
||||
contracts := make(map[string]*AddrContracts)
|
||||
for height := higher; height >= lower; height-- {
|
||||
if err := d.disconnectBlockTxsEthereumType(wb, height, blocks[height-lower], contracts); err != nil {
|
||||
return err
|
||||
}
|
||||
// delete address:height from the index
|
||||
wb.DeleteCF(d.cfh[cfAddresses], addrKey)
|
||||
key := packUint(height)
|
||||
wb.DeleteCF(d.cfh[cfBlockTxs], key)
|
||||
wb.DeleteCF(d.cfh[cfHeight], key)
|
||||
}
|
||||
for height := lower; height <= higher; height++ {
|
||||
if glog.V(2) {
|
||||
glog.Info("height ", height)
|
||||
}
|
||||
wb.DeleteCF(d.cfh[cfHeight], packUint(height))
|
||||
}
|
||||
err = d.db.Write(d.wo, wb)
|
||||
d.storeAddressContracts(wb, contracts)
|
||||
err := d.db.Write(d.wo, wb)
|
||||
if err == nil {
|
||||
glog.Infof("rocksdb: blocks %d-%d disconnected", lower, higher)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,11 @@ package db
|
|||
import (
|
||||
"blockbook/bchain/coins/eth"
|
||||
"blockbook/tests/dbtestdata"
|
||||
"encoding/hex"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/juju/errors"
|
||||
)
|
||||
|
||||
type testEthereumParser struct {
|
||||
|
@ -85,7 +89,7 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) {
|
|||
},
|
||||
keyPair{
|
||||
"0041eee9",
|
||||
"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6" + uintToHex(1534859988) + varuintToHex(2) + varuintToHex(2345678),
|
||||
"2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee" + uintToHex(1534859988) + varuintToHex(2) + varuintToHex(2345678),
|
||||
nil,
|
||||
},
|
||||
}); err != nil {
|
||||
|
@ -156,9 +160,8 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) {
|
|||
// 2) GetTransactions for various addresses / low-high ranges
|
||||
// 3) GetBestBlock, GetBlockHash
|
||||
// 4) Test tx caching functionality
|
||||
// 5) Disconnect block 2 - expect error
|
||||
// 6) Disconnect the block 2 using BlockTxs column
|
||||
// 7) Reconnect block 2 and check
|
||||
// 5) Disconnect the block 2 using BlockTxs column
|
||||
// 6) Reconnect block 2 and check
|
||||
// After each step, the content of DB is examined and any difference against expected state is regarded as failure
|
||||
func TestRocksDB_Index_EthereumType(t *testing.T) {
|
||||
d := setupRocksDB(t, &testEthereumParser{
|
||||
|
@ -166,18 +169,118 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
|
|||
})
|
||||
defer closeAndDestroyRocksDB(t, d)
|
||||
|
||||
// connect 1st block - will log warnings about missing UTXO transactions in txAddresses column
|
||||
// connect 1st block
|
||||
block1 := dbtestdata.GetTestEthereumTypeBlock1(d.chainParser)
|
||||
if err := d.ConnectBlock(block1); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyAfterEthereumTypeBlock1(t, d, false)
|
||||
|
||||
// connect 2nd block - use some outputs from the 1st block as the inputs and 1 input uses tx from the same block
|
||||
// connect 2nd block
|
||||
block2 := dbtestdata.GetTestEthereumTypeBlock2(d.chainParser)
|
||||
if err := d.ConnectBlock(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyAfterEthereumTypeBlock2(t, d)
|
||||
|
||||
// get transactions for various addresses / low-high ranges
|
||||
verifyGetTransactions(t, d, "0x"+dbtestdata.EthAddr55, 0, 10000000, []txidVoutOutput{
|
||||
txidVoutOutput{"0x" + dbtestdata.EthTxidB1T1, 0, true},
|
||||
txidVoutOutput{"0x" + dbtestdata.EthTxidB1T2, 1, true},
|
||||
txidVoutOutput{"0x" + dbtestdata.EthTxidB2T1, 0, false},
|
||||
txidVoutOutput{"0x" + dbtestdata.EthTxidB2T2, 2, false},
|
||||
txidVoutOutput{"0x" + dbtestdata.EthTxidB2T2, 1, true},
|
||||
}, nil)
|
||||
verifyGetTransactions(t, d, "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eBad", 500000, 1000000, []txidVoutOutput{}, errors.New("Address missing"))
|
||||
|
||||
// GetBestBlock
|
||||
height, hash, err := d.GetBestBlock()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if height != 4321001 {
|
||||
t.Fatalf("GetBestBlock: got height %v, expected %v", height, 4321001)
|
||||
}
|
||||
if hash != "0x2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee" {
|
||||
t.Fatalf("GetBestBlock: got hash %v, expected %v", hash, "0x2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee")
|
||||
}
|
||||
|
||||
// GetBlockHash
|
||||
hash, err = d.GetBlockHash(4321000)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hash != "0xc7b98df95acfd11c51ba25611a39e004fe56c8fdfc1582af99354fcd09c17b11" {
|
||||
t.Fatalf("GetBlockHash: got hash %v, expected %v", hash, "0xc7b98df95acfd11c51ba25611a39e004fe56c8fdfc1582af99354fcd09c17b11")
|
||||
}
|
||||
|
||||
// Not connected block
|
||||
hash, err = d.GetBlockHash(4321002)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if hash != "" {
|
||||
t.Fatalf("GetBlockHash: got hash '%v', expected ''", hash)
|
||||
}
|
||||
|
||||
// GetBlockHash
|
||||
info, err := d.GetBlockInfo(4321001)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
iw := &BlockInfo{
|
||||
Hash: "0x2b57e15e93a0ed197417a34c2498b7187df79099572c04a6b6e6ff418f74e6ee",
|
||||
Txs: 2,
|
||||
Size: 2345678,
|
||||
Time: 1534859988,
|
||||
Height: 4321001,
|
||||
}
|
||||
if !reflect.DeepEqual(info, iw) {
|
||||
t.Errorf("GetBlockInfo() = %+v, want %+v", info, iw)
|
||||
}
|
||||
|
||||
// Test tx caching functionality, leave one tx in db to test cleanup in DisconnectBlock
|
||||
testTxCache(t, d, block1, &block1.Txs[0])
|
||||
testTxCache(t, d, block2, &block2.Txs[0])
|
||||
if err = d.PutTx(&block2.Txs[1], block2.Height, block2.Txs[1].Blocktime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err = d.PutTx(&block2.Txs[1], block2.Height, block2.Txs[1].Blocktime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
// check that there is only the last tx in the cache
|
||||
packedTx, err := d.chainParser.PackTx(&block2.Txs[1], block2.Height, block2.Txs[1].Blocktime)
|
||||
if err := checkColumn(d, cfTransactions, []keyPair{
|
||||
keyPair{dbtestdata.EthTxidB2T2, hex.EncodeToString(packedTx), nil},
|
||||
}); err != nil {
|
||||
{
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
// try to disconnect both blocks, however only the last one is kept, it is not possible
|
||||
err = d.DisconnectBlockRangeEthereumType(4321000, 4321001)
|
||||
if err == nil || err.Error() != "Cannot disconnect blocks with height 4321000 and lower. It is necessary to rebuild index." {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyAfterEthereumTypeBlock2(t, d)
|
||||
|
||||
// disconnect the 2nd block, verify that the db contains only data from the 1st block with restored unspentTxs
|
||||
// and that the cached tx is removed
|
||||
err = d.DisconnectBlockRangeEthereumType(4321001, 4321001)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyAfterEthereumTypeBlock1(t, d, true)
|
||||
if err := checkColumn(d, cfTransactions, []keyPair{}); err != nil {
|
||||
{
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// connect block again and verify the state of db
|
||||
if err := d.ConnectBlock(block2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyAfterEthereumTypeBlock2(t, d)
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ import (
|
|||
"blockbook/tests/dbtestdata"
|
||||
"encoding/binary"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/big"
|
||||
"os"
|
||||
|
@ -415,7 +414,7 @@ func testTxCache(t *testing.T, d *RocksDB, b *bchain.Block, tx *bchain.Tx) {
|
|||
}
|
||||
// Confirmations are not stored in the DB, set them from input tx
|
||||
gtx.Confirmations = tx.Confirmations
|
||||
if fmt.Sprint(gtx) != fmt.Sprint(tx) {
|
||||
if !reflect.DeepEqual(gtx, tx) {
|
||||
t.Errorf("GetTx: %v, want %v", gtx, tx)
|
||||
}
|
||||
if err := d.DeleteTx(tx.Txid); err != nil {
|
||||
|
@ -429,9 +428,8 @@ func testTxCache(t *testing.T, d *RocksDB, b *bchain.Block, tx *bchain.Tx) {
|
|||
// 2) GetTransactions for various addresses / low-high ranges
|
||||
// 3) GetBestBlock, GetBlockHash
|
||||
// 4) Test tx caching functionality
|
||||
// 5) Disconnect block 2 - expect error
|
||||
// 6) Disconnect the block 2 using BlockTxs column
|
||||
// 7) Reconnect block 2 and check
|
||||
// 5) Disconnect the block 2 using BlockTxs column
|
||||
// 6) Reconnect block 2 and check
|
||||
// After each step, the content of DB is examined and any difference against expected state is regarded as failure
|
||||
func TestRocksDB_Index_BitcoinType(t *testing.T) {
|
||||
d := setupRocksDB(t, &testBitcoinParser{
|
||||
|
@ -513,7 +511,7 @@ func TestRocksDB_Index_BitcoinType(t *testing.T) {
|
|||
Height: 225494,
|
||||
}
|
||||
if !reflect.DeepEqual(info, iw) {
|
||||
t.Errorf("GetAddressBalance() = %+v, want %+v", info, iw)
|
||||
t.Errorf("GetBlockInfo() = %+v, want %+v", info, iw)
|
||||
}
|
||||
|
||||
// Test tx caching functionality, leave one tx in db to test cleanup in DisconnectBlock
|
||||
|
@ -532,13 +530,6 @@ func TestRocksDB_Index_BitcoinType(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
// DisconnectBlock for BitcoinType chains is not possible
|
||||
err = d.DisconnectBlock(block2)
|
||||
if err == nil || err.Error() != "DisconnectBlock is not supported for BitcoinType chains" {
|
||||
t.Fatal(err)
|
||||
}
|
||||
verifyAfterBitcoinTypeBlock2(t, d)
|
||||
|
||||
// try to disconnect both blocks, however only the last one is kept, it is not possible
|
||||
err = d.DisconnectBlockRangeBitcoinType(225493, 225494)
|
||||
if err == nil || err.Error() != "Cannot disconnect blocks with height 225493 and lower. It is necessary to rebuild index." {
|
||||
|
@ -566,7 +557,6 @@ func TestRocksDB_Index_BitcoinType(t *testing.T) {
|
|||
verifyAfterBitcoinTypeBlock2(t, d)
|
||||
|
||||
// test public methods for address balance and tx addresses
|
||||
|
||||
ab, err := d.GetAddressBalance(dbtestdata.Addr5)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -848,17 +838,17 @@ func Test_packTxAddresses_unpackTxAddresses(t *testing.T) {
|
|||
Height: 123456789,
|
||||
Inputs: []TxInput{
|
||||
{
|
||||
AddrDesc: []byte{},
|
||||
AddrDesc: []byte(nil),
|
||||
ValueSat: *big.NewInt(1234),
|
||||
},
|
||||
},
|
||||
Outputs: []TxOutput{
|
||||
{
|
||||
AddrDesc: []byte{},
|
||||
AddrDesc: []byte(nil),
|
||||
ValueSat: *big.NewInt(5678),
|
||||
},
|
||||
{
|
||||
AddrDesc: []byte{},
|
||||
AddrDesc: []byte(nil),
|
||||
ValueSat: *big.NewInt(98),
|
||||
Spent: true,
|
||||
},
|
||||
|
|
25
db/sync.go
25
db/sync.go
|
@ -393,26 +393,11 @@ func (w *SyncWorker) getBlockChain(out chan blockResult, done chan struct{}) {
|
|||
// DisconnectBlocks removes all data belonging to blocks in range lower-higher,
|
||||
func (w *SyncWorker) DisconnectBlocks(lower uint32, higher uint32, hashes []string) error {
|
||||
glog.Infof("sync: disconnecting blocks %d-%d", lower, higher)
|
||||
// if the chain is ChainBitcoinType, always use DisconnectBlockRange
|
||||
if w.chain.GetChainParser().GetChainType() == bchain.ChainBitcoinType {
|
||||
ct := w.chain.GetChainParser().GetChainType()
|
||||
if ct == bchain.ChainBitcoinType {
|
||||
return w.db.DisconnectBlockRangeBitcoinType(lower, higher)
|
||||
} else if ct == bchain.ChainEthereumType {
|
||||
return w.db.DisconnectBlockRangeEthereumType(lower, higher)
|
||||
}
|
||||
blocks := make([]*bchain.Block, len(hashes))
|
||||
var err error
|
||||
// try to get all blocks first to see if we can avoid full scan
|
||||
for i, hash := range hashes {
|
||||
blocks[i], err = w.chain.GetBlock(hash, 0)
|
||||
if err != nil {
|
||||
// cannot get a block, we must do full range scan
|
||||
return w.db.DisconnectBlockRangeNonUTXO(lower, higher)
|
||||
}
|
||||
}
|
||||
// got all blocks to be disconnected, disconnect them one after another
|
||||
for i, block := range blocks {
|
||||
glog.Info("Disconnecting block ", (int(higher) - i), " ", block.Hash)
|
||||
if err = w.db.DisconnectBlock(block); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return errors.New("Unknown chain type")
|
||||
}
|
||||
|
|
|
@ -396,7 +396,7 @@ func httpTests(t *testing.T, ts *httptest.Server) {
|
|||
status: http.StatusOK,
|
||||
contentType: "application/json; charset=utf-8",
|
||||
body: []string{
|
||||
`{"hex":"","txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","version":0,"locktime":0,"vin":null,"vout":[{"ValueSat":100000000,"value":0,"n":0,"scriptPubKey":{"hex":"76a914010d39800f86122416e28f485029acf77507169288ac","addresses":null}},{"ValueSat":12345,"value":0,"n":1,"scriptPubKey":{"hex":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","addresses":null}}],"confirmations":2,"time":22549300000,"blocktime":22549300000}`,
|
||||
`{"hex":"","txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","version":0,"locktime":0,"vin":[],"vout":[{"ValueSat":100000000,"value":0,"n":0,"scriptPubKey":{"hex":"76a914010d39800f86122416e28f485029acf77507169288ac","addresses":null}},{"ValueSat":12345,"value":0,"n":1,"scriptPubKey":{"hex":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","addresses":null}}],"confirmations":2,"time":22549300000,"blocktime":22549300000}`,
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
|
@ -66,6 +66,7 @@ func GetTestBitcoinTypeBlock1(parser bchain.BlockChainParser) *bchain.Block {
|
|||
Txs: []bchain.Tx{
|
||||
bchain.Tx{
|
||||
Txid: TxidB1T1,
|
||||
Vin: []bchain.Vin{},
|
||||
Vout: []bchain.Vout{
|
||||
bchain.Vout{
|
||||
N: 0,
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue