more fixes

This commit is contained in:
Federico Kunze 2021-04-18 18:39:15 +02:00
parent 614e62fb7e
commit cb2ab3d95d
No known key found for this signature in database
GPG Key ID: 655F93A970080A30
63 changed files with 83570 additions and 3393 deletions

View File

@ -2,6 +2,9 @@ package ante
import (
"fmt"
"runtime/debug"
log "github.com/xlab/suplog"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
@ -14,7 +17,6 @@ import (
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
const (
@ -28,6 +30,8 @@ const (
type AccountKeeper interface {
authante.AccountKeeper
NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
GetAccount(ctx sdk.Context, addr sdk.AccAddress) authtypes.AccountI
SetAccount(ctx sdk.Context, account authtypes.AccountI)
}
// BankKeeper defines an expected keeper interface for the bank module's Keeper
@ -42,26 +46,73 @@ type BankKeeper interface {
// transaction-level processing (e.g. fee payment, signature verification) before
// being passed onto it's respective handler.
func NewAnteHandler(
ak AccountKeeper, bankKeeper BankKeeper, evmKeeper EVMKeeper, signModeHandler authsigning.SignModeHandler,
ak AccountKeeper,
bankKeeper BankKeeper,
evmKeeper EVMKeeper,
signModeHandler authsigning.SignModeHandler,
) sdk.AnteHandler {
return func(
ctx sdk.Context, tx sdk.Tx, sim bool,
) (newCtx sdk.Context, err error) {
var anteHandler sdk.AnteHandler
switch tx.(type) {
case *evmtypes.MsgEthereumTx:
defer Recover(&err)
txWithExtensions, ok := tx.(authante.HasExtensionOptionsTx)
if ok {
opts := txWithExtensions.GetExtensionOptions()
if len(opts) > 0 {
switch typeURL := opts[0].GetTypeUrl(); typeURL {
case "/injective.evm.v1beta1.ExtensionOptionsEthereumTx":
// handle as *evmtypes.MsgEthereumTx
anteHandler = sdk.ChainAnteDecorators(
NewEthSetupContextDecorator(), // outermost AnteDecorator. EthSetUpContext must be called first
authante.NewRejectExtensionOptionsDecorator(),
NewEthMempoolFeeDecorator(evmKeeper),
authante.NewValidateBasicDecorator(),
NewEthValidateBasicDecorator(),
authante.TxTimeoutHeightDecorator{},
NewEthSigVerificationDecorator(),
NewAccountVerificationDecorator(ak, bankKeeper, evmKeeper),
NewNonceVerificationDecorator(ak),
NewEthAccountSetupDecorator(ak),
NewEthAccountVerificationDecorator(ak, bankKeeper, evmKeeper),
NewEthNonceVerificationDecorator(ak),
NewEthGasConsumeDecorator(ak, bankKeeper, evmKeeper),
NewIncrementSenderSequenceDecorator(ak), // innermost AnteDecorator.
NewEthIncrementSenderSequenceDecorator(ak), // innermost AnteDecorator.
)
case "/injective.evm.v1beta1.ExtensionOptionsWeb3Tx":
// handle as normal Cosmos SDK tx, except signature is checked for EIP712 representation
switch tx.(type) {
case sdk.Tx:
anteHandler = sdk.ChainAnteDecorators(
authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
authante.NewMempoolFeeDecorator(),
authante.NewValidateBasicDecorator(),
authante.TxTimeoutHeightDecorator{},
authante.NewValidateMemoDecorator(ak),
authante.NewConsumeGasForTxSizeDecorator(ak),
authante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators
authante.NewValidateSigCountDecorator(ak),
authante.NewDeductFeeDecorator(ak, bankKeeper),
authante.NewSigGasConsumeDecorator(ak, DefaultSigVerificationGasConsumer),
authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator
)
default:
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
}
default:
log.WithField("type_url", typeURL).Errorln("rejecting tx with unsupported extension option")
return ctx, sdkerrors.ErrUnknownExtensionOptions
}
return anteHandler(ctx, tx, sim)
}
}
// handle as totally normal Cosmos SDK tx
switch tx.(type) {
case sdk.Tx:
anteHandler = sdk.ChainAnteDecorators(
authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
@ -71,7 +122,6 @@ func NewAnteHandler(
authante.TxTimeoutHeightDecorator{},
authante.NewValidateMemoDecorator(ak),
authante.NewConsumeGasForTxSizeDecorator(ak),
authante.NewRejectFeeGranterDecorator(),
authante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators
authante.NewValidateSigCountDecorator(ak),
authante.NewDeductFeeDecorator(ak, bankKeeper),
@ -87,7 +137,20 @@ func NewAnteHandler(
}
}
var _ = DefaultSigVerificationGasConsumer
func Recover(err *error) {
if r := recover(); r != nil {
*err = sdkerrors.Wrapf(sdkerrors.ErrPanic, "%v", r)
if e, ok := r.(error); ok {
log.WithError(e).Errorln("ante handler panicked with an error")
log.Debugln(string(debug.Stack()))
} else {
log.Errorln(r)
}
}
}
var _ authante.SignatureVerificationGasConsumer = DefaultSigVerificationGasConsumer
// 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
@ -105,7 +168,7 @@ func DefaultSigVerificationGasConsumer(
meter.ConsumeGas(params.SigVerifyCostSecp256k1, "ante verify: secp256k1")
return nil
// support for etherum ECDSA secp256k1 keys
// support for ethereum ECDSA secp256k1 keys
case *ethsecp256k1.PubKey:
meter.ConsumeGas(secp256k1VerifyCost, "ante verify: eth_secp256k1")
return nil
@ -120,6 +183,7 @@ func DefaultSigVerificationGasConsumer(
return err
}
return nil
default:
return sdkerrors.Wrapf(sdkerrors.ErrInvalidPubKey, "unrecognized public key type: %T", pubkey)
}

View File

@ -9,9 +9,9 @@ import (
ethcmn "github.com/ethereum/go-ethereum/common"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/app"
@ -62,8 +62,7 @@ func (suite *AnteTestSuite) TestValidEthTx() {
requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
// the legacytx.NewStdTx has been deprecated and It only works with Amino now
func (suite *AnteTestSuite) TestInValidTx() {
func (suite *AnteTestSuite) TestValidTx() {
suite.ctx = suite.ctx.WithBlockHeight(1)
addr1, priv1 := newTestAddrKey()
@ -84,13 +83,13 @@ func (suite *AnteTestSuite) TestInValidTx() {
msg1 := newTestMsg(addr1, addr2)
msgs := []sdk.Msg{msg1}
privKeys := []cryptotypes.PrivKey{priv1, priv2}
privKeys := []tmcrypto.PrivKey{priv1, priv2}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
tx := newTestSDKTx(suite.ctx, msgs, privKeys, accNums, accSeqs, fee)
requireInvalidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
requireValidTx(suite.T(), suite.anteHandler, suite.ctx, tx, false)
}
func (suite *AnteTestSuite) TestSDKInvalidSigs() {
@ -116,7 +115,7 @@ func (suite *AnteTestSuite) TestSDKInvalidSigs() {
// require validation failure with no signers
msgs := []sdk.Msg{msg1}
privKeys := []cryptotypes.PrivKey{}
privKeys := []tmcrypto.PrivKey{}
accNums := []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs := []uint64{acc1.GetSequence(), acc2.GetSequence()}
@ -126,7 +125,7 @@ func (suite *AnteTestSuite) TestSDKInvalidSigs() {
// require validation failure with invalid number of signers
msgs = []sdk.Msg{msg1}
privKeys = []cryptotypes.PrivKey{priv1}
privKeys = []tmcrypto.PrivKey{priv1}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber()}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence()}
@ -137,7 +136,7 @@ func (suite *AnteTestSuite) TestSDKInvalidSigs() {
msg2 := newTestMsg(addr1, addr3)
msgs = []sdk.Msg{msg1, msg2}
privKeys = []cryptotypes.PrivKey{priv1, priv2, priv3}
privKeys = []tmcrypto.PrivKey{priv1, priv2, priv3}
accNums = []uint64{acc1.GetAccountNumber(), acc2.GetAccountNumber(), 0}
accSeqs = []uint64{acc1.GetSequence(), acc2.GetSequence(), 0}
@ -158,7 +157,7 @@ func (suite *AnteTestSuite) TestSDKInvalidAcc() {
fee := newTestStdFee()
msg1 := newTestMsg(addr1)
msgs := []sdk.Msg{msg1}
privKeys := []cryptotypes.PrivKey{priv1}
privKeys := []tmcrypto.PrivKey{priv1}
// require validation failure with invalid account number
accNums := []uint64{1}

View File

@ -4,11 +4,14 @@ import (
"fmt"
"math/big"
log "github.com/xlab/suplog"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
ethermint "github.com/cosmos/ethermint/types"
sidechain "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
@ -37,6 +40,8 @@ func NewEthSetupContextDecorator() EthSetupContextDecorator {
// This is undone at the EthGasConsumeDecorator, where the context is set with the
// ethereum tx GasLimit.
func (escd EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
// all transactions must implement GasTx
gasTx, ok := tx.(authante.GasTx)
if !ok {
@ -58,6 +63,7 @@ func (escd EthSetupContextDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simu
)
err = sdkerrors.Wrap(sdkerrors.ErrOutOfGas, log)
default:
log.Errorln(r)
panic(r)
}
}
@ -89,41 +95,77 @@ func (emfd EthMempoolFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
return next(ctx, tx, simulate)
}
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
msgEthTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type, not implements sdk.FeeTx: %T", tx)
}
evmDenom := emfd.evmKeeper.GetParams(ctx).EvmDenom
txFee := msgEthTx.GetFee().AmountOf(evmDenom).Int64()
if txFee < 0 {
return ctx, sdkerrors.Wrap(
sdkerrors.ErrInsufficientFee,
"negative fee not allowed",
)
}
// fee = gas price * gas limit
fee := sdk.NewDecCoin(evmDenom, sdk.NewIntFromBigInt(msgEthTx.Fee()))
// txFee = GP * GL
fee := sdk.NewInt64DecCoin(evmDenom, txFee)
minGasPrices := ctx.MinGasPrices()
minFees := minGasPrices.AmountOf(evmDenom).MulInt64(int64(msgEthTx.Data.GasLimit))
// check that fee provided is greater than the minimum defined by the validator node
// NOTE: we only check if the evm denom tokens are present in min gas prices. It is up to the
// check that fee provided is greater than the minimum
// NOTE: we only check if injs are present in min gas prices. It is up to the
// sender if they want to send additional fees in other denominations.
var hasEnoughFees bool
if fee.Amount.GTE(minFees) {
if fee.Amount.GTE(minGasPrices.AmountOf(evmDenom)) {
hasEnoughFees = true
}
// reject transaction if minimum gas price is not zero and the transaction does not
// reject transaction if minimum gas price is positive and the transaction does not
// meet the minimum fee
if !ctx.MinGasPrices().IsZero() && !hasEnoughFees {
return ctx, sdkerrors.Wrap(
sdkerrors.ErrInsufficientFee,
fmt.Sprintf("insufficient fee, got: %q required: %q", fee, minFees),
fmt.Sprintf("insufficient fee, got: %q required: %q", fee, ctx.MinGasPrices()),
)
}
return next(ctx, tx, simulate)
}
// EthValidateBasicDecorator will call tx.ValidateBasic and return any non-nil error.
// If ValidateBasic passes, decorator calls next AnteHandler in chain. Note,
// EthValidateBasicDecorator decorator will not get executed on ReCheckTx since it
// is not dependent on application state.
type EthValidateBasicDecorator struct{}
func NewEthValidateBasicDecorator() EthValidateBasicDecorator {
return EthValidateBasicDecorator{}
}
func (vbd EthValidateBasicDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// no need to validate basic on recheck tx, call next antehandler
if ctx.IsReCheckTx() {
return next(ctx, tx, simulate)
}
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
if err := msgEthTx.ValidateBasic(); err != nil {
return ctx, err
}
return next(ctx, tx, simulate)
}
// EthSigVerificationDecorator validates an ethereum signature
type EthSigVerificationDecorator struct{}
type EthSigVerificationDecorator struct {
interfaceRegistry codectypes.InterfaceRegistry
}
// NewEthSigVerificationDecorator creates a new EthSigVerificationDecorator
func NewEthSigVerificationDecorator() EthSigVerificationDecorator {
@ -132,39 +174,63 @@ func NewEthSigVerificationDecorator() EthSigVerificationDecorator {
// AnteHandle validates the signature and returns sender address
func (esvd EthSigVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
if simulate {
// when simulating, no signatures required and the from address is explicitly set
return next(ctx, tx, simulate)
}
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
// parse the chainID from a string to a base-10 integer
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
chainIDEpoch, err := sidechain.ParseChainID(ctx.ChainID())
if err != nil {
fmt.Println("chain id parsing failed")
return ctx, err
}
// validate sender/signature and cache the address
_, err = msgEthTx.VerifySig(chainIDEpoch)
if err != nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "signature verification failed: %s", err.Error())
// validate sender/signature
_, eip155Err := msgEthTx.VerifySig(chainIDEpoch)
if eip155Err != nil {
_, homesteadErr := msgEthTx.VerifySigHomestead()
if homesteadErr != nil {
errMsg := fmt.Sprintf("signature verification failed for both EIP155 and Homestead signers: (%s, %s)",
eip155Err.Error(), homesteadErr.Error())
err := sdkerrors.Wrap(sdkerrors.ErrUnauthorized, errMsg)
return ctx, err
}
}
// NOTE: when signature verification succeeds, a non-empty signer address can be
// retrieved from the transaction on the next AnteDecorators.
return next(ctx, msgEthTx, simulate)
return next(ctx, tx, simulate)
}
// AccountVerificationDecorator validates an account balance checks
type AccountVerificationDecorator struct {
type noMessages struct{}
func getTxMsg(tx sdk.Tx) interface{} {
msgs := tx.GetMsgs()
if len(msgs) == 0 {
return &noMessages{}
}
return msgs[0]
}
// EthAccountVerificationDecorator validates an account balance checks
type EthAccountVerificationDecorator struct {
ak AccountKeeper
bankKeeper BankKeeper
evmKeeper EVMKeeper
}
// NewAccountVerificationDecorator creates a new AccountVerificationDecorator
func NewAccountVerificationDecorator(ak AccountKeeper, bankKeeper BankKeeper, ek EVMKeeper) AccountVerificationDecorator {
return AccountVerificationDecorator{
// NewEthAccountVerificationDecorator creates a new EthAccountVerificationDecorator
func NewEthAccountVerificationDecorator(ak AccountKeeper, bankKeeper BankKeeper, ek EVMKeeper) EthAccountVerificationDecorator {
return EthAccountVerificationDecorator{
ak: ak,
bankKeeper: bankKeeper,
evmKeeper: ek,
@ -172,20 +238,20 @@ func NewAccountVerificationDecorator(ak AccountKeeper, bankKeeper BankKeeper, ek
}
// AnteHandle validates the signature and returns sender address
func (avd AccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
func (avd EthAccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
if !ctx.IsCheckTx() {
return next(ctx, tx, simulate)
}
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
// sender address should be in the tx cache from the previous AnteHandle call
address := msgEthTx.GetFrom()
if address.Empty() {
panic("sender address cannot be empty")
log.Panicln("sender address cannot be empty")
}
acc := avd.ak.GetAccount(ctx, address)
@ -216,31 +282,31 @@ func (avd AccountVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, s
return next(ctx, tx, simulate)
}
// NonceVerificationDecorator checks that the account nonce from the transaction matches
// EthNonceVerificationDecorator checks that the account nonce from the transaction matches
// the sender account sequence.
type NonceVerificationDecorator struct {
type EthNonceVerificationDecorator struct {
ak AccountKeeper
}
// NewNonceVerificationDecorator creates a new NonceVerificationDecorator
func NewNonceVerificationDecorator(ak AccountKeeper) NonceVerificationDecorator {
return NonceVerificationDecorator{
// NewEthNonceVerificationDecorator creates a new EthNonceVerificationDecorator
func NewEthNonceVerificationDecorator(ak AccountKeeper) EthNonceVerificationDecorator {
return EthNonceVerificationDecorator{
ak: ak,
}
}
// AnteHandle validates that the transaction nonce is valid (equivalent to the sender accounts
// current nonce).
func (nvd NonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
func (nvd EthNonceVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
// sender address should be in the tx cache from the previous AnteHandle call
address := msgEthTx.GetFrom()
if address.Empty() {
panic("sender address cannot be empty")
log.Panicln("sender address cannot be empty")
}
acc := nvd.ak.GetAccount(ctx, address)
@ -290,15 +356,15 @@ func NewEthGasConsumeDecorator(ak AccountKeeper, bankKeeper BankKeeper, ek EVMKe
// constant value of 21000 plus any cost inccured by additional bytes of data
// supplied with the transaction.
func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
// sender address should be in the tx cache from the previous AnteHandle call
address := msgEthTx.GetFrom()
if address.Empty() {
panic("sender address cannot be empty")
log.Panicln("sender address cannot be empty")
}
// fetch sender account from signature
@ -328,7 +394,7 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
// Charge sender for gas up to limit
if gasLimit != 0 {
// Cost calculates the fees paid to validators based on gas limit and price
cost := new(big.Int).Mul(msgEthTx.Data.Price.BigInt(), new(big.Int).SetUint64(gasLimit))
cost := new(big.Int).Mul(new(big.Int).SetBytes(msgEthTx.Data.Price), new(big.Int).SetUint64(gasLimit))
evmDenom := egcd.evmKeeper.GetParams(ctx).EvmDenom
@ -347,40 +413,40 @@ func (egcd EthGasConsumeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simula
return next(newCtx, tx, simulate)
}
// IncrementSenderSequenceDecorator increments the sequence of the signers. The
// EthIncrementSenderSequenceDecorator increments the sequence of the signers. The
// main difference with the SDK's IncrementSequenceDecorator is that the MsgEthereumTx
// doesn't implement the SigVerifiableTx interface.
//
// CONTRACT: must be called after msg.VerifySig in order to cache the sender address.
type IncrementSenderSequenceDecorator struct {
type EthIncrementSenderSequenceDecorator struct {
ak AccountKeeper
}
// NewIncrementSenderSequenceDecorator creates a new IncrementSenderSequenceDecorator.
func NewIncrementSenderSequenceDecorator(ak AccountKeeper) IncrementSenderSequenceDecorator {
return IncrementSenderSequenceDecorator{
// NewEthIncrementSenderSequenceDecorator creates a new EthIncrementSenderSequenceDecorator.
func NewEthIncrementSenderSequenceDecorator(ak AccountKeeper) EthIncrementSenderSequenceDecorator {
return EthIncrementSenderSequenceDecorator{
ak: ak,
}
}
// AnteHandle handles incrementing the sequence of the sender.
func (issd IncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
func (issd EthIncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// get and set account must be called with an infinite gas meter in order to prevent
// additional gas from being deducted.
gasMeter := ctx.GasMeter()
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
msgEthTx, ok := tx.(*evmtypes.MsgEthereumTx)
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
ctx = ctx.WithGasMeter(gasMeter)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", tx)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
// increment sequence of all signers
for _, addr := range msgEthTx.GetSigners() {
acc := issd.ak.GetAccount(ctx, addr)
if err := acc.SetSequence(acc.GetSequence() + 1); err != nil {
panic(err)
log.WithError(err).Panicln("failed to set acc sequence")
}
issd.ak.SetAccount(ctx, acc)
}
@ -389,3 +455,45 @@ func (issd IncrementSenderSequenceDecorator) AnteHandle(ctx sdk.Context, tx sdk.
ctx = ctx.WithGasMeter(gasMeter)
return next(ctx, tx, simulate)
}
// EthAccountSetupDecorator sets an account to state if it's not stored already. This only applies for MsgEthermint.
type EthAccountSetupDecorator struct {
ak AccountKeeper
}
// NewEthAccountSetupDecorator creates a new EthAccountSetupDecorator instance
func NewEthAccountSetupDecorator(ak AccountKeeper) EthAccountSetupDecorator {
return EthAccountSetupDecorator{
ak: ak,
}
}
// AnteHandle sets an account for MsgEthereumTx (evm) if the sender is registered.
func (asd EthAccountSetupDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (sdk.Context, error) {
// get and set account must be called with an infinite gas meter in order to prevent
// additional gas from being deducted.
gasMeter := ctx.GasMeter()
ctx = ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
msgEthTx, ok := getTxMsg(tx).(*evmtypes.MsgEthereumTx)
if !ok {
ctx = ctx.WithGasMeter(gasMeter)
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid transaction type: %T", getTxMsg(tx))
}
setupAccount(asd.ak, ctx, msgEthTx.GetFrom())
// set the original gas meter
ctx = ctx.WithGasMeter(gasMeter)
return next(ctx, tx, simulate)
}
func setupAccount(ak AccountKeeper, ctx sdk.Context, addr sdk.AccAddress) {
acc := ak.GetAccount(ctx, addr)
if acc != nil {
return
}
acc = ak.NewAccountWithAddress(ctx, addr)
ak.SetAccount(ctx, acc)
}

View File

@ -6,22 +6,22 @@ import (
"github.com/stretchr/testify/suite"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/simapp/params"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/legacy/legacytx"
"github.com/cosmos/cosmos-sdk/x/auth"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/ethermint/app"
ante "github.com/cosmos/ethermint/app/ante"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"
sidechain "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
tmcrypto "github.com/tendermint/tendermint/crypto"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
@ -39,11 +39,11 @@ func (suite *AnteTestSuite) SetupTest() {
suite.app = app.Setup(checkTx)
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 2, ChainID: "ethermint-3", Time: time.Now().UTC()})
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 2, ChainID: "injective-3", Time: time.Now().UTC()})
suite.app.AccountKeeper.SetParams(suite.ctx, authtypes.DefaultParams())
suite.app.EvmKeeper.SetParams(suite.ctx, evmtypes.DefaultParams())
suite.encodingConfig = app.MakeEncodingConfig()
suite.encodingConfig = simapp.MakeEncodingConfig()
// We're using TestMsg amino encoding in some tests, so register it here.
suite.encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
@ -54,20 +54,20 @@ func TestAnteTestSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}
func newTestMsg(addrs ...sdk.AccAddress) *testdata.TestMsg {
return testdata.NewTestMsg(addrs...)
func newTestMsg(addrs ...sdk.AccAddress) *sdk.TestMsg {
return sdk.NewTestMsg(addrs...)
}
func newTestCoins() sdk.Coins {
return sdk.NewCoins(ethermint.NewPhotonCoinInt64(500000000))
return sdk.NewCoins(sidechain.NewPhotonCoinInt64(500000000))
}
func newTestStdFee() legacytx.StdFee {
return legacytx.NewStdFee(220000, sdk.NewCoins(ethermint.NewPhotonCoinInt64(150)))
func newTestStdFee() auth.StdFee {
return auth.NewStdFee(220000, sdk.NewCoins(sidechain.NewPhotonCoinInt64(150)))
}
// GenerateAddress generates an Ethereum address.
func newTestAddrKey() (sdk.AccAddress, cryptotypes.PrivKey) {
func newTestAddrKey() (sdk.AccAddress, tmcrypto.PrivKey) {
privkey, _ := ethsecp256k1.GenerateKey()
addr := ethcrypto.PubkeyToAddress(privkey.ToECDSA().PublicKey)
@ -75,30 +75,30 @@ func newTestAddrKey() (sdk.AccAddress, cryptotypes.PrivKey) {
}
func newTestSDKTx(
ctx sdk.Context, msgs []sdk.Msg, privs []cryptotypes.PrivKey,
accNums []uint64, seqs []uint64, fee legacytx.StdFee,
ctx sdk.Context, msgs []sdk.Msg, privs []tmcrypto.PrivKey,
accNums []uint64, seqs []uint64, fee auth.StdFee,
) sdk.Tx {
sigs := make([]legacytx.StdSignature, len(privs))
sigs := make([]auth.StdSignature, len(privs))
for i, priv := range privs {
signBytes := legacytx.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], 0, fee, msgs, "")
signBytes := auth.StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "")
sig, err := priv.Sign(signBytes)
if err != nil {
panic(err)
}
sigs[i] = legacytx.StdSignature{
sigs[i] = auth.StdSignature{
PubKey: priv.PubKey(),
Signature: sig,
}
}
return legacytx.NewStdTx(msgs, fee, sigs, "")
return auth.NewStdTx(msgs, fee, sigs, "")
}
func newTestEthTx(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, priv cryptotypes.PrivKey) (sdk.Tx, error) {
chainIDEpoch, err := ethermint.ParseChainID(ctx.ChainID())
func newTestEthTx(ctx sdk.Context, msg *evmtypes.MsgEthereumTx, priv tmcrypto.PrivKey) (sdk.Tx, error) {
chainIDEpoch, err := sidechain.ParseChainID(ctx.ChainID())
if err != nil {
return nil, err
}

View File

@ -7,9 +7,9 @@ import (
"github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/cosmos/ethermint/codec"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
// MakeEncodingConfig creates an EncodingConfig for testing
@ -46,7 +46,7 @@ func NewTxConfig(marshaler amino.ProtoCodecMarshaler) client.TxConfig {
}
}
// TxEncoder overwites sdk.TxEncoder to support MsgEthereumTx
// TxEncoder overwrites sdk.TxEncoder to support MsgEthereumTx
func (g txConfig) TxEncoder() sdk.TxEncoder {
return func(tx sdk.Tx) ([]byte, error) {
ethtx, ok := tx.(*evmtypes.MsgEthereumTx)
@ -57,7 +57,7 @@ func (g txConfig) TxEncoder() sdk.TxEncoder {
}
}
// TxDecoder overwites sdk.TxDecoder to support MsgEthereumTx
// TxDecoder overwrites sdk.TxDecoder to support MsgEthereumTx
func (g txConfig) TxDecoder() sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, error) {
var ethtx evmtypes.MsgEthereumTx

View File

@ -2,27 +2,29 @@ package app
import (
"io"
"net/http"
"os"
"path/filepath"
"github.com/spf13/cast"
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
"github.com/cosmos/cosmos-sdk/client"
"github.com/gorilla/mux"
"github.com/rakyll/statik/fs"
abci "github.com/tendermint/tendermint/abci/types"
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/libs/log"
tmos "github.com/tendermint/tendermint/libs/os"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/grpc/tmservice"
"github.com/cosmos/cosmos-sdk/client/rpc"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/codec/types"
sdkapi "github.com/cosmos/cosmos-sdk/server/api"
sdkconfig "github.com/cosmos/cosmos-sdk/server/config"
servertypes "github.com/cosmos/cosmos-sdk/server/types"
"github.com/cosmos/cosmos-sdk/server/api"
"github.com/cosmos/cosmos-sdk/server/config"
"github.com/cosmos/cosmos-sdk/simapp"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -31,7 +33,6 @@ import (
"github.com/cosmos/cosmos-sdk/x/auth"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authsims "github.com/cosmos/cosmos-sdk/x/auth/simulation"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/bank"
@ -55,7 +56,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/gov"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
transfer "github.com/cosmos/cosmos-sdk/x/ibc/applications/transfer"
"github.com/cosmos/cosmos-sdk/x/ibc/applications/transfer"
ibctransferkeeper "github.com/cosmos/cosmos-sdk/x/ibc/applications/transfer/keeper"
ibctransfertypes "github.com/cosmos/cosmos-sdk/x/ibc/applications/transfer/types"
ibc "github.com/cosmos/cosmos-sdk/x/ibc/core"
@ -83,12 +84,11 @@ import (
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"
// unnamed import of statik for swagger UI support
_ "github.com/cosmos/cosmos-sdk/client/docs/statik"
_ "github.com/cosmos/ethermint/client/docs/statik"
"github.com/cosmos/ethermint/app/ante"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/ethermint/x/evm"
evmrest "github.com/cosmos/ethermint/x/evm/client/rest"
evmkeeper "github.com/cosmos/ethermint/x/evm/keeper"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
@ -105,10 +105,14 @@ func init() {
panic(err)
}
DefaultNodeHome = filepath.Join(userHomeDir, ".ethermint")
DefaultNodeHome = filepath.Join(userHomeDir, ".ethermintd")
}
const appName = "Ethermint"
func init() {
}
const appName = "ethermintd"
var (
// DefaultNodeHome default home directories for the application daemon
@ -156,10 +160,9 @@ var (
}
)
var (
// _ simapp.App = (*EthermintApp)(nil)
_ servertypes.Application = (*EthermintApp)(nil)
)
var _ simapp.App = (*EthermintApp)(nil)
//var _ server.Application (*EthermintApp)(nil)
// EthermintApp implements an extended ABCI application. It is an application
// that may process transactions through Ethereum's EVM running atop of
@ -197,18 +200,17 @@ type EthermintApp struct {
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
ScopedTransferKeeper capabilitykeeper.ScopedKeeper
// ethermint keepers
// Ethermint keepers
EvmKeeper *evmkeeper.Keeper
// the module manager
mm *module.Manager
// simulation manager
// disable for now, enable it once SDK side fix the simulator issue for custom keys
// sm *module.SimulationManager
sm *module.SimulationManager
}
// NewEthermintApp returns a reference to a new initialized Ethermint application.
// NewEthermintApp returns a reference to a new initialized Injective application.
func NewEthermintApp(
logger log.Logger,
db dbm.DB,
@ -226,7 +228,7 @@ func NewEthermintApp(
cdc := encodingConfig.Amino
interfaceRegistry := encodingConfig.InterfaceRegistry
// NOTE we use custom Ethermint transaction decoder that supports the sdk.Tx interface instead of sdk.StdTx
// NOTE we use custom Injective transaction decoder that supports the sdk.Tx interface instead of sdk.StdTx
bApp := baseapp.NewBaseApp(
appName,
logger,
@ -304,6 +306,11 @@ func NewEthermintApp(
stakingtypes.NewMultiStakingHooks(app.DistrKeeper.Hooks(), app.SlashingKeeper.Hooks()),
)
// Create Ethermint keepers
app.EvmKeeper = evmkeeper.NewKeeper(
appCodec, keys[evmtypes.StoreKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
)
// Create IBC Keeper
app.IBCKeeper = ibckeeper.NewKeeper(
appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, scopedIBCKeeper,
@ -315,7 +322,7 @@ func NewEthermintApp(
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)).
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)).
AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper))
AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)).
app.GovKeeper = govkeeper.NewKeeper(
appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
&stakingKeeper, govRouter,
@ -341,16 +348,13 @@ func NewEthermintApp(
// If evidence needs to be handled for the app, set routes in router here and seal
app.EvidenceKeeper = *evidenceKeeper
// Create Ethermint keepers
app.EvmKeeper = evmkeeper.NewKeeper(
appCodec, keys[evmtypes.StoreKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
)
/**** Module Options ****/
// TODO: do properly
// NOTE: we may consider parsing `appOpts` inside module constructors. For the moment
// we prefer to be more strict in what arguments the modules expect.
var skipGenesisInvariants = cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
//var skipGenesisInvariants = cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
skipGenesisInvariants := true
// NOTE: Any module instantiated in the module manager that is later modified
// must be passed by reference here.
@ -375,7 +379,7 @@ func NewEthermintApp(
ibc.NewAppModule(app.IBCKeeper),
params.NewAppModule(app.ParamsKeeper),
transferModule,
// Ethermint app modules
// Injective app modules
evm.NewAppModule(app.EvmKeeper, app.AccountKeeper, app.BankKeeper),
)
@ -391,8 +395,8 @@ func NewEthermintApp(
evidencetypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName,
)
app.mm.SetOrderEndBlockers(
evmtypes.ModuleName,
crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName,
evmtypes.ModuleName,
)
// NOTE: The genutils module must occur after staking so that pools are
@ -405,8 +409,9 @@ func NewEthermintApp(
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName,
slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName,
ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, ibctransfertypes.ModuleName,
// Ethermint modules
// Injective modules
evmtypes.ModuleName,
// NOTE: crisis module must go at the end to check for invariants on each module
crisistypes.ModuleName,
)
@ -415,29 +420,29 @@ func NewEthermintApp(
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
app.mm.RegisterServices(module.NewConfigurator(app.MsgServiceRouter(), app.GRPCQueryRouter()))
// add test gRPC service for testing gRPC queries in isolation
//add test gRPC service for testing gRPC queries in isolation
// testdata.RegisterTestServiceServer(app.GRPCQueryRouter(), testdata.TestServiceImpl{})
// create the simulation manager and define the order of the modules for deterministic simulations
//
// NOTE: this is not required apps that don't use the simulator for fuzz testing
// transactions
// app.sm = module.NewSimulationManager(
// auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
// bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
// capability.NewAppModule(appCodec, *app.CapabilityKeeper),
// gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
// mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
// staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
// distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
// slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
// params.NewAppModule(app.ParamsKeeper),
// evidence.NewAppModule(app.EvidenceKeeper),
// ibc.NewAppModule(app.IBCKeeper),
// transferModule,
//)
//create the simulation manager and define the order of the modules for deterministic simulations
// app.sm.RegisterStoreDecoders()
//NOTE: this is not required apps that don't use the simulator for fuzz testing
// transactions
app.sm = module.NewSimulationManager(
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
params.NewAppModule(app.ParamsKeeper),
evidence.NewAppModule(app.EvidenceKeeper),
ibc.NewAppModule(app.IBCKeeper),
transferModule,
)
app.sm.RegisterStoreDecoders()
// initialize stores
app.MountKVStores(keys)
@ -448,13 +453,14 @@ func NewEthermintApp(
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
// use Ethermint's custom AnteHandler
// use Injective's custom AnteHandler
app.SetAnteHandler(
ante.NewAnteHandler(
app.AccountKeeper, app.BankKeeper, app.EvmKeeper,
encodingConfig.TxConfig.SignModeHandler(),
),
)
app.SetEndBlocker(app.EndBlocker)
if loadLatest {
@ -495,9 +501,7 @@ func (app *EthermintApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) a
// InitChainer updates at chain initialization
func (app *EthermintApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
var genesisState simapp.GenesisState
if err := tmjson.Unmarshal(req.AppStateBytes, &genesisState); err != nil {
panic(err)
}
app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState)
return app.mm.InitGenesis(ctx, app.appCodec, genesisState)
}
@ -578,36 +582,52 @@ func (app *EthermintApp) GetSubspace(moduleName string) paramstypes.Subspace {
}
// SimulationManager implements the SimulationApp interface
// func (app *EthermintApp) SimulationManager() *module.SimulationManager {
// return app.sm
//}
func (app *EthermintApp) SimulationManager() *module.SimulationManager {
return app.sm
}
// RegisterAPIRoutes registers all application module routes with the provided
// API server.
func (app *EthermintApp) RegisterAPIRoutes(apiSvr *sdkapi.Server, apiConfig sdkconfig.APIConfig) {
func (app *EthermintApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
clientCtx := apiSvr.ClientCtx
rpc.RegisterRoutes(clientCtx, apiSvr.Router)
evmrest.RegisterTxRoutes(clientCtx, apiSvr.Router)
// Register new tx routes from grpc-gateway.
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
// Register new tendermint queries routes from grpc-gateway.
tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
// Register legacy and grpc-gateway routes for all modules.
ModuleBasics.RegisterRESTRoutes(clientCtx, apiSvr.Router)
ModuleBasics.RegisterGRPCGatewayRoutes(apiSvr.ClientCtx, apiSvr.GRPCGatewayRouter)
ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
// register swagger API from root so that other applications can override easily
if apiConfig.Swagger {
simapp.RegisterSwaggerAPI(clientCtx, apiSvr.Router)
RegisterSwaggerAPI(clientCtx, apiSvr.Router)
}
}
// RegisterTxService implements the Application.RegisterTxService method.
func (app *EthermintApp) RegisterTxService(clientCtx client.Context) {
authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry)
}
// RegisterTendermintService implements the Application.RegisterTendermintService method.
func (app *EthermintApp) RegisterTendermintService(clientCtx client.Context) {
tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry)
}
// RegisterSwaggerAPI registers swagger route with API Server
func RegisterSwaggerAPI(ctx client.Context, rtr *mux.Router) {
statikFS, err := fs.New()
if err != nil {
panic(err)
}
staticServer := http.FileServer(statikFS)
rtr.PathPrefix("/swagger/").Handler(http.StripPrefix("/swagger/", staticServer))
}
// GetMaccPerms returns a copy of the module account permissions
func GetMaccPerms() map[string][]string {
dupMaccPerms := make(map[string][]string)
@ -636,6 +656,5 @@ func initParamsKeeper(
paramsKeeper.Subspace(ibchost.ModuleName)
// ethermint subspaces
paramsKeeper.Subspace(evmtypes.ModuleName)
return paramsKeeper
}

View File

@ -34,6 +34,7 @@ func (app *EthermintApp) ExportAppStateAndValidators(
height := app.LastBlockHeight() + 1
if forZeroHeight {
height = 0
if err := app.prepForZeroHeightGenesis(ctx, jailAllowedAddrs); err != nil {
return servertypes.ExportedApp{}, err
}
@ -60,7 +61,7 @@ func (app *EthermintApp) ExportAppStateAndValidators(
// prepare for fresh start at zero height
// NOTE zero height genesis is a temporary feature which will be deprecated
// in favour of export at a block height
// in favor of export at a block height
func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) error {
applyAllowedAddrs := false

69
client/docs/config.json Normal file
View File

@ -0,0 +1,69 @@
{
"swagger": "2.0",
"info": {
"title": "Ethermint Chain - Legacy REST and gRPC Gateway docs",
"description": "A REST interface for state queries, legacy transactions",
"version": "1.0.0"
},
"apis": [
{
"url": "./tmp-swagger-gen/ethermint/evm/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Params": "EvmParams"
}
}
},
{
"url": "./tmp-swagger-gen/cosmos/auth/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Account": "AuthAccount",
"Params": "AuthParams"
}
}
},
{
"url": "./tmp-swagger-gen/cosmos/bank/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Balance": "BankBalance",
"Params": "BankParams"
}
}
},
{
"url": "./tmp-swagger-gen/cosmos/distribution/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Params": "DistributionParams",
"DelegatorValidators": "DistDelegatorValidators"
}
}
},
{
"url": "./tmp-swagger-gen/cosmos/mint/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Params": "MintParams"
}
}
},
{
"url": "./tmp-swagger-gen/cosmos/gov/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Params": "GovParams"
}
}
},
{
"url": "./tmp-swagger-gen/cosmos/staking/v1beta1/query.swagger.json",
"operationIds": {
"rename": {
"Params": "StakingParams"
}
}
}
]
}

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -0,0 +1,60 @@
<!-- HTML for static distribution bundle build -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Swagger UI</title>
<link rel="stylesheet" type="text/css" href="swagger-ui.css" >
<link rel="icon" type="image/png" href="favicon-32x32.png" sizes="32x32" />
<link rel="icon" type="image/png" href="favicon-16x16.png" sizes="16x16" />
<style>
html
{
box-sizing: border-box;
overflow: -moz-scrollbars-vertical;
overflow-y: scroll;
}
*,
*:before,
*:after
{
box-sizing: inherit;
}
body
{
margin:0;
background: #fafafa;
}
</style>
</head>
<body>
<div id="swagger-ui"></div>
<script src="swagger-ui-bundle.js"> </script>
<script src="swagger-ui-standalone-preset.js"> </script>
<script>
window.onload = function() {
// Build a system
const ui = SwaggerUIBundle({
url: "./swagger.yaml",
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
})
window.ui = ui
}
</script>
</body>
</html>

View File

@ -0,0 +1,67 @@
<!doctype html>
<html lang="en-US">
<body onload="run()">
</body>
</html>
<script>
'use strict';
function run () {
var oauth2 = window.opener.swaggerUIRedirectOauth2;
var sentState = oauth2.state;
var redirectUrl = oauth2.redirectUrl;
var isValid, qp, arr;
if (/code|token|error/.test(window.location.hash)) {
qp = window.location.hash.substring(1);
} else {
qp = location.search.substring(1);
}
arr = qp.split("&")
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
qp = qp ? JSON.parse('{' + arr.join() + '}',
function (key, value) {
return key === "" ? value : decodeURIComponent(value)
}
) : {}
isValid = qp.state === sentState
if ((
oauth2.auth.schema.get("flow") === "accessCode"||
oauth2.auth.schema.get("flow") === "authorizationCode"
) && !oauth2.auth.code) {
if (!isValid) {
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "warning",
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
});
}
if (qp.code) {
delete oauth2.state;
oauth2.auth.code = qp.code;
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
} else {
let oauthErrorMsg
if (qp.error) {
oauthErrorMsg = "["+qp.error+"]: " +
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
(qp.error_uri ? "More info: "+qp.error_uri : "");
}
oauth2.errCb({
authId: oauth2.auth.name,
source: "auth",
level: "error",
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
});
}
} else {
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
}
window.close();
}
</script>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -46,7 +46,7 @@ import (
chaintypes "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/cosmos/ethermint/cmd/injectived/config"
"github.com/cosmos/ethermint/cmd/ethermintd/config"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
"github.com/ethereum/go-ethereum/common"
)
@ -74,7 +74,7 @@ necessary files (private validator, genesis, config, etc.).
Note, strict routability for addresses is turned off in the config file.`,
Example: "injectived testnet --v 4 --keyring-backend test --output-dir ./output --ip-addresses 192.168.10.2",
Example: "ethermintd testnet --v 4 --keyring-backend test --output-dir ./output --ip-addresses 192.168.10.2",
RunE: func(cmd *cobra.Command, _ []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
@ -106,7 +106,7 @@ Note, strict routability for addresses is turned off in the config file.`,
cmd.Flags().Int(flagNumValidators, 4, "Number of validators to initialize the testnet with")
cmd.Flags().StringP(flagOutputDir, "o", "./mytestnet", "Directory to store initialization data for the testnet")
cmd.Flags().String(flagNodeDirPrefix, "node", "Prefix the directory name for each node with (node results in node0, node1, ...)")
cmd.Flags().String(flagNodeDaemonHome, "injectived", "Home directory of the node's daemon configuration")
cmd.Flags().String(flagNodeDaemonHome, "ethermintd", "Home directory of the node's daemon configuration")
cmd.Flags().StringSlice(flagIPAddrs, []string{"192.168.0.1"}, "List of IP addresses to use (i.e. `192.168.0.1,172.168.0.1` results in persistent peers list ID0@192.168.0.1:46656, ID1@172.168.0.1)")
cmd.Flags().String(flags.FlagChainID, "", "genesis file chain-id, if left blank will be randomly created")
cmd.Flags().String(server.FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; All fees in a tx must meet this minimum (e.g. 0.01inj,0.001stake)")

View File

@ -0,0 +1,242 @@
package config
import (
"fmt"
"github.com/cosmos/cosmos-sdk/telemetry"
"strings"
"github.com/spf13/viper"
serverconfig "github.com/cosmos/cosmos-sdk/server/config"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
defaultMinGasPrices = ""
// DefaultGRPCAddress is the default address the gRPC server binds to.
DefaultGRPCAddress = "0.0.0.0:9900"
// DefaultEVMAddress is the default address the EVM JSON-RPC server binds to.
DefaultEVMAddress = "0.0.0.0:1317"
// DefaultEVMWSAddress is the default address the EVM WebSocket server binds to.
DefaultEVMWSAddress = "0.0.0.0:1318"
)
// BaseConfig defines the server's basic configuration
type BaseConfig struct {
// The minimum gas prices a validator is willing to accept for processing a
// transaction. A transaction's fees must meet the minimum of any denomination
// specified in this config (e.g. 0.25token1;0.0001token2).
MinGasPrices string `mapstructure:"minimum-gas-prices"`
Pruning string `mapstructure:"pruning"`
PruningKeepRecent string `mapstructure:"pruning-keep-recent"`
PruningKeepEvery string `mapstructure:"pruning-keep-every"`
PruningInterval string `mapstructure:"pruning-interval"`
// HaltHeight contains a non-zero block height at which a node will gracefully
// halt and shutdown that can be used to assist upgrades and testing.
//
// Note: Commitment of state will be attempted on the corresponding block.
HaltHeight uint64 `mapstructure:"halt-height"`
// HaltTime contains a non-zero minimum block time (in Unix seconds) at which
// a node will gracefully halt and shutdown that can be used to assist
// upgrades and testing.
//
// Note: Commitment of state will be attempted on the corresponding block.
HaltTime uint64 `mapstructure:"halt-time"`
// MinRetainBlocks defines the minimum block height offset from the current
// block being committed, such that blocks past this offset may be pruned
// from Tendermint. It is used as part of the process of determining the
// ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
// that no blocks should be pruned.
//
// This configuration value is only responsible for pruning Tendermint blocks.
// It has no bearing on application state pruning which is determined by the
// "pruning-*" configurations.
//
// Note: Tendermint block pruning is dependant on this parameter in conunction
// with the unbonding (safety threshold) period, state pruning and state sync
// snapshot parameters to determine the correct minimum value of
// ResponseCommit.RetainHeight.
MinRetainBlocks uint64 `mapstructure:"min-retain-blocks"`
// InterBlockCache enables inter-block caching.
InterBlockCache bool `mapstructure:"inter-block-cache"`
// IndexEvents defines the set of events in the form {eventType}.{attributeKey},
// which informs Tendermint what to index. If empty, all events will be indexed.
IndexEvents []string `mapstructure:"index-events"`
}
// APIConfig defines the API listener configuration.
type APIConfig = serverconfig.APIConfig
// GRPCConfig defines configuration for the gRPC server.
type GRPCConfig struct {
// Enable defines if the gRPC server should be enabled.
Enable bool `mapstructure:"enable"`
// Address defines the API server to listen on
Address string `mapstructure:"address"`
}
// EVMRPCConfig defines configuration for the EVM RPC server.
type EVMRPCConfig struct {
// Enable defines if the EVM RPC server should be enabled.
Enable bool `mapstructure:"enable"`
// Address defines the HTTP server to listen on
RpcAddress string `mapstructure:"address"`
// Address defines the WebSocket server to listen on
WsAddress string `mapstructure:"ws-address"`
}
// StateSyncConfig defines the state sync snapshot configuration.
type StateSyncConfig struct {
// SnapshotInterval sets the interval at which state sync snapshots are taken.
// 0 disables snapshots. Must be a multiple of PruningKeepEvery.
SnapshotInterval uint64 `mapstructure:"snapshot-interval"`
// SnapshotKeepRecent sets the number of recent state sync snapshots to keep.
// 0 keeps all snapshots.
SnapshotKeepRecent uint32 `mapstructure:"snapshot-keep-recent"`
}
// Config defines the server's top level configuration
type Config struct {
BaseConfig `mapstructure:",squash"`
// Telemetry defines the application telemetry configuration
Telemetry telemetry.Config `mapstructure:"telemetry"`
API APIConfig `mapstructure:"api"`
GRPC GRPCConfig `mapstructure:"grpc"`
EVMRPC EVMRPCConfig `mapstructure:"evm-rpc"`
StateSync StateSyncConfig `mapstructure:"state-sync"`
}
// SetMinGasPrices sets the validator's minimum gas prices.
func (c *Config) SetMinGasPrices(gasPrices sdk.DecCoins) {
c.MinGasPrices = gasPrices.String()
}
// GetMinGasPrices returns the validator's minimum gas prices based on the set
// configuration.
func (c *Config) GetMinGasPrices() sdk.DecCoins {
if c.MinGasPrices == "" {
return sdk.DecCoins{}
}
gasPricesStr := strings.Split(c.MinGasPrices, ";")
gasPrices := make(sdk.DecCoins, len(gasPricesStr))
for i, s := range gasPricesStr {
gasPrice, err := sdk.ParseDecCoin(s)
if err != nil {
panic(fmt.Errorf("failed to parse minimum gas price coin (%s): %s", s, err))
}
gasPrices[i] = gasPrice
}
return gasPrices
}
// DefaultConfig returns server's default configuration.
func DefaultConfig() *Config {
return &Config{
BaseConfig: BaseConfig{
MinGasPrices: defaultMinGasPrices,
InterBlockCache: true,
Pruning: storetypes.PruningOptionNothing,
PruningKeepRecent: "0",
PruningKeepEvery: "0",
PruningInterval: "0",
MinRetainBlocks: 0,
IndexEvents: make([]string, 0),
},
Telemetry: telemetry.Config{
Enabled: false,
GlobalLabels: [][]string{},
},
API: APIConfig{
Enable: true,
Swagger: true,
Address: "tcp://0.0.0.0:10337",
MaxOpenConnections: 1000,
RPCReadTimeout: 10,
RPCMaxBodyBytes: 1000000,
},
GRPC: GRPCConfig{
Enable: true,
Address: DefaultGRPCAddress,
},
EVMRPC: EVMRPCConfig{
Enable: true,
RpcAddress: DefaultEVMAddress,
WsAddress: DefaultEVMWSAddress,
},
StateSync: StateSyncConfig{
SnapshotInterval: 0,
SnapshotKeepRecent: 2,
},
}
}
// GetConfig returns a fully parsed Config object.
func GetConfig(v *viper.Viper) Config {
globalLabelsRaw := v.Get("telemetry.global-labels").([]interface{})
globalLabels := make([][]string, 0, len(globalLabelsRaw))
return Config{
BaseConfig: BaseConfig{
MinGasPrices: v.GetString("minimum-gas-prices"),
InterBlockCache: v.GetBool("inter-block-cache"),
Pruning: v.GetString("pruning"),
PruningKeepRecent: v.GetString("pruning-keep-recent"),
PruningKeepEvery: v.GetString("pruning-keep-every"),
PruningInterval: v.GetString("pruning-interval"),
HaltHeight: v.GetUint64("halt-height"),
HaltTime: v.GetUint64("halt-time"),
IndexEvents: v.GetStringSlice("index-events"),
MinRetainBlocks: v.GetUint64("min-retain-blocks"),
},
Telemetry: telemetry.Config{
ServiceName: v.GetString("telemetry.service-name"),
Enabled: v.GetBool("telemetry.enabled"),
EnableHostname: v.GetBool("telemetry.enable-hostname"),
EnableHostnameLabel: v.GetBool("telemetry.enable-hostname-label"),
EnableServiceLabel: v.GetBool("telemetry.enable-service-label"),
PrometheusRetentionTime: v.GetInt64("telemetry.prometheus-retention-time"),
GlobalLabels: globalLabels,
},
API: APIConfig{
Enable: v.GetBool("api.enable"),
Swagger: v.GetBool("api.swagger"),
Address: v.GetString("api.address"),
MaxOpenConnections: v.GetUint("api.max-open-connections"),
RPCReadTimeout: v.GetUint("api.rpc-read-timeout"),
RPCWriteTimeout: v.GetUint("api.rpc-write-timeout"),
RPCMaxBodyBytes: v.GetUint("api.rpc-max-body-bytes"),
EnableUnsafeCORS: v.GetBool("api.enabled-unsafe-cors"),
},
GRPC: GRPCConfig{
Enable: v.GetBool("grpc.enable"),
Address: v.GetString("grpc.address"),
},
EVMRPC: EVMRPCConfig{
Enable: v.GetBool("evm-rpc.enable"),
RpcAddress: v.GetString("evm-rpc.address"),
WsAddress: v.GetString("evm-rpc.ws-address"),
},
StateSync: StateSyncConfig{
SnapshotInterval: v.GetUint64("state-sync.snapshot-interval"),
SnapshotKeepRecent: v.GetUint32("state-sync.snapshot-keep-recent"),
},
}
}

View File

@ -0,0 +1,20 @@
package config
import (
"testing"
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func TestDefaultConfig(t *testing.T) {
cfg := DefaultConfig()
require.True(t, cfg.GetMinGasPrices().IsZero())
}
func TestSetMinimumFees(t *testing.T) {
cfg := DefaultConfig()
cfg.SetMinGasPrices(sdk.DecCoins{sdk.NewInt64DecCoin("foo", 5)})
require.Equal(t, "5.000000000000000000foo", cfg.MinGasPrices)
}

View File

@ -0,0 +1,212 @@
package config
import (
"bytes"
"text/template"
"github.com/spf13/viper"
tmos "github.com/tendermint/tendermint/libs/os"
)
const defaultConfigTemplate = `# This is a TOML config file.
# For more information, see https://github.com/toml-lang/toml
###############################################################################
### Base Configuration ###
###############################################################################
# The minimum gas prices a validator is willing to accept for processing a
# transaction. A transaction's fees must meet the minimum of any denomination
# specified in this config (e.g. 0.25token1;0.0001token2).
minimum-gas-prices = "{{ .BaseConfig.MinGasPrices }}"
# default: the last 100 states are kept in addition to every 500th state; pruning at 10 block intervals
# nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
# everything: all saved states will be deleted, storing only the current state; pruning at 10 block intervals
# custom: allow pruning options to be manually specified through 'pruning-keep-recent', 'pruning-keep-every', and 'pruning-interval'
pruning = "{{ .BaseConfig.Pruning }}"
# These are applied if and only if the pruning strategy is custom.
pruning-keep-recent = "{{ .BaseConfig.PruningKeepRecent }}"
pruning-keep-every = "{{ .BaseConfig.PruningKeepEvery }}"
pruning-interval = "{{ .BaseConfig.PruningInterval }}"
# HaltHeight contains a non-zero block height at which a node will gracefully
# halt and shutdown that can be used to assist upgrades and testing.
#
# Note: Commitment of state will be attempted on the corresponding block.
halt-height = {{ .BaseConfig.HaltHeight }}
# HaltTime contains a non-zero minimum block time (in Unix seconds) at which
# a node will gracefully halt and shutdown that can be used to assist upgrades
# and testing.
#
# Note: Commitment of state will be attempted on the corresponding block.
halt-time = {{ .BaseConfig.HaltTime }}
# MinRetainBlocks defines the minimum block height offset from the current
# block being committed, such that all blocks past this offset are pruned
# from Tendermint. It is used as part of the process of determining the
# ResponseCommit.RetainHeight value during ABCI Commit. A value of 0 indicates
# that no blocks should be pruned.
#
# This configuration value is only responsible for pruning Tendermint blocks.
# It has no bearing on application state pruning which is determined by the
# "pruning-*" configurations.
#
# Note: Tendermint block pruning is dependant on this parameter in conunction
# with the unbonding (safety threshold) period, state pruning and state sync
# snapshot parameters to determine the correct minimum value of
# ResponseCommit.RetainHeight.
min-retain-blocks = {{ .BaseConfig.MinRetainBlocks }}
# InterBlockCache enables inter-block caching.
inter-block-cache = {{ .BaseConfig.InterBlockCache }}
# IndexEvents defines the set of events in the form {eventType}.{attributeKey},
# which informs Tendermint what to index. If empty, all events will be indexed.
#
# Example:
# ["message.sender", "message.recipient"]
index-events = {{ .BaseConfig.IndexEvents }}
###############################################################################
### Telemetry Configuration ###
###############################################################################
[telemetry]
# Prefixed with keys to separate services.
service-name = "{{ .Telemetry.ServiceName }}"
# Enabled enables the application telemetry functionality. When enabled,
# an in-memory sink is also enabled by default. Operators may also enabled
# other sinks such as Prometheus.
enabled = {{ .Telemetry.Enabled }}
# Enable prefixing gauge values with hostname.
enable-hostname = {{ .Telemetry.EnableHostname }}
# Enable adding hostname to labels.
enable-hostname-label = {{ .Telemetry.EnableHostnameLabel }}
# Enable adding service to labels.
enable-service-label = {{ .Telemetry.EnableServiceLabel }}
# PrometheusRetentionTime, when positive, enables a Prometheus metrics sink.
prometheus-retention-time = {{ .Telemetry.PrometheusRetentionTime }}
# GlobalLabels defines a global set of name/value label tuples applied to all
# metrics emitted using the wrapper functions defined in telemetry package.
#
# Example:
# [["chain_id", "cosmoshub-1"]]
global-labels = [{{ range $k, $v := .Telemetry.GlobalLabels }}
["{{index $v 0 }}", "{{ index $v 1}}"],{{ end }}
]
###############################################################################
### API Configuration ###
###############################################################################
[api]
# Enable defines if the API server should be enabled.
enable = {{ .API.Enable }}
# Swagger defines if swagger documentation should automatically be registered.
swagger = {{ .API.Swagger }}
# Address defines the API server to listen on.
address = "{{ .API.Address }}"
# MaxOpenConnections defines the number of maximum open connections.
max-open-connections = {{ .API.MaxOpenConnections }}
# RPCReadTimeout defines the Tendermint RPC read timeout (in seconds).
rpc-read-timeout = {{ .API.RPCReadTimeout }}
# RPCWriteTimeout defines the Tendermint RPC write timeout (in seconds).
rpc-write-timeout = {{ .API.RPCWriteTimeout }}
# RPCMaxBodyBytes defines the Tendermint maximum response body (in bytes).
rpc-max-body-bytes = {{ .API.RPCMaxBodyBytes }}
# EnableUnsafeCORS defines if CORS should be enabled (unsafe - use it at your own risk).
enabled-unsafe-cors = {{ .API.EnableUnsafeCORS }}
###############################################################################
### gRPC Configuration ###
###############################################################################
[grpc]
# Enable defines if the gRPC server should be enabled.
enable = {{ .GRPC.Enable }}
# Address defines the gRPC server address to bind to.
address = "{{ .GRPC.Address }}"
###############################################################################
### EVM RPC Configuration ###
###############################################################################
[evm-rpc]
# Enable defines if the gRPC server should be enabled.
enable = {{ .EVMRPC.Enable }}
# Address defines the EVM RPC HTTP server address to bind to.
address = "{{ .EVMRPC.RpcAddress }}"
# Address defines the EVM WebSocket server address to bind to.
ws-address = "{{ .EVMRPC.WsAddress }}"
###############################################################################
### State Sync Configuration ###
###############################################################################
# State sync snapshots allow other nodes to rapidly join the network without replaying historical
# blocks, instead downloading and applying a snapshot of the application state at a given height.
[state-sync]
# snapshot-interval specifies the block interval at which local state sync snapshots are
# taken (0 to disable). Must be a multiple of pruning-keep-every.
snapshot-interval = {{ .StateSync.SnapshotInterval }}
# snapshot-keep-recent specifies the number of recent snapshots to keep and serve (0 to keep all).
snapshot-keep-recent = {{ .StateSync.SnapshotKeepRecent }}
`
var configTemplate *template.Template
func init() {
var err error
tmpl := template.New("appConfigFileTemplate")
if configTemplate, err = tmpl.Parse(defaultConfigTemplate); err != nil {
panic(err)
}
}
// ParseConfig retrieves the default environment configuration for the
// application.
func ParseConfig(v *viper.Viper) (*Config, error) {
conf := DefaultConfig()
err := v.Unmarshal(conf)
return conf, err
}
// WriteConfigFile renders config using the template and writes it to
// configFilePath.
func WriteConfigFile(configFilePath string, config *Config) {
var buffer bytes.Buffer
if err := configTemplate.Execute(&buffer, config); err != nil {
panic(err)
}
tmos.MustWriteFile(configFilePath, buffer.Bytes(), 0644)
}

90
cmd/ethermintd/flags.go Normal file
View File

@ -0,0 +1,90 @@
package main
import (
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/cosmos/ethermint/version"
)
const (
flagLong = "long"
flagLogLevel = "log_level"
)
func init() {
infoCmd.Flags().Bool(flagLong, false, "Print full information")
}
var (
infoCmd = &cobra.Command{
Use: "info",
Short: "Print version info",
RunE: func(_ *cobra.Command, _ []string) error {
fmt.Println(version.Version())
return nil
},
}
)
var (
fromPassphrase string
ethNodeWS string
ethNodeHTTP string
statsdEnabled bool
statsdPrefix string
statsdAddress string
statsdStuckFunc string
evmDebug bool
logJSON bool
logLevel string
)
// addTxFlags adds common flags for commands to post tx
func addTxFlags(cmd *cobra.Command) *cobra.Command {
cmd.PersistentFlags().String(flags.FlagChainID, "testnet", "Specify Chain ID for sending Tx")
cmd.PersistentFlags().String(flags.FlagFrom, "", "Name or address of private key with which to sign")
cmd.PersistentFlags().StringVar(&fromPassphrase, "from-passphrase", "12345678", "Passphrase for private key specified with 'from'")
cmd.PersistentFlags().StringVar(&ethNodeWS, "eth-node-ws", "ws://localhost:1317", "WebSocket endpoint for an Ethereum node.")
cmd.PersistentFlags().StringVar(&ethNodeHTTP, "eth-node-http", "http://localhost:1317", "HTTP endpoint for an Ethereum node.")
cmd.PersistentFlags().BoolVar(&statsdEnabled, "statsd-enabled", false, "Enabled StatsD reporting.")
cmd.PersistentFlags().StringVar(&statsdPrefix, "statsd-prefix", "ethermintd", "Specify StatsD compatible metrics prefix.")
cmd.PersistentFlags().StringVar(&statsdAddress, "statsd-address", "localhost:8125", "UDP address of a StatsD compatible metrics aggregator.")
cmd.PersistentFlags().StringVar(&statsdStuckFunc, "statsd-stuck-func", "5m", "Sets a duration to consider a function to be stuck (e.g. in deadlock).")
cmd.PersistentFlags().String(flags.FlagFees, "", "Fees to pay along with transaction; eg: 10uatom")
cmd.PersistentFlags().String(flags.FlagGasPrices, "", "Gas prices to determine the transaction fee (e.g. 10uatom)")
cmd.PersistentFlags().String(flags.FlagNode, "tcp://localhost:26657", "<host>:<port> to tendermint rpc interface for this chain")
cmd.PersistentFlags().Float64(flags.FlagGasAdjustment, flags.DefaultGasAdjustment, "adjustment factor to be multiplied against the estimate returned by the tx simulation; if the gas limit is set manually this flag is ignored ")
cmd.PersistentFlags().StringP(flags.FlagBroadcastMode, "b", flags.BroadcastSync, "Transaction broadcasting mode (sync|async|block)")
cmd.PersistentFlags().BoolVar(&logJSON, "log-json", false, "Use JSON as the output format of the own logger (default: text)")
cmd.PersistentFlags().BoolVar(&evmDebug, "evm-debug", false, "Enable EVM debug traces")
cmd.PersistentFlags().StringVar(&logLevel, "log-level", "info", "Sets the level of the own logger (error, warn, info, debug)")
//cmd.PersistentFlags().Bool(flags.FlagTrustNode, true, "Trust connected full node (don't verify proofs for responses)")
cmd.PersistentFlags().String(flags.FlagKeyringBackend, keyring.BackendFile, "Select keyring's backend")
// --gas can accept integers and "simulate"
//cmd.PersistentFlags().Var(&flags.GasFlagVar, "gas", fmt.Sprintf(
// "gas limit to set per-transaction; set to %q to calculate required gas automatically (default %d)",
// flags.GasFlagAuto, flags.DefaultGasLimit,
//))
//viper.BindPFlag(flags.FlagTrustNode, cmd.Flags().Lookup(flags.FlagTrustNode))
viper.BindPFlag(flags.FlagNode, cmd.Flags().Lookup(flags.FlagNode))
viper.BindPFlag(flags.FlagKeyringBackend, cmd.Flags().Lookup(flags.FlagKeyringBackend))
cmd.MarkFlagRequired(flags.FlagChainID)
return cmd
}
func duration(s string, defaults time.Duration) time.Duration {
dur, err := time.ParseDuration(s)
if err != nil {
dur = defaults
}
return dur
}

467
cmd/ethermintd/start.go Normal file
View File

@ -0,0 +1,467 @@
package main
import (
"context"
"io"
"net"
"net/http"
"os"
"path/filepath"
"runtime/pprof"
"time"
"github.com/gorilla/mux"
"github.com/improbable-eng/grpc-web/go/grpcweb"
"github.com/rs/cors"
"github.com/spf13/cobra"
"github.com/xlab/closer"
log "github.com/xlab/suplog"
"google.golang.org/grpc"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/node"
"github.com/tendermint/tendermint/p2p"
pvm "github.com/tendermint/tendermint/privval"
"github.com/tendermint/tendermint/proxy"
"github.com/tendermint/tendermint/rpc/client/local"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/server/api"
sdkconfig "github.com/cosmos/cosmos-sdk/server/config"
servergrpc "github.com/cosmos/cosmos-sdk/server/grpc"
"github.com/cosmos/cosmos-sdk/server/types"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
ethrpc "github.com/ethereum/go-ethereum/rpc"
"github.com/cosmos/ethermint/cmd/ethermintd/config"
"github.com/cosmos/ethermint/ethereum/rpc"
)
// Tendermint full-node start flags
const (
flagWithTendermint = "with-tendermint"
flagAddress = "address"
flagTransport = "transport"
flagTraceStore = "trace-store"
flagCPUProfile = "cpu-profile"
FlagMinGasPrices = "minimum-gas-prices"
FlagHaltHeight = "halt-height"
FlagHaltTime = "halt-time"
FlagInterBlockCache = "inter-block-cache"
FlagUnsafeSkipUpgrades = "unsafe-skip-upgrades"
FlagTrace = "trace"
FlagInvCheckPeriod = "inv-check-period"
FlagPruning = "pruning"
FlagPruningKeepRecent = "pruning-keep-recent"
FlagPruningKeepEvery = "pruning-keep-every"
FlagPruningInterval = "pruning-interval"
FlagIndexEvents = "index-events"
FlagMinRetainBlocks = "min-retain-blocks"
)
// GRPC-related flags.
const (
flagGRPCEnable = "grpc.enable"
flagGRPCAddress = "grpc.address"
flagEVMRPCEnable = "evm-rpc.enable"
flagEVMRPCAddress = "evm-rpc.address"
flagEVMWSAddress = "evm-rpc.ws-address"
)
// State sync-related flags.
const (
FlagStateSyncSnapshotInterval = "state-sync.snapshot-interval"
FlagStateSyncSnapshotKeepRecent = "state-sync.snapshot-keep-recent"
)
// StartCmd runs the service passed in, either stand-alone or in-process with
// Tendermint.
func StartCmd(appCreator types.AppCreator, defaultNodeHome string) *cobra.Command {
cmd := &cobra.Command{
Use: "start",
Short: "Run the full node",
Long: `Run the full node application with Tendermint in or out of process. By
default, the application will run with Tendermint in process.
Pruning options can be provided via the '--pruning' flag or alternatively with '--pruning-keep-recent',
'pruning-keep-every', and 'pruning-interval' together.
For '--pruning' the options are as follows:
default: the last 100 states are kept in addition to every 500th state; pruning at 10 block intervals
nothing: all historic states will be saved, nothing will be deleted (i.e. archiving node)
everything: all saved states will be deleted, storing only the current state; pruning at 10 block intervals
custom: allow pruning options to be manually specified through 'pruning-keep-recent', 'pruning-keep-every', and 'pruning-interval'
Node halting configurations exist in the form of two flags: '--halt-height' and '--halt-time'. During
the ABCI Commit phase, the node will check if the current block height is greater than or equal to
the halt-height or if the current block time is greater than or equal to the halt-time. If so, the
node will attempt to gracefully shutdown and the block will not be committed. In addition, the node
will not be able to commit subsequent blocks.
For profiling and benchmarking purposes, CPU profiling can be enabled via the '--cpu-profile' flag
which accepts a path for the resulting pprof file.
`,
PreRunE: func(cmd *cobra.Command, _ []string) error {
serverCtx := server.GetServerContextFromCmd(cmd)
// Bind flags to the Context's Viper so the app construction can set
// options accordingly.
serverCtx.Viper.BindPFlags(cmd.Flags())
_, err := server.GetPruningOptionsFromFlags(serverCtx.Viper)
return err
},
RunE: func(cmd *cobra.Command, _ []string) error {
serverCtx := server.GetServerContextFromCmd(cmd)
clientCtx := client.GetClientContextFromCmd(cmd)
serverCtx.Logger.Info("starting ABCI with Tendermint")
// amino is needed here for backwards compatibility of REST routes
err := startInProcess(serverCtx, clientCtx, appCreator)
return err
},
}
cmd.Flags().String(flags.FlagHome, defaultNodeHome, "The application home directory")
cmd.Flags().String(flagAddress, "tcp://0.0.0.0:26658", "Listen address")
cmd.Flags().String(flagTransport, "socket", "Transport protocol: socket, grpc")
cmd.Flags().String(flagTraceStore, "", "Enable KVStore tracing to an output file")
cmd.Flags().String(FlagMinGasPrices, "", "Minimum gas prices to accept for transactions; Any fee in a tx must meet this minimum (e.g. 0.01photino;0.0001stake)")
cmd.Flags().IntSlice(FlagUnsafeSkipUpgrades, []int{}, "Skip a set of upgrade heights to continue the old binary")
cmd.Flags().Uint64(FlagHaltHeight, 0, "Block height at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Uint64(FlagHaltTime, 0, "Minimum block time (in Unix seconds) at which to gracefully halt the chain and shutdown the node")
cmd.Flags().Bool(FlagInterBlockCache, true, "Enable inter-block caching")
cmd.Flags().String(flagCPUProfile, "", "Enable CPU profiling and write to the provided file")
cmd.Flags().Bool(FlagTrace, false, "Provide full stack traces for errors in ABCI Log")
cmd.Flags().String(FlagPruning, storetypes.PruningOptionDefault, "Pruning strategy (default|nothing|everything|custom)")
cmd.Flags().Uint64(FlagPruningKeepRecent, 0, "Number of recent heights to keep on disk (ignored if pruning is not 'custom')")
cmd.Flags().Uint64(FlagPruningKeepEvery, 0, "Offset heights to keep on disk after 'keep-every' (ignored if pruning is not 'custom')")
cmd.Flags().Uint64(FlagPruningInterval, 0, "Height interval at which pruned heights are removed from disk (ignored if pruning is not 'custom')")
cmd.Flags().Uint(FlagInvCheckPeriod, 0, "Assert registered invariants every N blocks")
cmd.Flags().Uint64(FlagMinRetainBlocks, 0, "Minimum block height offset during ABCI commit to prune Tendermint blocks")
cmd.Flags().Bool(flagGRPCEnable, true, "Define if the gRPC server should be enabled")
cmd.Flags().String(flagGRPCAddress, config.DefaultGRPCAddress, "the gRPC server address to listen on")
cmd.Flags().Bool(flagEVMRPCEnable, true, "Define if the gRPC server should be enabled")
cmd.Flags().String(flagEVMRPCAddress, config.DefaultEVMAddress, "the EVM RPC server address to listen on")
cmd.Flags().String(flagEVMWSAddress, config.DefaultEVMWSAddress, "the EVM WS server address to listen on")
cmd.Flags().Uint64(FlagStateSyncSnapshotInterval, 0, "State sync snapshot interval")
cmd.Flags().Uint32(FlagStateSyncSnapshotKeepRecent, 2, "State sync snapshot to keep")
// add support for all Tendermint-specific command line options
tcmd.AddNodeFlags(cmd)
return cmd
}
func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator types.AppCreator) error {
cfg := ctx.Config
home := cfg.RootDir
envName := "chain-" + ctx.Viper.GetString(flags.FlagChainID)
if env := os.Getenv("APP_ENV"); len(env) > 0 {
envName = env
}
if statsdEnabled {
hostname, _ := os.Hostname()
metrics.Init(statsdAddress, statsdPrefix, &metrics.StatterConfig{
EnvName: envName,
HostName: hostname,
StuckFunctionTimeout: duration(statsdStuckFunc, 5*time.Minute),
MockingEnabled: false,
})
closer.Bind(func() {
metrics.Close()
})
}
traceWriterFile := ctx.Viper.GetString(flagTraceStore)
db, err := openDB(home)
if err != nil {
log.WithError(err).Errorln("failed to open DB")
return err
}
traceWriter, err := openTraceWriter(traceWriterFile)
if err != nil {
log.WithError(err).Errorln("failed to open trace writer")
return err
}
app := appCreator(ctx.Logger, db, traceWriter, ctx.Viper)
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
if err != nil {
log.WithError(err).Errorln("failed load or gen node key")
return err
}
genDocProvider := node.DefaultGenesisDocProviderFunc(cfg)
tmNode, err := node.NewNode(
cfg,
pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile()),
nodeKey,
proxy.NewLocalClientCreator(app),
genDocProvider,
node.DefaultDBProvider,
node.DefaultMetricsProvider(cfg.Instrumentation),
ctx.Logger.With("module", "node"),
)
if err != nil {
log.WithError(err).Errorln("failed init node")
return err
}
if err := tmNode.Start(); err != nil {
log.WithError(err).Errorln("failed start tendermint server")
return err
}
genDoc, err := genDocProvider()
if err != nil {
return err
}
clientCtx = clientCtx.
WithHomeDir(home).
WithChainID(genDoc.ChainID).
WithClient(local.New(tmNode))
var apiSrv *api.Server
config := config.GetConfig(ctx.Viper)
var grpcSrv *grpc.Server
if config.GRPC.Enable {
grpcSrv, err = servergrpc.StartGRPCServer(app, config.GRPC.Address)
if err != nil {
log.WithError(err).Errorln("failed to boot GRPC server")
}
}
var httpSrv *http.Server
var httpSrvDone = make(chan struct{}, 1)
var wsSrv rpc.WebsocketsServer
if config.EVMRPC.Enable {
tmEndpoint := "/websocket"
tmRPCAddr := cfg.RPC.ListenAddress
log.Infoln("EVM RPC Connecting to Tendermint WebSocket at", tmRPCAddr+tmEndpoint)
tmWsClient := connectTmWS(tmRPCAddr, tmEndpoint)
rpcServer := ethrpc.NewServer()
apis := rpc.GetRPCAPIs(clientCtx, tmWsClient)
for _, api := range apis {
if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
log.WithFields(log.Fields{
"namespace": api.Namespace,
"service": api.Service,
}).WithError(err).Fatalln("failed to register service in EVM RPC namespace")
}
}
r := mux.NewRouter()
r.HandleFunc("/", rpcServer.ServeHTTP).Methods("POST")
if grpcSrv != nil {
grpcWeb := grpcweb.WrapServer(grpcSrv)
mountGrpcWebServices(r, grpcWeb, grpcweb.ListGRPCResources(grpcSrv))
}
handlerWithCors := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{
http.MethodHead,
http.MethodGet,
http.MethodPost,
http.MethodPut,
http.MethodPatch,
http.MethodDelete,
},
AllowedHeaders: []string{"*"},
AllowCredentials: false,
OptionsPassthrough: false,
})
httpSrv = &http.Server{
Addr: config.EVMRPC.RpcAddress,
Handler: handlerWithCors.Handler(r),
}
errCh := make(chan error)
go func() {
log.Infoln("Starting EVM RPC server on", config.EVMRPC.RpcAddress)
if err := httpSrv.ListenAndServe(); err != nil {
if err == http.ErrServerClosed {
close(httpSrvDone)
return
}
log.WithError(err).Errorln("failed to start EVM RPC server")
errCh <- err
}
}()
select {
case err := <-errCh:
log.WithError(err).Errorln("failed to boot EVM RPC server")
return err
case <-time.After(1 * time.Second): // assume EVM RPC server started successfully
}
log.Infoln("Starting EVM WebSocket server on", config.EVMRPC.WsAddress)
_, port, _ := net.SplitHostPort(config.EVMRPC.RpcAddress)
// allocate separate WS connection to Tendermint
tmWsClient = connectTmWS(tmRPCAddr, tmEndpoint)
wsSrv = rpc.NewWebsocketsServer(tmWsClient, "localhost:"+port, config.EVMRPC.WsAddress)
go wsSrv.Start()
}
sdkcfg := sdkconfig.GetConfig(ctx.Viper)
sdkcfg.API = config.API
if sdkcfg.API.Enable {
apiSrv = api.New(clientCtx, ctx.Logger.With("module", "api-server"))
app.RegisterAPIRoutes(apiSrv, sdkcfg.API)
errCh := make(chan error)
go func() {
if err := apiSrv.Start(sdkcfg); err != nil {
errCh <- err
}
}()
select {
case err := <-errCh:
log.WithError(err).Errorln("failed to boot API server")
return err
case <-time.After(5 * time.Second): // assume server started successfully
}
}
var cpuProfileCleanup func()
if cpuProfile := ctx.Viper.GetString(flagCPUProfile); cpuProfile != "" {
f, err := os.Create(cpuProfile)
if err != nil {
log.WithError(err).Errorln("failed to create CP profile")
return err
}
log.WithField("profile", cpuProfile).Infoln("starting CPU profiler")
if err := pprof.StartCPUProfile(f); err != nil {
return err
}
cpuProfileCleanup = func() {
log.WithField("profile", cpuProfile).Infoln("stopping CPU profiler")
pprof.StopCPUProfile()
f.Close()
}
}
closer.Bind(func() {
if tmNode.IsRunning() {
_ = tmNode.Stop()
}
if cpuProfileCleanup != nil {
cpuProfileCleanup()
}
if httpSrv != nil {
shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
defer cancelFn()
if err := httpSrv.Shutdown(shutdownCtx); err != nil {
log.WithError(err).Warningln("HTTP server shutdown produced a warning")
} else {
log.Infoln("HTTP server shut down, waiting 5 sec")
select {
case <-time.Tick(5 * time.Second):
case <-httpSrvDone:
}
}
}
if grpcSrv != nil {
grpcSrv.Stop()
}
log.Infoln("Bye!")
})
closer.Hold()
return nil
}
func connectTmWS(tmRPCAddr, tmEndpoint string) *rpcclient.WSClient {
tmWsClient, err := rpcclient.NewWS(tmRPCAddr, tmEndpoint,
rpcclient.MaxReconnectAttempts(256),
rpcclient.ReadWait(120*time.Second),
rpcclient.WriteWait(120*time.Second),
rpcclient.PingPeriod(50*time.Second),
rpcclient.OnReconnect(func() {
log.WithField("tendermint_rpc", tmRPCAddr+tmEndpoint).
Debugln("EVM RPC reconnects to Tendermint WS")
}),
)
if err != nil {
log.WithError(err).Fatalln("Tendermint WS client could not be created for ", tmRPCAddr+tmEndpoint)
} else if err := tmWsClient.OnStart(); err != nil {
log.WithError(err).Fatalln("Tendermint WS client could not start for ", tmRPCAddr+tmEndpoint)
}
return tmWsClient
}
func mountGrpcWebServices(
router *mux.Router,
grpcWeb *grpcweb.WrappedGrpcServer,
grpcResources []string,
) {
for _, res := range grpcResources {
log.Printf("[GRPC Web] HTTP POST mounted on %s", res)
s := router.Methods("POST").Subrouter()
s.HandleFunc(res, func(resp http.ResponseWriter, req *http.Request) {
if grpcWeb.IsGrpcWebSocketRequest(req) {
grpcWeb.HandleGrpcWebsocketRequest(resp, req)
return
}
if grpcWeb.IsGrpcWebRequest(req) {
grpcWeb.HandleGrpcWebRequest(resp, req)
return
}
})
}
}
func openDB(rootDir string) (dbm.DB, error) {
dataDir := filepath.Join(rootDir, "data")
return sdk.NewLevelDB("application", dataDir)
}
func openTraceWriter(traceWriterFile string) (w io.Writer, err error) {
if traceWriterFile == "" {
return
}
return os.OpenFile(
traceWriterFile,
os.O_WRONLY|os.O_APPEND|os.O_CREATE,
0666,
)
}

54
ethereum/rpc/apis.go Normal file
View File

@ -0,0 +1,54 @@
// Package rpc contains RPC handler methods and utilities to start
// Ethermint's Web3-compatibly JSON-RPC server.
package rpc
import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/ethermint/ethereum/rpc/types"
"github.com/ethereum/go-ethereum/rpc"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
)
// RPC namespaces and API version
const (
Web3Namespace = "web3"
EthNamespace = "eth"
PersonalNamespace = "personal"
NetNamespace = "net"
apiVersion = "1.0"
)
// GetRPCAPIs returns the list of all APIs
func GetRPCAPIs(clientCtx client.Context, tmWSClient *rpcclient.WSClient) []rpc.API {
nonceLock := new(types.AddrLocker)
backend := NewEVMBackend(clientCtx)
ethAPI := NewPublicEthAPI(clientCtx, backend, nonceLock)
return []rpc.API{
{
Namespace: Web3Namespace,
Version: apiVersion,
Service: NewPublicWeb3API(),
Public: true,
},
{
Namespace: EthNamespace,
Version: apiVersion,
Service: ethAPI,
Public: true,
},
{
Namespace: EthNamespace,
Version: apiVersion,
Service: NewPublicFilterAPI(tmWSClient, backend),
Public: true,
},
{
Namespace: NetNamespace,
Version: apiVersion,
Service: NewPublicNetAPI(clientCtx),
Public: true,
},
}
}

338
ethereum/rpc/backend.go Normal file
View File

@ -0,0 +1,338 @@
package rpc
import (
"context"
"math/big"
"regexp"
"github.com/cosmos/ethermint/ethereum/rpc/types"
"github.com/pkg/errors"
tmtypes "github.com/tendermint/tendermint/types"
log "github.com/xlab/suplog"
"github.com/cosmos/cosmos-sdk/client"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethermint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
// Backend implements the functionality needed to filter changes.
// Implemented by EVMBackend.
type Backend interface {
// Used by block filter; also used for polling
BlockNumber() (hexutil.Uint64, error)
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
// returns the logs of a given block
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
// Used by pending transaction filter
PendingTransactions() ([]*types.Transaction, error)
// Used by log filter
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
BloomStatus() (uint64, uint64)
}
var _ Backend = (*EVMBackend)(nil)
// implements the Backend interface
type EVMBackend struct {
ctx context.Context
clientCtx client.Context
queryClient *types.QueryClient // gRPC query client
logger log.Logger
}
func NewEVMBackend(clientCtx client.Context) *EVMBackend {
return &EVMBackend{
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: types.NewQueryClient(clientCtx),
logger: log.WithField("module", "evm-backend"),
}
}
// BlockNumber returns the current block number.
func (e *EVMBackend) BlockNumber() (hexutil.Uint64, error) {
// NOTE: using 0 as min and max height returns the blockchain info up to the latest block.
info, err := e.clientCtx.Client.BlockchainInfo(e.ctx, 0, 0)
if err != nil {
return hexutil.Uint64(0), err
}
return hexutil.Uint64(info.LastHeight), nil
}
// GetBlockByNumber returns the block identified by number.
func (e *EVMBackend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) {
height := blockNum.Int64()
currentBlockNumber, _ := e.BlockNumber()
switch blockNum {
case types.EthLatestBlockNumber:
if currentBlockNumber > 0 {
height = int64(currentBlockNumber - 1)
}
case types.EthPendingBlockNumber:
if currentBlockNumber > 0 {
height = int64(currentBlockNumber)
}
case types.EthEarliestBlockNumber:
height = 1
default:
if blockNum < 0 {
err := errors.Errorf("incorrect block height: %d", height)
return nil, err
} else if height > int64(currentBlockNumber) {
return nil, nil
}
}
resBlock, err := e.clientCtx.Client.Block(e.ctx, &height)
if err != nil {
// e.logger.Debugf("GetBlockByNumber safely bumping down from %d to latest", height)
if resBlock, err = e.clientCtx.Client.Block(e.ctx, nil); err != nil {
e.logger.Warningln("GetBlockByNumber failed to get latest block")
return nil, nil
}
}
res, err := e.ethBlockFromTendermint(e.clientCtx, e.queryClient, resBlock.Block, fullTx)
if err != nil {
e.logger.WithError(err).Warningf("EthBlockFromTendermint failed with block %s", resBlock.Block.String())
}
return res, err
}
// GetBlockByHash returns the block identified by hash.
func (e *EVMBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
e.logger.Warningf("BlockByHash failed for %s", hash.Hex())
return nil, err
}
return e.ethBlockFromTendermint(e.clientCtx, e.queryClient, resBlock.Block, fullTx)
}
// ethBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a given Tendermint block.
func (e *EVMBackend) ethBlockFromTendermint(
clientCtx client.Context,
queryClient evmtypes.QueryClient,
block *tmtypes.Block,
fullTx bool,
) (map[string]interface{}, error) {
txReceiptsResp, err := queryClient.TxReceiptsByBlockHeight(types.ContextWithHeight(0), &evmtypes.QueryTxReceiptsByBlockHeightRequest{
Height: block.Height,
})
if err != nil {
e.logger.Warningf("TxReceiptsByBlockHeight fail: %s", err.Error())
return nil, err
}
gasUsed := big.NewInt(0)
ethRPCTxs := make([]interface{}, 0, len(txReceiptsResp.Receipts))
if !fullTx {
// simply hashes
for _, receipt := range txReceiptsResp.Receipts {
ethRPCTxs = append(ethRPCTxs, common.BytesToHash(receipt.Hash))
}
} else {
// full txns from receipts
for _, receipt := range txReceiptsResp.Receipts {
tx, err := NewTransactionFromData(
receipt.Data,
common.BytesToAddress(receipt.From),
common.BytesToHash(receipt.Hash),
common.BytesToHash(receipt.BlockHash),
receipt.BlockHeight,
receipt.Index,
)
if err != nil {
e.logger.Warningf("NewTransactionFromData fail: %s", err.Error())
continue
}
ethRPCTxs = append(ethRPCTxs, tx)
gasUsed.Add(gasUsed, new(big.Int).SetUint64(receipt.Result.GasUsed))
}
}
blockBloomResp, err := queryClient.BlockBloom(types.ContextWithHeight(0), &evmtypes.QueryBlockBloomRequest{
Height: block.Height,
})
if err != nil {
err = errors.Wrapf(err, "failed to query BlockBloom for height %d", block.Height)
return nil, err
}
bloom := ethtypes.BytesToBloom(blockBloomResp.Bloom)
formattedBlock := formatBlock(block.Header, block.Size(), ethermint.DefaultRPCGasLimit, gasUsed, ethRPCTxs, bloom)
return formattedBlock, nil
}
// HeaderByNumber returns the block header identified by height.
func (e *EVMBackend) HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) {
height := blockNum.Int64()
currentBlockNumber, _ := e.BlockNumber()
switch blockNum {
case types.EthLatestBlockNumber:
if currentBlockNumber > 0 {
height = int64(currentBlockNumber - 1)
}
case types.EthPendingBlockNumber:
if currentBlockNumber > 0 {
height = int64(currentBlockNumber)
}
case types.EthEarliestBlockNumber:
height = 1
default:
if blockNum < 0 {
err := errors.Errorf("incorrect block height: %d", height)
return nil, err
}
}
resBlock, err := e.clientCtx.Client.Block(e.ctx, &height)
if err != nil {
e.logger.Warningf("HeaderByNumber failed")
return nil, err
}
req := &evmtypes.QueryBlockBloomRequest{
Height: resBlock.Block.Height,
}
res, err := e.queryClient.BlockBloom(types.ContextWithHeight(resBlock.Block.Height), req)
if err != nil {
e.logger.Warningf("HeaderByNumber BlockBloom fail %d", resBlock.Block.Height)
return nil, err
}
ethHeader := EthHeaderFromTendermint(resBlock.Block.Header)
ethHeader.Bloom = ethtypes.BytesToBloom(res.Bloom)
return ethHeader, nil
}
// HeaderByHash returns the block header identified by hash.
func (e *EVMBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, blockHash.Bytes())
if err != nil {
e.logger.Warningf("HeaderByHash fail")
return nil, err
}
req := &evmtypes.QueryBlockBloomRequest{
Height: resBlock.Block.Height,
}
res, err := e.queryClient.BlockBloom(types.ContextWithHeight(resBlock.Block.Height), req)
if err != nil {
e.logger.Warningf("HeaderByHash BlockBloom fail %d", resBlock.Block.Height)
return nil, err
}
ethHeader := EthHeaderFromTendermint(resBlock.Block.Header)
ethHeader.Bloom = ethtypes.BytesToBloom(res.Bloom)
return ethHeader, nil
}
// GetTransactionLogs returns the logs given a transaction hash.
// It returns an error if there's an encoding error.
// If no logs are found for the tx hash, the error is nil.
func (e *EVMBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
req := &evmtypes.QueryTxLogsRequest{
Hash: txHash.String(),
}
res, err := e.queryClient.TxLogs(e.ctx, req)
if err != nil {
e.logger.Warningf("TxLogs fail")
return nil, err
}
return evmtypes.LogsToEthereum(res.Logs), nil
}
// PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (e *EVMBackend) PendingTransactions() ([]*types.Transaction, error) {
return []*types.Transaction{}, nil
}
// GetLogs returns all the logs from all the ethereum transactions in a block.
func (e *EVMBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) {
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
req := &evmtypes.QueryBlockLogsRequest{
Hash: blockHash.String(),
}
res, err := e.queryClient.BlockLogs(e.ctx, req)
if err != nil {
e.logger.Warningf("BlockLogs fail")
return nil, err
}
var blockLogs = [][]*ethtypes.Log{}
for _, txLog := range res.TxLogs {
blockLogs = append(blockLogs, txLog.EthLogs())
}
return blockLogs, nil
}
// This is very brittle, see: https://github.com/tendermint/tendermint/issues/4740
var regexpMissingHeight = regexp.MustCompile(`height \d+ (must be less than or equal to|is not available)`)
func (e *EVMBackend) GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error) {
height := blockNum.Int64()
currentBlockNumber, _ := e.BlockNumber()
switch blockNum {
case types.EthLatestBlockNumber:
if currentBlockNumber > 0 {
height = int64(currentBlockNumber - 1)
}
case types.EthPendingBlockNumber:
if currentBlockNumber > 0 {
height = int64(currentBlockNumber)
}
case types.EthEarliestBlockNumber:
height = 1
default:
if blockNum < 0 {
err := errors.Errorf("incorrect block height: %d", height)
return nil, err
}
}
resBlock, err := e.clientCtx.Client.Block(e.ctx, &height)
if err != nil {
if regexpMissingHeight.MatchString(err.Error()) {
return [][]*ethtypes.Log{}, nil
}
e.logger.Warningf("failed to query block at %d", height)
return nil, err
}
return e.GetLogs(common.BytesToHash(resBlock.BlockID.Hash))
}
// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
// by the chain indexer.
func (e *EVMBackend) BloomStatus() (uint64, uint64) {
return 4096, 0
}

760
ethereum/rpc/eth_api.go Normal file
View File

@ -0,0 +1,760 @@
package rpc
import (
"bytes"
"context"
"encoding/json"
"math/big"
"strings"
"sync"
"github.com/cosmos/ethermint/ethereum/rpc/types"
"github.com/gogo/protobuf/jsonpb"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/pkg/errors"
log "github.com/xlab/suplog"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authtx "github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/tendermint/tendermint/crypto/tmhash"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/rlp"
rpctypes "github.com/cosmos/ethermint/ethereum/rpc/types"
ethermint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
// PublicEthAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicEthAPI struct {
ctx context.Context
clientCtx client.Context
queryClient *types.QueryClient
chainIDEpoch *big.Int
logger log.Logger
backend Backend
nonceLock *types.AddrLocker
keyringLock sync.Mutex
}
// NewPublicEthAPI creates an instance of the public ETH Web3 API.
func NewPublicEthAPI(
clientCtx client.Context,
backend Backend,
nonceLock *types.AddrLocker,
) *PublicEthAPI {
epoch, err := ethermint.ParseChainID(clientCtx.ChainID)
if err != nil {
panic(err)
}
api := &PublicEthAPI{
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: types.NewQueryClient(clientCtx),
chainIDEpoch: epoch,
logger: log.WithField("module", "json-rpc"),
backend: backend,
nonceLock: nonceLock,
}
return api
}
// ProtocolVersion returns the supported Ethereum protocol version.
func (e *PublicEthAPI) ProtocolVersion() hexutil.Uint {
e.logger.Debugln("eth_protocolVersion")
return hexutil.Uint(evmtypes.ProtocolVersion)
}
// ChainId returns the chain's identifier in hex format
func (e *PublicEthAPI) ChainId() (hexutil.Uint, error) { // nolint
e.logger.Debugln("eth_chainId")
return hexutil.Uint(uint(e.chainIDEpoch.Uint64())), nil
}
// Syncing returns whether or not the current node is syncing with other peers. Returns false if not, or a struct
// outlining the state of the sync if it is.
func (e *PublicEthAPI) Syncing() (interface{}, error) {
e.logger.Debugln("eth_syncing")
status, err := e.clientCtx.Client.Status(e.ctx)
if err != nil {
return false, err
}
if !status.SyncInfo.CatchingUp {
return false, nil
}
return map[string]interface{}{
// "startingBlock": nil, // NA
"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
// "highestBlock": nil, // NA
// "pulledStates": nil, // NA
// "knownStates": nil, // NA
}, nil
}
// Coinbase is the address that staking rewards will be send to (alias for Etherbase).
func (e *PublicEthAPI) Coinbase() (common.Address, error) {
e.logger.Debugln("eth_coinbase")
node, err := e.clientCtx.GetNode()
if err != nil {
return common.Address{}, err
}
status, err := node.Status(e.ctx)
if err != nil {
return common.Address{}, err
}
return common.BytesToAddress(status.ValidatorInfo.Address.Bytes()), nil
}
// Mining returns whether or not this node is currently mining. Always false.
func (e *PublicEthAPI) Mining() bool {
e.logger.Debugln("eth_mining")
return false
}
// Hashrate returns the current node's hashrate. Always 0.
func (e *PublicEthAPI) Hashrate() hexutil.Uint64 {
e.logger.Debugln("eth_hashrate")
return 0
}
// GasPrice returns the current gas price based on Ethermint's gas price oracle.
func (e *PublicEthAPI) GasPrice() *hexutil.Big {
e.logger.Debugln("eth_gasPrice")
out := big.NewInt(0)
return (*hexutil.Big)(out)
}
// Accounts returns the list of accounts available to this node.
func (e *PublicEthAPI) Accounts() ([]common.Address, error) {
e.logger.Debugln("eth_accounts")
return []common.Address{}, nil
}
// BlockNumber returns the current block number.
func (e *PublicEthAPI) BlockNumber() (hexutil.Uint64, error) {
//e.logger.Debugln("eth_blockNumber")
return e.backend.BlockNumber()
}
// GetBalance returns the provided account's balance up to the provided block number.
func (e *PublicEthAPI) GetBalance(address common.Address, blockNum types.BlockNumber) (*hexutil.Big, error) { // nolint: interfacer
e.logger.Debugln("eth_getBalance", "address", address.Hex(), "block number", blockNum)
req := &evmtypes.QueryBalanceRequest{
Address: address.String(),
}
res, err := e.queryClient.Balance(types.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
val, err := ethermint.UnmarshalBigInt(res.Balance)
if err != nil {
return nil, err
}
return (*hexutil.Big)(val), nil
}
// GetStorageAt returns the contract storage at the given address, block number, and key.
func (e *PublicEthAPI) GetStorageAt(address common.Address, key string, blockNum types.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer
e.logger.Debugln("eth_getStorageAt", "address", address.Hex(), "key", key, "block number", blockNum)
req := &evmtypes.QueryStorageRequest{
Address: address.String(),
Key: key,
}
res, err := e.queryClient.Storage(types.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
value := common.HexToHash(res.Value)
return value.Bytes(), nil
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
func (e *PublicEthAPI) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) {
e.logger.Debugln("eth_getTransactionCount", "address", address.Hex(), "block number", blockNum)
// Get nonce (sequence) from account
from := sdk.AccAddress(address.Bytes())
accRet := e.clientCtx.AccountRetriever
err := accRet.EnsureExists(e.clientCtx, from)
if err != nil {
// account doesn't exist yet, return 0
n := hexutil.Uint64(0)
return &n, nil
}
_, nonce, err := accRet.GetAccountNumberSequence(e.clientCtx, from)
if err != nil {
return nil, err
}
n := hexutil.Uint64(nonce)
return &n, nil
}
// GetBlockTransactionCountByHash returns the number of transactions in the block identified by hash.
func (e *PublicEthAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
e.logger.Debugln("eth_getBlockTransactionCountByHash", "hash", hash.Hex())
resBlock, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
if err != nil {
return nil
}
n := hexutil.Uint(len(resBlock.Block.Txs))
return &n
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
func (e *PublicEthAPI) GetBlockTransactionCountByNumber(blockNum types.BlockNumber) *hexutil.Uint {
e.logger.Debugln("eth_getBlockTransactionCountByNumber", "block number", blockNum)
resBlock, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
if err != nil {
return nil
}
n := hexutil.Uint(len(resBlock.Block.Txs))
return &n
}
// GetUncleCountByBlockHash returns the number of uncles in the block identified by hash. Always zero.
func (e *PublicEthAPI) GetUncleCountByBlockHash(hash common.Hash) hexutil.Uint {
return 0
}
// GetUncleCountByBlockNumber returns the number of uncles in the block identified by number. Always zero.
func (e *PublicEthAPI) GetUncleCountByBlockNumber(blockNum types.BlockNumber) hexutil.Uint {
return 0
}
// GetCode returns the contract code at the given address and block number.
func (e *PublicEthAPI) GetCode(address common.Address, blockNumber types.BlockNumber) (hexutil.Bytes, error) { // nolint: interfacer
e.logger.Debugln("eth_getCode", "address", address.Hex(), "block number", blockNumber)
req := &evmtypes.QueryCodeRequest{
Address: address.String(),
}
res, err := e.queryClient.Code(types.ContextWithHeight(blockNumber.Int64()), req)
if err != nil {
return nil, err
}
return res.Code, nil
}
// GetTransactionLogs returns the logs given a transaction hash.
func (e *PublicEthAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
e.logger.Debugln("eth_getTransactionLogs", "hash", txHash)
return e.backend.GetTransactionLogs(txHash)
}
// Sign signs the provided data using the private key of address via Geth's signature standard.
func (e *PublicEthAPI) Sign(address common.Address, data hexutil.Bytes) (hexutil.Bytes, error) {
e.logger.Debugln("eth_sign", "address", address.Hex(), "data", common.Bytes2Hex(data))
return nil, errors.New("eth_sign not supported")
}
// SendTransaction sends an Ethereum transaction.
func (e *PublicEthAPI) SendTransaction(args types.SendTxArgs) (common.Hash, error) {
e.logger.Debugln("eth_sendTransaction", "args", args)
return common.Hash{}, errors.New("eth_sendTransaction not supported")
}
// SendRawTransaction send a raw Ethereum transaction.
func (e *PublicEthAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error) {
e.logger.Debugln("eth_sendRawTransaction", "data_len", len(data))
ethereumTx := new(evmtypes.MsgEthereumTx)
// RLP decode raw transaction bytes
if err := rlp.DecodeBytes(data, ethereumTx); err != nil {
e.logger.WithError(err).Errorln("transaction RLP decode failed")
// Return nil is for when gasLimit overflows uint64
return common.Hash{}, err
}
builder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
e.logger.Panicln("clientCtx.TxConfig.NewTxBuilder returns unsupported builder")
}
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
if err != nil {
e.logger.Panicln("codectypes.NewAnyWithValue failed to pack an obvious value")
}
builder.SetExtensionOptions(option)
err = builder.SetMsgs(ethereumTx.GetMsgs()...)
if err != nil {
e.logger.Panicln("builder.SetMsgs failed")
}
fees := sdk.NewCoins(ethermint.NewPhotonCoin(sdk.NewIntFromBigInt(ethereumTx.Fee())))
builder.SetFeeAmount(fees)
builder.SetGasLimit(ethereumTx.GetGas())
// Encode transaction by default Tx encoder
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(builder.GetTx())
if err != nil {
e.logger.WithError(err).Errorln("failed to encode Eth tx using default encoder")
return common.Hash{}, err
}
txHash := common.BytesToHash(tmhash.Sum(txBytes))
asyncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastAsync)
if _, err := asyncCtx.BroadcastTx(txBytes); err != nil {
e.logger.WithError(err).Errorln("failed to broadcast Eth tx")
return txHash, err
}
return txHash, nil
}
// Call performs a raw contract call.
func (e *PublicEthAPI) Call(args types.CallArgs, blockNr types.BlockNumber, _ *map[common.Address]types.Account) (hexutil.Bytes, error) {
//e.logger.Debugln("eth_call", "args", args, "block number", blockNr)
simRes, err := e.doCall(args, blockNr, big.NewInt(ethermint.DefaultRPCGasLimit))
if err != nil {
return []byte{}, err
} else if len(simRes.Result.Log) > 0 {
var logs []sdkTxLogs
if err := json.Unmarshal([]byte(simRes.Result.Log), &logs); err != nil {
e.logger.WithError(err).Errorln("failed to unmarshal simRes.Result.Log")
}
if len(logs) > 0 && logs[0].Log == logRevertedFlag {
data, err := evmtypes.DecodeTxResponse(simRes.Result.Data)
if err != nil {
e.logger.WithError(err).Warningln("call result decoding failed")
return []byte{}, err
}
return []byte{}, errRevertedWith(data.Ret)
}
}
data, err := evmtypes.DecodeTxResponse(simRes.Result.Data)
if err != nil {
e.logger.WithError(err).Warningln("call result decoding failed")
return []byte{}, err
}
return (hexutil.Bytes)(data.Ret), nil
}
var zeroAddr = common.HexToAddress("0x0000000000000000000000000000000000000000")
// DoCall performs a simulated call operation through the evmtypes. It returns the
// estimated gas used on the operation or an error if fails.
func (e *PublicEthAPI) doCall(
args types.CallArgs, blockNr types.BlockNumber, globalGasCap *big.Int,
) (*sdk.SimulationResponse, error) {
// Set default gas & gas price if none were set
// Change this to uint64(math.MaxUint64 / 2) if gas cap can be configured
gas := uint64(ethermint.DefaultRPCGasLimit)
if args.Gas != nil {
gas = uint64(*args.Gas)
}
if globalGasCap != nil && globalGasCap.Uint64() < gas {
e.logger.Debugln("Caller gas above allowance, capping", "requested", gas, "cap", globalGasCap)
gas = globalGasCap.Uint64()
}
// Set gas price using default or parameter if passed in
gasPrice := new(big.Int).SetUint64(ethermint.DefaultGasPrice)
if args.GasPrice != nil {
gasPrice = args.GasPrice.ToInt()
}
// Set value for transaction
value := new(big.Int)
if args.Value != nil {
value = args.Value.ToInt()
}
// Set Data if provided
var data []byte
if args.Data != nil {
data = []byte(*args.Data)
}
// Set destination address for call
var fromAddr sdk.AccAddress
if args.From != nil {
fromAddr = sdk.AccAddress(args.From.Bytes())
} else {
fromAddr = sdk.AccAddress(zeroAddr.Bytes())
}
_, seq, err := e.clientCtx.AccountRetriever.GetAccountNumberSequence(e.clientCtx, fromAddr)
if err != nil {
return nil, err
}
// Create new call message
msg := evmtypes.NewMsgEthereumTx(seq, args.To, value, gas, gasPrice, data)
if err := msg.ValidateBasic(); err != nil {
return nil, err
}
msg.From = &evmtypes.SigCache{
Address: fromAddr.Bytes(),
}
if err := msg.ValidateBasic(); err != nil {
return nil, err
}
// Create a TxBuilder
txBuilder, ok := e.clientCtx.TxConfig.NewTxBuilder().(authtx.ExtensionOptionsTxBuilder)
if !ok {
log.Panicln("clientCtx.TxConfig.NewTxBuilder returns unsupported builder")
}
option, err := codectypes.NewAnyWithValue(&evmtypes.ExtensionOptionsEthereumTx{})
if err != nil {
log.Panicln("codectypes.NewAnyWithValue failed to pack an obvious value")
}
txBuilder.SetExtensionOptions(option)
if err := txBuilder.SetMsgs(msg); err != nil {
log.Panicln("builder.SetMsgs failed")
}
fees := sdk.NewCoins(ethermint.NewPhotonCoin(sdk.NewIntFromBigInt(msg.Fee())))
txBuilder.SetFeeAmount(fees)
txBuilder.SetGasLimit(gas)
//doc about generate and transform tx into json, protobuf bytes
//https://github.com/cosmos/cosmos-sdk/blob/master/docs/run-node/txs.md
txBytes, err := e.clientCtx.TxConfig.TxEncoder()(txBuilder.GetTx())
if err != nil {
return nil, err
}
// simulate by calling ABCI Query
query := abci.RequestQuery{
Path: "/app/simulate",
Data: txBytes,
Height: blockNr.Int64(),
}
queryResult, err := e.clientCtx.QueryABCI(query)
if err != nil {
return nil, err
}
var simResponse sdk.SimulationResponse
err = jsonpb.Unmarshal(strings.NewReader(string(queryResult.Value)), &simResponse)
if err != nil {
return nil, err
}
return &simResponse, nil
}
// EstimateGas returns an estimate of gas usage for the given smart contract call.
// It adds 1,000 gas to the returned value instead of using the gas adjustment
// param from the SDK.
func (e *PublicEthAPI) EstimateGas(args types.CallArgs) (hexutil.Uint64, error) {
e.logger.Debugln("eth_estimateGas")
// From ContextWithHeight: if the provided height is 0,
// it will return an empty context and the gRPC query will use
// the latest block height for querying.
simRes, err := e.doCall(args, 0, big.NewInt(ethermint.DefaultRPCGasLimit))
if err != nil {
return 0, err
} else if len(simRes.Result.Log) > 0 {
var logs []sdkTxLogs
if err := json.Unmarshal([]byte(simRes.Result.Log), &logs); err != nil {
e.logger.WithError(err).Errorln("failed to unmarshal simRes.Result.Log")
}
if len(logs) > 0 && logs[0].Log == logRevertedFlag {
data, err := evmtypes.DecodeTxResponse(simRes.Result.Data)
if err != nil {
e.logger.WithError(err).Warningln("call result decoding failed")
return 0, err
}
return 0, errRevertedWith(data.Ret)
}
}
// TODO: change 1000 buffer for more accurate buffer (eg: SDK's gasAdjusted)
estimatedGas := simRes.GasInfo.GasUsed
gas := estimatedGas + 200000
return hexutil.Uint64(gas), nil
}
// GetBlockByHash returns the block identified by hash.
func (e *PublicEthAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
e.logger.Debugln("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx)
return e.backend.GetBlockByHash(hash, fullTx)
}
// GetBlockByNumber returns the block identified by number.
func (e *PublicEthAPI) GetBlockByNumber(ethBlockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) {
// e.logger.Debugln("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx)
return e.backend.GetBlockByNumber(ethBlockNum, fullTx)
}
// GetTransactionByHash returns the transaction identified by hash.
func (e *PublicEthAPI) GetTransactionByHash(hash common.Hash) (*types.Transaction, error) {
e.logger.Debugln("eth_getTransactionByHash", "hash", hash.Hex())
resp, err := e.queryClient.TxReceipt(e.ctx, &evmtypes.QueryTxReceiptRequest{
Hash: hash.Hex(),
})
if err != nil {
e.logger.Debugf("failed to get tx info for %s: %s", hash.Hex(), err.Error())
return nil, nil
}
return NewTransactionFromData(
resp.Receipt.Data,
common.BytesToAddress(resp.Receipt.From),
common.BytesToHash(resp.Receipt.Hash),
common.BytesToHash(resp.Receipt.BlockHash),
resp.Receipt.BlockHeight,
resp.Receipt.Index,
)
}
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
func (e *PublicEthAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*types.Transaction, error) {
e.logger.Debugln("eth_getTransactionByHashAndIndex", "hash", hash.Hex(), "index", idx)
resp, err := e.queryClient.TxReceiptsByBlockHash(e.ctx, &evmtypes.QueryTxReceiptsByBlockHashRequest{
Hash: hash.Hex(),
})
if err != nil {
err = errors.Wrap(err, "failed to query tx receipts by block hash")
return nil, err
}
return e.getReceiptByIndex(resp.Receipts, hash, idx)
}
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
func (e *PublicEthAPI) GetTransactionByBlockNumberAndIndex(blockNum types.BlockNumber, idx hexutil.Uint) (*types.Transaction, error) {
e.logger.Debugln("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
resp, err := e.queryClient.TxReceiptsByBlockHeight(e.ctx, &evmtypes.QueryTxReceiptsByBlockHeightRequest{
Height: blockNum.Int64(),
})
if err != nil {
err = errors.Wrap(err, "failed to query tx receipts by block height")
return nil, err
}
return e.getReceiptByIndex(resp.Receipts, common.Hash{}, idx)
}
func (e *PublicEthAPI) getReceiptByIndex(receipts []*evmtypes.TxReceipt, blockHash common.Hash, idx hexutil.Uint) (*types.Transaction, error) {
// return if index out of bounds
if uint64(idx) >= uint64(len(receipts)) {
return nil, nil
}
receipt := receipts[idx]
if (blockHash != common.Hash{}) {
if !bytes.Equal(receipt.BlockHash, blockHash.Bytes()) {
err := errors.Errorf("receipt found but block hashes don't match %s != %s",
common.Bytes2Hex(receipt.BlockHash),
blockHash.Hex(),
)
return nil, err
}
}
return NewTransactionFromData(
receipt.Data,
common.BytesToAddress(receipt.From),
common.BytesToHash(receipt.Hash),
blockHash,
receipt.BlockHeight,
uint64(idx),
)
}
// GetTransactionReceipt returns the transaction receipt identified by hash.
func (e *PublicEthAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
e.logger.Debugln("eth_getTransactionReceipt", "hash", hash.Hex())
ctx := types.ContextWithHeight(int64(0))
tx, err := e.queryClient.TxReceipt(ctx, &evmtypes.QueryTxReceiptRequest{
Hash: hash.Hex(),
})
if err != nil {
e.logger.Debugf("failed to get tx receipt for %s: %s", hash.Hex(), err.Error())
return nil, nil
}
// Query block for consensus hash
height := int64(tx.Receipt.BlockHeight)
block, err := e.clientCtx.Client.Block(e.ctx, &height)
if err != nil {
e.logger.Warningln("didnt find block for tx height", height)
return nil, err
}
cumulativeGasUsed := uint64(tx.Receipt.Result.GasUsed)
if tx.Receipt.Index != 0 {
cumulativeGasUsed += rpctypes.GetBlockCumulativeGas(e.clientCtx, block.Block, int(tx.Receipt.Index))
}
var status hexutil.Uint
if tx.Receipt.Result.Reverted {
status = hexutil.Uint(0)
} else {
status = hexutil.Uint(1)
}
toHex := common.Address{}
if len(tx.Receipt.Data.Recipient) > 0 {
toHex = common.BytesToAddress(tx.Receipt.Data.Recipient)
}
contractAddress := common.HexToAddress(tx.Receipt.Result.ContractAddress)
logsBloom := hexutil.Encode(ethtypes.BytesToBloom(tx.Receipt.Result.Bloom).Bytes())
receipt := map[string]interface{}{
// Consensus fields: These fields are defined by the Yellow Paper
"status": status,
"cumulativeGasUsed": hexutil.Uint64(cumulativeGasUsed),
"logsBloom": logsBloom,
"logs": tx.Receipt.Result.TxLogs.EthLogs(),
// Implementation fields: These fields are added by geth when processing a transaction.
// They are stored in the chain database.
"transactionHash": hash.Hex(),
"contractAddress": contractAddress.Hex(),
"gasUsed": hexutil.Uint64(tx.Receipt.Result.GasUsed),
// Inclusion information: These fields provide information about the inclusion of the
// transaction corresponding to this receipt.
"blockHash": common.BytesToHash(block.Block.Header.Hash()).Hex(),
"blockNumber": hexutil.Uint64(tx.Receipt.BlockHeight),
"transactionIndex": hexutil.Uint64(tx.Receipt.Index),
// sender and receiver (contract or EOA) addreses
"from": common.BytesToAddress(tx.Receipt.From),
"to": toHex,
}
return receipt, nil
}
// PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (e *PublicEthAPI) PendingTransactions() ([]*types.Transaction, error) {
e.logger.Debugln("eth_getPendingTransactions")
return e.backend.PendingTransactions()
}
// GetUncleByBlockHashAndIndex returns the uncle identified by hash and index. Always returns nil.
func (e *PublicEthAPI) GetUncleByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) map[string]interface{} {
return nil
}
// GetUncleByBlockNumberAndIndex returns the uncle identified by number and index. Always returns nil.
func (e *PublicEthAPI) GetUncleByBlockNumberAndIndex(number hexutil.Uint, idx hexutil.Uint) map[string]interface{} {
return nil
}
// GetProof returns an account object with proof and any storage proofs
func (e *PublicEthAPI) GetProof(address common.Address, storageKeys []string, blockNumber types.BlockNumber) (*types.AccountResult, error) {
height := blockNumber.Int64()
e.logger.Debugln("eth_getProof", "address", address.Hex(), "keys", storageKeys, "number", height)
ctx := types.ContextWithHeight(height)
clientCtx := e.clientCtx.WithHeight(height)
// query storage proofs
storageProofs := make([]types.StorageResult, len(storageKeys))
for i, key := range storageKeys {
hexKey := common.HexToHash(key)
valueBz, proof, err := e.queryClient.GetProof(clientCtx, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
if err != nil {
return nil, err
}
// check for proof
var proofStr string
if proof != nil {
proofStr = proof.String()
}
storageProofs[i] = types.StorageResult{
Key: key,
Value: (*hexutil.Big)(new(big.Int).SetBytes(valueBz)),
Proof: []string{proofStr},
}
}
// query EVM account
req := &evmtypes.QueryAccountRequest{
Address: address.String(),
}
res, err := e.queryClient.Account(ctx, req)
if err != nil {
return nil, err
}
// query account proofs
accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes()))
_, proof, err := e.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey)
if err != nil {
return nil, err
}
// check for proof
var accProofStr string
if proof != nil {
accProofStr = proof.String()
}
balance, err := ethermint.UnmarshalBigInt(res.Balance)
if err != nil {
return nil, err
}
return &types.AccountResult{
Address: address,
AccountProof: []string{accProofStr},
Balance: (*hexutil.Big)(balance),
CodeHash: common.BytesToHash(res.CodeHash),
Nonce: hexutil.Uint64(res.Nonce),
StorageHash: common.Hash{}, // NOTE: Ethermint doesn't have a storage hash. TODO: implement?
StorageProof: storageProofs,
}, nil
}

View File

@ -1,4 +1,4 @@
package filters
package rpc
import (
"context"
@ -6,9 +6,12 @@ import (
"sync"
"time"
"github.com/gogo/protobuf/proto"
"github.com/cosmos/ethermint/ethereum/rpc/types"
log "github.com/xlab/suplog"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/ethereum/go-ethereum/common"
@ -16,18 +19,16 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
"github.com/cosmos/cosmos-sdk/client"
rpctypes "github.com/cosmos/ethermint/rpc/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
// Backend defines the methods requided by the PublicFilterAPI backend
type Backend interface {
GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
HeaderByNumber(blockNr rpctypes.BlockNumber) (*ethtypes.Header, error)
// FiltersBackend defines the methods requided by the PublicFilterAPI backend
type FiltersBackend interface {
GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error)
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
BloomStatus() (uint64, uint64)
@ -50,20 +51,18 @@ type filter struct {
// PublicFilterAPI offers support to create and manage filters. This will allow external clients to retrieve various
// information related to the Ethereum protocol such as blocks, transactions and logs.
type PublicFilterAPI struct {
clientCtx client.Context
backend Backend
backend FiltersBackend
events *EventSystem
filtersMu sync.Mutex
filters map[rpc.ID]*filter
}
// NewAPI returns a new PublicFilterAPI instance.
func NewAPI(clientCtx client.Context, backend Backend) *PublicFilterAPI {
// NewPublicFilterAPI returns a new PublicFilterAPI instance.
func NewPublicFilterAPI(tmWSClient *rpcclient.WSClient, backend FiltersBackend) *PublicFilterAPI {
api := &PublicFilterAPI{
clientCtx: clientCtx,
backend: backend,
filters: make(map[rpc.ID]*filter),
events: NewEventSystem(clientCtx.Client),
events: NewEventSystem(tmWSClient),
}
go api.timeoutLoop()
@ -116,8 +115,22 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
for {
select {
case ev := <-txsCh:
data, _ := ev.Data.(tmtypes.EventDataTx)
case ev, ok := <-txsCh:
if !ok {
api.filtersMu.Lock()
delete(api.filters, pendingTxSub.ID())
api.filtersMu.Unlock()
return
}
data, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataTx",
"actual": fmt.Sprintf("%T", ev.Data),
}).Warningln("event Data type mismatch")
continue
}
txHash := common.BytesToHash(tmtypes.Tx(data.Tx).Hash())
@ -162,8 +175,23 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
for {
select {
case ev := <-txsCh:
data, _ := ev.Data.(tmtypes.EventDataTx)
case ev, ok := <-txsCh:
if !ok {
api.filtersMu.Lock()
delete(api.filters, pendingTxSub.ID())
api.filtersMu.Unlock()
return
}
data, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataTx",
"actual": fmt.Sprintf("%T", ev.Data),
}).Warningln("event Data type mismatch")
continue
}
txHash := common.BytesToHash(tmtypes.Tx(data.Tx).Hash())
// To keep the original behaviour, send a single tx hash in one notification.
@ -205,9 +233,24 @@ func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
for {
select {
case ev := <-headersCh:
data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
header := rpctypes.EthHeaderFromTendermint(data.Header)
case ev, ok := <-headersCh:
if !ok {
api.filtersMu.Lock()
delete(api.filters, headerSub.ID())
api.filtersMu.Unlock()
return
}
data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataNewBlockHeader",
"actual": fmt.Sprintf("%T", ev.Data),
}).Warningln("event Data type mismatch")
continue
}
header := EthHeaderFromTendermint(data.Header)
api.filtersMu.Lock()
if f, found := api.filters[headerSub.ID()]; found {
f.hashes = append(f.hashes, header.Hash())
@ -245,15 +288,22 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er
for {
select {
case ev := <-headersCh:
data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
case ev, ok := <-headersCh:
if !ok {
err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventNewBlockHeader)
headersSub.err <- err
headersSub.Unsubscribe(api.events)
return
}
header := rpctypes.EthHeaderFromTendermint(data.Header)
data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataNewBlockHeader",
"actual": fmt.Sprintf("%T", ev.Data),
}).Warningln("event Data type mismatch")
continue
}
header := EthHeaderFromTendermint(data.Header)
err = notifier.Notify(rpcSub.ID, header)
if err != nil {
headersSub.err <- err
@ -292,9 +342,14 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
for {
select {
case event := <-logsCh:
case ev, ok := <-logsCh:
if !ok {
logsSub.Unsubscribe(api.events)
return
}
// filter only events from EVM module txs
_, isMsgEthereumTx := event.Events[evmtypes.TypeMsgEthereumTx]
_, isMsgEthereumTx := ev.Events[evmtypes.TypeMsgEthereumTx]
if !isMsgEthereumTx {
// ignore transaction as it's not from the evm module
@ -302,20 +357,21 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
}
// get transaction result data
dataTx, ok := event.Data.(tmtypes.EventDataTx)
dataTx, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
err = fmt.Errorf("invalid event data %T, expected %s", event.Data, tmtypes.EventTx)
logsSub.err <- err
return
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataTx",
"actual": fmt.Sprintf("%T", ev.Data),
}).Warningln("event Data type mismatch")
continue
}
var txResponse evmtypes.MsgEthereumTxResponse
err := proto.Unmarshal(dataTx.TxResult.Result.Data, &txResponse)
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
return
}
logs := FilterLogs(txResponse.TxLogs.EthLogs(), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
logs := filterLogs(txResponse.TxLogs.EthLogs(), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
for _, log := range logs {
err = notifier.Notify(rpcSub.ID, log)
@ -371,19 +427,28 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
for {
select {
case event := <-eventCh:
dataTx, ok := event.Data.(tmtypes.EventDataTx)
case ev, ok := <-eventCh:
if !ok {
err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data)
api.filtersMu.Lock()
delete(api.filters, filterID)
api.filtersMu.Unlock()
return
}
dataTx, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataTx",
"actual": fmt.Sprintf("%T", ev.Data),
}).Warningln("event Data type mismatch")
continue
}
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
return
}
logs := FilterLogs(txResponse.TxLogs.EthLogs(), criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics)
logs := filterLogs(txResponse.TxLogs.EthLogs(), criteria.FromBlock, criteria.ToBlock, criteria.Addresses, criteria.Topics)
api.filtersMu.Lock()
if f, found := api.filters[filterID]; found {
@ -404,7 +469,7 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
// GetLogs returns logs matching the given argument that are stored within the state.
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getLogs
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getlogs
func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) {
var filter *Filter
if crit.BlockHash != nil {
@ -430,7 +495,7 @@ func (api *PublicFilterAPI) GetLogs(ctx context.Context, crit filters.FilterCrit
return nil, err
}
return returnLogs(logs), nil
return returnLogs(logs), err
}
// UninstallFilter removes the filter with the given filter id.
@ -530,3 +595,21 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ)
}
}
// returnHashes is a helper that will return an empty hash array case the given hash array is nil,
// otherwise the given hashes array is returned.
func returnHashes(hashes []common.Hash) []common.Hash {
if hashes == nil {
return []common.Hash{}
}
return hashes
}
// returnLogs is a helper that will return an empty log array in case the given logs array is nil,
// otherwise the given logs array is returned.
func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log {
if logs == nil {
return []*ethtypes.Log{}
}
return logs
}

View File

@ -0,0 +1,345 @@
package rpc
import (
"context"
"fmt"
"sync"
"time"
"github.com/pkg/errors"
log "github.com/xlab/suplog"
tmjson "github.com/tendermint/tendermint/libs/json"
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/ethereum/rpc/pubsub"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
var (
txEvents = tmtypes.QueryForEvent(tmtypes.EventTx).String()
evmEvents = tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName)).String()
headerEvents = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String()
)
// EventSystem creates subscriptions, processes events and broadcasts them to the
// subscription which match the subscription criteria using the Tendermint's RPC client.
type EventSystem struct {
ctx context.Context
tmWSClient *rpcclient.WSClient
// light client mode
lightMode bool
index filterIndex
topicChans map[string]chan<- coretypes.ResultEvent
indexMux *sync.RWMutex
// Channels
install chan *Subscription // install filter for event notification
uninstall chan *Subscription // remove filter for event notification
eventBus pubsub.EventBus
}
// NewEventSystem creates a new manager that listens for event on the given mux,
// parses and filters them. It uses the all map to retrieve filter changes. The
// work loop holds its own index that is used to forward events to filters.
//
// The returned manager has a loop that needs to be stopped with the Stop function
// or by stopping the given mux.
func NewEventSystem(tmWSClient *rpcclient.WSClient) *EventSystem {
index := make(filterIndex)
for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ {
index[i] = make(map[rpc.ID]*Subscription)
}
es := &EventSystem{
ctx: context.Background(),
tmWSClient: tmWSClient,
lightMode: false,
index: index,
topicChans: make(map[string]chan<- coretypes.ResultEvent, len(index)),
indexMux: new(sync.RWMutex),
install: make(chan *Subscription),
uninstall: make(chan *Subscription),
eventBus: pubsub.NewEventBus(),
}
go es.eventLoop()
go es.consumeEvents()
return es
}
// WithContext sets a new context to the EventSystem. This is required to set a timeout context when
// a new filter is intantiated.
func (es *EventSystem) WithContext(ctx context.Context) {
es.ctx = ctx
}
// subscribe performs a new event subscription to a given Tendermint event.
// The subscription creates a unidirectional receive event channel to receive the ResultEvent.
func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.CancelFunc, error) {
var (
err error
cancelFn context.CancelFunc
)
es.ctx, cancelFn = context.WithCancel(context.Background())
existingSubs := es.eventBus.Topics()
for _, topic := range existingSubs {
if topic == sub.event {
eventCh, err := es.eventBus.Subscribe(sub.event)
if err != nil {
err := errors.Wrapf(err, "failed to subscribe to topic: %s", sub.event)
return nil, cancelFn, err
}
sub.eventCh = eventCh
return sub, cancelFn, nil
}
}
switch sub.typ {
case filters.LogsSubscription:
err = es.tmWSClient.Subscribe(es.ctx, sub.event)
case filters.BlocksSubscription:
err = es.tmWSClient.Subscribe(es.ctx, sub.event)
default:
err = fmt.Errorf("invalid filter subscription type %d", sub.typ)
}
if err != nil {
sub.err <- err
return nil, cancelFn, err
}
// wrap events in a go routine to prevent blocking
es.install <- sub
<-sub.installed
eventCh, err := es.eventBus.Subscribe(sub.event)
if err != nil {
err := errors.Wrapf(err, "failed to subscribe to topic after installed: %s", sub.event)
return sub, cancelFn, err
}
sub.eventCh = eventCh
return sub, cancelFn, nil
}
// SubscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel. Default value for the from and to
// block is "latest". If the fromBlock > toBlock an error is returned.
func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
var from, to rpc.BlockNumber
if crit.FromBlock == nil {
from = rpc.LatestBlockNumber
} else {
from = rpc.BlockNumber(crit.FromBlock.Int64())
}
if crit.ToBlock == nil {
to = rpc.LatestBlockNumber
} else {
to = rpc.BlockNumber(crit.ToBlock.Int64())
}
switch {
// only interested in new mined logs, mined logs within a specific block range, or
// logs from a specific block number to new mined blocks
case (from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber),
(from >= 0 && to >= 0 && to >= from):
return es.subscribeLogs(crit)
default:
return nil, nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to)
}
}
// subscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel.
func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.LogsSubscription,
event: evmEvents,
logsCrit: crit,
created: time.Now().UTC(),
logs: make(chan []*ethtypes.Log),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
// SubscribeNewHeads subscribes to new block headers events.
func (es EventSystem) SubscribeNewHeads() (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.BlocksSubscription,
event: headerEvents,
created: time.Now().UTC(),
headers: make(chan *ethtypes.Header),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
// SubscribePendingTxs subscribes to new pending transactions events from the mempool.
func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.PendingTransactionsSubscription,
event: txEvents,
created: time.Now().UTC(),
hashes: make(chan []common.Hash),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
type filterIndex map[filters.Type]map[rpc.ID]*Subscription
// eventLoop (un)installs filters and processes mux events.
func (es *EventSystem) eventLoop() {
for {
select {
case f := <-es.install:
es.indexMux.Lock()
es.index[f.typ][f.id] = f
ch := make(chan coretypes.ResultEvent)
es.topicChans[f.event] = ch
if err := es.eventBus.AddTopic(f.event, ch); err != nil {
log.WithField("topic", f.event).WithError(err).Errorln("failed to add event topic to event bus")
}
es.indexMux.Unlock()
close(f.installed)
case f := <-es.uninstall:
es.indexMux.Lock()
delete(es.index[f.typ], f.id)
var channelInUse bool
for _, sub := range es.index[f.typ] {
if sub.event == f.event {
channelInUse = true
break
}
}
// remove topic only when channel is not used by other subscriptions
if !channelInUse {
if err := es.tmWSClient.Unsubscribe(es.ctx, f.event); err != nil {
log.WithError(err).WithField("query", f.event).Errorln("failed to unsubscribe from query")
}
ch, ok := es.topicChans[f.event]
if ok {
es.eventBus.RemoveTopic(f.event)
close(ch)
delete(es.topicChans, f.event)
}
}
es.indexMux.Unlock()
close(f.err)
}
}
}
func (es *EventSystem) consumeEvents() {
for {
for rpcResp := range es.tmWSClient.ResponsesCh {
var ev coretypes.ResultEvent
if rpcResp.Error != nil {
time.Sleep(5 * time.Second)
continue
} else if err := tmjson.Unmarshal(rpcResp.Result, &ev); err != nil {
log.WithError(err).Warningln("failed to JSON unmarshal ResponsesCh result event")
continue
}
if len(ev.Query) == 0 {
// skip empty responses
continue
}
es.indexMux.RLock()
ch, ok := es.topicChans[ev.Query]
es.indexMux.RUnlock()
if !ok {
log.WithField("topic", ev.Query).Warningln("channel for subscription not found, lol")
log.Infoln("available channels:", es.eventBus.Topics())
continue
}
// gracefully handle lagging subscribers
t := time.NewTimer(time.Second)
select {
case <-t.C:
log.WithField("topic", ev.Query).Warningln("dropped event during lagging subscription")
case ch <- ev:
}
}
time.Sleep(time.Second)
}
}
// Subscription defines a wrapper for the private subscription
type Subscription struct {
id rpc.ID
typ filters.Type
event string
created time.Time
logsCrit filters.FilterCriteria
logs chan []*ethtypes.Log
hashes chan []common.Hash
headers chan *ethtypes.Header
installed chan struct{} // closed when the filter is installed
eventCh <-chan coretypes.ResultEvent
err chan error
}
// ID returns the underlying subscription RPC identifier.
func (s Subscription) ID() rpc.ID {
return s.id
}
// Unsubscribe from the current subscription to Tendermint Websocket. It sends an error to the
// subscription error channel if unsubscription fails.
func (s *Subscription) Unsubscribe(es *EventSystem) {
go func() {
uninstallLoop:
for {
// write uninstall request and consume logs/hashes. This prevents
// the eventLoop broadcast method to deadlock when writing to the
// filter event channel while the subscription loop is waiting for
// this method to return (and thus not reading these events).
select {
case es.uninstall <- s:
break uninstallLoop
case <-s.logs:
case <-s.hashes:
case <-s.headers:
}
}
}()
}
// Err returns the error channel
func (s *Subscription) Err() <-chan error {
return s.err
}

302
ethereum/rpc/filters.go Normal file
View File

@ -0,0 +1,302 @@
package rpc
import (
"context"
"fmt"
"math/big"
"github.com/cosmos/ethermint/ethereum/rpc/types"
"github.com/pkg/errors"
log "github.com/xlab/suplog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/bloombits"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters"
)
// Filter can be used to retrieve and filter logs.
type Filter struct {
backend FiltersBackend
criteria filters.FilterCriteria
matcher *bloombits.Matcher
}
// NewBlockFilter creates a new filter which directly inspects the contents of
// a block to figure out whether it is interesting or not.
func NewBlockFilter(backend FiltersBackend, criteria filters.FilterCriteria) *Filter {
// Create a generic filter and convert it into a block filter
return newFilter(backend, criteria, nil)
}
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
// figure out whether a particular block is interesting or not.
func NewRangeFilter(backend FiltersBackend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
// Flatten the address and topic filter clauses into a single bloombits filter
// system. Since the bloombits are not positional, nil topics are permitted,
// which get flattened into a nil byte slice.
var filtersBz [][][]byte // nolint: prealloc
if len(addresses) > 0 {
filter := make([][]byte, len(addresses))
for i, address := range addresses {
filter[i] = address.Bytes()
}
filtersBz = append(filtersBz, filter)
}
for _, topicList := range topics {
filter := make([][]byte, len(topicList))
for i, topic := range topicList {
filter[i] = topic.Bytes()
}
filtersBz = append(filtersBz, filter)
}
size, _ := backend.BloomStatus()
// Create a generic filter and convert it into a range filter
criteria := filters.FilterCriteria{
FromBlock: big.NewInt(begin),
ToBlock: big.NewInt(end),
Addresses: addresses,
Topics: topics,
}
return newFilter(backend, criteria, bloombits.NewMatcher(size, filtersBz))
}
// newFilter returns a new Filter
func newFilter(backend FiltersBackend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter {
return &Filter{
backend: backend,
criteria: criteria,
matcher: matcher,
}
}
const (
maxFilterBlocks = 100000
maxToOverhang = 600
)
// Logs searches the blockchain for matching log entries, returning all from the
// first block that contains matches, updating the start of the filter accordingly.
func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
var err error
// If we're doing singleton block filtering, execute and return
if f.criteria.BlockHash != nil && *f.criteria.BlockHash != (common.Hash{}) {
header, err := f.backend.HeaderByHash(*f.criteria.BlockHash)
if err != nil {
err = errors.Wrap(err, "failed to fetch header by hash")
return nil, err
}
if header == nil {
err := errors.Errorf("unknown block header %s", f.criteria.BlockHash.String())
return nil, err
}
return f.blockLogs(header)
}
// Figure out the limits of the filter range
header, err := f.backend.HeaderByNumber(types.EthLatestBlockNumber)
if err != nil {
err = errors.Wrap(err, "failed to fetch header by number (latest)")
return nil, err
}
if header == nil || header.Number == nil {
log.Warningln("header not found or has no number")
return nil, nil
}
head := header.Number.Int64()
if f.criteria.FromBlock.Int64() == -1 {
f.criteria.FromBlock = big.NewInt(head)
}
if f.criteria.ToBlock.Int64() == -1 {
f.criteria.ToBlock = big.NewInt(head)
}
if f.criteria.ToBlock.Int64()-f.criteria.FromBlock.Int64() > maxFilterBlocks {
err := errors.Errorf("maximum [from, to] blocks distance: %d", maxFilterBlocks)
return nil, err
}
// check bounds
if f.criteria.FromBlock.Int64() > head {
return []*ethtypes.Log{}, nil
} else if f.criteria.ToBlock.Int64() > head+maxToOverhang {
f.criteria.ToBlock = big.NewInt(head + maxToOverhang)
}
for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ {
block, err := f.backend.GetBlockByNumber(types.BlockNumber(i), false)
if err != nil {
err = errors.Wrapf(err, "failed to fetch block by number %d", i)
return logs, err
} else if block["transactions"] == nil {
continue
}
var txHashes []common.Hash
txs, ok := block["transactions"].([]interface{})
if !ok {
txHashes, ok = block["transactions"].([]common.Hash)
if !ok {
log.WithField(
"transactions",
fmt.Sprintf("%T", block["transactions"]),
).Errorln("reading transactions from block data: bad field type")
continue
}
} else if len(txs) == 0 && len(txHashes) == 0 {
continue
}
for _, tx := range txs {
txHash, ok := tx.(common.Hash)
if !ok {
log.WithField(
"tx",
fmt.Sprintf("%T", tx),
).Errorln("transactions list contains non-hash element")
} else {
txHashes = append(txHashes, txHash)
}
}
logsMatched := f.checkMatches(txHashes)
logs = append(logs, logsMatched...)
}
return logs, nil
}
// blockLogs returns the logs matching the filter criteria within a single block.
func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) {
if !bloomFilter(header.Bloom, f.criteria.Addresses, f.criteria.Topics) {
return []*ethtypes.Log{}, nil
}
// DANGER: do not call GetLogs(header.Hash())
// eth header's hash doesn't match tm block hash
logsList, err := f.backend.GetLogsByNumber(types.BlockNumber(header.Number.Int64()))
if err != nil {
err = errors.Wrapf(err, "failed to fetch logs block number %d", header.Number.Int64())
return []*ethtypes.Log{}, err
}
var unfiltered []*ethtypes.Log // nolint: prealloc
for _, logs := range logsList {
unfiltered = append(unfiltered, logs...)
}
logs := filterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics)
if len(logs) == 0 {
return []*ethtypes.Log{}, nil
}
return logs, nil
}
// checkMatches checks if the logs from the a list of transactions transaction
// contain any log events that match the filter criteria. This function is
// called when the bloom filter signals a potential match.
func (f *Filter) checkMatches(transactions []common.Hash) []*ethtypes.Log {
unfiltered := []*ethtypes.Log{}
for _, tx := range transactions {
logs, err := f.backend.GetTransactionLogs(tx)
if err != nil {
// ignore error if transaction didn't set any logs (eg: when tx type is not
// MsgEthereumTx or MsgEthermint)
continue
}
unfiltered = append(unfiltered, logs...)
}
return filterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics)
}
// filterLogs creates a slice of logs matching the given criteria.
// [] -> anything
// [A] -> A in first position of log topics, anything after
// [null, B] -> anything in first position, B in second position
// [A, B] -> A in first position and B in second position
// [[A, B], [A, B]] -> A or B in first position, A or B in second position
func filterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log {
var ret []*ethtypes.Log
Logs:
for _, log := range logs {
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
continue
}
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
continue
}
if len(addresses) > 0 && !includes(addresses, log.Address) {
continue
}
// If the to filtered topics is greater than the amount of topics in logs, skip.
if len(topics) > len(log.Topics) {
continue
}
for i, sub := range topics {
match := len(sub) == 0 // empty rule set == wildcard
for _, topic := range sub {
if log.Topics[i] == topic {
match = true
break
}
}
if !match {
continue Logs
}
}
ret = append(ret, log)
}
return ret
}
func includes(addresses []common.Address, a common.Address) bool {
for _, addr := range addresses {
if addr == a {
return true
}
}
return false
}
func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
var included bool
if len(addresses) > 0 {
for _, addr := range addresses {
if ethtypes.BloomLookup(bloom, addr) {
included = true
break
}
}
if !included {
return false
}
}
for _, sub := range topics {
included = len(sub) == 0 // empty rule set == wildcard
for _, topic := range sub {
if ethtypes.BloomLookup(bloom, topic) {
included = true
break
}
}
}
return included
}

View File

@ -1,11 +1,14 @@
package net
package rpc
import (
"fmt"
"github.com/cosmos/cosmos-sdk/client"
"github.com/spf13/viper"
ethermint "github.com/cosmos/ethermint/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
)
// PublicNetAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
@ -13,10 +16,11 @@ type PublicNetAPI struct {
networkVersion uint64
}
// NewAPI creates an instance of the public Net Web3 API.
func NewAPI(clientCtx client.Context) *PublicNetAPI {
// NewPublicNetAPI creates an instance of the public Net Web3 API.
func NewPublicNetAPI(_ client.Context) *PublicNetAPI {
chainID := viper.GetString(flags.FlagChainID)
// parse the chainID from a integer string
chainIDEpoch, err := ethermint.ParseChainID(clientCtx.ChainID)
chainIDEpoch, err := ethermint.ParseChainID(chainID)
if err != nil {
panic(err)
}
@ -27,6 +31,6 @@ func NewAPI(clientCtx client.Context) *PublicNetAPI {
}
// Version returns the current ethereum protocol version.
func (api *PublicNetAPI) Version() string {
return fmt.Sprintf("%d", api.networkVersion)
func (s *PublicNetAPI) Version() string {
return fmt.Sprintf("%d", s.networkVersion)
}

View File

@ -0,0 +1,128 @@
package pubsub
import (
"sync"
"github.com/pkg/errors"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
)
type EventBus interface {
AddTopic(name string, src <-chan coretypes.ResultEvent) error
RemoveTopic(name string)
Subscribe(name string) (<-chan coretypes.ResultEvent, error)
Topics() []string
}
type memEventBus struct {
topics map[string]<-chan coretypes.ResultEvent
topicsMux *sync.RWMutex
subscribers map[string][]chan<- coretypes.ResultEvent
subscribersMux *sync.RWMutex
}
func NewEventBus() EventBus {
return &memEventBus{
topics: make(map[string]<-chan coretypes.ResultEvent),
topicsMux: new(sync.RWMutex),
subscribers: make(map[string][]chan<- coretypes.ResultEvent),
subscribersMux: new(sync.RWMutex),
}
}
func (m *memEventBus) Topics() (topics []string) {
m.topicsMux.RLock()
defer m.topicsMux.RUnlock()
topics = make([]string, 0, len(m.topics))
for topicName := range m.topics {
topics = append(topics, topicName)
}
return topics
}
func (m *memEventBus) AddTopic(name string, src <-chan coretypes.ResultEvent) error {
m.topicsMux.RLock()
_, ok := m.topics[name]
m.topicsMux.RUnlock()
if ok {
return errors.New("topic already registered")
}
m.topicsMux.Lock()
m.topics[name] = src
m.topicsMux.Unlock()
go m.publishTopic(name, src)
return nil
}
func (m *memEventBus) RemoveTopic(name string) {
m.topicsMux.Lock()
delete(m.topics, name)
m.topicsMux.Unlock()
}
func (m *memEventBus) Subscribe(name string) (<-chan coretypes.ResultEvent, error) {
m.topicsMux.RLock()
_, ok := m.topics[name]
m.topicsMux.RUnlock()
if !ok {
return nil, errors.Errorf("topic not found: %s", name)
}
ch := make(chan coretypes.ResultEvent)
m.subscribersMux.Lock()
defer m.subscribersMux.Unlock()
m.subscribers[name] = append(m.subscribers[name], ch)
return ch, nil
}
func (m *memEventBus) publishTopic(name string, src <-chan coretypes.ResultEvent) {
for {
select {
case msg, ok := <-src:
if !ok {
m.closeAllSubscribers(name)
m.topicsMux.Lock()
delete(m.topics, name)
m.topicsMux.Unlock()
return
}
m.publishAllSubscribers(name, msg)
}
}
}
func (m *memEventBus) closeAllSubscribers(name string) {
m.subscribersMux.Lock()
defer m.subscribersMux.Unlock()
subsribers := m.subscribers[name]
delete(m.subscribers, name)
for _, sub := range subsribers {
close(sub)
}
}
func (m *memEventBus) publishAllSubscribers(name string, msg coretypes.ResultEvent) {
m.subscribersMux.RLock()
subsribers := m.subscribers[name]
m.subscribersMux.RUnlock()
for _, sub := range subsribers {
select {
case sub <- msg:
default:
}
}
}

View File

@ -0,0 +1,96 @@
package pubsub
import (
"log"
"sync"
"testing"
"time"
"github.com/stretchr/testify/assert"
)
func TestAddTopic(t *testing.T) {
assert := assert.New(t)
q := NewEventBus()
err := q.AddTopic("kek", make(chan interface{}))
if !assert.NoError(err) {
return
}
err = q.AddTopic("lol", make(chan interface{}))
if !assert.NoError(err) {
return
}
err = q.AddTopic("lol", make(chan interface{}))
if !assert.Error(err) {
return
}
assert.EqualValues([]string{"kek", "lol"}, q.Topics())
}
func TestSubscribe(t *testing.T) {
assert := assert.New(t)
q := NewEventBus()
kekSrc := make(chan interface{})
q.AddTopic("kek", kekSrc)
lolSrc := make(chan interface{})
q.AddTopic("lol", lolSrc)
kekSubC, err := q.Subscribe("kek")
if !assert.NoError(err) {
return
}
lolSubC, err := q.Subscribe("lol")
if !assert.NoError(err) {
return
}
lol2SubC, err := q.Subscribe("lol")
if !assert.NoError(err) {
return
}
wg := new(sync.WaitGroup)
wg.Add(4)
go func() {
defer wg.Done()
msg := <-kekSubC
log.Println("kek:", msg)
assert.EqualValues(1, msg)
}()
go func() {
defer wg.Done()
msg := <-lolSubC
log.Println("lol:", msg)
assert.EqualValues(1, msg)
}()
go func() {
defer wg.Done()
msg := <-lol2SubC
log.Println("lol2:", msg)
assert.EqualValues(1, msg)
}()
go func() {
defer wg.Done()
time.Sleep(time.Second)
kekSrc <- 1
lolSrc <- 1
close(kekSrc)
close(lolSrc)
}()
wg.Wait()
time.Sleep(time.Second)
}

View File

@ -7,9 +7,9 @@ import (
"math/big"
"strings"
"google.golang.org/grpc/metadata"
grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/spf13/cast"
"google.golang.org/grpc/metadata"
"github.com/ethereum/go-ethereum/common/hexutil"
)
@ -18,14 +18,9 @@ import (
type BlockNumber int64
const (
// LatestBlockNumber mapping from "latest" to 0 for tm query
LatestBlockNumber = BlockNumber(0)
// EarliestBlockNumber mapping from "earliest" to 1 for tm query (earliest query not supported)
EarliestBlockNumber = BlockNumber(1)
// PendingBlockNumber mapping from "pending" to -1 for tm query
PendingBlockNumber = BlockNumber(-1)
EthPendingBlockNumber = BlockNumber(-2)
EthLatestBlockNumber = BlockNumber(-1)
EthEarliestBlockNumber = BlockNumber(0)
)
// NewBlockNumber creates a new BlockNumber instance.
@ -34,9 +29,9 @@ func NewBlockNumber(n *big.Int) BlockNumber {
}
// ContextWithHeight wraps a context with the a gRPC block height header. If the provided height is
// 0 or -1, it will return an empty context and the gRPC query will use the latest block height for querying.
// 0, it will return an empty context and the gRPC query will use the latest block height for querying.
func ContextWithHeight(height int64) context.Context {
if height == LatestBlockNumber.Int64() || height == PendingBlockNumber.Int64() {
if height == 0 {
return context.Background()
}
@ -57,30 +52,39 @@ func (bn *BlockNumber) UnmarshalJSON(data []byte) error {
switch input {
case "earliest":
*bn = EarliestBlockNumber
*bn = EthEarliestBlockNumber
return nil
case "latest":
*bn = LatestBlockNumber
*bn = EthLatestBlockNumber
return nil
case "pending":
*bn = PendingBlockNumber
*bn = EthPendingBlockNumber
return nil
}
blckNum, err := hexutil.DecodeUint64(input)
if err != nil {
if err == hexutil.ErrMissingPrefix {
blckNum = cast.ToUint64(input)
} else if err != nil {
return err
}
if blckNum > math.MaxInt64 {
return fmt.Errorf("blocknumber too high")
}
if blckNum > math.MaxInt64 {
return fmt.Errorf("block number larger than int64")
}
*bn = BlockNumber(blckNum)
return nil
}
// Int64 converts block number to primitive type
func (bn BlockNumber) Int64() int64 {
if bn < 0 {
return 0
} else if bn == 0 {
return 1
}
return int64(bn)
}
@ -88,9 +92,13 @@ func (bn BlockNumber) Int64() int64 {
// nil if the block number is "latest". Otherwise, it returns the pointer of the
// int64 value of the height.
func (bn BlockNumber) TmHeight() *int64 {
if bn == LatestBlockNumber {
if bn < 0 {
return nil
} else if bn == EthEarliestBlockNumber {
var firstHeight int64 = 0
return &firstHeight
}
height := bn.Int64()
return &height
}

View File

@ -3,11 +3,12 @@ package types
import (
"fmt"
"github.com/cosmos/cosmos-sdk/types/tx"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/proto/tendermint/crypto"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/types/tx"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)

View File

@ -70,9 +70,9 @@ type CallArgs struct {
Data *hexutil.Bytes `json:"data"`
}
// Account indicates the overriding fields of account during the execution of
// account indicates the overriding fields of account during the execution of
// a message call.
// NOTE: state and stateDiff can't be specified at the same time. If state is
// Note, state and stateDiff can't be specified at the same time. If state is
// set, message execution will only use the data in the given state. Otherwise
// if statDiff is set, all diff will be applied first and then execute the call
// message.

View File

@ -17,6 +17,7 @@ import (
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
ethermint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/ethereum/go-ethereum/common"
@ -47,15 +48,20 @@ func NewTransaction(tx *evmtypes.MsgEthereumTx, txHash, blockHash common.Hash, b
return nil, err
}
gasPrice := new(big.Int).SetBytes(tx.Data.Price)
value := new(big.Int).SetBytes(tx.Data.Amount)
rpcTx := &Transaction{
From: from,
Gas: hexutil.Uint64(tx.Data.GasLimit),
GasPrice: (*hexutil.Big)(tx.Data.Price.BigInt()),
GasPrice: (*hexutil.Big)(gasPrice),
//GasPrice: (*hexutil.Big)(tx.Data.Price.BigInt()),
Hash: txHash,
Input: hexutil.Bytes(tx.Data.Payload),
Nonce: hexutil.Uint64(tx.Data.AccountNonce),
To: tx.To(),
Value: (*hexutil.Big)(tx.Data.Amount.BigInt()),
Value: (*hexutil.Big)(value),
//Value: (*hexutil.Big)(tx.Data.Amount.BigInt()),
V: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.V)),
R: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.R)),
S: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.S)),
@ -84,15 +90,14 @@ func EthBlockFromTendermint(clientCtx client.Context, queryClient *QueryClient,
req := &evmtypes.QueryBlockBloomRequest{}
// use height 0 for querying the latest block height
res, err := queryClient.BlockBloom(ContextWithHeight(0), req)
res, err := queryClient.BlockBloom(ContextWithHeight(block.Height), req)
if err != nil {
return nil, err
}
bloom := ethtypes.BytesToBloom(res.Bloom)
return FormatBlock(block.Header, block.Size(), block.Hash(), gasLimit, gasUsed, transactions, bloom), nil
return FormatBlock(block.Header, block.Size(), gasLimit, gasUsed, transactions, bloom), nil
}
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
@ -127,7 +132,7 @@ func EthTransactionsFromTendermint(clientCtx client.Context, txs []tmtypes.Tx) (
continue
}
// TODO: Remove gas usage calculation if saving gasUsed per block
gasUsed.Add(gasUsed, big.NewInt(int64(ethTx.GetGas())))
gasUsed.Add(gasUsed, ethTx.Fee())
transactionHashes = append(transactionHashes, common.BytesToHash(tx.Hash()))
}
@ -155,7 +160,7 @@ func BlockMaxGasFromConsensusParams(ctx context.Context, clientCtx client.Contex
// FormatBlock creates an ethereum block from a tendermint header and ethereum-formatted
// transactions.
func FormatBlock(
header tmtypes.Header, size int, curBlockHash tmbytes.HexBytes, gasLimit int64,
header tmtypes.Header, size int, gasLimit int64,
gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom,
) map[string]interface{} {
if len(header.DataHash) == 0 {
@ -164,7 +169,7 @@ func FormatBlock(
return map[string]interface{}{
"number": hexutil.Uint64(header.Height),
"hash": hexutil.Bytes(curBlockHash),
"hash": hexutil.Bytes(header.Hash()),
"parentHash": hexutil.Bytes(header.LastBlockID.Hash),
"nonce": hexutil.Uint64(0), // PoW specific
"sha3Uncles": common.Hash{}, // No uncles in Tendermint
@ -197,13 +202,9 @@ func GetKeyByAddress(keys []ethsecp256k1.PrivKey, address common.Address) (key *
}
// BuildEthereumTx builds and signs a Cosmos transaction from a MsgEthereumTx and returns the tx
func BuildEthereumTx(
clientCtx client.Context,
msgs []sdk.Msg,
accNumber, seq, gasLimit uint64,
fees sdk.Coins,
privKey cryptotypes.PrivKey,
) ([]byte, error) {
func BuildEthereumTx(clientCtx client.Context, msg *evmtypes.MsgEthereumTx, accNumber, seq uint64, privKey cryptotypes.PrivKey) ([]byte, error) {
// TODO: user defined evm coin
fees := sdk.NewCoins(ethermint.NewPhotonCoin(sdk.NewIntFromBigInt(msg.Fee())))
signMode := clientCtx.TxConfig.SignModeHandler().DefaultMode()
signerData := authsigning.SignerData{
ChainID: clientCtx.ChainID,
@ -213,12 +214,12 @@ func BuildEthereumTx(
// Create a TxBuilder
txBuilder := clientCtx.TxConfig.NewTxBuilder()
if err := txBuilder.SetMsgs(msgs...); err != nil {
if err := txBuilder.SetMsgs(msg); err != nil {
return nil, err
}
txBuilder.SetFeeAmount(fees)
txBuilder.SetGasLimit(gasLimit)
txBuilder.SetGasLimit(msg.GetGas())
// sign with the private key
sigV2, err := tx.SignWithPrivKey(
@ -259,6 +260,7 @@ func GetBlockCumulativeGas(clientCtx client.Context, block *tmtypes.Block, idx i
case *evmtypes.MsgEthereumTx:
gasUsed += tx.GetGas()
case sdk.FeeTx:
// TODO: add internal txns
gasUsed += tx.GetGas()
}
}

242
ethereum/rpc/utils.go Normal file
View File

@ -0,0 +1,242 @@
package rpc
import (
"context"
"encoding/hex"
"fmt"
"math/big"
"github.com/xlab/suplog"
tmbytes "github.com/tendermint/tendermint/libs/bytes"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/client"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
rpctypes "github.com/cosmos/ethermint/ethereum/rpc/types"
ethermint "github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
type DataError interface {
Error() string // returns the message
ErrorData() interface{} // returns the error data
}
type dataError struct {
msg string
data string
}
func (d *dataError) Error() string {
return d.msg
}
func (d *dataError) ErrorData() interface{} {
return d.data
}
type sdkTxLogs struct {
Log string `json:"log"`
}
const logRevertedFlag = "transaction reverted"
func errRevertedWith(data []byte) DataError {
return &dataError{
msg: "VM execution error.",
data: fmt.Sprintf("0x%s", hex.EncodeToString(data)),
}
}
// NewTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func NewTransaction(tx *evmtypes.MsgEthereumTx, txHash, blockHash common.Hash, blockNumber, index uint64) (*rpctypes.Transaction, error) {
// Verify signature and retrieve sender address
from, err := tx.VerifySig(tx.ChainID())
if err != nil {
return nil, err
}
rpcTx := &rpctypes.Transaction{
From: from,
Gas: hexutil.Uint64(tx.Data.GasLimit),
GasPrice: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.Price)),
Hash: txHash,
Input: hexutil.Bytes(tx.Data.Payload),
Nonce: hexutil.Uint64(tx.Data.AccountNonce),
To: tx.To(),
Value: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.Amount)),
V: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.V)),
R: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.R)),
S: (*hexutil.Big)(new(big.Int).SetBytes(tx.Data.S)),
}
if rpcTx.To == nil {
addr := common.HexToAddress("0x0000000000000000000000000000000000000000")
rpcTx.To = &addr
}
if blockHash != (common.Hash{}) {
rpcTx.BlockHash = &blockHash
rpcTx.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
rpcTx.TransactionIndex = (*hexutil.Uint64)(&index)
}
return rpcTx, nil
}
// NewTransaction returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func NewTransactionFromData(
txData *evmtypes.TxData,
from common.Address,
txHash, blockHash common.Hash,
blockNumber, index uint64,
) (*rpctypes.Transaction, error) {
var to *common.Address
if len(txData.Recipient) > 0 {
recipient := common.BytesToAddress(txData.Recipient)
to = &recipient
}
rpcTx := &rpctypes.Transaction{
From: from,
Gas: hexutil.Uint64(txData.GasLimit),
GasPrice: (*hexutil.Big)(new(big.Int).SetBytes(txData.Price)),
Hash: txHash,
Input: hexutil.Bytes(txData.Payload),
Nonce: hexutil.Uint64(txData.AccountNonce),
To: to,
Value: (*hexutil.Big)(new(big.Int).SetBytes(txData.Amount)),
V: (*hexutil.Big)(new(big.Int).SetBytes(txData.V)),
R: (*hexutil.Big)(new(big.Int).SetBytes(txData.R)),
S: (*hexutil.Big)(new(big.Int).SetBytes(txData.S)),
}
if rpcTx.To == nil {
addr := common.HexToAddress("0x0000000000000000000000000000000000000000")
rpcTx.To = &addr
}
if blockHash != (common.Hash{}) {
rpcTx.BlockHash = &blockHash
rpcTx.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
rpcTx.TransactionIndex = (*hexutil.Uint64)(&index)
}
return rpcTx, nil
}
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
// from a tendermint Header.
func EthHeaderFromTendermint(header tmtypes.Header) *ethtypes.Header {
return &ethtypes.Header{
ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
UncleHash: common.Hash{},
Coinbase: common.Address{},
Root: common.BytesToHash(header.AppHash),
TxHash: common.BytesToHash(header.DataHash),
ReceiptHash: common.Hash{},
Difficulty: nil,
Number: big.NewInt(header.Height),
Time: uint64(header.Time.Unix()),
Extra: nil,
MixDigest: common.Hash{},
Nonce: ethtypes.BlockNonce{},
}
}
// BlockMaxGasFromConsensusParams returns the gas limit for the latest block from the chain consensus params.
func BlockMaxGasFromConsensusParams(ctx context.Context, clientCtx client.Context, height *int64, logger suplog.Logger) (int64, error) {
return ethermint.DefaultRPCGasLimit, nil
}
var zeroHash = hexutil.Bytes(make([]byte, 32))
func hashOrZero(data []byte) hexutil.Bytes {
if len(data) == 0 {
return zeroHash
}
return hexutil.Bytes(data)
}
func bigOrZero(i *big.Int) *hexutil.Big {
if i == nil {
return new(hexutil.Big)
}
return (*hexutil.Big)(i)
}
func formatBlock(
header tmtypes.Header, size int, gasLimit int64,
gasUsed *big.Int, transactions interface{}, bloom ethtypes.Bloom,
) map[string]interface{} {
if len(header.DataHash) == 0 {
header.DataHash = tmbytes.HexBytes(common.Hash{}.Bytes())
}
var txRoot interface{}
txDescriptors, ok := transactions.([]interface{})
if !ok || len(txDescriptors) == 0 {
txRoot = ethtypes.EmptyRootHash
transactions = []common.Hash{}
} else {
txRoot = hashOrZero(header.DataHash)
}
ret := map[string]interface{}{
"parentHash": hashOrZero(header.LastBlockID.Hash),
"sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint
"miner": common.Address{},
"stateRoot": hashOrZero(header.AppHash),
"transactionsRoot": txRoot,
"receiptsRoot": zeroHash,
"logsBloom": hexutil.Encode(bloom.Bytes()),
"difficulty": new(hexutil.Big),
"number": hexutil.Uint64(header.Height),
"gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
"gasUsed": bigOrZero(gasUsed),
"timestamp": hexutil.Uint64(header.Time.Unix()),
"extraData": hexutil.Bytes([]byte{}),
"mixHash": zeroHash,
"hash": hashOrZero(header.Hash()),
"nonce": ethtypes.EncodeNonce(0),
"totalDifficulty": new(hexutil.Big),
"size": hexutil.Uint64(size),
"transactions": transactions,
"uncles": []string{},
}
return ret
}
// GetBlockCumulativeGas returns the cumulative gas used on a block up to a given
// transaction index. The returned gas used includes the gas from both the SDK and
// EVM module transactions.
func GetBlockCumulativeGas(clientCtx client.Context, block *tmtypes.Block, idx int) uint64 {
var gasUsed uint64
txDecoder := clientCtx.TxConfig.TxDecoder()
for i := 0; i < idx && i < len(block.Txs); i++ {
txi, err := txDecoder(block.Txs[i])
if err != nil {
continue
}
switch tx := txi.(type) {
case *evmtypes.MsgEthereumTx:
gasUsed += tx.GetGas()
case sdk.FeeTx:
gasUsed += tx.GetGas()
}
}
return gasUsed
}

View File

@ -1,29 +1,26 @@
package web3
package rpc
import (
"fmt"
"github.com/cosmos/ethermint/version"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/cosmos/cosmos-sdk/version"
)
// PublicWeb3API is the web3_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicWeb3API struct{}
// NewAPI creates an instance of the Web3 API.
func NewAPI() *PublicWeb3API {
// NewPublicWeb3API creates an instance of the Web3 API.
func NewPublicWeb3API() *PublicWeb3API {
return &PublicWeb3API{}
}
// ClientVersion returns the client version in the Web3 user agent format.
func (PublicWeb3API) ClientVersion() string {
info := version.NewInfo()
return fmt.Sprintf("%s-%s", info.Name, info.Version)
func (a *PublicWeb3API) ClientVersion() string {
return version.ClientVersion()
}
// Sha3 returns the keccak-256 hash of the passed-in input.
func (PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
func (a *PublicWeb3API) Sha3(input hexutil.Bytes) hexutil.Bytes {
return crypto.Keccak256(input)
}

747
ethereum/rpc/websockets.go Normal file
View File

@ -0,0 +1,747 @@
package rpc
import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"sync"
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/pkg/errors"
log "github.com/xlab/suplog"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
type WebsocketsServer interface {
Start()
}
type SubscriptionResponseJSON struct {
Jsonrpc string `json:"jsonrpc"`
Result interface{} `json:"result"`
ID float64 `json:"id"`
}
type SubscriptionNotification struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Params *SubscriptionResult `json:"params"`
}
type SubscriptionResult struct {
Subscription rpc.ID `json:"subscription"`
Result interface{} `json:"result"`
}
type ErrorResponseJSON struct {
Jsonrpc string `json:"jsonrpc"`
Error *ErrorMessageJSON `json:"error"`
ID *big.Int `json:"id"`
}
type ErrorMessageJSON struct {
Code *big.Int `json:"code"`
Message string `json:"message"`
}
type websocketsServer struct {
rpcAddr string // listen address of rest-server
wsAddr string // listen address of ws server
api *pubSubAPI
logger log.Logger
}
func NewWebsocketsServer(tmWSClient *rpcclient.WSClient, rpcAddr, wsAddr string) *websocketsServer {
return &websocketsServer{
rpcAddr: rpcAddr,
wsAddr: wsAddr,
api: newPubSubAPI(tmWSClient),
logger: log.WithField("module", "websocket-server"),
}
}
func (s *websocketsServer) Start() {
ws := mux.NewRouter()
ws.Handle("/", s)
go func() {
err := http.ListenAndServe(s.wsAddr, ws)
if err != nil {
if err == http.ErrServerClosed {
return
}
s.logger.WithError(err).Errorln("failed to start HTTP server for WS")
}
}()
}
func (s *websocketsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
s.logger.WithError(err).Warningln("websocket upgrade failed")
return
}
s.readLoop(&wsConn{
mux: new(sync.Mutex),
conn: conn,
})
}
func (s *websocketsServer) sendErrResponse(wsConn *wsConn, msg string) {
res := &ErrorResponseJSON{
Jsonrpc: "2.0",
Error: &ErrorMessageJSON{
Code: big.NewInt(-32600),
Message: msg,
},
ID: nil,
}
_ = wsConn.WriteJSON(res)
}
type wsConn struct {
conn *websocket.Conn
mux *sync.Mutex
}
func (w *wsConn) WriteJSON(v interface{}) error {
w.mux.Lock()
defer w.mux.Unlock()
return w.conn.WriteJSON(v)
}
func (w *wsConn) Close() error {
w.mux.Lock()
defer w.mux.Unlock()
return w.conn.Close()
}
func (w *wsConn) ReadMessage() (messageType int, p []byte, err error) {
// not protected by write mutex
return w.conn.ReadMessage()
}
func (s *websocketsServer) readLoop(wsConn *wsConn) {
for {
_, mb, err := wsConn.ReadMessage()
if err != nil {
_ = wsConn.Close()
return
}
var msg map[string]interface{}
err = json.Unmarshal(mb, &msg)
if err != nil {
s.sendErrResponse(wsConn, "invalid request")
continue
}
// check if method == eth_subscribe or eth_unsubscribe
method, ok := msg["method"].(string)
if !ok {
// otherwise, call the usual rpc server to respond
err = s.tcpGetAndSendResponse(wsConn, mb)
if err != nil {
s.sendErrResponse(wsConn, err.Error())
}
continue
}
connId := msg["id"].(float64)
if method == "eth_subscribe" {
params := msg["params"].([]interface{})
if len(params) == 0 {
s.sendErrResponse(wsConn, "invalid parameters")
continue
}
id, err := s.api.subscribe(wsConn, params)
if err != nil {
s.sendErrResponse(wsConn, err.Error())
continue
}
res := &SubscriptionResponseJSON{
Jsonrpc: "2.0",
ID: connId,
Result: id,
}
err = wsConn.WriteJSON(res)
if err != nil {
continue
}
continue
} else if method == "eth_unsubscribe" {
ids, ok := msg["params"].([]interface{})
if _, idok := ids[0].(string); !ok || !idok {
s.sendErrResponse(wsConn, "invalid parameters")
continue
}
ok = s.api.unsubscribe(rpc.ID(ids[0].(string)))
res := &SubscriptionResponseJSON{
Jsonrpc: "2.0",
ID: connId,
Result: ok,
}
err = wsConn.WriteJSON(res)
if err != nil {
continue
}
continue
}
// otherwise, call the usual rpc server to respond
err = s.tcpGetAndSendResponse(wsConn, mb)
if err != nil {
s.sendErrResponse(wsConn, err.Error())
}
}
}
// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
// to the client over websockets
func (s *websocketsServer) tcpGetAndSendResponse(wsConn *wsConn, mb []byte) error {
req, err := http.NewRequest("POST", "http://"+s.rpcAddr, bytes.NewBuffer(mb))
if err != nil {
return errors.Wrap(err, "Could not build request")
}
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return errors.Wrap(err, "Could not perform request")
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return errors.Wrap(err, "could not read body from response")
}
var wsSend interface{}
err = json.Unmarshal(body, &wsSend)
if err != nil {
return errors.Wrap(err, "failed to unmarshal rest-server response")
}
return wsConn.WriteJSON(wsSend)
}
type wsSubscription struct {
sub *Subscription
unsubscribed chan struct{} // closed when unsubscribing
wsConn *wsConn
query string
}
// pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
type pubSubAPI struct {
events *EventSystem
filtersMu *sync.RWMutex
filters map[rpc.ID]*wsSubscription
logger log.Logger
}
// newPubSubAPI creates an instance of the ethereum PubSub API.
func newPubSubAPI(tmWSClient *rpcclient.WSClient) *pubSubAPI {
return &pubSubAPI{
events: NewEventSystem(tmWSClient),
filtersMu: new(sync.RWMutex),
filters: make(map[rpc.ID]*wsSubscription),
logger: log.WithField("module", "websocket-client"),
}
}
func (api *pubSubAPI) subscribe(wsConn *wsConn, params []interface{}) (rpc.ID, error) {
method, ok := params[0].(string)
if !ok {
return "0", errors.New("invalid parameters")
}
switch method {
case "newHeads":
// TODO: handle extra params
return api.subscribeNewHeads(wsConn)
case "logs":
if len(params) > 1 {
return api.subscribeLogs(wsConn, params[1])
}
return api.subscribeLogs(wsConn, nil)
case "newPendingTransactions":
return api.subscribePendingTransactions(wsConn)
case "syncing":
return api.subscribeSyncing(wsConn)
default:
return "0", errors.Errorf("unsupported method %s", method)
}
}
func (api *pubSubAPI) unsubscribe(id rpc.ID) bool {
api.filtersMu.Lock()
defer api.filtersMu.Unlock()
wsSub, ok := api.filters[id]
if !ok {
return false
}
wsSub.sub.Unsubscribe(api.events)
close(api.filters[id].unsubscribed)
delete(api.filters, id)
return true
}
func (api *pubSubAPI) getSubscriptionByQuery(query string) *Subscription {
api.filtersMu.Lock()
defer api.filtersMu.Unlock()
for _, wsSub := range api.filters {
if wsSub.query == query {
return wsSub.sub
}
}
return nil
}
func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn) (rpc.ID, error) {
var query = "subscribeNewHeads"
var subID = rpc.NewID()
sub, _, err := api.events.SubscribeNewHeads()
if err != nil {
return "", errors.Wrap(err, "error creating block filter")
}
unsubscribed := make(chan struct{})
api.filtersMu.Lock()
api.filters[subID] = &wsSubscription{
sub: sub,
wsConn: wsConn,
unsubscribed: unsubscribed,
query: query,
}
api.filtersMu.Unlock()
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
for {
select {
case event, ok := <-headersCh:
if !ok {
api.unsubscribe(subID)
return
}
data, ok := event.Data.(tmtypes.EventDataNewBlockHeader)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataNewBlockHeader",
"actual": fmt.Sprintf("%T", event.Data),
}).Warningln("event Data type mismatch")
continue
}
header := EthHeaderFromTendermint(data.Header)
api.filtersMu.RLock()
for subID, wsSub := range api.filters {
if wsSub.query != query {
continue
}
// write to ws conn
res := &SubscriptionNotification{
Jsonrpc: "2.0",
Method: "eth_subscription",
Params: &SubscriptionResult{
Subscription: subID,
Result: header,
},
}
err = wsSub.wsConn.WriteJSON(res)
if err != nil {
api.logger.WithError(err).Error("error writing header, will drop peer")
try(func() {
api.filtersMu.RUnlock()
api.filtersMu.Lock()
defer func() {
api.filtersMu.Unlock()
api.filtersMu.RLock()
}()
if err != websocket.ErrCloseSent {
_ = wsSub.wsConn.Close()
}
delete(api.filters, subID)
close(wsSub.unsubscribed)
}, api.logger, "closing websocket peer sub")
}
}
api.filtersMu.RUnlock()
case err, ok := <-errCh:
if !ok {
api.unsubscribe(subID)
return
}
log.WithError(err).Debugln("dropping NewHeads WebSocket subscription", subID)
api.unsubscribe(subID)
case <-unsubscribed:
return
}
}
}(sub.eventCh, sub.Err())
return subID, nil
}
func try(fn func(), l log.Logger, desc string) {
if l == nil {
l = log.DefaultLogger
}
defer func() {
if x := recover(); x != nil {
if err, ok := x.(error); ok {
// debug.PrintStack()
l.WithError(err).Debugln("panic during", desc)
return
}
// debug.PrintStack()
l.Debugf("panic during %s: %+v", desc, x)
return
}
}()
fn()
}
func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, extra interface{}) (rpc.ID, error) {
crit := filters.FilterCriteria{}
if extra != nil {
params, ok := extra.(map[string]interface{})
if !ok {
err := errors.New("invalid criteria")
log.WithFields(log.Fields{
"criteria": fmt.Sprintf("expected: map[string]interface{}, got %T", extra),
}).Errorln(err)
return "", err
}
if params["address"] != nil {
address, ok := params["address"].(string)
addresses, sok := params["address"].([]interface{})
if !ok && !sok {
err := errors.New("invalid address; must be address or array of addresses")
log.WithFields(log.Fields{
"address": ok,
"addresses": sok,
"type": fmt.Sprintf("%T", params["address"]),
}).Errorln(err)
return "", err
}
if ok {
crit.Addresses = []common.Address{common.HexToAddress(address)}
}
if sok {
crit.Addresses = []common.Address{}
for _, addr := range addresses {
address, ok := addr.(string)
if !ok {
err := errors.New("invalid address")
log.WithField("type", fmt.Sprintf("%T", addr)).Errorln(err)
return "", err
}
crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
}
}
}
if params["topics"] != nil {
topics, ok := params["topics"].([]interface{})
if !ok {
err := errors.New("invalid topics")
log.WithFields(log.Fields{
"type": fmt.Sprintf("expected []interface{}, got %T", params["topics"]),
}).Errorln(err)
return "", err
}
crit.Topics = make([][]common.Hash, len(topics))
addCritTopic := func(topicIdx int, topic interface{}) error {
tstr, ok := topic.(string)
if !ok {
err := errors.Errorf("invalid topic: %s", topic)
log.WithField("type", fmt.Sprintf("expected string, got %T", topic)).Errorln(err)
return err
}
crit.Topics[topicIdx] = []common.Hash{common.HexToHash(tstr)}
return nil
}
for topicIdx, subtopics := range topics {
if subtopics == nil {
continue
}
// in case we don't have list, but a single topic value
if topic, ok := subtopics.(string); ok {
if err := addCritTopic(topicIdx, topic); err != nil {
return "", err
}
continue
}
// in case we actually have a list of subtopics
subtopicsList, ok := subtopics.([]interface{})
if !ok {
err := errors.New("invalid subtopics")
log.WithFields(log.Fields{
"type": fmt.Sprintf("expected []interface{}, got %T", subtopics),
}).Errorln(err)
return "", err
}
subtopicsCollect := make([]common.Hash, len(subtopicsList))
for idx, subtopic := range subtopicsList {
tstr, ok := subtopic.(string)
if !ok {
err := errors.Errorf("invalid subtopic: %s", subtopic)
log.WithField("type", fmt.Sprintf("expected string, got %T", subtopic)).Errorln(err)
return "", err
}
subtopicsCollect[idx] = common.HexToHash(tstr)
}
crit.Topics[topicIdx] = subtopicsCollect
}
}
}
critBz, err := json.Marshal(crit)
if err != nil {
log.WithError(err).Errorln("failed to JSON marshal criteria")
return rpc.ID(""), err
}
var query = "subscribeLogs" + string(critBz)
var subID = rpc.NewID()
sub, _, err := api.events.SubscribeLogs(crit)
if err != nil {
log.WithError(err).Errorln("failed to subscribe logs")
return rpc.ID(""), err
}
unsubscribed := make(chan struct{})
api.filtersMu.Lock()
api.filters[subID] = &wsSubscription{
sub: sub,
wsConn: wsConn,
unsubscribed: unsubscribed,
query: query,
}
api.filtersMu.Unlock()
go func(ch <-chan coretypes.ResultEvent, errCh <-chan error, subID rpc.ID) {
for {
select {
case event, ok := <-ch:
if !ok {
api.unsubscribe(subID)
return
}
dataTx, ok := event.Data.(tmtypes.EventDataTx)
if !ok {
log.WithFields(log.Fields{
"expected": "tmtypes.EventDataTx",
"actual": fmt.Sprintf("%T", event.Data),
}).Warningln("event Data type mismatch")
continue
}
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
log.WithError(err).Errorln("failed to decode tx response")
return
}
logs := filterLogs(txResponse.TxLogs.EthLogs(), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
if len(logs) == 0 {
continue
}
api.filtersMu.RLock()
wsSub, ok := api.filters[subID]
if !ok {
log.Warningln("subID not in filters", subID)
return
}
api.filtersMu.RUnlock()
for _, ethLog := range logs {
res := &SubscriptionNotification{
Jsonrpc: "2.0",
Method: "eth_subscription",
Params: &SubscriptionResult{
Subscription: subID,
Result: ethLog,
},
}
err = wsSub.wsConn.WriteJSON(res)
if err != nil {
try(func() {
api.filtersMu.Lock()
defer api.filtersMu.Unlock()
if err != websocket.ErrCloseSent {
_ = wsSub.wsConn.Close()
}
delete(api.filters, subID)
close(wsSub.unsubscribed)
}, api.logger, "closing websocket peer sub")
}
}
case err, ok := <-errCh:
if !ok {
api.unsubscribe(subID)
return
}
log.WithError(err).Warningln("dropping Logs WebSocket subscription", subID)
api.unsubscribe(subID)
case <-unsubscribed:
return
}
}
}(sub.eventCh, sub.Err(), subID)
return subID, nil
}
func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn) (rpc.ID, error) {
var query = "subscribePendingTransactions"
var subID = rpc.NewID()
sub, _, err := api.events.SubscribePendingTxs()
if err != nil {
return "", errors.Wrap(err, "error creating block filter: %s")
}
unsubscribed := make(chan struct{})
api.filtersMu.Lock()
api.filters[subID] = &wsSubscription{
sub: sub,
wsConn: wsConn,
unsubscribed: unsubscribed,
query: query,
}
api.filtersMu.Unlock()
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
for {
select {
case ev := <-txsCh:
data, _ := ev.Data.(tmtypes.EventDataTx)
txHash := common.BytesToHash(tmtypes.Tx(data.Tx).Hash())
api.filtersMu.RLock()
for subID, wsSub := range api.filters {
if wsSub.query != query {
continue
}
// write to ws conn
res := &SubscriptionNotification{
Jsonrpc: "2.0",
Method: "eth_subscription",
Params: &SubscriptionResult{
Subscription: subID,
Result: txHash,
},
}
err = wsSub.wsConn.WriteJSON(res)
if err != nil {
api.logger.WithError(err).Warningln("error writing header, will drop peer")
try(func() {
api.filtersMu.Lock()
defer api.filtersMu.Unlock()
if err != websocket.ErrCloseSent {
_ = wsSub.wsConn.Close()
}
delete(api.filters, subID)
close(wsSub.unsubscribed)
}, api.logger, "closing websocket peer sub")
}
}
api.filtersMu.RUnlock()
case err, ok := <-errCh:
if !ok {
api.unsubscribe(subID)
return
}
log.WithError(err).Warningln("dropping PendingTransactions WebSocket subscription", subID)
api.unsubscribe(subID)
case <-unsubscribed:
return
}
}
}(sub.eventCh, sub.Err())
return subID, nil
}
func (api *pubSubAPI) subscribeSyncing(wsConn *wsConn) (rpc.ID, error) {
return "", nil
}

11
go.mod
View File

@ -5,20 +5,25 @@ go 1.15
require (
github.com/aristanetworks/goarista v0.0.0-20201012165903-2cb20defcd66 // indirect
github.com/armon/go-metrics v0.3.6
github.com/aws/aws-sdk-go v1.38.21 // indirect
github.com/btcsuite/btcd v0.21.0-beta
github.com/btcsuite/btcutil v1.0.2
github.com/bugsnag/bugsnag-go v2.1.0+incompatible // indirect
github.com/bugsnag/panicwrap v1.3.2 // indirect
github.com/cespare/cp v1.1.1 // indirect
github.com/cosmos/cosmos-sdk v0.42.4
github.com/cosmos/go-bip39 v1.0.0
github.com/deckarep/golang-set v1.7.1 // indirect
github.com/ethereum/go-ethereum v1.9.25
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect
github.com/gofrs/uuid v4.0.0+incompatible // indirect
github.com/gogo/protobuf v1.3.3
github.com/golang/protobuf v1.5.2
github.com/google/uuid v1.2.0
github.com/gorilla/mux v1.8.0
github.com/gorilla/websocket v1.4.2
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
github.com/mattn/go-colorable v0.1.8 // indirect
github.com/miguelmota/go-ethereum-hdwallet v0.0.0-20200123000308-a60dcd172b4c
github.com/pkg/errors v0.9.1
@ -30,12 +35,14 @@ require (
github.com/spf13/viper v1.7.1
github.com/status-im/keycard-go v0.0.0-20200402102358-957c09536969
github.com/stretchr/testify v1.7.0
github.com/tendermint/tendermint v0.34.9
github.com/tendermint/tendermint v0.34.10
github.com/tendermint/tm-db v0.6.4
github.com/tyler-smith/go-bip39 v1.0.2
github.com/xlab/suplog v1.3.0 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c // indirect
google.golang.org/genproto v0.0.0-20210405174219-a39eb2f71cb9
google.golang.org/grpc v1.36.1
google.golang.org/grpc v1.37.0
gopkg.in/yaml.v2 v2.4.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

33
go.sum
View File

@ -80,8 +80,11 @@ github.com/armon/go-metrics v0.3.6/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A=
github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU=
github.com/aws/aws-sdk-go v1.25.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/aws/aws-sdk-go v1.38.21 h1:D08DXWI4QRaawLaW+OtsIEClOI90I6eheJs1GwXTQVI=
github.com/aws/aws-sdk-go v1.38.21/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
@ -90,6 +93,7 @@ github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6r
github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4=
github.com/btcsuite/btcd v0.0.0-20171128150713-2e60448ffcc6/go.mod h1:Dmm/EzmjnCiweXmzRIAiUWCInVmPgjkzgv5k4tVyXiQ=
github.com/btcsuite/btcd v0.0.0-20190115013929-ed77733ec07d/go.mod h1:d3C0AkH6BRcvO8T0UEPu53cnw4IbV63x1bEjildYhO0=
github.com/btcsuite/btcd v0.0.0-20190824003749-130ea5bddde3/go.mod h1:3J08xEfcugPacsc34/LKRU2yO7YmuT8yt28J8k2+rrI=
@ -108,6 +112,11 @@ github.com/btcsuite/snappy-go v0.0.0-20151229074030-0bdef8d06723/go.mod h1:8woku
github.com/btcsuite/snappy-go v1.0.0/go.mod h1:8woku9dyThutzjeg+3xrA5iCpBRH8XEEg3lh6TiUghc=
github.com/btcsuite/websocket v0.0.0-20150119174127-31079b680792/go.mod h1:ghJtEyQwv5/p4Mg4C0fgbePVuGr935/5ddU9Z3TmDRY=
github.com/btcsuite/winsvc v1.0.0/go.mod h1:jsenWakMcC0zFBFurPLEAyrnc/teJEM1O46fmI40EZs=
github.com/bugsnag/bugsnag-go v1.5.3/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/bugsnag-go v2.1.0+incompatible h1:SuqsBHDutts2rZh4swHEWTexxi0F/JZ/6j1rR9BFe7I=
github.com/bugsnag/bugsnag-go v2.1.0+incompatible/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
github.com/bugsnag/panicwrap v1.3.2 h1:pNcbtPtH4Y6VwK+oZVNV/2H6Hh3jOL0ZNVFZEfd/eA4=
github.com/bugsnag/panicwrap v1.3.2/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ=
github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
@ -157,6 +166,7 @@ github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwc
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/danieljoos/wincred v1.0.2 h1:zf4bhty2iLuwgjgpraD2E9UbvO+fe54XXGJbOwe23fU=
github.com/danieljoos/wincred v1.0.2/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
github.com/davecgh/go-spew v0.0.0-20171005155431-ecdeabc65495/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -202,6 +212,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/ethereum/go-ethereum v1.9.5/go.mod h1:PwpWDrCLZrV+tfrhqqF6kPknbISMHaJv9Ln3kPCZLwY=
github.com/ethereum/go-ethereum v1.9.25 h1:mMiw/zOOtCLdGLWfcekua0qPrJTe7FVIiHJ4IKNTfR0=
@ -249,6 +260,8 @@ github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 h1:ZpnhV/YsD2/4cESfV5+Hoeu/iUR3ruzNvZ+yQfO03a0=
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/gogo/gateway v1.1.0 h1:u0SuhL9+Il+UbjM9VIE3ntfRujKbvVpFvNB4HbjeVQ0=
github.com/gogo/gateway v1.1.0/go.mod h1:S7rR8FRQyG3QFESeSv4l2WnsyzlCLG0CzBbUUo/mbic=
github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s=
@ -396,6 +409,9 @@ github.com/jedisct1/go-minisign v0.0.0-20190909160543-45766022959e/go.mod h1:G1C
github.com/jessevdk/go-flags v0.0.0-20141203071132-1679536dcc89/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
github.com/jmhodges/levigo v1.0.0 h1:q5EC36kV79HWeTBWsod3mG11EgStG3qArTKcvlksN1U=
github.com/jmhodges/levigo v1.0.0/go.mod h1:Q6Qx+uH3RAqyK4rFQroq9RL7mdkABMcfhEI+nNuzMJQ=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
@ -415,6 +431,8 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8
github.com/karalabe/hid v1.0.0/go.mod h1:Vr51f8rUOLYrfrWDFlV12GGQgM5AT8sVh+2fY4MPeu8=
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356 h1:I/yrLt2WilKxlQKCM52clh5rGzTKpVctGT1lH4Dc8Jw=
github.com/karalabe/usb v0.0.0-20190919080040-51dc0efba356/go.mod h1:Od972xHfMJowv7NGVDiWVxk2zxnWgjLlJzE+F4F7AGU=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 h1:iQTw/8FWTuc7uiaSepXwyf3o52HaUYcV+Tu66S3F5GA=
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0/go.mod h1:1NbS8ALrpOvjt0rHPNLyCIeMtbizbir8U//inJ+zuB8=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d h1:Z+RDyXzjKE0i2sTjZ/b1uxiGtPhFy34Ou/Tk0qwN0kM=
github.com/keybase/go-keychain v0.0.0-20190712205309-48d3d31d256d/go.mod h1:JJNrCn9otv/2QP4D7SMJBgaleKpOf66PnW6F5WGNRIc=
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
@ -436,6 +454,7 @@ github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfn
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
@ -503,6 +522,7 @@ github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs=
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
github.com/oklog/ulid v1.3.1 h1:EGfNDEx6MqHz8B3uNV6QAib1UR2Lm97sHi3ocA6ESJ4=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
github.com/olekukonko/tablewriter v0.0.1/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo=
@ -648,6 +668,8 @@ github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeV
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
@ -723,6 +745,8 @@ github.com/tendermint/tendermint v0.34.0-rc6/go.mod h1:ugzyZO5foutZImv0Iyx/gOFCX
github.com/tendermint/tendermint v0.34.0/go.mod h1:Aj3PIipBFSNO21r+Lq3TtzQ+uKESxkbA3yo/INM4QwQ=
github.com/tendermint/tendermint v0.34.9 h1:9P2MXDEPOcPW0NBcHQ/HDSfvczZm+q5nUUw7AZ6f1Vc=
github.com/tendermint/tendermint v0.34.9/go.mod h1:kl4Z1JwGx1I+u1SXIzMDy7Z3T8LiMeCAOnzNn6AIMT4=
github.com/tendermint/tendermint v0.34.10 h1:wBOc/It8sh/pVH9np2V5fBvRmIyFN/bUrGPx+eAHexs=
github.com/tendermint/tendermint v0.34.10/go.mod h1:aeHL7alPh4uTBIJQ8mgFEE8VwJLXI1VD3rVOmH2Mcy0=
github.com/tendermint/tm-db v0.6.2/go.mod h1:GYtQ67SUvATOcoY8/+x6ylk8Qo02BQyLrAs+yAcLvGI=
github.com/tendermint/tm-db v0.6.3/go.mod h1:lfA1dL9/Y/Y8wwyPp2NMLyn5P5Ptr/gvDFNWtrCWSf8=
github.com/tendermint/tm-db v0.6.4 h1:3N2jlnYQkXNQclQwd/eKV/NzlqPlfK21cpRRIx80XXQ=
@ -745,6 +769,10 @@ github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+m
github.com/xdg/scram v0.0.0-20180814205039-7eeb5667e42c/go.mod h1:lB8K/P019DLNhemzwFU4jHLhdvlE6uDZjXFejJXr49I=
github.com/xdg/stringprep v1.0.0/go.mod h1:Jhud4/sHMO4oL310DaZAKk9ZaJ08SJfe+sJh0HrGL1Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xlab/closer v0.0.0-20190328110542-03326addb7c2 h1:LPYwXwwHigHHFX3SFa9W9zBIa5reyaLJos2e95eHh68=
github.com/xlab/closer v0.0.0-20190328110542-03326addb7c2/go.mod h1:Y8IYP9aVODN3Vnw1FCqygCG5IWyYBeBlZqQ5aX+fHFw=
github.com/xlab/suplog v1.3.0 h1:bbnKR8R8gSs2Q4Y25u2xH6shNNV/4r+bNspqViJQTLY=
github.com/xlab/suplog v1.3.0/go.mod h1:Fq+wOrO0v1DZhfHxgCFB/MlFMzost3Mf/xLuJlfyUA0=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xtaci/kcp-go v5.4.5+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
github.com/xtaci/kcp-go v5.4.20+incompatible/go.mod h1:bN6vIwHQbfHaHtFpEssmWsN45a+AZwO7eyRCmEIbtvE=
@ -857,6 +885,8 @@ golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81R
golang.org/x/net v0.0.0-20200813134508-3edf25e44fcc/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
@ -924,6 +954,8 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4 h1:EZ2mChiOa8udjfp6rRmswTbtZN/QzUQp4ptM4rnjHvc=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c h1:6L+uOeS3OQt/f4eFHXZcTxeZrGCuz+CLElgEBjbcTA4=
golang.org/x/sys v0.0.0-20210415045647-66c3f260301c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -1030,6 +1062,7 @@ google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY=
google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=

View File

@ -1,87 +0,0 @@
package rpc
import (
"github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
"github.com/cosmos/ethermint/rpc/namespaces/net"
"github.com/cosmos/ethermint/rpc/namespaces/personal"
"github.com/cosmos/ethermint/rpc/namespaces/web3"
"github.com/ethereum/go-ethereum/rpc"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
"github.com/cosmos/ethermint/rpc/backend"
"github.com/cosmos/ethermint/rpc/namespaces/eth"
rpctypes "github.com/cosmos/ethermint/rpc/types"
"github.com/cosmos/cosmos-sdk/client"
)
// RPC namespaces and API version
const (
Web3Namespace = "web3"
EthNamespace = "eth"
PersonalNamespace = "personal"
NetNamespace = "net"
flagRPCAPI = "rpc-api"
apiVersion = "1.0"
flagRPCAPI = "rpc-api"
)
// GetAPIs returns the list of all APIs from the Ethereum namespaces
func GetAPIs(clientCtx client.Context, selectedApis []string, keys ...ethsecp256k1.PrivKey) []rpc.API {
nonceLock := new(rpctypes.AddrLocker)
backend := backend.New(clientCtx)
ethAPI := eth.NewAPI(clientCtx, backend, nonceLock, keys...)
var apis []rpc.API
for _, api := range selectedApis {
switch api {
case Web3Namespace:
apis = append(apis,
rpc.API{
Namespace: Web3Namespace,
Version: apiVersion,
Service: web3.NewAPI(),
Public: true,
},
)
case EthNamespace:
apis = append(apis,
rpc.API{
Namespace: EthNamespace,
Version: apiVersion,
Service: ethAPI,
Public: true,
},
rpc.API{
Namespace: EthNamespace,
Version: apiVersion,
Service: filters.NewAPI(clientCtx, backend),
Public: true,
},
)
case PersonalNamespace:
apis = append(apis,
rpc.API{
Namespace: PersonalNamespace,
Version: apiVersion,
Service: personal.NewAPI(ethAPI),
Public: false,
},
)
case NetNamespace:
apis = append(apis,
rpc.API{
Namespace: NetNamespace,
Version: apiVersion,
Service: net.NewAPI(clientCtx),
Public: true,
},
)
}
}
return apis
}

View File

@ -1,225 +0,0 @@
package backend
import (
"context"
"errors"
"os"
"github.com/tendermint/tendermint/libs/log"
rpctypes "github.com/cosmos/ethermint/rpc/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// Backend implements the functionality needed to filter changes.
// Implemented by EthermintBackend.
type Backend interface {
// Used by block filter; also used for polling
BlockNumber() (hexutil.Uint64, error)
LatestBlockNumber() (int64, error)
HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error)
HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error)
GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
// returns the logs of a given block
GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error)
// Used by pending transaction filter
PendingTransactions() ([]*rpctypes.Transaction, error)
// Used by log filter
GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error)
BloomStatus() (uint64, uint64)
}
var _ Backend = (*EthermintBackend)(nil)
// EthermintBackend implements the Backend interface
type EthermintBackend struct {
ctx context.Context
clientCtx client.Context
queryClient *rpctypes.QueryClient // gRPC query client
logger log.Logger
}
// New creates a new EthermintBackend instance
func New(clientCtx client.Context) *EthermintBackend {
return &EthermintBackend{
ctx: context.Background(),
clientCtx: clientCtx,
queryClient: rpctypes.NewQueryClient(clientCtx),
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc"),
}
}
// BlockNumber returns the current block number.
func (b *EthermintBackend) BlockNumber() (hexutil.Uint64, error) {
// NOTE: using 0 as min and max height returns the blockchain info up to the latest block.
info, err := b.clientCtx.Client.BlockchainInfo(b.ctx, 0, 0)
if err != nil {
return hexutil.Uint64(0), err
}
return hexutil.Uint64(info.LastHeight), nil
}
// GetBlockByNumber returns the block identified by number.
func (b *EthermintBackend) GetBlockByNumber(blockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
var height *int64
// NOTE: here pending and latest are defined as a nil height, which fetches the latest block
if !(blockNum == rpctypes.PendingBlockNumber || blockNum == rpctypes.LatestBlockNumber) {
height = blockNum.TmHeight()
}
resBlock, err := b.clientCtx.Client.Block(b.ctx, height)
if err != nil {
return nil, err
}
if resBlock.BlockID.IsZero() {
return nil, errors.New("failed to query block by number: nil block returned")
}
return rpctypes.EthBlockFromTendermint(b.clientCtx, b.queryClient, resBlock.Block)
}
// GetBlockByHash returns the block identified by hash.
func (b *EthermintBackend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
if err != nil {
return nil, err
}
return rpctypes.EthBlockFromTendermint(b.clientCtx, b.queryClient, resBlock.Block)
}
// HeaderByNumber returns the block header identified by height.
func (b *EthermintBackend) HeaderByNumber(blockNum rpctypes.BlockNumber) (*ethtypes.Header, error) {
var height *int64
// NOTE: here pending and latest are defined as a nil height, which fetches the latest header
if !(blockNum == rpctypes.PendingBlockNumber || blockNum == rpctypes.LatestBlockNumber) {
height = blockNum.TmHeight()
}
resBlock, err := b.clientCtx.Client.Block(b.ctx, height)
if err != nil {
return nil, err
}
req := &evmtypes.QueryBlockBloomRequest{}
res, err := b.queryClient.BlockBloom(rpctypes.ContextWithHeight(blockNum.Int64()), req)
if err != nil {
return nil, err
}
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header)
ethHeader.Bloom = ethtypes.BytesToBloom(res.Bloom)
return ethHeader, nil
}
// HeaderByHash returns the block header identified by hash.
func (b *EthermintBackend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, blockHash.Bytes())
if err != nil {
return nil, err
}
req := &evmtypes.QueryBlockBloomRequest{}
res, err := b.queryClient.BlockBloom(rpctypes.ContextWithHeight(resBlock.Block.Height), req)
if err != nil {
return nil, err
}
ethHeader := rpctypes.EthHeaderFromTendermint(resBlock.Block.Header)
ethHeader.Bloom = ethtypes.BytesToBloom(res.Bloom)
return ethHeader, nil
}
// GetTransactionLogs returns the logs given a transaction hash.
// It returns an error if there's an encoding error.
// If no logs are found for the tx hash, the error is nil.
func (b *EthermintBackend) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, error) {
req := &evmtypes.QueryTxLogsRequest{
Hash: txHash.String(),
}
res, err := b.queryClient.TxLogs(b.ctx, req)
if err != nil {
return nil, err
}
return evmtypes.LogsToEthereum(res.Logs), nil
}
// PendingTransactions returns the transactions that are in the transaction pool
// and have a from address that is one of the accounts this node manages.
func (b *EthermintBackend) PendingTransactions() ([]*rpctypes.Transaction, error) {
limit := 1000
pendingTxs, err := b.clientCtx.Client.UnconfirmedTxs(b.ctx, &limit)
if err != nil {
return nil, err
}
transactions := make([]*rpctypes.Transaction, 0)
for _, tx := range pendingTxs.Txs {
ethTx, err := rpctypes.RawTxToEthTx(b.clientCtx, tx)
if err != nil {
// ignore non Ethermint EVM transactions
continue
}
// TODO: check signer and reference against accounts the node manages
rpcTx, err := rpctypes.NewTransaction(ethTx, common.BytesToHash(tx.Hash()), common.Hash{}, 0, 0)
if err != nil {
return nil, err
}
transactions = append(transactions, rpcTx)
}
return transactions, nil
}
// GetLogs returns all the logs from all the ethereum transactions in a block.
func (b *EthermintBackend) GetLogs(blockHash common.Hash) ([][]*ethtypes.Log, error) {
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
req := &evmtypes.QueryBlockLogsRequest{
Hash: blockHash.String(),
}
res, err := b.queryClient.BlockLogs(b.ctx, req)
if err != nil {
return nil, err
}
var blockLogs = [][]*ethtypes.Log{}
for _, txLog := range res.TxLogs {
blockLogs = append(blockLogs, txLog.EthLogs())
}
return blockLogs, nil
}
// BloomStatus returns the BloomBitsBlocks and the number of processed sections maintained
// by the chain indexer.
func (b *EthermintBackend) BloomStatus() (uint64, uint64) {
return 4096, 0
}
// LatestBlockNumber gets the latest block height in int64 format.
func (b *EthermintBackend) LatestBlockNumber() (int64, error) {
// NOTE: using 0 as min and max height returns the blockchain info up to the latest block.
info, err := b.clientCtx.Client.BlockchainInfo(context.Background(), 0, 0)
if err != nil {
return 0, err
}
return info.LastHeight, nil
}

View File

@ -1,33 +0,0 @@
package rpc
import (
"strings"
"github.com/gorilla/mux"
"github.com/spf13/viper"
"github.com/cosmos/cosmos-sdk/client"
"github.com/ethereum/go-ethereum/rpc"
)
// RegisterEthereum creates a new ethereum JSON-RPC server and recreates a CLI command to start Cosmos REST server with web3 RPC API and
// Cosmos rest-server endpoints
func RegisterEthereum(clientCtx client.Context, r *mux.Router) {
server := rpc.NewServer()
r.HandleFunc("/", server.ServeHTTP).Methods("POST", "OPTIONS")
rpcapi := viper.GetString(flagRPCAPI)
rpcapi = strings.ReplaceAll(rpcapi, " ", "")
rpcapiArr := strings.Split(rpcapi, ",")
apis := GetAPIs(clientCtx, rpcapiArr)
// Register all the APIs exposed by the namespace services
// TODO: handle allowlist and private APIs
for _, api := range apis {
if err := server.RegisterName(api.Namespace, api.Service); err != nil {
panic(err)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,379 +0,0 @@
package filters
import (
"context"
"fmt"
"time"
tmquery "github.com/tendermint/tendermint/libs/pubsub/query"
rpcclient "github.com/tendermint/tendermint/rpc/client"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
sdk "github.com/cosmos/cosmos-sdk/types"
rpctypes "github.com/cosmos/ethermint/rpc/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
var (
txEvents = tmtypes.QueryForEvent(tmtypes.EventTx).String()
evmEvents = tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName)).String()
headerEvents = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String()
)
// EventSystem creates subscriptions, processes events and broadcasts them to the
// subscription which match the subscription criteria using the Tendermint's RPC client.
type EventSystem struct {
ctx context.Context
client rpcclient.Client
// light client mode
lightMode bool
index filterIndex
// Subscriptions
txsSub *Subscription // Subscription for new transaction event
logsSub *Subscription // Subscription for new log event
// rmLogsSub *Subscription // Subscription for removed log event
pendingLogsSub *Subscription // Subscription for pending log event
chainSub *Subscription // Subscription for new chain event
// Channels
install chan *Subscription // install filter for event notification
uninstall chan *Subscription // remove filter for event notification
// Unidirectional channels to receive Tendermint ResultEvents
txsCh <-chan coretypes.ResultEvent // Channel to receive new pending transactions event
logsCh <-chan coretypes.ResultEvent // Channel to receive new log event
pendingLogsCh <-chan coretypes.ResultEvent // Channel to receive new pending log event
// rmLogsCh <-chan coretypes.ResultEvent // Channel to receive removed log event
chainCh <-chan coretypes.ResultEvent // Channel to receive new chain event
}
// NewEventSystem creates a new manager that listens for event on the given mux,
// parses and filters them. It uses the all map to retrieve filter changes. The
// work loop holds its own index that is used to forward events to filters.
//
// The returned manager has a loop that needs to be stopped with the Stop function
// or by stopping the given mux.
func NewEventSystem(client rpcclient.Client) *EventSystem {
index := make(filterIndex)
for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ {
index[i] = make(map[rpc.ID]*Subscription)
}
es := &EventSystem{
ctx: context.Background(),
client: client,
lightMode: false,
index: index,
install: make(chan *Subscription),
uninstall: make(chan *Subscription),
txsCh: make(<-chan coretypes.ResultEvent),
logsCh: make(<-chan coretypes.ResultEvent),
pendingLogsCh: make(<-chan coretypes.ResultEvent),
// rmLogsCh: make(<-chan coretypes.ResultEvent),
chainCh: make(<-chan coretypes.ResultEvent),
}
go es.eventLoop()
return es
}
// WithContext sets a new context to the EventSystem. This is required to set a timeout context when
// a new filter is intantiated.
func (es *EventSystem) WithContext(ctx context.Context) {
es.ctx = ctx
}
// subscribe performs a new event subscription to a given Tendermint event.
// The subscription creates a unidirectional receive event channel to receive the ResultEvent. By
// default, the subscription timeouts (i.e is canceled) after 5 minutes. This function returns an
// error if the subscription fails (eg: if the identifier is already subscribed) or if the filter
// type is invalid.
func (es *EventSystem) subscribe(sub *Subscription) (*Subscription, context.CancelFunc, error) {
var (
err error
cancelFn context.CancelFunc
eventCh <-chan coretypes.ResultEvent
)
es.ctx, cancelFn = context.WithTimeout(context.Background(), deadline)
switch sub.typ {
case filters.PendingTransactionsSubscription:
eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event)
case filters.PendingLogsSubscription, filters.MinedAndPendingLogsSubscription:
eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event)
case filters.LogsSubscription:
eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event)
case filters.BlocksSubscription:
eventCh, err = es.client.Subscribe(es.ctx, string(sub.id), sub.event)
default:
err = fmt.Errorf("invalid filter subscription type %d", sub.typ)
}
if err != nil {
sub.err <- err
return nil, cancelFn, err
}
// wrap events in a go routine to prevent blocking
go func() {
es.install <- sub
<-sub.installed
}()
sub.eventCh = eventCh
return sub, cancelFn, nil
}
// SubscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel. Default value for the from and to
// block is "latest". If the fromBlock > toBlock an error is returned.
func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
var from, to rpc.BlockNumber
if crit.FromBlock == nil {
from = rpc.LatestBlockNumber
} else {
from = rpc.BlockNumber(crit.FromBlock.Int64())
}
if crit.ToBlock == nil {
to = rpc.LatestBlockNumber
} else {
to = rpc.BlockNumber(crit.ToBlock.Int64())
}
switch {
// only interested in pending logs
case from == rpc.PendingBlockNumber && to == rpc.PendingBlockNumber:
return es.subscribePendingLogs(crit)
// only interested in new mined logs, mined logs within a specific block range, or
// logs from a specific block number to new mined blocks
case (from == rpc.LatestBlockNumber && to == rpc.LatestBlockNumber),
(from >= 0 && to >= 0 && to >= from):
return es.subscribeLogs(crit)
// interested in mined logs from a specific block number, new logs and pending logs
case from >= rpc.LatestBlockNumber && (to == rpc.PendingBlockNumber || to == rpc.LatestBlockNumber):
return es.subscribeMinedPendingLogs(crit)
default:
return nil, nil, fmt.Errorf("invalid from and to block combination: from > to (%d > %d)", from, to)
}
}
// subscribeMinedPendingLogs creates a subscription that returned mined and
// pending logs that match the given criteria.
func (es *EventSystem) subscribeMinedPendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.MinedAndPendingLogsSubscription,
event: evmEvents,
logsCrit: crit,
created: time.Now().UTC(),
logs: make(chan []*ethtypes.Log),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
// subscribeLogs creates a subscription that will write all logs matching the
// given criteria to the given logs channel.
func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.LogsSubscription,
event: evmEvents,
logsCrit: crit,
created: time.Now().UTC(),
logs: make(chan []*ethtypes.Log),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
// subscribePendingLogs creates a subscription that writes transaction hashes for
// transactions that enter the transaction pool.
func (es *EventSystem) subscribePendingLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.PendingLogsSubscription,
event: evmEvents,
logsCrit: crit,
created: time.Now().UTC(),
logs: make(chan []*ethtypes.Log),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
// SubscribeNewHeads subscribes to new block headers events.
func (es EventSystem) SubscribeNewHeads() (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.BlocksSubscription,
event: headerEvents,
created: time.Now().UTC(),
headers: make(chan *ethtypes.Header),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
// SubscribePendingTxs subscribes to new pending transactions events from the mempool.
func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.PendingTransactionsSubscription,
event: txEvents,
created: time.Now().UTC(),
hashes: make(chan []common.Hash),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
return es.subscribe(sub)
}
type filterIndex map[filters.Type]map[rpc.ID]*Subscription
func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) {
data, _ := ev.Data.(tmtypes.EventDataTx)
txResponse, err := evmtypes.DecodeTxResponse(data.TxResult.Result.Data)
if err != nil {
return
}
if len(txResponse.TxLogs.Logs) == 0 {
return
}
for _, f := range es.index[filters.LogsSubscription] {
matchedLogs := FilterLogs(txResponse.TxLogs.EthLogs(), f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
if len(matchedLogs) > 0 {
f.logs <- matchedLogs
}
}
}
func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) {
data, _ := ev.Data.(tmtypes.EventDataTx)
for _, f := range es.index[filters.PendingTransactionsSubscription] {
f.hashes <- []common.Hash{common.BytesToHash(tmtypes.Tx(data.Tx).Hash())}
}
}
func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) {
data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
for _, f := range es.index[filters.BlocksSubscription] {
f.headers <- rpctypes.EthHeaderFromTendermint(data.Header)
}
// TODO: light client
}
// eventLoop (un)installs filters and processes mux events.
func (es *EventSystem) eventLoop() {
var (
err error
cancelPendingTxsSubs, cancelLogsSubs, cancelPendingLogsSubs, cancelHeaderSubs context.CancelFunc
)
// Subscribe events
es.txsSub, cancelPendingTxsSubs, err = es.SubscribePendingTxs()
if err != nil {
panic(fmt.Errorf("failed to subscribe pending txs: %w", err))
}
defer cancelPendingTxsSubs()
es.logsSub, cancelLogsSubs, err = es.SubscribeLogs(filters.FilterCriteria{})
if err != nil {
panic(fmt.Errorf("failed to subscribe logs: %w", err))
}
defer cancelLogsSubs()
es.pendingLogsSub, cancelPendingLogsSubs, err = es.subscribePendingLogs(filters.FilterCriteria{})
if err != nil {
panic(fmt.Errorf("failed to subscribe pending logs: %w", err))
}
defer cancelPendingLogsSubs()
es.chainSub, cancelHeaderSubs, err = es.SubscribeNewHeads()
if err != nil {
panic(fmt.Errorf("failed to subscribe headers: %w", err))
}
defer cancelHeaderSubs()
// Ensure all subscriptions get cleaned up
defer func() {
es.txsSub.Unsubscribe(es)
es.logsSub.Unsubscribe(es)
// es.rmLogsSub.Unsubscribe(es)
es.pendingLogsSub.Unsubscribe(es)
es.chainSub.Unsubscribe(es)
}()
for {
select {
case txEvent := <-es.txsSub.eventCh:
es.handleTxsEvent(txEvent)
case headerEv := <-es.chainSub.eventCh:
es.handleChainEvent(headerEv)
case logsEv := <-es.logsSub.eventCh:
es.handleLogs(logsEv)
// TODO: figure out how to handle removed logs
// case logsEv := <-es.rmLogsSub.eventCh:
// es.handleLogs(logsEv)
case logsEv := <-es.pendingLogsSub.eventCh:
es.handleLogs(logsEv)
case f := <-es.install:
if f.typ == filters.MinedAndPendingLogsSubscription {
// the type are logs and pending logs subscriptions
es.index[filters.LogsSubscription][f.id] = f
es.index[filters.PendingLogsSubscription][f.id] = f
} else {
es.index[f.typ][f.id] = f
}
close(f.installed)
case f := <-es.uninstall:
if f.typ == filters.MinedAndPendingLogsSubscription {
// the type are logs and pending logs subscriptions
delete(es.index[filters.LogsSubscription], f.id)
delete(es.index[filters.PendingLogsSubscription], f.id)
} else {
delete(es.index[f.typ], f.id)
}
close(f.err)
// System stopped
case <-es.txsSub.Err():
return
case <-es.logsSub.Err():
return
// case <-es.rmLogsSub.Err():
// return
case <-es.pendingLogsSub.Err():
return
case <-es.chainSub.Err():
return
}
}
// }()
}

View File

@ -1,168 +0,0 @@
package filters
import (
"context"
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/bloombits"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters"
rpctypes "github.com/cosmos/ethermint/rpc/types"
)
// Filter can be used to retrieve and filter logs.
type Filter struct {
backend Backend
criteria filters.FilterCriteria
matcher *bloombits.Matcher
}
// NewBlockFilter creates a new filter which directly inspects the contents of
// a block to figure out whether it is interesting or not.
func NewBlockFilter(backend Backend, criteria filters.FilterCriteria) *Filter {
// Create a generic filter and convert it into a block filter
return newFilter(backend, criteria, nil)
}
// NewRangeFilter creates a new filter which uses a bloom filter on blocks to
// figure out whether a particular block is interesting or not.
func NewRangeFilter(backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
// Flatten the address and topic filter clauses into a single bloombits filter
// system. Since the bloombits are not positional, nil topics are permitted,
// which get flattened into a nil byte slice.
var filtersBz [][][]byte // nolint: prealloc
if len(addresses) > 0 {
filter := make([][]byte, len(addresses))
for i, address := range addresses {
filter[i] = address.Bytes()
}
filtersBz = append(filtersBz, filter)
}
for _, topicList := range topics {
filter := make([][]byte, len(topicList))
for i, topic := range topicList {
filter[i] = topic.Bytes()
}
filtersBz = append(filtersBz, filter)
}
size, _ := backend.BloomStatus()
// Create a generic filter and convert it into a range filter
criteria := filters.FilterCriteria{
FromBlock: big.NewInt(begin),
ToBlock: big.NewInt(end),
Addresses: addresses,
Topics: topics,
}
return newFilter(backend, criteria, bloombits.NewMatcher(size, filtersBz))
}
// newFilter returns a new Filter
func newFilter(backend Backend, criteria filters.FilterCriteria, matcher *bloombits.Matcher) *Filter {
return &Filter{
backend: backend,
criteria: criteria,
matcher: matcher,
}
}
// Logs searches the blockchain for matching log entries, returning all from the
// first block that contains matches, updating the start of the filter accordingly.
func (f *Filter) Logs(_ context.Context) ([]*ethtypes.Log, error) {
logs := []*ethtypes.Log{}
var err error
// If we're doing singleton block filtering, execute and return
if f.criteria.BlockHash != nil && f.criteria.BlockHash != (&common.Hash{}) {
header, err := f.backend.HeaderByHash(*f.criteria.BlockHash)
if err != nil {
return nil, err
}
if header == nil {
return nil, fmt.Errorf("unknown block header %s", f.criteria.BlockHash.String())
}
return f.blockLogs(header)
}
// Figure out the limits of the filter range
header, err := f.backend.HeaderByNumber(rpctypes.LatestBlockNumber)
if err != nil {
return nil, err
}
if header == nil || header.Number == nil {
return nil, nil
}
head := header.Number.Int64()
if f.criteria.FromBlock.Int64() == -1 {
f.criteria.FromBlock = big.NewInt(head)
}
if f.criteria.ToBlock.Int64() == -1 {
f.criteria.ToBlock = big.NewInt(head)
}
for i := f.criteria.FromBlock.Int64(); i <= f.criteria.ToBlock.Int64(); i++ {
block, err := f.backend.GetBlockByNumber(rpctypes.BlockNumber(i), true)
if err != nil {
return logs, err
}
txs, ok := block["transactions"].([]common.Hash)
if !ok || len(txs) == 0 {
continue
}
logsMatched := f.checkMatches(txs)
logs = append(logs, logsMatched...)
}
return logs, nil
}
// blockLogs returns the logs matching the filter criteria within a single block.
func (f *Filter) blockLogs(header *ethtypes.Header) ([]*ethtypes.Log, error) {
if !bloomFilter(header.Bloom, f.criteria.Addresses, f.criteria.Topics) {
return []*ethtypes.Log{}, nil
}
logsList, err := f.backend.GetLogs(header.Hash())
if err != nil {
return []*ethtypes.Log{}, err
}
var unfiltered []*ethtypes.Log // nolint: prealloc
for _, logs := range logsList {
unfiltered = append(unfiltered, logs...)
}
logs := FilterLogs(unfiltered, nil, nil, f.criteria.Addresses, f.criteria.Topics)
if len(logs) == 0 {
return []*ethtypes.Log{}, nil
}
return logs, nil
}
// checkMatches checks if the logs from the a list of transactions transaction
// contain any log events that match the filter criteria. This function is
// called when the bloom filter signals a potential match.
func (f *Filter) checkMatches(transactions []common.Hash) []*ethtypes.Log {
unfiltered := []*ethtypes.Log{}
for _, tx := range transactions {
logs, err := f.backend.GetTransactionLogs(tx)
if err != nil {
// ignore error if transaction didn't set any logs (eg: when tx type is not
// MsgEthereumTx or MsgEthermint)
continue
}
unfiltered = append(unfiltered, logs...)
}
return FilterLogs(unfiltered, f.criteria.FromBlock, f.criteria.ToBlock, f.criteria.Addresses, f.criteria.Topics)
}

View File

@ -1,72 +0,0 @@
package filters
import (
"log"
"time"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
)
// Subscription defines a wrapper for the private subscription
type Subscription struct {
id rpc.ID
typ filters.Type
event string
created time.Time
logsCrit filters.FilterCriteria
logs chan []*ethtypes.Log
hashes chan []common.Hash
headers chan *ethtypes.Header
installed chan struct{} // closed when the filter is installed
eventCh <-chan coretypes.ResultEvent
err chan error
}
// ID returns the underlying subscription RPC identifier.
func (s Subscription) ID() rpc.ID {
return s.id
}
// Unsubscribe to the current subscription from Tendermint Websocket. It sends an error to the
// subscription error channel if unsubscription fails.
func (s *Subscription) Unsubscribe(es *EventSystem) {
if err := es.client.Unsubscribe(es.ctx, string(s.ID()), s.event); err != nil {
s.err <- err
}
go func() {
defer func() {
log.Println("successfully unsubscribed to event", s.event)
}()
uninstallLoop:
for {
// write uninstall request and consume logs/hashes. This prevents
// the eventLoop broadcast method to deadlock when writing to the
// filter event channel while the subscription loop is waiting for
// this method to return (and thus not reading these events).
select {
case es.uninstall <- s:
break uninstallLoop
case <-s.logs:
case <-s.hashes:
case <-s.headers:
}
}
}()
}
// Err returns the error channel
func (s *Subscription) Err() <-chan error {
return s.err
}
// Event returns the tendermint result event channel
func (s *Subscription) Event() <-chan coretypes.ResultEvent {
return s.eventCh
}

View File

@ -1,102 +0,0 @@
package filters
import (
"math/big"
"github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
)
// filterLogs creates a slice of logs matching the given criteria.
// [] -> anything
// [A] -> A in first position of log topics, anything after
// [null, B] -> anything in first position, B in second position
// [A, B] -> A in first position and B in second position
// [[A, B], [A, B]] -> A or B in first position, A or B in second position
func FilterLogs(logs []*ethtypes.Log, fromBlock, toBlock *big.Int, addresses []common.Address, topics [][]common.Hash) []*ethtypes.Log {
var ret []*ethtypes.Log
Logs:
for _, log := range logs {
if fromBlock != nil && fromBlock.Int64() >= 0 && fromBlock.Uint64() > log.BlockNumber {
continue
}
if toBlock != nil && toBlock.Int64() >= 0 && toBlock.Uint64() < log.BlockNumber {
continue
}
if len(addresses) > 0 && !includes(addresses, log.Address) {
continue
}
// If the to filtered topics is greater than the amount of topics in logs, skip.
if len(topics) > len(log.Topics) {
continue
}
for i, sub := range topics {
match := len(sub) == 0 // empty rule set == wildcard
for _, topic := range sub {
if log.Topics[i] == topic {
match = true
break
}
}
if !match {
continue Logs
}
}
ret = append(ret, log)
}
return ret
}
func includes(addresses []common.Address, a common.Address) bool {
for _, addr := range addresses {
if addr == a {
return true
}
}
return false
}
func bloomFilter(bloom ethtypes.Bloom, addresses []common.Address, topics [][]common.Hash) bool {
var included bool
if len(addresses) > 0 {
for _, addr := range addresses {
if ethtypes.BloomLookup(bloom, addr) {
included = true
break
}
}
if !included {
return false
}
}
for _, sub := range topics {
included = len(sub) == 0 // empty rule set == wildcard
for _, topic := range sub {
if ethtypes.BloomLookup(bloom, topic) {
included = true
break
}
}
}
return included
}
// returnHashes is a helper that will return an empty hash array case the given hash array is nil,
// otherwise the given hashes array is returned.
func returnHashes(hashes []common.Hash) []common.Hash {
if hashes == nil {
return []common.Hash{}
}
return hashes
}
// returnLogs is a helper that will return an empty log array in case the given logs array is nil,
// otherwise the given logs array is returned.
func returnLogs(logs []*ethtypes.Log) []*ethtypes.Log {
if logs == nil {
return []*ethtypes.Log{}
}
return logs
}

View File

@ -1,277 +0,0 @@
package personal
import (
"bytes"
"context"
"fmt"
"os"
"time"
"github.com/cosmos/ethermint/crypto/hd"
ethermint "github.com/cosmos/ethermint/types"
"github.com/tendermint/tendermint/libs/log"
sdkcrypto "github.com/cosmos/cosmos-sdk/crypto"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/cosmos/ethermint/crypto/ethsecp256k1"
"github.com/cosmos/ethermint/rpc/namespaces/eth"
rpctypes "github.com/cosmos/ethermint/rpc/types"
)
// PrivateAccountAPI is the personal_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PrivateAccountAPI struct {
ethAPI *eth.PublicEthereumAPI
logger log.Logger
keyInfos []keyring.Info // all keys, both locked and unlocked. unlocked keys are stored in ethAPI.keys
}
// NewAPI creates an instance of the public Personal Eth API.
func NewAPI(ethAPI *eth.PublicEthereumAPI) *PrivateAccountAPI {
api := &PrivateAccountAPI{
ethAPI: ethAPI,
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "json-rpc", "namespace", "personal"),
}
err := api.ethAPI.GetKeyringInfo()
if err != nil {
return api
}
api.keyInfos, err = api.ethAPI.ClientCtx().Keyring.List()
if err != nil {
return api
}
return api
}
// ImportRawKey armors and encrypts a given raw hex encoded ECDSA key and stores it into the key directory.
// The name of the key will have the format "personal_<length-keys>", where <length-keys> is the total number of
// keys stored on the keyring.
// NOTE: The key will be both armored and encrypted using the same passphrase.
func (api *PrivateAccountAPI) ImportRawKey(privkey, password string) (common.Address, error) {
api.logger.Debug("personal_importRawKey")
priv, err := crypto.HexToECDSA(privkey)
if err != nil {
return common.Address{}, err
}
privKey := &ethsecp256k1.PrivKey{Key: crypto.FromECDSA(priv)}
armor := sdkcrypto.EncryptArmorPrivKey(privKey, password, ethsecp256k1.KeyType)
// ignore error as we only care about the length of the list
list, _ := api.ethAPI.ClientCtx().Keyring.List()
privKeyName := fmt.Sprintf("personal_%d", len(list))
if err := api.ethAPI.ClientCtx().Keyring.ImportPrivKey(privKeyName, armor, password); err != nil {
return common.Address{}, err
}
addr := common.BytesToAddress(privKey.PubKey().Address().Bytes())
info, err := api.ethAPI.ClientCtx().Keyring.Key(privKeyName)
if err != nil {
return common.Address{}, err
}
// append key and info to be able to lock and list the account
// api.ethAPI.keys = append(api.ethAPI.keys, privKey)
api.keyInfos = append(api.keyInfos, info)
api.logger.Info("key successfully imported", "name", privKeyName, "address", addr.String())
return addr, nil
}
// ListAccounts will return a list of addresses for accounts this node manages.
func (api *PrivateAccountAPI) ListAccounts() ([]common.Address, error) {
api.logger.Debug("personal_listAccounts")
addrs := []common.Address{}
for _, info := range api.keyInfos {
addressBytes := info.GetPubKey().Address().Bytes()
addrs = append(addrs, common.BytesToAddress(addressBytes))
}
return addrs, nil
}
// LockAccount will lock the account associated with the given address when it's unlocked.
// It removes the key corresponding to the given address from the API's local keys.
func (api *PrivateAccountAPI) LockAccount(address common.Address) bool {
api.logger.Debug("personal_lockAccount", "address", address.String())
keys := api.ethAPI.GetKeys()
for i, key := range keys {
if !bytes.Equal(key.PubKey().Address().Bytes(), address.Bytes()) {
continue
}
tmp := make([]ethsecp256k1.PrivKey, len(keys)-1)
copy(tmp[:i], keys[:i])
copy(tmp[i:], keys[i+1:])
api.ethAPI.SetKeys(tmp)
api.logger.Debug("account locked", "address", address.String())
return true
}
return false
}
// NewAccount will create a new account and returns the address for the new account.
func (api *PrivateAccountAPI) NewAccount(password string) (common.Address, error) {
api.logger.Debug("personal_newAccount")
name := "key_" + time.Now().UTC().Format(time.RFC3339)
info, _, err := api.ethAPI.ClientCtx().Keyring.NewMnemonic(name, keyring.English, ethermint.BIP44HDPath, hd.EthSecp256k1)
if err != nil {
return common.Address{}, err
}
// TODO: rpc_test_fix: if we use NewMnemonic, it will create an account automatically, and
// the new account encrypted with default empty string in cosmos-SDK,
// if we create mnemonic then create new account, we basically generated two accounts
// nolint
//name = "key_" + time.Now().UTC().Format(time.RFC3339)
//info, err := api.ethAPI.ClientCtx().Keyring.NewAccount(name, mnemonic, password, ethermint.BIP44HDPath, hd.EthSecp256k1)
//if err != nil {
// return common.Address{}, err
//}
//
//// encrypt the key info with given password
//armor, err := api.ethAPI.ClientCtx().Keyring.ExportPrivKeyArmor(info.GetName(), password)
//if err != nil {
// return common.Address{}, err
//}
api.keyInfos = append(api.keyInfos, info)
addr := common.BytesToAddress(info.GetPubKey().Address().Bytes())
api.logger.Info("Your new key was generated", "address", addr.String())
api.logger.Info("Please backup your key file!", "path", os.Getenv("HOME")+"/.ethermint/"+name)
api.logger.Info("Please remember your password!")
return addr, nil
}
// UnlockAccount will unlock the account associated with the given address with
// the given password for duration seconds. If duration is nil it will use a
// default of 300 seconds. It returns an indication if the account was unlocked.
// It exports the private key corresponding to the given address from the keyring and stores it in the API's local keys.
func (api *PrivateAccountAPI) UnlockAccount(_ context.Context, addr common.Address, password string, _ *uint64) (bool, error) { // nolint: interfacer
api.logger.Debug("personal_unlockAccount", "address", addr.String())
// TODO: use duration
var keyInfo keyring.Info
for _, info := range api.keyInfos {
addressBytes := info.GetPubKey().Address().Bytes()
if bytes.Equal(addressBytes, addr[:]) {
keyInfo = info
break
}
}
if keyInfo == nil {
return false, fmt.Errorf("cannot find key with given address %s", addr.String())
}
// exporting private key only works on local keys
if keyInfo.GetType() != keyring.TypeLocal {
return false, fmt.Errorf("key type must be %s, got %s", keyring.TypeLedger.String(), keyInfo.GetType().String())
}
// TODO: rpc_test_fix: password is used for encrypt and then decrypt, what's the point of locking the account ?
armor, err := api.ethAPI.ClientCtx().Keyring.ExportPrivKeyArmor(keyInfo.GetName(), password)
if err != nil {
return false, err
}
privKey, algo, err := sdkcrypto.UnarmorDecryptPrivKey(armor, password)
if err != nil {
return false, err
}
// TODO: rpc_test_fix: we need to support both ethsecp256k1 and secp256k1, however, cosmos-SDK only support secp256k1 when init with keys add CLI
// so that the very first account has key type as secp256k1, thus, we need to support both key types here
if algo != ethsecp256k1.KeyType {
return false, fmt.Errorf("invalid key algorithm, got %s, expected %s", algo, ethsecp256k1.KeyType)
}
ethermintPrivKey, ok := privKey.(*ethsecp256k1.PrivKey)
if !ok {
return false, fmt.Errorf("invalid private key type %T, expected %T", privKey, &ethsecp256k1.PrivKey{})
}
api.ethAPI.SetKeys(append(api.ethAPI.GetKeys(), *ethermintPrivKey))
api.logger.Debug("account unlocked", "address", addr.String())
return true, nil
}
// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.To. If the given password isn't
// able to decrypt the key it fails.
func (api *PrivateAccountAPI) SendTransaction(_ context.Context, args rpctypes.SendTxArgs, _ string) (common.Hash, error) {
return api.ethAPI.SendTransaction(args)
}
// Sign calculates an Ethereum ECDSA signature for:
// keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))
//
// Note, the produced signature conforms to the secp256k1 curve R, S and V values,
// where the V value will be 27 or 28 for legacy reasons.
//
// The key used to calculate the signature is decrypted with the given password.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_sign
func (api *PrivateAccountAPI) Sign(_ context.Context, data hexutil.Bytes, addr common.Address, _ string) (hexutil.Bytes, error) {
api.logger.Debug("personal_sign", "data", data, "address", addr.String())
// TODO: rpc_test_fix: because the default account is secp256k1, it can not be unlocked and put into api.ethAPI array
key, ok := rpctypes.GetKeyByAddress(api.ethAPI.GetKeys(), addr)
if !ok {
return nil, fmt.Errorf("cannot find key with address %s", addr.String())
}
sig, err := crypto.Sign(accounts.TextHash(data), key.ToECDSA())
if err != nil {
return nil, err
}
sig[crypto.RecoveryIDOffset] += 27 // transform V from 0/1 to 27/28
return sig, nil
}
// EcRecover returns the address for the account that was used to create the signature.
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
// the address of:
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
// addr = ecrecover(hash, signature)
//
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
// the V value must be 27 or 28 for legacy reasons.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecove
func (api *PrivateAccountAPI) EcRecover(_ context.Context, data, sig hexutil.Bytes) (common.Address, error) {
api.logger.Debug("personal_ecRecover", "data", data, "sig", sig)
if len(sig) != crypto.SignatureLength {
return common.Address{}, fmt.Errorf("signature must be %d bytes long", crypto.SignatureLength)
}
if sig[crypto.RecoveryIDOffset] != 27 && sig[crypto.RecoveryIDOffset] != 28 {
return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
}
sig[crypto.RecoveryIDOffset] -= 27 // Transform yellow paper V from 27/28 to 0/1
pubkey, err := crypto.SigToPub(accounts.TextHash(data), sig)
if err != nil {
return common.Address{}, err
}
return crypto.PubkeyToAddress(*pubkey), nil
}

View File

@ -1,322 +0,0 @@
package websockets
import (
"fmt"
"os"
"sync"
"github.com/gorilla/websocket"
"github.com/tendermint/tendermint/libs/log"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
"github.com/cosmos/cosmos-sdk/client"
rpcfilters "github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
rpctypes "github.com/cosmos/ethermint/rpc/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
)
// PubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
type PubSubAPI struct {
clientCtx client.Context
events *rpcfilters.EventSystem
filtersMu sync.Mutex
filters map[rpc.ID]*wsSubscription
logger log.Logger
}
// NewAPI creates an instance of the ethereum PubSub API.
func NewAPI(clientCtx client.Context) *PubSubAPI {
return &PubSubAPI{
clientCtx: clientCtx,
events: rpcfilters.NewEventSystem(clientCtx.Client),
filters: make(map[rpc.ID]*wsSubscription),
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-client"),
}
}
func (api *PubSubAPI) subscribe(conn *websocket.Conn, params []interface{}) (rpc.ID, error) {
method, ok := params[0].(string)
if !ok {
return "0", fmt.Errorf("invalid parameters")
}
switch method {
case "newHeads":
// TODO: handle extra params
return api.subscribeNewHeads(conn)
case "logs":
if len(params) > 1 {
return api.subscribeLogs(conn, params[1])
}
return api.subscribeLogs(conn, nil)
case "newPendingTransactions":
return api.subscribePendingTransactions(conn)
case "syncing":
return api.subscribeSyncing(conn)
default:
return "0", fmt.Errorf("unsupported method %s", method)
}
}
func (api *PubSubAPI) unsubscribe(id rpc.ID) bool {
api.filtersMu.Lock()
defer api.filtersMu.Unlock()
if api.filters[id] == nil {
return false
}
close(api.filters[id].unsubscribed)
delete(api.filters, id)
return true
}
// unsubscribeAll unsubscribes all the current subscriptions
func (api *PubSubAPI) unsubscribeAll() bool { // nolint: unused
api.filtersMu.Lock()
defer api.filtersMu.Unlock()
for id, filter := range api.filters {
close(filter.unsubscribed)
delete(api.filters, id)
}
return true
}
func (api *PubSubAPI) subscribeNewHeads(conn *websocket.Conn) (rpc.ID, error) {
sub, _, err := api.events.SubscribeNewHeads()
if err != nil {
return "", fmt.Errorf("error creating block filter: %s", err.Error())
}
unsubscribed := make(chan struct{})
api.filtersMu.Lock()
api.filters[sub.ID()] = &wsSubscription{
sub: sub,
conn: conn,
unsubscribed: unsubscribed,
}
api.filtersMu.Unlock()
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
for {
select {
case event := <-headersCh:
data, _ := event.Data.(tmtypes.EventDataNewBlockHeader)
header := rpctypes.EthHeaderFromTendermint(data.Header)
api.filtersMu.Lock()
if f, found := api.filters[sub.ID()]; found {
// write to ws conn
res := &SubscriptionNotification{
Jsonrpc: "2.0",
Method: "eth_subscription",
Params: &SubscriptionResult{
Subscription: sub.ID(),
Result: header,
},
}
err = f.conn.WriteJSON(res)
if err != nil {
api.logger.Error("error writing header")
}
}
api.filtersMu.Unlock()
case <-errCh:
api.filtersMu.Lock()
delete(api.filters, sub.ID())
api.filtersMu.Unlock()
return
case <-unsubscribed:
return
}
}
}(sub.Event(), sub.Err())
return sub.ID(), nil
}
func (api *PubSubAPI) subscribeLogs(conn *websocket.Conn, extra interface{}) (rpc.ID, error) {
crit := filters.FilterCriteria{}
if extra != nil {
params, ok := extra.(map[string]interface{})
if !ok {
return "", fmt.Errorf("invalid criteria")
}
if params["address"] != nil {
address, ok := params["address"].(string)
addresses, sok := params["address"].([]interface{})
if !ok && !sok {
return "", fmt.Errorf("invalid address; must be address or array of addresses")
}
if ok {
crit.Addresses = []common.Address{common.HexToAddress(address)}
}
if sok {
crit.Addresses = []common.Address{}
for _, addr := range addresses {
address, ok := addr.(string)
if !ok {
return "", fmt.Errorf("invalid address")
}
crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
}
}
}
if params["topics"] != nil {
topics, ok := params["topics"].([]interface{})
if !ok {
return "", fmt.Errorf("invalid topics")
}
crit.Topics = [][]common.Hash{}
for _, topic := range topics {
tstr, ok := topic.(string)
if !ok {
return "", fmt.Errorf("invalid topics")
}
h := common.HexToHash(tstr)
crit.Topics = append(crit.Topics, []common.Hash{h})
}
}
}
sub, _, err := api.events.SubscribeLogs(crit)
if err != nil {
return rpc.ID(""), err
}
unsubscribed := make(chan struct{})
api.filtersMu.Lock()
api.filters[sub.ID()] = &wsSubscription{
sub: sub,
conn: conn,
unsubscribed: unsubscribed,
}
api.filtersMu.Unlock()
go func(ch <-chan coretypes.ResultEvent, errCh <-chan error) {
for {
select {
case event := <-ch:
dataTx, ok := event.Data.(tmtypes.EventDataTx)
if !ok {
err = fmt.Errorf("invalid event data %T, expected EventDataTx", event.Data)
return
}
var resultData evmtypes.MsgEthereumTxResponse
resultData, err = evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
return
}
logs := rpcfilters.FilterLogs(resultData.TxLogs.EthLogs(), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
api.filtersMu.Lock()
if f, found := api.filters[sub.ID()]; found {
// write to ws conn
res := &SubscriptionNotification{
Jsonrpc: "2.0",
Method: "eth_subscription",
Params: &SubscriptionResult{
Subscription: sub.ID(),
Result: logs,
},
}
err = f.conn.WriteJSON(res)
}
api.filtersMu.Unlock()
if err != nil {
err = fmt.Errorf("failed to write header: %w", err)
return
}
case <-errCh:
api.filtersMu.Lock()
delete(api.filters, sub.ID())
api.filtersMu.Unlock()
return
case <-unsubscribed:
return
}
}
}(sub.Event(), sub.Err())
return sub.ID(), nil
}
func (api *PubSubAPI) subscribePendingTransactions(conn *websocket.Conn) (rpc.ID, error) {
sub, _, err := api.events.SubscribePendingTxs()
if err != nil {
return "", fmt.Errorf("error creating block filter: %s", err.Error())
}
unsubscribed := make(chan struct{})
api.filtersMu.Lock()
api.filters[sub.ID()] = &wsSubscription{
sub: sub,
conn: conn,
unsubscribed: unsubscribed,
}
api.filtersMu.Unlock()
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
for {
select {
case ev := <-txsCh:
data, _ := ev.Data.(tmtypes.EventDataTx)
txHash := common.BytesToHash(tmtypes.Tx(data.Tx).Hash())
api.filtersMu.Lock()
if f, found := api.filters[sub.ID()]; found {
// write to ws conn
res := &SubscriptionNotification{
Jsonrpc: "2.0",
Method: "eth_subscription",
Params: &SubscriptionResult{
Subscription: sub.ID(),
Result: txHash,
},
}
err = f.conn.WriteJSON(res)
}
api.filtersMu.Unlock()
if err != nil {
err = fmt.Errorf("failed to write header: %w", err)
return
}
case <-errCh:
api.filtersMu.Lock()
delete(api.filters, sub.ID())
api.filtersMu.Unlock()
}
}
}(sub.Event(), sub.Err())
return sub.ID(), nil
}
func (api *PubSubAPI) subscribeSyncing(conn *websocket.Conn) (rpc.ID, error) {
return "", nil
}

View File

@ -1,204 +0,0 @@
package websockets
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"math/big"
"net"
"net/http"
"os"
"strings"
"github.com/gorilla/websocket"
"github.com/tendermint/tendermint/libs/log"
"github.com/ethereum/go-ethereum/rpc"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/types/rest"
)
// Server defines a server that handles Ethereum websockets.
type Server struct {
Address string
api *PubSubAPI
logger log.Logger
}
// NewServer creates a new websocket server instance.
func NewServer(clientCtx client.Context) *Server {
return &Server{
Address: "",
api: NewAPI(clientCtx),
logger: log.NewTMLogger(log.NewSyncWriter(os.Stdout)).With("module", "websocket-server"),
}
}
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
wsConn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
err := fmt.Errorf("websocket upgrade failed: %w", err)
rest.WriteErrorResponse(w, http.StatusInternalServerError, err.Error())
return
}
s.readLoop(wsConn)
}
func (s *Server) sendErrResponse(conn *websocket.Conn, msg string) {
res := &ErrorResponseJSON{
Jsonrpc: "2.0",
Error: &ErrorMessageJSON{
Code: big.NewInt(-32600),
Message: msg,
},
ID: nil,
}
err := conn.WriteJSON(res)
if err != nil {
s.logger.Error("websocket failed write message", "error", err)
}
}
func (s *Server) readLoop(wsConn *websocket.Conn) {
for {
_, mb, err := wsConn.ReadMessage()
if err != nil {
_ = wsConn.Close()
s.logger.Error("failed to read message; error", err)
return
}
var msg map[string]interface{}
err = json.Unmarshal(mb, &msg)
if err != nil {
s.sendErrResponse(wsConn, "invalid request")
continue
}
// check if method == eth_subscribe or eth_unsubscribe
method := msg["method"]
if method.(string) == "eth_subscribe" {
params := msg["params"].([]interface{})
if len(params) == 0 {
s.sendErrResponse(wsConn, "invalid parameters")
continue
}
id, err := s.api.subscribe(wsConn, params)
if err != nil {
s.sendErrResponse(wsConn, err.Error())
continue
}
res := &SubscriptionResponseJSON{
Jsonrpc: "2.0",
ID: 1,
Result: id,
}
err = wsConn.WriteJSON(res)
if err != nil {
s.logger.Error("failed to write json response", err)
continue
}
continue
} else if method.(string) == "eth_unsubscribe" {
ids, ok := msg["params"].([]interface{})
if _, idok := ids[0].(string); !ok || !idok {
s.sendErrResponse(wsConn, "invalid parameters")
continue
}
ok = s.api.unsubscribe(rpc.ID(ids[0].(string)))
res := &SubscriptionResponseJSON{
Jsonrpc: "2.0",
ID: 1,
Result: ok,
}
err = wsConn.WriteJSON(res)
if err != nil {
s.logger.Error("failed to write json response", err)
continue
}
continue
}
// otherwise, call the usual rpc server to respond
err = s.tcpGetAndSendResponse(wsConn, mb)
if err != nil {
s.sendErrResponse(wsConn, err.Error())
}
}
}
// tcpGetAndSendResponse connects to the rest-server over tcp, posts a JSON-RPC request, and sends the response
// to the client over websockets
func (s *Server) tcpGetAndSendResponse(conn *websocket.Conn, mb []byte) error {
addr := strings.Split(s.Address, "tcp://")
if len(addr) != 2 {
return fmt.Errorf("invalid laddr %s", s.Address)
}
tcpConn, err := net.Dial("tcp", addr[1])
if err != nil {
return fmt.Errorf("cannot connect to %s; %s", s.Address, err)
}
buf := &bytes.Buffer{}
_, err = buf.Write(mb)
if err != nil {
return fmt.Errorf("failed to write message; %s", err)
}
req, err := http.NewRequest("POST", s.Address, buf)
if err != nil {
return fmt.Errorf("failed to request; %s", err)
}
req.Header.Set("Content-Type", "application/json;")
err = req.Write(tcpConn)
if err != nil {
return fmt.Errorf("failed to write to rest-server; %s", err)
}
respBytes, err := ioutil.ReadAll(tcpConn)
if err != nil {
return fmt.Errorf("error reading response from rest-server; %s", err)
}
respbuf := &bytes.Buffer{}
respbuf.Write(respBytes)
resp, err := http.ReadResponse(bufio.NewReader(respbuf), req)
if err != nil {
return fmt.Errorf("could not read response; %s", err)
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("could not read body from response; %s", err)
}
var wsSend interface{}
err = json.Unmarshal(body, &wsSend)
if err != nil {
return fmt.Errorf("failed to unmarshal rest-server response; %s", err)
}
return conn.WriteJSON(wsSend)
}

View File

@ -1,45 +0,0 @@
package websockets
import (
"math/big"
"github.com/gorilla/websocket"
"github.com/ethereum/go-ethereum/rpc"
rpcfilters "github.com/cosmos/ethermint/rpc/namespaces/eth/filters"
)
type SubscriptionResponseJSON struct {
Jsonrpc string `json:"jsonrpc"`
Result interface{} `json:"result"`
ID float64 `json:"id"`
}
type SubscriptionNotification struct {
Jsonrpc string `json:"jsonrpc"`
Method string `json:"method"`
Params *SubscriptionResult `json:"params"`
}
type SubscriptionResult struct {
Subscription rpc.ID `json:"subscription"`
Result interface{} `json:"result"`
}
type ErrorResponseJSON struct {
Jsonrpc string `json:"jsonrpc"`
Error *ErrorMessageJSON `json:"error"`
ID *big.Int `json:"id"`
}
type ErrorMessageJSON struct {
Code *big.Int `json:"code"`
Message string `json:"message"`
}
type wsSubscription struct {
sub *rpcfilters.Subscription
unsubscribed chan struct{} // closed when unsubscribing
conn *websocket.Conn
}

View File

@ -89,7 +89,7 @@ option objc_class_prefix = "GPB";
// The pack methods provided by protobuf library will by default use
// 'type.googleapis.com/full.type.name' as the type URL and the unpack
// methods only use the fully qualified type name after the last '/'
// in the type URL, for example "foo.bar.com/modules/y.z" will yield type
// in the type URL, for example "foo.bar.com/x/y.z" will yield type
// name "y.z".
//
//

35
version/version.go Executable file
View File

@ -0,0 +1,35 @@
package version
import (
"fmt"
"runtime"
)
var (
AppVersion = ""
GitCommit = ""
BuildDate = ""
GoVersion = ""
GoArch = ""
)
func init() {
if len(AppVersion) == 0 {
AppVersion = "dev"
}
GoVersion = runtime.Version()
GoArch = runtime.GOARCH
}
func Version() string {
return fmt.Sprintf(
"Version %s (%s)\nCompiled at %s using Go %s (%s)",
AppVersion,
GitCommit,
BuildDate,
GoVersion,
GoArch,
)
}

View File

@ -47,7 +47,7 @@ func (suite *EvmTestSuite) TestInitGenesis() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
err := suite.app.BankKeeper.SetBalance(suite.ctx, address.Bytes(), ethermint.NewInjectiveCoinInt64(1))
err := suite.app.BankKeeper.SetBalance(suite.ctx, address.Bytes(), ethermint.NewPhotonCoinInt64(1))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
},

View File

@ -25,7 +25,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewInjectiveCoinInt64(1))
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewPhotonCoinInt64(1))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
@ -38,7 +38,7 @@ func (suite *KeeperTestSuite) TestBalanceInvariant() {
func() {
acc := suite.app.AccountKeeper.NewAccountWithAddress(suite.ctx, address.Bytes())
suite.Require().NotNil(acc)
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewInjectiveCoinInt64(1))
suite.app.BankKeeper.SetBalance(suite.ctx, acc.GetAddress(), ethermint.NewPhotonCoinInt64(1))
suite.Require().NoError(err)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)

View File

@ -44,7 +44,7 @@ func (suite *KeeperTestSuite) SetupTest() {
suite.ctx = suite.app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1, ChainID: "3", Time: time.Now().UTC()})
suite.address = ethcmn.HexToAddress(addrHex)
balance := ethermint.NewInjectiveCoin(sdk.ZeroInt())
balance := ethermint.NewPhotonCoin(sdk.ZeroInt())
acc := &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),

View File

@ -62,7 +62,7 @@ func (suite *JournalTestSuite) SetupTest() {
suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes())
suite.journal = newJournal()
balance := ethermint.NewInjectiveCoin(sdk.NewInt(100))
balance := ethermint.NewPhotonCoin(sdk.NewInt(100))
acc := &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),

View File

@ -17,7 +17,7 @@ func (suite *StateDBTestSuite) TestTransitionDb() {
suite.stateDB.SetNonce(suite.address, 123)
addr := sdk.AccAddress(suite.address.Bytes())
balance := ethermint.NewInjectiveCoin(sdk.NewInt(5000))
balance := ethermint.NewPhotonCoin(sdk.NewInt(5000))
acc := suite.app.AccountKeeper.GetAccount(suite.ctx, addr)
suite.app.AccountKeeper.SetAccount(suite.ctx, acc)
suite.app.BankKeeper.SetBalance(suite.ctx, addr, balance)

View File

@ -48,7 +48,7 @@ func (suite *StateDBTestSuite) SetupTest() {
suite.address = ethcmn.BytesToAddress(privkey.PubKey().Address().Bytes())
balance := ethermint.NewInjectiveCoin(sdk.ZeroInt())
balance := ethermint.NewPhotonCoin(sdk.ZeroInt())
acc := &ethermint.EthAccount{
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
CodeHash: ethcrypto.Keccak256(nil),