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

feat: add archive web3 gw support #281

Merged
merged 2 commits into from
Jun 3, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
179 changes: 179 additions & 0 deletions archive/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
// Package archive implements an archive node client.
package archive

import (
"context"
"fmt"
"math/big"

"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/ethclient"

"github.com/oasisprotocol/emerald-web3-gateway/rpc/utils"
)

// Client is an archive node client backed by web, implementing a limited
// subset of rpc/eth.API, that is sufficient to support historical queries.
//
// All of the parameters that are `ethrpc.BlockNumberOrHash` just assume
// that the caller will handle converting to a block number, because they
// need to anyway, and historical estimate gas calls are not supported.
type Client struct {
inner *ethclient.Client
latestBlock uint64
}

func (c *Client) LatestBlock() uint64 {
return c.latestBlock
}

func (c *Client) GetStorageAt(
ctx context.Context,
address common.Address,
position hexutil.Big,
blockNr uint64,
) (hexutil.Big, error) {
storageBytes, err := c.inner.StorageAt(
ctx,
address,
common.BigToHash((*big.Int)(&position)),
new(big.Int).SetUint64(blockNr),
)
if err != nil {
return hexutil.Big{}, fmt.Errorf("archive: failed to query storage: %w", err)
}

// Oh for fuck's sake.
var storageBig big.Int
storageBig.SetBytes(storageBytes)
return hexutil.Big(storageBig), nil
}

func (c *Client) GetBalance(
ctx context.Context,
address common.Address,
blockNr uint64,
) (*hexutil.Big, error) {
balance, err := c.inner.BalanceAt(
ctx,
address,
new(big.Int).SetUint64(blockNr),
)
if err != nil {
return nil, fmt.Errorf("archive: failed to query balance: %w", err)
}

return (*hexutil.Big)(balance), nil
}

func (c *Client) GetTransactionCount(
ctx context.Context,
address common.Address,
blockNr uint64,
) (*hexutil.Uint64, error) {
nonce, err := c.inner.NonceAt(
ctx,
address,
new(big.Int).SetUint64(blockNr),
)
if err != nil {
return nil, fmt.Errorf("archive: failed to query nonce: %w", err)
}

return (*hexutil.Uint64)(&nonce), nil
}

func (c *Client) GetCode(
ctx context.Context,
address common.Address,
blockNr uint64,
) (hexutil.Bytes, error) {
code, err := c.inner.CodeAt(
ctx,
address,
new(big.Int).SetUint64(blockNr),
)
if err != nil {
return nil, fmt.Errorf("archive: failed to query code: %w", err)
}

return hexutil.Bytes(code), nil
}

func (c *Client) Call(
ctx context.Context,
args utils.TransactionArgs,
blockNr uint64,
) (hexutil.Bytes, error) {
// You have got to be fucking shitting me, what in the actual fuck.

if args.From == nil {
return nil, fmt.Errorf("archive: no `from` in call")
}
callMsg := ethereum.CallMsg{
From: *args.From,
To: args.To,
GasPrice: (*big.Int)(args.GasPrice),
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
Value: (*big.Int)(args.Value),
// args.Nonce? I guess it can't be that important if there's no field for it.
}
if args.Gas != nil {
callMsg.Gas = uint64(*args.Gas)
}
if args.Data != nil {
callMsg.Data = []byte(*args.Data)
}
if args.Input != nil {
// Data and Input are the same damn thing, Input is newer.
callMsg.Data = []byte(*args.Input)
}
if args.AccessList != nil {
callMsg.AccessList = *args.AccessList
}

result, err := c.inner.CallContract(
ctx,
callMsg,
new(big.Int).SetUint64(blockNr),
)
if err != nil {
return nil, fmt.Errorf("archive: failed to call contract: %w", err)
}

return hexutil.Bytes(result), nil
}

func (c *Client) Close() {
c.inner.Close()
c.inner = nil
}

func New(
ctx context.Context,
uri string,
heightMax uint64,
) (*Client, error) {
c, err := ethclient.DialContext(ctx, uri)
if err != nil {
return nil, fmt.Errorf("archive: failed to dial archival web3 node: %w", err)
}

var latestBlock uint64
switch heightMax {
case 0:
if latestBlock, err = c.BlockNumber(ctx); err != nil {
return nil, fmt.Errorf("archive: failed to query block number: %w", err)
}
default:
latestBlock = heightMax
}

return &Client{
inner: c,
latestBlock: latestBlock,
}, nil
}
12 changes: 11 additions & 1 deletion conf/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,22 @@ type Config struct {
// blocks that the node doesn't have data for, such as by skipping them in checkpoint sync.
// For sensible reasons, indexing may actually start at an even later block, such as if
// this block is already indexed or the node indicates that it doesn't have this block.
IndexingStart uint64 `koanf:"indexing_start"`
IndexingStart uint64 `koanf:"indexing_start"`
IndexingDisable bool `koanf:"indexing_disable"`

Log *LogConfig `koanf:"log"`
Cache *CacheConfig `koanf:"cache"`
Database *DatabaseConfig `koanf:"database"`
Gateway *GatewayConfig `koanf:"gateway"`

// ArchiveURI is the URI of an archival web3 gateway instance
// for servicing historical queries.
ArchiveURI string `koanf:"archive_uri"`
// ArchiveHeightMax is the maximum height (inclusive) to query the
// archvie node (ArchiveURI). If the archive node is configured
// with it's own SQL database instance, this parameter should not
// be needed.
ArchiveHeightMax uint64 `koanf:"archive_height_max"`
}

// Validate performs config validation.
Expand Down
29 changes: 25 additions & 4 deletions indexer/indexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,11 @@ var ErrNotHealthy = errors.New("not healthy")
type Service struct {
service.BaseBackgroundService

runtimeID common.Namespace
enablePruning bool
pruningStep uint64
indexingStart uint64
runtimeID common.Namespace
enablePruning bool
pruningStep uint64
indexingStart uint64
indexingDisable bool

backend Backend
client client.RuntimeClient
Expand Down Expand Up @@ -303,6 +304,14 @@ func (s *Service) indexingWorker() {

// Start starts service.
func (s *Service) Start() {
// TODO/NotYawning: Non-archive nodes that have the indexer disabled
// likey want to use a different notion of healthy, and probably also
// want to start a worker that monitors the database for changes.
if s.indexingDisable {
s.updateHealth(true)
return
}

go s.indexingWorker()
go s.healthWorker()

Expand Down Expand Up @@ -339,8 +348,20 @@ func New(
enablePruning: cfg.EnablePruning,
pruningStep: cfg.PruningStep,
indexingStart: cfg.IndexingStart,
indexingDisable: cfg.IndexingDisable,
}
s.Logger = s.Logger.With("runtime_id", s.runtimeID.String())

// TODO/NotYawning: Non-archive nodes probably want to do something
// different here.
if s.indexingDisable {
if _, err := s.backend.QueryLastIndexedRound(ctx); err != nil {
s.Logger.Error("indexer disabled and no rounds indexed, this will never work",
"err", err,
)
return nil, nil, err
}
}

return s, cachingBackend, nil
}
24 changes: 19 additions & 5 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"

"github.com/oasisprotocol/emerald-web3-gateway/archive"
"github.com/oasisprotocol/emerald-web3-gateway/conf"
"github.com/oasisprotocol/emerald-web3-gateway/db/migrations"
"github.com/oasisprotocol/emerald-web3-gateway/filters"
Expand Down Expand Up @@ -113,7 +114,7 @@ func truncateExec(cmd *cobra.Command, args []string) error {
}

// Initialize db.
db, err := psql.InitDB(ctx, cfg.Database, true)
db, err := psql.InitDB(ctx, cfg.Database, true, false)
if err != nil {
logger.Error("failed to initialize db", "err", err)
return err
Expand Down Expand Up @@ -144,7 +145,7 @@ func migrateExec(cmd *cobra.Command, args []string) error {
logger := logging.GetLogger("migrate-db")

// Initialize db.
db, err := psql.InitDB(ctx, cfg.Database, true)
db, err := psql.InitDB(ctx, cfg.Database, true, false)
if err != nil {
logger.Error("failed to initialize db", "err", err)
return err
Expand Down Expand Up @@ -191,8 +192,13 @@ func runRoot() error {
// Create the runtime client with account module query helpers.
rc := client.New(conn, runtimeID)

// For now, "disable" write access to the DB in a kind of kludgy way
// if the indexer is disabled. Yes this means that no migrations
// can be done. Deal with it.
dbReadOnly := cfg.IndexingDisable

// Initialize db for migrations (higher timeouts).
db, err := psql.InitDB(ctx, cfg.Database, true)
db, err := psql.InitDB(ctx, cfg.Database, true, dbReadOnly)
if err != nil {
logger.Error("failed to initialize db", "err", err)
return err
Expand All @@ -207,7 +213,7 @@ func runRoot() error {

// Initialize db again, now with configured timeouts.
var storage storage.Storage
storage, err = psql.InitDB(ctx, cfg.Database, false)
storage, err = psql.InitDB(ctx, cfg.Database, false, dbReadOnly)
if err != nil {
logger.Error("failed to initialize db", "err", err)
return err
Expand Down Expand Up @@ -245,7 +251,15 @@ func runRoot() error {
return err
}

w3.RegisterAPIs(rpc.GetRPCAPIs(ctx, rc, backend, gasPriceOracle, cfg.Gateway, es))
var archiveClient *archive.Client
if cfg.ArchiveURI != "" {
if archiveClient, err = archive.New(ctx, cfg.ArchiveURI, cfg.ArchiveHeightMax); err != nil {
logger.Error("failed to create archive client", err)
return err
}
}

w3.RegisterAPIs(rpc.GetRPCAPIs(ctx, rc, archiveClient, backend, gasPriceOracle, cfg.Gateway, es))
w3.RegisterHealthChecks([]server.HealthCheck{indx})

svr := server.Server{
Expand Down
4 changes: 3 additions & 1 deletion rpc/apis.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/oasisprotocol/oasis-core/go/common/logging"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/client"

"github.com/oasisprotocol/emerald-web3-gateway/archive"
"github.com/oasisprotocol/emerald-web3-gateway/conf"
eventFilters "github.com/oasisprotocol/emerald-web3-gateway/filters"
"github.com/oasisprotocol/emerald-web3-gateway/gas"
Expand All @@ -23,6 +24,7 @@ import (
func GetRPCAPIs(
ctx context.Context,
client client.RuntimeClient,
archiveClient *archive.Client,
backend indexer.Backend,
gasPriceOracle gas.Backend,
config *conf.GatewayConfig,
Expand All @@ -31,7 +33,7 @@ func GetRPCAPIs(
var apis []ethRpc.API

web3Service := web3.NewPublicAPI()
ethService := eth.NewPublicAPI(client, logging.GetLogger("eth_rpc"), config.ChainID, backend, gasPriceOracle, config.MethodLimits)
ethService := eth.NewPublicAPI(client, archiveClient, logging.GetLogger("eth_rpc"), config.ChainID, backend, gasPriceOracle, config.MethodLimits)
netService := net.NewPublicAPI(config.ChainID)
txpoolService := txpool.NewPublicAPI()
filtersService := filters.NewPublicAPI(client, logging.GetLogger("eth_filters"), backend, eventSystem)
Expand Down
Loading