more fixes
This commit is contained in:
+82
-18
@@ -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:
|
||||
anteHandler = sdk.ChainAnteDecorators(
|
||||
NewEthSetupContextDecorator(), // outermost AnteDecorator. EthSetUpContext must be called first
|
||||
authante.NewRejectExtensionOptionsDecorator(),
|
||||
NewEthMempoolFeeDecorator(evmKeeper),
|
||||
authante.NewValidateBasicDecorator(),
|
||||
NewEthSigVerificationDecorator(),
|
||||
NewAccountVerificationDecorator(ak, bankKeeper, evmKeeper),
|
||||
NewNonceVerificationDecorator(ak),
|
||||
NewEthGasConsumeDecorator(ak, bankKeeper, evmKeeper),
|
||||
NewIncrementSenderSequenceDecorator(ak), // innermost AnteDecorator.
|
||||
)
|
||||
|
||||
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
|
||||
NewEthMempoolFeeDecorator(evmKeeper),
|
||||
NewEthValidateBasicDecorator(),
|
||||
authante.TxTimeoutHeightDecorator{},
|
||||
NewEthSigVerificationDecorator(),
|
||||
NewEthAccountSetupDecorator(ak),
|
||||
NewEthAccountVerificationDecorator(ak, bankKeeper, evmKeeper),
|
||||
NewEthNonceVerificationDecorator(ak),
|
||||
NewEthGasConsumeDecorator(ak, bankKeeper, evmKeeper),
|
||||
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}
|
||||
|
||||
+159
-51
@@ -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)
|
||||
}
|
||||
|
||||
+20
-20
@@ -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
|
||||
}
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
+85
-66
@@ -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,8 +322,8 @@ 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))
|
||||
app.GovKeeper = govkeeper.NewKeeper(
|
||||
AddRoute(ibchost.RouterKey, ibcclient.NewClientUpdateProposalHandler(app.IBCKeeper.ClientKeeper)).
|
||||
app.GovKeeper = govkeeper.NewKeeper(
|
||||
appCodec, keys[govtypes.StoreKey], app.GetSubspace(govtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
|
||||
&stakingKeeper, govRouter,
|
||||
)
|
||||
@@ -341,16 +348,13 @@ func NewEthermintApp(
|
||||
// If evidence needs to be handled for the app, set routes in router here and seal
|
||||
app.EvidenceKeeper = *evidenceKeeper
|
||||
|
||||
// Create Ethermint keepers
|
||||
app.EvmKeeper = evmkeeper.NewKeeper(
|
||||
appCodec, keys[evmtypes.StoreKey], app.GetSubspace(evmtypes.ModuleName), app.AccountKeeper, app.BankKeeper,
|
||||
)
|
||||
|
||||
/**** Module Options ****/
|
||||
|
||||
// TODO: do properly
|
||||
// NOTE: we may consider parsing `appOpts` inside module constructors. For the moment
|
||||
// we prefer to be more strict in what arguments the modules expect.
|
||||
var skipGenesisInvariants = cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
|
||||
//var skipGenesisInvariants = cast.ToBool(appOpts.Get(crisis.FlagSkipGenesisInvariants))
|
||||
skipGenesisInvariants := true
|
||||
|
||||
// NOTE: Any module instantiated in the module manager that is later modified
|
||||
// must be passed by reference here.
|
||||
@@ -375,7 +379,7 @@ func NewEthermintApp(
|
||||
ibc.NewAppModule(app.IBCKeeper),
|
||||
params.NewAppModule(app.ParamsKeeper),
|
||||
transferModule,
|
||||
// Ethermint app modules
|
||||
// Injective app modules
|
||||
evm.NewAppModule(app.EvmKeeper, app.AccountKeeper, app.BankKeeper),
|
||||
)
|
||||
|
||||
@@ -391,8 +395,8 @@ func NewEthermintApp(
|
||||
evidencetypes.ModuleName, stakingtypes.ModuleName, ibchost.ModuleName,
|
||||
)
|
||||
app.mm.SetOrderEndBlockers(
|
||||
evmtypes.ModuleName,
|
||||
crisistypes.ModuleName, govtypes.ModuleName, stakingtypes.ModuleName,
|
||||
evmtypes.ModuleName,
|
||||
)
|
||||
|
||||
// NOTE: The genutils module must occur after staking so that pools are
|
||||
@@ -405,8 +409,9 @@ func NewEthermintApp(
|
||||
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName,
|
||||
slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName,
|
||||
ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, ibctransfertypes.ModuleName,
|
||||
// Ethermint modules
|
||||
// Injective modules
|
||||
evmtypes.ModuleName,
|
||||
|
||||
// NOTE: crisis module must go at the end to check for invariants on each module
|
||||
crisistypes.ModuleName,
|
||||
)
|
||||
@@ -415,29 +420,29 @@ func NewEthermintApp(
|
||||
app.mm.RegisterRoutes(app.Router(), app.QueryRouter(), encodingConfig.Amino)
|
||||
app.mm.RegisterServices(module.NewConfigurator(app.MsgServiceRouter(), app.GRPCQueryRouter()))
|
||||
|
||||
// add test gRPC service for testing gRPC queries in isolation
|
||||
//add test gRPC service for testing gRPC queries in isolation
|
||||
// testdata.RegisterTestServiceServer(app.GRPCQueryRouter(), testdata.TestServiceImpl{})
|
||||
|
||||
// create the simulation manager and define the order of the modules for deterministic simulations
|
||||
//
|
||||
// NOTE: this is not required apps that don't use the simulator for fuzz testing
|
||||
// transactions
|
||||
// app.sm = module.NewSimulationManager(
|
||||
// auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
|
||||
// bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
|
||||
// capability.NewAppModule(appCodec, *app.CapabilityKeeper),
|
||||
// gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
|
||||
// mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
|
||||
// staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
|
||||
// distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
||||
// slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
||||
// params.NewAppModule(app.ParamsKeeper),
|
||||
// evidence.NewAppModule(app.EvidenceKeeper),
|
||||
// ibc.NewAppModule(app.IBCKeeper),
|
||||
// transferModule,
|
||||
//)
|
||||
//create the simulation manager and define the order of the modules for deterministic simulations
|
||||
|
||||
// app.sm.RegisterStoreDecoders()
|
||||
//NOTE: this is not required apps that don't use the simulator for fuzz testing
|
||||
// transactions
|
||||
app.sm = module.NewSimulationManager(
|
||||
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
|
||||
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
|
||||
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
|
||||
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
|
||||
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
|
||||
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
|
||||
distr.NewAppModule(appCodec, app.DistrKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
||||
slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
||||
params.NewAppModule(app.ParamsKeeper),
|
||||
evidence.NewAppModule(app.EvidenceKeeper),
|
||||
ibc.NewAppModule(app.IBCKeeper),
|
||||
transferModule,
|
||||
)
|
||||
|
||||
app.sm.RegisterStoreDecoders()
|
||||
|
||||
// initialize stores
|
||||
app.MountKVStores(keys)
|
||||
@@ -448,13 +453,14 @@ func NewEthermintApp(
|
||||
app.SetInitChainer(app.InitChainer)
|
||||
app.SetBeginBlocker(app.BeginBlocker)
|
||||
|
||||
// use Ethermint's custom AnteHandler
|
||||
// use Injective's custom AnteHandler
|
||||
app.SetAnteHandler(
|
||||
ante.NewAnteHandler(
|
||||
app.AccountKeeper, app.BankKeeper, app.EvmKeeper,
|
||||
encodingConfig.TxConfig.SignModeHandler(),
|
||||
),
|
||||
)
|
||||
|
||||
app.SetEndBlocker(app.EndBlocker)
|
||||
|
||||
if loadLatest {
|
||||
@@ -495,9 +501,7 @@ func (app *EthermintApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) a
|
||||
// InitChainer updates at chain initialization
|
||||
func (app *EthermintApp) InitChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
|
||||
var genesisState simapp.GenesisState
|
||||
if err := tmjson.Unmarshal(req.AppStateBytes, &genesisState); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
app.cdc.MustUnmarshalJSON(req.AppStateBytes, &genesisState)
|
||||
return app.mm.InitGenesis(ctx, app.appCodec, genesisState)
|
||||
}
|
||||
|
||||
@@ -578,36 +582,52 @@ func (app *EthermintApp) GetSubspace(moduleName string) paramstypes.Subspace {
|
||||
}
|
||||
|
||||
// SimulationManager implements the SimulationApp interface
|
||||
// func (app *EthermintApp) SimulationManager() *module.SimulationManager {
|
||||
// return app.sm
|
||||
//}
|
||||
func (app *EthermintApp) SimulationManager() *module.SimulationManager {
|
||||
return app.sm
|
||||
}
|
||||
|
||||
// RegisterAPIRoutes registers all application module routes with the provided
|
||||
// API server.
|
||||
func (app *EthermintApp) RegisterAPIRoutes(apiSvr *sdkapi.Server, apiConfig sdkconfig.APIConfig) {
|
||||
func (app *EthermintApp) RegisterAPIRoutes(apiSvr *api.Server, apiConfig config.APIConfig) {
|
||||
clientCtx := apiSvr.ClientCtx
|
||||
rpc.RegisterRoutes(clientCtx, apiSvr.Router)
|
||||
|
||||
evmrest.RegisterTxRoutes(clientCtx, apiSvr.Router)
|
||||
|
||||
// Register new tx routes from grpc-gateway.
|
||||
authtx.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
||||
// Register new tendermint queries routes from grpc-gateway.
|
||||
tmservice.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
||||
|
||||
// Register legacy and grpc-gateway routes for all modules.
|
||||
ModuleBasics.RegisterRESTRoutes(clientCtx, apiSvr.Router)
|
||||
ModuleBasics.RegisterGRPCGatewayRoutes(apiSvr.ClientCtx, apiSvr.GRPCGatewayRouter)
|
||||
ModuleBasics.RegisterGRPCGatewayRoutes(clientCtx, apiSvr.GRPCGatewayRouter)
|
||||
|
||||
// register swagger API from root so that other applications can override easily
|
||||
if apiConfig.Swagger {
|
||||
simapp.RegisterSwaggerAPI(clientCtx, apiSvr.Router)
|
||||
RegisterSwaggerAPI(clientCtx, apiSvr.Router)
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterTxService implements the Application.RegisterTxService method.
|
||||
func (app *EthermintApp) RegisterTxService(clientCtx client.Context) {
|
||||
authtx.RegisterTxService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.BaseApp.Simulate, app.interfaceRegistry)
|
||||
}
|
||||
|
||||
// RegisterTendermintService implements the Application.RegisterTendermintService method.
|
||||
func (app *EthermintApp) RegisterTendermintService(clientCtx client.Context) {
|
||||
tmservice.RegisterTendermintService(app.BaseApp.GRPCQueryRouter(), clientCtx, app.interfaceRegistry)
|
||||
}
|
||||
|
||||
// RegisterSwaggerAPI registers swagger route with API Server
|
||||
func RegisterSwaggerAPI(ctx client.Context, rtr *mux.Router) {
|
||||
statikFS, err := fs.New()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
staticServer := http.FileServer(statikFS)
|
||||
rtr.PathPrefix("/swagger/").Handler(http.StripPrefix("/swagger/", staticServer))
|
||||
}
|
||||
|
||||
// GetMaccPerms returns a copy of the module account permissions
|
||||
func GetMaccPerms() map[string][]string {
|
||||
dupMaccPerms := make(map[string][]string)
|
||||
@@ -636,6 +656,5 @@ func initParamsKeeper(
|
||||
paramsKeeper.Subspace(ibchost.ModuleName)
|
||||
// ethermint subspaces
|
||||
paramsKeeper.Subspace(evmtypes.ModuleName)
|
||||
|
||||
return paramsKeeper
|
||||
}
|
||||
|
||||
+2
-1
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"title": "Ethermint Chain - Legacy REST and gRPC Gateway docs",
|
||||
"description": "A REST interface for state queries, legacy transactions",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"apis": [
|
||||
{
|
||||
"url": "./tmp-swagger-gen/ethermint/evm/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Params": "EvmParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "./tmp-swagger-gen/cosmos/auth/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Account": "AuthAccount",
|
||||
"Params": "AuthParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "./tmp-swagger-gen/cosmos/bank/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Balance": "BankBalance",
|
||||
"Params": "BankParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "./tmp-swagger-gen/cosmos/distribution/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Params": "DistributionParams",
|
||||
"DelegatorValidators": "DistDelegatorValidators"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "./tmp-swagger-gen/cosmos/mint/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Params": "MintParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "./tmp-swagger-gen/cosmos/gov/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Params": "GovParams"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"url": "./tmp-swagger-gen/cosmos/staking/v1beta1/query.swagger.json",
|
||||
"operationIds": {
|
||||
"rename": {
|
||||
"Params": "StakingParams"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
|
After Width: | Height: | Size: 445 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 KiB |
@@ -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>
|
||||
@@ -0,0 +1,67 @@
|
||||
<!doctype html>
|
||||
<html lang="en-US">
|
||||
<body onload="run()">
|
||||
</body>
|
||||
</html>
|
||||
<script>
|
||||
'use strict';
|
||||
function run () {
|
||||
var oauth2 = window.opener.swaggerUIRedirectOauth2;
|
||||
var sentState = oauth2.state;
|
||||
var redirectUrl = oauth2.redirectUrl;
|
||||
var isValid, qp, arr;
|
||||
|
||||
if (/code|token|error/.test(window.location.hash)) {
|
||||
qp = window.location.hash.substring(1);
|
||||
} else {
|
||||
qp = location.search.substring(1);
|
||||
}
|
||||
|
||||
arr = qp.split("&")
|
||||
arr.forEach(function (v,i,_arr) { _arr[i] = '"' + v.replace('=', '":"') + '"';})
|
||||
qp = qp ? JSON.parse('{' + arr.join() + '}',
|
||||
function (key, value) {
|
||||
return key === "" ? value : decodeURIComponent(value)
|
||||
}
|
||||
) : {}
|
||||
|
||||
isValid = qp.state === sentState
|
||||
|
||||
if ((
|
||||
oauth2.auth.schema.get("flow") === "accessCode"||
|
||||
oauth2.auth.schema.get("flow") === "authorizationCode"
|
||||
) && !oauth2.auth.code) {
|
||||
if (!isValid) {
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "warning",
|
||||
message: "Authorization may be unsafe, passed state was changed in server Passed state wasn't returned from auth server"
|
||||
});
|
||||
}
|
||||
|
||||
if (qp.code) {
|
||||
delete oauth2.state;
|
||||
oauth2.auth.code = qp.code;
|
||||
oauth2.callback({auth: oauth2.auth, redirectUrl: redirectUrl});
|
||||
} else {
|
||||
let oauthErrorMsg
|
||||
if (qp.error) {
|
||||
oauthErrorMsg = "["+qp.error+"]: " +
|
||||
(qp.error_description ? qp.error_description+ ". " : "no accessCode received from the server. ") +
|
||||
(qp.error_uri ? "More info: "+qp.error_uri : "");
|
||||
}
|
||||
|
||||
oauth2.errCb({
|
||||
authId: oauth2.auth.name,
|
||||
source: "auth",
|
||||
level: "error",
|
||||
message: oauthErrorMsg || "[Authorization failed]: no accessCode received from the server"
|
||||
});
|
||||
}
|
||||
} else {
|
||||
oauth2.callback({auth: oauth2.auth, token: qp, isValid: isValid, redirectUrl: redirectUrl});
|
||||
}
|
||||
window.close();
|
||||
}
|
||||
</script>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
+3
-3
@@ -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)")
|
||||
|
||||
@@ -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"),
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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,
|
||||
)
|
||||
}
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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),
|
||||
backend: backend,
|
||||
filters: make(map[rpc.ID]*filter),
|
||||
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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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:
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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,18 +48,23 @@ 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()),
|
||||
Hash: txHash,
|
||||
Input: hexutil.Bytes(tx.Data.Payload),
|
||||
Nonce: hexutil.Uint64(tx.Data.AccountNonce),
|
||||
To: tx.To(),
|
||||
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)),
|
||||
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)(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)),
|
||||
}
|
||||
|
||||
if blockHash != (common.Hash{}) {
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
+1
-1
@@ -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".
|
||||
//
|
||||
//
|
||||
|
||||
Executable
+35
@@ -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),
|
||||
|
||||
Reference in New Issue
Block a user