diff --git a/indexer/backend.go b/indexer/backend.go index 632d8e3b..7315bcc7 100644 --- a/indexer/backend.go +++ b/indexer/backend.go @@ -3,6 +3,7 @@ package indexer import ( "context" "errors" + "sync" ethcommon "github.com/ethereum/go-ethereum/common" "github.com/oasisprotocol/oasis-core/go/common" @@ -12,6 +13,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/quantity" "github.com/oasisprotocol/oasis-core/go/roothash/api/block" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/client" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/core" "github.com/oasisprotocol/oasis-web3-gateway/db/model" "github.com/oasisprotocol/oasis-web3-gateway/filters" @@ -82,6 +84,7 @@ type Backend interface { oasisBlock *block.Block, txResults []*client.TransactionWithResults, blockGasLimit uint64, + rtInfo *core.RuntimeInfoResponse, ) error // Prune removes indexed data for rounds equal to or earlier than the passed round. @@ -89,6 +92,9 @@ type Backend interface { // SetObserver sets the intrusive backend observer. SetObserver(BackendObserver) + + // RuntimeInfo returns the runtime info. + RuntimeInfo() *core.RuntimeInfoResponse } // BlockData contains all per block indexed data. @@ -113,7 +119,11 @@ type BackendObserver interface { } type indexBackend struct { - runtimeID common.Namespace + runtimeID common.Namespace + + rtInfoLock sync.RWMutex + rtInfo *core.RuntimeInfoResponse + logger *logging.Logger storage storage.Storage blockNotifier *pubsub.Broker @@ -130,7 +140,7 @@ func (ib *indexBackend) SetObserver(ob BackendObserver) { } // Index indexes oasis block. -func (ib *indexBackend) Index(ctx context.Context, oasisBlock *block.Block, txResults []*client.TransactionWithResults, blockGasLimit uint64) error { +func (ib *indexBackend) Index(ctx context.Context, oasisBlock *block.Block, txResults []*client.TransactionWithResults, blockGasLimit uint64, rtInfo *core.RuntimeInfoResponse) error { round := oasisBlock.Header.Round err := ib.StoreBlockData(ctx, oasisBlock, txResults, blockGasLimit) @@ -138,6 +148,11 @@ func (ib *indexBackend) Index(ctx context.Context, oasisBlock *block.Block, txRe ib.logger.Error("generateEthBlock failed", "err", err) return err } + if rtInfo != nil { + ib.rtInfoLock.Lock() + ib.rtInfo = rtInfo + ib.rtInfoLock.Unlock() + } ib.logger.Info("indexed block", "round", round) @@ -167,6 +182,12 @@ func (ib *indexBackend) Prune(ctx context.Context, round uint64) error { }) } +func (ib *indexBackend) RuntimeInfo() *core.RuntimeInfoResponse { + ib.rtInfoLock.RLock() + defer ib.rtInfoLock.RUnlock() + return ib.rtInfo +} + func (ib *indexBackend) WatchBlocks(ctx context.Context, buffer int64) (<-chan *BlockData, pubsub.ClosableSubscription, error) { typedCh := make(chan *BlockData) sub := ib.blockNotifier.SubscribeBuffered(buffer) diff --git a/indexer/backend_cache.go b/indexer/backend_cache.go index aaa745f5..74b12475 100644 --- a/indexer/backend_cache.go +++ b/indexer/backend_cache.go @@ -12,6 +12,7 @@ import ( "github.com/oasisprotocol/oasis-core/go/common/pubsub" "github.com/oasisprotocol/oasis-core/go/roothash/api/block" "github.com/oasisprotocol/oasis-sdk/client-sdk/go/client" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/core" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" @@ -168,8 +169,9 @@ func (cb *cachingBackend) Index( oasisBlock *block.Block, txResults []*client.TransactionWithResults, blockGasLimit uint64, + rtInfo *core.RuntimeInfoResponse, ) error { - return cb.inner.Index(ctx, oasisBlock, txResults, blockGasLimit) + return cb.inner.Index(ctx, oasisBlock, txResults, blockGasLimit, rtInfo) } func (cb *cachingBackend) Prune( @@ -408,6 +410,10 @@ func (cb *cachingBackend) WatchBlocks(ctx context.Context, buffer int64) (<-chan return cb.inner.WatchBlocks(ctx, buffer) } +func (cb *cachingBackend) RuntimeInfo() *core.RuntimeInfoResponse { + return cb.inner.RuntimeInfo() +} + func (cb *cachingBackend) pruneCache( blockNumber uint64, ) { diff --git a/indexer/indexer.go b/indexer/indexer.go index da481102..ad1f657a 100644 --- a/indexer/indexer.go +++ b/indexer/indexer.go @@ -53,8 +53,8 @@ type Service struct { client client.RuntimeClient core core.V1 - queryBlockGasLimit bool - blockGasLimit uint64 + queryEpochParameters bool + blockGasLimit uint64 ctx context.Context cancelCtx context.CancelFunc @@ -77,7 +77,8 @@ func (s *Service) indexBlock(ctx context.Context, round uint64) error { return fmt.Errorf("querying block: %w", err) } - if s.blockGasLimit == 0 || s.queryBlockGasLimit { + var rtInfo *core.RuntimeInfoResponse + if s.blockGasLimit == 0 || s.queryEpochParameters { // Query parameters for block gas limit. var params *core.Parameters params, err = s.core.Parameters(ctx, round) @@ -85,6 +86,12 @@ func (s *Service) indexBlock(ctx context.Context, round uint64) error { return fmt.Errorf("querying block parameters: %w", err) } s.blockGasLimit = params.MaxBatchGas + + // Query runtime info. + rtInfo, err = s.core.RuntimeInfo(ctx) + if err != nil { + return fmt.Errorf("querying runtime info: %w", err) + } } txs, err := s.client.GetTransactionsWithResults(ctx, blk.Header.Round) @@ -92,7 +99,7 @@ func (s *Service) indexBlock(ctx context.Context, round uint64) error { return fmt.Errorf("querying transactions with results: %w", err) } - err = s.backend.Index(ctx, blk, txs, s.blockGasLimit) + err = s.backend.Index(ctx, blk, txs, s.blockGasLimit, rtInfo) if err != nil { return fmt.Errorf("indexing block: %w", err) } @@ -100,11 +107,11 @@ func (s *Service) indexBlock(ctx context.Context, round uint64) error { switch { case blk.Header.HeaderType == block.EpochTransition: - // Epoch transition block, ensure block gas is queried on next normal round. - s.queryBlockGasLimit = true - case s.queryBlockGasLimit && blk.Header.HeaderType == block.Normal: - // Block gas was queried, no need to query it until next epoch. - s.queryBlockGasLimit = false + // Epoch transition block, ensure epoch parameters are queried on next normal round. + s.queryEpochParameters = true + case s.queryEpochParameters && blk.Header.HeaderType == block.Normal: + // Epoch parameters were queried in last block, no need to re-query until next epoch. + s.queryEpochParameters = false default: } diff --git a/rpc/eth/api.go b/rpc/eth/api.go index c497d4e8..745ee9a9 100644 --- a/rpc/eth/api.go +++ b/rpc/eth/api.go @@ -8,9 +8,7 @@ import ( "errors" "fmt" "math/big" - "strings" - "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common/hexutil" ethtypes "github.com/ethereum/go-ethereum/core/types" @@ -50,10 +48,6 @@ var ( estimateGasSigSpec = estimateGasDummySigSpec() ) -const ( - revertErrorPrefix = "reverted: " -) - // API is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec. type API interface { // GetBlockByNumber returns the block identified by number. @@ -354,39 +348,6 @@ func (api *publicAPI) GetCode(ctx context.Context, address common.Address, block return res, nil } -type RevertError struct { - error - Reason string `json:"reason"` -} - -// ErrorData returns the ABI encoded error reason. -func (e *RevertError) ErrorData() interface{} { - return e.Reason -} - -// NewRevertError returns an revert error with ABI encoded revert reason. -func (api *publicAPI) NewRevertError(revertErr error) *RevertError { - // ABI encoded function. - abiReason := []byte{0x08, 0xc3, 0x79, 0xa0} // Keccak256("Error(string)") - - // ABI encode the revert Reason string. - revertReason := strings.TrimPrefix(revertErr.Error(), revertErrorPrefix) - typ, _ := abi.NewType("string", "", nil) - unpacked, err := (abi.Arguments{{Type: typ}}).Pack(revertReason) - if err != nil { - api.Logger.Error("failed to encode revert error", "revert_reason", revertReason, "err", err) - return &RevertError{ - error: revertErr, - } - } - abiReason = append(abiReason, unpacked...) - - return &RevertError{ - error: revertErr, - Reason: hexutil.Encode(abiReason), - } -} - func (api *publicAPI) Call(ctx context.Context, args utils.TransactionArgs, blockNrOrHash ethrpc.BlockNumberOrHash, _ *utils.StateOverride) (hexutil.Bytes, error) { logger := api.Logger.With("method", "eth_call", "block_or_hash", blockNrOrHash) logger.Debug("request", "args", args) @@ -438,13 +399,7 @@ func (api *publicAPI) Call(ctx context.Context, args utils.TransactionArgs, bloc input, ) if err != nil { - if strings.HasPrefix(err.Error(), revertErrorPrefix) { - revertErr := api.NewRevertError(err) - logger.Debug("failed to execute SimulateCall, reverted", "err", err, "reason", revertErr.Reason) - return nil, revertErr - } - logger.Debug("failed to execute SimulateCall", "err", err) - return nil, err + return nil, api.handleCallFailure(logger, err) } logger.Debug("response", "args", args, "resp", res) diff --git a/rpc/eth/revert_errors.go b/rpc/eth/revert_errors.go new file mode 100644 index 00000000..00102405 --- /dev/null +++ b/rpc/eth/revert_errors.go @@ -0,0 +1,66 @@ +package eth + +import ( + "strings" + + "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/oasisprotocol/oasis-core/go/common/logging" + "github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm" +) + +const ( + revertErrorPrefix = "reverted: " +) + +type RevertErrorV1 struct { + error + Reason string `json:"reason"` +} + +// ErrorData returns the ABI encoded error reason. +func (e *RevertErrorV1) ErrorData() interface{} { + return e.Reason +} + +func (api *publicAPI) newRevertErrorV1(revertErr error) *RevertErrorV1 { + // ABI encoded function. + abiReason := []byte{0x08, 0xc3, 0x79, 0xa0} // Keccak256("Error(string)") + + // ABI encode the revert Reason string. + revertReason := strings.TrimPrefix(revertErr.Error(), revertErrorPrefix) + typ, _ := abi.NewType("string", "", nil) + unpacked, err := (abi.Arguments{{Type: typ}}).Pack(revertReason) + if err != nil { + api.Logger.Error("failed to encode revert error", "revert_reason", revertReason, "err", err) + return &RevertErrorV1{ + error: revertErr, + } + } + abiReason = append(abiReason, unpacked...) + + return &RevertErrorV1{ + error: revertErr, + Reason: hexutil.Encode(abiReason), + } +} + +func (api *publicAPI) handleCallFailure(logger *logging.Logger, err error) error { + // In EVM module version 1 the SDK decoded and returned readable revert reasons. + // Since EVM version 2 the raw data is just base64 encoded and returned. + // Changed in https://github.com/oasisprotocol/oasis-sdk/pull/1479 + rtInfo := api.backend.RuntimeInfo() + switch rtInfo.Modules[evm.ModuleName].Version { + case 1: + if strings.HasPrefix(err.Error(), revertErrorPrefix) { + revertErr := api.newRevertErrorV1(err) + logger.Debug("failed to execute SimulateCall, reverted", "err", err, "reason", revertErr.Reason) + return revertErr + } + logger.Debug("failed to execute SimulateCall", "err", err) + return err + default: + // EVM version 2 onward. + return err + } +}