forked from cerc-io/laconicd-deprecated
more fixes
This commit is contained in:
parent
614e62fb7e
commit
cb2ab3d95d
@ -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)
|
||||
}
|
||||
|
@ -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}
|
||||
|
210
app/ante/eth.go
210
app/ante/eth.go
@ -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 account’s
|
||||
// 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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
141
app/ethermint.go
141
app/ethermint.go
@ -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,
|
||||
)
|
||||
@ -419,25 +424,25 @@ func NewEthermintApp(
|
||||
// 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,
|
||||
//)
|
||||
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()
|
||||
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
|
||||
}
|
||||
|
@ -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
69
client/docs/config.json
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
13
client/docs/statik/statik.go
Normal file
13
client/docs/statik/statik.go
Normal file
File diff suppressed because one or more lines are too long
BIN
client/docs/swagger-ui/favicon-16x16.png
Normal file
BIN
client/docs/swagger-ui/favicon-16x16.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 445 B |
BIN
client/docs/swagger-ui/favicon-32x32.png
Normal file
BIN
client/docs/swagger-ui/favicon-32x32.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
60
client/docs/swagger-ui/index.html
Normal file
60
client/docs/swagger-ui/index.html
Normal 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>
|
67
client/docs/swagger-ui/oauth2-redirect.html
Normal file
67
client/docs/swagger-ui/oauth2-redirect.html
Normal 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>
|
41776
client/docs/swagger-ui/swagger-ui-bundle.js
Normal file
41776
client/docs/swagger-ui/swagger-ui-bundle.js
Normal file
File diff suppressed because it is too large
Load Diff
13173
client/docs/swagger-ui/swagger-ui-standalone-preset.js
Normal file
13173
client/docs/swagger-ui/swagger-ui-standalone-preset.js
Normal file
File diff suppressed because it is too large
Load Diff
3
client/docs/swagger-ui/swagger-ui.css
Normal file
3
client/docs/swagger-ui/swagger-ui.css
Normal file
File diff suppressed because one or more lines are too long
23716
client/docs/swagger-ui/swagger.yaml
Normal file
23716
client/docs/swagger-ui/swagger.yaml
Normal file
File diff suppressed because it is too large
Load Diff
@ -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)")
|
||||
|
242
cmd/ethermintd/config/config.go
Normal file
242
cmd/ethermintd/config/config.go
Normal 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"),
|
||||
},
|
||||
}
|
||||
}
|
20
cmd/ethermintd/config/config_test.go
Normal file
20
cmd/ethermintd/config/config_test.go
Normal 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)
|
||||
}
|
212
cmd/ethermintd/config/toml.go
Normal file
212
cmd/ethermintd/config/toml.go
Normal 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
90
cmd/ethermintd/flags.go
Normal 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(ðNodeWS, "eth-node-ws", "ws://localhost:1317", "WebSocket endpoint for an Ethereum node.")
|
||||
cmd.PersistentFlags().StringVar(ðNodeHTTP, "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
467
cmd/ethermintd/start.go
Normal 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
54
ethereum/rpc/apis.go
Normal 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
338
ethereum/rpc/backend.go
Normal 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
760
ethereum/rpc/eth_api.go
Normal 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
|
||||
}
|
@ -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
|
||||
}
|
345
ethereum/rpc/filter_system.go
Normal file
345
ethereum/rpc/filter_system.go
Normal 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
302
ethereum/rpc/filters.go
Normal 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
|
||||
}
|
@ -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)
|
||||
}
|
128
ethereum/rpc/pubsub/pubsub.go
Normal file
128
ethereum/rpc/pubsub/pubsub.go
Normal 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:
|
||||
}
|
||||
}
|
||||
}
|
96
ethereum/rpc/pubsub/pubsub_test.go
Normal file
96
ethereum/rpc/pubsub/pubsub_test.go
Normal 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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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"
|
||||
)
|
@ -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.
|
@ -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
242
ethereum/rpc/utils.go
Normal 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 ðtypes.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
|
||||
}
|
@ -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
747
ethereum/rpc/websockets.go
Normal 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
11
go.mod
@ -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
33
go.sum
@ -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=
|
||||
|
87
rpc/apis.go
87
rpc/apis.go
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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
@ -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
|
||||
}
|
||||
}
|
||||
// }()
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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
|
||||
}
|
@ -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 := ðsecp256k1.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, ðsecp256k1.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
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
2
third_party/proto/google/protobuf/any.proto
vendored
2
third_party/proto/google/protobuf/any.proto
vendored
@ -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
35
version/version.go
Executable 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,
|
||||
)
|
||||
}
|
@ -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)
|
||||
},
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 := ðermint.EthAccount{
|
||||
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
|
||||
CodeHash: ethcrypto.Keccak256(nil),
|
||||
|
@ -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 := ðermint.EthAccount{
|
||||
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
|
||||
CodeHash: ethcrypto.Keccak256(nil),
|
||||
|
@ -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)
|
||||
|
@ -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 := ðermint.EthAccount{
|
||||
BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(suite.address.Bytes()), nil, 0, 0),
|
||||
CodeHash: ethcrypto.Keccak256(nil),
|
||||
|
Loading…
Reference in New Issue
Block a user