diff --git a/analyzer/aggregate_stats.go b/analyzer/aggregate_stats.go index 007e000d9..385392c5e 100644 --- a/analyzer/aggregate_stats.go +++ b/analyzer/aggregate_stats.go @@ -10,6 +10,7 @@ import ( "github.com/jackc/pgx/v5" "github.com/oasisprotocol/oasis-indexer/analyzer/queries" + "github.com/oasisprotocol/oasis-indexer/common" "github.com/oasisprotocol/oasis-indexer/config" "github.com/oasisprotocol/oasis-indexer/log" "github.com/oasisprotocol/oasis-indexer/metrics" @@ -48,8 +49,8 @@ const ( // by (periodically) querying the `runtime_related_transactions` table. var dailyActiveAccountsLayers = []string{ layerConsensus, - RuntimeEmerald.String(), - RuntimeSapphire.String(), + common.RuntimeEmerald.String(), + common.RuntimeSapphire.String(), // RuntimeCipher.String(), // Enable once Cipher is supported by the indexer. } diff --git a/analyzer/api.go b/analyzer/api.go index 72d8e68a0..9385bc1b9 100644 --- a/analyzer/api.go +++ b/analyzer/api.go @@ -2,9 +2,6 @@ package analyzer import ( "errors" - "strings" - - oasisConfig "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" "github.com/oasisprotocol/oasis-indexer/storage" ) @@ -17,14 +14,6 @@ var ( // ErrLatestBlockNotFound is returned if the analyzer has not indexed any // blocks yet. This indicates to begin from the start of its range. ErrLatestBlockNotFound = errors.New("latest block not found") - - // ErrNetworkUnknown is returned if a chain context does not correspond - // to a known network identifier. - ErrNetworkUnknown = errors.New("network unknown") - - // ErrRuntimeUnknown is returned if a chain context does not correspond - // to a known runtime identifier. - ErrRuntimeUnknown = errors.New("runtime unknown") ) // Analyzer is a worker that analyzes a subset of the Oasis Network. @@ -81,100 +70,3 @@ type RoundRange struct { // To is the last block to process in this range, inclusive. To uint64 } - -// ChainID is the ID of a chain. -type ChainID string - -// String returns the string representation of a ChainID. -func (c ChainID) String() string { - return string(c) -} - -// Network is an instance of the Oasis Network. -type Network uint8 - -const ( - // NetworkTestnet is the identifier for testnet. - NetworkTestnet Network = iota - // NetworkMainnet is the identifier for mainnet. - NetworkMainnet - // NetworkUnknown is the identifier for an unknown network. - NetworkUnknown = 255 -) - -// FromChainContext identifies a Network using its ChainContext. -func FromChainContext(chainContext string) (Network, error) { - // TODO: Remove this hardcoded value once indexer config supports multiple nodes. - if chainContext == "53852332637bacb61b91b6411ab4095168ba02a50be4c3f82448438826f23898" { - return NetworkMainnet, nil // cobalt mainnet - } - var network Network - for name, nw := range oasisConfig.DefaultNetworks.All { - if nw.ChainContext == chainContext { - if err := network.Set(name); err != nil { - return NetworkUnknown, err - } - return network, nil - } - } - - return NetworkUnknown, ErrNetworkUnknown -} - -// Set sets the Network to the value specified by the provided string. -func (n *Network) Set(s string) error { - switch strings.ToLower(s) { - case "mainnet": - *n = NetworkMainnet - case "testnet": - *n = NetworkTestnet - default: - return ErrNetworkUnknown - } - - return nil -} - -// String returns the string representation of a network. -func (n Network) String() string { - switch n { - case NetworkTestnet: - return "testnet" - case NetworkMainnet: - return "mainnet" - default: - return "unknown" - } -} - -// Runtime is an identifier for a runtime on the Oasis Network. -type Runtime string - -const ( - RuntimeEmerald Runtime = "emerald" - RuntimeCipher Runtime = "cipher" - RuntimeSapphire Runtime = "sapphire" - RuntimeUnknown Runtime = "unknown" -) - -// String returns the string representation of a runtime. -func (r Runtime) String() string { - return string(r) -} - -// ID returns the ID for a Runtime on the provided network. -func (r Runtime) ID(n Network) (string, error) { - for nname, nw := range oasisConfig.DefaultNetworks.All { - if nname == n.String() { - for pname, pt := range nw.ParaTimes.All { - if pname == r.String() { - return pt.ID, nil - } - } - - return "", ErrRuntimeUnknown - } - } - - return "", ErrRuntimeUnknown -} diff --git a/analyzer/evmtokenbalances/evm_token_balances.go b/analyzer/evmtokenbalances/evm_token_balances.go index 7ee83406d..ce2e0106b 100644 --- a/analyzer/evmtokenbalances/evm_token_balances.go +++ b/analyzer/evmtokenbalances/evm_token_balances.go @@ -79,7 +79,7 @@ const ( ) type Main struct { - runtime analyzer.Runtime + runtime common.Runtime cfg analyzer.RuntimeConfig target storage.TargetStorage logger *log.Logger @@ -88,7 +88,7 @@ type Main struct { var _ analyzer.Analyzer = (*Main)(nil) func NewMain( - runtime analyzer.Runtime, + runtime common.Runtime, sourceConfig *config.SourceConfig, target storage.TargetStorage, logger *log.Logger, diff --git a/analyzer/evmtokens/evm_tokens.go b/analyzer/evmtokens/evm_tokens.go index a8cfcd232..4c1130e22 100644 --- a/analyzer/evmtokens/evm_tokens.go +++ b/analyzer/evmtokens/evm_tokens.go @@ -11,6 +11,7 @@ import ( "github.com/oasisprotocol/oasis-indexer/analyzer/queries" "github.com/oasisprotocol/oasis-indexer/analyzer/runtime" "github.com/oasisprotocol/oasis-indexer/analyzer/util" + "github.com/oasisprotocol/oasis-indexer/common" "github.com/oasisprotocol/oasis-indexer/config" "github.com/oasisprotocol/oasis-indexer/log" "github.com/oasisprotocol/oasis-indexer/storage" @@ -35,7 +36,7 @@ const ( ) type Main struct { - runtime analyzer.Runtime + runtime common.Runtime cfg analyzer.RuntimeConfig target storage.TargetStorage logger *log.Logger @@ -44,7 +45,7 @@ type Main struct { var _ analyzer.Analyzer = (*Main)(nil) func NewMain( - runtime analyzer.Runtime, + runtime common.Runtime, sourceConfig *config.SourceConfig, target storage.TargetStorage, logger *log.Logger, diff --git a/analyzer/runtime/evm_test.go b/analyzer/runtime/evm_test.go index 6b4d1a46b..e3ae678f1 100644 --- a/analyzer/runtime/evm_test.go +++ b/analyzer/runtime/evm_test.go @@ -10,13 +10,13 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" ethCommon "github.com/ethereum/go-ethereum/common" - "github.com/oasisprotocol/oasis-core/go/runtime/client/api" + runtimeClient "github.com/oasisprotocol/oasis-core/go/runtime/client/api" sdkConfig "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" "github.com/stretchr/testify/require" - "github.com/oasisprotocol/oasis-indexer/analyzer" "github.com/oasisprotocol/oasis-indexer/analyzer/evmabi" - "github.com/oasisprotocol/oasis-indexer/cmd/common" + cmdCommon "github.com/oasisprotocol/oasis-indexer/cmd/common" + "github.com/oasisprotocol/oasis-indexer/common" "github.com/oasisprotocol/oasis-indexer/config" "github.com/oasisprotocol/oasis-indexer/storage/oasis" ) @@ -38,19 +38,19 @@ var ( func TestEVMDownloadTokenERC20(t *testing.T) { ctx := context.Background() - source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, analyzer.RuntimeEmerald) + source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, common.RuntimeEmerald) require.NoError(t, err) // Wormhole bridged USDT on Emerald mainnet. tokenEthAddr, err := hex.DecodeString("dC19A122e268128B5eE20366299fc7b5b199C8e3") require.NoError(t, err) - data, err := evmDownloadTokenERC20(ctx, common.Logger(), source, api.RoundLatest, tokenEthAddr) + data, err := evmDownloadTokenERC20(ctx, cmdCommon.Logger(), source, runtimeClient.RoundLatest, tokenEthAddr) require.NoError(t, err) t.Logf("data %#v", data) } func TestEVMDownloadTokenBalanceERC20(t *testing.T) { ctx := context.Background() - source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, analyzer.RuntimeEmerald) + source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, common.RuntimeEmerald) require.NoError(t, err) // Wormhole bridged USDT on Emerald mainnet. tokenEthAddr, err := hex.DecodeString("dC19A122e268128B5eE20366299fc7b5b199C8e3") @@ -58,20 +58,20 @@ func TestEVMDownloadTokenBalanceERC20(t *testing.T) { // An address that possesses no USDT. accountEthAddr, err := hex.DecodeString("5555555555555555555555555555555555555555") require.NoError(t, err) - balanceData, err := evmDownloadTokenBalanceERC20(ctx, common.Logger(), source, api.RoundLatest, tokenEthAddr, accountEthAddr) + balanceData, err := evmDownloadTokenBalanceERC20(ctx, cmdCommon.Logger(), source, runtimeClient.RoundLatest, tokenEthAddr, accountEthAddr) require.NoError(t, err) t.Logf("balance %#v", balanceData) } func TestEVMFailDeterministicUnoccupied(t *testing.T) { ctx := context.Background() - source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, analyzer.RuntimeEmerald) + source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, common.RuntimeEmerald) require.NoError(t, err) // An address at which no smart contract exists. tokenEthAddr, err := hex.DecodeString("5555555555555555555555555555555555555555") require.NoError(t, err) var name string - err = evmCallWithABI(ctx, source, api.RoundLatest, tokenEthAddr, evmabi.ERC20, &name, "name") + err = evmCallWithABI(ctx, source, runtimeClient.RoundLatest, tokenEthAddr, evmabi.ERC20, &name, "name") require.Error(t, err) fmt.Printf("getting ERC-20 name from unoccupied address should fail: %+v\n", err) require.True(t, errors.Is(err, EVMDeterministicError{})) @@ -79,7 +79,7 @@ func TestEVMFailDeterministicUnoccupied(t *testing.T) { func TestEVMFailDeterministicOutOfGas(t *testing.T) { ctx := context.Background() - source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, analyzer.RuntimeEmerald) + source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, common.RuntimeEmerald) require.NoError(t, err) // Wormhole bridged USDT on Emerald mainnet. tokenEthAddr, err := hex.DecodeString("dC19A122e268128B5eE20366299fc7b5b199C8e3") @@ -90,7 +90,7 @@ func TestEVMFailDeterministicOutOfGas(t *testing.T) { gasLimit := uint64(10) caller := ethCommon.Address{1}.Bytes() value := []byte{0} - err = evmCallWithABICustom(ctx, source, api.RoundLatest, gasPrice, gasLimit, caller, tokenEthAddr, value, evmabi.ERC20, &name, "name") + err = evmCallWithABICustom(ctx, source, runtimeClient.RoundLatest, gasPrice, gasLimit, caller, tokenEthAddr, value, evmabi.ERC20, &name, "name") require.Error(t, err) fmt.Printf("query that runs out of gas should fail: %+v\n", err) require.True(t, errors.Is(err, EVMDeterministicError{})) @@ -98,7 +98,7 @@ func TestEVMFailDeterministicOutOfGas(t *testing.T) { func TestEVMFailDeterministicUnsupportedMethod(t *testing.T) { ctx := context.Background() - source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, analyzer.RuntimeEmerald) + source, err := oasis.NewRuntimeClient(ctx, PublicSourceConfig, common.RuntimeEmerald) require.NoError(t, err) // Wormhole bridged USDT on Emerald mainnet. tokenEthAddr, err := hex.DecodeString("dC19A122e268128B5eE20366299fc7b5b199C8e3") @@ -119,7 +119,7 @@ func TestEVMFailDeterministicUnsupportedMethod(t *testing.T) { }]`)) require.NoError(t, err) var name string - err = evmCallWithABI(ctx, source, api.RoundLatest, tokenEthAddr, &fakeABI, &name, "bike") + err = evmCallWithABI(ctx, source, runtimeClient.RoundLatest, tokenEthAddr, &fakeABI, &name, "bike") require.Error(t, err) fmt.Printf("querying an unsupported method should fail: %+v\n", err) require.True(t, errors.Is(err, EVMDeterministicError{})) diff --git a/analyzer/runtime/runtime.go b/analyzer/runtime/runtime.go index 3b7ebf084..1d52ac167 100644 --- a/analyzer/runtime/runtime.go +++ b/analyzer/runtime/runtime.go @@ -13,8 +13,9 @@ import ( "github.com/oasisprotocol/oasis-indexer/analyzer" "github.com/oasisprotocol/oasis-indexer/analyzer/queries" - common "github.com/oasisprotocol/oasis-indexer/analyzer/uncategorized" + uncategorized "github.com/oasisprotocol/oasis-indexer/analyzer/uncategorized" "github.com/oasisprotocol/oasis-indexer/analyzer/util" + "github.com/oasisprotocol/oasis-indexer/common" "github.com/oasisprotocol/oasis-indexer/config" "github.com/oasisprotocol/oasis-indexer/log" "github.com/oasisprotocol/oasis-indexer/metrics" @@ -28,7 +29,7 @@ const ( // Main is the main Analyzer for runtimes. type Main struct { - runtime analyzer.Runtime + runtime common.Runtime cfg analyzer.RuntimeConfig target storage.TargetStorage logger *log.Logger @@ -39,7 +40,7 @@ var _ analyzer.Analyzer = (*Main)(nil) // NewRuntimeAnalyzer returns a new main analyzer for a runtime. func NewRuntimeAnalyzer( - runtime analyzer.Runtime, + runtime common.Runtime, sourceConfig *config.SourceConfig, cfg *config.BlockBasedAnalyzerConfig, target storage.TargetStorage, @@ -299,7 +300,7 @@ func (m *Main) queueDbUpdates(batch *storage.QueryBatch, data *BlockData) { // Insert events. for _, eventData := range data.EventData { - eventRelatedAddresses := common.ExtractAddresses(eventData.RelatedAddresses) + eventRelatedAddresses := uncategorized.ExtractAddresses(eventData.RelatedAddresses) batch.Queue( queries.RuntimeEventInsert, m.runtime, diff --git a/cmd/analyzer/analyzer.go b/cmd/analyzer/analyzer.go index aa545260e..d44ac3d9a 100644 --- a/cmd/analyzer/analyzer.go +++ b/cmd/analyzer/analyzer.go @@ -17,7 +17,8 @@ import ( "github.com/oasisprotocol/oasis-indexer/analyzer/evmtokenbalances" "github.com/oasisprotocol/oasis-indexer/analyzer/evmtokens" "github.com/oasisprotocol/oasis-indexer/analyzer/runtime" - "github.com/oasisprotocol/oasis-indexer/cmd/common" + cmdCommon "github.com/oasisprotocol/oasis-indexer/cmd/common" + "github.com/oasisprotocol/oasis-indexer/common" "github.com/oasisprotocol/oasis-indexer/config" "github.com/oasisprotocol/oasis-indexer/log" "github.com/oasisprotocol/oasis-indexer/storage" @@ -49,13 +50,13 @@ func runAnalyzer(cmd *cobra.Command, args []string) { } // Initialize common environment. - if err = common.Init(cfg); err != nil { + if err = cmdCommon.Init(cfg); err != nil { log.NewDefaultLogger("init").Error("init failed", "error", err, ) os.Exit(1) } - logger := common.Logger() + logger := cmdCommon.Logger() if cfg.Analysis == nil { logger.Error("analysis config not provided") @@ -73,7 +74,7 @@ func runAnalyzer(cmd *cobra.Command, args []string) { // Init initializes the analysis service. func Init(cfg *config.AnalysisConfig) (*Service, error) { - logger := common.Logger() + logger := cmdCommon.Logger() logger.Info("initializing analysis service", "config", cfg) if cfg.Storage.WipeStorage { @@ -118,10 +119,10 @@ func Init(cfg *config.AnalysisConfig) (*Service, error) { } func wipeStorage(cfg *config.StorageConfig) error { - logger := common.Logger().WithModule(moduleName) + logger := cmdCommon.Logger().WithModule(moduleName) // Initialize target storage. - storage, err := common.NewClient(cfg, logger) + storage, err := cmdCommon.NewClient(cfg, logger) if err != nil { return err } @@ -160,10 +161,10 @@ func addAnalyzer(analyzers map[string]A, errSoFar error, analyzerGenerator func( // NewService creates new Service. func NewService(cfg *config.AnalysisConfig) (*Service, error) { - logger := common.Logger().WithModule(moduleName) + logger := cmdCommon.Logger().WithModule(moduleName) // Initialize target storage. - client, err := common.NewClient(cfg.Storage, logger) + client, err := cmdCommon.NewClient(cfg.Storage, logger) if err != nil { return nil, err } @@ -177,37 +178,37 @@ func NewService(cfg *config.AnalysisConfig) (*Service, error) { } if cfg.Analyzers.Emerald != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return runtime.NewRuntimeAnalyzer(analyzer.RuntimeEmerald, &cfg.Source, cfg.Analyzers.Emerald, client, logger) + return runtime.NewRuntimeAnalyzer(common.RuntimeEmerald, &cfg.Source, cfg.Analyzers.Emerald, client, logger) }) } if cfg.Analyzers.Sapphire != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return runtime.NewRuntimeAnalyzer(analyzer.RuntimeSapphire, &cfg.Source, cfg.Analyzers.Sapphire, client, logger) + return runtime.NewRuntimeAnalyzer(common.RuntimeSapphire, &cfg.Source, cfg.Analyzers.Sapphire, client, logger) }) } if cfg.Analyzers.Cipher != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return runtime.NewRuntimeAnalyzer(analyzer.RuntimeCipher, &cfg.Source, cfg.Analyzers.Cipher, client, logger) + return runtime.NewRuntimeAnalyzer(common.RuntimeCipher, &cfg.Source, cfg.Analyzers.Cipher, client, logger) }) } if cfg.Analyzers.EmeraldEvmTokens != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return evmtokens.NewMain(analyzer.RuntimeEmerald, &cfg.Source, client, logger) + return evmtokens.NewMain(common.RuntimeEmerald, &cfg.Source, client, logger) }) } if cfg.Analyzers.SapphireEvmTokens != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return evmtokens.NewMain(analyzer.RuntimeSapphire, &cfg.Source, client, logger) + return evmtokens.NewMain(common.RuntimeSapphire, &cfg.Source, client, logger) }) } if cfg.Analyzers.EmeraldEvmTokenBalances != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return evmtokenbalances.NewMain(analyzer.RuntimeEmerald, &cfg.Source, client, logger) + return evmtokenbalances.NewMain(common.RuntimeEmerald, &cfg.Source, client, logger) }) } if cfg.Analyzers.SapphireEvmTokenBalances != nil { analyzers, err = addAnalyzer(analyzers, err, func() (A, error) { - return evmtokenbalances.NewMain(analyzer.RuntimeSapphire, &cfg.Source, client, logger) + return evmtokenbalances.NewMain(common.RuntimeSapphire, &cfg.Source, client, logger) }) } if cfg.Analyzers.MetadataRegistry != nil { diff --git a/common/types.go b/common/types.go index 38d10cfda..34c93d4e9 100644 --- a/common/types.go +++ b/common/types.go @@ -1,11 +1,13 @@ package common import ( + "errors" "fmt" "math/big" "strings" "github.com/jackc/pgx/v5/pgtype" + sdkConfig "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" "github.com/oasisprotocol/oasis-core/go/common/quantity" ) @@ -100,3 +102,110 @@ const ( // in a request context. RequestIDContextKey ContextKey = "request_id" ) + +var ( + // ErrNetworkUnknown is returned if a chain context does not correspond + // to a known network identifier. + ErrNetworkUnknown = errors.New("network unknown") + + // ErrRuntimeUnknown is returned if a chain context does not correspond + // to a known runtime identifier. + ErrRuntimeUnknown = errors.New("runtime unknown") +) + +// ChainID is the ID of a chain. +type ChainID string + +// String returns the string representation of a ChainID. +func (c ChainID) String() string { + return string(c) +} + +// Network is an instance of the Oasis Network. +type Network uint8 + +const ( + // NetworkTestnet is the identifier for testnet. + NetworkTestnet Network = iota + // NetworkMainnet is the identifier for mainnet. + NetworkMainnet + // NetworkUnknown is the identifier for an unknown network. + NetworkUnknown = 255 +) + +// FromChainContext identifies a Network using its ChainContext. +func FromChainContext(chainContext string) (Network, error) { + // TODO: Remove this hardcoded value once indexer config supports multiple nodes. + if chainContext == "53852332637bacb61b91b6411ab4095168ba02a50be4c3f82448438826f23898" { + return NetworkMainnet, nil // cobalt mainnet + } + var network Network + for name, nw := range sdkConfig.DefaultNetworks.All { + if nw.ChainContext == chainContext { + if err := network.Set(name); err != nil { + return NetworkUnknown, err + } + return network, nil + } + } + + return NetworkUnknown, ErrNetworkUnknown +} + +// Set sets the Network to the value specified by the provided string. +func (n *Network) Set(s string) error { + switch strings.ToLower(s) { + case "mainnet": + *n = NetworkMainnet + case "testnet": + *n = NetworkTestnet + default: + return ErrNetworkUnknown + } + + return nil +} + +// String returns the string representation of a network. +func (n Network) String() string { + switch n { + case NetworkTestnet: + return "testnet" + case NetworkMainnet: + return "mainnet" + default: + return "unknown" + } +} + +// Runtime is an identifier for a runtime on the Oasis Network. +type Runtime string + +const ( + RuntimeEmerald Runtime = "emerald" + RuntimeCipher Runtime = "cipher" + RuntimeSapphire Runtime = "sapphire" + RuntimeUnknown Runtime = "unknown" +) + +// String returns the string representation of a runtime. +func (r Runtime) String() string { + return string(r) +} + +// ID returns the ID for a Runtime on the provided network. +func (r Runtime) ID(n Network) (string, error) { + for nname, nw := range sdkConfig.DefaultNetworks.All { + if nname == n.String() { + for pname, pt := range nw.ParaTimes.All { + if pname == r.String() { + return pt.ID, nil + } + } + + return "", ErrRuntimeUnknown + } + } + + return "", ErrRuntimeUnknown +} diff --git a/config/history.go b/config/history.go index f9cc401e0..c0b439ab1 100644 --- a/config/history.go +++ b/config/history.go @@ -3,13 +3,23 @@ package config import ( "fmt" - "github.com/oasisprotocol/oasis-core/go/consensus/api" + consensus "github.com/oasisprotocol/oasis-core/go/consensus/api" + runtimeClient "github.com/oasisprotocol/oasis-core/go/runtime/client/api" + + "github.com/oasisprotocol/oasis-indexer/common" ) type Record struct { ArchiveName string `koanf:"archive_name"` GenesisHeight int64 `koanf:"genesis_height"` - ChainContext string `koanf:"chain_context"` + // RuntimeStartRounds has entries for runtimes that already exist at the + // genesis of this network. Look these up in the genesis document's + // .roothash.runtime_states[runtime_id_hex].round. For clarity, add an + // entry stating round 0 in the first network where a runtime is available + // (although code does not differentiate between the presence or absence + // of a zero entry). + RuntimeStartRounds map[common.Runtime]uint64 `koanf:"runtime_start_rounds"` + ChainContext string `koanf:"chain_context"` } type History struct { @@ -25,7 +35,7 @@ func (h *History) EarliestRecord() *Record { } func (h *History) RecordForHeight(height int64) (*Record, error) { - if height == api.HeightLatest { + if height == consensus.HeightLatest { return h.CurrentRecord(), nil } for _, r := range h.Records { @@ -42,6 +52,25 @@ func (h *History) RecordForHeight(height int64) (*Record, error) { ) } +func (h *History) RecordForRuntimeRound(runtime common.Runtime, round uint64) (*Record, error) { + if round == runtimeClient.RoundLatest { + return h.CurrentRecord(), nil + } + for _, r := range h.Records { + if round >= r.RuntimeStartRounds[runtime] { + return r, nil + } + } + earliestRecord := h.EarliestRecord() + return nil, fmt.Errorf( + "runtime %s round %d earlier than earliest history record %s start round %d", + runtime, + round, + earliestRecord.ArchiveName, + earliestRecord.RuntimeStartRounds[runtime], + ) +} + func SingleRecordHistory(chainContext string) *History { return &History{ Records: []*Record{ @@ -61,13 +90,22 @@ var DefaultChains = map[string]*History{ // https://github.com/oasisprotocol/mainnet-artifacts/releases/tag/2022-04-11 ArchiveName: "damask", GenesisHeight: 8048956, - ChainContext: "b11b369e0da5bb230b220127f5e7b242d385ef8c6f54906243f30af63c815535", + RuntimeStartRounds: map[common.Runtime]uint64{ + common.RuntimeCipher: 8284, + common.RuntimeEmerald: 1003298, + common.RuntimeSapphire: 0, + }, + ChainContext: "b11b369e0da5bb230b220127f5e7b242d385ef8c6f54906243f30af63c815535", }, { // https://github.com/oasisprotocol/mainnet-artifacts/releases/tag/2021-04-28 ArchiveName: "cobalt", GenesisHeight: 3027601, - ChainContext: "53852332637bacb61b91b6411ab4095168ba02a50be4c3f82448438826f23898", + RuntimeStartRounds: map[common.Runtime]uint64{ + common.RuntimeCipher: 0, + common.RuntimeEmerald: 0, + }, + ChainContext: "53852332637bacb61b91b6411ab4095168ba02a50be4c3f82448438826f23898", }, { // https://github.com/oasisprotocol/mainnet-artifacts/releases/tag/2020-11-18 @@ -97,11 +135,20 @@ var DefaultChains = map[string]*History{ // https://github.com/oasisprotocol/testnet-artifacts/releases/tag/2022-03-03 ArchiveName: "2022-03-03", GenesisHeight: 8535081, + RuntimeStartRounds: map[common.Runtime]uint64{ + common.RuntimeCipher: 1675996, + common.RuntimeEmerald: 398623, + common.RuntimeSapphire: 0, + }, }, { // https://github.com/oasisprotocol/testnet-artifacts/releases/tag/2021-04-13 ArchiveName: "2021-04-13", GenesisHeight: 3398334, + RuntimeStartRounds: map[common.Runtime]uint64{ + common.RuntimeCipher: 0, + common.RuntimeEmerald: 0, + }, }, { // https://github.com/oasisprotocol/testnet-artifacts/releases/tag/2021-03-24 diff --git a/storage/oasis/client.go b/storage/oasis/client.go index 6be51e216..76fcd07af 100644 --- a/storage/oasis/client.go +++ b/storage/oasis/client.go @@ -6,10 +6,8 @@ import ( "context" "fmt" - "github.com/oasisprotocol/oasis-indexer/analyzer" + "github.com/oasisprotocol/oasis-indexer/common" "github.com/oasisprotocol/oasis-indexer/config" - "github.com/oasisprotocol/oasis-indexer/storage/oasis/connections" - "github.com/oasisprotocol/oasis-indexer/storage/oasis/nodeapi" "github.com/oasisprotocol/oasis-indexer/storage/oasis/nodeapi/history" ) @@ -30,27 +28,15 @@ func NewConsensusClient(ctx context.Context, sourceConfig *config.SourceConfig) } // NewRuntimeClient creates a new RuntimeClient. -func NewRuntimeClient(ctx context.Context, sourceConfig *config.SourceConfig, runtime analyzer.Runtime) (*RuntimeClient, error) { - currentHistoryRecord := sourceConfig.History().CurrentRecord() - currentNode := sourceConfig.Nodes[currentHistoryRecord.ArchiveName] - rawConn, err := connections.RawConnect(currentNode) - if err != nil { - return nil, fmt.Errorf("indexer RawConnect: %w", err) - } - sdkConn, err := connections.SDKConnect(ctx, currentHistoryRecord.ChainContext, currentNode, sourceConfig.FastStartup) - if err != nil { - return nil, err - } +func NewRuntimeClient(ctx context.Context, sourceConfig *config.SourceConfig, runtime common.Runtime) (*RuntimeClient, error) { sdkPT := sourceConfig.SDKNetwork().ParaTimes.All[runtime.String()] - sdkClient := sdkConn.Runtime(sdkPT) - - info, err := sdkClient.GetInfo(ctx) + nodeApi, err := history.NewHistoryRuntimeApiLite(ctx, sourceConfig.History(), sdkPT, sourceConfig.Nodes, sourceConfig.FastStartup, runtime) if err != nil { - return nil, err + return nil, fmt.Errorf("instantiating history runtime API lite: %w", err) } return &RuntimeClient{ - nodeApi: nodeapi.NewUniversalRuntimeApiLite(info.ID, rawConn, &sdkClient), - info: info, + nodeApi: nodeApi, + sdkPT: sdkPT, }, nil } diff --git a/storage/oasis/nodeapi/history/history.go b/storage/oasis/nodeapi/history/history.go index ab226eb04..6d7fdc6af 100644 --- a/storage/oasis/nodeapi/history/history.go +++ b/storage/oasis/nodeapi/history/history.go @@ -45,7 +45,7 @@ type HistoryConsensusApiLite struct { } func NewHistoryConsensusApiLite(ctx context.Context, history *config.History, nodes map[string]*config.NodeConfig, fastStartup bool) (*HistoryConsensusApiLite, error) { - APIs := map[string]nodeapi.ConsensusApiLite{} + apis := map[string]nodeapi.ConsensusApiLite{} for _, record := range history.Records { if nodeConfig, ok := nodes[record.ArchiveName]; ok { apiConstructor := APIConstructors[record.ArchiveName] @@ -56,12 +56,12 @@ func NewHistoryConsensusApiLite(ctx context.Context, history *config.History, no if err != nil { return nil, fmt.Errorf("connecting to archive %s: %w", record.ArchiveName, err) } - APIs[record.ArchiveName] = api + apis[record.ArchiveName] = api } } return &HistoryConsensusApiLite{ History: history, - APIs: APIs, + APIs: apis, }, nil } @@ -72,7 +72,7 @@ func (c *HistoryConsensusApiLite) APIForHeight(height int64) (nodeapi.ConsensusA } api, ok := c.APIs[record.ArchiveName] if !ok { - return nil, fmt.Errorf("archive %s for has no node configured", record.ArchiveName) + return nil, fmt.Errorf("archive %s has no node configured", record.ArchiveName) } return api, nil } diff --git a/storage/oasis/nodeapi/history/runtime.go b/storage/oasis/nodeapi/history/runtime.go new file mode 100644 index 000000000..c4acd4829 --- /dev/null +++ b/storage/oasis/nodeapi/history/runtime.go @@ -0,0 +1,88 @@ +package history + +import ( + "context" + "fmt" + + sdkConfig "github.com/oasisprotocol/oasis-sdk/client-sdk/go/config" + + "github.com/oasisprotocol/oasis-indexer/common" + "github.com/oasisprotocol/oasis-indexer/config" + "github.com/oasisprotocol/oasis-indexer/storage/oasis/connections" + "github.com/oasisprotocol/oasis-indexer/storage/oasis/nodeapi" +) + +var _ nodeapi.RuntimeApiLite = (*HistoryRuntimeApiLite)(nil) + +type HistoryRuntimeApiLite struct { + Runtime common.Runtime + History *config.History + APIs map[string]nodeapi.RuntimeApiLite +} + +func NewHistoryRuntimeApiLite(ctx context.Context, history *config.History, sdkPT *sdkConfig.ParaTime, nodes map[string]*config.NodeConfig, fastStartup bool, runtime common.Runtime) (*HistoryRuntimeApiLite, error) { + apis := map[string]nodeapi.RuntimeApiLite{} + for _, record := range history.Records { + if nodeConfig, ok := nodes[record.ArchiveName]; ok { + sdkConn, err := connections.SDKConnect(ctx, record.ChainContext, nodeConfig, fastStartup) + if err != nil { + return nil, err + } + sdkClient := sdkConn.Runtime(sdkPT) + rawConn, err := connections.RawConnect(nodeConfig) + if err != nil { + return nil, fmt.Errorf("indexer RawConnect: %w", err) + } + apis[record.ArchiveName] = nodeapi.NewUniversalRuntimeApiLite(sdkPT.Namespace(), rawConn, &sdkClient) + } + } + return &HistoryRuntimeApiLite{ + Runtime: runtime, + History: history, + APIs: apis, + }, nil +} + +func (rc *HistoryRuntimeApiLite) APIForRound(round uint64) (nodeapi.RuntimeApiLite, error) { + record, err := rc.History.RecordForRuntimeRound(rc.Runtime, round) + if err != nil { + return nil, fmt.Errorf("determining archive: %w", err) + } + api, ok := rc.APIs[record.ArchiveName] + if !ok { + return nil, fmt.Errorf("archive %s has no node configured", record.ArchiveName) + } + return api, nil +} + +func (rc *HistoryRuntimeApiLite) GetEventsRaw(ctx context.Context, round uint64) ([]*nodeapi.RuntimeEvent, error) { + api, err := rc.APIForRound(round) + if err != nil { + return nil, fmt.Errorf("getting api for runtime %s round %d: %w", rc.Runtime, round, err) + } + return api.GetEventsRaw(ctx, round) +} + +func (rc *HistoryRuntimeApiLite) EVMSimulateCall(ctx context.Context, round uint64, gasPrice []byte, gasLimit uint64, caller []byte, address []byte, value []byte, data []byte) ([]byte, error) { + api, err := rc.APIForRound(round) + if err != nil { + return nil, fmt.Errorf("getting api for runtime %s round %d: %w", rc.Runtime, round, err) + } + return api.EVMSimulateCall(ctx, round, gasPrice, gasLimit, caller, address, value, data) +} + +func (rc *HistoryRuntimeApiLite) GetBlockHeader(ctx context.Context, round uint64) (*nodeapi.RuntimeBlockHeader, error) { + api, err := rc.APIForRound(round) + if err != nil { + return nil, fmt.Errorf("getting api for runtime %s round %d: %w", rc.Runtime, round, err) + } + return api.GetBlockHeader(ctx, round) +} + +func (rc *HistoryRuntimeApiLite) GetTransactionsWithResults(ctx context.Context, round uint64) ([]*nodeapi.RuntimeTransactionWithResults, error) { + api, err := rc.APIForRound(round) + if err != nil { + return nil, fmt.Errorf("getting api for runtime %s round %d: %w", rc.Runtime, round, err) + } + return api.GetTransactionsWithResults(ctx, round) +} diff --git a/storage/oasis/runtime.go b/storage/oasis/runtime.go index 5fca96d0e..e0c5c0319 100644 --- a/storage/oasis/runtime.go +++ b/storage/oasis/runtime.go @@ -17,7 +17,7 @@ import ( // TODO: Get rid of this struct, it hardly provides any value. type RuntimeClient struct { nodeApi nodeapi.RuntimeApiLite - info *sdkTypes.RuntimeInfo + sdkPT *sdkConfig.ParaTime } // AllData returns all relevant data to the given round. @@ -49,19 +49,7 @@ func (rc *RuntimeClient) EVMSimulateCall(ctx context.Context, round uint64, gasP } func (rc *RuntimeClient) nativeTokenSymbol() string { - for _, network := range sdkConfig.DefaultNetworks.All { - // Iterate over all networks and find the one that contains the runtime. - // Any network will do; we assume that paratime IDs are unique across networks. - // TODO: Remove this assumption; paratime IDs are chosen by the entity that registers them, - // so conflicts (particularly intentional/malicious) are possible. - // https://github.com/oasisprotocol/oasis-indexer/pull/362#discussion_r1153606360 - for _, paratime := range network.ParaTimes.All { - if paratime.ID == rc.info.ID.Hex() { - return paratime.Denominations[sdkConfig.NativeDenominationKey].Symbol - } - } - } - panic("Cannot find native token symbol for runtime") + return rc.sdkPT.Denominations[sdkConfig.NativeDenominationKey].Symbol } func (rc *RuntimeClient) StringifyDenomination(d sdkTypes.Denomination) string { diff --git a/tests/statecheck/runtime_test.go b/tests/statecheck/runtime_test.go index 71936ae34..6e0615132 100644 --- a/tests/statecheck/runtime_test.go +++ b/tests/statecheck/runtime_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/oasisprotocol/oasis-indexer/analyzer" common "github.com/oasisprotocol/oasis-indexer/common" ) @@ -39,15 +38,15 @@ func testRuntimeAccounts(t *testing.T, runtime string) { ctx := context.Background() - network, err := analyzer.FromChainContext(MainnetChainContext) + network, err := common.FromChainContext(MainnetChainContext) require.Nil(t, err) var runtimeID string switch runtime { case "emerald": - runtimeID, err = analyzer.RuntimeEmerald.ID(network) + runtimeID, err = common.RuntimeEmerald.ID(network) case "sapphire": - runtimeID, err = analyzer.RuntimeSapphire.ID(network) + runtimeID, err = common.RuntimeSapphire.ID(network) } require.Nil(t, err) t.Log("Runtime ID determined", "runtime", runtime, "runtime_id", runtimeID)