Skip to content

Commit 6f19a05

Browse files
authored
Merge pull request #281 from oasisprotocol/feature/280
feat: add archive web3 gw support
2 parents 1f8f4ff + b66b4b0 commit 6f19a05

File tree

9 files changed

+303
-26
lines changed

9 files changed

+303
-26
lines changed

archive/client.go

+179
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
// Package archive implements an archive node client.
2+
package archive
3+
4+
import (
5+
"context"
6+
"fmt"
7+
"math/big"
8+
9+
"github.com/ethereum/go-ethereum"
10+
"github.com/ethereum/go-ethereum/common"
11+
"github.com/ethereum/go-ethereum/common/hexutil"
12+
"github.com/ethereum/go-ethereum/ethclient"
13+
14+
"github.com/oasisprotocol/emerald-web3-gateway/rpc/utils"
15+
)
16+
17+
// Client is an archive node client backed by web, implementing a limited
18+
// subset of rpc/eth.API, that is sufficient to support historical queries.
19+
//
20+
// All of the parameters that are `ethrpc.BlockNumberOrHash` just assume
21+
// that the caller will handle converting to a block number, because they
22+
// need to anyway, and historical estimate gas calls are not supported.
23+
type Client struct {
24+
inner *ethclient.Client
25+
latestBlock uint64
26+
}
27+
28+
func (c *Client) LatestBlock() uint64 {
29+
return c.latestBlock
30+
}
31+
32+
func (c *Client) GetStorageAt(
33+
ctx context.Context,
34+
address common.Address,
35+
position hexutil.Big,
36+
blockNr uint64,
37+
) (hexutil.Big, error) {
38+
storageBytes, err := c.inner.StorageAt(
39+
ctx,
40+
address,
41+
common.BigToHash((*big.Int)(&position)),
42+
new(big.Int).SetUint64(blockNr),
43+
)
44+
if err != nil {
45+
return hexutil.Big{}, fmt.Errorf("archive: failed to query storage: %w", err)
46+
}
47+
48+
// Oh for fuck's sake.
49+
var storageBig big.Int
50+
storageBig.SetBytes(storageBytes)
51+
return hexutil.Big(storageBig), nil
52+
}
53+
54+
func (c *Client) GetBalance(
55+
ctx context.Context,
56+
address common.Address,
57+
blockNr uint64,
58+
) (*hexutil.Big, error) {
59+
balance, err := c.inner.BalanceAt(
60+
ctx,
61+
address,
62+
new(big.Int).SetUint64(blockNr),
63+
)
64+
if err != nil {
65+
return nil, fmt.Errorf("archive: failed to query balance: %w", err)
66+
}
67+
68+
return (*hexutil.Big)(balance), nil
69+
}
70+
71+
func (c *Client) GetTransactionCount(
72+
ctx context.Context,
73+
address common.Address,
74+
blockNr uint64,
75+
) (*hexutil.Uint64, error) {
76+
nonce, err := c.inner.NonceAt(
77+
ctx,
78+
address,
79+
new(big.Int).SetUint64(blockNr),
80+
)
81+
if err != nil {
82+
return nil, fmt.Errorf("archive: failed to query nonce: %w", err)
83+
}
84+
85+
return (*hexutil.Uint64)(&nonce), nil
86+
}
87+
88+
func (c *Client) GetCode(
89+
ctx context.Context,
90+
address common.Address,
91+
blockNr uint64,
92+
) (hexutil.Bytes, error) {
93+
code, err := c.inner.CodeAt(
94+
ctx,
95+
address,
96+
new(big.Int).SetUint64(blockNr),
97+
)
98+
if err != nil {
99+
return nil, fmt.Errorf("archive: failed to query code: %w", err)
100+
}
101+
102+
return hexutil.Bytes(code), nil
103+
}
104+
105+
func (c *Client) Call(
106+
ctx context.Context,
107+
args utils.TransactionArgs,
108+
blockNr uint64,
109+
) (hexutil.Bytes, error) {
110+
// You have got to be fucking shitting me, what in the actual fuck.
111+
112+
if args.From == nil {
113+
return nil, fmt.Errorf("archive: no `from` in call")
114+
}
115+
callMsg := ethereum.CallMsg{
116+
From: *args.From,
117+
To: args.To,
118+
GasPrice: (*big.Int)(args.GasPrice),
119+
GasFeeCap: (*big.Int)(args.MaxFeePerGas),
120+
GasTipCap: (*big.Int)(args.MaxPriorityFeePerGas),
121+
Value: (*big.Int)(args.Value),
122+
// args.Nonce? I guess it can't be that important if there's no field for it.
123+
}
124+
if args.Gas != nil {
125+
callMsg.Gas = uint64(*args.Gas)
126+
}
127+
if args.Data != nil {
128+
callMsg.Data = []byte(*args.Data)
129+
}
130+
if args.Input != nil {
131+
// Data and Input are the same damn thing, Input is newer.
132+
callMsg.Data = []byte(*args.Input)
133+
}
134+
if args.AccessList != nil {
135+
callMsg.AccessList = *args.AccessList
136+
}
137+
138+
result, err := c.inner.CallContract(
139+
ctx,
140+
callMsg,
141+
new(big.Int).SetUint64(blockNr),
142+
)
143+
if err != nil {
144+
return nil, fmt.Errorf("archive: failed to call contract: %w", err)
145+
}
146+
147+
return hexutil.Bytes(result), nil
148+
}
149+
150+
func (c *Client) Close() {
151+
c.inner.Close()
152+
c.inner = nil
153+
}
154+
155+
func New(
156+
ctx context.Context,
157+
uri string,
158+
heightMax uint64,
159+
) (*Client, error) {
160+
c, err := ethclient.DialContext(ctx, uri)
161+
if err != nil {
162+
return nil, fmt.Errorf("archive: failed to dial archival web3 node: %w", err)
163+
}
164+
165+
var latestBlock uint64
166+
switch heightMax {
167+
case 0:
168+
if latestBlock, err = c.BlockNumber(ctx); err != nil {
169+
return nil, fmt.Errorf("archive: failed to query block number: %w", err)
170+
}
171+
default:
172+
latestBlock = heightMax
173+
}
174+
175+
return &Client{
176+
inner: c,
177+
latestBlock: latestBlock,
178+
}, nil
179+
}

conf/config.go

+11-1
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,22 @@ type Config struct {
2222
// blocks that the node doesn't have data for, such as by skipping them in checkpoint sync.
2323
// For sensible reasons, indexing may actually start at an even later block, such as if
2424
// this block is already indexed or the node indicates that it doesn't have this block.
25-
IndexingStart uint64 `koanf:"indexing_start"`
25+
IndexingStart uint64 `koanf:"indexing_start"`
26+
IndexingDisable bool `koanf:"indexing_disable"`
2627

2728
Log *LogConfig `koanf:"log"`
2829
Cache *CacheConfig `koanf:"cache"`
2930
Database *DatabaseConfig `koanf:"database"`
3031
Gateway *GatewayConfig `koanf:"gateway"`
32+
33+
// ArchiveURI is the URI of an archival web3 gateway instance
34+
// for servicing historical queries.
35+
ArchiveURI string `koanf:"archive_uri"`
36+
// ArchiveHeightMax is the maximum height (inclusive) to query the
37+
// archvie node (ArchiveURI). If the archive node is configured
38+
// with it's own SQL database instance, this parameter should not
39+
// be needed.
40+
ArchiveHeightMax uint64 `koanf:"archive_height_max"`
3141
}
3242

3343
// Validate performs config validation.

indexer/indexer.go

+25-4
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,11 @@ var ErrNotHealthy = errors.New("not healthy")
4343
type Service struct {
4444
service.BaseBackgroundService
4545

46-
runtimeID common.Namespace
47-
enablePruning bool
48-
pruningStep uint64
49-
indexingStart uint64
46+
runtimeID common.Namespace
47+
enablePruning bool
48+
pruningStep uint64
49+
indexingStart uint64
50+
indexingDisable bool
5051

5152
backend Backend
5253
client client.RuntimeClient
@@ -303,6 +304,14 @@ func (s *Service) indexingWorker() {
303304

304305
// Start starts service.
305306
func (s *Service) Start() {
307+
// TODO/NotYawning: Non-archive nodes that have the indexer disabled
308+
// likey want to use a different notion of healthy, and probably also
309+
// want to start a worker that monitors the database for changes.
310+
if s.indexingDisable {
311+
s.updateHealth(true)
312+
return
313+
}
314+
306315
go s.indexingWorker()
307316
go s.healthWorker()
308317

@@ -339,8 +348,20 @@ func New(
339348
enablePruning: cfg.EnablePruning,
340349
pruningStep: cfg.PruningStep,
341350
indexingStart: cfg.IndexingStart,
351+
indexingDisable: cfg.IndexingDisable,
342352
}
343353
s.Logger = s.Logger.With("runtime_id", s.runtimeID.String())
344354

355+
// TODO/NotYawning: Non-archive nodes probably want to do something
356+
// different here.
357+
if s.indexingDisable {
358+
if _, err := s.backend.QueryLastIndexedRound(ctx); err != nil {
359+
s.Logger.Error("indexer disabled and no rounds indexed, this will never work",
360+
"err", err,
361+
)
362+
return nil, nil, err
363+
}
364+
}
365+
345366
return s, cachingBackend, nil
346367
}

main.go

+19-5
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"google.golang.org/grpc"
1818
"google.golang.org/grpc/credentials/insecure"
1919

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

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

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

195+
// For now, "disable" write access to the DB in a kind of kludgy way
196+
// if the indexer is disabled. Yes this means that no migrations
197+
// can be done. Deal with it.
198+
dbReadOnly := cfg.IndexingDisable
199+
194200
// Initialize db for migrations (higher timeouts).
195-
db, err := psql.InitDB(ctx, cfg.Database, true)
201+
db, err := psql.InitDB(ctx, cfg.Database, true, dbReadOnly)
196202
if err != nil {
197203
logger.Error("failed to initialize db", "err", err)
198204
return err
@@ -207,7 +213,7 @@ func runRoot() error {
207213

208214
// Initialize db again, now with configured timeouts.
209215
var storage storage.Storage
210-
storage, err = psql.InitDB(ctx, cfg.Database, false)
216+
storage, err = psql.InitDB(ctx, cfg.Database, false, dbReadOnly)
211217
if err != nil {
212218
logger.Error("failed to initialize db", "err", err)
213219
return err
@@ -245,7 +251,15 @@ func runRoot() error {
245251
return err
246252
}
247253

248-
w3.RegisterAPIs(rpc.GetRPCAPIs(ctx, rc, backend, gasPriceOracle, cfg.Gateway, es))
254+
var archiveClient *archive.Client
255+
if cfg.ArchiveURI != "" {
256+
if archiveClient, err = archive.New(ctx, cfg.ArchiveURI, cfg.ArchiveHeightMax); err != nil {
257+
logger.Error("failed to create archive client", err)
258+
return err
259+
}
260+
}
261+
262+
w3.RegisterAPIs(rpc.GetRPCAPIs(ctx, rc, archiveClient, backend, gasPriceOracle, cfg.Gateway, es))
249263
w3.RegisterHealthChecks([]server.HealthCheck{indx})
250264

251265
svr := server.Server{

rpc/apis.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"github.com/oasisprotocol/oasis-core/go/common/logging"
88
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/client"
99

10+
"github.com/oasisprotocol/emerald-web3-gateway/archive"
1011
"github.com/oasisprotocol/emerald-web3-gateway/conf"
1112
eventFilters "github.com/oasisprotocol/emerald-web3-gateway/filters"
1213
"github.com/oasisprotocol/emerald-web3-gateway/gas"
@@ -23,6 +24,7 @@ import (
2324
func GetRPCAPIs(
2425
ctx context.Context,
2526
client client.RuntimeClient,
27+
archiveClient *archive.Client,
2628
backend indexer.Backend,
2729
gasPriceOracle gas.Backend,
2830
config *conf.GatewayConfig,
@@ -31,7 +33,7 @@ func GetRPCAPIs(
3133
var apis []ethRpc.API
3234

3335
web3Service := web3.NewPublicAPI()
34-
ethService := eth.NewPublicAPI(client, logging.GetLogger("eth_rpc"), config.ChainID, backend, gasPriceOracle, config.MethodLimits)
36+
ethService := eth.NewPublicAPI(client, archiveClient, logging.GetLogger("eth_rpc"), config.ChainID, backend, gasPriceOracle, config.MethodLimits)
3537
netService := net.NewPublicAPI(config.ChainID)
3638
txpoolService := txpool.NewPublicAPI()
3739
filtersService := filters.NewPublicAPI(client, logging.GetLogger("eth_filters"), backend, eventSystem)

0 commit comments

Comments
 (0)