Skip to content

Commit

Permalink
feat: json rpc (#22)
Browse files Browse the repository at this point in the history
* wip

* wip2

* feat: json-rpc signing

* direction

* add indexer

* enable jsonrpc

* add precision checking logic

* fix test

* reterive value from the msg

* fix: handle unit conversion in cosmos to ethereum tx (#24)

whenever ethereum tx is converted into cosmos tx by ConvertEthereumTxToCosmosTx, the value is converted to cosmos fee unit from wei.
so when convert the cosmos tx to back the original ethereum tx, we should convert the value back to wei to get original ethereum tx.

* fix pubkey recover

* fix test

* feat: implement new apis and refactoring (#28)

* feat: new eth apis, refactor: code structures

new eth apis
- GetStorageAt
- GetCode

refactor
- Restricted the scope of access for each namespace API (instead of accessing the backend directly, now access is through a scoped wrapper)
- Also renamed files and repositioned some methods within the backend directory to better reflect their purpose

* fix: not implemented txpool api

temp disable txpool api because we don't have any implementation

* fix: change event type for extract logs

* add cap for filters

* add filter system and apis

* fix comment

Co-authored-by: zsystm <[email protected]>

* delete unused interfaces and comments

* fix varible and function naming

* add TODO comments

* delete unused functions

* add error handling

* Set websocket params to prevent timeout

* fix: change GetLogsByHeight interface

* chore: change variable names from tm to comet

* docs: add comments about websocket client params

* fix: go.mod and go.work.sum

ran the followings for this commit:
- go mod tidy
- go work sync

* update: cometbft dep

current version of cometbft which minievm uses have a bug(cometbft/cometbft#3264 (comment)) which makes transaction broadcasting unavailable.

---------

Co-authored-by: jason song <[email protected]>
Co-authored-by: jasonsong0 <[email protected]>

* feat: directly subscribe logs from indexer  (#34)

* change filter system to directly subscribe from indexer

* use mutex and pass logs in bulk

* fix dependency problems

* add readme

* update readme

---------

Co-authored-by: suha jin <[email protected]>
Co-authored-by: zsystm <[email protected]>
Co-authored-by: jason song <[email protected]>
Co-authored-by: jasonsong0 <[email protected]>
  • Loading branch information
5 people authored Jul 12, 2024
1 parent a59fe85 commit cbf3be3
Show file tree
Hide file tree
Showing 84 changed files with 6,173 additions and 342 deletions.
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
# MiniEVM

MiniEVM is an optimistic rollup consumer chain powered by EVM, designed to simplify the process of bootstrapping an L2 network. The main advantage of using MiniEVM is that the users can leverage the OPinit stack for enhanced security and utilize all the Initia ecosystem tooling from day one, without the need to prepare a validator group or build the users' own ecosystem tools.
- https://github.com/initia-labs/evm

- [go-ethereum](https://github.com/initia-labs/evm)

## Prerequisites

Expand All @@ -19,6 +20,9 @@ To get started with L2, please visit the [documentation](https://initia.gitbook.
- Integrates seamlessly with the OPinit stack, enhancing security.
- Provides immediate access to the full suite of Initia ecosystem tools right from the start.

## JSON-RPC

For information of supported JSON-RPC methods, please refer to the [JSON-RPC documentation](jsonrpc/README.md).

## Contributing

Expand Down
20 changes: 15 additions & 5 deletions app/ante/ante.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ import (

opchildante "github.com/initia-labs/OPinit/x/opchild/ante"
opchildtypes "github.com/initia-labs/OPinit/x/opchild/types"
"github.com/initia-labs/initia/app/ante/accnum"
"github.com/initia-labs/initia/app/ante/sigverify"
evmkeeper "github.com/initia-labs/minievm/x/evm/keeper"

"github.com/skip-mev/block-sdk/v2/block"
auctionante "github.com/skip-mev/block-sdk/v2/x/auction/ante"
Expand All @@ -25,9 +28,11 @@ type HandlerOptions struct {
IBCkeeper *ibckeeper.Keeper
OPChildKeeper opchildtypes.AnteKeeper
AuctionKeeper auctionkeeper.Keeper
TxEncoder sdk.TxEncoder
MevLane auctionante.MEVLane
FreeLane block.Lane
EVMKeeper *evmkeeper.Keeper

TxEncoder sdk.TxEncoder
MevLane auctionante.MEVLane
FreeLane block.Lane
}

// NewAnteHandler returns an AnteHandler that checks and increments sequence
Expand All @@ -46,9 +51,13 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "sign mode handler is required for ante builder")
}

if options.EVMKeeper == nil {
return nil, errorsmod.Wrap(sdkerrors.ErrLogic, "EVM keeper is required for ante builder")
}

sigGasConsumer := options.SigGasConsumer
if sigGasConsumer == nil {
sigGasConsumer = ante.DefaultSigVerificationGasConsumer
sigGasConsumer = sigverify.DefaultSigVerificationGasConsumer
}

txFeeChecker := options.TxFeeChecker
Expand All @@ -72,6 +81,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
}

anteDecorators := []sdk.AnteDecorator{
accnum.NewAccountNumberDecorator(options.AccountKeeper),
ante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
ante.NewExtensionOptionsDecorator(options.ExtensionOptionChecker),
ante.NewValidateBasicDecorator(),
Expand All @@ -83,7 +93,7 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) {
ante.NewSetPubKeyDecorator(options.AccountKeeper),
ante.NewValidateSigCountDecorator(options.AccountKeeper),
ante.NewSigGasConsumeDecorator(options.AccountKeeper, sigGasConsumer),
ante.NewSigVerificationDecorator(options.AccountKeeper, options.SignModeHandler),
NewSigVerificationDecorator(options.AccountKeeper, options.EVMKeeper, options.SignModeHandler),
ante.NewIncrementSequenceDecorator(options.AccountKeeper),
ibcante.NewRedundantRelayDecorator(options.IBCkeeper),
auctionante.NewAuctionDecorator(options.AuctionKeeper, options.TxEncoder, options.MevLane),
Expand Down
200 changes: 200 additions & 0 deletions app/ante/sigverify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
package ante

import (
"fmt"

"google.golang.org/protobuf/types/known/anypb"

errorsmod "cosmossdk.io/errors"
storetypes "cosmossdk.io/store/types"
txsigning "cosmossdk.io/x/tx/signing"

codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256r1"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/types"

"github.com/initia-labs/initia/crypto/ethsecp256k1"
evmkeeper "github.com/initia-labs/minievm/x/evm/keeper"
)

// SigVerificationDecorator verifies all signatures for a tx and return an error if any are invalid. Note,
// the SigVerificationDecorator will not check signatures on ReCheck.
//
// CONTRACT: Pubkeys are set in context for all signers before this decorator runs
// CONTRACT: Tx must implement SigVerifiableTx interface
type SigVerificationDecorator struct {
ak authante.AccountKeeper
ek *evmkeeper.Keeper
signModeHandler *txsigning.HandlerMap
}

func NewSigVerificationDecorator(
ak authante.AccountKeeper,
ek *evmkeeper.Keeper,
signModeHandler *txsigning.HandlerMap) SigVerificationDecorator {
return SigVerificationDecorator{
ak: ak,
ek: ek,
signModeHandler: signModeHandler,
}
}

func (svd SigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
sigTx, ok := tx.(authsigning.Tx)
if !ok {
return ctx, errorsmod.Wrap(sdkerrors.ErrTxDecode, "invalid transaction type")
}

// stdSigs contains the sequence number, account number, and signatures.
// When simulating, this would just be a 0-length slice.
sigs, err := sigTx.GetSignaturesV2()
if err != nil {
return ctx, err
}

signers, err := sigTx.GetSigners()
if err != nil {
return ctx, err
}

// check that signer length and signature length are the same
if len(sigs) != len(signers) {
return ctx, errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "invalid number of signer; expected: %d, got %d", len(signers), len(sigs))
}

for i, sig := range sigs {
acc, err := authante.GetSignerAcc(ctx, svd.ak, signers[i])
if err != nil {
return ctx, err
}

// retrieve pubkey
pubKey := acc.GetPubKey()
if !simulate && pubKey == nil {
return ctx, errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "pubkey on account is not set")
}

// Check account sequence number.
if sig.Sequence != acc.GetSequence() {
return ctx, errorsmod.Wrapf(
sdkerrors.ErrWrongSequence,
"account sequence mismatch, expected %d, got %d", acc.GetSequence(), sig.Sequence,
)
}

// retrieve signer data
genesis := ctx.BlockHeight() == 0
chainID := ctx.ChainID()
var accNum uint64
if !genesis {
accNum = acc.GetAccountNumber()
}

// no need to verify signatures on recheck tx
if !simulate && !ctx.IsReCheckTx() {
anyPk, _ := codectypes.NewAnyWithValue(pubKey)

signerData := txsigning.SignerData{
Address: acc.GetAddress().String(),
ChainID: chainID,
AccountNumber: accNum,
Sequence: acc.GetSequence(),
PubKey: &anypb.Any{
TypeUrl: anyPk.TypeUrl,
Value: anyPk.Value,
},
}
adaptableTx, ok := tx.(authsigning.V2AdaptableTx)
if !ok {
return ctx, fmt.Errorf("expected tx to implement V2AdaptableTx, got %T", tx)
}
txData := adaptableTx.GetSigningTxData()
err = verifySignature(ctx, pubKey, signerData, sig.Data, svd.signModeHandler, txData, svd.ek, tx)
if err != nil {
var errMsg string
if authante.OnlyLegacyAminoSigners(sig.Data) {
// If all signers are using SIGN_MODE_LEGACY_AMINO, we rely on VerifySignature to check account sequence number,
// and therefore communicate sequence number as a potential cause of error.
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d), sequence (%d) and chain-id (%s)", accNum, acc.GetSequence(), chainID)
} else {
errMsg = fmt.Sprintf("signature verification failed; please verify account number (%d) and chain-id (%s): (%s)", accNum, chainID, err.Error())
}
return ctx, errorsmod.Wrap(sdkerrors.ErrUnauthorized, errMsg)

}
}
}

return next(ctx, tx, simulate)
}

// defaultSigVerificationGasConsumer is the default implementation of SignatureVerificationGasConsumer. It consumes gas
// for signature verification based upon the public key type. The cost is fetched from the given params and is matched
// by the concrete type.
func DefaultSigVerificationGasConsumer(
meter storetypes.GasMeter, sig signing.SignatureV2, params types.Params,
) error {
pubkey := sig.PubKey
switch pubkey := pubkey.(type) {
case *ed25519.PubKey:
meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519")
return errorsmod.Wrap(sdkerrors.ErrInvalidPubKey, "ED25519 public keys are unsupported")

case *secp256k1.PubKey, *ethsecp256k1.PubKey:
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
return nil

case *secp256r1.PubKey:
meter.ConsumeGas(params.SigVerifyCostSecp256r1(), "ante verify: secp256r1")
return nil

case multisig.PubKey:
multisignature, ok := sig.Data.(*signing.MultiSignatureData)
if !ok {
return fmt.Errorf("expected %T, got, %T", &signing.MultiSignatureData{}, sig.Data)
}
err := consumeMultisignatureVerificationGas(meter, multisignature, pubkey, params, sig.Sequence)
if err != nil {
return err
}
return nil

default:
return errorsmod.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey)
}
}

// consumeMultisignatureVerificationGas consumes gas from a GasMeter for verifying a multisig pubkey signature
func consumeMultisignatureVerificationGas(
meter storetypes.GasMeter, sig *signing.MultiSignatureData, pubkey multisig.PubKey,
params types.Params, accSeq uint64,
) error {
size := sig.BitArray.Count()
sigIndex := 0

for i := 0; i < size; i++ {
if !sig.BitArray.GetIndex(i) {
continue
}
sigV2 := signing.SignatureV2{
PubKey: pubkey.GetPubKeys()[i],
Data: sig.Signatures[sigIndex],
Sequence: accSeq,
}
err := DefaultSigVerificationGasConsumer(meter, sigV2, params)
if err != nil {
return err
}
sigIndex++
}

return nil
}
116 changes: 116 additions & 0 deletions app/ante/verify.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package ante

import (
"context"
"fmt"

signingv1beta1 "cosmossdk.io/api/cosmos/tx/signing/v1beta1"
errorsmod "cosmossdk.io/errors"
txsigning "cosmossdk.io/x/tx/signing"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/crypto/types/multisig"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/tx/signing"

evmkeeper "github.com/initia-labs/minievm/x/evm/keeper"
evmtypes "github.com/initia-labs/minievm/x/evm/types"

coretypes "github.com/ethereum/go-ethereum/core/types"
)

// internalSignModeToAPI converts a signing.SignMode to a protobuf SignMode.
func internalSignModeToAPI(mode signing.SignMode) (signingv1beta1.SignMode, error) {
switch mode {
case signing.SignMode_SIGN_MODE_DIRECT:
return signingv1beta1.SignMode_SIGN_MODE_DIRECT, nil
case signing.SignMode_SIGN_MODE_LEGACY_AMINO_JSON:
return signingv1beta1.SignMode_SIGN_MODE_LEGACY_AMINO_JSON, nil
case signing.SignMode_SIGN_MODE_TEXTUAL:
return signingv1beta1.SignMode_SIGN_MODE_TEXTUAL, nil
case signing.SignMode_SIGN_MODE_DIRECT_AUX:
return signingv1beta1.SignMode_SIGN_MODE_DIRECT_AUX, nil
case signing.SignMode_SIGN_MODE_EIP_191:
return signingv1beta1.SignMode_SIGN_MODE_EIP_191, nil
default:
return signingv1beta1.SignMode_SIGN_MODE_UNSPECIFIED, fmt.Errorf("unsupported sign mode %s", mode)
}
}

// verifySignature verifies a transaction signature contained in SignatureData abstracting over different signing
// modes. It differs from verifySignature in that it uses the new txsigning.TxData interface in x/tx.
func verifySignature(
ctx context.Context,
pubKey cryptotypes.PubKey,
signerData txsigning.SignerData,
signatureData signing.SignatureData,
handler *txsigning.HandlerMap,
txData txsigning.TxData,
// required to verify EVM signatures
ek *evmkeeper.Keeper,
tx sdk.Tx,
) error {
switch data := signatureData.(type) {
case *signing.SingleSignatureData:
if data.SignMode == evmkeeper.SignMode_SIGN_MODE_ETHEREUM {
// eth sign mode
ethTx, expectedSender, err := evmkeeper.NewTxUtils(ek).ConvertCosmosTxToEthereumTx(ctx, tx)
if err != nil {
return err
}
if ethTx == nil {
return fmt.Errorf("failed to convert tx to ethereum tx")
}

sdkCtx := sdk.UnwrapSDKContext(ctx)
ethChainID := evmtypes.ConvertCosmosChainIDToEthereumChainID(sdkCtx.ChainID())
signer := coretypes.LatestSignerForChainID(ethChainID)
sender, err := signer.Sender(ethTx)
if err != nil {
return errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "failed to recover sender address: %v", err)
}

// check if the recovered sender matches the expected sender
if expectedSender == nil || *expectedSender != sender {
return errorsmod.Wrapf(sdkerrors.ErrorInvalidSigner, "expected sender %s, got %s", expectedSender, sender)
}

return nil
}

signMode, err := internalSignModeToAPI(data.SignMode)
if err != nil {
return err
}
signBytes, err := handler.GetSignBytes(ctx, signMode, signerData, txData)
if err != nil {
return err
}

if !pubKey.VerifySignature(signBytes, data.Signature) {
return fmt.Errorf("unable to verify single signer signature")
}

return nil

case *signing.MultiSignatureData:
multiPK, ok := pubKey.(multisig.PubKey)
if !ok {
return fmt.Errorf("expected %T, got %T", (multisig.PubKey)(nil), pubKey)
}
err := multiPK.VerifyMultisignature(func(mode signing.SignMode) ([]byte, error) {
signMode, err := internalSignModeToAPI(mode)
if err != nil {
return nil, err
}
return handler.GetSignBytes(ctx, signMode, signerData, txData)
}, data)
if err != nil {
return err
}
return nil
default:
return fmt.Errorf("unexpected SignatureData %T", signatureData)
}
}
Loading

0 comments on commit cbf3be3

Please sign in to comment.