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 600a3c1 commit 08835ac
Show file tree
Hide file tree
Showing 5 changed files with 113 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
}

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()
}

func (cb *cachingBackend) pruneCache(
blockNumber uint64,
) {
Expand Down
25 changes: 16 additions & 9 deletions indexer/indexer.go
Expand Up @@ -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
Expand All @@ -77,34 +77,41 @@ 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)
if err != nil {
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)
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, 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(logger, err)
}

logger.Debug("response", "args", args, "resp", res)
Expand Down
66 changes: 66 additions & 0 deletions 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
}
}

0 comments on commit 08835ac

Please sign in to comment.