Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for legacy RPC calls #2386

Merged
merged 20 commits into from
Dec 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
f90657f
Updated quai API transactionArgs for Qi TxIn and TxOut
jdowning100 Oct 29, 2024
134eba7
EstimateGas ignores provided nonce and sets correct nonce from state
jdowning100 Oct 30, 2024
a1c5015
UTXO indexer bugfix: don't load outpoints for address that shouldn't …
jdowning100 Oct 30, 2024
12c0f0a
getTransactionByHash returns Qi tx from pool if it exists
jdowning100 Oct 30, 2024
02c2da9
getBalance does not return locked Qi UTXOs and added getLockedBalance…
jdowning100 Oct 30, 2024
9dcc2e9
Added Quai lockups to indexer and added GetLockupsByAddress and GetLo…
jdowning100 Oct 31, 2024
ea73a8c
Added --node.reindex flag that reindexes the UTXO/lockup indexer from…
jdowning100 Oct 31, 2024
8aa8ce0
Feat: Add chain indexer validation logic and node.validate-indexer flag
jdowning100 Nov 1, 2024
fcc7421
Fixed indexer so consensus UTXO set is not read from
jdowning100 Nov 12, 2024
2b401c6
Added quai_getOutpointDeltasForAddressesInRange API
jdowning100 Nov 12, 2024
2a6144b
Reindex bloom filters
jdowning100 Nov 14, 2024
cab804b
Fix ethclient
jdowning100 Nov 18, 2024
42bb3ba
Added Quai->Qi conversion case for quai_estimateGas RPC
jdowning100 Nov 21, 2024
ce8df0a
Reset gasprice/basefee and minertip to zero in estimateGas and create…
jdowning100 Nov 27, 2024
79a31dc
Added legacy Eth API methods
jdowning100 Nov 8, 2024
01cc329
Manually search logs
jdowning100 Nov 13, 2024
7fb1864
Updated Logs filtering to run in parallel
jdowning100 Nov 14, 2024
04a2df4
Refactor chain indexer to use an address-utxo map per block
jdowning100 Dec 4, 2024
9f6d6bc
Check if UTXO exists on disk before responding
jdowning100 Dec 5, 2024
34c7263
Enforce max block range of 10k blocks
jdowning100 Dec 10, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions cmd/utils/cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ func defaultNodeConfig() node.Config {
cfg.Name = ""
cfg.Version = params.VersionWithCommit("", "")
cfg.HTTPModules = append(cfg.HTTPModules, "quai")
cfg.HTTPModules = append(cfg.HTTPModules, "net")
cfg.WSModules = append(cfg.WSModules, "quai")
return cfg
}
Expand Down
14 changes: 14 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ var NodeFlags = []Flag{
QuaiStatsURLFlag,
SendFullStatsFlag,
IndexAddressUtxos,
ReIndex,
ValidateIndexer,
StartingExpansionNumberFlag,
NodeLogLevelFlag,
GenesisNonce,
Expand Down Expand Up @@ -544,6 +546,18 @@ var (
Usage: "Index address utxos" + generateEnvDoc(c_NodeFlagPrefix+"index-address-utxos"),
}

ReIndex = Flag{
Name: c_NodeFlagPrefix + "reindex",
Value: false,
Usage: "Re-index the UTXO indexer. This will take a long time!" + generateEnvDoc(c_NodeFlagPrefix+"reindex"),
}

ValidateIndexer = Flag{
Name: c_NodeFlagPrefix + "validate-indexer",
Value: false,
Usage: "Validate the UTXO indexer. This will take a long time!" + generateEnvDoc(c_NodeFlagPrefix+"validate-index"),
}

EnvironmentFlag = Flag{
Name: c_NodeFlagPrefix + "environment",
Value: params.ColosseumName,
Expand Down
239 changes: 239 additions & 0 deletions cmd/utils/hierarchical_coordinator.go
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
package utils

import (
"encoding/binary"
"errors"
"fmt"
"math/big"
"path/filepath"
"runtime/debug"
"sort"
"sync"
"time"

"github.com/dominant-strategies/go-quai/common"
"github.com/dominant-strategies/go-quai/core"
"github.com/dominant-strategies/go-quai/core/rawdb"
"github.com/dominant-strategies/go-quai/core/types"
"github.com/dominant-strategies/go-quai/event"
"github.com/dominant-strategies/go-quai/internal/quaiapi"
Expand Down Expand Up @@ -260,6 +263,12 @@ func NewHierarchicalCoordinator(p2p quai.NetworkingAPI, logLevel string, nodeWg
if err != nil {
log.Global.WithField("err", err).Fatal("Error opening the backend db")
}
if viper.GetBool(ReIndex.Name) {
ReIndexChainIndexer()
}
if viper.GetBool(ValidateIndexer.Name) {
ValidateChainIndexer()
}
hc := &HierarchicalCoordinator{
wg: nodeWg,
db: db,
Expand Down Expand Up @@ -1321,3 +1330,233 @@ func (hc *HierarchicalCoordinator) GetBackendForLocationAndOrder(location common
}
return nil
}

func ReIndexChainIndexer() {
providedDataDir := viper.GetString(DataDirFlag.Name)
if providedDataDir == "" {
log.Global.Fatal("Data directory not provided for reindexing")
}
dbDir := filepath.Join(filepath.Join(providedDataDir, "zone-0-0/go-quai"), "chaindata")
Djadih marked this conversation as resolved.
Show resolved Hide resolved
ancientDir := filepath.Join(dbDir, "ancient")
zoneDb, err := rawdb.Open(rawdb.OpenOptions{
Type: "leveldb",
Directory: dbDir,
AncientsDirectory: ancientDir,
Namespace: "eth/db/chaindata/",
Cache: 512,
Handles: 5120,
ReadOnly: false,
}, common.ZONE_CTX, log.Global, common.Location{0, 0})
if err != nil {
log.Global.WithField("err", err).Fatal("Error opening the zone db for reindexing")
}
core.ReIndexChainIndexer(zoneDb)
if err := zoneDb.Close(); err != nil {
log.Global.WithField("err", err).Fatal("Error closing the zone db")
}
time.Sleep(10 * time.Second)
}

func ValidateChainIndexer() {
providedDataDir := viper.GetString(DataDirFlag.Name)
if providedDataDir == "" {
log.Global.Fatal("Data directory not provided for reindexing")
}
dbDir := filepath.Join(filepath.Join(providedDataDir, "zone-0-0/go-quai"), "chaindata")
ancientDir := filepath.Join(dbDir, "ancient")
zoneDb, err := rawdb.Open(rawdb.OpenOptions{
Type: "leveldb",
Directory: dbDir,
AncientsDirectory: ancientDir,
Namespace: "eth/db/chaindata/",
Cache: 512,
Handles: 5120,
ReadOnly: false,
}, common.ZONE_CTX, log.Global, common.Location{0, 0})
if err != nil {
log.Global.WithField("err", err).Fatal("Error opening the zone db for reindexing")
}
start := time.Now()
head := rawdb.ReadHeadBlockHash(zoneDb)
if head == (common.Hash{}) {
log.Global.Fatal("Head block hash not found")
}
headNum := rawdb.ReadHeaderNumber(zoneDb, head)
latestSetSize := rawdb.ReadUTXOSetSize(zoneDb, head)
log.Global.Infof("Starting the UTXO indexer validation for height %d set size %d", *headNum, latestSetSize)
i := 0
utxosChecked := make(map[[34]byte]uint8)
it := zoneDb.NewIterator(rawdb.UtxoPrefix, nil)
for it.Next() {
if len(it.Key()) != rawdb.UtxoKeyLength {
continue
}
data := it.Value()
if len(data) == 0 {
log.Global.Infof("Empty key found")
continue
}
utxoProto := new(types.ProtoTxOut)
if err := proto.Unmarshal(data, utxoProto); err != nil {
log.Global.Errorf("Failed to unmarshal ProtoTxOut: %+v data: %+v key: %+v", err, data, it.Key())
continue
}

utxo := new(types.UtxoEntry)
if err := utxo.ProtoDecode(utxoProto); err != nil {
log.Global.WithFields(log.Fields{
"key": it.Key(),
"data": data,
"err": err,
}).Error("Invalid utxo Proto")
continue
}
txHash, index, err := rawdb.ReverseUtxoKey(it.Key())
if err != nil {
log.Global.WithField("err", err).Error("Failed to parse utxo key")
continue
}
u16 := make([]byte, 2)
binary.BigEndian.PutUint16(u16, index)
key := [34]byte(append(txHash.Bytes(), u16...))
if _, exists := utxosChecked[key]; exists {
log.Global.WithField("hash", key).Error("Duplicate utxo found")
continue
}
height := rawdb.ReadUtxoToBlockHeight(zoneDb, txHash, index)
addr20 := common.BytesToAddress(utxo.Address, common.Location{0, 0}).Bytes20()
binary.BigEndian.PutUint32(addr20[16:], height)
outpoints, err := rawdb.ReadOutpointsForAddressAtBlock(zoneDb, addr20)
if err != nil {
log.Global.WithField("err", err).Error("Error reading outpoints for address")
continue
}
found := false
for _, outpoint := range outpoints {
if outpoint.TxHash == txHash && outpoint.Index == index {
utxosChecked[key] = outpoint.Denomination
found = true
}
}
if !found {
log.Global.WithFields(log.Fields{
"tx": txHash,
"index": index,
}).Error("Utxo not found in outpoints")
prefix := append(rawdb.AddressUtxosPrefix, addr20.Bytes()[:16]...)
it2 := zoneDb.NewIterator(prefix, nil)
for it2.Next() {
if len(it.Key()) != len(rawdb.AddressUtxosPrefix)+common.AddressLength {
continue
}
addressOutpointsProto := &types.ProtoAddressOutPoints{
OutPoints: make([]*types.ProtoOutPointAndDenomination, 0),
}
if err := proto.Unmarshal(it.Value(), addressOutpointsProto); err != nil {
log.Global.WithField("err", err).Fatal("Failed to proto Unmarshal addressOutpointsProto")
continue
}
for _, outpointProto := range addressOutpointsProto.OutPoints {
outpoint := new(types.OutpointAndDenomination)
if err := outpoint.ProtoDecode(outpointProto); err != nil {
log.Global.WithFields(log.Fields{
"err": err,
"outpoint": outpointProto,
}).Error("Invalid outpointProto")
continue
}
if outpoint.TxHash == txHash && outpoint.Index == index {
log.Global.WithFields(log.Fields{
"tx": txHash,
"index": index,
}).Error("Utxo found in address outpoints")
utxosChecked[key] = outpoint.Denomination
found = true
}
}
}
it2.Release()
}
i++
if i%100000 == 0 {
log.Global.Infof("Checked %d utxos out of %d total elapsed %s", i, latestSetSize, common.PrettyDuration(time.Since(start)))
}
}
it.Release()
log.Global.Infof("Checked %d utxos and %d are good, elapsed %s", i, len(utxosChecked), common.PrettyDuration(time.Since(start)))
if len(utxosChecked) != int(latestSetSize) {
log.Global.WithFields(log.Fields{
"expected": latestSetSize,
"actual": len(utxosChecked),
}).Error("Mismatch in utxo set size")
}
log.Global.Infof("Checking for duplicates in Address Outpoints Index...")
utxosChecked_ := make(map[[34]byte]uint8)
duplicatesFound := false
it = zoneDb.NewIterator(rawdb.AddressUtxosPrefix, nil)
for it.Next() {
if len(it.Key()) != len(rawdb.AddressUtxosPrefix)+common.AddressLength {
continue
}
addressOutpointsProto := &types.ProtoAddressOutPoints{
OutPoints: make([]*types.ProtoOutPointAndDenomination, 0),
}
if err := proto.Unmarshal(it.Value(), addressOutpointsProto); err != nil {
log.Global.WithField("err", err).Fatal("Failed to proto Unmarshal addressOutpointsProto")
continue
}
for _, outpointProto := range addressOutpointsProto.OutPoints {
outpoint := new(types.OutpointAndDenomination)
if err := outpoint.ProtoDecode(outpointProto); err != nil {
log.Global.WithFields(log.Fields{
"err": err,
"outpoint": outpointProto,
}).Error("Invalid outpointProto")
continue
}
u16 := make([]byte, 2)
binary.BigEndian.PutUint16(u16, outpoint.Index)
key := [34]byte(append(outpoint.TxHash.Bytes(), u16...))
if _, exists := utxosChecked_[key]; exists {
log.Global.WithFields(log.Fields{
"tx": outpoint.TxHash.String(),
"index": outpoint.Index,
}).Error("Duplicate outpoint found")
duplicatesFound = true
continue
}
utxosChecked_[key] = outpoint.Denomination
}
}
it.Release()
if len(utxosChecked_) != int(latestSetSize) {
log.Global.WithFields(log.Fields{
"expected": latestSetSize,
"actual": len(utxosChecked_),
}).Error("Mismatch in utxo set size")
time.Sleep(5 * time.Second)
if len(utxosChecked_) > len(utxosChecked) {
log.Global.Infof("Finding diff...")
for key, val := range utxosChecked_ {
if _, exists := utxosChecked[key]; !exists {
txhash := key[:32]
index := binary.BigEndian.Uint16(key[32:])
log.Global.WithFields(log.Fields{
"tx": common.BytesToHash(txhash).String(),
"index": index,
"denomination": val,
}).Error("Missing key")
}
}
}
}
if duplicatesFound {
log.Global.Error("Duplicates found in address outpoints")
} else {
log.Global.Info("No duplicates found in address-outpoints index. Validation completed")
}
if err := zoneDb.Close(); err != nil {
log.Global.WithField("err", err).Fatal("Error closing the zone db")
}
time.Sleep(30 * time.Second)
}
Loading
Loading