Compare commits
15 Commits
deepcrayon
...
utxocheck
Author | SHA1 | Date |
---|---|---|
Martin Boehm | b6e3f26eb2 | |
Martin Boehm | 72e48ce658 | |
Martin Boehm | 9cfe1f3d25 | |
Martin Boehm | 839f67f6eb | |
Martin Boehm | 267c2b22c1 | |
Martin Boehm | 5a801ed038 | |
Martin Boehm | 3c90059994 | |
Martin Boehm | b4d0e2e819 | |
Martin Boehm | a5a5edef9d | |
Martin Boehm | 923234e97d | |
Vladyslav Burzakovskyy | 466d89a670 | |
Martin Boehm | 358858b418 | |
Martin Boehm | f903a1d6f9 | |
Martin Boehm | 7df8f05b31 | |
Martin Boehm | af1d3fceaa |
|
@ -181,12 +181,9 @@ func (w *Worker) GetTransactionFromBchainTx(bchainTx *bchain.Tx, height int, spe
|
||||||
return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
|
return nil, errors.Annotatef(err, "txCache.GetTransaction %v", bchainVin.Txid)
|
||||||
}
|
}
|
||||||
// mempool transactions are not in TxAddresses but confirmed should be there, log a problem
|
// mempool transactions are not in TxAddresses but confirmed should be there, log a problem
|
||||||
if bchainTx.Confirmations > 0 {
|
// ignore when Confirmations==1, it may be just a timing problem
|
||||||
inSync, _, _ := w.is.GetSyncState()
|
if bchainTx.Confirmations > 1 {
|
||||||
// backend can report tx as confirmed, however blockbook is still syncing (!inSync), in this case do not log a problem
|
glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses, confirmations ", bchainTx.Confirmations)
|
||||||
if bchainTx.Confirmations != 1 || inSync {
|
|
||||||
glog.Warning("DB inconsistency: tx ", bchainVin.Txid, ": not found in txAddresses")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if len(otx.Vout) > int(vin.Vout) {
|
if len(otx.Vout) > int(vin.Vout) {
|
||||||
vout := &otx.Vout[vin.Vout]
|
vout := &otx.Vout[vin.Vout]
|
||||||
|
@ -652,7 +649,12 @@ func (w *Worker) txFromTxid(txid string, bestheight uint32, option AccountDetail
|
||||||
func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressDescriptor, string, error) {
|
func (w *Worker) getAddrDescAndNormalizeAddress(address string) (bchain.AddressDescriptor, string, error) {
|
||||||
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
addrDesc, err := w.chainParser.GetAddrDescFromAddress(address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, "", NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
var errAd error
|
||||||
|
// try if the address is not address descriptor converted to string
|
||||||
|
addrDesc, errAd = bchain.AddressDescriptorFromString(address)
|
||||||
|
if errAd != nil {
|
||||||
|
return nil, "", NewAPIError(fmt.Sprintf("Invalid address, %v", err), true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// convert the address to the format defined by the parser
|
// convert the address to the format defined by the parser
|
||||||
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
|
addresses, _, err := w.chainParser.GetAddressesFromAddrDesc(addrDesc)
|
||||||
|
@ -945,6 +947,7 @@ func (w *Worker) setFiatRateToBalanceHistories(histories BalanceHistories, curre
|
||||||
|
|
||||||
// GetBalanceHistory returns history of balance for given address
|
// GetBalanceHistory returns history of balance for given address
|
||||||
func (w *Worker) GetBalanceHistory(address string, fromTimestamp, toTimestamp int64, currencies []string, groupBy uint32) (BalanceHistories, error) {
|
func (w *Worker) GetBalanceHistory(address string, fromTimestamp, toTimestamp int64, currencies []string, groupBy uint32) (BalanceHistories, error) {
|
||||||
|
currencies = removeEmpty(currencies)
|
||||||
bhs := make(BalanceHistories, 0)
|
bhs := make(BalanceHistories, 0)
|
||||||
start := time.Now()
|
start := time.Now()
|
||||||
addrDesc, _, err := w.getAddrDescAndNormalizeAddress(address)
|
addrDesc, _, err := w.getAddrDescAndNormalizeAddress(address)
|
||||||
|
@ -1155,7 +1158,7 @@ func (w *Worker) GetBlocks(page int, blocksOnPage int) (*Blocks, error) {
|
||||||
return r, nil
|
return r, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// removeEmty removes empty strings from a slice
|
// removeEmpty removes empty strings from a slice
|
||||||
func removeEmpty(stringSlice []string) []string {
|
func removeEmpty(stringSlice []string) []string {
|
||||||
var ret []string
|
var ret []string
|
||||||
for _, str := range stringSlice {
|
for _, str := range stringSlice {
|
||||||
|
|
|
@ -168,6 +168,14 @@ func (ad AddressDescriptor) String() string {
|
||||||
return "ad:" + hex.EncodeToString(ad)
|
return "ad:" + hex.EncodeToString(ad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddressDescriptorFromString converts string created by AddressDescriptor.String to AddressDescriptor
|
||||||
|
func AddressDescriptorFromString(s string) (AddressDescriptor, error) {
|
||||||
|
if len(s) > 3 && s[0:3] == "ad:" {
|
||||||
|
return hex.DecodeString(s[3:])
|
||||||
|
}
|
||||||
|
return nil, errors.New("Not AddressDescriptor")
|
||||||
|
}
|
||||||
|
|
||||||
// EthereumType specific
|
// EthereumType specific
|
||||||
|
|
||||||
// Erc20Contract contains info about ERC20 contract
|
// Erc20Contract contains info about ERC20 contract
|
||||||
|
|
21
blockbook.go
21
blockbook.go
|
@ -54,6 +54,7 @@ var (
|
||||||
|
|
||||||
synchronize = flag.Bool("sync", false, "synchronizes until tip, if together with zeromq, keeps index synchronized")
|
synchronize = flag.Bool("sync", false, "synchronizes until tip, if together with zeromq, keeps index synchronized")
|
||||||
repair = flag.Bool("repair", false, "repair the database")
|
repair = flag.Bool("repair", false, "repair the database")
|
||||||
|
fixUtxo = flag.Bool("fixutxo", false, "check and fix utxo db and exit")
|
||||||
prof = flag.String("prof", "", "http server binding [address]:port of the interface to profiling data /debug/pprof/ (default no profiling)")
|
prof = flag.String("prof", "", "http server binding [address]:port of the interface to profiling data /debug/pprof/ (default no profiling)")
|
||||||
|
|
||||||
syncChunk = flag.Int("chunk", 100, "block chunk size for processing in bulk mode")
|
syncChunk = flag.Int("chunk", 100, "block chunk size for processing in bulk mode")
|
||||||
|
@ -183,7 +184,26 @@ func mainWithExitCode() int {
|
||||||
glog.Error("internalState: ", err)
|
glog.Error("internalState: ", err)
|
||||||
return exitCodeFatal
|
return exitCodeFatal
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fix possible inconsistencies in the UTXO index
|
||||||
|
if *fixUtxo || !internalState.UtxoChecked {
|
||||||
|
err = index.FixUtxos(chanOsSignal)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("fixUtxos: ", err)
|
||||||
|
return exitCodeFatal
|
||||||
|
}
|
||||||
|
internalState.UtxoChecked = true
|
||||||
|
}
|
||||||
index.SetInternalState(internalState)
|
index.SetInternalState(internalState)
|
||||||
|
if *fixUtxo {
|
||||||
|
err = index.StoreInternalState(internalState)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("StoreInternalState: ", err)
|
||||||
|
return exitCodeFatal
|
||||||
|
}
|
||||||
|
return exitCodeOK
|
||||||
|
}
|
||||||
|
|
||||||
if internalState.DbState != common.DbStateClosed {
|
if internalState.DbState != common.DbStateClosed {
|
||||||
if internalState.DbState == common.DbStateInconsistent {
|
if internalState.DbState == common.DbStateInconsistent {
|
||||||
glog.Error("internalState: database is in inconsistent state and cannot be used")
|
glog.Error("internalState: database is in inconsistent state and cannot be used")
|
||||||
|
@ -556,6 +576,7 @@ func storeInternalStateLoop() {
|
||||||
close(stopCompute)
|
close(stopCompute)
|
||||||
close(chanStoreInternalStateDone)
|
close(chanStoreInternalStateDone)
|
||||||
}()
|
}()
|
||||||
|
signal.Notify(stopCompute, syscall.SIGHUP, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||||
var computeRunning bool
|
var computeRunning bool
|
||||||
lastCompute := time.Now()
|
lastCompute := time.Now()
|
||||||
lastAppInfo := time.Now()
|
lastAppInfo := time.Now()
|
||||||
|
|
|
@ -53,6 +53,8 @@ type InternalState struct {
|
||||||
LastMempoolSync time.Time `json:"lastMempoolSync"`
|
LastMempoolSync time.Time `json:"lastMempoolSync"`
|
||||||
|
|
||||||
DbColumns []InternalStateColumn `json:"dbColumns"`
|
DbColumns []InternalStateColumn `json:"dbColumns"`
|
||||||
|
|
||||||
|
UtxoChecked bool `json:"utxoChecked"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartedSync signals start of synchronization
|
// StartedSync signals start of synchronization
|
||||||
|
|
431
db/rocksdb.go
431
db/rocksdb.go
|
@ -373,6 +373,7 @@ func (d *RocksDB) GetTransactions(address string, lower uint32, higher uint32, f
|
||||||
// Transaction are passed to callback function in the order from newest block to the oldest
|
// 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 GetTransactionsCallback) (err error) {
|
func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, lower uint32, higher uint32, fn GetTransactionsCallback) (err error) {
|
||||||
txidUnpackedLen := d.chainParser.PackedTxidLen()
|
txidUnpackedLen := d.chainParser.PackedTxidLen()
|
||||||
|
addrDescLen := len(addrDesc)
|
||||||
startKey := packAddressKey(addrDesc, higher)
|
startKey := packAddressKey(addrDesc, higher)
|
||||||
stopKey := packAddressKey(addrDesc, lower)
|
stopKey := packAddressKey(addrDesc, lower)
|
||||||
indexes := make([]int32, 0, 16)
|
indexes := make([]int32, 0, 16)
|
||||||
|
@ -380,10 +381,16 @@ func (d *RocksDB) GetAddrDescTransactions(addrDesc bchain.AddressDescriptor, low
|
||||||
defer it.Close()
|
defer it.Close()
|
||||||
for it.Seek(startKey); it.Valid(); it.Next() {
|
for it.Seek(startKey); it.Valid(); it.Next() {
|
||||||
key := it.Key().Data()
|
key := it.Key().Data()
|
||||||
val := it.Value().Data()
|
|
||||||
if bytes.Compare(key, stopKey) > 0 {
|
if bytes.Compare(key, stopKey) > 0 {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
if len(key) != addrDescLen+packedHeightBytes {
|
||||||
|
if glog.V(2) {
|
||||||
|
glog.Warningf("rocksdb: addrDesc %s - mixed with %s", addrDesc, hex.EncodeToString(key))
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val := it.Value().Data()
|
||||||
if glog.V(2) {
|
if glog.V(2) {
|
||||||
glog.Infof("rocksdb: addresses %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
|
glog.Infof("rocksdb: addresses %s: %s", hex.EncodeToString(key), hex.EncodeToString(val))
|
||||||
}
|
}
|
||||||
|
@ -557,6 +564,10 @@ func (ab *AddrBalance) ReceivedSat() *big.Int {
|
||||||
// addUtxo
|
// addUtxo
|
||||||
func (ab *AddrBalance) addUtxo(u *Utxo) {
|
func (ab *AddrBalance) addUtxo(u *Utxo) {
|
||||||
ab.Utxos = append(ab.Utxos, *u)
|
ab.Utxos = append(ab.Utxos, *u)
|
||||||
|
ab.manageUtxoMap(u)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ab *AddrBalance) manageUtxoMap(u *Utxo) {
|
||||||
l := len(ab.Utxos)
|
l := len(ab.Utxos)
|
||||||
if l >= 16 {
|
if l >= 16 {
|
||||||
if len(ab.utxosMap) == 0 {
|
if len(ab.utxosMap) == 0 {
|
||||||
|
@ -576,9 +587,45 @@ func (ab *AddrBalance) addUtxo(u *Utxo) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// on disconnect, the added utxos must be inserted in the right position so that utxosMap index works
|
||||||
|
func (ab *AddrBalance) addUtxoInDisconnect(u *Utxo) {
|
||||||
|
insert := -1
|
||||||
|
if len(ab.utxosMap) > 0 {
|
||||||
|
if i, e := ab.utxosMap[string(u.BtxID)]; e {
|
||||||
|
insert = i
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for i := range ab.Utxos {
|
||||||
|
utxo := &ab.Utxos[i]
|
||||||
|
if *(*int)(unsafe.Pointer(&utxo.BtxID[0])) == *(*int)(unsafe.Pointer(&u.BtxID[0])) && bytes.Equal(utxo.BtxID, u.BtxID) {
|
||||||
|
insert = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if insert > -1 {
|
||||||
|
// check if it is necessary to insert the utxo into the array
|
||||||
|
for i := insert; i < len(ab.Utxos); i++ {
|
||||||
|
utxo := &ab.Utxos[i]
|
||||||
|
// either the vout is greater than the inserted vout or it is a different tx
|
||||||
|
if utxo.Vout > u.Vout || *(*int)(unsafe.Pointer(&utxo.BtxID[0])) != *(*int)(unsafe.Pointer(&u.BtxID[0])) || !bytes.Equal(utxo.BtxID, u.BtxID) {
|
||||||
|
// found the right place, insert the utxo
|
||||||
|
ab.Utxos = append(ab.Utxos, *u)
|
||||||
|
copy(ab.Utxos[i+1:], ab.Utxos[i:])
|
||||||
|
ab.Utxos[i] = *u
|
||||||
|
// reset utxosMap after insert, the index will have to be rebuilt if needed
|
||||||
|
ab.utxosMap = nil
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ab.Utxos = append(ab.Utxos, *u)
|
||||||
|
ab.manageUtxoMap(u)
|
||||||
|
}
|
||||||
|
|
||||||
// markUtxoAsSpent finds outpoint btxID:vout in utxos and marks it as spent
|
// markUtxoAsSpent finds outpoint btxID:vout in utxos and marks it as spent
|
||||||
// for small number of utxos the linear search is done, for larger number there is a hashmap index
|
// for small number of utxos the linear search is done, for larger number there is a hashmap index
|
||||||
// it is much faster than removing the utxo from the slice as it would cause in memory copy operations
|
// it is much faster than removing the utxo from the slice as it would cause in memory reallocations
|
||||||
func (ab *AddrBalance) markUtxoAsSpent(btxID []byte, vout int32) {
|
func (ab *AddrBalance) markUtxoAsSpent(btxID []byte, vout int32) {
|
||||||
if len(ab.utxosMap) == 0 {
|
if len(ab.utxosMap) == 0 {
|
||||||
for i := range ab.Utxos {
|
for i := range ab.Utxos {
|
||||||
|
@ -605,7 +652,7 @@ func (ab *AddrBalance) markUtxoAsSpent(btxID []byte, vout int32) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
glog.Errorf("Utxo %s:%d not found, using in map %v", hex.EncodeToString(btxID), vout, len(ab.utxosMap) != 0)
|
glog.Errorf("Utxo %s:%d not found, utxosMap size %d", hex.EncodeToString(btxID), vout, len(ab.utxosMap))
|
||||||
}
|
}
|
||||||
|
|
||||||
type blockTxs struct {
|
type blockTxs struct {
|
||||||
|
@ -1197,6 +1244,15 @@ func (d *RocksDB) packBlockInfo(block *BlockInfo) ([]byte, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
pl := d.chainParser.PackedTxidLen()
|
||||||
|
if len(b) != pl {
|
||||||
|
glog.Warning("Non standard block hash for height ", block.Height, ", hash [", block.Hash, "]")
|
||||||
|
if len(b) > pl {
|
||||||
|
b = b[:pl]
|
||||||
|
} else {
|
||||||
|
b = append(b, make([]byte, len(b)-pl)...)
|
||||||
|
}
|
||||||
|
}
|
||||||
packed = append(packed, b...)
|
packed = append(packed, b...)
|
||||||
packed = append(packed, packUint(uint32(block.Time))...)
|
packed = append(packed, packUint(uint32(block.Time))...)
|
||||||
l := packVaruint(uint(block.Txs), varBuf)
|
l := packVaruint(uint(block.Txs), varBuf)
|
||||||
|
@ -1304,33 +1360,16 @@ func (d *RocksDB) writeHeight(wb *gorocksdb.WriteBatch, height uint32, bi *Block
|
||||||
|
|
||||||
// Disconnect blocks
|
// Disconnect blocks
|
||||||
|
|
||||||
func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32, btxID []byte, inputs []outpoint, txa *TxAddresses,
|
func (d *RocksDB) disconnectTxAddressesInputs(wb *gorocksdb.WriteBatch, btxID []byte, inputs []outpoint, txa *TxAddresses, txAddressesToUpdate map[string]*TxAddresses,
|
||||||
txAddressesToUpdate map[string]*TxAddresses, balances map[string]*AddrBalance) error {
|
getAddressBalance func(addrDesc bchain.AddressDescriptor) (*AddrBalance, error),
|
||||||
|
addressFoundInTx func(addrDesc bchain.AddressDescriptor, btxID []byte) bool) error {
|
||||||
var err error
|
var err error
|
||||||
var balance *AddrBalance
|
var balance *AddrBalance
|
||||||
addresses := make(map[string]struct{})
|
|
||||||
getAddressBalance := func(addrDesc bchain.AddressDescriptor) (*AddrBalance, error) {
|
|
||||||
var err error
|
|
||||||
s := string(addrDesc)
|
|
||||||
b, fb := balances[s]
|
|
||||||
if !fb {
|
|
||||||
b, err = d.GetAddrDescBalance(addrDesc, addressBalanceDetailUTXOIndexed)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
balances[s] = b
|
|
||||||
}
|
|
||||||
return b, nil
|
|
||||||
}
|
|
||||||
for i, t := range txa.Inputs {
|
for i, t := range txa.Inputs {
|
||||||
if len(t.AddrDesc) > 0 {
|
if len(t.AddrDesc) > 0 {
|
||||||
input := &inputs[i]
|
input := &inputs[i]
|
||||||
s := string(t.AddrDesc)
|
exist := addressFoundInTx(t.AddrDesc, btxID)
|
||||||
_, exist := addresses[s]
|
s := string(input.btxID)
|
||||||
if !exist {
|
|
||||||
addresses[s] = struct{}{}
|
|
||||||
}
|
|
||||||
s = string(input.btxID)
|
|
||||||
sa, found := txAddressesToUpdate[s]
|
sa, found := txAddressesToUpdate[s]
|
||||||
if !found {
|
if !found {
|
||||||
sa, err = d.getTxAddresses(input.btxID)
|
sa, err = d.getTxAddresses(input.btxID)
|
||||||
|
@ -1361,7 +1400,7 @@ func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32,
|
||||||
d.resetValueSatToZero(&balance.SentSat, t.AddrDesc, "sent amount")
|
d.resetValueSatToZero(&balance.SentSat, t.AddrDesc, "sent amount")
|
||||||
}
|
}
|
||||||
balance.BalanceSat.Add(&balance.BalanceSat, &t.ValueSat)
|
balance.BalanceSat.Add(&balance.BalanceSat, &t.ValueSat)
|
||||||
balance.Utxos = append(balance.Utxos, Utxo{
|
balance.addUtxoInDisconnect(&Utxo{
|
||||||
BtxID: input.btxID,
|
BtxID: input.btxID,
|
||||||
Vout: input.index,
|
Vout: input.index,
|
||||||
Height: inputHeight,
|
Height: inputHeight,
|
||||||
|
@ -1374,13 +1413,15 @@ func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *RocksDB) disconnectTxAddressesOutputs(wb *gorocksdb.WriteBatch, btxID []byte, txa *TxAddresses,
|
||||||
|
getAddressBalance func(addrDesc bchain.AddressDescriptor) (*AddrBalance, error),
|
||||||
|
addressFoundInTx func(addrDesc bchain.AddressDescriptor, btxID []byte) bool) error {
|
||||||
for i, t := range txa.Outputs {
|
for i, t := range txa.Outputs {
|
||||||
if len(t.AddrDesc) > 0 {
|
if len(t.AddrDesc) > 0 {
|
||||||
s := string(t.AddrDesc)
|
exist := addressFoundInTx(t.AddrDesc, btxID)
|
||||||
_, exist := addresses[s]
|
|
||||||
if !exist {
|
|
||||||
addresses[s] = struct{}{}
|
|
||||||
}
|
|
||||||
if d.chainParser.IsAddrDescIndexable(t.AddrDesc) {
|
if d.chainParser.IsAddrDescIndexable(t.AddrDesc) {
|
||||||
balance, err := getAddressBalance(t.AddrDesc)
|
balance, err := getAddressBalance(t.AddrDesc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1403,11 +1444,95 @@ func (d *RocksDB) disconnectTxAddresses(wb *gorocksdb.WriteBatch, height uint32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for a := range addresses {
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *RocksDB) disconnectBlock(height uint32, blockTxs []blockTxs) error {
|
||||||
|
wb := gorocksdb.NewWriteBatch()
|
||||||
|
defer wb.Destroy()
|
||||||
|
txAddressesToUpdate := make(map[string]*TxAddresses)
|
||||||
|
txAddresses := make([]*TxAddresses, len(blockTxs))
|
||||||
|
txsToDelete := make(map[string]struct{})
|
||||||
|
|
||||||
|
balances := make(map[string]*AddrBalance)
|
||||||
|
getAddressBalance := func(addrDesc bchain.AddressDescriptor) (*AddrBalance, error) {
|
||||||
|
var err error
|
||||||
|
s := string(addrDesc)
|
||||||
|
b, fb := balances[s]
|
||||||
|
if !fb {
|
||||||
|
b, err = d.GetAddrDescBalance(addrDesc, addressBalanceDetailUTXOIndexed)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
balances[s] = b
|
||||||
|
}
|
||||||
|
return b, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// all addresses in the block are stored in blockAddressesTxs, together with a map of transactions where they appear
|
||||||
|
blockAddressesTxs := make(map[string]map[string]struct{})
|
||||||
|
// addressFoundInTx handles updates of the blockAddressesTxs map and returns true if the address+tx was already encountered
|
||||||
|
addressFoundInTx := func(addrDesc bchain.AddressDescriptor, btxID []byte) bool {
|
||||||
|
sAddrDesc := string(addrDesc)
|
||||||
|
sBtxID := string(btxID)
|
||||||
|
a, exist := blockAddressesTxs[sAddrDesc]
|
||||||
|
if !exist {
|
||||||
|
blockAddressesTxs[sAddrDesc] = map[string]struct{}{sBtxID: {}}
|
||||||
|
} else {
|
||||||
|
_, exist = a[sBtxID]
|
||||||
|
if !exist {
|
||||||
|
a[sBtxID] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return exist
|
||||||
|
}
|
||||||
|
|
||||||
|
glog.Info("Disconnecting block ", height, " containing ", len(blockTxs), " transactions")
|
||||||
|
// when connecting block, outputs are processed first
|
||||||
|
// when disconnecting, inputs must be reversed first
|
||||||
|
for i := range blockTxs {
|
||||||
|
btxID := blockTxs[i].btxID
|
||||||
|
s := string(btxID)
|
||||||
|
txsToDelete[s] = struct{}{}
|
||||||
|
txa, err := d.getTxAddresses(btxID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if txa == nil {
|
||||||
|
ut, _ := d.chainParser.UnpackTxid(btxID)
|
||||||
|
glog.Warning("TxAddress for txid ", ut, " not found")
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
txAddresses[i] = txa
|
||||||
|
if err := d.disconnectTxAddressesInputs(wb, btxID, blockTxs[i].inputs, txa, txAddressesToUpdate, getAddressBalance, addressFoundInTx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for i := range blockTxs {
|
||||||
|
btxID := blockTxs[i].btxID
|
||||||
|
txa := txAddresses[i]
|
||||||
|
if txa == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := d.disconnectTxAddressesOutputs(wb, btxID, txa, getAddressBalance, addressFoundInTx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for a := range blockAddressesTxs {
|
||||||
key := packAddressKey([]byte(a), height)
|
key := packAddressKey([]byte(a), height)
|
||||||
wb.DeleteCF(d.cfh[cfAddresses], key)
|
wb.DeleteCF(d.cfh[cfAddresses], key)
|
||||||
}
|
}
|
||||||
return nil
|
key := packUint(height)
|
||||||
|
wb.DeleteCF(d.cfh[cfBlockTxs], key)
|
||||||
|
wb.DeleteCF(d.cfh[cfHeight], key)
|
||||||
|
d.storeTxAddresses(wb, txAddressesToUpdate)
|
||||||
|
d.storeBalancesDisconnect(wb, balances)
|
||||||
|
for s := range txsToDelete {
|
||||||
|
b := []byte(s)
|
||||||
|
wb.DeleteCF(d.cfh[cfTransactions], b)
|
||||||
|
wb.DeleteCF(d.cfh[cfTxAddresses], b)
|
||||||
|
}
|
||||||
|
return d.db.Write(d.wo, wb)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DisconnectBlockRangeBitcoinType removes all data belonging to blocks in range lower-higher
|
// DisconnectBlockRangeBitcoinType removes all data belonging to blocks in range lower-higher
|
||||||
|
@ -1424,51 +1549,15 @@ func (d *RocksDB) DisconnectBlockRangeBitcoinType(lower uint32, higher uint32) e
|
||||||
}
|
}
|
||||||
blocks[height-lower] = blockTxs
|
blocks[height-lower] = blockTxs
|
||||||
}
|
}
|
||||||
wb := gorocksdb.NewWriteBatch()
|
|
||||||
defer wb.Destroy()
|
|
||||||
txAddressesToUpdate := make(map[string]*TxAddresses)
|
|
||||||
txsToDelete := make(map[string]struct{})
|
|
||||||
balances := make(map[string]*AddrBalance)
|
|
||||||
for height := higher; height >= lower; height-- {
|
for height := higher; height >= lower; height-- {
|
||||||
blockTxs := blocks[height-lower]
|
err := d.disconnectBlock(height, blocks[height-lower])
|
||||||
glog.Info("Disconnecting block ", height, " containing ", len(blockTxs), " transactions")
|
if err != nil {
|
||||||
// go backwards to avoid interim negative balance
|
return err
|
||||||
// when connecting block, amount is first in tx on the output side, then in another tx on the input side
|
|
||||||
// when disconnecting, it must be done backwards
|
|
||||||
for i := len(blockTxs) - 1; i >= 0; i-- {
|
|
||||||
btxID := blockTxs[i].btxID
|
|
||||||
s := string(btxID)
|
|
||||||
txsToDelete[s] = struct{}{}
|
|
||||||
txa, err := d.getTxAddresses(btxID)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if txa == nil {
|
|
||||||
ut, _ := d.chainParser.UnpackTxid(btxID)
|
|
||||||
glog.Warning("TxAddress for txid ", ut, " not found")
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := d.disconnectTxAddresses(wb, height, btxID, blockTxs[i].inputs, txa, txAddressesToUpdate, balances); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
key := packUint(height)
|
|
||||||
wb.DeleteCF(d.cfh[cfBlockTxs], key)
|
|
||||||
wb.DeleteCF(d.cfh[cfHeight], key)
|
|
||||||
}
|
}
|
||||||
d.storeTxAddresses(wb, txAddressesToUpdate)
|
d.is.RemoveLastBlockTimes(int(higher-lower) + 1)
|
||||||
d.storeBalancesDisconnect(wb, balances)
|
glog.Infof("rocksdb: blocks %d-%d disconnected", lower, higher)
|
||||||
for s := range txsToDelete {
|
return nil
|
||||||
b := []byte(s)
|
|
||||||
wb.DeleteCF(d.cfh[cfTransactions], b)
|
|
||||||
wb.DeleteCF(d.cfh[cfTxAddresses], b)
|
|
||||||
}
|
|
||||||
err := d.db.Write(d.wo, wb)
|
|
||||||
if err == nil {
|
|
||||||
d.is.RemoveLastBlockTimes(int(higher-lower) + 1)
|
|
||||||
glog.Infof("rocksdb: blocks %d-%d disconnected", lower, higher)
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *RocksDB) storeBalancesDisconnect(wb *gorocksdb.WriteBatch, balances map[string]*AddrBalance) {
|
func (d *RocksDB) storeBalancesDisconnect(wb *gorocksdb.WriteBatch, balances map[string]*AddrBalance) {
|
||||||
|
@ -1599,7 +1688,9 @@ func (d *RocksDB) loadBlockTimes() ([]uint32, error) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
time = uint32(info.Time)
|
if info != nil {
|
||||||
|
time = uint32(info.Time)
|
||||||
|
}
|
||||||
times = append(times, time)
|
times = append(times, time)
|
||||||
}
|
}
|
||||||
glog.Info("loaded ", len(times), " block times")
|
glog.Info("loaded ", len(times), " block times")
|
||||||
|
@ -1616,7 +1707,7 @@ func (d *RocksDB) LoadInternalState(rpcCoin string) (*common.InternalState, erro
|
||||||
data := val.Data()
|
data := val.Data()
|
||||||
var is *common.InternalState
|
var is *common.InternalState
|
||||||
if len(data) == 0 {
|
if len(data) == 0 {
|
||||||
is = &common.InternalState{Coin: rpcCoin}
|
is = &common.InternalState{Coin: rpcCoin, UtxoChecked: true}
|
||||||
} else {
|
} else {
|
||||||
is, err = common.UnpackInternalState(data)
|
is, err = common.UnpackInternalState(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1758,6 +1849,192 @@ func (d *RocksDB) ComputeInternalStateColumnStats(stopCompute chan os.Signal) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func reorderUtxo(utxos []Utxo, index int) {
|
||||||
|
var from, to int
|
||||||
|
for from = index; from >= 0; from-- {
|
||||||
|
if !bytes.Equal(utxos[from].BtxID, utxos[index].BtxID) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
from++
|
||||||
|
for to = index + 1; to < len(utxos); to++ {
|
||||||
|
if !bytes.Equal(utxos[to].BtxID, utxos[index].BtxID) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
toSort := utxos[from:to]
|
||||||
|
sort.SliceStable(toSort, func(i, j int) bool {
|
||||||
|
return toSort[i].Vout < toSort[j].Vout
|
||||||
|
})
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *RocksDB) fixUtxo(addrDesc bchain.AddressDescriptor, ba *AddrBalance) (bool, bool, error) {
|
||||||
|
reorder := false
|
||||||
|
var checksum big.Int
|
||||||
|
var prevUtxo *Utxo
|
||||||
|
for i := range ba.Utxos {
|
||||||
|
utxo := &ba.Utxos[i]
|
||||||
|
checksum.Add(&checksum, &utxo.ValueSat)
|
||||||
|
if prevUtxo != nil {
|
||||||
|
if prevUtxo.Vout > utxo.Vout && *(*int)(unsafe.Pointer(&utxo.BtxID[0])) == *(*int)(unsafe.Pointer(&prevUtxo.BtxID[0])) && bytes.Equal(utxo.BtxID, prevUtxo.BtxID) {
|
||||||
|
reorderUtxo(ba.Utxos, i)
|
||||||
|
reorder = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
prevUtxo = utxo
|
||||||
|
}
|
||||||
|
if reorder {
|
||||||
|
// get the checksum again after reorder
|
||||||
|
checksum.SetInt64(0)
|
||||||
|
for i := range ba.Utxos {
|
||||||
|
utxo := &ba.Utxos[i]
|
||||||
|
checksum.Add(&checksum, &utxo.ValueSat)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if checksum.Cmp(&ba.BalanceSat) != 0 {
|
||||||
|
var checksumFromTxs big.Int
|
||||||
|
var utxos []Utxo
|
||||||
|
err := d.GetAddrDescTransactions(addrDesc, 0, ^uint32(0), func(txid string, height uint32, indexes []int32) error {
|
||||||
|
var ta *TxAddresses
|
||||||
|
var err error
|
||||||
|
// sort the indexes so that the utxos are appended in the reverse order
|
||||||
|
sort.Slice(indexes, func(i, j int) bool {
|
||||||
|
return indexes[i] > indexes[j]
|
||||||
|
})
|
||||||
|
for _, index := range indexes {
|
||||||
|
// take only outputs
|
||||||
|
if index < 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if ta == nil {
|
||||||
|
ta, err = d.GetTxAddresses(txid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ta == nil {
|
||||||
|
return errors.New("DB inconsistency: tx " + txid + ": not found in txAddresses")
|
||||||
|
}
|
||||||
|
if len(ta.Outputs) <= int(index) {
|
||||||
|
glog.Warning("DB inconsistency: txAddresses " + txid + " does not have enough outputs")
|
||||||
|
} else {
|
||||||
|
tao := &ta.Outputs[index]
|
||||||
|
if !tao.Spent {
|
||||||
|
bTxid, _ := d.chainParser.PackTxid(txid)
|
||||||
|
checksumFromTxs.Add(&checksumFromTxs, &tao.ValueSat)
|
||||||
|
utxos = append(utxos, Utxo{BtxID: bTxid, Height: height, Vout: index, ValueSat: tao.ValueSat})
|
||||||
|
if checksumFromTxs.Cmp(&ba.BalanceSat) == 0 {
|
||||||
|
return &StopIteration{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, false, err
|
||||||
|
}
|
||||||
|
fixed := false
|
||||||
|
if checksumFromTxs.Cmp(&ba.BalanceSat) == 0 {
|
||||||
|
// reverse the utxos as they are added in descending order by height
|
||||||
|
for i := len(utxos)/2 - 1; i >= 0; i-- {
|
||||||
|
opp := len(utxos) - 1 - i
|
||||||
|
utxos[i], utxos[opp] = utxos[opp], utxos[i]
|
||||||
|
}
|
||||||
|
ba.Utxos = utxos
|
||||||
|
wb := gorocksdb.NewWriteBatch()
|
||||||
|
err = d.storeBalances(wb, map[string]*AddrBalance{string(addrDesc): ba})
|
||||||
|
if err == nil {
|
||||||
|
err = d.db.Write(d.wo, wb)
|
||||||
|
}
|
||||||
|
wb.Destroy()
|
||||||
|
if err != nil {
|
||||||
|
return false, false, errors.Errorf("balance %s, checksum %s, from txa %s, txs %d, error storing fixed utxos %v", ba.BalanceSat.String(), checksum.String(), checksumFromTxs.String(), ba.Txs, err)
|
||||||
|
}
|
||||||
|
fixed = true
|
||||||
|
}
|
||||||
|
return fixed, false, errors.Errorf("balance %s, checksum %s, from txa %s, txs %d", ba.BalanceSat.String(), checksum.String(), checksumFromTxs.String(), ba.Txs)
|
||||||
|
} else if reorder {
|
||||||
|
wb := gorocksdb.NewWriteBatch()
|
||||||
|
err := d.storeBalances(wb, map[string]*AddrBalance{string(addrDesc): ba})
|
||||||
|
if err == nil {
|
||||||
|
err = d.db.Write(d.wo, wb)
|
||||||
|
}
|
||||||
|
wb.Destroy()
|
||||||
|
if err != nil {
|
||||||
|
return false, false, errors.Errorf("error storing reordered utxos %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, reorder, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FixUtxos checks and fixes possible
|
||||||
|
func (d *RocksDB) FixUtxos(stop chan os.Signal) error {
|
||||||
|
if d.chainParser.GetChainType() != bchain.ChainBitcoinType {
|
||||||
|
glog.Info("FixUtxos: applicable only for bitcoin type coins")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
glog.Info("FixUtxos: starting")
|
||||||
|
var row, errorsCount, fixedCount int64
|
||||||
|
var seekKey []byte
|
||||||
|
// do not use cache
|
||||||
|
ro := gorocksdb.NewDefaultReadOptions()
|
||||||
|
ro.SetFillCache(false)
|
||||||
|
for {
|
||||||
|
var addrDesc bchain.AddressDescriptor
|
||||||
|
it := d.db.NewIteratorCF(ro, d.cfh[cfAddressBalance])
|
||||||
|
if row == 0 {
|
||||||
|
it.SeekToFirst()
|
||||||
|
} else {
|
||||||
|
glog.Info("FixUtxos: row ", row, ", errors ", errorsCount)
|
||||||
|
it.Seek(seekKey)
|
||||||
|
it.Next()
|
||||||
|
}
|
||||||
|
for count := 0; it.Valid() && count < refreshIterator; it.Next() {
|
||||||
|
select {
|
||||||
|
case <-stop:
|
||||||
|
return errors.New("Interrupted")
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
addrDesc = it.Key().Data()
|
||||||
|
buf := it.Value().Data()
|
||||||
|
count++
|
||||||
|
row++
|
||||||
|
if len(buf) < 3 {
|
||||||
|
glog.Error("FixUtxos: row ", row, ", addrDesc ", addrDesc, ", empty data")
|
||||||
|
errorsCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ba, err := unpackAddrBalance(buf, d.chainParser.PackedTxidLen(), AddressBalanceDetailUTXO)
|
||||||
|
if err != nil {
|
||||||
|
glog.Error("FixUtxos: row ", row, ", addrDesc ", addrDesc, ", unpackAddrBalance error ", err)
|
||||||
|
errorsCount++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
fixed, reordered, err := d.fixUtxo(addrDesc, ba)
|
||||||
|
if err != nil {
|
||||||
|
errorsCount++
|
||||||
|
glog.Error("FixUtxos: row ", row, ", addrDesc ", addrDesc, ", error ", err, ", fixed ", fixed)
|
||||||
|
if fixed {
|
||||||
|
fixedCount++
|
||||||
|
}
|
||||||
|
} else if reordered {
|
||||||
|
glog.Error("FixUtxos: row ", row, ", addrDesc ", addrDesc, " reordered")
|
||||||
|
fixedCount++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
seekKey = append([]byte{}, addrDesc...)
|
||||||
|
valid := it.Valid()
|
||||||
|
it.Close()
|
||||||
|
if !valid {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
glog.Info("FixUtxos: finished, scanned ", row, " rows, found ", errorsCount, " errors, fixed ", fixedCount)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// Helpers
|
// Helpers
|
||||||
|
|
||||||
func packAddressKey(addrDesc bchain.AddressDescriptor, height uint32) []byte {
|
func packAddressKey(addrDesc bchain.AddressDescriptor, height uint32) []byte {
|
||||||
|
|
|
@ -192,7 +192,7 @@ func verifyAfterBitcoinTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect bool
|
||||||
// the vout is encoded as signed varint, i.e. value * 2 for non negative values
|
// the vout is encoded as signed varint, i.e. value * 2 for non negative values
|
||||||
if err := checkColumn(d, cfAddresses, []keyPair{
|
if err := checkColumn(d, cfAddresses, []keyPair{
|
||||||
{addressKeyHex(dbtestdata.Addr1, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{0}), nil},
|
{addressKeyHex(dbtestdata.Addr1, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{0}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr2, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{1}), nil},
|
{addressKeyHex(dbtestdata.Addr2, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{1, 2}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr3, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{0}), nil},
|
{addressKeyHex(dbtestdata.Addr3, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{0}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr4, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{1}), nil},
|
{addressKeyHex(dbtestdata.Addr4, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{1}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr5, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{2}), nil},
|
{addressKeyHex(dbtestdata.Addr5, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{2}), nil},
|
||||||
|
@ -206,8 +206,9 @@ func verifyAfterBitcoinTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect bool
|
||||||
dbtestdata.TxidB1T1,
|
dbtestdata.TxidB1T1,
|
||||||
varuintToHex(225493) +
|
varuintToHex(225493) +
|
||||||
"00" +
|
"00" +
|
||||||
"02" +
|
"03" +
|
||||||
addressToPubKeyHexWithLength(dbtestdata.Addr1, t, d) + bigintToHex(dbtestdata.SatB1T1A1) +
|
addressToPubKeyHexWithLength(dbtestdata.Addr1, t, d) + bigintToHex(dbtestdata.SatB1T1A1) +
|
||||||
|
addressToPubKeyHexWithLength(dbtestdata.Addr2, t, d) + bigintToHex(dbtestdata.SatB1T1A2) +
|
||||||
addressToPubKeyHexWithLength(dbtestdata.Addr2, t, d) + bigintToHex(dbtestdata.SatB1T1A2),
|
addressToPubKeyHexWithLength(dbtestdata.Addr2, t, d) + bigintToHex(dbtestdata.SatB1T1A2),
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
|
@ -235,8 +236,9 @@ func verifyAfterBitcoinTypeBlock1(t *testing.T, d *RocksDB, afterDisconnect bool
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dbtestdata.AddressToPubKeyHex(dbtestdata.Addr2, d.chainParser),
|
dbtestdata.AddressToPubKeyHex(dbtestdata.Addr2, d.chainParser),
|
||||||
"01" + bigintToHex(dbtestdata.SatZero) + bigintToHex(dbtestdata.SatB1T1A2) +
|
"01" + bigintToHex(dbtestdata.SatZero) + bigintToHex(dbtestdata.SatB1T1A2Double) +
|
||||||
dbtestdata.TxidB1T1 + varuintToHex(1) + varuintToHex(225493) + bigintToHex(dbtestdata.SatB1T1A2),
|
dbtestdata.TxidB1T1 + varuintToHex(1) + varuintToHex(225493) + bigintToHex(dbtestdata.SatB1T1A2) +
|
||||||
|
dbtestdata.TxidB1T1 + varuintToHex(2) + varuintToHex(225493) + bigintToHex(dbtestdata.SatB1T1A2),
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -302,7 +304,7 @@ func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) {
|
||||||
}
|
}
|
||||||
if err := checkColumn(d, cfAddresses, []keyPair{
|
if err := checkColumn(d, cfAddresses, []keyPair{
|
||||||
{addressKeyHex(dbtestdata.Addr1, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{0}), nil},
|
{addressKeyHex(dbtestdata.Addr1, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{0}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr2, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{1}), nil},
|
{addressKeyHex(dbtestdata.Addr2, 225493, d), txIndexesHex(dbtestdata.TxidB1T1, []int32{1, 2}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr3, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{0}), nil},
|
{addressKeyHex(dbtestdata.Addr3, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{0}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr4, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{1}), nil},
|
{addressKeyHex(dbtestdata.Addr4, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{1}), nil},
|
||||||
{addressKeyHex(dbtestdata.Addr5, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{2}), nil},
|
{addressKeyHex(dbtestdata.Addr5, 225493, d), txIndexesHex(dbtestdata.TxidB1T2, []int32{2}), nil},
|
||||||
|
@ -325,9 +327,10 @@ func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) {
|
||||||
dbtestdata.TxidB1T1,
|
dbtestdata.TxidB1T1,
|
||||||
varuintToHex(225493) +
|
varuintToHex(225493) +
|
||||||
"00" +
|
"00" +
|
||||||
"02" +
|
"03" +
|
||||||
addressToPubKeyHexWithLength(dbtestdata.Addr1, t, d) + bigintToHex(dbtestdata.SatB1T1A1) +
|
addressToPubKeyHexWithLength(dbtestdata.Addr1, t, d) + bigintToHex(dbtestdata.SatB1T1A1) +
|
||||||
spentAddressToPubKeyHexWithLength(dbtestdata.Addr2, t, d) + bigintToHex(dbtestdata.SatB1T1A2),
|
spentAddressToPubKeyHexWithLength(dbtestdata.Addr2, t, d) + bigintToHex(dbtestdata.SatB1T1A2) +
|
||||||
|
addressToPubKeyHexWithLength(dbtestdata.Addr2, t, d) + bigintToHex(dbtestdata.SatB1T1A2),
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -395,7 +398,8 @@ func verifyAfterBitcoinTypeBlock2(t *testing.T, d *RocksDB) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
dbtestdata.AddressToPubKeyHex(dbtestdata.Addr2, d.chainParser),
|
dbtestdata.AddressToPubKeyHex(dbtestdata.Addr2, d.chainParser),
|
||||||
"02" + bigintToHex(dbtestdata.SatB1T1A2) + bigintToHex(dbtestdata.SatZero),
|
"02" + bigintToHex(dbtestdata.SatB1T1A2) + bigintToHex(dbtestdata.SatB1T1A2) +
|
||||||
|
dbtestdata.TxidB1T1 + varuintToHex(2) + varuintToHex(225493) + bigintToHex(dbtestdata.SatB1T1A2),
|
||||||
nil,
|
nil,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -563,9 +567,11 @@ func TestRocksDB_Index_BitcoinType(t *testing.T) {
|
||||||
verifyGetTransactions(t, d, dbtestdata.Addr2, 0, 1000000, []txidIndex{
|
verifyGetTransactions(t, d, dbtestdata.Addr2, 0, 1000000, []txidIndex{
|
||||||
{dbtestdata.TxidB2T1, ^1},
|
{dbtestdata.TxidB2T1, ^1},
|
||||||
{dbtestdata.TxidB1T1, 1},
|
{dbtestdata.TxidB1T1, 1},
|
||||||
|
{dbtestdata.TxidB1T1, 2},
|
||||||
}, nil)
|
}, nil)
|
||||||
verifyGetTransactions(t, d, dbtestdata.Addr2, 225493, 225493, []txidIndex{
|
verifyGetTransactions(t, d, dbtestdata.Addr2, 225493, 225493, []txidIndex{
|
||||||
{dbtestdata.TxidB1T1, 1},
|
{dbtestdata.TxidB1T1, 1},
|
||||||
|
{dbtestdata.TxidB1T1, 2},
|
||||||
}, nil)
|
}, nil)
|
||||||
verifyGetTransactions(t, d, dbtestdata.Addr2, 225494, 1000000, []txidIndex{
|
verifyGetTransactions(t, d, dbtestdata.Addr2, 225494, 1000000, []txidIndex{
|
||||||
{dbtestdata.TxidB2T1, ^1},
|
{dbtestdata.TxidB2T1, ^1},
|
||||||
|
@ -1101,6 +1107,382 @@ func Test_packAddrBalance_unpackAddrBalance(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createUtxoMap(ab *AddrBalance) {
|
||||||
|
l := len(ab.Utxos)
|
||||||
|
ab.utxosMap = make(map[string]int, 32)
|
||||||
|
for i := 0; i < l; i++ {
|
||||||
|
s := string(ab.Utxos[i].BtxID)
|
||||||
|
if _, e := ab.utxosMap[s]; !e {
|
||||||
|
ab.utxosMap[s] = i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestAddrBalance_utxo_methods(t *testing.T) {
|
||||||
|
ab := &AddrBalance{
|
||||||
|
Txs: 10,
|
||||||
|
SentSat: *big.NewInt(10000),
|
||||||
|
BalanceSat: *big.NewInt(1000),
|
||||||
|
}
|
||||||
|
|
||||||
|
// addUtxo
|
||||||
|
ab.addUtxo(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
})
|
||||||
|
ab.addUtxo(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 4,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
})
|
||||||
|
ab.addUtxo(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5001,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
})
|
||||||
|
want := &AddrBalance{
|
||||||
|
Txs: 10,
|
||||||
|
SentSat: *big.NewInt(10000),
|
||||||
|
BalanceSat: *big.NewInt(1000),
|
||||||
|
Utxos: []Utxo{
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 4,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5001,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ab, want) {
|
||||||
|
t.Errorf("addUtxo, got %+v, want %+v", ab, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addUtxoInDisconnect
|
||||||
|
ab.addUtxoInDisconnect(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5003,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
})
|
||||||
|
ab.addUtxoInDisconnect(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 1,
|
||||||
|
Height: 5003,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
})
|
||||||
|
ab.addUtxoInDisconnect(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 10,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
})
|
||||||
|
ab.addUtxoInDisconnect(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 2,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
})
|
||||||
|
ab.addUtxoInDisconnect(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
})
|
||||||
|
want = &AddrBalance{
|
||||||
|
Txs: 10,
|
||||||
|
SentSat: *big.NewInt(10000),
|
||||||
|
BalanceSat: *big.NewInt(1000),
|
||||||
|
Utxos: []Utxo{
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 2,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 4,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 10,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5001,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 0,
|
||||||
|
Height: 5003,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 1,
|
||||||
|
Height: 5003,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if !reflect.DeepEqual(ab, want) {
|
||||||
|
t.Errorf("addUtxoInDisconnect, got %+v, want %+v", ab, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// markUtxoAsSpent
|
||||||
|
ab.markUtxoAsSpent(hexToBytes(dbtestdata.TxidB2T1), 0)
|
||||||
|
want.Utxos[6].Vout = -1
|
||||||
|
if !reflect.DeepEqual(ab, want) {
|
||||||
|
t.Errorf("markUtxoAsSpent, got %+v, want %+v", ab, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addUtxo with utxosMap
|
||||||
|
for i := 0; i < 20; i += 2 {
|
||||||
|
utxo := Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T2),
|
||||||
|
Vout: int32(i),
|
||||||
|
Height: 5009,
|
||||||
|
ValueSat: *big.NewInt(800),
|
||||||
|
}
|
||||||
|
ab.addUtxo(&utxo)
|
||||||
|
want.Utxos = append(want.Utxos, utxo)
|
||||||
|
}
|
||||||
|
createUtxoMap(want)
|
||||||
|
if !reflect.DeepEqual(ab, want) {
|
||||||
|
t.Errorf("addUtxo with utxosMap, got %+v, want %+v", ab, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// markUtxoAsSpent with utxosMap
|
||||||
|
ab.markUtxoAsSpent(hexToBytes(dbtestdata.TxidB2T1), 1)
|
||||||
|
want.Utxos[7].Vout = -1
|
||||||
|
if !reflect.DeepEqual(ab, want) {
|
||||||
|
t.Errorf("markUtxoAsSpent with utxosMap, got %+v, want %+v", ab, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
// addUtxoInDisconnect with utxosMap
|
||||||
|
ab.addUtxoInDisconnect(&Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 3,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
})
|
||||||
|
want.Utxos = append(want.Utxos, Utxo{})
|
||||||
|
copy(want.Utxos[3+1:], want.Utxos[3:])
|
||||||
|
want.Utxos[3] = Utxo{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 3,
|
||||||
|
Height: 5000,
|
||||||
|
ValueSat: *big.NewInt(100),
|
||||||
|
}
|
||||||
|
want.utxosMap = nil
|
||||||
|
if !reflect.DeepEqual(ab, want) {
|
||||||
|
t.Errorf("addUtxoInDisconnect with utxosMap, got %+v, want %+v", ab, want)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_reorderUtxo(t *testing.T) {
|
||||||
|
utxos := []Utxo{
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
utxos []Utxo
|
||||||
|
index int
|
||||||
|
want []Utxo
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "middle",
|
||||||
|
utxos: utxos,
|
||||||
|
index: 4,
|
||||||
|
want: []Utxo{
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "start",
|
||||||
|
utxos: utxos,
|
||||||
|
index: 1,
|
||||||
|
want: []Utxo{
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "end",
|
||||||
|
utxos: utxos,
|
||||||
|
index: 6,
|
||||||
|
want: []Utxo{
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T1),
|
||||||
|
Vout: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB1T2),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
BtxID: hexToBytes(dbtestdata.TxidB2T1),
|
||||||
|
Vout: 2,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
reorderUtxo(tt.utxos, tt.index)
|
||||||
|
if !reflect.DeepEqual(tt.utxos, tt.want) {
|
||||||
|
t.Errorf("reorderUtxo %s, got %+v, want %+v", tt.name, tt.utxos, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestRocksTickers(t *testing.T) {
|
func TestRocksTickers(t *testing.T) {
|
||||||
d := setupRocksDB(t, &testBitcoinParser{
|
d := setupRocksDB(t, &testBitcoinParser{
|
||||||
BitcoinParser: bitcoinTestnetParser(),
|
BitcoinParser: bitcoinTestnetParser(),
|
||||||
|
|
|
@ -236,7 +236,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
body: []string{
|
body: []string{
|
||||||
`<a class="navbar-brand" href="/">Fake Coin Explorer</a>`,
|
`<a class="navbar-brand" href="/">Fake Coin Explorer</a>`,
|
||||||
`<h1>Address`,
|
`<h1>Address`,
|
||||||
`<small class="text-muted">0 FAKE</small>`,
|
`<small class="text-muted">0.00012345 FAKE</small>`,
|
||||||
`<span class="data">mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz</span>`,
|
`<span class="data">mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz</span>`,
|
||||||
`<td class="data">0.00012345 FAKE</td>`,
|
`<td class="data">0.00012345 FAKE</td>`,
|
||||||
`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
|
`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
|
||||||
|
@ -375,7 +375,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
body: []string{
|
body: []string{
|
||||||
`<a class="navbar-brand" href="/">Fake Coin Explorer</a>`,
|
`<a class="navbar-brand" href="/">Fake Coin Explorer</a>`,
|
||||||
`<h1>Address`,
|
`<h1>Address`,
|
||||||
`<small class="text-muted">0 FAKE</small>`,
|
`<small class="text-muted">0.00012345 FAKE</small>`,
|
||||||
`<span class="data">mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz</span>`,
|
`<span class="data">mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz</span>`,
|
||||||
`<td class="data">0.00012345 FAKE</td>`,
|
`<td class="data">0.00012345 FAKE</td>`,
|
||||||
`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
|
`<a href="/tx/7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25">7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25</a>`,
|
||||||
|
@ -506,7 +506,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
body: []string{
|
body: []string{
|
||||||
`{"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":1521515026,"blocktime":1521515026}`,
|
`{"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}},{"ValueSat":12345,"value":0,"n":2,"scriptPubKey":{"hex":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","addresses":null}}],"confirmations":2,"time":1521515026,"blocktime":1521515026}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -776,7 +776,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
body: []string{
|
body: []string{
|
||||||
`[{"time":1521514800,"txs":1,"received":"12345","sent":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","rates":{"eur":1303,"usd":2003}}]`,
|
`[{"time":1521514800,"txs":1,"received":"24690","sent":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","rates":{"eur":1303,"usd":2003}}]`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -803,7 +803,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
body: []string{
|
body: []string{
|
||||||
`[{"time":1521514800,"txs":1,"received":"12345","sent":"0","rates":{"eur":1301,"usd":2001}}]`,
|
`[{"time":1521514800,"txs":1,"received":"24690","sent":"0","rates":{"eur":1301,"usd":2001}}]`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -884,7 +884,7 @@ func httpTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
status: http.StatusOK,
|
status: http.StatusOK,
|
||||||
contentType: "application/json; charset=utf-8",
|
contentType: "application/json; charset=utf-8",
|
||||||
body: []string{
|
body: []string{
|
||||||
`{"page":1,"totalPages":1,"itemsOnPage":1000,"hash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","nextBlockHash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","height":225493,"confirmations":2,"size":1234567,"time":1521515026,"version":0,"merkleRoot":"","nonce":"","bits":"","difficulty":"","txCount":2,"txs":[{"txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","vin":[],"vout":[{"value":"100000000","n":0,"addresses":["mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti"],"isAddress":true},{"value":"12345","n":1,"spent":true,"addresses":["mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"],"isAddress":true}],"blockHash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","blockHeight":225493,"confirmations":2,"blockTime":1521515026,"value":"100012345","valueIn":"0","fees":"0"},{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vin":[],"vout":[{"value":"1234567890123","n":0,"spent":true,"addresses":["mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"],"isAddress":true},{"value":"1","n":1,"spent":true,"addresses":["2MzmAKayJmja784jyHvRUW1bXPget1csRRG"],"isAddress":true},{"value":"9876","n":2,"spent":true,"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"isAddress":true}],"blockHash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","blockHeight":225493,"confirmations":2,"blockTime":1521515026,"value":"1234567900000","valueIn":"0","fees":"0"}]}`,
|
`{"page":1,"totalPages":1,"itemsOnPage":1000,"hash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","nextBlockHash":"00000000eb0443fd7dc4a1ed5c686a8e995057805f9a161d9a5a77a95e72b7b6","height":225493,"confirmations":2,"size":1234567,"time":1521515026,"version":0,"merkleRoot":"","nonce":"","bits":"","difficulty":"","txCount":2,"txs":[{"txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","vin":[],"vout":[{"value":"100000000","n":0,"addresses":["mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti"],"isAddress":true},{"value":"12345","n":1,"spent":true,"addresses":["mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"],"isAddress":true},{"value":"12345","n":2,"addresses":["mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"],"isAddress":true}],"blockHash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","blockHeight":225493,"confirmations":2,"blockTime":1521515026,"value":"100024690","valueIn":"0","fees":"0"},{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","vin":[],"vout":[{"value":"1234567890123","n":0,"spent":true,"addresses":["mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw"],"isAddress":true},{"value":"1","n":1,"spent":true,"addresses":["2MzmAKayJmja784jyHvRUW1bXPget1csRRG"],"isAddress":true},{"value":"9876","n":2,"spent":true,"addresses":["2NEVv9LJmAnY99W1pFoc5UJjVdypBqdnvu1"],"isAddress":true}],"blockHash":"0000000076fbbed90fd75b0e18856aa35baa984e9c9d444cf746ad85e94e2997","blockHeight":225493,"confirmations":2,"blockTime":1521515026,"value":"1234567900000","valueIn":"0","fees":"0"}]}`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -986,7 +986,7 @@ func socketioTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
"to": 5,
|
"to": 5,
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
want: `{"result":{"totalCount":2,"items":[{"addresses":{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz":{"inputIndexes":[1],"outputIndexes":[]}},"satoshis":-12345,"confirmations":1,"tx":{"hex":"","height":225494,"blockTimestamp":1521595678,"version":0,"hash":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","inputs":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","outputIndex":0,"script":"","sequence":0,"address":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","satoshis":1234567890123},{"txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","outputIndex":1,"script":"","sequence":0,"address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz","satoshis":12345}],"inputSatoshis":1234567902468,"outputs":[{"satoshis":317283951061,"script":"76a914ccaaaf374e1b06cb83118453d102587b4273d09588ac","address":"mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX"},{"satoshis":917283951061,"script":"76a9148d802c045445df49613f6a70ddd2e48526f3701f88ac","address":"mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"},{"satoshis":0,"script":"6a072020f1686f6a20","address":"OP_RETURN 2020f1686f6a20"}],"outputSatoshis":1234567902122,"feeSatoshis":346}},{"addresses":{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz":{"inputIndexes":[],"outputIndexes":[1]}},"satoshis":12345,"confirmations":2,"tx":{"hex":"","height":225493,"blockTimestamp":1521515026,"version":0,"hash":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","inputs":[],"outputs":[{"satoshis":100000000,"script":"76a914010d39800f86122416e28f485029acf77507169288ac","address":"mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti"},{"satoshis":12345,"script":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"}],"outputSatoshis":100012345}}]}}`,
|
want: `{"result":{"totalCount":2,"items":[{"addresses":{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz":{"inputIndexes":[1],"outputIndexes":[]}},"satoshis":-12345,"confirmations":1,"tx":{"hex":"","height":225494,"blockTimestamp":1521595678,"version":0,"hash":"7c3be24063f268aaa1ed81b64776798f56088757641a34fb156c4f51ed2e9d25","inputs":[{"txid":"effd9ef509383d536b1c8af5bf434c8efbf521a4f2befd4022bbd68694b4ac75","outputIndex":0,"script":"","sequence":0,"address":"mv9uLThosiEnGRbVPS7Vhyw6VssbVRsiAw","satoshis":1234567890123},{"txid":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","outputIndex":1,"script":"","sequence":0,"address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz","satoshis":12345}],"inputSatoshis":1234567902468,"outputs":[{"satoshis":317283951061,"script":"76a914ccaaaf374e1b06cb83118453d102587b4273d09588ac","address":"mzB8cYrfRwFRFAGTDzV8LkUQy5BQicxGhX"},{"satoshis":917283951061,"script":"76a9148d802c045445df49613f6a70ddd2e48526f3701f88ac","address":"mtR97eM2HPWVM6c8FGLGcukgaHHQv7THoL"},{"satoshis":0,"script":"6a072020f1686f6a20","address":"OP_RETURN 2020f1686f6a20"}],"outputSatoshis":1234567902122,"feeSatoshis":346}},{"addresses":{"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz":{"inputIndexes":[],"outputIndexes":[1,2]}},"satoshis":24690,"confirmations":2,"tx":{"hex":"","height":225493,"blockTimestamp":1521515026,"version":0,"hash":"00b2c06055e5e90e9c82bd4181fde310104391a7fa4f289b1704e5d90caa3840","inputs":[],"outputs":[{"satoshis":100000000,"script":"76a914010d39800f86122416e28f485029acf77507169288ac","address":"mfcWp7DB6NuaZsExybTTXpVgWz559Np4Ti"},{"satoshis":12345,"script":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"},{"satoshis":12345,"script":"76a9148bdf0aa3c567aa5975c2e61321b8bebbe7293df688ac","address":"mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz"}],"outputSatoshis":100024690}}]}}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "socketio getBlockHeader",
|
name: "socketio getBlockHeader",
|
||||||
|
@ -1370,7 +1370,7 @@ func websocketTestsBitcoinType(t *testing.T, ts *httptest.Server) {
|
||||||
"descriptor": "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz",
|
"descriptor": "mtGXQvBowMkBpnhLckhxhbwYK44Gs9eEtz",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
want: `{"id":"32","data":[{"time":1521514800,"txs":1,"received":"12345","sent":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","rates":{"eur":1303,"usd":2003}}]}`,
|
want: `{"id":"32","data":[{"time":1521514800,"txs":1,"received":"24690","sent":"0","rates":{"eur":1301,"usd":2001}},{"time":1521594000,"txs":1,"received":"0","sent":"12345","rates":{"eur":1303,"usd":2003}}]}`,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "websocket getBalanceHistory xpub",
|
name: "websocket getBalanceHistory xpub",
|
||||||
|
|
|
@ -35,18 +35,19 @@ const (
|
||||||
|
|
||||||
// Amounts in satoshis
|
// Amounts in satoshis
|
||||||
var (
|
var (
|
||||||
SatZero = big.NewInt(0)
|
SatZero = big.NewInt(0)
|
||||||
SatB1T1A1 = big.NewInt(100000000)
|
SatB1T1A1 = big.NewInt(100000000)
|
||||||
SatB1T1A2 = big.NewInt(12345)
|
SatB1T1A2 = big.NewInt(12345)
|
||||||
SatB1T2A3 = big.NewInt(1234567890123)
|
SatB1T1A2Double = big.NewInt(12345 * 2)
|
||||||
SatB1T2A4 = big.NewInt(1)
|
SatB1T2A3 = big.NewInt(1234567890123)
|
||||||
SatB1T2A5 = big.NewInt(9876)
|
SatB1T2A4 = big.NewInt(1)
|
||||||
SatB2T1A6 = big.NewInt(317283951061)
|
SatB1T2A5 = big.NewInt(9876)
|
||||||
SatB2T1A7 = big.NewInt(917283951061)
|
SatB2T1A6 = big.NewInt(317283951061)
|
||||||
SatB2T2A8 = big.NewInt(118641975500)
|
SatB2T1A7 = big.NewInt(917283951061)
|
||||||
SatB2T2A9 = big.NewInt(198641975500)
|
SatB2T2A8 = big.NewInt(118641975500)
|
||||||
SatB2T3A5 = big.NewInt(9000)
|
SatB2T2A9 = big.NewInt(198641975500)
|
||||||
SatB2T4AA = big.NewInt(1360030331)
|
SatB2T3A5 = big.NewInt(9000)
|
||||||
|
SatB2T4AA = big.NewInt(1360030331)
|
||||||
)
|
)
|
||||||
|
|
||||||
// AddressToPubKeyHex is a utility conversion function
|
// AddressToPubKeyHex is a utility conversion function
|
||||||
|
@ -90,6 +91,13 @@ func GetTestBitcoinTypeBlock1(parser bchain.BlockChainParser) *bchain.Block {
|
||||||
},
|
},
|
||||||
ValueSat: *SatB1T1A2,
|
ValueSat: *SatB1T1A2,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
N: 2,
|
||||||
|
ScriptPubKey: bchain.ScriptPubKey{
|
||||||
|
Hex: AddressToPubKeyHex(Addr2, parser),
|
||||||
|
},
|
||||||
|
ValueSat: *SatB1T1A2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
Blocktime: 1521515026,
|
Blocktime: 1521515026,
|
||||||
Time: 1521515026,
|
Time: 1521515026,
|
||||||
|
|
Loading…
Reference in New Issue