Skip to content

Commit

Permalink
feat: Support simplified revert reason encoding
Browse files Browse the repository at this point in the history
  • Loading branch information
ptrus committed Sep 20, 2023
1 parent 9b8bc3d commit 440f73c
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 58 deletions.
25 changes: 23 additions & 2 deletions indexer/backend.go
Expand Up @@ -3,6 +3,7 @@ package indexer
import (
"context"
"errors"
"sync"

ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/oasisprotocol/oasis-core/go/common"
Expand All @@ -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"
Expand Down Expand Up @@ -82,13 +84,17 @@ 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.
Prune(ctx context.Context, round uint64) error

// SetObserver sets the intrusive backend observer.
SetObserver(BackendObserver)

// RuntimeInfo returns the runtime info.
RuntimeInfo() *core.RuntimeInfoResponse
}

// BlockData contains all per block indexed data.
Expand All @@ -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
Expand All @@ -130,14 +140,19 @@ 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)
if err != nil {
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)

Expand Down Expand Up @@ -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

Check warning on line 188 in indexer/backend.go

View check run for this annotation

Codecov / codecov/patch

indexer/backend.go#L185-L188

Added lines #L185 - L188 were not covered by tests
}

func (ib *indexBackend) WatchBlocks(ctx context.Context, buffer int64) (<-chan *BlockData, pubsub.ClosableSubscription, error) {
typedCh := make(chan *BlockData)
sub := ib.blockNotifier.SubscribeBuffered(buffer)
Expand Down
8 changes: 7 additions & 1 deletion indexer/backend_cache.go
Expand Up @@ -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"

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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()

Check warning on line 414 in indexer/backend_cache.go

View check run for this annotation

Codecov / codecov/patch

indexer/backend_cache.go#L413-L414

Added lines #L413 - L414 were not covered by tests
}

func (cb *cachingBackend) pruneCache(
blockNumber uint64,
) {
Expand Down
25 changes: 16 additions & 9 deletions indexer/indexer.go
Expand Up @@ -53,8 +53,9 @@ type Service struct {
client client.RuntimeClient
core core.V1

queryBlockGasLimit bool
blockGasLimit uint64
queryEpochParameters bool
blockGasLimit uint64
rtInfo *core.RuntimeInfoResponse

ctx context.Context
cancelCtx context.CancelFunc
Expand All @@ -77,34 +78,40 @@ func (s *Service) indexBlock(ctx context.Context, round uint64) error {
return fmt.Errorf("querying block: %w", err)
}

if s.blockGasLimit == 0 || s.queryBlockGasLimit {
if s.blockGasLimit == 0 || s.rtInfo == nil || s.queryEpochParameters {
// Query parameters for block gas limit.
var params *core.Parameters
params, err = s.core.Parameters(ctx, round)
if err != nil {
return fmt.Errorf("querying block parameters: %w", err)
}
s.blockGasLimit = params.MaxBatchGas

// Query runtime info.
s.rtInfo, err = s.core.RuntimeInfo(ctx)
if err != nil {
return fmt.Errorf("querying runtime info: %w", err)
}

Check warning on line 94 in indexer/indexer.go

View check run for this annotation

Codecov / codecov/patch

indexer/indexer.go#L93-L94

Added lines #L93 - L94 were not covered by tests
}

txs, err := s.client.GetTransactionsWithResults(ctx, blk.Header.Round)
if err != nil {
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, s.rtInfo)
if err != nil {
return fmt.Errorf("indexing block: %w", err)
}
metricBlockIndexed.Set(float64(blk.Header.Round))

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:
}

Expand Down
47 changes: 1 addition & 46 deletions rpc/eth/api.go
Expand Up @@ -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"
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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(ctx, logger, err)

Check warning on line 402 in rpc/eth/api.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/api.go#L402

Added line #L402 was not covered by tests
}

logger.Debug("response", "args", args, "resp", res)
Expand Down
104 changes: 104 additions & 0 deletions rpc/eth/revert_errors.go
@@ -0,0 +1,104 @@
package eth

import (
"context"
"encoding/base64"
"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/core"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/evm"
)

const (
revertErrorPrefix = "reverted: "
)

type RevertError struct {
error
Reason string `json:"reason"`
}

// ErrorData returns the ABI encoded error reason.
func (e *RevertError) ErrorData() interface{} {
return e.Reason

Check warning on line 26 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L25-L26

Added lines #L25 - L26 were not covered by tests
}

func (api *publicAPI) newRevertErrorV1(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 V1 revert error", "revert_reason", revertReason, "err", err)
return &RevertError{
error: revertErr,
}
}
abiReason = append(abiReason, unpacked...)

return &RevertError{
error: revertErr,
Reason: hexutil.Encode(abiReason),
}

Check warning on line 48 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L29-L48

Added lines #L29 - L48 were not covered by tests
}

func (api *publicAPI) newRevertErrorV2(revertErr error) *RevertError {
b64Reason := strings.TrimPrefix(revertErr.Error(), revertErrorPrefix)
reason, err := base64.RawStdEncoding.DecodeString(b64Reason)
if err != nil {
api.Logger.Error("failed to encode V2 revert error", "revert_reason", b64Reason, "err", err)
return &RevertError{
error: revertErr,
}
}

Check warning on line 59 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L51-L59

Added lines #L51 - L59 were not covered by tests

return &RevertError{
error: revertErr,
Reason: hexutil.Encode(reason),
}

Check warning on line 64 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L61-L64

Added lines #L61 - L64 were not covered by tests
}

func (api *publicAPI) handleCallFailure(ctx context.Context, logger *logging.Logger, err error) error {
if err == nil {
return nil
}

Check warning on line 70 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L67-L70

Added lines #L67 - L70 were not covered by tests

// No special handling for non-reverts.
if !strings.HasPrefix(err.Error(), revertErrorPrefix) {
logger.Debug("failed to execute SimulateCall", "err", err)
return err
}

Check warning on line 76 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L73-L76

Added lines #L73 - L76 were not covered by tests

// Query the indexer for runtime info, to obtain EVM module version.
rtInfo := api.backend.RuntimeInfo()
if rtInfo == nil {
// In case the node has indexer disabled, query the runtime info on the fly.
// XXX: This could be cached per epoch.
var cerr error
rtInfo, cerr = core.NewV1(api.client).RuntimeInfo(ctx)
if cerr != nil {
logger.Debug("failed querying runtime info", "err", err)
}

Check warning on line 87 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L79-L87

Added lines #L79 - L87 were not covered by tests
}

// 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
var revertErr *RevertError
switch {
case rtInfo == nil || rtInfo.Modules[evm.ModuleName].Version > 1:
// EVM version 2 onward (if no runtime info assume >1).
revertErr = api.newRevertErrorV2(err)
default:
// EVM version 1 (deprecated).
revertErr = api.newRevertErrorV1(err)

Check warning on line 100 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L93-L100

Added lines #L93 - L100 were not covered by tests
}
logger.Debug("failed to execute SimulateCall, reverted", "err", err, "reason", revertErr.Reason)
return revertErr

Check warning on line 103 in rpc/eth/revert_errors.go

View check run for this annotation

Codecov / codecov/patch

rpc/eth/revert_errors.go#L102-L103

Added lines #L102 - L103 were not covered by tests
}

0 comments on commit 440f73c

Please sign in to comment.