Skip to content

Commit

Permalink
Merge pull request #281 from oasisprotocol/feature/280
Browse files Browse the repository at this point in the history
feat: add archive web3 gw support
  • Loading branch information
Yawning authored Jun 3, 2022
2 parents 1f8f4ff + b66b4b0 commit 6f19a05
Show file tree
Hide file tree
Showing 9 changed files with 303 additions and 26 deletions.
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

0 comments on commit 6f19a05

Please sign in to comment.