Store data in addresses column in more compact way

pull/105/head
Martin Boehm 2019-01-03 17:19:56 +01:00
parent 4e040cb1f0
commit 2552a429e8
9 changed files with 317 additions and 237 deletions

View File

@ -51,25 +51,29 @@ func (w *Worker) getAddressesFromVout(vout *bchain.Vout) (bchain.AddressDescript
// setSpendingTxToVout is helper function, that finds transaction that spent given output and sets it to the output
// there is no direct index for the operation, it must be found using addresses -> txaddresses -> tx
func (w *Worker) setSpendingTxToVout(vout *Vout, txid string, height uint32) error {
err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, ^uint32(0), func(t string, index int32, isOutput bool) error {
if isOutput == false {
tsp, err := w.db.GetTxAddresses(t)
if err != nil {
return err
} else if tsp == nil {
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
} else if len(tsp.Inputs) > int(index) {
if tsp.Inputs[index].ValueSat.Cmp((*big.Int)(vout.ValueSat)) == 0 {
spentTx, spentHeight, err := w.txCache.GetTransaction(t)
if err != nil {
glog.Warning("Tx ", t, ": not found")
} else {
if len(spentTx.Vin) > int(index) {
if spentTx.Vin[index].Txid == txid {
vout.SpentTxID = t
vout.SpentHeight = int(spentHeight)
vout.SpentIndex = int(index)
return &db.StopIteration{}
err := w.db.GetAddrDescTransactions(vout.AddrDesc, height, ^uint32(0), func(t string, height uint32, indexes []int32) error {
for _, index := range indexes {
// take only inputs
if index < 0 {
index = ^index
tsp, err := w.db.GetTxAddresses(t)
if err != nil {
return err
} else if tsp == nil {
glog.Warning("DB inconsistency: tx ", t, ": not found in txAddresses")
} else if len(tsp.Inputs) > int(index) {
if tsp.Inputs[index].ValueSat.Cmp((*big.Int)(vout.ValueSat)) == 0 {
spentTx, spentHeight, err := w.txCache.GetTransaction(t)
if err != nil {
glog.Warning("Tx ", t, ": not found")
} else {
if len(spentTx.Vin) > int(index) {
if spentTx.Vin[index].Txid == txid {
vout.SpentTxID = t
vout.SpentHeight = int(spentHeight)
vout.SpentIndex = int(index)
return &db.StopIteration{}
}
}
}
}
@ -299,35 +303,50 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height uint32,
func (w *Worker) getAddressTxids(addrDesc bchain.AddressDescriptor, mempool bool, filter *AddressFilter) ([]string, error) {
var err error
txids := make([]string, 0, 4)
addFilteredTxid := func(txid string, vout int32, isOutput bool) error {
if filter.Vout == AddressFilterVoutOff ||
(filter.Vout == AddressFilterVoutInputs && !isOutput) ||
(filter.Vout == AddressFilterVoutOutputs && isOutput) ||
(vout == int32(filter.Vout)) {
var callback db.GetTransactionsCallback
if filter.Vout == AddressFilterVoutOff {
callback = func(txid string, height uint32, indexes []int32) error {
txids = append(txids, txid)
return nil
}
} else {
callback = func(txid string, height uint32, indexes []int32) error {
for _, index := range indexes {
vout := index
if vout < 0 {
vout = ^vout
}
if (filter.Vout == AddressFilterVoutInputs && index < 0) ||
(filter.Vout == AddressFilterVoutOutputs && index >= 0) ||
(vout == int32(filter.Vout)) {
txids = append(txids, txid)
break
}
}
return nil
}
return nil
}
if mempool {
uniqueTxs := make(map[string]struct{})
o, err := w.chain.GetMempoolTransactionsForAddrDesc(addrDesc)
if err != nil {
return nil, err
}
for _, m := range o {
vout := m.Vout
isOutput := true
if vout < 0 {
isOutput = false
vout = ^vout
if _, found := uniqueTxs[m.Txid]; !found {
l := len(txids)
callback(m.Txid, 0, []int32{m.Vout})
if len(txids) > l {
uniqueTxs[m.Txid] = struct{}{}
}
}
addFilteredTxid(m.Txid, vout, isOutput)
}
} else {
to := filter.ToHeight
if to == 0 {
to = ^uint32(0)
}
err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, addFilteredTxid)
err = w.db.GetAddrDescTransactions(addrDesc, filter.FromHeight, to, callback)
if err != nil {
return nil, err
}
@ -454,7 +473,7 @@ func (w *Worker) getEthereumTypeAddressBalances(addrDesc bchain.AddressDescripto
}
if ca != nil {
ba = &db.AddrBalance{
Txs: uint32(ca.EthTxs),
Txs: uint32(ca.TotalTxs),
}
var b *big.Int
b, err = w.chain.EthereumTypeGetBalance(addrDesc)
@ -578,7 +597,6 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", addrDesc)
}
txm = GetUniqueTxids(txm)
// if there are only unconfirmed transactions, there is no paging
if ba == nil {
ba = &db.AddrBalance{}
@ -589,7 +607,6 @@ func (w *Worker) GetAddress(address string, page int, txsOnPage int, option GetA
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v false", addrDesc)
}
txc = GetUniqueTxids(txc)
bestheight, _, err := w.db.GetBestBlock()
if err != nil {
return nil, errors.Annotatef(err, "GetBestBlock")
@ -700,7 +717,6 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
if err != nil {
return nil, errors.Annotatef(err, "getAddressTxids %v true", address)
}
txm = GetUniqueTxids(txm)
mc := make([]*bchain.Tx, len(txm))
for i, txid := range txm {
// get mempool txs and process their inputs to detect spends between mempool txs
@ -746,9 +762,12 @@ func (w *Worker) GetAddressUtxo(address string, onlyConfirmed bool) ([]AddressUt
// ba can be nil if the address is only in mempool!
if ba != nil && ba.BalanceSat.Uint64() > 0 {
outpoints := make([]bchain.Outpoint, 0, 8)
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, vout int32, isOutput bool) error {
if isOutput {
outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: vout})
err = w.db.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error {
for _, index := range indexes {
// take only outputs
if index >= 0 {
outpoints = append(outpoints, bchain.Outpoint{Txid: txid, Vout: index})
}
}
return nil
})

View File

@ -44,8 +44,6 @@ var (
blockUntil = flag.Int("blockuntil", -1, "height of the final block")
rollbackHeight = flag.Int("rollback", -1, "rollback to the given height and quit")
queryAddress = flag.String("address", "", "query contents of this address")
synchronize = flag.Bool("sync", false, "synchronizes until tip, if together with zeromq, keeps index synchronized")
repair = flag.Bool("repair", false, "repair the database")
prof = flag.String("prof", "", "http server binding [address]:port of the interface to profiling data /debug/pprof/ (default no profiling)")
@ -319,14 +317,8 @@ func main() {
}
height := uint32(*blockFrom)
until := uint32(*blockUntil)
address := *queryAddress
if address != "" {
if err = index.GetTransactions(address, height, until, printResult); err != nil {
glog.Error("GetTransactions ", err)
return
}
} else if !*synchronize {
if !*synchronize {
if err = syncWorker.ConnectBlocksParallel(height, until); err != nil {
glog.Error("connectBlocksParallel ", err)
return

View File

@ -16,7 +16,7 @@ import (
type bulkAddresses struct {
bi BlockInfo
addresses map[string][]outpoint
addresses addressesMap
}
// BulkConnect is used to connect blocks in bulk, faster but if interrupted inconsistent way
@ -40,17 +40,21 @@ const (
// InitBulkConnect initializes bulk connect and switches DB to inconsistent state
func (d *RocksDB) InitBulkConnect() (*BulkConnect, error) {
bc := &BulkConnect{
b := &BulkConnect{
d: d,
chainType: d.chainParser.GetChainType(),
txAddressesMap: make(map[string]*TxAddresses),
balances: make(map[string]*AddrBalance),
}
if err := d.SetInconsistentState(true); err != nil {
return nil, err
if b.chainType == bchain.ChainBitcoinType {
if err := d.SetInconsistentState(true); err != nil {
return nil, err
}
glog.Info("rocksdb: bulk connect init, db set to inconsistent state")
} else {
glog.Info("rocksdb: bulk connect init")
}
glog.Info("rocksdb: bulk connect init, db set to inconsistent state")
return bc, nil
return b, nil
}
func (b *BulkConnect) storeTxAddresses(wb *gorocksdb.WriteBatch, all bool) (int, int, error) {
@ -172,7 +176,7 @@ func (b *BulkConnect) ConnectBlock(block *bchain.Block, storeBlockTxs bool) erro
if b.chainType != bchain.ChainBitcoinType {
return b.d.ConnectBlock(block)
}
addresses := make(map[string][]outpoint)
addresses := make(addressesMap)
if err := b.d.processAddressesBitcoinType(block, addresses, b.txAddressesMap, b.balances); err != nil {
return err
}

View File

@ -208,9 +208,13 @@ func (e *StopIteration) Error() string {
return ""
}
// GetTransactionsCallback is called by GetTransactions/GetAddrDescTransactions for each found tx
// indexes contain array of indexes (input negative, output positive) in tx where is given address
type GetTransactionsCallback func(txid string, height uint32, indexes []int32) error
// GetTransactions finds all input/output transactions for address
// Transaction are passed to callback function.
func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, fn func(txid string, vout int32, isOutput bool) error) (err error) {
func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, fn GetTransactionsCallback) (err error) {
if glog.V(1) {
glog.Infof("rocksdb: address get %s %d-%d ", address, lower, higher)
}
@ -223,47 +227,54 @@ func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, f
// GetAddrDescTransactions finds all input/output transactions for address descriptor
// Transaction are passed to callback function in the order from newest block to the oldest
func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, lower uint32, higher uint32, fn func(txid string, vout int32, isOutput bool) error) (err error) {
kstart := packAddressKey(addrDesc, higher)
kstop := packAddressKey(addrDesc, lower)
func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, lower uint32, higher uint32, fn GetTransactionsCallback) (err error) {
txidUnpackedLen := d.chainParser.PackedTxidLen()
startKey := packAddressKey(addrDesc, higher)
stopKey := packAddressKey(addrDesc, lower)
indexes := make([]int32, 0, 16)
it := d.db.NewIteratorCF(d.ro, d.cfh[cfAddresses])
defer it.Close()
for it.Seek(kstart); it.Valid(); it.Next() {
for it.Seek(startKey); it.Valid(); it.Next() {
key := it.Key().Data()
val := it.Value().Data()
if bytes.Compare(key, kstop) > 0 {
if bytes.Compare(key, stopKey) > 0 {
break
}
outpoints, err := d.unpackOutpoints(val)
if glog.V(2) {
glog.Infof("rocksdb: addresses %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
}
_, height, err := unpackAddressKey(key)
if err != nil {
return err
}
if glog.V(2) {
glog.Infof("rocksdb: output %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
}
for _, o := range outpoints {
var vout int32
var isOutput bool
if o.index < 0 {
vout = int32(^o.index)
isOutput = false
} else {
vout = int32(o.index)
isOutput = true
}
tx, err := d.chainParser.UnpackTxid(o.btxID)
for len(val) > txidUnpackedLen {
tx, err := d.chainParser.UnpackTxid(val[:txidUnpackedLen])
if err != nil {
return err
}
if err := fn(tx, vout, isOutput); err != nil {
indexes = indexes[:0]
val = val[txidUnpackedLen:]
for {
index, l := unpackVarint32(val)
indexes = append(indexes, index>>1)
val = val[l:]
if index&1 == 1 {
break
} else if len(val) == 0 {
glog.Warningf("rocksdb: addresses contain incorrect data %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
break
}
}
if err := fn(tx, height, indexes); err != nil {
if _, ok := err.(*StopIteration); ok {
return nil
}
return err
}
}
if len(val) != 0 {
glog.Warningf("rocksdb: addresses contain incorrect data %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
}
}
return nil
}
@ -287,7 +298,7 @@ func (d *RocksDB) ConnectBlock(block *bchain.Block) error {
if err := d.writeHeightFromBlock(wb, block, opInsert); err != nil {
return err
}
addresses := make(map[string][]outpoint)
addresses := make(addressesMap)
if chainType == bchain.ChainBitcoinType {
txAddressesMap := make(map[string]*TxAddresses)
balances := make(map[string]*AddrBalance)
@ -327,6 +338,16 @@ func (d *RocksDB) ConnectBlock(block *bchain.Block) error {
// Addresses index
type txIndexes struct {
btxID []byte
indexes []int32
}
// addressesMap is a map of addresses in a block
// each address contains a slice of transactions with indexes where the address appears
// slice is used instead of map so that order is defined and also search in case of few items
type addressesMap map[string][]txIndexes
type outpoint struct {
btxID []byte
index int32
@ -391,7 +412,7 @@ func (d *RocksDB) GetAndResetConnectBlockStats() string {
return s
}
func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map[string][]outpoint, txAddressesMap map[string]*TxAddresses, balances map[string]*AddrBalance) error {
func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses addressesMap, txAddressesMap map[string]*TxAddresses, balances map[string]*AddrBalance) error {
blockTxIDs := make([][]byte, len(block.Txs))
blockTxAddresses := make([]*TxAddresses, len(block.Txs))
// first process all outputs so that inputs can point to txs in this block
@ -423,16 +444,6 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map
}
tao.AddrDesc = addrDesc
strAddrDesc := string(addrDesc)
// check that the address was used already in this block
o, processed := addresses[strAddrDesc]
if processed {
// check that the address was already used in this tx
processed = processedInTx(o, btxID)
}
addresses[strAddrDesc] = append(o, outpoint{
btxID: btxID,
index: int32(i),
})
ab, e := balances[strAddrDesc]
if !e {
ab, err = d.GetAddrDescBalance(addrDesc)
@ -447,11 +458,11 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map
} else {
d.cbs.balancesHit++
}
// add number of trx in balance only once, address can be multiple times in tx
if !processed {
ab.BalanceSat.Add(&ab.BalanceSat, &output.ValueSat)
counted := addToAddressesMap(addresses, strAddrDesc, btxID, int32(i))
if !counted {
ab.Txs++
}
ab.BalanceSat.Add(&ab.BalanceSat, &output.ValueSat)
}
}
// process inputs
@ -507,16 +518,6 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map
continue
}
strAddrDesc := string(ot.AddrDesc)
// check that the address was used already in this block
o, processed := addresses[strAddrDesc]
if processed {
// check that the address was already used in this tx
processed = processedInTx(o, spendingTxid)
}
addresses[strAddrDesc] = append(o, outpoint{
btxID: spendingTxid,
index: ^int32(i),
})
ab, e := balances[strAddrDesc]
if !e {
ab, err = d.GetAddrDescBalance(ot.AddrDesc)
@ -531,8 +532,8 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map
} else {
d.cbs.balancesHit++
}
// add number of trx in balance only once, address can be multiple times in tx
if !processed {
counted := addToAddressesMap(addresses, strAddrDesc, spendingTxid, ^int32(i))
if !counted {
ab.Txs++
}
ab.BalanceSat.Sub(&ab.BalanceSat, &ot.ValueSat)
@ -545,20 +546,31 @@ func (d *RocksDB) processAddressesBitcoinType(block *bchain.Block, addresses map
return nil
}
func processedInTx(o []outpoint, btxID []byte) bool {
for _, op := range o {
if bytes.Equal(btxID, op.btxID) {
return true
func addToAddressesMap(addresses addressesMap, strAddrDesc string, btxID []byte, index int32) bool {
// check that the address was used already in this block
// if not found, it has certainly not been counted
at, found := addresses[strAddrDesc]
if found {
// check that the address was already used in this tx
for i, t := range at {
if bytes.Equal(btxID, t.btxID) {
at[i].indexes = append(at[i].indexes, index)
return true
}
}
}
addresses[strAddrDesc] = append(at, txIndexes{
btxID: btxID,
indexes: []int32{index},
})
return false
}
func (d *RocksDB) storeAddresses(wb *gorocksdb.WriteBatch, height uint32, addresses map[string][]outpoint) error {
for addrDesc, outpoints := range addresses {
func (d *RocksDB) storeAddresses(wb *gorocksdb.WriteBatch, height uint32, addresses addressesMap) error {
for addrDesc, txi := range addresses {
ba := bchain.AddressDescriptor(addrDesc)
key := packAddressKey(ba, height)
val := d.packOutpoints(outpoints)
val := d.packTxIndexes(txi)
wb.PutCF(d.cfh[cfAddresses], key, val)
}
return nil
@ -813,6 +825,23 @@ func unpackTxOutput(to *TxOutput, buf []byte) int {
return l + al
}
func (d *RocksDB) packTxIndexes(txi []txIndexes) []byte {
buf := make([]byte, 0, 32)
bvout := make([]byte, vlq.MaxLen32)
for _, t := range txi {
buf = append(buf, []byte(t.btxID)...)
for i, index := range t.indexes {
index <<= 1
if i == len(t.indexes)-1 {
index |= 1
}
l := packVarint32(index, bvout)
buf = append(buf, bvout[:l]...)
}
}
return buf
}
func (d *RocksDB) packOutpoints(outpoints []outpoint) []byte {
buf := make([]byte, 0, 32)
bvout := make([]byte, vlq.MaxLen32)
@ -824,22 +853,6 @@ func (d *RocksDB) packOutpoints(outpoints []outpoint) []byte {
return buf
}
func (d *RocksDB) unpackOutpoints(buf []byte) ([]outpoint, error) {
txidUnpackedLen := d.chainParser.PackedTxidLen()
outpoints := make([]outpoint, 0, 8)
for i := 0; i < len(buf); {
btxID := append([]byte(nil), buf[i:i+txidUnpackedLen]...)
i += txidUnpackedLen
vout, voutLen := unpackVarint32(buf[i:])
i += voutLen
outpoints = append(outpoints, outpoint{
btxID: btxID,
index: vout,
})
}
return outpoints, nil
}
func (d *RocksDB) unpackNOutpoints(buf []byte) ([]outpoint, int, error) {
txidUnpackedLen := d.chainParser.PackedTxidLen()
n, p := unpackVaruint(buf)

View File

@ -18,10 +18,11 @@ type AddrContract struct {
Txs uint
}
// AddrContracts is array of contracts with
// AddrContracts contains number of transactions and contracts for an address
type AddrContracts struct {
EthTxs uint
Contracts []AddrContract
TotalTxs uint
NonContractTxs uint
Contracts []AddrContract
}
func (d *RocksDB) storeAddressContracts(wb *gorocksdb.WriteBatch, acm map[string]*AddrContracts) error {
@ -29,11 +30,13 @@ func (d *RocksDB) storeAddressContracts(wb *gorocksdb.WriteBatch, acm map[string
varBuf := make([]byte, vlq.MaxLen64)
for addrDesc, acs := range acm {
// address with 0 contracts is removed from db - happens on disconnect
if acs == nil || (acs.EthTxs == 0 && len(acs.Contracts) == 0) {
if acs == nil || (acs.NonContractTxs == 0 && len(acs.Contracts) == 0) {
wb.DeleteCF(d.cfh[cfAddressContracts], bchain.AddressDescriptor(addrDesc))
} else {
buf = buf[:0]
l := packVaruint(acs.EthTxs, varBuf)
l := packVaruint(acs.TotalTxs, varBuf)
buf = append(buf, varBuf[:l]...)
l = packVaruint(acs.NonContractTxs, varBuf)
buf = append(buf, varBuf[:l]...)
for _, ac := range acs.Contracts {
buf = append(buf, ac.Contract...)
@ -57,9 +60,11 @@ func (d *RocksDB) GetAddrDescContracts(addrDesc bchain.AddressDescriptor) (*Addr
if len(buf) == 0 {
return nil, nil
}
c := make([]AddrContract, 0, 4)
et, l := unpackVaruint(buf)
tt, l := unpackVaruint(buf)
buf = buf[l:]
nct, l := unpackVaruint(buf)
buf = buf[l:]
c := make([]AddrContract, 0, 4)
for len(buf) > 0 {
if len(buf) < eth.EthereumTypeAddressDescriptorLen {
return nil, errors.New("Invalid data stored in cfAddressContracts for AddrDesc " + addrDesc.String())
@ -72,7 +77,11 @@ func (d *RocksDB) GetAddrDescContracts(addrDesc bchain.AddressDescriptor) (*Addr
})
buf = buf[eth.EthereumTypeAddressDescriptorLen+l:]
}
return &AddrContracts{EthTxs: et, Contracts: c}, nil
return &AddrContracts{
TotalTxs: tt,
NonContractTxs: nct,
Contracts: c,
}, nil
}
func findContractInAddressContracts(contract bchain.AddressDescriptor, contracts []AddrContract) (int, bool) {
@ -84,7 +93,7 @@ func findContractInAddressContracts(contract bchain.AddressDescriptor, contracts
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 {
func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.AddressDescriptor, btxID []byte, index int32, contract bchain.AddressDescriptor, addresses addressesMap, addressContracts map[string]*AddrContracts, addTxCount bool) error {
var err error
strAddrDesc := string(addrDesc)
ac, e := addressContracts[strAddrDesc]
@ -102,7 +111,9 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address
d.cbs.balancesHit++
}
if contract == nil {
ac.EthTxs++
if addTxCount {
ac.NonContractTxs++
}
} else {
// locate the contract and set i to the index in the array of contracts
i, found := findContractInAddressContracts(contract, ac.Contracts)
@ -116,12 +127,14 @@ func (d *RocksDB) addToAddressesAndContractsEthereumType(addrDesc bchain.Address
} else {
index = int32(i + 1)
}
ac.Contracts[i].Txs++
if addTxCount {
ac.Contracts[i].Txs++
}
}
counted := addToAddressesMap(addresses, strAddrDesc, btxID, index)
if !counted {
ac.TotalTxs++
}
addresses[strAddrDesc] = append(addresses[strAddrDesc], outpoint{
btxID: btxID,
index: index,
})
return nil
}
@ -135,7 +148,7 @@ type ethBlockTx struct {
contracts []ethBlockTxContract
}
func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses map[string][]outpoint, addressContracts map[string]*AddrContracts) ([]ethBlockTx, error) {
func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses addressesMap, addressContracts map[string]*AddrContracts) ([]ethBlockTx, error) {
blockTxs := make([]ethBlockTx, len(block.Txs))
for txi, tx := range block.Txs {
btxID, err := d.chainParser.PackTxid(tx.Txid)
@ -144,9 +157,10 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ma
}
blockTx := &blockTxs[txi]
blockTx.btxID = btxID
var from, to bchain.AddressDescriptor
// there is only one output address in EthereumType transaction, store it in format txid 0
if len(tx.Vout) == 1 && len(tx.Vout[0].ScriptPubKey.Addresses) == 1 {
addrDesc, err := d.chainParser.GetAddrDescFromAddress(tx.Vout[0].ScriptPubKey.Addresses[0])
to, err = d.chainParser.GetAddrDescFromAddress(tx.Vout[0].ScriptPubKey.Addresses[0])
if err != nil {
// do not log ErrAddressMissing, transactions can be without to address (for example eth contracts)
if err != bchain.ErrAddressMissing {
@ -154,24 +168,24 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ma
}
continue
}
if err = d.addToAddressesAndContractsEthereumType(addrDesc, btxID, 0, nil, addresses, addressContracts); err != nil {
if err = d.addToAddressesAndContractsEthereumType(to, btxID, 0, nil, addresses, addressContracts, true); err != nil {
return nil, err
}
blockTx.to = addrDesc
blockTx.to = to
}
// there is only one input address in EthereumType transaction, store it in format txid ^0
if len(tx.Vin) == 1 && len(tx.Vin[0].Addresses) == 1 {
addrDesc, err := d.chainParser.GetAddrDescFromAddress(tx.Vin[0].Addresses[0])
from, err = d.chainParser.GetAddrDescFromAddress(tx.Vin[0].Addresses[0])
if err != nil {
if err != bchain.ErrAddressMissing {
glog.Warningf("rocksdb: addrDesc: %v - height %d, tx %v, input", err, block.Height, tx.Txid)
}
continue
}
if err = d.addToAddressesAndContractsEthereumType(addrDesc, btxID, ^int32(0), nil, addresses, addressContracts); err != nil {
if err = d.addToAddressesAndContractsEthereumType(from, btxID, ^int32(0), nil, addresses, addressContracts, !bytes.Equal(from, to)); err != nil {
return nil, err
}
blockTx.from = addrDesc
blockTx.from = from
}
// store erc20 transfers
erc20, err := d.chainParser.EthereumTypeGetErc20FromTx(&tx)
@ -179,6 +193,7 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ma
glog.Warningf("rocksdb: GetErc20FromTx %v - height %d, tx %v", err, block.Height, tx.Txid)
}
blockTx.contracts = make([]ethBlockTxContract, len(erc20)*2)
j := 0
for i, t := range erc20 {
var contract, from, to bchain.AddressDescriptor
contract, err = d.chainParser.GetAddrDescFromAddress(t.Contract)
@ -192,19 +207,26 @@ func (d *RocksDB) processAddressesEthereumType(block *bchain.Block, addresses ma
glog.Warningf("rocksdb: GetErc20FromTx %v - height %d, tx %v, transfer %v", err, block.Height, tx.Txid, t)
continue
}
if err = d.addToAddressesAndContractsEthereumType(from, btxID, ^int32(i), contract, addresses, addressContracts); err != nil {
if err = d.addToAddressesAndContractsEthereumType(from, btxID, ^int32(i), contract, addresses, addressContracts, true); err != nil {
return nil, err
}
bc := &blockTx.contracts[i*2]
eq := bytes.Equal(from, to)
bc := &blockTx.contracts[j]
j++
bc.addr = from
bc.contract = contract
if err = d.addToAddressesAndContractsEthereumType(to, btxID, int32(i), contract, addresses, addressContracts); err != nil {
if err = d.addToAddressesAndContractsEthereumType(to, btxID, int32(i), contract, addresses, addressContracts, !eq); err != nil {
return nil, err
}
bc = &blockTx.contracts[i*2+1]
bc.addr = to
bc.contract = contract
// add to address to blockTx.contracts only if it is different from from address
if !eq {
bc = &blockTx.contracts[j]
j++
bc.addr = to
bc.contract = contract
}
}
blockTx.contracts = blockTx.contracts[:j]
}
return blockTxs, nil
}
@ -308,7 +330,7 @@ func (d *RocksDB) getBlockTxsEthereumType(height uint32) ([]ethBlockTx, error) {
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{})
addresses := make(map[string]map[string]struct{})
disconnectAddress := func(btxID []byte, addrDesc, contract bchain.AddressDescriptor) error {
var err error
// do not process empty address
@ -316,7 +338,19 @@ func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, heigh
return nil
}
s := string(addrDesc)
addresses[s] = struct{}{}
txid := string(btxID)
// find if tx for this address was already encountered
mtx, ftx := addresses[s]
if !ftx {
mtx = make(map[string]struct{})
mtx[txid] = struct{}{}
addresses[s] = mtx
} else {
_, ftx = mtx[txid]
if !ftx {
mtx[txid] = struct{}{}
}
}
c, fc := contracts[s]
if !fc {
c, err = d.GetAddrDescContracts(addrDesc)
@ -326,9 +360,12 @@ func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, heigh
contracts[s] = c
}
if c != nil {
if !ftx {
c.TotalTxs--
}
if contract == nil {
if c.EthTxs > 0 {
c.EthTxs--
if c.NonContractTxs > 0 {
c.NonContractTxs--
} else {
glog.Warning("AddressContracts ", addrDesc, ", EthTxs would be negative, tx ", hex.EncodeToString(btxID))
}
@ -357,8 +394,11 @@ func (d *RocksDB) disconnectBlockTxsEthereumType(wb *gorocksdb.WriteBatch, heigh
if err := disconnectAddress(blockTx.btxID, blockTx.from, nil); err != nil {
return err
}
if err := disconnectAddress(blockTx.btxID, blockTx.to, nil); err != nil {
return err
// if from==to, tx is counted only once and does not have to be disconnected again
if !bytes.Equal(blockTx.from, blockTx.to) {
if err := disconnectAddress(blockTx.btxID, blockTx.to, nil); err != nil {
return err
}
}
for _, c := range blockTx.contracts {
if err := disconnectAddress(blockTx.btxID, c.addr, c.contract); err != nil {

View File

@ -32,12 +32,11 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
t.Fatal(err)
}
}
// the vout is encoded as signed varint, i.e. value * 2 for positive values, abs(value)*2 + 1 for negative values
if err := checkColumn(d, cfAddresses, []keyPair{
keyPair{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), dbtestdata.EthTxidB1T1 + "01", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr55, 4321000, d), dbtestdata.EthTxidB1T1 + "00" + dbtestdata.EthTxidB1T2 + "02", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr20, 4321000, d), dbtestdata.EthTxidB1T2 + "01" + dbtestdata.EthTxidB1T2 + "03", nil},
keyPair{addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), dbtestdata.EthTxidB1T2 + "00", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}) + txIndexesHex(dbtestdata.EthTxidB1T2, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^1}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{0}), nil},
}); err != nil {
{
t.Fatal(err)
@ -45,10 +44,10 @@ func verifyAfterEthereumTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect boo
}
if err := checkColumn(d, cfAddressContracts, []keyPair{
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "0101", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "0201" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "0101" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "0101", nil},
}); err != nil {
{
t.Fatal(err)
@ -97,17 +96,16 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) {
t.Fatal(err)
}
}
// the vout is encoded as signed varint, i.e. value * 2 for positive values, abs(value)*2 + 1 for negative values
if err := checkColumn(d, cfAddresses, []keyPair{
keyPair{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), dbtestdata.EthTxidB1T1 + "01", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr55, 4321000, d), dbtestdata.EthTxidB1T1 + "00" + dbtestdata.EthTxidB1T2 + "02", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr20, 4321000, d), dbtestdata.EthTxidB1T2 + "01" + dbtestdata.EthTxidB1T2 + "03", nil},
keyPair{addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), dbtestdata.EthTxidB1T2 + "00", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr55, 4321001, d), dbtestdata.EthTxidB2T1 + "01" + dbtestdata.EthTxidB2T2 + "05" + dbtestdata.EthTxidB2T2 + "02", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), dbtestdata.EthTxidB2T1 + "00", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr4b, 4321001, d), dbtestdata.EthTxidB2T2 + "01" + dbtestdata.EthTxidB2T2 + "02" + dbtestdata.EthTxidB2T2 + "05" + dbtestdata.EthTxidB2T2 + "04" + dbtestdata.EthTxidB2T2 + "03", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), dbtestdata.EthTxidB2T2 + "03" + dbtestdata.EthTxidB2T2 + "04", nil},
keyPair{addressKeyHex(dbtestdata.EthAddrContract47, 4321001, d), dbtestdata.EthTxidB2T2 + "00", nil},
keyPair{addressKeyHex(dbtestdata.EthAddr3e, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T1, []int32{^0}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr55, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T1, []int32{0}) + txIndexesHex(dbtestdata.EthTxidB1T2, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr20, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{^0, ^1}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddrContract4a, 4321000, d), txIndexesHex(dbtestdata.EthTxidB1T2, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr55, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T1, []int32{^0}) + txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^2, 1}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr9f, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T1, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr4b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^0, 1, ^2, 2, ^1}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddr7b, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{^1, 2}), nil},
keyPair{addressKeyHex(dbtestdata.EthAddrContract47, 4321001, d), txIndexesHex(dbtestdata.EthTxidB2T2, []int32{0}), nil},
}); err != nil {
{
t.Fatal(err)
@ -115,14 +113,14 @@ func verifyAfterEthereumTypeBlock2(t *testing.T, d *RocksDB) {
}
if err := checkColumn(d, cfAddressContracts, []keyPair{
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser), "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser), "00" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser), "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr3e, d.chainParser), "0101", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr55, d.chainParser), "0402" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr20, d.chainParser), "0101" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser), "0101", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr9f, d.chainParser), "0101", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr4b, d.chainParser), "0101" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "02" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "02", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddr7b, d.chainParser), "0100" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract4a, d.chainParser) + "01" + dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract0d, d.chainParser) + "01", nil},
keyPair{dbtestdata.AddressToPubKeyHex(dbtestdata.EthAddrContract47, d.chainParser), "0101", nil},
}); err != nil {
{
t.Fatal(err)
@ -184,14 +182,14 @@ func TestRocksDB_Index_EthereumType(t *testing.T) {
verifyAfterEthereumTypeBlock2(t, d)
// get transactions for various addresses / low-high ranges
verifyGetTransactions(t, d, "0x"+dbtestdata.EthAddr55, 0, 10000000, []txidVoutOutput{
txidVoutOutput{"0x" + dbtestdata.EthTxidB2T1, 0, false},
txidVoutOutput{"0x" + dbtestdata.EthTxidB2T2, 2, false},
txidVoutOutput{"0x" + dbtestdata.EthTxidB2T2, 1, true},
txidVoutOutput{"0x" + dbtestdata.EthTxidB1T1, 0, true},
txidVoutOutput{"0x" + dbtestdata.EthTxidB1T2, 1, true},
verifyGetTransactions(t, d, "0x"+dbtestdata.EthAddr55, 0, 10000000, []txidIndex{
{"0x" + dbtestdata.EthTxidB2T1, ^0},
{"0x" + dbtestdata.EthTxidB2T2, ^2},
{"0x" + dbtestdata.EthTxidB2T2, 1},
{"0x" + dbtestdata.EthTxidB1T1, 0},
{"0x" + dbtestdata.EthTxidB1T2, 1},
}, nil)
verifyGetTransactions(t, d, "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eBad", 500000, 1000000, []txidVoutOutput{}, errors.New("Address missing"))
verifyGetTransactions(t, d, "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eBad", 500000, 1000000, []txidIndex{}, errors.New("Address missing"))
// GetBestBlock
height, hash, err := d.GetBestBlock()

View File

@ -103,6 +103,19 @@ func addressKeyHex(a string, height uint32, d *RocksDB) string {
return dbtestdata.AddressToPubKeyHex(a, d.chainParser) + uintToHex(^height)
}
func txIndexesHex(tx string, indexes []int32) string {
buf := make([]byte, vlq.MaxLen32)
for i, index := range indexes {
index <<= 1
if i == len(indexes)-1 {
index |= 1
}
l := packVarint32(index, buf)
tx += hex.EncodeToString(buf[:l])
}
return tx
}
// keyPair is used to compare given key value in DB with expected
// for more complicated compares it is possible to specify CompareFunc
type keyPair struct {
@ -172,11 +185,11 @@ func verifyAfterBitcoinTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect bool
}
// the vout is encoded as signed varint, i.e. value * 2 for non negative values
if err := checkColumn(d, cfAddresses, []keyPair{
keyPair{addressKeyHex(dbtestdata.Addr1, 225493, d), dbtestdata.TxidB1T1 + "00", nil},
keyPair{addressKeyHex(dbtestdata.Addr2, 225493, d), dbtestdata.TxidB1T1 + "02", nil},
keyPair{addressKeyHex(dbtestdata.Addr3, 225493, d), dbtestdata.TxidB1T2 + "00", nil},
keyPair{addressKeyHex(dbtestdata.Addr4, 225493, d), dbtestdata.TxidB1T2 + "02", nil},
keyPair{addressKeyHex(dbtestdata.Addr5, 225493, d), dbtestdata.TxidB1T2 + "04", nil},
keyPair{addressKeyHex(dbtestdata.Addr1, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr2, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr3, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr4, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr5, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{2}), nil},
}); err != nil {
{
t.Fatal(err)
@ -257,20 +270,20 @@ func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) {
}
}
if err := checkColumn(d, cfAddresses, []keyPair{
keyPair{addressKeyHex(dbtestdata.Addr1, 225493, d), dbtestdata.TxidB1T1 + "00", nil},
keyPair{addressKeyHex(dbtestdata.Addr2, 225493, d), dbtestdata.TxidB1T1 + "02", nil},
keyPair{addressKeyHex(dbtestdata.Addr3, 225493, d), dbtestdata.TxidB1T2 + "00", nil},
keyPair{addressKeyHex(dbtestdata.Addr4, 225493, d), dbtestdata.TxidB1T2 + "02", nil},
keyPair{addressKeyHex(dbtestdata.Addr5, 225493, d), dbtestdata.TxidB1T2 + "04", nil},
keyPair{addressKeyHex(dbtestdata.Addr6, 225494, d), dbtestdata.TxidB2T1 + "00" + dbtestdata.TxidB2T2 + "01", nil},
keyPair{addressKeyHex(dbtestdata.Addr7, 225494, d), dbtestdata.TxidB2T1 + "02", nil},
keyPair{addressKeyHex(dbtestdata.Addr8, 225494, d), dbtestdata.TxidB2T2 + "00", nil},
keyPair{addressKeyHex(dbtestdata.Addr9, 225494, d), dbtestdata.TxidB2T2 + "02", nil},
keyPair{addressKeyHex(dbtestdata.Addr3, 225494, d), dbtestdata.TxidB2T1 + "01", nil},
keyPair{addressKeyHex(dbtestdata.Addr2, 225494, d), dbtestdata.TxidB2T1 + "03", nil},
keyPair{addressKeyHex(dbtestdata.Addr5, 225494, d), dbtestdata.TxidB2T3 + "00" + dbtestdata.TxidB2T3 + "01", nil},
keyPair{addressKeyHex(dbtestdata.AddrA, 225494, d), dbtestdata.TxidB2T4 + "00", nil},
keyPair{addressKeyHex(dbtestdata.Addr4, 225494, d), dbtestdata.TxidB2T2 + "03", nil},
keyPair{addressKeyHex(dbtestdata.Addr1, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr2, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr3, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr4, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr5, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{2}), nil},
keyPair{addressKeyHex(dbtestdata.Addr6, 225494, d), txIndexesHex(dbtestdata.TxidB2T1, []int32{0}) + txIndexesHex(dbtestdata.TxidB2T2, []int32{^0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr7, 225494, d), txIndexesHex(dbtestdata.TxidB2T1, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr8, 225494, d), txIndexesHex(dbtestdata.TxidB2T2, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr9, 225494, d), txIndexesHex(dbtestdata.TxidB2T2, []int32{1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr3, 225494, d), txIndexesHex(dbtestdata.TxidB2T1, []int32{^0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr2, 225494, d), txIndexesHex(dbtestdata.TxidB2T1, []int32{^1}), nil},
keyPair{addressKeyHex(dbtestdata.Addr5, 225494, d), txIndexesHex(dbtestdata.TxidB2T3, []int32{0, ^0}), nil},
keyPair{addressKeyHex(dbtestdata.AddrA, 225494, d), txIndexesHex(dbtestdata.TxidB2T4, []int32{0}), nil},
keyPair{addressKeyHex(dbtestdata.Addr4, 225494, d), txIndexesHex(dbtestdata.TxidB2T2, []int32{^1}), nil},
}); err != nil {
{
t.Fatal(err)
@ -373,16 +386,17 @@ func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) {
}
}
type txidVoutOutput struct {
txid string
vout int32
isOutput bool
type txidIndex struct {
txid string
index int32
}
func verifyGetTransactions(t *testing.T, d *RocksDB, addr string, low, high uint32, wantTxids []txidVoutOutput, wantErr error) {
gotTxids := make([]txidVoutOutput, 0)
addToTxids := func(txid string, vout int32, isOutput bool) error {
gotTxids = append(gotTxids, txidVoutOutput{txid, vout, isOutput})
func verifyGetTransactions(t *testing.T, d *RocksDB, addr string, low, high uint32, wantTxids []txidIndex, wantErr error) {
gotTxids := make([]txidIndex, 0)
addToTxids := func(txid string, height uint32, indexes []int32) error {
for _, index := range indexes {
gotTxids = append(gotTxids, txidIndex{txid, index})
}
return nil
}
if err := d.GetTransactions(addr, low, high, addToTxids); err != nil {
@ -456,21 +470,21 @@ func TestRocksDB_Index_BitcoinType(t *testing.T) {
verifyAfterBitcoinTypeBlock2(t, d)
// get transactions for various addresses / low-high ranges
verifyGetTransactions(t, d, dbtestdata.Addr2, 0, 1000000, []txidVoutOutput{
txidVoutOutput{dbtestdata.TxidB2T1, 1, false},
txidVoutOutput{dbtestdata.TxidB1T1, 1, true},
verifyGetTransactions(t, d, dbtestdata.Addr2, 0, 1000000, []txidIndex{
{dbtestdata.TxidB2T1, ^1},
{dbtestdata.TxidB1T1, 1},
}, nil)
verifyGetTransactions(t, d, dbtestdata.Addr2, 225493, 225493, []txidVoutOutput{
txidVoutOutput{dbtestdata.TxidB1T1, 1, true},
verifyGetTransactions(t, d, dbtestdata.Addr2, 225493, 225493, []txidIndex{
{dbtestdata.TxidB1T1, 1},
}, nil)
verifyGetTransactions(t, d, dbtestdata.Addr2, 225494, 1000000, []txidVoutOutput{
txidVoutOutput{dbtestdata.TxidB2T1, 1, false},
verifyGetTransactions(t, d, dbtestdata.Addr2, 225494, 1000000, []txidIndex{
{dbtestdata.TxidB2T1, ^1},
}, nil)
verifyGetTransactions(t, d, dbtestdata.Addr2, 500000, 1000000, []txidVoutOutput{}, nil)
verifyGetTransactions(t, d, dbtestdata.Addr8, 0, 1000000, []txidVoutOutput{
txidVoutOutput{dbtestdata.TxidB2T2, 0, true},
verifyGetTransactions(t, d, dbtestdata.Addr2, 500000, 1000000, []txidIndex{}, nil)
verifyGetTransactions(t, d, dbtestdata.Addr8, 0, 1000000, []txidIndex{
{dbtestdata.TxidB2T2, 0},
}, nil)
verifyGetTransactions(t, d, "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eBad", 500000, 1000000, []txidVoutOutput{}, errors.New("checksum mismatch"))
verifyGetTransactions(t, d, "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eBad", 500000, 1000000, []txidIndex{}, errors.New("checksum mismatch"))
// GetBestBlock
height, hash, err := d.GetBestBlock()

View File

@ -216,7 +216,7 @@ func (s *SocketIoServer) getAddressTxids(addr []string, opts *addrOpts) (res res
lower, higher := uint32(opts.End), uint32(opts.Start)
for _, address := range addr {
if !opts.QueryMempoolOnly {
err = s.db.GetTransactions(address, lower, higher, func(txid string, vout int32, isOutput bool) error {
err = s.db.GetTransactions(address, lower, higher, func(txid string, height uint32, indexes []int32) error {
txids = append(txids, txid)
return nil
})

View File

@ -16,7 +16,7 @@
<td class="data">{{formatAmount $addr.BalanceSat}} {{$cs}}</td>
</tr>
<tr>
<td>Non-contract Transactions</td>
<td>Transactions</td>
<td class="data">{{$addr.TxApperances}}</td>
</tr>
<tr>