Skip to content

Commit

Permalink
Merge pull request #2783 from gobitfly/BIDS-2437/ens
Browse files Browse the repository at this point in the history
(BIDS-2437) ens: make sure that reverse-resolve resolves to the same …
  • Loading branch information
recy21 authored Dec 27, 2023
2 parents 1c76da5 + baea4f1 commit 29f3739
Show file tree
Hide file tree
Showing 3 changed files with 274 additions and 20 deletions.
248 changes: 248 additions & 0 deletions cmd/misc/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bytes"
"context"
"database/sql"
"encoding/json"
"eth2-exporter/cmd/misc/commands"
"eth2-exporter/db"
Expand All @@ -22,9 +23,11 @@ import (
"time"

"github.com/coocood/freecache"
"github.com/ethereum/go-ethereum/common"
_ "github.com/jackc/pgx/v5/stdlib"
"github.com/pkg/errors"
utilMath "github.com/protolambda/zrnt/eth2/util/math"
go_ens "github.com/wealdtech/go-ens/v3"
"golang.org/x/sync/errgroup"

"flag"
Expand All @@ -35,6 +38,7 @@ import (
var opts = struct {
Command string
User uint64
Addresses string
TargetVersion int64
StartEpoch uint64
EndEpoch uint64
Expand Down Expand Up @@ -75,6 +79,7 @@ func main() {
flag.Uint64Var(&opts.BatchSize, "data.batchSize", 1000, "Batch size")
flag.StringVar(&opts.Transformers, "transformers", "", "Comma separated list of transformers used by the eth1 indexer")
flag.StringVar(&opts.ValidatorNameRanges, "validator-name-ranges", "https://config.dencun-devnet-8.ethpandaops.io/api/v1/nodes/validator-ranges", "url to or json of validator-ranges (format must be: {'ranges':{'X-Y':'name'}})")
flag.StringVar(&opts.Addresses, "addresses", "", "Comma separated list of addresses that should be processed by the command")
flag.StringVar(&opts.Columns, "columns", "", "Comma separated list of columns that should be affected by the command")
dryRun := flag.String("dry-run", "true", "if 'false' it deletes all rows starting with the key, per default it only logs the rows that would be deleted, but does not really delete them")
versionFlag := flag.Bool("version", false, "Show version and exit")
Expand Down Expand Up @@ -376,6 +381,10 @@ func main() {
case "partition-validator-stats":
statsPartitionCommand.Config.DryRun = opts.DryRun
err = statsPartitionCommand.StartStatsPartitionCommand()
case "fix-ens":
err = fixEns(erigonClient)
case "fix-ens-addresses":
err = fixEnsAddresses(erigonClient)
default:
utils.LogFatal(nil, fmt.Sprintf("unknown command %s", opts.Command), 0)
}
Expand All @@ -387,6 +396,245 @@ func main() {
}
}

func fixEns(erigonClient *rpc.ErigonClient) error {
logrus.Infof("command: fix-ens")
addrs := []struct {
Address []byte `db:"address"`
EnsName string `db:"ens_name"`
}{}
err := db.WriterDb.Select(&addrs, `select address, ens_name from ens where is_primary_name = true`)
if err != nil {
return err
}

logrus.Infof("found %v ens entries", len(addrs))

g := new(errgroup.Group)
g.SetLimit(10) // limit load on the node

batchSize := 100
total := len(addrs)
for i := 0; i < total; i += batchSize {
to := i + batchSize
if to > total {
to = total
}
batch := addrs[i:to]

logrus.Infof("processing batch %v-%v / %v", i, to, total)
for _, addr := range batch {
addr := addr
g.Go(func() error {
ensAddr, err := go_ens.Resolve(erigonClient.GetNativeClient(), addr.EnsName)
if err != nil {
if err.Error() == "unregistered name" ||
err.Error() == "no address" ||
err.Error() == "no resolver" ||
err.Error() == "abi: attempting to unmarshall an empty string while arguments are expected" ||
strings.Contains(err.Error(), "execution reverted") ||
err.Error() == "invalid jump destination" {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("failed resolve: %v", err.Error())}).Warnf("deleting ens entry")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`delete from ens where address = $1 and ens_name = $2`, addr.Address, addr.EnsName)
if err != nil {
return err
}
}
return nil
}
return err
}

dbAddr := common.BytesToAddress(addr.Address)
if dbAddr.Cmp(ensAddr) != 0 {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("dbAddr != resolved ensAddr: %#x != %#x", addr.Address, ensAddr.Bytes())}).Warnf("deleting ens entry")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`delete from ens where address = $1 and ens_name = $2`, addr.Address, addr.EnsName)
if err != nil {
return err
}
}
}

reverseName, err := go_ens.ReverseResolve(erigonClient.GetNativeClient(), dbAddr)
if err != nil {
if err.Error() == "not a resolver" || err.Error() == "no resolution" {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("failed reverse-resolve: %v", err.Error())}).Warnf("updating ens entry: is_primary_name = false")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`update ens set is_primary_name = false where address = $1 and ens_name = $2`, addr.Address, addr.EnsName)
if err != nil {
return err
}
}
return nil
}
return err
}

if reverseName != addr.EnsName {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%#x", addr.Address), "name": addr.EnsName, "reason": fmt.Sprintf("resolved != reverseResolved: %v != %v", addr.EnsName, reverseName)}).Warnf("updating ens entry: is_primary_name = false")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`update ens set is_primary_name = false where address = $1 and ens_name = $2`, addr.Address, addr.EnsName)
if err != nil {
return err
}
}
}

return nil
})
}

err = g.Wait()
if err != nil {
return err
}
time.Sleep(time.Millisecond * 100)
}
return nil
}

func fixEnsAddresses(erigonClient *rpc.ErigonClient) error {
logrus.WithFields(logrus.Fields{"dry": opts.DryRun}).Infof("command: fix-ens-addresses")
if opts.Addresses == "" {
return errors.New("no addresses specified")
}

type DbEntry struct {
NameHash []byte `db:"name_hash"`
EnsName string `db:"ens_name"`
Address []byte `db:"address"`
IsPrimaryName bool `db:"is_primary_name"`
ValidTo time.Time `db:"valid_to"`
}

for _, addrHex := range strings.Split(opts.Addresses, ",") {
if !common.IsHexAddress(addrHex) {
return fmt.Errorf("invalid address: %v", addrHex)
}

addr := common.HexToAddress(addrHex)

dbEntry := &DbEntry{}
err := db.WriterDb.Get(dbEntry, `select name_hash, ens_name, address, is_primary_name, valid_to from ens where address = $1`, addr.Bytes())
if err != nil && err != sql.ErrNoRows {
return fmt.Errorf("error getting ens entry for addr [%v]: %w", addr.Hex(), err)
}
if err == sql.ErrNoRows {
dbEntry = nil
}

name, err := go_ens.ReverseResolve(erigonClient.GetNativeClient(), addr)
if err != nil {
if err.Error() == "not a resolver" ||
err.Error() == "no resolution" {
logrus.WithFields(logrus.Fields{"addr": addr.Hex()}).Warnf("error reverse-resolving name: %v", err)
if dbEntry != nil {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%v", addr.Hex()), "reason": fmt.Sprintf("error reverse-resolving name: %v", err)}).Warnf("deleting ens entry")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`delete from ens where address = $1`, addr.Bytes())
if err != nil {
return fmt.Errorf("error deleting ens entry: %w", err)
}
}
}
continue
} else {
return fmt.Errorf("error go_ens.ReverseResolve for addr %v: %w", addr.Hex(), err)
}
}

if !strings.HasSuffix(name, ".eth") {
logrus.Infof("need to add .eth to %v for addr %v", name, addr.Hex())
name = name + ".eth"
}

resolvedAddr, err := go_ens.Resolve(erigonClient.GetNativeClient(), name)
if err != nil {
if err.Error() == "unregistered name" ||
err.Error() == "no address" ||
err.Error() == "no resolver" ||
err.Error() == "abi: attempting to unmarshall an empty string while arguments are expected" ||
strings.Contains(err.Error(), "execution reverted") ||
err.Error() == "invalid jump destination" {
if dbEntry != nil {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%v", addr.Hex()), "reason": fmt.Sprintf("error resolving name: %v", err)}).Warnf("deleting ens entry")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`delete from ens where address = $1`, addr.Bytes())
if err != nil {
return fmt.Errorf("error deleting ens entry: %w", err)
}
}
}
} else {
return fmt.Errorf("error go_ens.Resolve(%v) for addr %v: %w", name, addr.Hex(), err)
}
}

if !bytes.Equal(resolvedAddr.Bytes(), addr.Bytes()) {
logrus.WithFields(logrus.Fields{"addr": fmt.Sprintf("%v", addr.Hex()), "reason": fmt.Sprintf("addr != resolvedAddr: %v != %v", addr.Hex(), resolvedAddr.Hex())}).Warnf("deleting ens entry")
if !opts.DryRun {
_, err = db.WriterDb.Exec(`delete from ens where address = $1`, addr.Bytes())
if err != nil {
return fmt.Errorf("error deleting ens entry: %w", err)
}
}
}

nameHash, err := go_ens.NameHash(name)
if err != nil {
return fmt.Errorf("error go_ens.NameHash(%v) for addr %v: %w", name, addr.Hex(), err)
}
parts := strings.Split(name, ".")
mainName := strings.Join(parts[len(parts)-2:], ".")
ensName, err := go_ens.NewName(erigonClient.GetNativeClient(), mainName)
if err != nil {
return fmt.Errorf("error could not create name via go_ens.NewName for [%v]: %w", name, err)
}
expires, err := ensName.Expires()
if err != nil {
return fmt.Errorf("error could not get ens expire date for [%v]: %w", name, err)
}

if dbEntry == nil || dbEntry.EnsName != name || !bytes.Equal(dbEntry.NameHash, nameHash[:]) || !bytes.Equal(dbEntry.Address, resolvedAddr.Bytes()) || dbEntry.ValidTo != expires {
logFields := logrus.Fields{"resolvedAddr": resolvedAddr, "addr": addr.Hex(), "name": name, "nameHash": fmt.Sprintf("%#x", nameHash), "expires": expires}
if dbEntry == nil {
logFields["db"] = "nil"
logrus.WithFields(logFields).Warnf("adding ens entry")
} else {
logFields["db.name"] = dbEntry.EnsName
logFields["db.nameHash"] = fmt.Sprintf("%#x", dbEntry.NameHash)
logFields["db.addr"] = fmt.Sprintf("%#x", dbEntry.Address)
logFields["db.expire"] = dbEntry.ValidTo
logrus.WithFields(logFields).Warnf("updating ens entry")
}

if !opts.DryRun {
_, err = db.WriterDb.Exec(`
INSERT INTO ens (
name_hash,
ens_name,
address,
is_primary_name,
valid_to)
VALUES ($1, $2, $3, $4, $5)
ON CONFLICT
(name_hash)
DO UPDATE SET
ens_name = excluded.ens_name,
address = excluded.address,
is_primary_name = excluded.is_primary_name,
valid_to = excluded.valid_to`,
nameHash[:], name, addr.Bytes(), true, expires)
if err != nil {
return fmt.Errorf("error writing ens data for addr [%v]: %w", addr.Hex(), err)
}
}
}
}
return nil
}

func fixExecTransactionsCount() error {
startBlockNumber := uint64(opts.StartBlock)
endBlockNumber := uint64(opts.EndBlock)
Expand Down
35 changes: 17 additions & 18 deletions db/ens.go
Original file line number Diff line number Diff line change
Expand Up @@ -512,20 +512,21 @@ func validateEnsName(client *ethclient.Client, name string, alreadyChecked *EnsC
addr, err := go_ens.Resolve(client, name)
if err != nil {
utils.LogError(err, "error, could not resolve name", 0, map[string]interface{}{"name": name})
/* if err.Error() == "unregistered name" ||
err.Error() == "no address" ||
err.Error() == "no resolver" ||
err.Error() == "abi: attempting to unmarshall an empty string while arguments are expected" ||
strings.Contains(err.Error(), "execution reverted") ||
err.Error() == "invalid jump destination" { */
// the given name is not available anymore or resolving it did not work properly => we can remove it from the db (if it is there)
err = removeEnsName(client, name)
if err != nil {
return fmt.Errorf("error removing ens name [%v]: %w", name, err)
if err.Error() == "unregistered name" ||
err.Error() == "no address" ||
err.Error() == "no resolver" ||
err.Error() == "abi: attempting to unmarshall an empty string while arguments are expected" ||
strings.Contains(err.Error(), "execution reverted") ||
err.Error() == "invalid jump destination" ||
err.Error() == "invalid opcode: INVALID" {
// the given name is not available anymore or resolving it did not work properly => we can remove it from the db (if it is there)
err = removeEnsName(client, name)
if err != nil {
return fmt.Errorf("error removing ens name after resolve failed [%v]: %w", name, err)
}
return nil
}
return nil
// }
// return fmt.Errorf("error could not resolve name [%v]: %w", name, err)
return fmt.Errorf("error could not resolve name [%v]: %w", name, err)
}

// we need to get the main domain to get the expiration date
Expand All @@ -545,13 +546,11 @@ func validateEnsName(client *ethclient.Client, name string, alreadyChecked *EnsC
reverseName, err := go_ens.ReverseResolve(client, addr)
if err != nil {
if err.Error() == "not a resolver" || err.Error() == "no resolution" {
logger.Warnf("reverse resolving address [%v] resulted in a skippable error [%s], skipping it", addr, err.Error())
return nil
logger.Warnf("reverse resolving address [%v] for name [%v] resulted in an error [%s], marking entry as not primary", addr, name, err.Error())
} else {
return fmt.Errorf("error could not reverse resolve address [%v]: %w", addr, err)
}

return fmt.Errorf("error could not reverse resolve address [%v]: %w", addr, err)
}

if reverseName == name {
isPrimary = true
}
Expand Down
11 changes: 9 additions & 2 deletions handlers/ens.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,26 @@ func GetEnsDomain(search string) (*types.EnsDomainResponse, error) {
var returnError error

if utils.IsValidEnsDomain(search) {
data.Domain = search

cacheKey := fmt.Sprintf("%d:ens:address:%v", utils.Config.Chain.ClConfig.DepositChainID, search)

if address, err := cache.TieredCache.GetStringWithLocalTimeout(cacheKey, time.Minute); err == nil && len(address) > 0 {
data.Address = address
return data, nil
}

address, err := db.GetAddressForEnsName(search)
if err != nil {
data.Domain = search
return data, err // We want to return the data if it was a valid domain even if there was an error getting the address from bigtable. A valid domain might be enough for the caller.
}
data.Address = address.Hex()

name, err := db.GetEnsNameForAddress(*address)
if err != nil {
return data, err // We want to return the data if it was a valid address even if there was an error getting the domain from bigtable. A valid address might be enough for the caller.
}
data.Domain = *name

err = cache.TieredCache.SetString(cacheKey, data.Address, time.Minute)
if err != nil {
logger.Errorf("error caching ens address: %v", err)
Expand Down

0 comments on commit 29f3739

Please sign in to comment.