diff --git a/.gitignore b/.gitignore
index 1f732223..62dfce57 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@ vendor/
playground.go
/tmp-swagger-gen/
/github.com/
+.DS_Store
diff --git a/app/ante/eth.go b/app/ante/eth.go
index b354e489..2a9d11b1 100644
--- a/app/ante/eth.go
+++ b/app/ante/eth.go
@@ -17,6 +17,41 @@ import (
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
)
+// EthTxPayloadVerificationDecorator validates base tx payload and check some limitations
+type EthTxPayloadVerificationDecorator struct {
+}
+
+func NewEthTxPayloadVerificationDecorator() EthTxPayloadVerificationDecorator {
+ return EthTxPayloadVerificationDecorator{}
+}
+
+// AnteHandle validates msgs count, some signature protection and applied limitations
+func (tpvd EthTxPayloadVerificationDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
+ if len(tx.GetMsgs()) > 1 {
+ return ctx, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "evm transactions only operates with 1 msg")
+ }
+
+ for _, msg := range tx.GetMsgs() {
+ msgEthTx, ok := msg.(*evmtypes.MsgEthereumTx)
+ if !ok {
+ return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "invalid message type %T, expected %T", msg, (*evmtypes.MsgEthereumTx)(nil))
+ }
+
+ ethTx := msgEthTx.AsTransaction()
+ // EIP-155 only allowed
+ if !ethTx.Protected() {
+ return ctx, sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "legacy pre-eip-155 transactions not supported")
+ }
+
+ // forbid EIP-2930 update with access list
+ if len(ethTx.AccessList()) > 0 {
+ return ctx, sdkerrors.Wrapf(sdkerrors.ErrNotSupported, "eip-2930 transactions not supported")
+ }
+ }
+
+ return next(ctx, tx, simulate)
+}
+
// EthSigVerificationDecorator validates an ethereum signatures
type EthSigVerificationDecorator struct {
evmKeeper EVMKeeper
diff --git a/app/ante/handler_options.go b/app/ante/handler_options.go
index 2c122484..a55f9589 100644
--- a/app/ante/handler_options.go
+++ b/app/ante/handler_options.go
@@ -48,6 +48,8 @@ func newEthAnteHandler(options HandlerOptions) sdk.AnteHandler {
NewEthSetUpContextDecorator(options.EvmKeeper), // outermost AnteDecorator. SetUpContext must be called first
NewEthMempoolFeeDecorator(options.EvmKeeper), // Check eth effective gas price against minimal-gas-prices
NewEthValidateBasicDecorator(options.EvmKeeper),
+ // TODO: UNCOMMENT THIS FOR PROD!!!!!!
+ // NewEthTxPayloadVerificationDecorator(),
NewEthSigVerificationDecorator(options.EvmKeeper),
NewEthAccountVerificationDecorator(options.AccountKeeper, options.EvmKeeper),
NewEthGasConsumeDecorator(options.EvmKeeper, options.MaxTxGasWanted),
diff --git a/app/app.go b/app/app.go
index b1ec71b7..3fe367cc 100644
--- a/app/app.go
+++ b/app/app.go
@@ -192,6 +192,10 @@ var (
}
)
+type EVMLKeeperApp interface {
+ GetEVMKeeper() *evmkeeper.Keeper
+}
+
type NewApp struct {
*baseapp.BaseApp
@@ -751,6 +755,10 @@ func (app *NewApp) GetPotKeeper() potkeeper.Keeper {
return app.potKeeper
}
+func (app *NewApp) GetEVMKeeper() *evmkeeper.Keeper {
+ return app.evmKeeper
+}
+
// RegisterSwaggerAPI registers swagger route with API Server
func RegisterSwaggerAPI(_ client.Context, rtr *mux.Router) {
statikFS, err := fs.New()
diff --git a/go.mod b/go.mod
index 2d2878b2..9d31e1cb 100644
--- a/go.mod
+++ b/go.mod
@@ -9,18 +9,17 @@ require (
github.com/cosmos/go-bip39 v1.0.0
github.com/cosmos/ibc-go/v3 v3.0.0
github.com/davecgh/go-spew v1.1.1
+ github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf
github.com/ethereum/go-ethereum v1.10.26
github.com/gogo/protobuf v1.3.3
github.com/golang/protobuf v1.5.2
github.com/gorilla/mux v1.8.0
- github.com/gorilla/websocket v1.5.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/improbable-eng/grpc-web v0.15.0
github.com/ipfs/go-cid v0.1.0
github.com/pkg/errors v0.9.1
github.com/rakyll/statik v0.1.7
github.com/regen-network/cosmos-proto v0.3.1
- github.com/rs/cors v1.8.2
github.com/spf13/cast v1.5.0
github.com/spf13/cobra v1.6.0
github.com/spf13/pflag v1.0.5
@@ -58,6 +57,7 @@ require (
github.com/cosmos/gorocksdb v1.2.0 // indirect
github.com/cosmos/iavl v0.19.4 // indirect
github.com/cosmos/ledger-cosmos-go v0.12.2 // indirect
+ github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect
github.com/creachadair/taskgroup v0.3.2 // indirect
github.com/danieljoos/wincred v1.1.2 // indirect
github.com/deckarep/golang-set v1.8.0 // indirect
@@ -66,10 +66,12 @@ require (
github.com/dgraph-io/badger/v2 v2.2007.2 // indirect
github.com/dgraph-io/ristretto v0.0.3 // indirect
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
+ github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/dvsekhvalnov/jose2go v1.5.0 // indirect
github.com/edsrzf/mmap-go v1.0.0 // indirect
github.com/felixge/httpsnoop v1.0.1 // indirect
+ github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 // indirect
github.com/fsnotify/fsnotify v1.6.0 // indirect
github.com/gballet/go-libpcsclite v0.0.0-20190607065134-2772fd86a8ff // indirect
github.com/go-kit/kit v0.12.0 // indirect
@@ -77,18 +79,22 @@ require (
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/go-ole/go-ole v1.2.1 // indirect
github.com/go-playground/validator/v10 v10.4.1 // indirect
+ github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect
github.com/go-stack/stack v1.8.0 // indirect
github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 // indirect
github.com/gogo/gateway v1.1.0 // indirect
+ github.com/golang-jwt/jwt/v4 v4.3.0 // indirect
github.com/golang/snappy v0.0.4 // indirect
github.com/google/btree v1.0.0 // indirect
github.com/google/orderedcode v0.0.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/gorilla/handlers v1.5.1 // indirect
+ github.com/gorilla/websocket v1.5.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 // indirect
github.com/gsterjov/go-libsecret v0.0.0-20161001094733-a6f4afe4910c // indirect
github.com/gtank/merlin v0.1.1 // indirect
github.com/gtank/ristretto255 v0.1.2 // indirect
+ github.com/hashicorp/go-bexpr v0.1.10 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/golang-lru v0.5.5-0.20210104140557-80c98217689d // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
@@ -114,6 +120,7 @@ require (
github.com/minio/highwayhash v1.0.2 // indirect
github.com/minio/sha256-simd v1.0.0 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
+ github.com/mitchellh/pointerstructure v1.2.0 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/mtibben/percent v0.2.1 // indirect
github.com/multiformats/go-base32 v0.0.3 // indirect
@@ -134,7 +141,9 @@ require (
github.com/prometheus/tsdb v0.7.1 // indirect
github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475 // indirect
github.com/rjeczalik/notify v0.9.1 // indirect
+ github.com/rs/cors v1.8.2 // indirect
github.com/rs/zerolog v1.27.0 // indirect
+ github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sasha-s/go-deadlock v0.3.1 // indirect
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
github.com/spf13/afero v1.9.2 // indirect
@@ -146,6 +155,8 @@ require (
github.com/tidwall/btree v1.5.0 // indirect
github.com/tklauser/go-sysconf v0.3.5 // indirect
github.com/tklauser/numcpus v0.2.2 // indirect
+ github.com/urfave/cli/v2 v2.10.2 // indirect
+ github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
github.com/zondax/hid v0.9.1 // indirect
github.com/zondax/ledger-go v0.14.1 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
diff --git a/go.sum b/go.sum
index f3ad16d0..80a95e03 100644
--- a/go.sum
+++ b/go.sum
@@ -188,7 +188,6 @@ github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 h1:DdzS1m6o/pCqeZ
github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76/go.mod h1:0mkLWIoZuQ7uBoospo5Q9zIpqq6rYCPJDSUdeCJvPM8=
github.com/cosmos/ledger-cosmos-go v0.12.2 h1:/XYaBlE2BJxtvpkHiBm97gFGSGmYGKunKyF3nNqAXZA=
github.com/cosmos/ledger-cosmos-go v0.12.2/go.mod h1:ZcqYgnfNJ6lAXe4HPtWgarNEY+B74i+2/8MhZw4ziiI=
-github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
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.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
@@ -196,6 +195,7 @@ github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46t
github.com/creachadair/taskgroup v0.3.2 h1:zlfutDS+5XG40AOxcHDSThxKzns8Tnr9jnr6VqkYlkM=
github.com/creachadair/taskgroup v0.3.2/go.mod h1:wieWwecHVzsidg2CsUnFinW1faVN4+kq+TDlRJQ0Wbk=
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/go.mod h1:SnuYRW9lp1oJrZX/dXJqr0cPK5gYXqx3EJbmjhLdK9U=
github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0=
github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0=
@@ -224,10 +224,15 @@ github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 h1:fAjc9m62+UWV/WA
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 h1:Izz0+t1Z5nI16/II7vuEo/nHjodOg0p7+OiDpjX5t1E=
+github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc=
github.com/docker/docker v1.4.2-0.20180625184442-8e610b2b55bf/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ=
github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4=
github.com/dop251/goja v0.0.0-20200721192441-a695b0cdd498/go.mod h1:Mw6PkjjMXWbTj+nnj4s3QPXq1jaT0s5pC0iFD4+BOAA=
+github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf h1:Yt+4K30SdjOkRoRRm3vYNQgR+/ZIy0RmeUDZo7Y8zeQ=
+github.com/dop251/goja v0.0.0-20220405120441-9037c2b61cbf/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk=
+github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y=
github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -256,6 +261,7 @@ github.com/felixge/httpsnoop v1.0.1 h1:lvB5Jl89CsZtGIWuTcDM1E/vkVs49/Ml7JJe07l8S
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fjl/memsize v0.0.0-20180418122429-ca190fb6ffbc/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5 h1:FtmdgXiUlNeRsoNMFlKLDt+S+6hbjVMEW6RGQ7aUf7c=
+github.com/fjl/memsize v0.0.0-20190710130421-bcb5799ab5e5/go.mod h1:VvhXpOYNQvB+uIk2RvXzuaQtkQJzzIx6lSBe1xv7hi0=
github.com/fortytw2/leaktest v1.3.0 h1:u8491cBMTQ8ft8aeV+adlcytMZylmA5nnwwkRZjI8vw=
github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4=
github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20=
@@ -299,6 +305,8 @@ github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GO
github.com/go-playground/validator/v10 v10.4.1 h1:pH2c5ADXtd66mxoE0Zm9SUhxE20r7aM3F26W0hOn+GE=
github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4=
github.com/go-sourcemap/sourcemap v2.1.2+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU=
+github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg=
github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
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=
@@ -315,6 +323,7 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
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/golang-jwt/jwt/v4 v4.3.0 h1:kHL1vqdqWNfATmA0FNMdmZNMyZI1U6O31X4rlIPoBog=
+github.com/golang-jwt/jwt/v4 v4.3.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -427,6 +436,7 @@ github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoP
github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpxn4uE=
+github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
@@ -523,10 +533,12 @@ github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
+github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
+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/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
@@ -582,9 +594,11 @@ github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0Qu
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
+github.com/mitchellh/mapstructure v1.4.1/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/mitchellh/pointerstructure v1.2.0 h1:O+i9nHnXS3l/9Wu7r4NrEdwA2VFTicjUEN1uBnDo34A=
+github.com/mitchellh/pointerstructure v1.2.0/go.mod h1:BRAsLI5zgXmw97Lf6s25bs8ohIXc3tViBH44KcwB2g4=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
@@ -621,7 +635,6 @@ github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxzi
github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w=
github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c=
github.com/neilotoole/errgroup v0.1.5/go.mod h1:Q2nLGf+594h0CLBs/Mbg6qOr7GtqDK7C2S41udRnToE=
-github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -750,7 +763,6 @@ github.com/rs/xhandler v0.0.0-20160618193221-ed27b6fd6521/go.mod h1:RvLn4FgxWubr
github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg=
github.com/rs/zerolog v1.27.0 h1:1T7qCieN22GVc8S4Q2yuexzBb1EqjbgjSH9RohbMjKs=
github.com/rs/zerolog v1.27.0/go.mod h1:7frBqO0oezxmnO7GF86FY++uy8I0Tk/If5ni1G9Qc0U=
-github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
@@ -858,15 +870,16 @@ github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljT
github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
-github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY=
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.10.2 h1:x3p8awjp/2arX+Nl/G2040AZpOCHS/eMJJ1/a+mye4Y=
+github.com/urfave/cli/v2 v2.10.2/go.mod h1:f8iq5LtQ/bLxafbdBSLPPNsgaW0l/2fYYEHhAyPlwvo=
github.com/vmihailenco/msgpack/v5 v5.1.4/go.mod h1:C5gboKD0TJPqWDTVTtrQNfRbiBwHZGo8UTqP/9/XvLI=
github.com/vmihailenco/tagparser v0.1.2/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI=
github.com/wsddn/go-ecdh v0.0.0-20161211032359-48726bab9208/go.mod h1:IotVbo4F+mw0EzQ08zFqg7pK3FebNXpaMsRy2RT+Ees=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
+github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/ybbus/jsonrpc v2.1.2+incompatible/go.mod h1:XJrh1eMSzdIYFbM08flv0wp5G35eRniyeGut1z+LSiE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
@@ -1281,8 +1294,9 @@ gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLks
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b h1:QRR6H1YWRnHb4Y/HeNFCTJLFVxaq6wH4YuVdsUOr75U=
gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
diff --git a/rpc/apis.go b/rpc/apis.go
index df3a02e4..ca599896 100644
--- a/rpc/apis.go
+++ b/rpc/apis.go
@@ -5,11 +5,12 @@ package rpc
import (
"fmt"
- rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
+ "github.com/tendermint/tendermint/node"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
+ storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/ethereum/go-ethereum/rpc"
"github.com/stratosnet/stratos-chain/rpc/backend"
@@ -22,6 +23,8 @@ import (
"github.com/stratosnet/stratos-chain/rpc/namespaces/ethereum/txpool"
"github.com/stratosnet/stratos-chain/rpc/namespaces/ethereum/web3"
"github.com/stratosnet/stratos-chain/rpc/types"
+
+ evmkeeper "github.com/stratosnet/stratos-chain/x/evm/keeper"
)
// RPC namespaces and API version
@@ -44,16 +47,16 @@ const (
)
// APICreator creates the JSON-RPC API implementations.
-type APICreator = func(*server.Context, client.Context, *rpcclient.WSClient) []rpc.API
+type APICreator = func(*server.Context, *node.Node, *evmkeeper.Keeper, storetypes.MultiStore, client.Context) []rpc.API
// apiCreators defines the JSON-RPC API namespaces.
var apiCreators map[string]APICreator
func init() {
apiCreators = map[string]APICreator{
- EthNamespace: func(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient) []rpc.API {
+ EthNamespace: func(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context) []rpc.API {
nonceLock := new(types.AddrLocker)
- evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
+ evmBackend := backend.NewBackend(ctx, tmNode, evmKeeper, ms, ctx.Logger, clientCtx)
return []rpc.API{
{
Namespace: EthNamespace,
@@ -64,12 +67,12 @@ func init() {
{
Namespace: EthNamespace,
Version: apiVersion,
- Service: filters.NewPublicAPI(ctx.Logger, clientCtx, tmWSClient, evmBackend),
+ Service: filters.NewPublicAPI(ctx.Logger, clientCtx, tmNode.EventBus(), evmBackend),
Public: true,
},
}
},
- Web3Namespace: func(*server.Context, client.Context, *rpcclient.WSClient) []rpc.API {
+ Web3Namespace: func(*server.Context, *node.Node, *evmkeeper.Keeper, storetypes.MultiStore, client.Context) []rpc.API {
return []rpc.API{
{
Namespace: Web3Namespace,
@@ -79,18 +82,19 @@ func init() {
},
}
},
- NetNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
+ NetNamespace: func(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context) []rpc.API {
+ evmBackend := backend.NewBackend(ctx, tmNode, evmKeeper, ms, ctx.Logger, clientCtx)
return []rpc.API{
{
Namespace: NetNamespace,
Version: apiVersion,
- Service: net.NewPublicAPI(clientCtx),
+ Service: net.NewPublicAPI(evmBackend),
Public: true,
},
}
},
- PersonalNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
- evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
+ PersonalNamespace: func(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context) []rpc.API {
+ evmBackend := backend.NewBackend(ctx, tmNode, evmKeeper, ms, ctx.Logger, clientCtx)
return []rpc.API{
{
Namespace: PersonalNamespace,
@@ -100,18 +104,19 @@ func init() {
},
}
},
- TxPoolNamespace: func(ctx *server.Context, _ client.Context, _ *rpcclient.WSClient) []rpc.API {
+ TxPoolNamespace: func(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context) []rpc.API {
+ evmBackend := backend.NewBackend(ctx, tmNode, evmKeeper, ms, ctx.Logger, clientCtx)
return []rpc.API{
{
Namespace: TxPoolNamespace,
Version: apiVersion,
- Service: txpool.NewPublicAPI(ctx.Logger),
+ Service: txpool.NewPublicAPI(ctx.Logger, clientCtx, evmBackend),
Public: true,
},
}
},
- DebugNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
- evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
+ DebugNamespace: func(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context) []rpc.API {
+ evmBackend := backend.NewBackend(ctx, tmNode, evmKeeper, ms, ctx.Logger, clientCtx)
return []rpc.API{
{
Namespace: DebugNamespace,
@@ -121,8 +126,8 @@ func init() {
},
}
},
- MinerNamespace: func(ctx *server.Context, clientCtx client.Context, _ *rpcclient.WSClient) []rpc.API {
- evmBackend := backend.NewBackend(ctx, ctx.Logger, clientCtx)
+ MinerNamespace: func(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context) []rpc.API {
+ evmBackend := backend.NewBackend(ctx, tmNode, evmKeeper, ms, ctx.Logger, clientCtx)
return []rpc.API{
{
Namespace: MinerNamespace,
@@ -136,12 +141,12 @@ func init() {
}
// GetRPCAPIs returns the list of all APIs
-func GetRPCAPIs(ctx *server.Context, clientCtx client.Context, tmWSClient *rpcclient.WSClient, selectedAPIs []string) []rpc.API {
+func GetRPCAPIs(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context, selectedAPIs []string) []rpc.API {
var apis []rpc.API
for _, ns := range selectedAPIs {
if creator, ok := apiCreators[ns]; ok {
- apis = append(apis, creator(ctx, clientCtx, tmWSClient)...)
+ apis = append(apis, creator(ctx, tmNode, evmKeeper, ms, clientCtx)...)
} else {
ctx.Logger.Error("invalid namespace value", "namespace", ns)
}
diff --git a/rpc/backend/backend.go b/rpc/backend/backend.go
index 7b9aee17..1b6080a0 100644
--- a/rpc/backend/backend.go
+++ b/rpc/backend/backend.go
@@ -2,11 +2,17 @@ package backend
import (
"context"
+ "fmt"
"math/big"
"time"
+ cs "github.com/tendermint/tendermint/consensus"
"github.com/tendermint/tendermint/libs/log"
+ "github.com/tendermint/tendermint/mempool"
+ "github.com/tendermint/tendermint/node"
+ "github.com/tendermint/tendermint/p2p"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
+ "github.com/tendermint/tendermint/store"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
@@ -18,15 +24,20 @@ import (
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
+ storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/stratosnet/stratos-chain/rpc/types"
"github.com/stratosnet/stratos-chain/server/config"
+ evmkeeper "github.com/stratosnet/stratos-chain/x/evm/keeper"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
+ tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
+ tmtypes "github.com/tendermint/tendermint/types"
)
// BackendI implements the Cosmos and EVM backend.
type BackendI interface { // nolint: revive
CosmosBackend
EVMBackend
+ TMBackend
}
// CosmosBackend implements the functionality shared within cosmos namespaces
@@ -37,6 +48,9 @@ type CosmosBackend interface {
// GetAccounts()
// SignDirect()
// SignAmino()
+ GetEVMKeeper() *evmkeeper.Keeper
+ GetSdkContext() sdk.Context
+ GetSdkContextWithHeader(header *tmtypes.Header) (sdk.Context, error)
}
// EVMBackend implements the functionality shared within ethereum namespaces
@@ -47,30 +61,32 @@ type EVMBackend interface {
RPCGasCap() uint64 // global gas cap for eth_call over rpc: DoS protection
RPCEVMTimeout() time.Duration // global timeout for eth_call over rpc: DoS protection
RPCTxFeeCap() float64 // RPCTxFeeCap is the global transaction fee(price * gaslimit) cap for send-transaction variants. The unit is ether.
-
+ RPCFilterCap() int32
RPCMinGasPrice() int64
- SuggestGasTipCap(baseFee *big.Int) (*big.Int, error)
+ SuggestGasTipCap() (*big.Int, error)
+ RPCLogsCap() int32
+ RPCBlockRangeCap() int32
// Blockchain API
BlockNumber() (hexutil.Uint64, error)
GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpctypes.ResultBlock, error)
GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error)
- GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error)
- GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error)
- BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block, error)
- BlockByHash(blockHash common.Hash) (*ethtypes.Block, error)
- CurrentHeader() *ethtypes.Header
- HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error)
- HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
+ GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (*types.Block, error)
+ GetBlockByHash(hash common.Hash, fullTx bool) (*types.Block, error)
+ CurrentHeader() *types.Header
+ HeaderByNumber(blockNum types.BlockNumber) (*types.Header, error)
+ HeaderByHash(blockHash common.Hash) (*types.Header, error)
PendingTransactions() ([]*sdk.Tx, error)
- GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error)
+ GetTransactionCount(address common.Address, blockNum types.BlockNumber) (hexutil.Uint64, error)
SendTransaction(args evmtypes.TransactionArgs) (common.Hash, error)
GetCoinbase() (sdk.AccAddress, error)
- GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error)
- GetTxByEthHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
+ GetTransactionByHash(txHash common.Hash) (*types.Transaction, error)
+ GetTxByHash(txHash common.Hash) (*tmrpctypes.ResultTx, error)
GetTxByTxIndex(height int64, txIndex uint) (*tmrpctypes.ResultTx, error)
EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *types.BlockNumber) (hexutil.Uint64, error)
- BaseFee(height int64) (*big.Int, error)
+ BaseFee() (*big.Int, error)
+ GetLogsByNumber(blockNum types.BlockNumber) ([][]*ethtypes.Log, error)
+ BlockBloom(height *int64) (ethtypes.Bloom, error)
// Fee API
FeeHistory(blockCount rpc.DecimalOrHex, lastBlock rpc.BlockNumber, rewardPercentiles []float64) (*types.FeeHistoryResult, error)
@@ -81,32 +97,107 @@ type EVMBackend interface {
GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error)
ChainConfig() *params.ChainConfig
SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.TransactionArgs, error)
- GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx
+}
+
+type TMBackend interface {
+ // tendermint helpers
+ GetNode() *node.Node
+ GetBlockStore() *store.BlockStore
+ GetMempool() mempool.Mempool
+ GetConsensusReactor() *cs.Reactor
+ GetSwitch() *p2p.Switch
}
var _ BackendI = (*Backend)(nil)
// Backend implements the BackendI interface
type Backend struct {
- ctx context.Context
- clientCtx client.Context
- queryClient *types.QueryClient // gRPC query client
- logger log.Logger
- cfg config.Config
+ ctx context.Context
+ clientCtx client.Context
+ tmNode *node.Node // directly tendermint access, new impl
+ evmkeeper *evmkeeper.Keeper
+ ms storetypes.MultiStore
+ logger log.Logger
+ cfg config.Config
}
// NewBackend creates a new Backend instance for cosmos and ethereum namespaces
-func NewBackend(ctx *server.Context, logger log.Logger, clientCtx client.Context) *Backend {
+func NewBackend(ctx *server.Context, tmNode *node.Node, evmkeeper *evmkeeper.Keeper, ms storetypes.MultiStore, logger log.Logger, clientCtx client.Context) *Backend {
appConf, err := config.GetConfig(ctx.Viper)
if err != nil {
panic(err)
}
return &Backend{
- ctx: context.Background(),
- clientCtx: clientCtx,
- queryClient: types.NewQueryClient(clientCtx),
- logger: logger.With("module", "backend"),
- cfg: appConf,
+ ctx: context.Background(),
+ clientCtx: clientCtx,
+ tmNode: tmNode,
+ evmkeeper: evmkeeper,
+ ms: ms,
+ logger: logger.With("module", "backend"),
+ cfg: appConf,
+ }
+}
+
+func (b *Backend) GetEVMKeeper() *evmkeeper.Keeper {
+ return b.evmkeeper
+}
+
+func (b *Backend) copySdkContext(ms storetypes.MultiStore, header *tmtypes.Header) sdk.Context {
+ sdkCtx := sdk.NewContext(ms, tmproto.Header{}, true, b.logger)
+ if header != nil {
+ return sdkCtx.WithHeaderHash(
+ header.Hash(),
+ ).WithBlockHeader(
+ types.FormatTmHeaderToProto(header),
+ ).WithBlockHeight(
+ header.Height,
+ ).WithProposer(
+ sdk.ConsAddress(header.ProposerAddress),
+ )
+ }
+ return sdkCtx
+}
+
+func (b *Backend) GetSdkContext() sdk.Context {
+ return b.copySdkContext(b.ms.CacheMultiStore(), nil)
+}
+
+func (b *Backend) GetSdkContextWithHeader(header *tmtypes.Header) (sdk.Context, error) {
+ if header == nil {
+ return b.GetSdkContext(), nil
+ }
+ latestHeight := b.GetBlockStore().Height()
+ if latestHeight == 0 {
+ return sdk.Context{}, fmt.Errorf("block store not loaded")
}
+ if latestHeight == header.Height {
+ return b.copySdkContext(b.ms.CacheMultiStore(), header), nil
+ }
+
+ cms, err := b.ms.CacheMultiStoreWithVersion(header.Height)
+ if err != nil {
+ return sdk.Context{}, err
+ }
+ return b.copySdkContext(cms, header), nil
+}
+
+func (b *Backend) GetNode() *node.Node {
+ return b.tmNode
+}
+
+func (b *Backend) GetBlockStore() *store.BlockStore {
+ return b.tmNode.BlockStore()
+}
+
+func (b *Backend) GetMempool() mempool.Mempool {
+ return b.tmNode.Mempool()
+}
+
+func (b *Backend) GetConsensusReactor() *cs.Reactor {
+ return b.tmNode.ConsensusReactor()
+}
+
+func (b *Backend) GetSwitch() *p2p.Switch {
+ return b.tmNode.Switch()
}
diff --git a/rpc/backend/evm_backend.go b/rpc/backend/evm_backend.go
index 98e0c611..978284e4 100644
--- a/rpc/backend/evm_backend.go
+++ b/rpc/backend/evm_backend.go
@@ -1,16 +1,12 @@
package backend
import (
- "bytes"
"encoding/json"
"fmt"
"math/big"
- "strconv"
"time"
"github.com/pkg/errors"
- "google.golang.org/grpc"
- "google.golang.org/grpc/metadata"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
@@ -20,46 +16,36 @@ import (
"github.com/ethereum/go-ethereum/rpc"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
- tmtypes "github.com/tendermint/tendermint/types"
+ tmjsonrpctypes "github.com/tendermint/tendermint/rpc/jsonrpc/types"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
- grpctypes "github.com/cosmos/cosmos-sdk/types/grpc"
"github.com/stratosnet/stratos-chain/rpc/types"
stratos "github.com/stratosnet/stratos-chain/types"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
+ tmrpccore "github.com/tendermint/tendermint/rpc/core"
)
-var bAttributeKeyEthereumBloom = []byte(evmtypes.AttributeKeyEthereumBloom)
-
// BlockNumber returns the current block number in abci app state.
// Because abci app state could lag behind from tendermint latest block, it's more stable
// for the client to use the latest block number in abci app state than tendermint rpc.
func (b *Backend) BlockNumber() (hexutil.Uint64, error) {
- // do any grpc query, ignore the response and use the returned block height
- var header metadata.MD
- _, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{}, grpc.Header(&header))
+ res, err := tmrpccore.Block(nil, nil)
if err != nil {
return hexutil.Uint64(0), err
}
- blockHeightHeader := header.Get(grpctypes.GRPCBlockHeightHeader)
- if headerLen := len(blockHeightHeader); headerLen != 1 {
- return 0, fmt.Errorf("unexpected '%s' gRPC header length; got %d, expected: %d", grpctypes.GRPCBlockHeightHeader, headerLen, 1)
- }
-
- height, err := strconv.ParseUint(blockHeightHeader[0], 10, 64)
- if err != nil {
- return 0, fmt.Errorf("failed to parse block height: %w", err)
+ if res.Block == nil {
+ return hexutil.Uint64(0), errors.Errorf("block store not loaded")
}
- return hexutil.Uint64(height), nil
+ return hexutil.Uint64(res.Block.Height), nil
}
// GetBlockByNumber returns the block identified by number.
-func (b *Backend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map[string]interface{}, error) {
+func (b *Backend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (*types.Block, error) {
resBlock, err := b.GetTendermintBlockByNumber(blockNum)
if err != nil {
return nil, err
@@ -70,119 +56,63 @@ func (b *Backend) GetBlockByNumber(blockNum types.BlockNumber, fullTx bool) (map
return nil, nil
}
- res, err := b.EthBlockFromTendermint(resBlock.Block, fullTx)
+ res, err := types.EthBlockFromTendermint(b.clientCtx.TxConfig.TxDecoder(), resBlock.Block, fullTx)
if err != nil {
b.logger.Debug("EthBlockFromTendermint failed", "height", blockNum, "error", err.Error())
- return nil, err
+ return nil, nil
}
- return res, nil
-}
-
-// GetBlockByHash returns the block identified by hash.
-func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
- resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
+ // override dynamicly miner address
+ sdkCtx, err := b.GetSdkContextWithHeader(&resBlock.Block.Header)
if err != nil {
- b.logger.Debug("BlockByHash block not found", "hash", hash.Hex(), "error", err.Error())
+ b.logger.Debug("GetSdkContextWithHeader context", "height", blockNum, "error", err.Error())
return nil, err
}
- if resBlock == nil || resBlock.Block == nil {
- b.logger.Debug("BlockByHash block not found", "hash", hash.Hex())
- return nil, nil
- }
-
- return b.EthBlockFromTendermint(resBlock.Block, fullTx)
-}
-
-// BlockByNumber returns the block identified by number.
-func (b *Backend) BlockByNumber(blockNum types.BlockNumber) (*ethtypes.Block, error) {
- height := blockNum.Int64()
-
- switch blockNum {
- case types.EthLatestBlockNumber:
- currentBlockNumber, _ := b.BlockNumber()
- if currentBlockNumber > 0 {
- height = int64(currentBlockNumber)
- }
- case types.EthPendingBlockNumber:
- currentBlockNumber, _ := b.BlockNumber()
- if currentBlockNumber > 0 {
- height = int64(currentBlockNumber)
- }
- case types.EthEarliestBlockNumber:
- height = 1
- default:
- if blockNum < 0 {
- return nil, errors.Errorf("incorrect block height: %d", height)
- }
- }
-
- resBlock, err := b.clientCtx.Client.Block(b.ctx, &height)
+ validator, err := b.evmkeeper.GetCoinbaseAddress(sdkCtx)
if err != nil {
- b.logger.Debug("HeaderByNumber failed", "height", height)
+ b.logger.Debug("GetCoinbaseAddress no validator", "height", blockNum, "error", err.Error())
return nil, err
}
+ res.Miner = validator
- if resBlock == nil || resBlock.Block == nil {
- return nil, errors.Errorf("block not found for height %d", height)
- }
-
- return b.EthBlockFromTm(resBlock.Block)
+ return res, nil
}
-// BlockByHash returns the block identified by hash.
-func (b *Backend) BlockByHash(hash common.Hash) (*ethtypes.Block, error) {
- resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
+// GetBlockByHash returns the block identified by hash.
+func (b *Backend) GetBlockByHash(hash common.Hash, fullTx bool) (*types.Block, error) {
+ resBlock, err := tmrpccore.BlockByHash(nil, hash.Bytes())
if err != nil {
- b.logger.Debug("HeaderByHash failed", "hash", hash.Hex())
+ b.logger.Debug("BlockByHash block not found", "hash", hash.Hex(), "error", err.Error())
return nil, err
}
if resBlock == nil || resBlock.Block == nil {
- return nil, errors.Errorf("block not found for hash %s", hash)
+ b.logger.Debug("BlockByHash block not found", "hash", hash.Hex())
+ return nil, nil
}
- return b.EthBlockFromTm(resBlock.Block)
-}
-
-func (b *Backend) EthBlockFromTm(block *tmtypes.Block) (*ethtypes.Block, error) {
- height := block.Height
- bloom, err := b.BlockBloom(&height)
+ res, err := types.EthBlockFromTendermint(b.clientCtx.TxConfig.TxDecoder(), resBlock.Block, fullTx)
if err != nil {
- b.logger.Debug("HeaderByNumber BlockBloom failed", "height", height)
+ b.logger.Debug("EthBlockFromTendermint failed", "hash", hash.Hex(), "error", err.Error())
+ return nil, nil
}
- baseFee, err := b.BaseFee(height)
+ // override dynamicly miner address
+ sdkCtx, err := b.GetSdkContextWithHeader(&resBlock.Block.Header)
if err != nil {
- b.logger.Debug("HeaderByNumber BaseFee failed", "height", height, "error", err.Error())
+ b.logger.Debug("GetSdkContextWithHeader context", "hash", hash.Hex(), "error", err.Error())
return nil, err
}
- ethHeader := types.EthHeaderFromTendermint(block.Header, bloom, baseFee)
-
- var txs []*ethtypes.Transaction
- for _, txBz := range block.Txs {
- tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
- if err != nil {
- b.logger.Debug("failed to decode transaction in block", "height", height, "error", err.Error())
- continue
- }
-
- for _, msg := range tx.GetMsgs() {
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- continue
- }
-
- tx := ethMsg.AsTransaction()
- txs = append(txs, tx)
- }
+ validator, err := b.evmkeeper.GetCoinbaseAddress(sdkCtx)
+ if err != nil {
+ b.logger.Debug("GetCoinbaseAddress no validator", "hash", hash.Hex(), "error", err.Error())
+ return nil, err
}
+ res.Miner = validator
- // TODO: add tx receipts
- ethBlock := ethtypes.NewBlock(ethHeader, txs, nil, nil, nil)
- return ethBlock, nil
+ return res, nil
}
// GetTendermintBlockByNumber returns a Tendermint format block by block number
@@ -210,9 +140,9 @@ func (b *Backend) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpc
}
}
- resBlock, err := b.clientCtx.Client.Block(b.ctx, &height)
+ resBlock, err := tmrpccore.Block(nil, &height)
if err != nil {
- if resBlock, err = b.clientCtx.Client.Block(b.ctx, nil); err != nil {
+ if resBlock, err = tmrpccore.Block(nil, nil); err != nil {
b.logger.Debug("tendermint client failed to get latest block", "height", height, "error", err.Error())
return nil, nil
}
@@ -228,7 +158,7 @@ func (b *Backend) GetTendermintBlockByNumber(blockNum types.BlockNumber) (*tmrpc
// GetTendermintBlockByHash returns a Tendermint format block by block number
func (b *Backend) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.ResultBlock, error) {
- resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, blockHash.Bytes())
+ resBlock, err := tmrpccore.BlockByHash(nil, blockHash.Bytes())
if err != nil {
b.logger.Debug("tendermint client failed to get block", "blockHash", blockHash.Hex(), "error", err.Error())
}
@@ -243,148 +173,21 @@ func (b *Backend) GetTendermintBlockByHash(blockHash common.Hash) (*tmrpctypes.R
// BlockBloom query block bloom filter from block results
func (b *Backend) BlockBloom(height *int64) (ethtypes.Bloom, error) {
- result, err := b.clientCtx.Client.BlockResults(b.ctx, height)
+ result, err := tmrpccore.BlockResults(nil, height)
if err != nil {
return ethtypes.Bloom{}, err
}
- for _, event := range result.EndBlockEvents {
- if event.Type != evmtypes.EventTypeBlockBloom {
- continue
- }
-
- for _, attr := range event.Attributes {
- if bytes.Equal(attr.Key, bAttributeKeyEthereumBloom) {
- return ethtypes.BytesToBloom(attr.Value), nil
- }
- }
- }
- return ethtypes.Bloom{}, errors.New("block bloom event is not found")
-}
-
-// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum block from a given Tendermint block and its block result.
-func (b *Backend) EthBlockFromTendermint(
- block *tmtypes.Block,
- fullTx bool,
-) (map[string]interface{}, error) {
- ethRPCTxs := []interface{}{}
-
- ctx := types.ContextWithHeight(block.Height)
-
- baseFee, err := b.BaseFee(block.Height)
- if err != nil {
- return nil, err
- }
-
- resBlockResult, err := b.clientCtx.Client.BlockResults(ctx, &block.Height)
- if err != nil {
- return nil, err
- }
-
- txResults := resBlockResult.TxsResults
- txIndex := uint64(0)
-
- for i, txBz := range block.Txs {
- tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
- if err != nil {
- b.logger.Debug("failed to decode transaction in block", "height", block.Height, "error", err.Error())
- continue
- }
-
- for _, msg := range tx.GetMsgs() {
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- continue
- }
-
- tx := ethMsg.AsTransaction()
-
- // check tx exists on EVM by cross checking with blockResults
- if txResults[i].Code != 0 {
- b.logger.Debug("invalid tx result code", "hash", tx.Hash().Hex())
- continue
- }
-
- if !fullTx {
- hash := tx.Hash()
- ethRPCTxs = append(ethRPCTxs, hash)
- continue
- }
-
- rpcTx, err := types.NewRPCTransaction(
- tx,
- common.BytesToHash(block.Hash()),
- uint64(block.Height),
- txIndex,
- baseFee,
- )
- if err != nil {
- b.logger.Debug("NewTransactionFromData for receipt failed", "hash", tx.Hash().Hex(), "error", err.Error())
- continue
- }
- ethRPCTxs = append(ethRPCTxs, rpcTx)
- txIndex++
- }
- }
-
- bloom, err := b.BlockBloom(&block.Height)
- if err != nil {
- b.logger.Debug("failed to query BlockBloom", "height", block.Height, "error", err.Error())
- }
-
- req := &evmtypes.QueryValidatorAccountRequest{
- ConsAddress: sdk.ConsAddress(block.Header.ProposerAddress).String(),
- }
-
- res, err := b.queryClient.ValidatorAccount(ctx, req)
- if err != nil {
- b.logger.Debug(
- "failed to query validator operator address",
- "height", block.Height,
- "cons-address", req.ConsAddress,
- "error", err.Error(),
- )
- return nil, err
- }
-
- addr, err := sdk.AccAddressFromBech32(res.AccountAddress)
- if err != nil {
- return nil, err
- }
-
- validatorAddr := common.BytesToAddress(addr)
-
- gasLimit, err := types.BlockMaxGasFromConsensusParams(ctx, b.clientCtx, block.Height)
- if err != nil {
- b.logger.Error("failed to query consensus params", "error", err.Error())
- }
-
- gasUsed := uint64(0)
-
- for _, txsResult := range txResults {
- // workaround for cosmos-sdk bug. https://github.com/cosmos/cosmos-sdk/issues/10832
- if txsResult.GetCode() == 11 && txsResult.GetLog() == "no block gas left to run tx: out of gas" {
- // block gas limit has exceeded, other txs must have failed with same reason.
- break
- }
- gasUsed += uint64(txsResult.GetGasUsed())
- }
-
- formattedBlock := types.FormatBlock(
- block.Header, block.Size(),
- gasLimit, new(big.Int).SetUint64(gasUsed),
- ethRPCTxs, bloom, validatorAddr, baseFee,
- )
- return formattedBlock, nil
+ return types.GetBlockBloom(result)
}
// CurrentHeader returns the latest block header
-func (b *Backend) CurrentHeader() *ethtypes.Header {
+func (b *Backend) CurrentHeader() *types.Header {
header, _ := b.HeaderByNumber(types.EthLatestBlockNumber)
return header
}
// HeaderByNumber returns the block header identified by height.
-func (b *Backend) HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header, error) {
+func (b *Backend) HeaderByNumber(blockNum types.BlockNumber) (*types.Header, error) {
height := blockNum.Int64()
switch blockNum {
@@ -406,30 +209,37 @@ func (b *Backend) HeaderByNumber(blockNum types.BlockNumber) (*ethtypes.Header,
}
}
- resBlock, err := b.clientCtx.Client.Block(b.ctx, &height)
+ resBlock, err := tmrpccore.Block(nil, &height)
if err != nil {
b.logger.Debug("HeaderByNumber failed")
return nil, err
}
- bloom, err := b.BlockBloom(&resBlock.Block.Height)
+ ethHeader, err := types.EthHeaderFromTendermint(resBlock.Block.Header)
if err != nil {
- b.logger.Debug("HeaderByNumber BlockBloom failed", "height", resBlock.Block.Height)
+ b.logger.Debug("HeaderByNumber EthHeaderFromTendermint failed", "height", resBlock.Block.Height, "error", err.Error())
+ return nil, err
}
- baseFee, err := b.BaseFee(resBlock.Block.Height)
+ // override dynamicly miner address
+ sdkCtx, err := b.GetSdkContextWithHeader(&resBlock.Block.Header)
if err != nil {
- b.logger.Debug("HeaderByNumber BaseFee failed", "height", resBlock.Block.Height, "error", err.Error())
+ b.logger.Debug("GetSdkContextWithHeader context", "height", blockNum, "error", err.Error())
return nil, err
}
- ethHeader := types.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
+ validator, err := b.evmkeeper.GetCoinbaseAddress(sdkCtx)
+ if err != nil {
+ b.logger.Debug("EthBlockFromTendermint no validator", "height", blockNum, "error", err.Error())
+ return nil, err
+ }
+ ethHeader.Coinbase = validator
return ethHeader, nil
}
// HeaderByHash returns the block header identified by hash.
-func (b *Backend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error) {
- resBlock, err := b.clientCtx.Client.BlockByHash(b.ctx, blockHash.Bytes())
+func (b *Backend) HeaderByHash(blockHash common.Hash) (*types.Header, error) {
+ resBlock, err := tmrpccore.BlockByHash(nil, blockHash.Bytes())
if err != nil {
b.logger.Debug("HeaderByHash failed", "hash", blockHash.Hex())
return nil, err
@@ -439,31 +249,35 @@ func (b *Backend) HeaderByHash(blockHash common.Hash) (*ethtypes.Header, error)
return nil, errors.Errorf("block not found for hash %s", blockHash.Hex())
}
- bloom, err := b.BlockBloom(&resBlock.Block.Height)
+ ethHeader, err := types.EthHeaderFromTendermint(resBlock.Block.Header)
if err != nil {
- b.logger.Debug("HeaderByHash BlockBloom failed", "height", resBlock.Block.Height)
+ b.logger.Debug("HeaderByHash EthHeaderFromTendermint failed", "height", resBlock.Block.Height, "error", err.Error())
+ return nil, err
}
- baseFee, err := b.BaseFee(resBlock.Block.Height)
+ // override dynamicly miner address
+ sdkCtx, err := b.GetSdkContextWithHeader(&resBlock.Block.Header)
if err != nil {
- b.logger.Debug("HeaderByHash BaseFee failed", "height", resBlock.Block.Height, "error", err.Error())
+ b.logger.Debug("GetSdkContextWithHeader context", "hash", blockHash.Hex(), "error", err.Error())
return nil, err
}
- ethHeader := types.EthHeaderFromTendermint(resBlock.Block.Header, bloom, baseFee)
+ validator, err := b.evmkeeper.GetCoinbaseAddress(sdkCtx)
+ if err != nil {
+ b.logger.Debug("EthBlockFromTendermint no validator", "hash", blockHash.Hex(), "error", err.Error())
+ return nil, err
+ }
+ ethHeader.Coinbase = validator
return ethHeader, 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 *Backend) PendingTransactions() ([]*sdk.Tx, error) {
- res, err := b.clientCtx.Client.UnconfirmedTxs(b.ctx, nil)
- if err != nil {
- return nil, err
- }
+ txs := b.GetMempool().ReapMaxTxs(100)
- result := make([]*sdk.Tx, 0, len(res.Txs))
- for _, txBz := range res.Txs {
+ result := make([]*sdk.Tx, 0, len(txs))
+ for _, txBz := range txs {
tx, err := b.clientCtx.TxConfig.TxDecoder()(txBz)
if err != nil {
return nil, err
@@ -477,7 +291,7 @@ func (b *Backend) PendingTransactions() ([]*sdk.Tx, error) {
// GetLogsByHeight returns all the logs from all the ethereum transactions in a block.
func (b *Backend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
// NOTE: we query the state in case the tx result logs are not persisted after an upgrade.
- blockRes, err := b.clientCtx.Client.BlockResults(b.ctx, height)
+ blockRes, err := tmrpccore.BlockResults(nil, height)
if err != nil {
return nil, err
}
@@ -497,7 +311,7 @@ func (b *Backend) GetLogsByHeight(height *int64) ([][]*ethtypes.Log, error) {
// GetLogs returns all the logs from all the ethereum transactions in a block.
func (b *Backend) GetLogs(hash common.Hash) ([][]*ethtypes.Log, error) {
- block, err := b.clientCtx.Client.BlockByHash(b.ctx, hash.Bytes())
+ block, err := tmrpccore.BlockByHash(nil, hash.Bytes())
if err != nil {
return nil, err
}
@@ -537,12 +351,7 @@ func (b *Backend) BloomStatus() (uint64, uint64) {
// GetCoinbase is the address that staking rewards will be send to (alias for Etherbase).
func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
- node, err := b.clientCtx.GetNode()
- if err != nil {
- return nil, err
- }
-
- status, err := node.Status(b.ctx)
+ status, err := tmrpccore.Status(nil)
if err != nil {
return nil, err
}
@@ -551,7 +360,8 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
ConsAddress: sdk.ConsAddress(status.ValidatorInfo.Address).String(),
}
- res, err := b.queryClient.ValidatorAccount(b.ctx, req)
+ ctx := b.GetSdkContext()
+ res, err := b.GetEVMKeeper().ValidatorAccount(sdk.WrapSDKContext(ctx), req)
if err != nil {
return nil, err
}
@@ -561,115 +371,51 @@ func (b *Backend) GetCoinbase() (sdk.AccAddress, error) {
}
// GetTransactionByHash returns the Ethereum format transaction identified by Ethereum transaction hash
-func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.RPCTransaction, error) {
- res, err := b.GetTxByEthHash(txHash)
- hexTx := txHash.Hex()
+func (b *Backend) GetTransactionByHash(txHash common.Hash) (*types.Transaction, error) {
+ res, err := b.GetTxByHash(txHash)
if err != nil {
- // try to find tx in mempool
- txs, err := b.PendingTransactions()
+ // TODO: Get chain id value from genesis
+ tx, err := types.GetPendingTx(b.clientCtx.TxConfig.TxDecoder(), b.GetMempool(), txHash)
if err != nil {
- b.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
+ b.logger.Debug("tx not found", "hash", txHash, "error", err.Error())
return nil, nil
}
-
- for _, tx := range txs {
- msg, err := evmtypes.UnwrapEthereumMsg(tx, txHash)
- if err != nil {
- // not ethereum tx
- continue
- }
-
- if msg.Hash == hexTx {
- rpctx, err := types.NewTransactionFromMsg(
- msg,
- common.Hash{},
- uint64(0),
- uint64(0),
- b.ChainConfig().ChainID,
- )
- if err != nil {
- return nil, err
- }
- return rpctx, nil
- }
- }
-
- b.logger.Debug("tx not found", "hash", hexTx)
- return nil, nil
- }
-
- if res.TxResult.Code != 0 {
- return nil, errors.New("invalid ethereum tx")
- }
-
- msgIndex, attrs := types.FindTxAttributes(res.TxResult.Events, hexTx)
- if msgIndex < 0 {
- return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
+ return tx, nil
}
- tx, err := b.clientCtx.TxConfig.TxDecoder()(res.Tx)
- if err != nil {
+ block := b.GetBlockStore().LoadBlock(res.Height)
+ if block == nil {
+ b.logger.Debug("eth_getTransactionByHash", "hash", txHash, "block not found")
return nil, err
}
- // the `msgIndex` is inferred from tx events, should be within the bound.
- msg, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
- if !ok {
- return nil, errors.New("invalid ethereum tx")
- }
-
- block, err := b.clientCtx.Client.Block(b.ctx, &res.Height)
- if err != nil {
- b.logger.Debug("block not found", "height", res.Height, "error", err.Error())
- return nil, err
- }
+ blockHash := common.BytesToHash(block.Hash())
+ blockHeight := uint64(res.Height)
+ txIndex := uint64(res.Index)
- // Try to find txIndex from events
- found := false
- txIndex, err := types.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
- if err == nil {
- found = true
- } else {
- // Fallback to find tx index by iterating all valid eth transactions
- blockRes, err := b.clientCtx.Client.BlockResults(b.ctx, &block.Block.Height)
- if err != nil {
- return nil, nil
- }
- msgs := b.GetEthereumMsgsFromTendermintBlock(block, blockRes)
- for i := range msgs {
- if msgs[i].Hash == hexTx {
- txIndex = uint64(i)
- found = true
- break
- }
- }
- }
- if !found {
- return nil, errors.New("can't find index of ethereum tx")
- }
-
- return types.NewTransactionFromMsg(
- msg,
- common.BytesToHash(block.BlockID.Hash.Bytes()),
- uint64(res.Height),
- txIndex,
- b.ChainConfig().ChainID,
+ return types.TmTxToEthTx(
+ b.clientCtx.TxConfig.TxDecoder(),
+ res.Tx,
+ &blockHash,
+ &blockHeight,
+ &txIndex,
)
}
-// GetTxByEthHash uses `/tx_query` to find transaction by ethereum tx hash
-// TODO: Don't need to convert once hashing is fixed on Tendermint
-// https://github.com/tendermint/tendermint/issues/6539
-func (b *Backend) GetTxByEthHash(hash common.Hash) (*tmrpctypes.ResultTx, error) {
- query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
- resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
+func (b *Backend) GetTxByHash(hash common.Hash) (*tmrpctypes.ResultTx, error) {
+ resTx, err := tmrpccore.Tx(nil, hash.Bytes(), false)
if err != nil {
- return nil, err
- }
- if len(resTxs.Txs) == 0 {
- return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex())
+ query := fmt.Sprintf("%s.%s='%s'", evmtypes.TypeMsgEthereumTx, evmtypes.AttributeKeyEthereumTxHash, hash.Hex())
+ resTxs, err := tmrpccore.TxSearch(new(tmjsonrpctypes.Context), query, false, nil, nil, "")
+ if err != nil {
+ return nil, err
+ }
+ if len(resTxs.Txs) == 0 {
+ return nil, errors.Errorf("ethereum tx not found for hash %s", hash.Hex())
+ }
+ return resTxs.Txs[0], nil
}
- return resTxs.Txs[0], nil
+ return resTx, nil
}
// GetTxByTxIndex uses `/tx_query` to find transaction by tx index of valid ethereum txs
@@ -678,7 +424,7 @@ func (b *Backend) GetTxByTxIndex(height int64, index uint) (*tmrpctypes.ResultTx
height, evmtypes.TypeMsgEthereumTx,
evmtypes.AttributeKeyTxIndex, index,
)
- resTxs, err := b.clientCtx.Client.TxSearch(b.ctx, query, false, nil, nil, "")
+ resTxs, err := tmrpccore.TxSearch(new(tmjsonrpctypes.Context), query, false, nil, nil, "")
if err != nil {
return nil, err
}
@@ -722,7 +468,8 @@ func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, e
}
// Query params to use the EVM denomination
- res, err := b.queryClient.QueryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
+ sdkCtx := b.GetSdkContext()
+ res, err := b.GetEVMKeeper().Params(sdk.WrapSDKContext(sdkCtx), &evmtypes.QueryParamsRequest{})
if err != nil {
b.logger.Error("failed to query evm params", "error", err.Error())
return common.Hash{}, err
@@ -743,7 +490,13 @@ func (b *Backend) SendTransaction(args evmtypes.TransactionArgs) (common.Hash, e
return common.Hash{}, err
}
- txHash := msg.AsTransaction().Hash()
+ ethTx := msg.AsTransaction()
+ if !ethTx.Protected() {
+ // Ensure only eip155 signed transactions are submitted.
+ return common.Hash{}, errors.New("legacy pre-eip-155 transactions not supported")
+ }
+
+ txHash := ethTx.Hash()
// Broadcast transaction in sync mode (default)
// NOTE: If error is encountered on the node, the broadcast will not return an error
@@ -778,10 +531,23 @@ func (b *Backend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *ty
GasCap: b.RPCGasCap(),
}
- // From ContextWithHeight: if the provided height is 0,
- // it will return an empty context and the gRPC query will use
+ resBlock, err := b.GetTendermintBlockByNumber(blockNr)
+ if err != nil {
+ return 0, err
+ }
+
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return 0, nil
+ }
+
+ // it will return an empty context and the sdk.Context will use
// the latest block height for querying.
- res, err := b.queryClient.EstimateGas(types.ContextWithHeight(blockNr.Int64()), &req)
+ sdkCtx, err := b.GetSdkContextWithHeader(&resBlock.Block.Header)
+ if err != nil {
+ return 0, err
+ }
+ res, err := b.GetEVMKeeper().EstimateGas(sdk.WrapSDKContext(sdkCtx), &req)
if err != nil {
return 0, err
}
@@ -789,26 +555,21 @@ func (b *Backend) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *ty
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
-func (b *Backend) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (*hexutil.Uint64, error) {
+func (b *Backend) GetTransactionCount(address common.Address, blockNum types.BlockNumber) (hexutil.Uint64, error) {
// Get nonce (sequence) from account
from := sdk.AccAddress(address.Bytes())
accRet := b.clientCtx.AccountRetriever
err := accRet.EnsureExists(b.clientCtx, from)
if err != nil {
- // account doesn't exist yet, return 0
- n := hexutil.Uint64(0)
- return &n, nil
+ return hexutil.Uint64(0), nil
}
- includePending := blockNum == types.EthPendingBlockNumber
- nonce, err := b.getAccountNonce(address, includePending, blockNum.Int64(), b.logger)
+ nonce, err := b.getAccountNonce(address, blockNum)
if err != nil {
- return nil, err
+ return hexutil.Uint64(0), err
}
-
- n := hexutil.Uint64(nonce)
- return &n, nil
+ return hexutil.Uint64(nonce), nil
}
// RPCGasCap is the global gas cap for eth-call variants.
@@ -850,7 +611,8 @@ func (b *Backend) RPCBlockRangeCap() int32 {
// the node config. If set value is 0, it will default to 20.
func (b *Backend) RPCMinGasPrice() int64 {
- evmParams, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
+ sdkCtx := b.GetSdkContext()
+ evmParams, err := b.GetEVMKeeper().Params(sdk.WrapSDKContext(sdkCtx), &evmtypes.QueryParamsRequest{})
if err != nil {
return stratos.DefaultGasPrice
}
@@ -866,7 +628,8 @@ func (b *Backend) RPCMinGasPrice() int64 {
// ChainConfig returns the latest ethereum chain configuration
func (b *Backend) ChainConfig() *params.ChainConfig {
- params, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
+ sdkCtx := b.GetSdkContext()
+ params, err := b.GetEVMKeeper().Params(sdk.WrapSDKContext(sdkCtx), &evmtypes.QueryParamsRequest{})
if err != nil {
return nil
}
@@ -877,13 +640,15 @@ func (b *Backend) ChainConfig() *params.ChainConfig {
// SuggestGasTipCap returns the suggested tip cap
// Although we don't support tx prioritization yet, but we return a positive value to help client to
// mitigate the base fee changes.
-func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) {
- if baseFee == nil {
+func (b *Backend) SuggestGasTipCap() (*big.Int, error) {
+ baseFee, err := b.BaseFee()
+ if err != nil {
// london hardfork not enabled or feemarket not enabled
return big.NewInt(0), nil
}
- params, err := b.queryClient.Params(b.ctx, &evmtypes.QueryParamsRequest{})
+ sdkCtx := b.GetSdkContext()
+ params, err := b.GetEVMKeeper().Params(sdk.WrapSDKContext(sdkCtx), &evmtypes.QueryParamsRequest{})
if err != nil {
return nil, err
}
@@ -909,9 +674,23 @@ func (b *Backend) SuggestGasTipCap(baseFee *big.Int) (*big.Int, error) {
// If the base fee is not enabled globally, the query returns nil.
// If the London hard fork is not activated at the current height, the query will
// return nil.
-func (b *Backend) BaseFee(height int64) (*big.Int, error) {
+func (b *Backend) BaseFee() (*big.Int, error) {
+ resBlock, err := b.GetTendermintBlockByNumber(types.EthLatestBlockNumber)
+ if err != nil {
+ return nil, err
+ }
+
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return nil, nil
+ }
+
+ sdkCtx, err := b.GetSdkContextWithHeader(&resBlock.Block.Header)
+ if err != nil {
+ return nil, err
+ }
// return BaseFee if London hard fork is activated and feemarket is enabled
- res, err := b.queryClient.BaseFee(types.ContextWithHeight(height), &evmtypes.QueryBaseFeeRequest{})
+ res, err := b.GetEVMKeeper().BaseFee(sdk.WrapSDKContext(sdkCtx), nil)
if err != nil {
return nil, err
}
@@ -980,14 +759,14 @@ func (b *Backend) FeeHistory(
}
// tendermint block result
- tendermintBlockResult, err := b.clientCtx.Client.BlockResults(b.ctx, &tendermintblock.Block.Height)
+ tendermintBlockResult, err := tmrpccore.BlockResults(nil, &tendermintblock.Block.Height)
if tendermintBlockResult == nil {
b.logger.Debug("block result not found", "height", tendermintblock.Block.Height, "error", err.Error())
return nil, err
}
oneFeeHistory := types.OneFeeHistory{}
- err = b.processBlock(tendermintblock, ðBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
+ err = b.processBlock(tendermintblock, ethBlock, rewardPercentiles, tendermintBlockResult, &oneFeeHistory)
if err != nil {
return nil, err
}
@@ -1017,36 +796,3 @@ func (b *Backend) FeeHistory(
return &feeHistory, nil
}
-
-// GetEthereumMsgsFromTendermintBlock returns all real MsgEthereumTxs from a Tendermint block.
-// It also ensures consistency over the correct txs indexes across RPC endpoints
-func (b *Backend) GetEthereumMsgsFromTendermintBlock(block *tmrpctypes.ResultBlock, blockRes *tmrpctypes.ResultBlockResults) []*evmtypes.MsgEthereumTx {
- var result []*evmtypes.MsgEthereumTx
-
- txResults := blockRes.TxsResults
-
- for i, tx := range block.Block.Txs {
- // check tx exists on EVM by cross checking with blockResults
- if txResults[i].Code != 0 {
- b.logger.Debug("invalid tx result code", "cosmos-hash", hexutil.Encode(tx.Hash()))
- continue
- }
-
- tx, err := b.clientCtx.TxConfig.TxDecoder()(tx)
- if err != nil {
- b.logger.Debug("failed to decode transaction in block", "height", block.Block.Height, "error", err.Error())
- continue
- }
-
- for _, msg := range tx.GetMsgs() {
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- continue
- }
-
- result = append(result, ethMsg)
- }
- }
-
- return result
-}
diff --git a/rpc/backend/utils.go b/rpc/backend/utils.go
index b2ac112c..81bc8d28 100644
--- a/rpc/backend/utils.go
+++ b/rpc/backend/utils.go
@@ -8,16 +8,14 @@ import (
"math/big"
"sort"
- sdk "github.com/cosmos/cosmos-sdk/types"
- authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
ethtypes "github.com/ethereum/go-ethereum/core/types"
abci "github.com/tendermint/tendermint/abci/types"
- "github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stratosnet/stratos-chain/rpc/types"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
)
@@ -50,13 +48,18 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac
return args, errors.New("latest header is nil")
}
+ baseFee, err := b.BaseFee()
+ if err != nil {
+ return args, err
+ }
+
// If user specifies both maxPriorityfee and maxFee, then we do not
// need to consult the chain for defaults. It's definitely a London tx.
if args.MaxPriorityFeePerGas == nil || args.MaxFeePerGas == nil {
// In this clause, user left some fields unspecified.
- if head.BaseFee != nil && args.GasPrice == nil {
+ if baseFee != nil && args.GasPrice == nil {
if args.MaxPriorityFeePerGas == nil {
- tip, err := b.SuggestGasTipCap(head.BaseFee)
+ tip, err := b.SuggestGasTipCap()
if err != nil {
return args, err
}
@@ -66,7 +69,7 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac
if args.MaxFeePerGas == nil {
gasFeeCap := new(big.Int).Add(
(*big.Int)(args.MaxPriorityFeePerGas),
- new(big.Int).Mul(head.BaseFee, big.NewInt(2)),
+ new(big.Int).Mul(baseFee, big.NewInt(2)),
)
args.MaxFeePerGas = (*hexutil.Big)(gasFeeCap)
}
@@ -81,15 +84,15 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac
}
if args.GasPrice == nil {
- price, err := b.SuggestGasTipCap(head.BaseFee)
+ price, err := b.SuggestGasTipCap()
if err != nil {
return args, err
}
- if head.BaseFee != nil {
+ if baseFee != nil {
// The legacy tx gas price suggestion should not add 2x base fee
// because all fees are consumed, so it would result in a spiral
// upwards.
- price.Add(price, head.BaseFee)
+ price.Add(price, baseFee)
}
args.GasPrice = (*hexutil.Big)(price)
}
@@ -107,7 +110,10 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac
if args.Nonce == nil {
// get the nonce from the account retriever
// ignore error in case tge account doesn't exist yet
- nonce, _ := b.getAccountNonce(*args.From, true, 0, b.logger)
+ nonce, err := b.getAccountNonce(*args.From, types.EthPendingBlockNumber)
+ if err != nil {
+ return args, err
+ }
args.Nonce = (*hexutil.Uint64)(&nonce)
}
@@ -170,64 +176,47 @@ func (b *Backend) SetTxDefaults(args evmtypes.TransactionArgs) (evmtypes.Transac
// If the pending value is true, it will iterate over the mempool (pending)
// txs in order to compute and return the pending tx sequence.
// Todo: include the ability to specify a blockNumber
-func (b *Backend) getAccountNonce(accAddr common.Address, pending bool, height int64, logger log.Logger) (uint64, error) {
- queryClient := authtypes.NewQueryClient(b.clientCtx)
- res, err := queryClient.Account(types.ContextWithHeight(height), &authtypes.QueryAccountRequest{Address: sdk.AccAddress(accAddr.Bytes()).String()})
- if err != nil {
- return 0, err
+func (b *Backend) getAccountNonce(address common.Address, height types.BlockNumber) (uint64, error) {
+ var (
+ pendingNonce uint64
+ )
+ if height == types.EthPendingBlockNumber {
+ pendingNonce = types.GetPendingTxCountByAddress(b.clientCtx.TxConfig.TxDecoder(), b.GetMempool(), address)
}
- var acc authtypes.AccountI
- if err := b.clientCtx.InterfaceRegistry.UnpackAny(res.Account, &acc); err != nil {
- return 0, err
+ req := evmtypes.QueryCosmosAccountRequest{
+ Address: address.Hex(),
}
- nonce := acc.GetSequence()
+ block, err := b.GetTendermintBlockByNumber(height)
+ if err != nil {
+ return 0, err
+ }
- if !pending {
- return nonce, nil
+ if block.Block == nil {
+ return 0, fmt.Errorf("failed to get block for %d height", height)
}
- // the account retriever doesn't include the uncommitted transactions on the nonce so we need to
- // to manually add them.
- pendingTxs, err := b.PendingTransactions()
+ sdkCtx, err := b.GetSdkContextWithHeader(&block.Block.Header)
if err != nil {
- logger.Error("failed to fetch pending transactions", "error", err.Error())
- return nonce, nil
+ return 0, err
}
-
- // add the uncommitted txs to the nonce counter
- // only supports `MsgEthereumTx` style tx
- for _, tx := range pendingTxs {
- for _, msg := range (*tx).GetMsgs() {
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- // not ethereum tx
- break
- }
-
- sender, err := ethMsg.GetSender(b.ChainConfig().ChainID)
- if err != nil {
- continue
- }
- if sender == accAddr {
- nonce++
- }
- }
+ acc, err := b.GetEVMKeeper().CosmosAccount(sdk.WrapSDKContext(sdkCtx), &req)
+ if err != nil {
+ return pendingNonce, err
}
-
- return nonce, nil
+ return acc.GetSequence() + pendingNonce, nil
}
// output: targetOneFeeHistory
func (b *Backend) processBlock(
tendermintBlock *tmrpctypes.ResultBlock,
- ethBlock *map[string]interface{},
+ ethBlock *types.Block,
rewardPercentiles []float64,
tendermintBlockResult *tmrpctypes.ResultBlockResults,
targetOneFeeHistory *types.OneFeeHistory,
) error {
blockHeight := tendermintBlock.Block.Height
- blockBaseFee, err := b.BaseFee(blockHeight)
+ blockBaseFee, err := b.BaseFee()
if err != nil {
return err
}
@@ -236,15 +225,9 @@ func (b *Backend) processBlock(
targetOneFeeHistory.BaseFee = blockBaseFee
// set gas used ratio
- gasLimitUint64, ok := (*ethBlock)["gasLimit"].(hexutil.Uint64)
- if !ok {
- return fmt.Errorf("invalid gas limit type: %T", (*ethBlock)["gasLimit"])
- }
+ gasLimitUint64 := hexutil.Uint64(ethBlock.GasLimit.ToInt().Uint64())
- gasUsedBig, ok := (*ethBlock)["gasUsed"].(*hexutil.Big)
- if !ok {
- return fmt.Errorf("invalid gas used type: %T", (*ethBlock)["gasUsed"])
- }
+ gasUsedBig := ethBlock.GasUsed
gasusedfloat, _ := new(big.Float).SetInt(gasUsedBig.ToInt()).Float64()
diff --git a/rpc/ethereum/pubsub/pubsub.go b/rpc/ethereum/pubsub/pubsub.go
deleted file mode 100644
index 4ac7f34c..00000000
--- a/rpc/ethereum/pubsub/pubsub.go
+++ /dev/null
@@ -1,143 +0,0 @@
-package pubsub
-
-import (
- "sync"
- "sync/atomic"
-
- "github.com/pkg/errors"
-
- coretypes "github.com/tendermint/tendermint/rpc/core/types"
-)
-
-type UnsubscribeFunc func()
-
-type EventBus interface {
- AddTopic(name string, src <-chan coretypes.ResultEvent) error
- RemoveTopic(name string)
- Subscribe(name string) (<-chan coretypes.ResultEvent, UnsubscribeFunc, error)
- Topics() []string
-}
-
-type memEventBus struct {
- topics map[string]<-chan coretypes.ResultEvent
- topicsMux *sync.RWMutex
- subscribers map[string]map[uint64]chan<- coretypes.ResultEvent
- subscribersMux *sync.RWMutex
- currentUniqueID uint64
-}
-
-func NewEventBus() EventBus {
- return &memEventBus{
- topics: make(map[string]<-chan coretypes.ResultEvent),
- topicsMux: new(sync.RWMutex),
- subscribers: make(map[string]map[uint64]chan<- coretypes.ResultEvent),
- subscribersMux: new(sync.RWMutex),
- }
-}
-
-func (m *memEventBus) GenUniqueID() uint64 {
- return atomic.AddUint64(&m.currentUniqueID, 1)
-}
-
-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, UnsubscribeFunc, error) {
- m.topicsMux.RLock()
- _, ok := m.topics[name]
- m.topicsMux.RUnlock()
-
- if !ok {
- return nil, nil, errors.Errorf("topic not found: %s", name)
- }
-
- ch := make(chan coretypes.ResultEvent)
- m.subscribersMux.Lock()
- defer m.subscribersMux.Unlock()
-
- id := m.GenUniqueID()
- if _, ok := m.subscribers[name]; !ok {
- m.subscribers[name] = make(map[uint64]chan<- coretypes.ResultEvent)
- }
- m.subscribers[name][id] = ch
-
- unsubscribe := func() {
- m.subscribersMux.Lock()
- defer m.subscribersMux.Unlock()
- delete(m.subscribers[name], id)
- }
-
- return ch, unsubscribe, nil
-}
-
-func (m *memEventBus) publishTopic(name string, src <-chan coretypes.ResultEvent) {
- for {
- 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:
- }
- }
-}
diff --git a/rpc/namespaces/ethereum/debug/api.go b/rpc/namespaces/ethereum/debug/api.go
index 363f12ab..93c4908d 100644
--- a/rpc/namespaces/ethereum/debug/api.go
+++ b/rpc/namespaces/ethereum/debug/api.go
@@ -2,10 +2,12 @@ package debug
import (
"bytes"
+ "context"
"encoding/json"
"errors"
"fmt"
"io"
+ "math/big"
"os"
"runtime"
"runtime/debug"
@@ -14,14 +16,19 @@ import (
"time"
"github.com/davecgh/go-spew/spew"
+ "github.com/ethereum/go-ethereum/eth/tracers"
stderrors "github.com/pkg/errors"
+ "github.com/ethereum/go-ethereum/eth/tracers/logger"
+
"github.com/tendermint/tendermint/libs/log"
tmrpctypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
+ ethtypes "github.com/ethereum/go-ethereum/core/types"
+ sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/consensus/ethash"
@@ -29,9 +36,33 @@ import (
"github.com/stratosnet/stratos-chain/rpc/backend"
rpctypes "github.com/stratosnet/stratos-chain/rpc/types"
+ jstracers "github.com/stratosnet/stratos-chain/x/evm/tracers/js"
+ nativetracers "github.com/stratosnet/stratos-chain/x/evm/tracers/native"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
+ tmrpccore "github.com/tendermint/tendermint/rpc/core"
+)
+
+const (
+ // defaultTraceTimeout is the amount of time a single transaction can execute
+ // by default before being forcefully aborted.
+ defaultTraceTimeout = 5 * time.Second
+
+ // NOTE: Commented for now. Maybe even not needed as cosmos could obtain state by version
+ // If we have transaction, we could just take snapshot from previous height, whih should be enough
+ // defaultTraceReexec is the number of blocks the tracer is willing to go back
+ // and reexecute to produce missing historical state necessary to run a specific
+ // trace.
+ // defaultTraceReexec = uint64(128)
)
+// initialize tracers
+// NOTE: Required action as some go-ethereum modules incapsulated, so some tracer copy pasted
+// so should be periodically checked in order to update/extend functionality
+func init() {
+ jstracers.InitTracer()
+ nativetracers.InitTracer()
+}
+
// HandlerT keeps track of the cpu profiler and trace execution
type HandlerT struct {
cpuFilename string
@@ -43,133 +74,125 @@ type HandlerT struct {
// API is the collection of tracing APIs exposed over the private debugging endpoint.
type API struct {
- ctx *server.Context
- logger log.Logger
- backend backend.EVMBackend
- clientCtx client.Context
- queryClient *rpctypes.QueryClient
- handler *HandlerT
+ ctx *server.Context
+ logger log.Logger
+ backend backend.BackendI
+ clientCtx client.Context
+ handler *HandlerT
}
// NewAPI creates a new API definition for the tracing methods of the Ethereum service.
func NewAPI(
ctx *server.Context,
- backend backend.EVMBackend,
+ backend backend.BackendI,
clientCtx client.Context,
) *API {
return &API{
- ctx: ctx,
- logger: ctx.Logger.With("module", "debug"),
- backend: backend,
- clientCtx: clientCtx,
- queryClient: rpctypes.NewQueryClient(clientCtx),
- handler: new(HandlerT),
+ ctx: ctx,
+ logger: ctx.Logger.With("module", "debug"),
+ backend: backend,
+ clientCtx: clientCtx,
+ handler: new(HandlerT),
}
}
// TraceTransaction returns the structured logs created during the execution of EVM
// and returns them as a JSON object.
-func (a *API) TraceTransaction(hash common.Hash, config *evmtypes.TraceConfig) (interface{}, error) {
+func (a *API) TraceTransaction(ctx context.Context, hash common.Hash, config *tracers.TraceConfig) (interface{}, error) {
+ // Assemble the structured logger or the JavaScript tracer
+ var (
+ tracer tracers.Tracer
+ err error
+ timeout = defaultTraceTimeout
+ )
a.logger.Debug("debug_traceTransaction", "hash", hash)
+
// Get transaction by hash
- transaction, err := a.backend.GetTxByEthHash(hash)
+ resultTx, err := a.backend.GetTxByHash(hash)
if err != nil {
- a.logger.Debug("tx not found", "hash", hash)
+ a.logger.Debug("debug_traceTransaction", "tx not found", "hash", hash)
return nil, err
}
- // check if block number is 0
- if transaction.Height == 0 {
- return nil, errors.New("genesis is not traceable")
- }
-
- blk, err := a.backend.GetTendermintBlockByNumber(rpctypes.BlockNumber(transaction.Height))
+ tx, err := a.clientCtx.TxConfig.TxDecoder()(resultTx.Tx)
if err != nil {
- a.logger.Debug("block not found", "height", transaction.Height)
+ a.logger.Debug("tx not found", "hash", hash)
return nil, err
}
- msgIndex, _ := rpctypes.FindTxAttributes(transaction.TxResult.Events, hash.Hex())
- if msgIndex < 0 {
- return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hash.Hex())
+ if len(tx.GetMsgs()) == 0 {
+ return nil, fmt.Errorf("empty msg")
}
- // check tx index is not out of bound
- if uint32(len(blk.Block.Txs)) < transaction.Index {
- a.logger.Debug("tx index out of bounds", "index", transaction.Index, "hash", hash.String(), "height", blk.Block.Height)
- return nil, fmt.Errorf("transaction not included in block %v", blk.Block.Height)
+ ethMsg, ok := tx.GetMsgs()[0].(*evmtypes.MsgEthereumTx)
+ if !ok {
+ a.logger.Debug("debug_traceTransaction", "invalid transaction type", "type", fmt.Sprintf("%T", tx))
+ return logger.NewStructLogger(nil).GetResult()
}
- var predecessors []*evmtypes.MsgEthereumTx
- for _, txBz := range blk.Block.Txs[:transaction.Index] {
- tx, err := a.clientCtx.TxConfig.TxDecoder()(txBz)
- if err != nil {
- a.logger.Debug("failed to decode transaction in block", "height", blk.Block.Height, "error", err.Error())
- continue
- }
- for _, msg := range tx.GetMsgs() {
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- continue
- }
-
- predecessors = append(predecessors, ethMsg)
- }
+ parentHeight := resultTx.Height - 1
+ parentBlock, err := tmrpccore.Block(nil, &parentHeight)
+ if err != nil || parentBlock.Block == nil {
+ a.logger.Debug("debug_traceTransaction", "block not found", "height", resultTx.Height)
+ return nil, err
}
-
- tx, err := a.clientCtx.TxConfig.TxDecoder()(transaction.Tx)
- if err != nil {
- a.logger.Debug("tx not found", "hash", hash)
+ currentBlock, err := tmrpccore.Block(nil, &resultTx.Height)
+ if err != nil || parentBlock.Block == nil {
+ a.logger.Debug("debug_traceTransaction", "block not found", "height", resultTx.Height)
return nil, err
}
- // add predecessor messages in current cosmos tx
- for i := 0; i < msgIndex; i++ {
- ethMsg, ok := tx.GetMsgs()[i].(*evmtypes.MsgEthereumTx)
- if !ok {
- continue
- }
- predecessors = append(predecessors, ethMsg)
+ txctx := &tracers.Context{
+ BlockHash: common.BytesToHash(currentBlock.Block.Hash()),
+ TxIndex: int(resultTx.Index),
+ TxHash: ethMsg.AsTransaction().Hash(),
}
- ethMessage, ok := tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
- if !ok {
- a.logger.Debug("invalid transaction type", "type", fmt.Sprintf("%T", tx))
- return nil, fmt.Errorf("invalid transaction type %T", tx)
+ // Default tracer is the struct logger
+ tracer = logger.NewStructLogger(config.Config)
+ if config.Tracer != nil {
+ tracer, err = tracers.New(*config.Tracer, txctx, config.TracerConfig)
+ if err != nil {
+ return nil, err
+ }
}
-
- traceTxRequest := evmtypes.QueryTraceTxRequest{
- Msg: ethMessage,
- Predecessors: predecessors,
- BlockNumber: blk.Block.Height,
- BlockTime: blk.Block.Time,
- BlockHash: common.Bytes2Hex(blk.BlockID.Hash),
+ // Define a meaningful timeout of a single transaction trace
+ if config.Timeout != nil {
+ if timeout, err = time.ParseDuration(*config.Timeout); err != nil {
+ return nil, err
+ }
}
- if config != nil {
- traceTxRequest.TraceConfig = config
- }
+ deadlineCtx, cancel := context.WithTimeout(ctx, timeout)
+ go func() {
+ <-deadlineCtx.Done()
+ if errors.Is(deadlineCtx.Err(), context.DeadlineExceeded) {
+ tracer.Stop(errors.New("execution timeout"))
+ }
+ }()
+ defer cancel()
- // minus one to get the context of block beginning
- contextHeight := transaction.Height - 1
- if contextHeight < 1 {
- // 0 is a special value in `ContextWithHeight`
- contextHeight = 1
+ sdkCtx, err := a.backend.GetSdkContextWithHeader(&parentBlock.Block.Header)
+ if err != nil {
+ return nil, fmt.Errorf("Failed to load state at height: %d\n", parentHeight)
}
- traceResult, err := a.queryClient.TraceTx(rpctypes.ContextWithHeight(contextHeight), &traceTxRequest)
+
+ keeper := a.backend.GetEVMKeeper()
+
+ cfg, err := keeper.EVMConfig(sdkCtx)
if err != nil {
return nil, err
}
+ signer := ethtypes.MakeSigner(cfg.ChainConfig, big.NewInt(parentBlock.Block.Height))
- // Response format is unknown due to custom tracer config param
- // More information can be found here https://geth.ethereum.org/docs/dapp/tracing-filtered
- var decodedResult interface{}
- err = json.Unmarshal(traceResult.Data, &decodedResult)
+ msg, err := ethMsg.AsMessage(signer, cfg.BaseFee)
if err != nil {
return nil, err
}
-
- return decodedResult, nil
+ if _, err := keeper.ApplyMessage(sdkCtx, msg, tracer, false); err != nil {
+ return nil, fmt.Errorf("tracing failed: %w", err)
+ }
+ return tracer.GetResult()
}
// TraceBlockByNumber returns the structured logs created during the execution of
@@ -246,7 +269,6 @@ func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConf
// 0 is a special value for `ContextWithHeight`.
contextHeight = 1
}
- ctxWithHeight := rpctypes.ContextWithHeight(int64(contextHeight))
traceBlockRequest := &evmtypes.QueryTraceBlockRequest{
Txs: txsMessages,
@@ -256,7 +278,12 @@ func (a *API) traceBlock(height rpctypes.BlockNumber, config *evmtypes.TraceConf
BlockHash: common.Bytes2Hex(block.BlockID.Hash),
}
- res, err := a.queryClient.TraceBlock(ctxWithHeight, traceBlockRequest)
+ sdkCtx, err := a.backend.GetSdkContextWithHeader(&block.Block.Header)
+ if err != nil {
+ return nil, err
+ }
+
+ res, err := a.backend.GetEVMKeeper().TraceBlock(sdk.WrapSDKContext(sdkCtx), traceBlockRequest)
if err != nil {
return nil, err
}
@@ -465,7 +492,7 @@ func (a *API) GetHeaderRlp(number uint64) (hexutil.Bytes, error) {
// GetBlockRlp retrieves the RLP encoded for of a single block.
func (a *API) GetBlockRlp(number uint64) (hexutil.Bytes, error) {
- block, err := a.backend.BlockByNumber(rpctypes.BlockNumber(number))
+ block, err := a.backend.GetBlockByNumber(rpctypes.BlockNumber(number), true)
if err != nil {
return nil, err
}
@@ -475,7 +502,7 @@ func (a *API) GetBlockRlp(number uint64) (hexutil.Bytes, error) {
// PrintBlock retrieves a block and returns its pretty printed form.
func (a *API) PrintBlock(number uint64) (string, error) {
- block, err := a.backend.BlockByNumber(rpctypes.BlockNumber(number))
+ block, err := a.backend.GetBlockByNumber(rpctypes.BlockNumber(number), true)
if err != nil {
return "", err
}
diff --git a/rpc/namespaces/ethereum/eth/api.go b/rpc/namespaces/ethereum/eth/api.go
index 44b315f4..05243e5e 100644
--- a/rpc/namespaces/ethereum/eth/api.go
+++ b/rpc/namespaces/ethereum/eth/api.go
@@ -7,6 +7,7 @@ import (
"math"
"math/big"
+ abci "github.com/tendermint/tendermint/abci/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
@@ -20,7 +21,6 @@ import (
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
sdk "github.com/cosmos/cosmos-sdk/types"
- sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/ethereum/go-ethereum/accounts/keystore"
@@ -35,19 +35,21 @@ import (
"github.com/stratosnet/stratos-chain/crypto/hd"
"github.com/stratosnet/stratos-chain/ethereum/eip712"
"github.com/stratosnet/stratos-chain/rpc/backend"
+ "github.com/stratosnet/stratos-chain/rpc/types"
rpctypes "github.com/stratosnet/stratos-chain/rpc/types"
stratos "github.com/stratosnet/stratos-chain/types"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
+ mempl "github.com/tendermint/tendermint/mempool"
+ tmrpccore "github.com/tendermint/tendermint/rpc/core"
)
// PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicAPI struct {
ctx context.Context
clientCtx client.Context
- queryClient *rpctypes.QueryClient
chainIDEpoch *big.Int
logger log.Logger
- backend backend.EVMBackend
+ backend backend.BackendI
nonceLock *rpctypes.AddrLocker
signer ethtypes.Signer
}
@@ -56,7 +58,7 @@ type PublicAPI struct {
func NewPublicAPI(
logger log.Logger,
clientCtx client.Context,
- backend backend.EVMBackend,
+ backend backend.BackendI,
nonceLock *rpctypes.AddrLocker,
) *PublicAPI {
algos, _ := clientCtx.Keyring.SupportedAlgorithms()
@@ -88,7 +90,6 @@ func NewPublicAPI(
api := &PublicAPI{
ctx: context.Background(),
clientCtx: clientCtx,
- queryClient: rpctypes.NewQueryClient(clientCtx),
chainIDEpoch: cfg.ChainID,
logger: logger.With("client", "json-rpc"),
backend: backend,
@@ -104,10 +105,6 @@ func (e *PublicAPI) ClientCtx() client.Context {
return e.clientCtx
}
-func (e *PublicAPI) QueryClient() *rpctypes.QueryClient {
- return e.queryClient
-}
-
func (e *PublicAPI) Ctx() context.Context {
return e.ctx
}
@@ -121,18 +118,9 @@ func (e *PublicAPI) ProtocolVersion() hexutil.Uint {
// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { // nolint
e.logger.Debug("eth_chainId")
- // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
- bn, err := e.backend.BlockNumber()
- if err != nil {
- e.logger.Debug("failed to fetch latest block number", "error", err.Error())
- return (*hexutil.Big)(e.chainIDEpoch), nil
- }
-
- if config := e.backend.ChainConfig(); config.IsEIP155(new(big.Int).SetUint64(uint64(bn))) {
- return (*hexutil.Big)(config.ChainID), nil
- }
-
- return nil, fmt.Errorf("chain not synced beyond EIP-155 replay-protection fork block")
+ ctx := e.backend.GetSdkContext()
+ params := e.backend.GetEVMKeeper().GetParams(ctx)
+ return (*hexutil.Big)(params.ChainConfig.ChainID.BigInt()), nil
}
// Syncing returns false in case the node is currently not syncing with the network. It can be up to date or has not
@@ -145,7 +133,11 @@ func (e *PublicAPI) ChainId() (*hexutil.Big, error) { // nolint
func (e *PublicAPI) Syncing() (interface{}, error) {
e.logger.Debug("eth_syncing")
- status, err := e.clientCtx.Client.Status(e.ctx)
+ if !e.backend.GetConsensusReactor().WaitSync() {
+ return false, nil
+ }
+
+ status, err := tmrpccore.Status(nil)
if err != nil {
return false, err
}
@@ -157,9 +149,9 @@ func (e *PublicAPI) Syncing() (interface{}, error) {
return map[string]interface{}{
"startingBlock": hexutil.Uint64(status.SyncInfo.EarliestBlockHeight),
"currentBlock": hexutil.Uint64(status.SyncInfo.LatestBlockHeight),
- // "highestBlock": nil, // NA
- // "pulledStates": nil, // NA
- // "knownStates": nil, // NA
+ "highestBlock": nil, // NA
+ "pulledStates": nil, // NA
+ "knownStates": nil, // NA
}, nil
}
@@ -194,12 +186,16 @@ func (e *PublicAPI) GasPrice() (*hexutil.Big, error) {
result *big.Int
err error
)
- if head := e.backend.CurrentHeader(); head.BaseFee != nil {
- result, err = e.backend.SuggestGasTipCap(head.BaseFee)
+ baseFee, err := e.backend.BaseFee()
+ if err != nil {
+ return nil, err
+ }
+ if baseFee != nil {
+ result, err = e.backend.SuggestGasTipCap()
if err != nil {
return nil, err
}
- result = result.Add(result, head.BaseFee)
+ result = result.Add(result, baseFee)
} else {
result = big.NewInt(e.backend.RPCMinGasPrice())
}
@@ -210,8 +206,7 @@ func (e *PublicAPI) GasPrice() (*hexutil.Big, error) {
// MaxPriorityFeePerGas returns a suggestion for a gas tip cap for dynamic fee transactions.
func (e *PublicAPI) MaxPriorityFeePerGas() (*hexutil.Big, error) {
e.logger.Debug("eth_maxPriorityFeePerGas")
- head := e.backend.CurrentHeader()
- tipcap, err := e.backend.SuggestGasTipCap(head.BaseFee)
+ tipcap, err := e.backend.SuggestGasTipCap()
if err != nil {
return nil, err
}
@@ -257,21 +252,23 @@ func (e *PublicAPI) GetBalance(address common.Address, blockNrOrHash rpctypes.Bl
return nil, err
}
- req := &evmtypes.QueryBalanceRequest{
- Address: address.String(),
- }
-
- res, err := e.queryClient.Balance(rpctypes.ContextWithHeight(blockNum.Int64()), req)
+ resBlock, err := e.backend.GetTendermintBlockByNumber(blockNum)
if err != nil {
return nil, err
}
- val, ok := sdk.NewIntFromString(res.Balance)
- if !ok {
- return nil, errors.New("invalid balance")
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return nil, nil
+ }
+
+ sdkCtx, err := e.backend.GetSdkContextWithHeader(&resBlock.Block.Header)
+ if err != nil {
+ return nil, err
}
+ balance := e.backend.GetEVMKeeper().GetBalance(sdkCtx, address)
- return (*hexutil.Big)(val.BigInt()), nil
+ return (*hexutil.Big)(balance), nil
}
// GetStorageAt returns the contract storage at the given address, block number, and key.
@@ -283,26 +280,30 @@ func (e *PublicAPI) GetStorageAt(address common.Address, key string, blockNrOrHa
return nil, err
}
- req := &evmtypes.QueryStorageRequest{
- Address: address.String(),
- Key: key,
+ resBlock, err := e.backend.GetTendermintBlockByNumber(blockNum)
+ if err != nil {
+ return nil, err
+ }
+
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return nil, nil
}
- res, err := e.queryClient.Storage(rpctypes.ContextWithHeight(blockNum.Int64()), req)
+ sdkCtx, err := e.backend.GetSdkContextWithHeader(&resBlock.Block.Header)
if err != nil {
return nil, err
}
-
- value := common.HexToHash(res.Value)
- return value.Bytes(), nil
+ state := e.backend.GetEVMKeeper().GetState(sdkCtx, address, common.HexToHash(key))
+ return state.Bytes(), nil
}
// GetTransactionCount returns the number of transactions at the given address up to the given block number.
-func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (*hexutil.Uint64, error) {
+func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rpctypes.BlockNumberOrHash) (hexutil.Uint64, error) {
e.logger.Debug("eth_getTransactionCount", "address", address.Hex(), "block number or hash", blockNrOrHash)
blockNum, err := e.getBlockNumber(blockNrOrHash)
if err != nil {
- return nil, err
+ return 0, err
}
return e.backend.GetTransactionCount(address, blockNum)
}
@@ -311,7 +312,7 @@ func (e *PublicAPI) GetTransactionCount(address common.Address, blockNrOrHash rp
func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByHash", "hash", hash.Hex())
- block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
+ block, err := tmrpccore.BlockByHash(nil, hash.Bytes())
if err != nil {
e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
return nil
@@ -322,20 +323,14 @@ func (e *PublicAPI) GetBlockTransactionCountByHash(hash common.Hash) *hexutil.Ui
return nil
}
- blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
- if err != nil {
- return nil
- }
-
- ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
- n := hexutil.Uint(len(ethMsgs))
+ n := hexutil.Uint(len(block.Block.Txs))
return &n
}
// GetBlockTransactionCountByNumber returns the number of transactions in the block identified by number.
func (e *PublicAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumber) *hexutil.Uint {
e.logger.Debug("eth_getBlockTransactionCountByNumber", "height", blockNum.Int64())
- block, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
+ block, err := tmrpccore.Block(nil, blockNum.TmHeight())
if err != nil {
e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
return nil
@@ -346,13 +341,7 @@ func (e *PublicAPI) GetBlockTransactionCountByNumber(blockNum rpctypes.BlockNumb
return nil
}
- blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
- if err != nil {
- return nil
- }
-
- ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
- n := hexutil.Uint(len(ethMsgs))
+ n := hexutil.Uint(len(block.Block.Txs))
return &n
}
@@ -379,7 +368,21 @@ func (e *PublicAPI) GetCode(address common.Address, blockNrOrHash rpctypes.Block
Address: address.String(),
}
- res, err := e.queryClient.Code(rpctypes.ContextWithHeight(blockNum.Int64()), req)
+ resBlock, err := e.backend.GetTendermintBlockByNumber(blockNum)
+ if err != nil {
+ return nil, err
+ }
+
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return nil, nil
+ }
+
+ sdkCtx, err := e.backend.GetSdkContextWithHeader(&resBlock.Block.Header)
+ if err != nil {
+ return nil, err
+ }
+ res, err := e.backend.GetEVMKeeper().Code(sdk.WrapSDKContext(sdkCtx), req)
if err != nil {
return nil, err
}
@@ -392,7 +395,7 @@ func (e *PublicAPI) GetTransactionLogs(txHash common.Hash) ([]*ethtypes.Log, err
e.logger.Debug("eth_getTransactionLogs", "hash", txHash)
hexTx := txHash.Hex()
- res, err := e.backend.GetTxByEthHash(txHash)
+ res, err := e.backend.GetTxByHash(txHash)
if err != nil {
e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
return nil, nil
@@ -508,36 +511,53 @@ func (e *PublicAPI) SendRawTransaction(data hexutil.Bytes) (common.Hash, error)
return common.Hash{}, err
}
+ sdkCtx := e.backend.GetSdkContext()
// Query params to use the EVM denomination
- res, err := e.queryClient.QueryClient.Params(e.ctx, &evmtypes.QueryParamsRequest{})
- if err != nil {
- e.logger.Error("failed to query evm params", "error", err.Error())
- return common.Hash{}, err
- }
+ params := e.backend.GetEVMKeeper().GetParams(sdkCtx)
- cosmosTx, err := ethereumTx.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), res.Params.EvmDenom)
+ cosmosTx, err := ethereumTx.BuildTx(e.clientCtx.TxConfig.NewTxBuilder(), params.EvmDenom)
if err != nil {
e.logger.Error("failed to build cosmos tx", "error", err.Error())
return common.Hash{}, err
}
// Encode transaction by default Tx encoder
- txBytes, err := e.clientCtx.TxConfig.TxEncoder()(cosmosTx)
+ packet, err := e.clientCtx.TxConfig.TxEncoder()(cosmosTx)
if err != nil {
e.logger.Error("failed to encode eth tx using default encoder", "error", err.Error())
return common.Hash{}, err
}
- txHash := ethereumTx.AsTransaction().Hash()
-
- syncCtx := e.clientCtx.WithBroadcastMode(flags.BroadcastSync)
- rsp, err := syncCtx.BroadcastTx(txBytes)
- if rsp != nil && rsp.Code != 0 {
- err = sdkerrors.ABCIError(rsp.Codespace, rsp.Code, rsp.RawLog)
+ ethTx := ethereumTx.AsTransaction()
+ if !ethTx.Protected() {
+ // Ensure only eip155 signed transactions are submitted.
+ return common.Hash{}, errors.New("legacy pre-eip-155 transactions not supported")
}
- if err != nil {
- e.logger.Error("failed to broadcast tx", "error", err.Error())
- return txHash, err
+ txHash := ethTx.Hash()
+
+ mempool := e.backend.GetMempool()
+ if e.clientCtx.BroadcastMode == "async" {
+ e.logger.Info("Use async mode to propagate tx", txHash)
+ err = mempool.CheckTx(packet, nil, mempl.TxInfo{})
+ if err != nil {
+ return common.Hash{}, err
+ }
+ } else {
+ e.logger.Info("Use sync mode to propagate tx", txHash)
+ resCh := make(chan *abci.Response, 1)
+ err = mempool.CheckTx(packet, func(res *abci.Response) {
+ resCh <- res
+ }, mempl.TxInfo{})
+ if err != nil {
+ e.logger.Error("failed to send eth tx packet to mempool", "error", err.Error())
+ return common.Hash{}, err
+ }
+ res := <-resCh
+ resBrodTx := res.GetCheckTx()
+ if resBrodTx.Code != 0 {
+ e.logger.Error("exec failed on check tx", "error", resBrodTx.Log)
+ return common.Hash{}, fmt.Errorf(resBrodTx.Log)
+ }
}
return txHash, nil
@@ -658,12 +678,27 @@ func (e *PublicAPI) doCall(
GasCap: e.backend.RPCGasCap(),
}
- // From ContextWithHeight: if the provided height is 0,
+ resBlock, err := e.backend.GetTendermintBlockByNumber(blockNr)
+ if err != nil {
+ return nil, err
+ }
+
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return nil, nil
+ }
+
+ sdkCtx, err := e.backend.GetSdkContextWithHeader(&resBlock.Block.Header)
+ if err != nil {
+ return nil, err
+ }
+
// it will return an empty context and the gRPC query will use
// the latest block height for querying.
- ctx := rpctypes.ContextWithHeight(blockNr.Int64())
timeout := e.backend.RPCEVMTimeout()
+ ctx := sdk.WrapSDKContext(sdkCtx)
+
// Setup context so it may be canceled the call has completed
// or, in case of unmetered gas, setup a context with a timeout.
var cancel context.CancelFunc
@@ -677,7 +712,7 @@ func (e *PublicAPI) doCall(
// this makes sure resources are cleaned up.
defer cancel()
- res, err := e.queryClient.EthCall(ctx, &req)
+ res, err := e.backend.GetEVMKeeper().EthCall(ctx, &req)
if err != nil {
return nil, err
}
@@ -699,77 +734,50 @@ func (e *PublicAPI) EstimateGas(args evmtypes.TransactionArgs, blockNrOptional *
}
// GetBlockByHash returns the block identified by hash.
-func (e *PublicAPI) GetBlockByHash(hash common.Hash, fullTx bool) (map[string]interface{}, error) {
+func (e *PublicAPI) GetBlockByHash(hash common.Hash, fullTx bool) (*types.Block, error) {
e.logger.Debug("eth_getBlockByHash", "hash", hash.Hex(), "full", fullTx)
return e.backend.GetBlockByHash(hash, fullTx)
}
// GetBlockByNumber returns the block identified by number.
-func (e *PublicAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (map[string]interface{}, error) {
+func (e *PublicAPI) GetBlockByNumber(ethBlockNum rpctypes.BlockNumber, fullTx bool) (*types.Block, error) {
e.logger.Debug("eth_getBlockByNumber", "number", ethBlockNum, "full", fullTx)
return e.backend.GetBlockByNumber(ethBlockNum, fullTx)
}
// GetTransactionByHash returns the transaction identified by hash.
-func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.RPCTransaction, error) {
+func (e *PublicAPI) GetTransactionByHash(hash common.Hash) (*rpctypes.Transaction, error) {
e.logger.Debug("eth_getTransactionByHash", "hash", hash.Hex())
return e.backend.GetTransactionByHash(hash)
}
// getTransactionByBlockAndIndex is the common code shared by `GetTransactionByBlockNumberAndIndex` and `GetTransactionByBlockHashAndIndex`.
-func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
- var msg *evmtypes.MsgEthereumTx
- // try /tx_search first
- res, err := e.backend.GetTxByTxIndex(block.Block.Height, uint(idx))
- if err == nil {
- tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
- if err != nil {
- e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
- return nil, nil
- }
- // find msg index in events
- msgIndex := rpctypes.FindTxAttributesByIndex(res.TxResult.Events, uint64(idx))
- if msgIndex < 0 {
- e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
- return nil, nil
- }
- var ok bool
- // msgIndex is inferred from tx events, should be within bound.
- msg, ok = tx.GetMsgs()[msgIndex].(*evmtypes.MsgEthereumTx)
- if !ok {
- e.logger.Debug("invalid ethereum tx", "height", block.Block.Header, "index", idx)
- return nil, nil
- }
- } else {
- blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &block.Block.Height)
- if err != nil {
- return nil, nil
- }
+func (e *PublicAPI) getTransactionByBlockAndIndex(block *tmrpctypes.ResultBlock, idx hexutil.Uint) (*rpctypes.Transaction, error) {
+ // return if index out of bounds
+ if uint64(idx) >= uint64(len(block.Block.Txs)) {
+ return nil, nil
+ }
- i := int(idx)
- ethMsgs := e.backend.GetEthereumMsgsFromTendermintBlock(block, blockRes)
- if i >= len(ethMsgs) {
- e.logger.Debug("block txs index out of bound", "index", i)
- return nil, nil
- }
+ tx := block.Block.Txs[idx]
- msg = ethMsgs[i]
- }
+ blockHash := common.BytesToHash(block.Block.Hash())
+ blockHeight := uint64(block.Block.Height)
+ txIndex := uint64(idx)
- return rpctypes.NewTransactionFromMsg(
- msg,
- common.BytesToHash(block.Block.Hash()),
- uint64(block.Block.Height),
- uint64(idx),
- e.chainIDEpoch,
+ return rpctypes.TmTxToEthTx(
+ e.clientCtx.TxConfig.TxDecoder(),
+ tx,
+ &blockHash,
+ &blockHeight,
+ &txIndex,
)
}
// GetTransactionByBlockHashAndIndex returns the transaction identified by hash and index.
-func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
+func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexutil.Uint) (*rpctypes.Transaction, error) {
e.logger.Debug("eth_getTransactionByBlockHashAndIndex", "hash", hash.Hex(), "index", idx)
- block, err := e.clientCtx.Client.BlockByHash(e.ctx, hash.Bytes())
+ block, err := tmrpccore.BlockByHash(nil, hash.Bytes())
if err != nil {
e.logger.Debug("block not found", "hash", hash.Hex(), "error", err.Error())
return nil, nil
@@ -784,10 +792,10 @@ func (e *PublicAPI) GetTransactionByBlockHashAndIndex(hash common.Hash, idx hexu
}
// GetTransactionByBlockNumberAndIndex returns the transaction identified by number and index.
-func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.RPCTransaction, error) {
+func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockNumber, idx hexutil.Uint) (*rpctypes.Transaction, error) {
e.logger.Debug("eth_getTransactionByBlockNumberAndIndex", "number", blockNum, "index", idx)
- block, err := e.clientCtx.Client.Block(e.ctx, blockNum.TmHeight())
+ block, err := tmrpccore.Block(nil, blockNum.TmHeight())
if err != nil {
e.logger.Debug("block not found", "height", blockNum.Int64(), "error", err.Error())
return nil, nil
@@ -802,150 +810,113 @@ func (e *PublicAPI) GetTransactionByBlockNumberAndIndex(blockNum rpctypes.BlockN
}
// GetTransactionReceipt returns the transaction receipt identified by hash.
-func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interface{}, error) {
- hexTx := hash.Hex()
- e.logger.Debug("eth_getTransactionReceipt", "hash", hexTx)
-
- res, err := e.backend.GetTxByEthHash(hash)
+func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (*rpctypes.TransactionReceipt, error) {
+ e.logger.Debug("eth_getTransactionReceipt", "hash", hash)
+ res, err := e.backend.GetTxByHash(hash)
if err != nil {
- e.logger.Debug("tx not found", "hash", hexTx, "error", err.Error())
return nil, nil
}
- msgIndex, attrs := rpctypes.FindTxAttributes(res.TxResult.Events, hexTx)
- if msgIndex < 0 {
- return nil, fmt.Errorf("ethereum tx not found in msgs: %s", hexTx)
- }
-
- resBlock, err := e.clientCtx.Client.Block(e.ctx, &res.Height)
- if err != nil {
- e.logger.Debug("block not found", "height", res.Height, "error", err.Error())
+ block := e.backend.GetBlockStore().LoadBlock(res.Height)
+ if block == nil {
+ e.logger.Debug("eth_getTransactionReceipt", "hash", hash, "block not found")
return nil, nil
}
- tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
+ blockResults, err := tmrpccore.BlockResults(nil, &block.Height)
if err != nil {
- e.logger.Debug("decoding failed", "error", err.Error())
- return nil, fmt.Errorf("failed to decode tx: %w", err)
+ e.logger.Debug("eth_getTransactionReceipt", "hash", hash, "block not found")
+ return nil, nil
}
- // the `msgIndex` is inferred from tx events, should be within the bound.
- msg := tx.GetMsgs()[msgIndex]
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- e.logger.Debug(fmt.Sprintf("invalid tx type: %T", msg))
- return nil, fmt.Errorf("invalid tx type: %T", msg)
- }
+ blockHash := common.BytesToHash(block.Hash())
+ blockHeight := uint64(res.Height)
+ txIndex := uint64(res.Index)
- txData, err := evmtypes.UnpackTxData(ethMsg.Data)
+ rpcTx, err := rpctypes.TmTxToEthTx(
+ e.clientCtx.TxConfig.TxDecoder(),
+ res.Tx,
+ &blockHash,
+ &blockHeight,
+ &txIndex,
+ )
if err != nil {
- e.logger.Error("failed to unpack tx data", "error", err.Error())
return nil, err
}
- cumulativeGasUsed := uint64(0)
- blockRes, err := e.clientCtx.Client.BlockResults(e.ctx, &res.Height)
- if err != nil {
- e.logger.Debug("failed to retrieve block results", "height", res.Height, "error", err.Error())
- return nil, nil
+ cumulativeGasUsed := uint64(res.TxResult.GasUsed)
+ if *rpcTx.TransactionIndex != 0 {
+ cumulativeGasUsed += rpctypes.GetBlockCumulativeGas(blockResults, int(*rpcTx.TransactionIndex))
}
- for i := 0; i < int(res.Index) && i < len(blockRes.TxsResults); i++ {
- cumulativeGasUsed += uint64(blockRes.TxsResults[i].GasUsed)
- }
- cumulativeGasUsed += rpctypes.AccumulativeGasUsedOfMsg(res.TxResult.Events, msgIndex)
+ _, attrs := rpctypes.FindTxAttributes(res.TxResult.Events, hash.Hex())
- var gasUsed uint64
- if len(tx.GetMsgs()) == 1 {
- // backward compatibility
- gasUsed = uint64(res.TxResult.GasUsed)
+ var (
+ contractAddress *common.Address
+ bloom = ethtypes.BytesToBloom(make([]byte, 6))
+ logs = make([]*ethtypes.Log, 0)
+ )
+ // Set status codes based on tx result
+ status := ethtypes.ReceiptStatusSuccessful
+ if res.TxResult.GetCode() == 1 {
+ status = ethtypes.ReceiptStatusFailed
} else {
- gasUsed, err = rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxGasUsed)
- if err != nil {
- return nil, err
+ // Get the transaction result from the log
+ _, found := attrs[evmtypes.AttributeKeyEthereumTxFailed]
+ if found {
+ status = ethtypes.ReceiptStatusFailed
}
- }
- // Get the transaction result from the log
- _, found := attrs[evmtypes.AttributeKeyEthereumTxFailed]
- var status hexutil.Uint
- if found {
- status = hexutil.Uint(ethtypes.ReceiptStatusFailed)
- } else {
- status = hexutil.Uint(ethtypes.ReceiptStatusSuccessful)
- }
-
- from, err := ethMsg.GetSender(e.chainIDEpoch)
- if err != nil {
- return nil, err
- }
-
- // parse tx logs from events
- logs, err := backend.TxLogsFromEvents(res.TxResult.Events, msgIndex)
- if err != nil {
- e.logger.Debug("logs not found", "hash", hexTx, "error", err.Error())
- }
-
- // Try to find txIndex from events
- found = false
- txIndex, err := rpctypes.GetUint64Attribute(attrs, evmtypes.AttributeKeyTxIndex)
- if err == nil {
- found = true
- } else {
- // Fallback to find tx index by iterating all valid eth transactions
- msgs := e.backend.GetEthereumMsgsFromTendermintBlock(resBlock, blockRes)
- for i := range msgs {
- if msgs[i].Hash == hexTx {
- txIndex = uint64(i)
- found = true
- break
+ if status == ethtypes.ReceiptStatusSuccessful {
+ // parse tx logs from events
+ logs, err = backend.TxLogsFromEvents(res.TxResult.Events, 0)
+ if err != nil {
+ e.logger.Debug("logs not found", "hash", hash, "error", err.Error())
+ }
+ if logs == nil {
+ logs = make([]*ethtypes.Log, 0)
+ }
+ if rpcTx.To == nil {
+ // TODO: Rewrite on more optimal way in order to get a contract address
+ tx, err := e.clientCtx.TxConfig.TxDecoder()(res.Tx)
+ if err != nil {
+ e.logger.Debug("decoding failed", "error", err.Error())
+ return nil, fmt.Errorf("failed to decode tx: %w", err)
+ }
+
+ // the `msgIndex` is inferred from tx events, should be within the bound.
+ msg := tx.GetMsgs()[0]
+ ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
+ if !ok {
+ e.logger.Debug(fmt.Sprintf("invalid tx type: %T", msg))
+ return nil, fmt.Errorf("invalid tx type: %T", msg)
+ }
+
+ txData, err := evmtypes.UnpackTxData(ethMsg.Data)
+ if err != nil {
+ e.logger.Error("failed to unpack tx data", "error", err.Error())
+ return nil, err
+ }
+ contractAddress = new(common.Address)
+ *contractAddress = crypto.CreateAddress(rpcTx.From, txData.GetNonce())
}
+ bloom = ethtypes.BytesToBloom(ethtypes.LogsBloom(logs))
}
}
- if !found {
- return nil, errors.New("can't find index of ethereum tx")
- }
-
- receipt := map[string]interface{}{
- // Consensus fields: These fields are defined by the Yellow Paper
- "status": status,
- "cumulativeGasUsed": hexutil.Uint64(cumulativeGasUsed),
- "logsBloom": ethtypes.BytesToBloom(ethtypes.LogsBloom(logs)),
- "logs": logs,
-
- // Implementation fields: These fields are added by geth when processing a transaction.
- // They are stored in the chain database.
- "transactionHash": hash,
- "contractAddress": nil,
- "gasUsed": hexutil.Uint64(gasUsed),
- "type": hexutil.Uint(txData.TxType()),
- // Inclusion information: These fields provide information about the inclusion of the
- // transaction corresponding to this receipt.
- "blockHash": common.BytesToHash(resBlock.Block.Header.Hash()).Hex(),
- "blockNumber": hexutil.Uint64(res.Height),
- "transactionIndex": hexutil.Uint64(txIndex),
-
- // sender and receiver (contract or EOA) addreses
- "from": from,
- "to": txData.GetTo(),
- }
-
- if logs == nil {
- receipt["logs"] = [][]*ethtypes.Log{}
- }
-
- // If the ContractAddress is 20 0x0 bytes, assume it is not a contract creation
- if txData.GetTo() == nil {
- receipt["contractAddress"] = crypto.CreateAddress(from, txData.GetNonce())
- }
-
- if dynamicTx, ok := txData.(*evmtypes.DynamicFeeTx); ok {
- baseFee, err := e.backend.BaseFee(res.Height)
- if err != nil {
- return nil, err
- }
- receipt["effectiveGasPrice"] = hexutil.Big(*dynamicTx.GetEffectiveGasPrice(baseFee))
+ receipt := &rpctypes.TransactionReceipt{
+ Status: hexutil.Uint64(status),
+ CumulativeGasUsed: hexutil.Uint64(cumulativeGasUsed),
+ LogsBloom: bloom,
+ Logs: logs,
+ TransactionHash: rpcTx.Hash,
+ ContractAddress: contractAddress,
+ GasUsed: hexutil.Uint64(res.TxResult.GasUsed),
+ BlockHash: *rpcTx.BlockHash,
+ BlockNumber: *rpcTx.BlockNumber,
+ TransactionIndex: *rpcTx.TransactionIndex,
+ From: rpcTx.From,
+ To: rpcTx.To,
}
return receipt, nil
@@ -953,36 +924,19 @@ func (e *PublicAPI) GetTransactionReceipt(hash common.Hash) (map[string]interfac
// GetPendingTransactions 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 *PublicAPI) GetPendingTransactions() ([]*rpctypes.RPCTransaction, error) {
+func (e *PublicAPI) GetPendingTransactions() ([]*rpctypes.Transaction, error) {
e.logger.Debug("eth_getPendingTransactions")
- txs, err := e.backend.PendingTransactions()
- if err != nil {
- return nil, err
- }
+ txs := e.backend.GetMempool().ReapMaxTxs(100)
- result := make([]*rpctypes.RPCTransaction, 0, len(txs))
+ result := make([]*rpctypes.Transaction, 0, len(txs))
for _, tx := range txs {
- for _, msg := range (*tx).GetMsgs() {
- ethMsg, ok := msg.(*evmtypes.MsgEthereumTx)
- if !ok {
- // not valid ethereum tx
- break
- }
-
- rpctx, err := rpctypes.NewTransactionFromMsg(
- ethMsg,
- common.Hash{},
- uint64(0),
- uint64(0),
- e.chainIDEpoch,
- )
- if err != nil {
- return nil, err
- }
-
- result = append(result, rpctx)
+ rpctx, err := rpctypes.TmTxToEthTx(e.clientCtx.TxConfig.TxDecoder(), tx, nil, nil, nil)
+ if err != nil {
+ return nil, err
}
+
+ result = append(result, rpctx)
}
return result, nil
@@ -1008,7 +962,6 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block
}
height := blockNum.Int64()
- ctx := rpctypes.ContextWithHeight(height)
// if the height is equal to zero, meaning the query condition of the block is either "pending" or "latest"
if height == 0 {
@@ -1024,14 +977,27 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block
height = int64(bn)
}
- clientCtx := e.clientCtx.WithHeight(height)
-
// query storage proofs
storageProofs := make([]rpctypes.StorageResult, len(storageKeys))
+ resBlock, err := e.backend.GetTendermintBlockByNumber(blockNum)
+ if err != nil {
+ return nil, err
+ }
+
+ // return if requested block height is greater than the current one or chain not synced
+ if resBlock == nil || resBlock.Block == nil {
+ return nil, nil
+ }
+
+ sdkCtx, err := e.backend.GetSdkContextWithHeader(&resBlock.Block.Header)
+ if err != nil {
+ return nil, err
+ }
+
for i, key := range storageKeys {
hexKey := common.HexToHash(key)
- valueBz, proof, err := e.queryClient.GetProof(clientCtx, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
+ valueBz, proof, err := types.GetProof(height, evmtypes.StoreKey, evmtypes.StateKey(address, hexKey.Bytes()))
if err != nil {
return nil, err
}
@@ -1050,18 +1016,11 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block
}
// query EVM account
- req := &evmtypes.QueryAccountRequest{
- Address: address.String(),
- }
-
- res, err := e.queryClient.Account(ctx, req)
- if err != nil {
- return nil, err
- }
+ acc := e.backend.GetEVMKeeper().GetAccountOrEmpty(sdkCtx, address)
// query account proofs
accountKey := authtypes.AddressStoreKey(sdk.AccAddress(address.Bytes()))
- _, proof, err := e.queryClient.GetProof(clientCtx, authtypes.StoreKey, accountKey)
+ _, proof, err := types.GetProof(height, authtypes.StoreKey, accountKey)
if err != nil {
return nil, err
}
@@ -1072,17 +1031,12 @@ func (e *PublicAPI) GetProof(address common.Address, storageKeys []string, block
accProofStr = proof.String()
}
- balance, ok := sdk.NewIntFromString(res.Balance)
- if !ok {
- return nil, errors.New("invalid balance")
- }
-
return &rpctypes.AccountResult{
Address: address,
AccountProof: []string{accProofStr},
- Balance: (*hexutil.Big)(balance.BigInt()),
- CodeHash: common.HexToHash(res.CodeHash),
- Nonce: hexutil.Uint64(res.Nonce),
+ Balance: (*hexutil.Big)(acc.Balance),
+ CodeHash: common.BytesToHash(acc.CodeHash),
+ Nonce: hexutil.Uint64(acc.Nonce),
StorageHash: common.Hash{}, // NOTE: stratos doesn't have a storage hash. TODO: implement?
StorageProof: storageProofs,
}, nil
diff --git a/rpc/namespaces/ethereum/eth/filters/api.go b/rpc/namespaces/ethereum/eth/filters/api.go
index 11f6867c..ccadf761 100644
--- a/rpc/namespaces/ethereum/eth/filters/api.go
+++ b/rpc/namespaces/ethereum/eth/filters/api.go
@@ -8,7 +8,6 @@ import (
"github.com/tendermint/tendermint/libs/log"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
- rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
tmtypes "github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/client"
@@ -18,26 +17,11 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
+ "github.com/stratosnet/stratos-chain/rpc/backend"
"github.com/stratosnet/stratos-chain/rpc/types"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
)
-// Backend defines the methods requided by the PublicFilterAPI backend
-type Backend 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)
- BlockBloom(height *int64) (ethtypes.Bloom, error)
-
- BloomStatus() (uint64, uint64)
-
- RPCFilterCap() int32
- RPCLogsCap() int32
- RPCBlockRangeCap() int32
-}
-
// consider a filter inactive if it has not been polled for within deadline
var deadline = 5 * time.Minute
@@ -57,21 +41,21 @@ type filter struct {
type PublicFilterAPI struct {
logger log.Logger
clientCtx client.Context
- backend Backend
+ backend backend.BackendI
events *EventSystem
filtersMu sync.Mutex
filters map[rpc.ID]*filter
}
// NewPublicAPI returns a new PublicFilterAPI instance.
-func NewPublicAPI(logger log.Logger, clientCtx client.Context, tmWSClient *rpcclient.WSClient, backend Backend) *PublicFilterAPI {
+func NewPublicAPI(logger log.Logger, clientCtx client.Context, eventBus *tmtypes.EventBus, b backend.BackendI) *PublicFilterAPI {
logger = logger.With("api", "filter")
api := &PublicFilterAPI{
logger: logger,
clientCtx: clientCtx,
- backend: backend,
+ backend: b,
filters: make(map[rpc.ID]*filter),
- events: NewEventSystem(logger, tmWSClient),
+ events: NewEventSystem(clientCtx, logger, eventBus),
}
go api.timeoutLoop()
@@ -109,9 +93,6 @@ func (api *PublicFilterAPI) timeoutLoop() {
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newPendingTransactionFilter
func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
- api.filtersMu.Lock()
- defer api.filtersMu.Unlock()
-
if len(api.filters) >= int(api.backend.RPCFilterCap()) {
return rpc.ID("error creating pending tx filter: max limit reached")
}
@@ -121,42 +102,32 @@ func (api *PublicFilterAPI) NewPendingTransactionFilter() rpc.ID {
// wrap error on the ID
return rpc.ID(fmt.Sprintf("error creating pending tx filter: %s", err.Error()))
}
-
+ api.filtersMu.Lock()
api.filters[pendingTxSub.ID()] = &filter{typ: filters.PendingTransactionsSubscription, deadline: time.NewTimer(deadline), hashes: make([]common.Hash, 0), s: pendingTxSub}
+ api.filtersMu.Unlock()
go func(txsCh <-chan coretypes.ResultEvent, errCh <-chan error) {
defer cancelSubs()
for {
select {
- case ev, ok := <-txsCh:
- if !ok {
- api.filtersMu.Lock()
- delete(api.filters, pendingTxSub.ID())
- api.filtersMu.Unlock()
- return
- }
-
+ case ev := <-txsCh:
data, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
+ err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventTx)
+ pendingTxSub.err <- err
+ return
}
- tx, err := api.clientCtx.TxConfig.TxDecoder()(data.Tx)
+ txHash, err := types.GetTxHash(api.clientCtx.TxConfig.TxDecoder(), tmtypes.Tx(data.Tx))
if err != nil {
- api.logger.Debug("fail to decode tx", "error", err.Error())
- continue
+ pendingTxSub.err <- err
+ return
}
api.filtersMu.Lock()
if f, found := api.filters[pendingTxSub.ID()]; found {
- for _, msg := range tx.GetMsgs() {
- ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
- if ok {
- f.hashes = append(f.hashes, common.HexToHash(ethTx.Hash))
- }
- }
+ f.hashes = append(f.hashes, txHash)
}
api.filtersMu.Unlock()
case <-errCh:
@@ -195,31 +166,25 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
for {
select {
- case ev, ok := <-txsCh:
- if !ok {
- api.filtersMu.Lock()
- delete(api.filters, pendingTxSub.ID())
- api.filtersMu.Unlock()
- return
- }
-
+ case ev := <-txsCh:
data, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
+ err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventTx)
+ pendingTxSub.err <- err
+ return
}
- tx, err := api.clientCtx.TxConfig.TxDecoder()(data.Tx)
+ txHash, err := types.GetTxHash(api.clientCtx.TxConfig.TxDecoder(), tmtypes.Tx(data.Tx))
if err != nil {
- api.logger.Debug("fail to decode tx", "error", err.Error())
- continue
+ pendingTxSub.err <- err
+ return
}
- for _, msg := range tx.GetMsgs() {
- ethTx, ok := msg.(*evmtypes.MsgEthereumTx)
- if ok {
- _ = notifier.Notify(rpcSub.ID, common.HexToHash(ethTx.Hash))
- }
+ // To keep the original behaviour, send a single tx hash in one notification.
+ // TODO(rjl493456442) Send a batch of tx hashes in one notification
+ err = notifier.Notify(rpcSub.ID, txHash)
+ if err != nil {
+ return
}
case <-rpcSub.Err():
pendingTxSub.Unsubscribe(api.events)
@@ -239,58 +204,62 @@ func (api *PublicFilterAPI) NewPendingTransactions(ctx context.Context) (*rpc.Su
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newblockfilter
func (api *PublicFilterAPI) NewBlockFilter() rpc.ID {
- api.filtersMu.Lock()
- defer api.filtersMu.Unlock()
-
- if len(api.filters) >= int(api.backend.RPCFilterCap()) {
- return rpc.ID("error creating block filter: max limit reached")
- }
-
- headerSub, cancelSubs, err := api.events.SubscribeNewHeads()
+ headersSub, cancelSubs, err := api.events.SubscribeNewHeads()
if err != nil {
// wrap error on the ID
return rpc.ID(fmt.Sprintf("error creating block filter: %s", err.Error()))
}
- api.filters[headerSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headerSub}
+ api.filtersMu.Lock()
+ api.filters[headersSub.ID()] = &filter{typ: filters.BlocksSubscription, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: headersSub}
+ api.filtersMu.Unlock()
go func(headersCh <-chan coretypes.ResultEvent, errCh <-chan error) {
defer cancelSubs()
for {
select {
- case ev, ok := <-headersCh:
+ case ev := <-headersCh:
+ data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
if !ok {
- api.filtersMu.Lock()
- delete(api.filters, headerSub.ID())
- api.filtersMu.Unlock()
+ err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventNewBlockHeader)
+ headersSub.err <- err
return
}
-
- data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
- if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
+ header, err := types.EthHeaderFromTendermint(data.Header)
+ if err != nil {
+ headersSub.err <- err
+ return
+ }
+ // override dynamicly miner address
+ sdkCtx, err := api.backend.GetSdkContextWithHeader(&data.Header)
+ if err != nil {
+ headersSub.err <- err
+ return
}
- baseFee := types.BaseFeeFromEvents(data.ResultBeginBlock.Events)
+ validator, err := api.backend.GetEVMKeeper().GetCoinbaseAddress(sdkCtx)
+ if err != nil {
+ headersSub.err <- err
+ return
+ }
+ header.Coinbase = validator
- header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee)
api.filtersMu.Lock()
- if f, found := api.filters[headerSub.ID()]; found {
- f.hashes = append(f.hashes, header.Hash())
+ if f, found := api.filters[headersSub.ID()]; found {
+ f.hashes = append(f.hashes, header.Hash)
}
api.filtersMu.Unlock()
case <-errCh:
api.filtersMu.Lock()
- delete(api.filters, headerSub.ID())
+ delete(api.filters, headersSub.ID())
api.filtersMu.Unlock()
return
}
}
- }(headerSub.eventCh, headerSub.Err())
+ }(headersSub.eventCh, headersSub.Err())
- return headerSub.ID()
+ return headersSub.ID()
}
// NewHeads send a notification each time a new (header) block is appended to the chain.
@@ -313,23 +282,38 @@ func (api *PublicFilterAPI) NewHeads(ctx context.Context) (*rpc.Subscription, er
for {
select {
- case ev, ok := <-headersCh:
+ case ev := <-headersCh:
+ data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
if !ok {
- headersSub.Unsubscribe(api.events)
+ err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventNewBlockHeader)
+ headersSub.err <- err
return
}
- data, ok := ev.Data.(tmtypes.EventDataNewBlockHeader)
- if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
+ header, err := types.EthHeaderFromTendermint(data.Header)
+ if err != nil {
+ headersSub.err <- err
+ return
+ }
+ // override dynamicly miner address
+ sdkCtx, err := api.backend.GetSdkContextWithHeader(&data.Header)
+ if err != nil {
+ headersSub.err <- err
+ return
}
- baseFee := types.BaseFeeFromEvents(data.ResultBeginBlock.Events)
+ validator, err := api.backend.GetEVMKeeper().GetCoinbaseAddress(sdkCtx)
+ if err != nil {
+ headersSub.err <- err
+ return
+ }
+ header.Coinbase = validator
- // TODO: fetch bloom from events
- header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee)
- _ = notifier.Notify(rpcSub.ID, header)
+ err = notifier.Notify(rpcSub.ID, header)
+ if err != nil {
+ headersSub.err <- err
+ return
+ }
case <-rpcSub.Err():
headersSub.Unsubscribe(api.events)
return
@@ -363,36 +347,38 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
for {
select {
- case ev, ok := <-logsCh:
- if !ok {
- logsSub.Unsubscribe(api.events)
- return
- }
-
- // filter only events from EVM module txs
- _, isMsgEthereumTx := ev.Events[evmtypes.TypeMsgEthereumTx]
-
+ case ev := <-logsCh:
+ _, isMsgEthereumTx := ev.Events[fmt.Sprintf("%s.%s", evmtypes.EventTypeEthereumTx, evmtypes.AttributeKeyEthereumTxHash)]
+ api.logger.Debug("\x1b[32m------ logs tx type is evm from sub: %t\x1b[0m\n", isMsgEthereumTx)
if !isMsgEthereumTx {
- // ignore transaction as it's not from the evm module
- return
+ continue
}
// get transaction result data
dataTx, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
+ err = fmt.Errorf("invalid event data %T, expected %s", ev.Data, tmtypes.EventTx)
+ logsSub.err <- err
+ return
}
+ api.logger.Debug("\x1b[32m------ logs tx dataTx: %+v\x1b[0m\n", dataTx)
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
+ logsSub.err <- err
return
}
-
- logs := FilterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
-
- for _, log := range logs {
- _ = notifier.Notify(rpcSub.ID, log)
+ api.logger.Debug("\x1b[32m------ logs tx response: %+v\x1b[0m\n", txResponse)
+ api.logger.Debug("\x1b[32m------ logs crit: %+v\x1b[0m\n", crit)
+
+ matchedLogs := FilterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
+ api.logger.Debug("\x1b[32m------ logs matchedLogs: %+v\x1b[0m\n", matchedLogs)
+ for _, log := range matchedLogs {
+ err = notifier.Notify(rpcSub.ID, log)
+ if err != nil {
+ logsSub.err <- err
+ return
+ }
}
case <-rpcSub.Err(): // client send an unsubscribe request
logsSub.Unsubscribe(api.events)
@@ -421,9 +407,6 @@ func (api *PublicFilterAPI) Logs(ctx context.Context, crit filters.FilterCriteri
//
// https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_newfilter
func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) {
- api.filtersMu.Lock()
- defer api.filtersMu.Unlock()
-
if len(api.filters) >= int(api.backend.RPCFilterCap()) {
return rpc.ID(""), fmt.Errorf("error creating filter: max limit reached")
}
@@ -440,28 +423,31 @@ func (api *PublicFilterAPI) NewFilter(criteria filters.FilterCriteria) (rpc.ID,
filterID = logsSub.ID()
+ api.filtersMu.Lock()
api.filters[filterID] = &filter{typ: filters.LogsSubscription, crit: criteria, deadline: time.NewTimer(deadline), hashes: []common.Hash{}, s: logsSub}
+ api.filtersMu.Unlock()
go func(eventCh <-chan coretypes.ResultEvent) {
defer cancelSubs()
for {
select {
- case ev, ok := <-eventCh:
- if !ok {
- api.filtersMu.Lock()
- delete(api.filters, filterID)
- api.filtersMu.Unlock()
- return
+ case ev := <-eventCh:
+ _, isMsgEthereumTx := ev.Events[fmt.Sprintf("%s.%s", evmtypes.EventTypeEthereumTx, evmtypes.AttributeKeyEthereumTxHash)]
+ if !isMsgEthereumTx {
+ continue
}
+
dataTx, ok := ev.Data.(tmtypes.EventDataTx)
if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
+ err = fmt.Errorf("invalid event data %T, expected EventDataTx", ev.Data)
+ logsSub.err <- err
+ return
}
txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
if err != nil {
+ logsSub.err <- err
return
}
@@ -612,3 +598,8 @@ func (api *PublicFilterAPI) GetFilterChanges(id rpc.ID) (interface{}, error) {
return nil, fmt.Errorf("invalid filter %s type %d", id, f.typ)
}
}
+
+// Syncing provides information when this nodes starts synchronising with the OneLedger network and when it's finished.
+func (api *PublicFilterAPI) Syncing() (*rpc.Subscription, error) {
+ return &rpc.Subscription{}, rpc.ErrNotificationsUnsupported
+}
diff --git a/rpc/namespaces/ethereum/eth/filters/filter_system.go b/rpc/namespaces/ethereum/eth/filters/filter_system.go
index a8b0ec72..6cb8e06c 100644
--- a/rpc/namespaces/ethereum/eth/filters/filter_system.go
+++ b/rpc/namespaces/ethereum/eth/filters/filter_system.go
@@ -6,13 +6,12 @@ import (
"sync"
"time"
+ "github.com/cosmos/cosmos-sdk/client"
"github.com/pkg/errors"
-
- tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/libs/log"
+ tmpubsub "github.com/tendermint/tendermint/libs/pubsub"
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"
sdk "github.com/cosmos/cosmos-sdk/types"
@@ -22,34 +21,48 @@ import (
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/rpc"
- "github.com/stratosnet/stratos-chain/rpc/ethereum/pubsub"
+ "github.com/stratosnet/stratos-chain/rpc/types"
evmtypes "github.com/stratosnet/stratos-chain/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()
+ // base tm tx event
+ txEvents = tmtypes.QueryForEvent(tmtypes.EventTx).String()
+ // listen only evm txs
+ evmEvents = tmquery.MustParse(fmt.Sprintf("%s='%s' AND %s.%s='%s'", tmtypes.EventTypeKey, tmtypes.EventTx, sdk.EventTypeMessage, sdk.AttributeKeyModule, evmtypes.ModuleName)).String()
+ // specify to listen a blocks instead of heads as it contain more details about the block header including current block hash
headerEvents = tmtypes.QueryForEvent(tmtypes.EventNewBlockHeader).String()
+ // for tendermint subscribe channel capacity to make it buffered
+ tmChannelCapacity = 100
)
// 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 {
- logger log.Logger
- ctx context.Context
- tmWSClient *rpcclient.WSClient
-
- // light client mode
- lightMode bool
+ logger log.Logger
+ ctx context.Context
+ clientCtx client.Context
index filterIndex
topicChans map[string]chan<- coretypes.ResultEvent
indexMux *sync.RWMutex
+ // Subscriptions
+ txsSub *Subscription // Subscription for new transaction event
+ logsSub *Subscription // Subscription for new log event
+ pendingLogsSub *Subscription // Subscription for pending log event
+ chainSub *Subscription // Subscription for new chain event
+
+ // 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
+ chainCh <-chan coretypes.ResultEvent // Channel to receive new chain event
+
// Channels
install chan *Subscription // install filter for event notification
uninstall chan *Subscription // remove filter for event notification
- eventBus pubsub.EventBus
+ eventBus *tmtypes.EventBus
}
// NewEventSystem creates a new manager that listens for event on the given mux,
@@ -58,27 +71,29 @@ type EventSystem struct {
//
// The returned manager has a loop that needs to be stopped with the Stop function
// or by stopping the given mux.
-func NewEventSystem(logger log.Logger, tmWSClient *rpcclient.WSClient) *EventSystem {
+func NewEventSystem(clientCtx client.Context, logger log.Logger, eventBus *tmtypes.EventBus) *EventSystem {
index := make(filterIndex)
for i := filters.UnknownSubscription; i < filters.LastIndexSubscription; i++ {
index[i] = make(map[rpc.ID]*Subscription)
}
es := &EventSystem{
- logger: logger,
- 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(),
+ logger: logger,
+ ctx: context.Background(),
+ clientCtx: clientCtx,
+ index: index,
+ topicChans: make(map[string]chan<- coretypes.ResultEvent, len(index)),
+ indexMux: new(sync.RWMutex),
+ install: make(chan *Subscription),
+ uninstall: make(chan *Subscription),
+ eventBus: eventBus,
+ txsCh: make(<-chan coretypes.ResultEvent),
+ logsCh: make(<-chan coretypes.ResultEvent),
+ pendingLogsCh: make(<-chan coretypes.ResultEvent),
+ chainCh: make(<-chan coretypes.ResultEvent),
}
go es.eventLoop()
- go es.consumeEvents()
return es
}
@@ -89,64 +104,128 @@ func (es *EventSystem) WithContext(ctx context.Context) {
}
// 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, pubsub.UnsubscribeFunc, error) {
+// 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
)
- ctx, cancelFn := context.WithCancel(context.Background())
- defer cancelFn()
-
- existingSubs := es.eventBus.Topics()
- for _, topic := range existingSubs {
- if topic == sub.event {
- eventCh, unsubFn, err := es.eventBus.Subscribe(sub.event)
- if err != nil {
- err := errors.Wrapf(err, "failed to subscribe to topic: %s", sub.event)
- return nil, nil, err
- }
-
- sub.eventCh = eventCh
- return sub, unsubFn, nil
- }
- }
+ es.ctx, cancelFn = context.WithTimeout(context.Background(), deadline)
switch sub.typ {
- case filters.LogsSubscription:
- err = es.tmWSClient.Subscribe(ctx, sub.event)
- case filters.BlocksSubscription:
- err = es.tmWSClient.Subscribe(ctx, sub.event)
- case filters.PendingTransactionsSubscription:
- err = es.tmWSClient.Subscribe(ctx, sub.event)
+ case
+ filters.PendingTransactionsSubscription,
+ filters.PendingLogsSubscription,
+ filters.MinedAndPendingLogsSubscription,
+ filters.LogsSubscription,
+ filters.BlocksSubscription:
+
+ eventCh, err = es.createEventBusSubscription(string(sub.id), sub.event)
default:
err = fmt.Errorf("invalid filter subscription type %d", sub.typ)
}
if err != nil {
sub.err <- err
- return nil, nil, err
+ return nil, cancelFn, err
}
// wrap events in a go routine to prevent blocking
- es.install <- sub
- <-sub.installed
+ go func() {
+ es.install <- sub
+ <-sub.installed
+ }()
+
+ sub.eventCh = eventCh
+ return sub, cancelFn, nil
+}
- eventCh, unsubFn, err := es.eventBus.Subscribe(sub.event)
+func (es *EventSystem) createEventBusSubscription(subscriber, query string) (out <-chan coretypes.ResultEvent, err error) {
+ q, err := tmquery.New(query)
if err != nil {
- return nil, nil, errors.Wrapf(err, "failed to subscribe to topic after installed: %s", sub.event)
+ return nil, errors.Wrap(err, "failed to parse query")
}
- sub.eventCh = eventCh
- return sub, unsubFn, nil
+ var sub tmtypes.Subscription
+ if tmChannelCapacity > 0 {
+ sub, err = es.eventBus.Subscribe(es.ctx, subscriber, q, tmChannelCapacity)
+ } else {
+ sub, err = es.eventBus.SubscribeUnbuffered(es.ctx, subscriber, q)
+ }
+
+ if err != nil {
+ return nil, errors.Wrap(err, "failed to subscribe")
+ }
+
+ outc := make(chan coretypes.ResultEvent, tmChannelCapacity)
+ go es.eventsRoutine(sub, subscriber, q, outc)
+
+ return outc, nil
+}
+
+func (es *EventSystem) eventsRoutine(
+ sub tmtypes.Subscription,
+ subscriber string,
+ q tmpubsub.Query,
+ outc chan<- coretypes.ResultEvent) {
+ for {
+ select {
+ case msg := <-sub.Out():
+ result := coretypes.ResultEvent{Query: q.String(), Data: msg.Data(), Events: msg.Events()}
+ if cap(outc) == 0 {
+ outc <- result
+ } else {
+ select {
+ case outc <- result:
+ default:
+ // es.logger.Error("wanted to publish ResultEvent, but out channel is full", "result", result, "query", result.Query)
+ }
+ }
+ case <-sub.Cancelled():
+ if sub.Err() == tmpubsub.ErrUnsubscribed {
+ return
+ }
+
+ es.logger.Error("subscription was cancelled, resubscribing...", "err", sub.Err(), "query", q.String())
+ sub = es.resubscribe(subscriber, q)
+ if sub == nil { // client was stopped
+ return
+ }
+ case <-es.eventBus.Quit():
+ return
+ }
+ }
+}
+
+// Try to resubscribe with exponential backoff.
+func (es *EventSystem) resubscribe(subscriber string, q tmpubsub.Query) tmtypes.Subscription {
+ attempts := 0
+ for {
+ if !es.eventBus.IsRunning() {
+ return nil
+ }
+
+ sub, err := es.eventBus.Subscribe(context.Background(), subscriber, q)
+ if err == nil {
+ return sub
+ }
+
+ attempts++
+ time.Sleep((10 << uint(attempts)) * time.Millisecond) // 10ms -> 20ms -> 40ms
+ }
}
// 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, pubsub.UnsubscribeFunc, error) {
+func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
var from, to rpc.BlockNumber
+ es.logger.Debug("\x1b[32m------ SubscribeLogs crit: %+v\x1b[0m\n", crit)
if crit.FromBlock == nil {
from = rpc.LatestBlockNumber
} else {
@@ -159,21 +238,44 @@ func (es *EventSystem) SubscribeLogs(crit filters.FilterCriteria) (*Subscription
}
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),
- (from >= 0 && 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)
}
}
+// 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)
+}
+
// 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, pubsub.UnsubscribeFunc, error) {
+func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.LogsSubscription,
@@ -184,17 +286,34 @@ func (es *EventSystem) subscribeLogs(crit filters.FilterCriteria) (*Subscription
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
+ es.logger.Debug("\x1b[32m------ logs subscribe: %+v\x1b[0m\n", sub)
+ return es.subscribe(sub)
+}
+
+// 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)
}
// SubscribeNewHeads subscribes to new block headers events.
-func (es EventSystem) SubscribeNewHeads() (*Subscription, pubsub.UnsubscribeFunc, error) {
+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),
+ headers: make(chan *types.Header),
installed: make(chan struct{}, 1),
err: make(chan error, 1),
}
@@ -202,7 +321,7 @@ func (es EventSystem) SubscribeNewHeads() (*Subscription, pubsub.UnsubscribeFunc
}
// SubscribePendingTxs subscribes to new pending transactions events from the mempool.
-func (es EventSystem) SubscribePendingTxs() (*Subscription, pubsub.UnsubscribeFunc, error) {
+func (es EventSystem) SubscribePendingTxs() (*Subscription, context.CancelFunc, error) {
sub := &Subscription{
id: rpc.NewID(),
typ: filters.PendingTransactionsSubscription,
@@ -217,88 +336,138 @@ func (es EventSystem) SubscribePendingTxs() (*Subscription, pubsub.UnsubscribeFu
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 {
- es.logger.Error("failed to add event topic to event bus", "topic", f.event, "error", err.Error())
- }
- 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
- }
- }
+func (es *EventSystem) handleLogs(ev coretypes.ResultEvent) {
+ data, _ := ev.Data.(tmtypes.EventDataTx)
+ es.logger.Debug("\x1b[32m------ logs data: %+v\x1b[0m\n", data)
+ // logReceipt := onetypes.GetTxEthLogs(&data.TxResult.Result, data.Index)
+ resultData, err := evmtypes.DecodeTransactionLogs(data.TxResult.Result.Data)
+ if err != nil {
+ return
+ }
- // remove topic only when channel is not used by other subscriptions
- if !channelInUse {
- if err := es.tmWSClient.Unsubscribe(es.ctx, f.event); err != nil {
- es.logger.Error("failed to unsubscribe from query", "query", f.event, "error", err.Error())
- }
+ if len(resultData.Logs) == 0 {
+ return
+ }
- ch, ok := es.topicChans[f.event]
- if ok {
- es.eventBus.RemoveTopic(f.event)
- close(ch)
- delete(es.topicChans, f.event)
- }
- }
+ for _, f := range es.index[filters.LogsSubscription] {
+ matchedLogs := FilterLogs(evmtypes.LogsToEthereum(resultData.Logs), f.logsCrit.FromBlock, f.logsCrit.ToBlock, f.logsCrit.Addresses, f.logsCrit.Topics)
+ if len(matchedLogs) > 0 {
+ f.logs <- matchedLogs
+ }
+ }
+}
- es.indexMux.Unlock()
- close(f.err)
+func (es *EventSystem) handleTxsEvent(ev coretypes.ResultEvent) {
+ data, _ := ev.Data.(tmtypes.EventDataTx)
+ for _, f := range es.index[filters.PendingTransactionsSubscription] {
+ // NOTE: In previous version, data.Tx return types.Tx, but right now just bytes,
+ // so we need manually covert in order to get sha256 hash fot tx payload.
+ txHash, err := types.GetTxHash(es.clientCtx.TxConfig.TxDecoder(), tmtypes.Tx(data.Tx))
+ if err != nil {
+ continue
}
+ f.hashes <- []common.Hash{txHash}
}
}
-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 {
- es.logger.Error("failed to JSON unmarshal ResponsesCh result event", "error", err.Error())
- continue
- }
+func (es *EventSystem) handleChainEvent(ev coretypes.ResultEvent) {
+ data, _ := ev.Data.(tmtypes.EventDataNewBlockHeader)
+ for _, f := range es.index[filters.BlocksSubscription] {
+ header, err := types.EthHeaderFromTendermint(data.Header)
+ if err != nil {
+ continue
+ }
+ f.headers <- header
+ }
+}
- if len(ev.Query) == 0 {
- // skip empty responses
- continue
- }
+// 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()
- es.indexMux.RLock()
- ch, ok := es.topicChans[ev.Query]
- es.indexMux.RUnlock()
- if !ok {
- es.logger.Debug("channel for subscription not found", "topic", ev.Query)
- es.logger.Debug("list of available channels", "channels", es.eventBus.Topics())
- continue
+ // Ensure all subscriptions get cleaned up
+ defer func() {
+ es.txsSub.Unsubscribe(es)
+ es.logsSub.Unsubscribe(es)
+ es.pendingLogsSub.Unsubscribe(es)
+ es.chainSub.Unsubscribe(es)
+ }()
+
+ for {
+ select {
+ case txEvent := <-es.txsSub.eventCh:
+ es.logger.Debug("\x1b[32m------ tx event trigger from event loop: %+v\x1b[0m\n", txEvent)
+ es.handleTxsEvent(txEvent)
+ case headerEv := <-es.chainSub.eventCh:
+ es.logger.Debug("\x1b[32m------ header event trigger from event loop: %+v\x1b[0m\n", headerEv)
+ es.handleChainEvent(headerEv)
+ case logsEv := <-es.logsSub.eventCh:
+ es.logger.Debug("\x1b[32m------ logs event trigger from event loop: %+v\x1b[0m\n", logsEv)
+ es.handleLogs(logsEv)
+ case logsEv := <-es.pendingLogsSub.eventCh:
+ es.logger.Debug("\x1b[32m------ pending logs event trigger from event loop: %+v\x1b[0m\n", logsEv)
+ 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)
- // gracefully handle lagging subscribers
- t := time.NewTimer(time.Second)
- select {
- case <-t.C:
- es.logger.Debug("dropped event during lagging subscription", "topic", ev.Query)
- case ch <- ev:
+ 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.pendingLogsSub.Err():
+ return
+ case <-es.chainSub.Err():
+ return
}
-
- time.Sleep(time.Second)
}
}
diff --git a/rpc/namespaces/ethereum/eth/filters/filters.go b/rpc/namespaces/ethereum/eth/filters/filters.go
index 1b6cb647..98faa945 100644
--- a/rpc/namespaces/ethereum/eth/filters/filters.go
+++ b/rpc/namespaces/ethereum/eth/filters/filters.go
@@ -13,6 +13,7 @@ import (
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/eth/filters"
+ "github.com/stratosnet/stratos-chain/rpc/backend"
"github.com/stratosnet/stratos-chain/rpc/types"
)
@@ -26,7 +27,7 @@ type BloomIV struct {
// Filter can be used to retrieve and filter logs.
type Filter struct {
logger log.Logger
- backend Backend
+ backend backend.BackendI
criteria filters.FilterCriteria
bloomFilters [][]BloomIV // Filter the system is matching for
@@ -34,14 +35,14 @@ type Filter struct {
// NewBlockFilter creates a new filter which directly inspects the contents of
// a block to figure out whether it is interesting or not.
-func NewBlockFilter(logger log.Logger, backend Backend, criteria filters.FilterCriteria) *Filter {
+func NewBlockFilter(logger log.Logger, b backend.BackendI, criteria filters.FilterCriteria) *Filter {
// Create a generic filter and convert it into a block filter
- return newFilter(logger, backend, criteria, nil)
+ return newFilter(logger, b, 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(logger log.Logger, backend Backend, begin, end int64, addresses []common.Address, topics [][]common.Hash) *Filter {
+func NewRangeFilter(logger log.Logger, b backend.BackendI, 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.
@@ -70,14 +71,14 @@ func NewRangeFilter(logger log.Logger, backend Backend, begin, end int64, addres
Topics: topics,
}
- return newFilter(logger, backend, criteria, createBloomFilters(filtersBz, logger))
+ return newFilter(logger, b, criteria, createBloomFilters(filtersBz, logger))
}
// newFilter returns a new Filter
-func newFilter(logger log.Logger, backend Backend, criteria filters.FilterCriteria, bloomFilters [][]BloomIV) *Filter {
+func newFilter(logger log.Logger, b backend.BackendI, criteria filters.FilterCriteria, bloomFilters [][]BloomIV) *Filter {
return &Filter{
logger: logger,
- backend: backend,
+ backend: b,
criteria: criteria,
bloomFilters: bloomFilters,
}
diff --git a/rpc/namespaces/ethereum/eth/filters/subscription.go b/rpc/namespaces/ethereum/eth/filters/subscription.go
index d9b22798..288dac60 100644
--- a/rpc/namespaces/ethereum/eth/filters/subscription.go
+++ b/rpc/namespaces/ethereum/eth/filters/subscription.go
@@ -3,6 +3,7 @@ package filters
import (
"time"
+ "github.com/stratosnet/stratos-chain/rpc/types"
coretypes "github.com/tendermint/tendermint/rpc/core/types"
"github.com/ethereum/go-ethereum/common"
@@ -20,7 +21,7 @@ type Subscription struct {
logsCrit filters.FilterCriteria
logs chan []*ethtypes.Log
hashes chan []common.Hash
- headers chan *ethtypes.Header
+ headers chan *types.Header
installed chan struct{} // closed when the filter is installed
eventCh <-chan coretypes.ResultEvent
err chan error
diff --git a/rpc/namespaces/ethereum/miner/api.go b/rpc/namespaces/ethereum/miner/api.go
index 0e89d33d..59084403 100644
--- a/rpc/namespaces/ethereum/miner/api.go
+++ b/rpc/namespaces/ethereum/miner/api.go
@@ -104,7 +104,7 @@ func (api *API) SetEtherbase(etherbase common.Address) bool {
WithChainID(api.clientCtx.ChainID).
WithKeybase(api.clientCtx.Keyring).
WithTxConfig(api.clientCtx.TxConfig).
- WithSequence(uint64(*nonce)).
+ WithSequence(uint64(nonce)).
WithGasAdjustment(1.25)
_, gas, err := tx.CalculateGas(api.clientCtx, txFactory, msg)
diff --git a/rpc/namespaces/ethereum/net/api.go b/rpc/namespaces/ethereum/net/api.go
index 40a908f8..6cc61677 100644
--- a/rpc/namespaces/ethereum/net/api.go
+++ b/rpc/namespaces/ethereum/net/api.go
@@ -1,55 +1,37 @@
package net
import (
- "context"
- "fmt"
"math/big"
- rpcclient "github.com/tendermint/tendermint/rpc/client"
-
- "github.com/cosmos/cosmos-sdk/client"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/stratosnet/stratos-chain/rpc/backend"
)
// PublicAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec.
type PublicAPI struct {
- networkVersion uint64
- tmClient rpcclient.Client
+ backend backend.BackendI
}
// NewPublicAPI creates an instance of the public Net Web3 API.
-func NewPublicAPI(clientCtx client.Context) *PublicAPI {
- eip155ChainId, ok := new(big.Int).SetString(clientCtx.ChainID, 10)
- if !ok {
- panic("failed to parse chainID")
- }
-
+func NewPublicAPI(backend backend.BackendI) *PublicAPI {
return &PublicAPI{
- networkVersion: eip155ChainId.Uint64(),
- tmClient: clientCtx.Client,
+ backend: backend,
}
}
// Version returns the current ethereum protocol version.
-func (s *PublicAPI) Version() string {
- return fmt.Sprintf("%d", s.networkVersion)
+func (s *PublicAPI) Version() (string, error) {
+ ctx := s.backend.GetSdkContext()
+ params := s.backend.GetEVMKeeper().GetParams(ctx)
+ return params.ChainConfig.ChainID.String(), nil
}
// Listening returns if client is actively listening for network connections.
func (s *PublicAPI) Listening() bool {
- ctx := context.Background()
- netInfo, err := s.tmClient.NetInfo(ctx)
- if err != nil {
- return false
- }
- return netInfo.Listening
+ return s.backend.GetNode().IsListening()
}
// PeerCount returns the number of peers currently connected to the client.
-func (s *PublicAPI) PeerCount() int {
- ctx := context.Background()
- netInfo, err := s.tmClient.NetInfo(ctx)
- if err != nil {
- return 0
- }
- return len(netInfo.Peers)
+func (s *PublicAPI) PeerCount() hexutil.Big {
+ return hexutil.Big(*big.NewInt(int64(len(s.backend.GetSwitch().Peers().List()))))
}
diff --git a/rpc/namespaces/ethereum/txpool/api.go b/rpc/namespaces/ethereum/txpool/api.go
index 48e49dfb..ea1dd0a7 100644
--- a/rpc/namespaces/ethereum/txpool/api.go
+++ b/rpc/namespaces/ethereum/txpool/api.go
@@ -1,50 +1,90 @@
package txpool
import (
+ "fmt"
+
"github.com/tendermint/tendermint/libs/log"
+ "github.com/cosmos/cosmos-sdk/client"
+ "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/stratosnet/stratos-chain/rpc/backend"
"github.com/stratosnet/stratos-chain/rpc/types"
)
// PublicAPI offers and API for the transaction pool. It only operates on data that is non-confidential.
type PublicAPI struct {
- logger log.Logger
+ logger log.Logger
+ clientCtx client.Context
+ backend backend.BackendI
}
// NewPublicAPI creates a new tx pool service that gives information about the transaction pool.
-func NewPublicAPI(logger log.Logger) *PublicAPI {
+func NewPublicAPI(logger log.Logger, clientCtx client.Context, backend backend.BackendI) *PublicAPI {
return &PublicAPI{
- logger: logger.With("module", "txpool"),
+ logger: logger.With("module", "txpool"),
+ clientCtx: clientCtx,
+ backend: backend,
+ }
+}
+
+func (api *PublicAPI) getPendingList() map[common.Address]map[string]*types.Transaction {
+ pTxs := types.GetPendingTxs(api.clientCtx.TxConfig.TxDecoder(), api.backend.GetMempool())
+ pending := make(map[common.Address]map[string]*types.Transaction)
+ for _, pTx := range pTxs {
+ addrMap, ok := pending[pTx.From]
+ if !ok {
+ addrMap = make(map[string]*types.Transaction)
+ pending[pTx.From] = addrMap
+ }
+ addrMap[fmt.Sprintf("%d", pTx.Nonce)] = pTx
}
+ return pending
}
// Content returns the transactions contained within the transaction pool
-func (api *PublicAPI) Content() (map[string]map[string]map[string]*types.RPCTransaction, error) {
+func (api *PublicAPI) Content() map[string]map[common.Address]map[string]*types.Transaction {
api.logger.Debug("txpool_content")
- content := map[string]map[string]map[string]*types.RPCTransaction{
- "pending": make(map[string]map[string]*types.RPCTransaction),
- "queued": make(map[string]map[string]*types.RPCTransaction),
+ content := map[string]map[common.Address]map[string]*types.Transaction{
+ "pending": api.getPendingList(),
+ "queued": make(map[common.Address]map[string]*types.Transaction),
}
- return content, nil
+ return content
}
// Inspect returns the content of the transaction pool and flattens it into an
-func (api *PublicAPI) Inspect() (map[string]map[string]map[string]string, error) {
+func (api *PublicAPI) Inspect() map[string]map[string]map[string]string {
api.logger.Debug("txpool_inspect")
content := map[string]map[string]map[string]string{
"pending": make(map[string]map[string]string),
"queued": make(map[string]map[string]string),
}
- return content, nil
+ pending := api.getPendingList()
+ // Define a formatter to flatten a transaction into a string
+ var format = func(tx *types.Transaction) string {
+ if to := tx.To; to != nil {
+ return fmt.Sprintf("%s: %d wei + %d gas × %d wei", tx.To.Hex(), tx.Value.ToInt(), tx.Gas, tx.GasPrice.ToInt())
+ }
+ return fmt.Sprintf("contract creation: %d wei + %d gas × %d wei", tx.Value.ToInt(), tx.Gas, tx.GasPrice.ToInt())
+ }
+ // Flatten the pending transactions
+ for account, txs := range pending {
+ dump := make(map[string]string)
+ for _, tx := range txs {
+ dump[fmt.Sprintf("%d", tx.Nonce)] = format(tx)
+ }
+ content["pending"][account.Hex()] = dump
+ }
+
+ return content
}
// Status returns the number of pending and queued transaction in the pool.
func (api *PublicAPI) Status() map[string]hexutil.Uint {
api.logger.Debug("txpool_status")
return map[string]hexutil.Uint{
- "pending": hexutil.Uint(0),
+ "pending": hexutil.Uint(types.GetPendingTxsLen(api.backend.GetMempool())),
"queued": hexutil.Uint(0),
}
}
diff --git a/rpc/server.go b/rpc/server.go
new file mode 100644
index 00000000..0c3f9fcd
--- /dev/null
+++ b/rpc/server.go
@@ -0,0 +1,104 @@
+package rpc
+
+import (
+ "context"
+ "net/http"
+ "time"
+
+ "github.com/ethereum/go-ethereum/node"
+ "github.com/ethereum/go-ethereum/rpc"
+ "github.com/stratosnet/stratos-chain/server/config"
+ "github.com/tendermint/tendermint/libs/log"
+)
+
+type Web3Server struct {
+ httpURI string
+ wsURI string
+ enabled bool
+ logger log.Logger
+}
+
+func NewWeb3Server(cfg config.Config, logger log.Logger) *Web3Server {
+ return &Web3Server{
+ httpURI: cfg.JSONRPC.Address,
+ wsURI: cfg.JSONRPC.WsAddress,
+ enabled: cfg.JSONRPC.Enable,
+ logger: logger,
+ }
+}
+
+func (web3 *Web3Server) start(uri string, handler http.Handler) error {
+ if !web3.enabled {
+ web3.logger.Info("Web3 api disabled, skipping")
+ return nil
+ }
+
+ var (
+ err error
+ )
+ channel := make(chan error)
+ timeout := make(chan error)
+
+ srv := &http.Server{
+ Addr: uri,
+ Handler: handler,
+ ReadTimeout: 60 * time.Second,
+ WriteTimeout: 60 * time.Second,
+ }
+ srv.SetKeepAlivesEnabled(true)
+
+ //Timeout Go routine
+ go func() {
+ time.Sleep(time.Duration(2) * time.Second)
+ timeout <- nil
+ }()
+
+ go func(ch chan error) {
+ web3.logger.Info("starting Web3 RPC server on " + uri)
+ err := srv.ListenAndServe()
+ if err != nil {
+ web3.logger.Error("server error, details: %s", err)
+ }
+ srv.Shutdown(context.TODO())
+ ch <- err
+ }(channel)
+
+ select {
+ case err = <-channel:
+ case err = <-timeout:
+ }
+
+ return err
+}
+
+func (web3 *Web3Server) registerAPIs(server *rpc.Server, apis []rpc.API) error {
+ for _, api := range apis {
+ if err := server.RegisterName(api.Namespace, api.Service); err != nil {
+ web3.logger.Error(
+ "failed to register service in JSON RPC namespace",
+ "namespace", api.Namespace,
+ "service", api.Service,
+ )
+ return err
+ }
+ }
+ return nil
+}
+
+func (web3 *Web3Server) StartHTTP(apis []rpc.API) error {
+ rpcSrv := rpc.NewServer()
+ handler := node.NewHTTPHandlerStack(rpcSrv, []string{"*"}, []string{"localhost", "host.docker.internal"}, []byte{}) // TODO: Replace cors and vshosts from config
+ if err := web3.registerAPIs(rpcSrv, apis); err != nil {
+ return err
+ }
+ return web3.start(web3.httpURI, handler)
+}
+
+func (web3 *Web3Server) StartWS(apis []rpc.API) error {
+ rpcSrv := rpc.NewServer()
+ handler := rpcSrv.WebsocketHandler([]string{}) // TODO: Add config origins
+ if err := web3.registerAPIs(rpcSrv, apis); err != nil {
+ return err
+ }
+ return web3.start(web3.wsURI, handler)
+}
diff --git a/rpc/types/block.go b/rpc/types/block.go
index 809505b3..a516f405 100644
--- a/rpc/types/block.go
+++ b/rpc/types/block.go
@@ -27,6 +27,7 @@ const (
EthPendingBlockNumber = BlockNumber(-2)
EthLatestBlockNumber = BlockNumber(-1)
EthEarliestBlockNumber = BlockNumber(0)
+ EthInitialBlockNumber = BlockNumber(1)
)
const (
@@ -45,6 +46,7 @@ func NewBlockNumber(n *big.Int) BlockNumber {
return BlockNumber(n.Int64())
}
+// NOTE: Legacy, should be removed after cli rework
// ContextWithHeight wraps a context with the a gRPC block height header. If the provided height is
// 0, it will return an empty context and the gRPC query will use the latest block height for querying.
// Note that all metadata are processed and removed by tendermint layer, so it wont be accessible at gRPC server level.
diff --git a/rpc/types/query_client.go b/rpc/types/query_client.go
index 1594a11d..57af7559 100644
--- a/rpc/types/query_client.go
+++ b/rpc/types/query_client.go
@@ -1,3 +1,4 @@
+// NOTE: Legacy!!!
package types
import (
@@ -14,9 +15,9 @@ import (
)
// QueryClient defines a gRPC Client used for:
-// - Transaction simulation
-// - EVM module queries
-// - Fee market module queries
+// - Transaction simulation
+// - EVM module queries
+// - Fee market module queries
type QueryClient struct {
tx.ServiceClient
evmtypes.QueryClient
diff --git a/rpc/types/types.go b/rpc/types/types.go
index 9459cf8e..3ad20188 100644
--- a/rpc/types/types.go
+++ b/rpc/types/types.go
@@ -29,8 +29,8 @@ type StorageResult struct {
Proof []string `json:"proof"`
}
-// RPCTransaction represents a transaction that will serialize to the RPC representation of a transaction
-type RPCTransaction struct {
+// Transaction represents a transaction that will serialize to the RPC representation of a transaction
+type Transaction struct {
BlockHash *common.Hash `json:"blockHash"`
BlockNumber *hexutil.Big `json:"blockNumber"`
From common.Address `json:"from"`
@@ -87,3 +87,91 @@ type OneFeeHistory struct {
Reward []*big.Int // each element of the array will have the tip provided to miners for the percentile given
GasUsedRatio float64 // the ratio of gas used to the gas limit for each block
}
+
+// NOTE: Forked because tendermint block hash calculated in another way
+// default ethereum take rlp from the struct
+type Header struct {
+ Hash common.Hash `json:"hash" gencodec:"required"`
+ ParentHash common.Hash `json:"parentHash" gencodec:"required"`
+ UncleHash common.Hash `json:"sha3Uncles" gencodec:"required"`
+ Coinbase common.Address `json:"miner" gencodec:"required"`
+ Root common.Hash `json:"stateRoot" gencodec:"required"`
+ TxHash common.Hash `json:"transactionsRoot" gencodec:"required"`
+ ReceiptHash common.Hash `json:"receiptsRoot" gencodec:"required"`
+ Bloom ethtypes.Bloom `json:"logsBloom" gencodec:"required"`
+ Difficulty *big.Int `json:"difficulty" gencodec:"required"`
+ Number *big.Int `json:"number" gencodec:"required"`
+ GasLimit uint64 `json:"gasLimit" gencodec:"required"`
+ GasUsed uint64 `json:"gasUsed" gencodec:"required"`
+ Time uint64 `json:"timestamp" gencodec:"required"`
+ Extra []byte `json:"extraData" gencodec:"required"`
+ MixDigest common.Hash `json:"mixHash"`
+ Nonce ethtypes.BlockNonce `json:"nonce"`
+ Size uint64 `json:"size"`
+
+ // BaseFee was added by EIP-1559 and is ignored in legacy headers.
+ // TODO: Add support
+ // NOTE: Do we need this in real?
+ // BaseFee *big.Int `json:"baseFeePerGas" rlp:"optional"`
+}
+
+// Block represents a block returned to RPC clients.
+type Block struct {
+ Number hexutil.Uint64 `json:"number"`
+ Hash common.Hash `json:"hash"`
+ ParentHash common.Hash `json:"parentHash"`
+ Nonce ethtypes.BlockNonce `json:"nonce"`
+ Sha3Uncles common.Hash `json:"sha3Uncles"`
+ LogsBloom ethtypes.Bloom `json:"logsBloom"`
+ TransactionsRoot common.Hash `json:"transactionsRoot"`
+ StateRoot common.Hash `json:"stateRoot"`
+ Miner common.Address `json:"miner"`
+ MixHash common.Hash `json:"mixHash"`
+ Difficulty hexutil.Uint64 `json:"difficulty"`
+ TotalDifficulty hexutil.Uint64 `json:"totalDifficulty"`
+ ExtraData hexutil.Bytes `json:"extraData"`
+ Size hexutil.Uint64 `json:"size"`
+ GasLimit *hexutil.Big `json:"gasLimit"`
+ GasUsed *hexutil.Big `json:"gasUsed"`
+ Timestamp hexutil.Uint64 `json:"timestamp"`
+ Uncles []common.Hash `json:"uncles"`
+ ReceiptsRoot common.Hash `json:"receiptsRoot"`
+ Transactions []interface{} `json:"transactions"`
+}
+
+// TransactionReceipt represents a mined transaction returned to RPC clients.
+type TransactionReceipt struct {
+ // Consensus fields: These fields are defined by the Yellow Paper
+ Status hexutil.Uint64 `json:"status"`
+ CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed"`
+ LogsBloom ethtypes.Bloom `json:"logsBloom"`
+ Logs []*ethtypes.Log `json:"logs"`
+
+ // Implementation fields: These fields are added by geth when processing a transaction.
+ // They are stored in the chain database.
+ TransactionHash common.Hash `json:"transactionHash"`
+ ContractAddress *common.Address `json:"contractAddress"`
+ GasUsed hexutil.Uint64 `json:"gasUsed"`
+
+ // Inclusion information: These fields provide information about the inclusion of the
+ // transaction corresponding to this receipt.
+ BlockHash common.Hash `json:"blockHash"`
+ BlockNumber hexutil.Big `json:"blockNumber"`
+ TransactionIndex hexutil.Uint64 `json:"transactionIndex"`
+
+ // sender and receiver (contract or EOA) addresses
+ From common.Address `json:"from"`
+ To *common.Address `json:"to"`
+}
+
+type TxByNonce []*Transaction
+
+func (s TxByNonce) Len() int { return len(s) }
+func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce < s[j].Nonce }
+func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+// Transactions implements DerivableList for transactions.
+type Transactions []*Transaction
+
+// Len returns the length of s.
+func (s Transactions) Len() int { return len(s) }
diff --git a/rpc/types/utils.go b/rpc/types/utils.go
index 21dfb729..007686f0 100644
--- a/rpc/types/utils.go
+++ b/rpc/types/utils.go
@@ -2,26 +2,53 @@ package types
import (
"bytes"
- "context"
+ "crypto/sha1"
"encoding/hex"
+ "errors"
"fmt"
"math/big"
+ "sort"
"strconv"
+ "github.com/cosmos/cosmos-sdk/client"
+ sdk "github.com/cosmos/cosmos-sdk/types"
+ sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
abci "github.com/tendermint/tendermint/abci/types"
+ "github.com/tendermint/tendermint/mempool"
+ tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtypes "github.com/tendermint/tendermint/types"
- "github.com/cosmos/cosmos-sdk/client"
- sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
+ "github.com/cosmos/cosmos-sdk/types/tx/signing"
+ authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
- "github.com/ethereum/go-ethereum/common/math"
ethtypes "github.com/ethereum/go-ethereum/core/types"
-
+ stratos "github.com/stratosnet/stratos-chain/types"
evmtypes "github.com/stratosnet/stratos-chain/x/evm/types"
+ "github.com/tendermint/tendermint/proto/tendermint/crypto"
+ tmrpccore "github.com/tendermint/tendermint/rpc/core"
+ tmrpccoretypes "github.com/tendermint/tendermint/rpc/core/types"
)
+var bAttributeKeyEthereumBloom = []byte(evmtypes.AttributeKeyEthereumBloom)
+var MempoolCapacity = 100
+
+// HashToUint64 used to convert string or hash string to uint64
+func HashToUint64(s string) uint64 {
+ h := sha1.New()
+ h.Write([]byte(s))
+
+ hash := h.Sum(nil)[:2]
+ result := uint64(0)
+ for i := 0; i < len(hash); i++ {
+ result = result << 8
+ result += uint64(hash[i])
+
+ }
+ return result
+}
+
// RawTxToEthTx returns a evm MsgEthereum transaction from raw tx bytes.
func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEthereumTx, error) {
tx, err := clientCtx.TxConfig.TxDecoder()(txBz)
@@ -40,37 +67,116 @@ func RawTxToEthTx(clientCtx client.Context, txBz tmtypes.Tx) ([]*evmtypes.MsgEth
return ethTxs, nil
}
+func GetBlockBloom(blockResults *tmrpccoretypes.ResultBlockResults) (ethtypes.Bloom, error) {
+ for _, event := range blockResults.EndBlockEvents {
+ if event.Type != evmtypes.EventTypeBlockBloom {
+ continue
+ }
+
+ for _, attr := range event.Attributes {
+ if bytes.Equal(attr.Key, bAttributeKeyEthereumBloom) {
+ return ethtypes.BytesToBloom(attr.Value), nil
+ }
+ }
+ }
+ return ethtypes.Bloom{}, errors.New("block bloom event is not found")
+}
+
// EthHeaderFromTendermint is an util function that returns an Ethereum Header
// from a tendermint Header.
-func EthHeaderFromTendermint(header tmtypes.Header, bloom ethtypes.Bloom, baseFee *big.Int) *ethtypes.Header {
- txHash := ethtypes.EmptyRootHash
- if len(header.DataHash) == 0 {
- txHash = common.BytesToHash(header.DataHash)
+func EthHeaderFromTendermint(header tmtypes.Header) (*Header, error) {
+ results, err := tmrpccore.BlockResults(nil, &header.Height)
+ if err != nil {
+ return nil, err
+ }
+ gasLimit, err := BlockMaxGasFromConsensusParams(header.Height)
+ if err != nil {
+ return nil, err
+ }
+
+ bloom, err := GetBlockBloom(results)
+ if err != nil {
+ return nil, err
+ }
+
+ gasUsed := int64(0)
+ for _, txResult := range results.TxsResults {
+ gasUsed += txResult.GasUsed
}
- return ðtypes.Header{
- ParentHash: common.BytesToHash(header.LastBlockID.Hash.Bytes()),
- UncleHash: ethtypes.EmptyUncleHash,
+ return &Header{
+ Hash: common.BytesToHash(header.Hash()),
+ ParentHash: common.BytesToHash(header.LastBlockID.Hash),
+ UncleHash: common.Hash{},
Coinbase: common.BytesToAddress(header.ProposerAddress),
Root: common.BytesToHash(header.AppHash),
- TxHash: txHash,
- ReceiptHash: ethtypes.EmptyRootHash,
+ TxHash: common.BytesToHash(header.DataHash),
+ ReceiptHash: common.Hash{},
Bloom: bloom,
- Difficulty: big.NewInt(0),
+ Difficulty: big.NewInt(1), // NOTE: Maybe move to some constant?
Number: big.NewInt(header.Height),
- GasLimit: 0,
- GasUsed: 0,
- Time: uint64(header.Time.UTC().Unix()),
- Extra: []byte{},
+ GasLimit: uint64(gasLimit),
+ GasUsed: uint64(gasUsed),
+ Time: uint64(header.Time.Unix()),
+ Extra: common.Hex2Bytes(""),
MixDigest: common.Hash{},
Nonce: ethtypes.BlockNonce{},
- BaseFee: baseFee,
+ // TODO: Add size somehow for legacy subscription support as for a new Header type after London
+ // is not exist but still present in newBlockHeaders call on subscription
+ }, nil
+}
+
+// EthBlockFromTendermint returns a JSON-RPC compatible Ethereum blockfrom a given Tendermint block.
+func EthBlockFromTendermint(txDecoder sdk.TxDecoder, block *tmtypes.Block, fullTx bool) (*Block, error) {
+ header, err := EthHeaderFromTendermint(block.Header)
+ if err != nil {
+ return nil, err
}
+
+ transactions := make([]interface{}, 0, len(block.Txs))
+ for i, tmTx := range block.Txs {
+ if !fullTx {
+ transactions = append(transactions, common.Bytes2Hex(tmTx.Hash()))
+ continue
+ }
+ blockHash := common.BytesToHash(block.Hash())
+ blockHeight := uint64(block.Height)
+ txIndex := uint64(i)
+ tx, err := TmTxToEthTx(txDecoder, tmTx, &blockHash, &blockHeight, &txIndex)
+ if err != nil {
+ // NOTE: Add debug?
+ continue
+ }
+ transactions = append(transactions, tx)
+ }
+
+ return &Block{
+ Number: hexutil.Uint64(header.Number.Uint64()),
+ Hash: header.Hash,
+ ParentHash: header.ParentHash,
+ Nonce: ethtypes.BlockNonce{}, // PoW specific
+ Sha3Uncles: common.Hash{}, // No uncles in Tendermint
+ LogsBloom: header.Bloom,
+ TransactionsRoot: header.TxHash,
+ StateRoot: header.Root,
+ Miner: header.Coinbase,
+ MixHash: common.Hash{},
+ Difficulty: hexutil.Uint64(header.Difficulty.Uint64()),
+ TotalDifficulty: hexutil.Uint64(header.Difficulty.Uint64()),
+ ExtraData: common.Hex2Bytes(""),
+ Size: hexutil.Uint64(block.Size()),
+ GasLimit: (*hexutil.Big)(new(big.Int).SetUint64(header.GasLimit)),
+ GasUsed: (*hexutil.Big)(new(big.Int).SetUint64(header.GasUsed)),
+ Timestamp: hexutil.Uint64(header.Time),
+ Transactions: transactions,
+ Uncles: make([]common.Hash, 0),
+ ReceiptsRoot: common.Hash{},
+ }, nil
}
// BlockMaxGasFromConsensusParams returns the gas limit for the current block from the chain consensus params.
-func BlockMaxGasFromConsensusParams(goCtx context.Context, clientCtx client.Context, blockHeight int64) (int64, error) {
- resConsParams, err := clientCtx.Client.ConsensusParams(goCtx, &blockHeight)
+func BlockMaxGasFromConsensusParams(blockHeight int64) (int64, error) {
+ resConsParams, err := tmrpccore.ConsensusParams(nil, &blockHeight)
if err != nil {
return int64(^uint32(0)), err
}
@@ -86,51 +192,6 @@ func BlockMaxGasFromConsensusParams(goCtx context.Context, clientCtx client.Cont
return gasLimit, nil
}
-// FormatBlock creates an ethereum block from a tendermint header and ethereum-formatted
-// transactions.
-func FormatBlock(
- header tmtypes.Header, size int, gasLimit int64,
- gasUsed *big.Int, transactions []interface{}, bloom ethtypes.Bloom,
- validatorAddr common.Address, baseFee *big.Int,
-) map[string]interface{} {
- var transactionsRoot common.Hash
- if len(transactions) == 0 {
- transactionsRoot = ethtypes.EmptyRootHash
- } else {
- transactionsRoot = common.BytesToHash(header.DataHash)
- }
-
- result := map[string]interface{}{
- "number": hexutil.Uint64(header.Height),
- "hash": hexutil.Bytes(header.Hash()),
- "parentHash": common.BytesToHash(header.LastBlockID.Hash.Bytes()),
- "nonce": ethtypes.BlockNonce{}, // PoW specific
- "sha3Uncles": ethtypes.EmptyUncleHash, // No uncles in Tendermint
- "logsBloom": bloom,
- "stateRoot": hexutil.Bytes(header.AppHash),
- "miner": validatorAddr,
- "mixHash": common.Hash{},
- "difficulty": (*hexutil.Big)(big.NewInt(0)),
- "extraData": "0x",
- "size": hexutil.Uint64(size),
- "gasLimit": hexutil.Uint64(gasLimit), // Static gas limit
- "gasUsed": (*hexutil.Big)(gasUsed),
- "timestamp": hexutil.Uint64(header.Time.Unix()),
- "transactionsRoot": transactionsRoot,
- "receiptsRoot": ethtypes.EmptyRootHash,
-
- "uncles": []common.Hash{},
- "transactions": transactions,
- "totalDifficulty": (*hexutil.Big)(big.NewInt(0)),
- }
-
- if baseFee != nil {
- result["baseFeePerGas"] = (*hexutil.Big)(baseFee)
- }
-
- return result
-}
-
type DataError interface {
Error() string // returns the message
ErrorData() interface{} // returns the error data
@@ -162,23 +223,230 @@ func ErrRevertedWith(data []byte) DataError {
}
}
-// NewTransactionFromMsg returns a transaction that will serialize to the RPC
-// representation, with the given location metadata set (if available).
-func NewTransactionFromMsg(
- msg *evmtypes.MsgEthereumTx,
- blockHash common.Hash,
- blockNumber, index uint64,
- baseFee *big.Int,
-) (*RPCTransaction, error) {
- tx := msg.AsTransaction()
- return NewRPCTransaction(tx, blockHash, blockNumber, index, baseFee)
+// GetPendingTxCountByAddress is used to get pending tx count (nonce) for user address
+func GetPendingTxCountByAddress(txDecoder sdk.TxDecoder, mem mempool.Mempool, address common.Address) (total uint64) {
+ for _, tmTx := range mem.ReapMaxTxs(MempoolCapacity) {
+ tx, err := txDecoder(tmTx)
+ if err != nil {
+ continue
+ }
+ msg := tx.GetMsgs()[0]
+ if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
+ ethTx := ethMsg.AsTransaction()
+ var signer ethtypes.Signer
+ if ethTx.Protected() {
+ signer = ethtypes.LatestSignerForChainID(ethTx.ChainId())
+ } else {
+ signer = ethtypes.HomesteadSigner{}
+ }
+ from, _ := ethtypes.Sender(signer, ethTx)
+ if bytes.Equal(from.Bytes(), address.Bytes()) {
+ total++
+ }
+ }
+ }
+ return
+}
+
+// GetProof performs an ABCI query with the given key and returns a merkle proof. The desired
+// tendermint height to perform the query should be set in the client context. The query will be
+// performed at one below this height (at the IAVL version) in order to obtain the correct merkle
+// proof. Proof queries at height less than or equal to 2 are not supported.
+// Issue: https://github.com/cosmos/cosmos-sdk/issues/6567
+func GetProof(height int64, storeKey string, key []byte) ([]byte, *crypto.ProofOps, error) {
+ // ABCI queries at height less than or equal to 2 are not supported.
+ // Base app does not support queries for height less than or equal to 1.
+ // Therefore, a query at height 2 would be equivalent to a query at height 3
+ if height <= 2 {
+ return nil, nil, fmt.Errorf("proof queries at height <= 2 are not supported")
+ }
+
+ // Use the IAVL height if a valid tendermint height is passed in.
+ height--
+
+ abciRes, err := tmrpccore.ABCIQuery(nil, fmt.Sprintf("store/%s/key", storeKey), key, height, true)
+ if err != nil {
+ return nil, nil, err
+ }
+
+ return abciRes.Response.Value, abciRes.Response.ProofOps, nil
+}
+
+func FormatTmHeaderToProto(header *tmtypes.Header) tmproto.Header {
+ return tmproto.Header{
+ Version: header.Version,
+ ChainID: header.ChainID,
+ Height: header.Height,
+ Time: header.Time,
+ LastBlockId: tmproto.BlockID{
+ Hash: header.LastBlockID.Hash,
+ PartSetHeader: tmproto.PartSetHeader{
+ Total: header.LastBlockID.PartSetHeader.Total,
+ Hash: header.LastBlockID.PartSetHeader.Hash,
+ },
+ },
+ LastCommitHash: header.LastCommitHash,
+ DataHash: header.DataHash,
+ ValidatorsHash: header.ValidatorsHash,
+ NextValidatorsHash: header.NextValidatorsHash,
+ ConsensusHash: header.ConsensusHash,
+ AppHash: header.AppHash,
+ LastResultsHash: header.LastResultsHash,
+ EvidenceHash: header.EvidenceHash,
+ ProposerAddress: header.ProposerAddress,
+ }
+}
+
+// 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(blockResults *tmrpccoretypes.ResultBlockResults, idx int) uint64 {
+ var gasUsed uint64
+
+ for i := 0; i < idx && i < len(blockResults.TxsResults); i++ {
+ tx := blockResults.TxsResults[i]
+ gasUsed += uint64(tx.GasUsed)
+ }
+ return gasUsed
+}
+
+func GetPendingTx(txDecoder sdk.TxDecoder, mem mempool.Mempool, hash common.Hash) (*Transaction, error) {
+ for _, uTx := range mem.ReapMaxTxs(MempoolCapacity) {
+ if bytes.Equal(uTx.Hash(), hash.Bytes()) {
+ return TmTxToEthTx(txDecoder, uTx, nil, nil, nil)
+ }
+ }
+ return nil, nil
+}
+
+func GetPendingTxs(txDecoder sdk.TxDecoder, mem mempool.Mempool) []*Transaction {
+ txs := make([]*Transaction, 0, MempoolCapacity)
+ for _, uTx := range mem.ReapMaxTxs(MempoolCapacity) {
+ if tx, err := TmTxToEthTx(txDecoder, uTx, nil, nil, nil); err == nil {
+ txs = append(txs, tx)
+ }
+ }
+ sort.Sort(TxByNonce(txs))
+ return txs
+}
+
+func GetPendingTxsLen(mem mempool.Mempool) int {
+ return len(mem.ReapMaxTxs(MempoolCapacity))
+}
+
+func GetNonEVMSignatures(sig []byte) (v, r, s *big.Int) {
+ var tmpV byte
+ if len(sig) == 65 {
+ tmpV = sig[len(sig)-1:][0]
+ } else {
+ // in case of 64 length
+ tmpV = byte(int(sig[0]) % 2)
+ }
+
+ v = new(big.Int).SetBytes([]byte{tmpV + 27})
+ r = new(big.Int).SetBytes(sig[:32])
+ s = new(big.Int).SetBytes(sig[32:64])
+ return
+}
+
+// GetTxHash get hash depends on what type, unfortunatelly system support two tx hash algo
+// in order to have opportunity for off chain tx hash computation
+func GetTxHash(txDecoder sdk.TxDecoder, tmTx tmtypes.Tx) (common.Hash, error) {
+ tx, err := txDecoder(tmTx)
+ if err != nil {
+ return common.Hash{}, err
+ }
+ if len(tx.GetMsgs()) == 0 {
+ return common.Hash{}, errors.New("tx contain empty msgs")
+ }
+ msg := tx.GetMsgs()[0]
+ if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
+ return ethMsg.AsTransaction().Hash(), nil
+ } else {
+ return common.BytesToHash(tmTx.Hash()), nil
+ }
+}
+
+// TmTxToEthTx convert ethereum and rest transaction on ethereum based structure
+func TmTxToEthTx(
+ txDecoder sdk.TxDecoder,
+ tmTx tmtypes.Tx,
+ blockHash *common.Hash,
+ blockNumber, index *uint64,
+) (*Transaction, error) {
+ tx, err := txDecoder(tmTx)
+ if err != nil {
+ return nil, err
+ }
+
+ // the `msgIndex` is inferred from tx events, should be within the bound.
+ // always taking first into account
+ msg := tx.GetMsgs()[0]
+
+ if ethMsg, ok := msg.(*evmtypes.MsgEthereumTx); ok {
+ tx := ethMsg.AsTransaction()
+ return NewRPCTransaction(tx, blockHash, blockNumber, index)
+ } else {
+ addr := msg.GetSigners()[0]
+ from := common.BytesToAddress(addr.Bytes())
+
+ nonce := uint64(0)
+ v := new(big.Int).SetInt64(0)
+ r := new(big.Int).SetInt64(0)
+ s := new(big.Int).SetInt64(0)
+ sigTx, ok := tx.(authsigning.SigVerifiableTx)
+ if ok {
+ sigs, _ := sigTx.GetSignaturesV2()
+ if len(sigs) > 0 {
+ sig := sigs[0]
+ sigProto := signing.SignatureDataToProto(sig.Data)
+ v, r, s = GetNonEVMSignatures(sigProto.GetSingle().GetSignature())
+ nonce = sig.Sequence
+ }
+ } else {
+ fmt.Printf("failed to get signature for %s\n", tmTx.Hash())
+ }
+
+ gas := uint64(0)
+ gasPrice := new(big.Int).SetInt64(stratos.DefaultGasPrice)
+ if feeTx, ok := tx.(sdk.FeeTx); ok {
+ gas = feeTx.GetGas()
+ gasPrice = new(big.Int).Div(
+ feeTx.GetFee().AmountOf("wei").BigInt(), // TODO: mv somehow wei from config
+ new(big.Int).SetUint64(gas),
+ )
+ }
+
+ var bNumber *big.Int
+ if blockNumber != nil {
+ bNumber = new(big.Int).SetUint64(*blockNumber)
+ }
+
+ return &Transaction{
+ BlockHash: blockHash,
+ BlockNumber: (*hexutil.Big)(bNumber),
+ Type: hexutil.Uint64(HashToUint64(sdk.MsgTypeURL(msg))),
+ From: from,
+ Gas: hexutil.Uint64(gas),
+ GasPrice: (*hexutil.Big)(gasPrice),
+ Hash: common.BytesToHash(tmTx.Hash()),
+ Input: make(hexutil.Bytes, 0),
+ Nonce: hexutil.Uint64(nonce),
+ To: new(common.Address),
+ TransactionIndex: (*hexutil.Uint64)(index),
+ Value: (*hexutil.Big)(new(big.Int).SetInt64(0)), // NOTE: How to get value in generic way?
+ V: (*hexutil.Big)(v),
+ R: (*hexutil.Big)(r),
+ S: (*hexutil.Big)(s),
+ }, nil
+ }
}
// NewTransactionFromData returns a transaction that will serialize to the RPC
// representation, with the given location metadata set (if available).
func NewRPCTransaction(
- tx *ethtypes.Transaction, blockHash common.Hash, blockNumber, index uint64, baseFee *big.Int,
-) (*RPCTransaction, error) {
+ tx *ethtypes.Transaction, blockHash *common.Hash, blockNumber, index *uint64,
+) (*Transaction, error) {
// Determine the signer. For replay-protected transactions, use the most permissive
// signer, because we assume that signers are backwards-compatible with old
// transactions. For non-protected transactions, the homestead signer signer is used
@@ -191,7 +459,7 @@ func NewRPCTransaction(
}
from, _ := ethtypes.Sender(signer, tx)
v, r, s := tx.RawSignatureValues()
- result := &RPCTransaction{
+ result := &Transaction{
Type: hexutil.Uint64(tx.Type()),
From: from,
Gas: hexutil.Uint64(tx.Gas()),
@@ -205,10 +473,10 @@ func NewRPCTransaction(
R: (*hexutil.Big)(r),
S: (*hexutil.Big)(s),
}
- if blockHash != (common.Hash{}) {
- result.BlockHash = &blockHash
- result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(blockNumber))
- result.TransactionIndex = (*hexutil.Uint64)(&index)
+ if blockHash != nil {
+ result.BlockHash = blockHash
+ result.BlockNumber = (*hexutil.Big)(new(big.Int).SetUint64(*blockNumber))
+ result.TransactionIndex = (*hexutil.Uint64)(index)
}
switch tx.Type() {
case ethtypes.AccessListTxType:
@@ -221,14 +489,7 @@ func NewRPCTransaction(
result.ChainID = (*hexutil.Big)(tx.ChainId())
result.GasFeeCap = (*hexutil.Big)(tx.GasFeeCap())
result.GasTipCap = (*hexutil.Big)(tx.GasTipCap())
- // if the transaction has been mined, compute the effective gas price
- if baseFee != nil && blockHash != (common.Hash{}) {
- // price = min(tip, gasFeeCap - baseFee) + baseFee
- price := math.BigMin(new(big.Int).Add(tx.GasTipCap(), baseFee), tx.GasFeeCap())
- result.GasPrice = (*hexutil.Big)(price)
- } else {
- result.GasPrice = (*hexutil.Big)(tx.GasFeeCap())
- }
+ result.GasPrice = (*hexutil.Big)(tx.GasPrice())
}
return result, nil
}
diff --git a/rpc/websockets.go b/rpc/websockets.go
deleted file mode 100644
index 1bc2e73b..00000000
--- a/rpc/websockets.go
+++ /dev/null
@@ -1,659 +0,0 @@
-package rpc
-
-import (
- "bytes"
- "context"
- "encoding/json"
- "fmt"
- "io/ioutil"
- "math/big"
- "net"
- "net/http"
- "sync"
-
- "github.com/gorilla/mux"
- "github.com/gorilla/websocket"
- "github.com/pkg/errors"
-
- "github.com/tendermint/tendermint/libs/log"
- rpcclient "github.com/tendermint/tendermint/rpc/jsonrpc/client"
- tmtypes "github.com/tendermint/tendermint/types"
-
- "github.com/cosmos/cosmos-sdk/client"
-
- "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/params"
- "github.com/ethereum/go-ethereum/rpc"
-
- "github.com/stratosnet/stratos-chain/rpc/ethereum/pubsub"
- rpcfilters "github.com/stratosnet/stratos-chain/rpc/namespaces/ethereum/eth/filters"
- "github.com/stratosnet/stratos-chain/rpc/types"
- "github.com/stratosnet/stratos-chain/server/config"
- evmtypes "github.com/stratosnet/stratos-chain/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
- certFile string
- keyFile string
- api *pubSubAPI
- logger log.Logger
-}
-
-func NewWebsocketsServer(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient, cfg config.Config) WebsocketsServer {
- logger = logger.With("api", "websocket-server")
- _, port, _ := net.SplitHostPort(cfg.JSONRPC.Address)
-
- return &websocketsServer{
- rpcAddr: "localhost:" + port, // FIXME: this shouldn't be hardcoded to localhost
- wsAddr: cfg.JSONRPC.WsAddress,
- certFile: cfg.TLS.CertificatePath,
- keyFile: cfg.TLS.KeyPath,
- api: newPubSubAPI(clientCtx, logger, tmWSClient),
- logger: logger,
- }
-}
-
-func (s *websocketsServer) Start() {
- ws := mux.NewRouter()
- ws.Handle("/", s)
-
- go func() {
- var err error
- if s.certFile == "" || s.keyFile == "" {
- err = http.ListenAndServe(s.wsAddr, ws)
- } else {
- err = http.ListenAndServeTLS(s.wsAddr, s.certFile, s.keyFile, ws)
- }
-
- if err != nil {
- if err == http.ErrServerClosed {
- return
- }
-
- s.logger.Error("failed to start HTTP server for WS", "error", err.Error())
- }
- }()
-}
-
-func (s *websocketsServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
- upgrader := websocket.Upgrader{
- CheckOrigin: func(r *http.Request) bool {
- return true
- },
- }
-
- conn, err := upgrader.Upgrade(w, r, nil)
- if err != nil {
- s.logger.Debug("websocket upgrade failed", "error", err.Error())
- 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) {
- // subscriptions of current connection
- subscriptions := make(map[rpc.ID]pubsub.UnsubscribeFunc)
- defer func() {
- // cancel all subscriptions when connection closed
- for _, unsubFn := range subscriptions {
- unsubFn()
- }
- }()
-
- for {
- _, mb, err := wsConn.ReadMessage()
- if err != nil {
- _ = wsConn.Close()
- return
- }
-
- var msg map[string]interface{}
- if err = json.Unmarshal(mb, &msg); err != nil {
- s.sendErrResponse(wsConn, err.Error())
- continue
- }
-
- // check if method == eth_subscribe or eth_unsubscribe
- method, ok := msg["method"].(string)
- if !ok {
- // otherwise, call the usual rpc server to respond
- if err := s.tcpGetAndSendResponse(wsConn, mb); err != nil {
- s.sendErrResponse(wsConn, err.Error())
- }
-
- continue
- }
-
- connID, ok := msg["id"].(float64)
- if !ok {
- s.sendErrResponse(
- wsConn,
- fmt.Errorf("invalid type for connection ID: %T", msg["id"]).Error(),
- )
- continue
- }
-
- switch method {
- case "eth_subscribe":
- params, ok := msg["params"].([]interface{})
- if !ok {
- s.sendErrResponse(wsConn, "invalid parameters")
- continue
- }
-
- if len(params) == 0 {
- s.sendErrResponse(wsConn, "empty parameters")
- continue
- }
-
- subID := rpc.NewID()
- unsubFn, err := s.api.subscribe(wsConn, subID, params)
- if err != nil {
- s.sendErrResponse(wsConn, err.Error())
- continue
- }
- subscriptions[subID] = unsubFn
-
- res := &SubscriptionResponseJSON{
- Jsonrpc: "2.0",
- ID: connID,
- Result: subID,
- }
-
- if err := wsConn.WriteJSON(res); err != nil {
- break
- }
- case "eth_unsubscribe":
- params, ok := msg["params"].([]interface{})
- if !ok {
- s.sendErrResponse(wsConn, "invalid parameters")
- continue
- }
-
- if len(params) == 0 {
- s.sendErrResponse(wsConn, "empty parameters")
- continue
- }
-
- id, ok := params[0].(string)
- if !ok {
- s.sendErrResponse(wsConn, "invalid parameters")
- continue
- }
-
- subID := rpc.ID(id)
- unsubFn, ok := subscriptions[subID]
- if ok {
- delete(subscriptions, subID)
- unsubFn()
- }
-
- res := &SubscriptionResponseJSON{
- Jsonrpc: "2.0",
- ID: connID,
- Result: ok,
- }
-
- if err := wsConn.WriteJSON(res); err != nil {
- break
- }
- default:
- // otherwise, call the usual rpc server to respond
- if err := s.tcpGetAndSendResponse(wsConn, mb); 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.NewRequestWithContext(context.Background(), "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)
-}
-
-// pubSubAPI is the eth_ prefixed set of APIs in the Web3 JSON-RPC spec
-type pubSubAPI struct {
- events *rpcfilters.EventSystem
- logger log.Logger
- clientCtx client.Context
-}
-
-// newPubSubAPI creates an instance of the ethereum PubSub API.
-func newPubSubAPI(clientCtx client.Context, logger log.Logger, tmWSClient *rpcclient.WSClient) *pubSubAPI {
- logger = logger.With("module", "websocket-client")
- return &pubSubAPI{
- events: rpcfilters.NewEventSystem(logger, tmWSClient),
- logger: logger,
- clientCtx: clientCtx,
- }
-}
-
-func (api *pubSubAPI) subscribe(wsConn *wsConn, subID rpc.ID, params []interface{}) (pubsub.UnsubscribeFunc, error) {
- method, ok := params[0].(string)
- if !ok {
- return nil, errors.New("invalid parameters")
- }
-
- switch method {
- case "newHeads":
- // TODO: handle extra params
- return api.subscribeNewHeads(wsConn, subID)
- case "logs":
- if len(params) > 1 {
- return api.subscribeLogs(wsConn, subID, params[1])
- }
- return api.subscribeLogs(wsConn, subID, nil)
- case "newPendingTransactions":
- return api.subscribePendingTransactions(wsConn, subID)
- case "syncing":
- return api.subscribeSyncing(wsConn, subID)
- default:
- return nil, errors.Errorf("unsupported method %s", method)
- }
-}
-
-func (api *pubSubAPI) subscribeNewHeads(wsConn *wsConn, subID rpc.ID) (pubsub.UnsubscribeFunc, error) {
- sub, unsubFn, err := api.events.SubscribeNewHeads()
- if err != nil {
- return nil, errors.Wrap(err, "error creating block filter")
- }
-
- // TODO: use events
- baseFee := big.NewInt(params.InitialBaseFee)
-
- go func() {
- headersCh := sub.Event()
- errCh := sub.Err()
- for {
- select {
- case event, ok := <-headersCh:
- if !ok {
- return
- }
-
- data, ok := event.Data.(tmtypes.EventDataNewBlockHeader)
- if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", event.Data))
- continue
- }
-
- header := types.EthHeaderFromTendermint(data.Header, ethtypes.Bloom{}, baseFee)
-
- // write to ws conn
- res := &SubscriptionNotification{
- Jsonrpc: "2.0",
- Method: "eth_subscription",
- Params: &SubscriptionResult{
- Subscription: subID,
- Result: header,
- },
- }
-
- err = wsConn.WriteJSON(res)
- if err != nil {
- api.logger.Error("error writing header, will drop peer", "error", err.Error())
-
- try(func() {
- if err != websocket.ErrCloseSent {
- _ = wsConn.Close()
- }
- }, api.logger, "closing websocket peer sub")
- }
- case err, ok := <-errCh:
- if !ok {
- return
- }
- api.logger.Debug("dropping NewHeads WebSocket subscription", "subscription-id", subID, "error", err.Error())
- }
- }
- }()
-
- return unsubFn, nil
-}
-
-func try(fn func(), l log.Logger, desc string) {
- defer func() {
- if x := recover(); x != nil {
- if err, ok := x.(error); ok {
- // debug.PrintStack()
- l.Debug("panic during "+desc, "error", err.Error())
- return
- }
-
- l.Debug(fmt.Sprintf("panic during %s: %+v", desc, x))
- return
- }
- }()
-
- fn()
-}
-
-func (api *pubSubAPI) subscribeLogs(wsConn *wsConn, subID rpc.ID, extra interface{}) (pubsub.UnsubscribeFunc, error) {
- crit := filters.FilterCriteria{}
-
- if extra != nil {
- params, ok := extra.(map[string]interface{})
- if !ok {
- err := errors.New("invalid criteria")
- api.logger.Debug("invalid criteria", "type", fmt.Sprintf("%T", extra))
- return nil, err
- }
-
- if params["address"] != nil {
- address, isString := params["address"].(string)
- addresses, isSlice := params["address"].([]interface{})
- if !isString && !isSlice {
- err := errors.New("invalid addresses; must be address or array of addresses")
- api.logger.Debug("invalid addresses", "type", fmt.Sprintf("%T", params["address"]))
- return nil, err
- }
-
- if ok {
- crit.Addresses = []common.Address{common.HexToAddress(address)}
- }
-
- if isSlice {
- crit.Addresses = []common.Address{}
- for _, addr := range addresses {
- address, ok := addr.(string)
- if !ok {
- err := errors.New("invalid address")
- api.logger.Debug("invalid address", "type", fmt.Sprintf("%T", addr))
- return nil, err
- }
-
- crit.Addresses = append(crit.Addresses, common.HexToAddress(address))
- }
- }
- }
-
- if params["topics"] != nil {
- topics, ok := params["topics"].([]interface{})
- if !ok {
- err := errors.Errorf("invalid topics: %s", topics)
- api.logger.Error("invalid topics", "type", fmt.Sprintf("%T", topics))
- return nil, 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)
- api.logger.Error("invalid topic", "type", fmt.Sprintf("%T", topic))
- 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 nil, err
- }
-
- continue
- }
-
- // in case we actually have a list of subtopics
- subtopicsList, ok := subtopics.([]interface{})
- if !ok {
- err := errors.New("invalid subtopics")
- api.logger.Error("invalid subtopic", "type", fmt.Sprintf("%T", subtopics))
- return nil, 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)
- api.logger.Error("invalid subtopic", "type", fmt.Sprintf("%T", subtopic))
- return nil, err
- }
-
- subtopicsCollect[idx] = common.HexToHash(tstr)
- }
-
- crit.Topics[topicIdx] = subtopicsCollect
- }
- }
- }
-
- sub, unsubFn, err := api.events.SubscribeLogs(crit)
- if err != nil {
- api.logger.Error("failed to subscribe logs", "error", err.Error())
- return nil, err
- }
-
- go func() {
- ch := sub.Event()
- errCh := sub.Err()
- for {
- select {
- case event, ok := <-ch:
- if !ok {
- return
- }
-
- dataTx, ok := event.Data.(tmtypes.EventDataTx)
- if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", event.Data))
- continue
- }
-
- txResponse, err := evmtypes.DecodeTxResponse(dataTx.TxResult.Result.Data)
- if err != nil {
- api.logger.Error("failed to decode tx response", "error", err.Error())
- return
- }
-
- logs := rpcfilters.FilterLogs(evmtypes.LogsToEthereum(txResponse.Logs), crit.FromBlock, crit.ToBlock, crit.Addresses, crit.Topics)
- if len(logs) == 0 {
- continue
- }
-
- for _, ethLog := range logs {
- res := &SubscriptionNotification{
- Jsonrpc: "2.0",
- Method: "eth_subscription",
- Params: &SubscriptionResult{
- Subscription: subID,
- Result: ethLog,
- },
- }
-
- err = wsConn.WriteJSON(res)
- if err != nil {
- try(func() {
- if err != websocket.ErrCloseSent {
- _ = wsConn.Close()
- }
- }, api.logger, "closing websocket peer sub")
- }
- }
- case err, ok := <-errCh:
- if !ok {
- return
- }
- api.logger.Debug("dropping Logs WebSocket subscription", "subscription-id", subID, "error", err.Error())
- }
- }
- }()
-
- return unsubFn, nil
-}
-
-func (api *pubSubAPI) subscribePendingTransactions(wsConn *wsConn, subID rpc.ID) (pubsub.UnsubscribeFunc, error) {
- sub, unsubFn, err := api.events.SubscribePendingTxs()
- if err != nil {
- return nil, errors.Wrap(err, "error creating block filter: %s")
- }
-
- go func() {
- txsCh := sub.Event()
- errCh := sub.Err()
- for {
- select {
- case ev := <-txsCh:
- data, ok := ev.Data.(tmtypes.EventDataTx)
- if !ok {
- api.logger.Debug("event data type mismatch", "type", fmt.Sprintf("%T", ev.Data))
- continue
- }
-
- ethTxs, err := types.RawTxToEthTx(api.clientCtx, data.Tx)
- if err != nil {
- // not ethereum tx
- continue
- }
-
- for _, ethTx := range ethTxs {
- // write to ws conn
- res := &SubscriptionNotification{
- Jsonrpc: "2.0",
- Method: "eth_subscription",
- Params: &SubscriptionResult{
- Subscription: subID,
- Result: ethTx.Hash,
- },
- }
-
- err = wsConn.WriteJSON(res)
- if err != nil {
- api.logger.Debug("error writing header, will drop peer", "error", err.Error())
-
- try(func() {
- if err != websocket.ErrCloseSent {
- _ = wsConn.Close()
- }
- }, api.logger, "closing websocket peer sub")
- }
- }
- case err, ok := <-errCh:
- if !ok {
- return
- }
- api.logger.Debug("dropping PendingTransactions WebSocket subscription", subID, "error", err.Error())
- }
- }
- }()
-
- return unsubFn, nil
-}
-
-func (api *pubSubAPI) subscribeSyncing(wsConn *wsConn, subID rpc.ID) (pubsub.UnsubscribeFunc, error) {
- return nil, errors.New("syncing subscription is not implemented")
-}
diff --git a/server/json_rpc.go b/server/json_rpc.go
index 6c420300..2a67bad8 100644
--- a/server/json_rpc.go
+++ b/server/json_rpc.go
@@ -1,26 +1,20 @@
package server
import (
- "net/http"
- "time"
-
- "github.com/gorilla/mux"
- "github.com/rs/cors"
+ "github.com/tendermint/tendermint/node"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
- "github.com/cosmos/cosmos-sdk/server/types"
ethlog "github.com/ethereum/go-ethereum/log"
- ethrpc "github.com/ethereum/go-ethereum/rpc"
+ storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/stratosnet/stratos-chain/rpc"
"github.com/stratosnet/stratos-chain/server/config"
+ evmkeeper "github.com/stratosnet/stratos-chain/x/evm/keeper"
)
// StartJSONRPC starts the JSON-RPC server
-func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEndpoint string, config config.Config) (*http.Server, chan struct{}, error) {
- tmWsClient := ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger)
-
+func StartJSONRPC(ctx *server.Context, tmNode *node.Node, evmKeeper *evmkeeper.Keeper, ms storetypes.MultiStore, clientCtx client.Context, config config.Config) error {
logger := ctx.Logger.With("module", "geth")
ethlog.Root().SetHandler(ethlog.FuncHandler(func(r *ethlog.Record) error {
switch r.Lvl {
@@ -34,65 +28,15 @@ func StartJSONRPC(ctx *server.Context, clientCtx client.Context, tmRPCAddr, tmEn
return nil
}))
- rpcServer := ethrpc.NewServer()
-
- rpcAPIArr := config.JSONRPC.API
- apis := rpc.GetRPCAPIs(ctx, clientCtx, tmWsClient, rpcAPIArr)
-
- for _, api := range apis {
- if err := rpcServer.RegisterName(api.Namespace, api.Service); err != nil {
- ctx.Logger.Error(
- "failed to register service in JSON RPC namespace",
- "namespace", api.Namespace,
- "service", api.Service,
- )
- return nil, nil, err
- }
- }
-
- r := mux.NewRouter()
- r.HandleFunc("/", rpcServer.ServeHTTP).Methods("POST")
-
- handlerWithCors := cors.Default()
- if config.API.EnableUnsafeCORS {
- handlerWithCors = cors.AllowAll()
+ apis := rpc.GetRPCAPIs(ctx, tmNode, evmKeeper, ms, clientCtx, config.JSONRPC.API)
+ web3Srv := rpc.NewWeb3Server(config, logger)
+ err := web3Srv.StartHTTP(apis)
+ if err != nil {
+ return err
}
-
- httpSrv := &http.Server{
- Addr: config.JSONRPC.Address,
- Handler: handlerWithCors.Handler(r),
- ReadTimeout: config.JSONRPC.HTTPTimeout,
- WriteTimeout: config.JSONRPC.HTTPTimeout,
- IdleTimeout: config.JSONRPC.HTTPIdleTimeout,
- }
- httpSrvDone := make(chan struct{}, 1)
-
- errCh := make(chan error)
- go func() {
- ctx.Logger.Info("Starting JSON-RPC server", "address", config.JSONRPC.Address)
- if err := httpSrv.ListenAndServe(); err != nil {
- if err == http.ErrServerClosed {
- close(httpSrvDone)
- return
- }
-
- ctx.Logger.Error("failed to start JSON-RPC server", "error", err.Error())
- errCh <- err
- }
- }()
-
- select {
- case err := <-errCh:
- ctx.Logger.Error("failed to boot JSON-RPC server", "error", err.Error())
- return nil, nil, err
- case <-time.After(types.ServerStartTime): // assume JSON RPC server started successfully
+ err = web3Srv.StartWS(apis)
+ if err != nil {
+ return err
}
-
- ctx.Logger.Info("Starting JSON WebSocket server", "address", config.JSONRPC.WsAddress)
-
- // allocate separate WS connection to Tendermint
- tmWsClient = ConnectTmWS(tmRPCAddr, tmEndpoint, ctx.Logger)
- wsSrv := rpc.NewWebsocketsServer(clientCtx, ctx.Logger, tmWsClient, config)
- wsSrv.Start()
- return httpSrv, httpSrvDone, nil
+ return nil
}
diff --git a/server/start.go b/server/start.go
index a0d33efc..582a7fbb 100644
--- a/server/start.go
+++ b/server/start.go
@@ -1,8 +1,6 @@
package server
import (
- "context"
- "encoding/json"
"fmt"
"io"
"net/http"
@@ -38,6 +36,7 @@ import (
"github.com/cosmos/cosmos-sdk/server/types"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
+ stosapp "github.com/stratosnet/stratos-chain/app"
ethdebug "github.com/stratosnet/stratos-chain/rpc/namespaces/ethereum/debug"
"github.com/stratosnet/stratos-chain/server/config"
@@ -413,28 +412,10 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
}
}
- var (
- httpSrv *http.Server
- httpSrvDone chan struct{}
- )
-
if config.JSONRPC.Enable {
- genDoc, err := genDocProvider()
- if err != nil {
- return err
- }
- var genAppState map[string]interface{}
- json.Unmarshal([]byte(genDoc.AppState), &genAppState)
- genEvm := genAppState["evm"].(map[string]interface{})
- genEvmParams := genEvm["params"].(map[string]interface{})
- genEvmParamsChainCfg := genEvmParams["chain_config"].(map[string]interface{})
- genEvmParamsChainId := genEvmParamsChainCfg["chain_id"].(string)
-
- clientCtx := clientCtx.WithChainID(genEvmParamsChainId)
-
- tmEndpoint := "/websocket"
- tmRPCAddr := cfg.RPC.ListenAddress
- httpSrv, httpSrvDone, err = StartJSONRPC(ctx, clientCtx, tmRPCAddr, tmEndpoint, config)
+ evmApp := app.(stosapp.EVMLKeeperApp)
+
+ err = StartJSONRPC(ctx, tmNode, evmApp.GetEVMKeeper(), app.CommitMultiStore(), clientCtx, config)
if err != nil {
return err
}
@@ -462,21 +443,6 @@ func startInProcess(ctx *server.Context, clientCtx client.Context, appCreator ty
}
}
- if httpSrv != nil {
- shutdownCtx, cancelFn := context.WithTimeout(context.Background(), 10*time.Second)
- defer cancelFn()
-
- if err := httpSrv.Shutdown(shutdownCtx); err != nil {
- logger.Error("HTTP server shutdown produced a warning", "error", err.Error())
- } else {
- logger.Info("HTTP server shut down, waiting 5 sec")
- select {
- case <-time.Tick(5 * time.Second):
- case <-httpSrvDone:
- }
- }
- }
-
logger.Info("Bye!")
}()
diff --git a/x/evm/client/rest/rest.go b/x/evm/client/rest/rest.go
index c6927aa5..b4953b55 100644
--- a/x/evm/client/rest/rest.go
+++ b/x/evm/client/rest/rest.go
@@ -5,7 +5,6 @@ import (
"encoding/hex"
"encoding/json"
"errors"
- "math/big"
"net/http"
"strings"
@@ -19,7 +18,6 @@ import (
"github.com/ethereum/go-ethereum/common"
rpctypes "github.com/stratosnet/stratos-chain/rpc/types"
- "github.com/stratosnet/stratos-chain/x/evm/types"
)
// RegisterTxRoutes - Central function to define routes that get registered by the main application
@@ -82,17 +80,6 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte,
return nil, err
}
- client := types.NewQueryClient(clientCtx)
- res, err := client.BaseFee(context.Background(), &types.QueryBaseFeeRequest{})
- if err != nil {
- return nil, err
- }
-
- var baseFee *big.Int
- if res.BaseFee != nil {
- baseFee = res.BaseFee.BigInt()
- }
-
blockHash := common.BytesToHash(block.Block.Header.Hash())
ethTxs, err := rpctypes.RawTxToEthTx(clientCtx, tx.Tx)
@@ -101,10 +88,11 @@ func getEthTransactionByHash(clientCtx client.Context, hashHex string) ([]byte,
}
height := uint64(tx.Height)
+ txIndex := uint64(tx.Index)
for _, ethTx := range ethTxs {
if common.HexToHash(ethTx.Hash) == common.BytesToHash(hash) {
- rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), blockHash, height, uint64(tx.Index), baseFee)
+ rpcTx, err := rpctypes.NewRPCTransaction(ethTx.AsTransaction(), &blockHash, &height, &txIndex)
if err != nil {
return nil, err
}
diff --git a/x/evm/keeper/state_transition.go b/x/evm/keeper/state_transition.go
index 9103d21a..92c097b8 100644
--- a/x/evm/keeper/state_transition.go
+++ b/x/evm/keeper/state_transition.go
@@ -3,6 +3,7 @@ package keeper
import (
"math"
"math/big"
+ "sync"
tmtypes "github.com/tendermint/tendermint/types"
@@ -107,6 +108,7 @@ func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, cfg *types.EVMConfig
var debug bool
if _, ok := tracer.(types.NoOpTracer); !ok {
debug = true
+ noBaseFee = true
}
return vm.Config{
@@ -122,6 +124,9 @@ func (k Keeper) VMConfig(ctx sdk.Context, msg core.Message, cfg *types.EVMConfig
// 2. The requested height is from an previous height from the same chain epoch
// 3. The requested height is from a height greater than the latest one
func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
+ cache := make(map[int64]common.Hash)
+ var rw sync.Mutex
+
return func(height uint64) common.Hash {
h, err := stratos.SafeInt64(height)
if err != nil {
@@ -135,24 +140,20 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
// hash directly from the context.
// Note: The headerHash is only set at begin block, it will be nil in case of a query context
headerHash := ctx.HeaderHash()
- if len(headerHash) != 0 {
- return common.BytesToHash(headerHash)
- }
-
- // only recompute the hash if not set (eg: checkTxState)
- contextBlockHeader := ctx.BlockHeader()
- header, err := tmtypes.HeaderFromProto(&contextBlockHeader)
- if err != nil {
- k.Logger(ctx).Error("failed to cast tendermint header from proto", "error", err)
- return common.Hash{}
- }
-
- headerHash = header.Hash()
return common.BytesToHash(headerHash)
case ctx.BlockHeight() > h:
// Case 2: if the chain is not the current height we need to retrieve the hash from the store for the
// current chain epoch. This only applies if the current height is greater than the requested height.
+
+ // NOTE: In case of concurrency
+ rw.Lock()
+ defer rw.Unlock()
+
+ if hash, ok := cache[h]; ok {
+ return hash
+ }
+
histInfo, found := k.stakingKeeper.GetHistoricalInfo(ctx, h)
if !found {
k.Logger(ctx).Debug("historical info not found", "height", h)
@@ -165,7 +166,9 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
return common.Hash{}
}
- return common.BytesToHash(header.Hash())
+ hash := common.BytesToHash(header.Hash())
+ cache[h] = hash
+ return hash
default:
// Case 3: heights greater than the current one returns an empty hash.
return common.Hash{}
@@ -374,14 +377,29 @@ func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, trace
stateDB.PrepareAccessList(msg.From(), msg.To(), vm.ActivePrecompiles(rules), msg.AccessList())
}
+ // NOTE: In order to achive this, nonce should be checked in ante handler and increased, othervise
+ // it could make pottential nonce overide with double spend or contract rewrite
+ // take over the nonce management from evm:
+ // reset sender's nonce to msg.Nonce() before calling evm on msg nonce
+ // as nonce already increased in db
+ // TODO: UNCOMMENT THIS FOR PROD!!!!!!
+ // stateDB.SetNonce(sender.Address(), msg.Nonce())
+
if contractCreation {
- // take over the nonce management from evm:
- // - reset sender's nonce to msg.Nonce() before calling evm.
- // - increase sender's nonce by one no matter the result.
+ // no need to increase nonce here as contract as during contract creation:
+ // - tx.origin nonce increase automatically
+ // - if IsEIP158 enabled, contract nonce will be set as 1
+
+ // NOTE: REMOVE THIS FOR PROD!!!!!!!!!!!
stateDB.SetNonce(sender.Address(), msg.Nonce())
ret, _, leftoverGas, vmErr = evm.Create(sender, msg.Data(), leftoverGas, msg.Value())
+
+ // NOTE: REMOVE THIS FOR PROD!!!!!!!!!!!
stateDB.SetNonce(sender.Address(), msg.Nonce()+1)
} else {
+ // should be incresed before call on nonce from msg so we make sure nonce remaining same as on init tx
+ // TODO: UNCOMMENT THIS FOR PROD!!!!!!
+ // stateDB.SetNonce(sender.Address(), msg.Nonce()+1)
ret, leftoverGas, vmErr = evm.Call(sender, *msg.To(), msg.Data(), leftoverGas, msg.Value())
}
diff --git a/x/evm/tracers/js/bigint.go b/x/evm/tracers/js/bigint.go
new file mode 100644
index 00000000..9aeb3304
--- /dev/null
+++ b/x/evm/tracers/js/bigint.go
@@ -0,0 +1,20 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package js
+
+// bigIntegerJS is the minified version of https://github.com/peterolson/BigInteger.js.
+const bigIntegerJS = `var bigInt=function(undefined){"use strict";var BASE=1e7,LOG_BASE=7,MAX_INT=9007199254740992,MAX_INT_ARR=smallToArray(MAX_INT),LOG_MAX_INT=Math.log(MAX_INT);function Integer(v,radix){if(typeof v==="undefined")return Integer[0];if(typeof radix!=="undefined")return+radix===10?parseValue(v):parseBase(v,radix);return parseValue(v)}function BigInteger(value,sign){this.value=value;this.sign=sign;this.isSmall=false}BigInteger.prototype=Object.create(Integer.prototype);function SmallInteger(value){this.value=value;this.sign=value<0;this.isSmall=true}SmallInteger.prototype=Object.create(Integer.prototype);function isPrecise(n){return-MAX_INT0)return Math.floor(n);return Math.ceil(n)}function add(a,b){var l_a=a.length,l_b=b.length,r=new Array(l_a),carry=0,base=BASE,sum,i;for(i=0;i=base?1:0;r[i]=sum-carry*base}while(i0)r.push(carry);return r}function addAny(a,b){if(a.length>=b.length)return add(a,b);return add(b,a)}function addSmall(a,carry){var l=a.length,r=new Array(l),base=BASE,sum,i;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}BigInteger.prototype.add=function(v){var n=parseValue(v);if(this.sign!==n.sign){return this.subtract(n.negate())}var a=this.value,b=n.value;if(n.isSmall){return new BigInteger(addSmall(a,Math.abs(b)),this.sign)}return new BigInteger(addAny(a,b),this.sign)};BigInteger.prototype.plus=BigInteger.prototype.add;SmallInteger.prototype.add=function(v){var n=parseValue(v);var a=this.value;if(a<0!==n.sign){return this.subtract(n.negate())}var b=n.value;if(n.isSmall){if(isPrecise(a+b))return new SmallInteger(a+b);b=smallToArray(Math.abs(b))}return new BigInteger(addSmall(b,Math.abs(a)),a<0)};SmallInteger.prototype.plus=SmallInteger.prototype.add;function subtract(a,b){var a_l=a.length,b_l=b.length,r=new Array(a_l),borrow=0,base=BASE,i,difference;for(i=0;i=0){value=subtract(a,b)}else{value=subtract(b,a);sign=!sign}value=arrayToSmall(value);if(typeof value==="number"){if(sign)value=-value;return new SmallInteger(value)}return new BigInteger(value,sign)}function subtractSmall(a,b,sign){var l=a.length,r=new Array(l),carry=-b,base=BASE,i,difference;for(i=0;i=0)};SmallInteger.prototype.minus=SmallInteger.prototype.subtract;BigInteger.prototype.negate=function(){return new BigInteger(this.value,!this.sign)};SmallInteger.prototype.negate=function(){var sign=this.sign;var small=new SmallInteger(-this.value);small.sign=!sign;return small};BigInteger.prototype.abs=function(){return new BigInteger(this.value,false)};SmallInteger.prototype.abs=function(){return new SmallInteger(Math.abs(this.value))};function multiplyLong(a,b){var a_l=a.length,b_l=b.length,l=a_l+b_l,r=createArray(l),base=BASE,product,carry,i,a_i,b_j;for(i=0;i0){r[i++]=carry%base;carry=Math.floor(carry/base)}return r}function shiftLeft(x,n){var r=[];while(n-- >0)r.push(0);return r.concat(x)}function multiplyKaratsuba(x,y){var n=Math.max(x.length,y.length);if(n<=30)return multiplyLong(x,y);n=Math.ceil(n/2);var b=x.slice(n),a=x.slice(0,n),d=y.slice(n),c=y.slice(0,n);var ac=multiplyKaratsuba(a,c),bd=multiplyKaratsuba(b,d),abcd=multiplyKaratsuba(addAny(a,b),addAny(c,d));var product=addAny(addAny(ac,shiftLeft(subtract(subtract(abcd,ac),bd),n)),shiftLeft(bd,2*n));trim(product);return product}function useKaratsuba(l1,l2){return-.012*l1-.012*l2+15e-6*l1*l2>0}BigInteger.prototype.multiply=function(v){var n=parseValue(v),a=this.value,b=n.value,sign=this.sign!==n.sign,abs;if(n.isSmall){if(b===0)return Integer[0];if(b===1)return this;if(b===-1)return this.negate();abs=Math.abs(b);if(abs=0;shift--){quotientDigit=base-1;if(remainder[shift+b_l]!==divisorMostSignificantDigit){quotientDigit=Math.floor((remainder[shift+b_l]*base+remainder[shift+b_l-1])/divisorMostSignificantDigit)}carry=0;borrow=0;l=divisor.length;for(i=0;ib_l){highx=(highx+1)*base}guess=Math.ceil(highx/highy);do{check=multiplySmall(b,guess);if(compareAbs(check,part)<=0)break;guess--}while(guess);result.push(guess);part=subtract(part,check)}result.reverse();return[arrayToSmall(result),arrayToSmall(part)]}function divModSmall(value,lambda){var length=value.length,quotient=createArray(length),base=BASE,i,q,remainder,divisor;remainder=0;for(i=length-1;i>=0;--i){divisor=remainder*base+value[i];q=truncate(divisor/lambda);remainder=divisor-q*lambda;quotient[i]=q|0}return[quotient,remainder|0]}function divModAny(self,v){var value,n=parseValue(v);var a=self.value,b=n.value;var quotient;if(b===0)throw new Error("Cannot divide by zero");if(self.isSmall){if(n.isSmall){return[new SmallInteger(truncate(a/b)),new SmallInteger(a%b)]}return[Integer[0],self]}if(n.isSmall){if(b===1)return[self,Integer[0]];if(b==-1)return[self.negate(),Integer[0]];var abs=Math.abs(b);if(absb.length?1:-1}for(var i=a.length-1;i>=0;i--){if(a[i]!==b[i])return a[i]>b[i]?1:-1}return 0}BigInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall)return 1;return compareAbs(a,b)};SmallInteger.prototype.compareAbs=function(v){var n=parseValue(v),a=Math.abs(this.value),b=n.value;if(n.isSmall){b=Math.abs(b);return a===b?0:a>b?1:-1}return-1};BigInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(this.sign!==n.sign){return n.sign?1:-1}if(n.isSmall){return this.sign?-1:1}return compareAbs(a,b)*(this.sign?-1:1)};BigInteger.prototype.compareTo=BigInteger.prototype.compare;SmallInteger.prototype.compare=function(v){if(v===Infinity){return-1}if(v===-Infinity){return 1}var n=parseValue(v),a=this.value,b=n.value;if(n.isSmall){return a==b?0:a>b?1:-1}if(a<0!==n.sign){return a<0?-1:1}return a<0?1:-1};SmallInteger.prototype.compareTo=SmallInteger.prototype.compare;BigInteger.prototype.equals=function(v){return this.compare(v)===0};SmallInteger.prototype.eq=SmallInteger.prototype.equals=BigInteger.prototype.eq=BigInteger.prototype.equals;BigInteger.prototype.notEquals=function(v){return this.compare(v)!==0};SmallInteger.prototype.neq=SmallInteger.prototype.notEquals=BigInteger.prototype.neq=BigInteger.prototype.notEquals;BigInteger.prototype.greater=function(v){return this.compare(v)>0};SmallInteger.prototype.gt=SmallInteger.prototype.greater=BigInteger.prototype.gt=BigInteger.prototype.greater;BigInteger.prototype.lesser=function(v){return this.compare(v)<0};SmallInteger.prototype.lt=SmallInteger.prototype.lesser=BigInteger.prototype.lt=BigInteger.prototype.lesser;BigInteger.prototype.greaterOrEquals=function(v){return this.compare(v)>=0};SmallInteger.prototype.geq=SmallInteger.prototype.greaterOrEquals=BigInteger.prototype.geq=BigInteger.prototype.greaterOrEquals;BigInteger.prototype.lesserOrEquals=function(v){return this.compare(v)<=0};SmallInteger.prototype.leq=SmallInteger.prototype.lesserOrEquals=BigInteger.prototype.leq=BigInteger.prototype.lesserOrEquals;BigInteger.prototype.isEven=function(){return(this.value[0]&1)===0};SmallInteger.prototype.isEven=function(){return(this.value&1)===0};BigInteger.prototype.isOdd=function(){return(this.value[0]&1)===1};SmallInteger.prototype.isOdd=function(){return(this.value&1)===1};BigInteger.prototype.isPositive=function(){return!this.sign};SmallInteger.prototype.isPositive=function(){return this.value>0};BigInteger.prototype.isNegative=function(){return this.sign};SmallInteger.prototype.isNegative=function(){return this.value<0};BigInteger.prototype.isUnit=function(){return false};SmallInteger.prototype.isUnit=function(){return Math.abs(this.value)===1};BigInteger.prototype.isZero=function(){return false};SmallInteger.prototype.isZero=function(){return this.value===0};BigInteger.prototype.isDivisibleBy=function(v){var n=parseValue(v);var value=n.value;if(value===0)return false;if(value===1)return true;if(value===2)return this.isEven();return this.mod(n).equals(Integer[0])};SmallInteger.prototype.isDivisibleBy=BigInteger.prototype.isDivisibleBy;function isBasicPrime(v){var n=v.abs();if(n.isUnit())return false;if(n.equals(2)||n.equals(3)||n.equals(5))return true;if(n.isEven()||n.isDivisibleBy(3)||n.isDivisibleBy(5))return false;if(n.lesser(25))return true}BigInteger.prototype.isPrime=function(){var isPrime=isBasicPrime(this);if(isPrime!==undefined)return isPrime;var n=this.abs(),nPrev=n.prev();var a=[2,3,5,7,11,13,17,19],b=nPrev,d,t,i,x;while(b.isEven())b=b.divide(2);for(i=0;i-MAX_INT)return new SmallInteger(value-1);return new BigInteger(MAX_INT_ARR,true)};var powersOfTwo=[1];while(2*powersOfTwo[powersOfTwo.length-1]<=BASE)powersOfTwo.push(2*powersOfTwo[powersOfTwo.length-1]);var powers2Length=powersOfTwo.length,highestPower2=powersOfTwo[powers2Length-1];function shift_isSmall(n){return(typeof n==="number"||typeof n==="string")&&+Math.abs(n)<=BASE||n instanceof BigInteger&&n.value.length<=1}BigInteger.prototype.shiftLeft=function(n){if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftRight(-n);var result=this;while(n>=powers2Length){result=result.multiply(highestPower2);n-=powers2Length-1}return result.multiply(powersOfTwo[n])};SmallInteger.prototype.shiftLeft=BigInteger.prototype.shiftLeft;BigInteger.prototype.shiftRight=function(n){var remQuo;if(!shift_isSmall(n)){throw new Error(String(n)+" is too large for shifting.")}n=+n;if(n<0)return this.shiftLeft(-n);var result=this;while(n>=powers2Length){if(result.isZero())return result;remQuo=divModAny(result,highestPower2);result=remQuo[1].isNegative()?remQuo[0].prev():remQuo[0];n-=powers2Length-1}remQuo=divModAny(result,powersOfTwo[n]);return remQuo[1].isNegative()?remQuo[0].prev():remQuo[0]};SmallInteger.prototype.shiftRight=BigInteger.prototype.shiftRight;function bitwise(x,y,fn){y=parseValue(y);var xSign=x.isNegative(),ySign=y.isNegative();var xRem=xSign?x.not():x,yRem=ySign?y.not():y;var xDigit=0,yDigit=0;var xDivMod=null,yDivMod=null;var result=[];while(!xRem.isZero()||!yRem.isZero()){xDivMod=divModAny(xRem,highestPower2);xDigit=xDivMod[1].toJSNumber();if(xSign){xDigit=highestPower2-1-xDigit}yDivMod=divModAny(yRem,highestPower2);yDigit=yDivMod[1].toJSNumber();if(ySign){yDigit=highestPower2-1-yDigit}xRem=xDivMod[0];yRem=yDivMod[0];result.push(fn(xDigit,yDigit))}var sum=fn(xSign?1:0,ySign?1:0)!==0?bigInt(-1):bigInt(0);for(var i=result.length-1;i>=0;i-=1){sum=sum.multiply(highestPower2).add(bigInt(result[i]))}return sum}BigInteger.prototype.not=function(){return this.negate().prev()};SmallInteger.prototype.not=BigInteger.prototype.not;BigInteger.prototype.and=function(n){return bitwise(this,n,function(a,b){return a&b})};SmallInteger.prototype.and=BigInteger.prototype.and;BigInteger.prototype.or=function(n){return bitwise(this,n,function(a,b){return a|b})};SmallInteger.prototype.or=BigInteger.prototype.or;BigInteger.prototype.xor=function(n){return bitwise(this,n,function(a,b){return a^b})};SmallInteger.prototype.xor=BigInteger.prototype.xor;var LOBMASK_I=1<<30,LOBMASK_BI=(BASE&-BASE)*(BASE&-BASE)|LOBMASK_I;function roughLOB(n){var v=n.value,x=typeof v==="number"?v|LOBMASK_I:v[0]+v[1]*BASE|LOBMASK_BI;return x&-x}function max(a,b){a=parseValue(a);b=parseValue(b);return a.greater(b)?a:b}function min(a,b){a=parseValue(a);b=parseValue(b);return a.lesser(b)?a:b}function gcd(a,b){a=parseValue(a).abs();b=parseValue(b).abs();if(a.equals(b))return a;if(a.isZero())return b;if(b.isZero())return a;var c=Integer[1],d,t;while(a.isEven()&&b.isEven()){d=Math.min(roughLOB(a),roughLOB(b));a=a.divide(d);b=b.divide(d);c=c.multiply(d)}while(a.isEven()){a=a.divide(roughLOB(a))}do{while(b.isEven()){b=b.divide(roughLOB(b))}if(a.greater(b)){t=b;b=a;a=t}b=b.subtract(a)}while(!b.isZero());return c.isUnit()?a:a.multiply(c)}function lcm(a,b){a=parseValue(a).abs();b=parseValue(b).abs();return a.divide(gcd(a,b)).multiply(b)}function randBetween(a,b){a=parseValue(a);b=parseValue(b);var low=min(a,b),high=max(a,b);var range=high.subtract(low).add(1);if(range.isSmall)return low.add(Math.floor(Math.random()*range));var length=range.value.length-1;var result=[],restricted=true;for(var i=length;i>=0;i--){var top=restricted?range.value[i]:BASE;var digit=truncate(Math.random()*top);result.unshift(digit);if(digit=absBase){if(c==="1"&&absBase===1)continue;throw new Error(c+" is not a valid digit in base "+base+".")}else if(c.charCodeAt(0)-87>=absBase){throw new Error(c+" is not a valid digit in base "+base+".")}}}if(2<=base&&base<=36){if(length<=LOG_MAX_INT/Math.log(base)){var result=parseInt(text,base);if(isNaN(result)){throw new Error(c+" is not a valid digit in base "+base+".")}return new SmallInteger(parseInt(text,base))}}base=parseValue(base);var digits=[];var isNegative=text[0]==="-";for(i=isNegative?1:0;i");digits.push(parseValue(text.slice(start+1,i)))}else throw new Error(c+" is not a valid character")}return parseBaseFromArray(digits,base,isNegative)};function parseBaseFromArray(digits,base,isNegative){var val=Integer[0],pow=Integer[1],i;for(i=digits.length-1;i>=0;i--){val=val.add(digits[i].times(pow));pow=pow.times(base)}return isNegative?val.negate():val}function stringify(digit){var v=digit.value;if(typeof v==="number")v=[v];if(v.length===1&&v[0]<=35){return"0123456789abcdefghijklmnopqrstuvwxyz".charAt(v[0])}return"<"+v+">"}function toBase(n,base){base=bigInt(base);if(base.isZero()){if(n.isZero())return"0";throw new Error("Cannot convert nonzero numbers to base 0.")}if(base.equals(-1)){if(n.isZero())return"0";if(n.isNegative())return new Array(1-n).join("10");return"1"+new Array(+n).join("01")}var minusSign="";if(n.isNegative()&&base.isPositive()){minusSign="-";n=n.abs()}if(base.equals(1)){if(n.isZero())return"0";return minusSign+new Array(+n+1).join(1)}var out=[];var left=n,divmod;while(left.isNegative()||left.compareAbs(base)>=0){divmod=left.divmod(base);left=divmod.quotient;var digit=divmod.remainder;if(digit.isNegative()){digit=base.minus(digit).abs();left=left.next()}out.push(stringify(digit))}out.push(stringify(left));return minusSign+out.reverse().join("")}BigInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!==10)return toBase(this,radix);var v=this.value,l=v.length,str=String(v[--l]),zeros="0000000",digit;while(--l>=0){digit=String(v[l]);str+=zeros.slice(digit.length)+digit}var sign=this.sign?"-":"";return sign+str};SmallInteger.prototype.toString=function(radix){if(radix===undefined)radix=10;if(radix!=10)return toBase(this,radix);return String(this.value)};BigInteger.prototype.toJSON=SmallInteger.prototype.toJSON=function(){return this.toString()};BigInteger.prototype.valueOf=function(){return+this.toString()};BigInteger.prototype.toJSNumber=BigInteger.prototype.valueOf;SmallInteger.prototype.valueOf=function(){return this.value};SmallInteger.prototype.toJSNumber=SmallInteger.prototype.valueOf;function parseStringValue(v){if(isPrecise(+v)){var x=+v;if(x===truncate(x))return new SmallInteger(x);throw"Invalid integer: "+v}var sign=v[0]==="-";if(sign)v=v.slice(1);var split=v.split(/e/i);if(split.length>2)throw new Error("Invalid integer: "+split.join("e"));if(split.length===2){var exp=split[1];if(exp[0]==="+")exp=exp.slice(1);exp=+exp;if(exp!==truncate(exp)||!isPrecise(exp))throw new Error("Invalid integer: "+exp+" is not a valid exponent.");var text=split[0];var decimalPlace=text.indexOf(".");if(decimalPlace>=0){exp-=text.length-decimalPlace-1;text=text.slice(0,decimalPlace)+text.slice(decimalPlace+1)}if(exp<0)throw new Error("Cannot include negative exponent part for integers");text+=new Array(exp+1).join("0");v=text}var isValid=/^([0-9][0-9]*)$/.test(v);if(!isValid)throw new Error("Invalid integer: "+v);var r=[],max=v.length,l=LOG_BASE,min=max-l;while(max>0){r.push(+v.slice(min,max));min-=l;if(min<0)min=0;max-=l}trim(r);return new BigInteger(r,sign)}function parseNumberValue(v){if(isPrecise(v)){if(v!==truncate(v))throw new Error(v+" is not an integer.");return new SmallInteger(v)}return parseStringValue(v.toString())}function parseValue(v){if(typeof v==="number"){return parseNumberValue(v)}if(typeof v==="string"){return parseStringValue(v)}return v}for(var i=0;i<1e3;i++){Integer[i]=new SmallInteger(i);if(i>0)Integer[-i]=new SmallInteger(-i)}Integer.one=Integer[1];Integer.zero=Integer[0];Integer.minusOne=Integer[-1];Integer.max=max;Integer.min=min;Integer.gcd=gcd;Integer.lcm=lcm;Integer.isInstance=function(x){return x instanceof BigInteger||x instanceof SmallInteger};Integer.randBetween=randBetween;Integer.fromArray=function(digits,base,isNegative){return parseBaseFromArray(digits.map(parseValue),parseValue(base||10),isNegative)};return Integer}();if(typeof module!=="undefined"&&module.hasOwnProperty("exports")){module.exports=bigInt}if(typeof define==="function"&&define.amd){define("big-integer",[],function(){return bigInt})}; bigInt`
diff --git a/x/evm/tracers/js/goja.go b/x/evm/tracers/js/goja.go
new file mode 100644
index 00000000..adbcfcaf
--- /dev/null
+++ b/x/evm/tracers/js/goja.go
@@ -0,0 +1,960 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package js
+
+import (
+ "encoding/json"
+ "errors"
+ "fmt"
+ "math/big"
+ "time"
+
+ "github.com/dop251/goja"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+ jsassets "github.com/stratosnet/stratos-chain/x/evm/tracers/js/internal/tracers"
+)
+
+var assetTracers = make(map[string]string)
+
+// InitTracer retrieves the JavaScript transaction tracers included in go-ethereum.
+func InitTracer() {
+ var err error
+ assetTracers, err = jsassets.Load()
+ if err != nil {
+ panic(err)
+ }
+ tracers.RegisterLookup(true, newJsTracer)
+}
+
+// bigIntProgram is compiled once and the exported function mostly invoked to convert
+// hex strings into big ints.
+var bigIntProgram = goja.MustCompile("bigInt", bigIntegerJS, false)
+
+type toBigFn = func(vm *goja.Runtime, val string) (goja.Value, error)
+type toBufFn = func(vm *goja.Runtime, val []byte) (goja.Value, error)
+type fromBufFn = func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error)
+
+func toBuf(vm *goja.Runtime, bufType goja.Value, val []byte) (goja.Value, error) {
+ // bufType is usually Uint8Array. This is equivalent to `new Uint8Array(val)` in JS.
+ return vm.New(bufType, vm.ToValue(vm.NewArrayBuffer(val)))
+}
+
+func fromBuf(vm *goja.Runtime, bufType goja.Value, buf goja.Value, allowString bool) ([]byte, error) {
+ obj := buf.ToObject(vm)
+ switch obj.ClassName() {
+ case "String":
+ if !allowString {
+ break
+ }
+ return common.FromHex(obj.String()), nil
+
+ case "Array":
+ var b []byte
+ if err := vm.ExportTo(buf, &b); err != nil {
+ return nil, err
+ }
+ return b, nil
+
+ case "Object":
+ if !obj.Get("constructor").SameAs(bufType) {
+ break
+ }
+ b := obj.Get("buffer").Export().(goja.ArrayBuffer).Bytes()
+ return b, nil
+ }
+ return nil, fmt.Errorf("invalid buffer type")
+}
+
+// jsTracer is an implementation of the Tracer interface which evaluates
+// JS functions on the relevant EVM hooks. It uses Goja as its JS engine.
+type jsTracer struct {
+ vm *goja.Runtime
+ env *vm.EVM
+ toBig toBigFn // Converts a hex string into a JS bigint
+ toBuf toBufFn // Converts a []byte into a JS buffer
+ fromBuf fromBufFn // Converts an array, hex string or Uint8Array to a []byte
+ ctx map[string]goja.Value // KV-bag passed to JS in `result`
+ activePrecompiles []common.Address // List of active precompiles at current block
+ traceStep bool // True if tracer object exposes a `step()` method
+ traceFrame bool // True if tracer object exposes the `enter()` and `exit()` methods
+ gasLimit uint64 // Amount of gas bought for the whole tx
+ err error // Any error that should stop tracing
+ obj *goja.Object // Trace object
+
+ // Methods exposed by tracer
+ result goja.Callable
+ fault goja.Callable
+ step goja.Callable
+ enter goja.Callable
+ exit goja.Callable
+
+ // Underlying structs being passed into JS
+ log *steplog
+ frame *callframe
+ frameResult *callframeResult
+
+ // Goja-wrapping of types prepared for JS consumption
+ logValue goja.Value
+ dbValue goja.Value
+ frameValue goja.Value
+ frameResultValue goja.Value
+}
+
+// newJsTracer instantiates a new JS tracer instance. code is either
+// the name of a built-in JS tracer or a Javascript snippet which
+// evaluates to an expression returning an object with certain methods.
+// The methods `result` and `fault` are required to be present.
+// The methods `step`, `enter`, and `exit` are optional, but note that
+// `enter` and `exit` always go together.
+func newJsTracer(code string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ if c, ok := assetTracers[code]; ok {
+ code = c
+ }
+ vm := goja.New()
+ // By default field names are exported to JS as is, i.e. capitalized.
+ vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
+ t := &jsTracer{
+ vm: vm,
+ ctx: make(map[string]goja.Value),
+ }
+ if ctx == nil {
+ ctx = new(tracers.Context)
+ }
+ if ctx.BlockHash != (common.Hash{}) {
+ t.ctx["blockHash"] = vm.ToValue(ctx.BlockHash.Bytes())
+ if ctx.TxHash != (common.Hash{}) {
+ t.ctx["txIndex"] = vm.ToValue(ctx.TxIndex)
+ t.ctx["txHash"] = vm.ToValue(ctx.TxHash.Bytes())
+ }
+ }
+
+ t.setTypeConverters()
+ t.setBuiltinFunctions()
+ ret, err := vm.RunString("(" + code + ")")
+ if err != nil {
+ return nil, err
+ }
+ // Check tracer's interface for required and optional methods.
+ obj := ret.ToObject(vm)
+ result, ok := goja.AssertFunction(obj.Get("result"))
+ if !ok {
+ return nil, errors.New("trace object must expose a function result()")
+ }
+ fault, ok := goja.AssertFunction(obj.Get("fault"))
+ if !ok {
+ return nil, errors.New("trace object must expose a function fault()")
+ }
+ step, ok := goja.AssertFunction(obj.Get("step"))
+ t.traceStep = ok
+ enter, hasEnter := goja.AssertFunction(obj.Get("enter"))
+ exit, hasExit := goja.AssertFunction(obj.Get("exit"))
+ if hasEnter != hasExit {
+ return nil, errors.New("trace object must expose either both or none of enter() and exit()")
+ }
+ t.traceFrame = hasEnter
+ t.obj = obj
+ t.step = step
+ t.enter = enter
+ t.exit = exit
+ t.result = result
+ t.fault = fault
+
+ // Pass in config
+ if setup, ok := goja.AssertFunction(obj.Get("setup")); ok {
+ cfgStr := "{}"
+ if cfg != nil {
+ cfgStr = string(cfg)
+ }
+ if _, err := setup(obj, vm.ToValue(cfgStr)); err != nil {
+ return nil, err
+ }
+ }
+ // Setup objects carrying data to JS. These are created once and re-used.
+ t.log = &steplog{
+ vm: vm,
+ op: &opObj{vm: vm},
+ memory: &memoryObj{vm: vm, toBig: t.toBig, toBuf: t.toBuf},
+ stack: &stackObj{vm: vm, toBig: t.toBig},
+ contract: &contractObj{vm: vm, toBig: t.toBig, toBuf: t.toBuf},
+ }
+ t.frame = &callframe{vm: vm, toBig: t.toBig, toBuf: t.toBuf}
+ t.frameResult = &callframeResult{vm: vm, toBuf: t.toBuf}
+ t.frameValue = t.frame.setupObject()
+ t.frameResultValue = t.frameResult.setupObject()
+ t.logValue = t.log.setupObject()
+ return t, nil
+}
+
+// CaptureTxStart implements the Tracer interface and is invoked at the beginning of
+// transaction processing.
+func (t *jsTracer) CaptureTxStart(gasLimit uint64) {
+ t.gasLimit = gasLimit
+}
+
+// CaptureTxStart implements the Tracer interface and is invoked at the end of
+// transaction processing.
+func (t *jsTracer) CaptureTxEnd(restGas uint64) {}
+
+// CaptureStart implements the Tracer interface to initialize the tracing operation.
+func (t *jsTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ db := &dbObj{db: env.StateDB, vm: t.vm, toBig: t.toBig, toBuf: t.toBuf, fromBuf: t.fromBuf}
+ t.dbValue = db.setupObject()
+ if create {
+ t.ctx["type"] = t.vm.ToValue("CREATE")
+ } else {
+ t.ctx["type"] = t.vm.ToValue("CALL")
+ }
+ t.ctx["from"] = t.vm.ToValue(from.Bytes())
+ t.ctx["to"] = t.vm.ToValue(to.Bytes())
+ t.ctx["input"] = t.vm.ToValue(input)
+ t.ctx["gas"] = t.vm.ToValue(gas)
+ t.ctx["gasPrice"] = t.vm.ToValue(env.TxContext.GasPrice)
+ valueBig, err := t.toBig(t.vm, value.String())
+ if err != nil {
+ t.err = err
+ return
+ }
+ t.ctx["value"] = valueBig
+ t.ctx["block"] = t.vm.ToValue(env.Context.BlockNumber.Uint64())
+ // Update list of precompiles based on current block
+ rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
+ t.activePrecompiles = vm.ActivePrecompiles(rules)
+ t.ctx["intrinsicGas"] = t.vm.ToValue(t.gasLimit - gas)
+}
+
+// CaptureState implements the Tracer interface to trace a single step of VM execution.
+func (t *jsTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ if !t.traceStep {
+ return
+ }
+ if t.err != nil {
+ return
+ }
+
+ log := t.log
+ log.op.op = op
+ log.memory.memory = scope.Memory
+ log.stack.stack = scope.Stack
+ log.contract.contract = scope.Contract
+ log.pc = uint(pc)
+ log.gas = uint(gas)
+ log.cost = uint(cost)
+ log.depth = uint(depth)
+ log.err = err
+ if _, err := t.step(t.obj, t.logValue, t.dbValue); err != nil {
+ t.onError("step", err)
+ }
+}
+
+// CaptureFault implements the Tracer interface to trace an execution fault
+func (t *jsTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+ if t.err != nil {
+ return
+ }
+ // Other log fields have been already set as part of the last CaptureState.
+ t.log.err = err
+ if _, err := t.fault(t.obj, t.logValue, t.dbValue); err != nil {
+ t.onError("fault", err)
+ }
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *jsTracer) CaptureEnd(output []byte, gasUsed uint64, duration time.Duration, err error) {
+ t.ctx["output"] = t.vm.ToValue(output)
+ t.ctx["time"] = t.vm.ToValue(duration.String())
+ t.ctx["gasUsed"] = t.vm.ToValue(gasUsed)
+ if err != nil {
+ t.ctx["error"] = t.vm.ToValue(err.Error())
+ }
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *jsTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ if !t.traceFrame {
+ return
+ }
+ if t.err != nil {
+ return
+ }
+
+ t.frame.typ = typ.String()
+ t.frame.from = from
+ t.frame.to = to
+ t.frame.input = common.CopyBytes(input)
+ t.frame.gas = uint(gas)
+ t.frame.value = nil
+ if value != nil {
+ t.frame.value = new(big.Int).SetBytes(value.Bytes())
+ }
+
+ if _, err := t.enter(t.obj, t.frameValue); err != nil {
+ t.onError("enter", err)
+ }
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *jsTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+ if !t.traceFrame {
+ return
+ }
+
+ t.frameResult.gasUsed = uint(gasUsed)
+ t.frameResult.output = common.CopyBytes(output)
+ t.frameResult.err = err
+
+ if _, err := t.exit(t.obj, t.frameResultValue); err != nil {
+ t.onError("exit", err)
+ }
+}
+
+// GetResult calls the Javascript 'result' function and returns its value, or any accumulated error
+func (t *jsTracer) GetResult() (json.RawMessage, error) {
+ ctx := t.vm.ToValue(t.ctx)
+ res, err := t.result(t.obj, ctx, t.dbValue)
+ if err != nil {
+ return nil, wrapError("result", err)
+ }
+ encoded, err := json.Marshal(res)
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(encoded), t.err
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *jsTracer) Stop(err error) {
+ t.vm.Interrupt(err)
+}
+
+// onError is called anytime the running JS code is interrupted
+// and returns an error. It in turn pings the EVM to cancel its
+// execution.
+func (t *jsTracer) onError(context string, err error) {
+ t.err = wrapError(context, err)
+ // `env` is set on CaptureStart which comes before any JS execution.
+ // So it should be non-nil.
+ t.env.Cancel()
+}
+
+func wrapError(context string, err error) error {
+ return fmt.Errorf("%v in server-side tracer function '%v'", err, context)
+}
+
+// setBuiltinFunctions injects Go functions which are available to tracers into the environment.
+// It depends on type converters having been set up.
+func (t *jsTracer) setBuiltinFunctions() {
+ vm := t.vm
+ // TODO: load console from goja-nodejs
+ vm.Set("toHex", func(v goja.Value) string {
+ b, err := t.fromBuf(vm, v, false)
+ if err != nil {
+ vm.Interrupt(err)
+ return ""
+ }
+ return hexutil.Encode(b)
+ })
+ vm.Set("toWord", func(v goja.Value) goja.Value {
+ // TODO: add test with []byte len < 32 or > 32
+ b, err := t.fromBuf(vm, v, true)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ b = common.BytesToHash(b).Bytes()
+ res, err := t.toBuf(vm, b)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ return res
+ })
+ vm.Set("toAddress", func(v goja.Value) goja.Value {
+ a, err := t.fromBuf(vm, v, true)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ a = common.BytesToAddress(a).Bytes()
+ res, err := t.toBuf(vm, a)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ return res
+ })
+ vm.Set("toContract", func(from goja.Value, nonce uint) goja.Value {
+ a, err := t.fromBuf(vm, from, true)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ addr := common.BytesToAddress(a)
+ b := crypto.CreateAddress(addr, uint64(nonce)).Bytes()
+ res, err := t.toBuf(vm, b)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ return res
+ })
+ vm.Set("toContract2", func(from goja.Value, salt string, initcode goja.Value) goja.Value {
+ a, err := t.fromBuf(vm, from, true)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ addr := common.BytesToAddress(a)
+ code, err := t.fromBuf(vm, initcode, true)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ code = common.CopyBytes(code)
+ codeHash := crypto.Keccak256(code)
+ b := crypto.CreateAddress2(addr, common.HexToHash(salt), codeHash).Bytes()
+ res, err := t.toBuf(vm, b)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ return res
+ })
+ vm.Set("isPrecompiled", func(v goja.Value) bool {
+ a, err := t.fromBuf(vm, v, true)
+ if err != nil {
+ vm.Interrupt(err)
+ return false
+ }
+ addr := common.BytesToAddress(a)
+ for _, p := range t.activePrecompiles {
+ if p == addr {
+ return true
+ }
+ }
+ return false
+ })
+ vm.Set("slice", func(slice goja.Value, start, end int) goja.Value {
+ b, err := t.fromBuf(vm, slice, false)
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ if start < 0 || start > end || end > len(b) {
+ vm.Interrupt(fmt.Sprintf("Tracer accessed out of bound memory: available %d, offset %d, size %d", len(b), start, end-start))
+ return nil
+ }
+ res, err := t.toBuf(vm, b[start:end])
+ if err != nil {
+ vm.Interrupt(err)
+ return nil
+ }
+ return res
+ })
+}
+
+// setTypeConverters sets up utilities for converting Go types into those
+// suitable for JS consumption.
+func (t *jsTracer) setTypeConverters() error {
+ // Inject bigint logic.
+ // TODO: To be replaced after goja adds support for native JS bigint.
+ toBigCode, err := t.vm.RunProgram(bigIntProgram)
+ if err != nil {
+ return err
+ }
+ // Used to create JS bigint objects from go.
+ toBigFn, ok := goja.AssertFunction(toBigCode)
+ if !ok {
+ return errors.New("failed to bind bigInt func")
+ }
+ toBigWrapper := func(vm *goja.Runtime, val string) (goja.Value, error) {
+ return toBigFn(goja.Undefined(), vm.ToValue(val))
+ }
+ t.toBig = toBigWrapper
+ // NOTE: We need this workaround to create JS buffers because
+ // goja doesn't at the moment expose constructors for typed arrays.
+ //
+ // Cache uint8ArrayType once to be used every time for less overhead.
+ uint8ArrayType := t.vm.Get("Uint8Array")
+ toBufWrapper := func(vm *goja.Runtime, val []byte) (goja.Value, error) {
+ return toBuf(vm, uint8ArrayType, val)
+ }
+ t.toBuf = toBufWrapper
+ fromBufWrapper := func(vm *goja.Runtime, buf goja.Value, allowString bool) ([]byte, error) {
+ return fromBuf(vm, uint8ArrayType, buf, allowString)
+ }
+ t.fromBuf = fromBufWrapper
+ return nil
+}
+
+type opObj struct {
+ vm *goja.Runtime
+ op vm.OpCode
+}
+
+func (o *opObj) ToNumber() int {
+ return int(o.op)
+}
+
+func (o *opObj) ToString() string {
+ return o.op.String()
+}
+
+func (o *opObj) IsPush() bool {
+ return o.op.IsPush()
+}
+
+func (o *opObj) setupObject() *goja.Object {
+ obj := o.vm.NewObject()
+ obj.Set("toNumber", o.vm.ToValue(o.ToNumber))
+ obj.Set("toString", o.vm.ToValue(o.ToString))
+ obj.Set("isPush", o.vm.ToValue(o.IsPush))
+ return obj
+}
+
+type memoryObj struct {
+ memory *vm.Memory
+ vm *goja.Runtime
+ toBig toBigFn
+ toBuf toBufFn
+}
+
+func (mo *memoryObj) Slice(begin, end int64) goja.Value {
+ b, err := mo.slice(begin, end)
+ if err != nil {
+ mo.vm.Interrupt(err)
+ return nil
+ }
+ res, err := mo.toBuf(mo.vm, b)
+ if err != nil {
+ mo.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+// slice returns the requested range of memory as a byte slice.
+func (mo *memoryObj) slice(begin, end int64) ([]byte, error) {
+ if end == begin {
+ return []byte{}, nil
+ }
+ if end < begin || begin < 0 {
+ return nil, fmt.Errorf("tracer accessed out of bound memory: offset %d, end %d", begin, end)
+ }
+ if mo.memory.Len() < int(end) {
+ return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), begin, end-begin)
+ }
+ return mo.memory.GetCopy(begin, end-begin), nil
+}
+
+func (mo *memoryObj) GetUint(addr int64) goja.Value {
+ value, err := mo.getUint(addr)
+ if err != nil {
+ mo.vm.Interrupt(err)
+ return nil
+ }
+ res, err := mo.toBig(mo.vm, value.String())
+ if err != nil {
+ mo.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+// getUint returns the 32 bytes at the specified address interpreted as a uint.
+func (mo *memoryObj) getUint(addr int64) (*big.Int, error) {
+ if mo.memory.Len() < int(addr)+32 || addr < 0 {
+ return nil, fmt.Errorf("tracer accessed out of bound memory: available %d, offset %d, size %d", mo.memory.Len(), addr, 32)
+ }
+ return new(big.Int).SetBytes(mo.memory.GetPtr(addr, 32)), nil
+}
+
+func (mo *memoryObj) Length() int {
+ return mo.memory.Len()
+}
+
+func (m *memoryObj) setupObject() *goja.Object {
+ o := m.vm.NewObject()
+ o.Set("slice", m.vm.ToValue(m.Slice))
+ o.Set("getUint", m.vm.ToValue(m.GetUint))
+ o.Set("length", m.vm.ToValue(m.Length))
+ return o
+}
+
+type stackObj struct {
+ stack *vm.Stack
+ vm *goja.Runtime
+ toBig toBigFn
+}
+
+func (s *stackObj) Peek(idx int) goja.Value {
+ value, err := s.peek(idx)
+ if err != nil {
+ s.vm.Interrupt(err)
+ return nil
+ }
+ res, err := s.toBig(s.vm, value.String())
+ if err != nil {
+ s.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+// peek returns the nth-from-the-top element of the stack.
+func (s *stackObj) peek(idx int) (*big.Int, error) {
+ if len(s.stack.Data()) <= idx || idx < 0 {
+ return nil, fmt.Errorf("tracer accessed out of bound stack: size %d, index %d", len(s.stack.Data()), idx)
+ }
+ return s.stack.Back(idx).ToBig(), nil
+}
+
+func (s *stackObj) Length() int {
+ return len(s.stack.Data())
+}
+
+func (s *stackObj) setupObject() *goja.Object {
+ o := s.vm.NewObject()
+ o.Set("peek", s.vm.ToValue(s.Peek))
+ o.Set("length", s.vm.ToValue(s.Length))
+ return o
+}
+
+type dbObj struct {
+ db vm.StateDB
+ vm *goja.Runtime
+ toBig toBigFn
+ toBuf toBufFn
+ fromBuf fromBufFn
+}
+
+func (do *dbObj) GetBalance(addrSlice goja.Value) goja.Value {
+ a, err := do.fromBuf(do.vm, addrSlice, false)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ addr := common.BytesToAddress(a)
+ value := do.db.GetBalance(addr)
+ res, err := do.toBig(do.vm, value.String())
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (do *dbObj) GetNonce(addrSlice goja.Value) uint64 {
+ a, err := do.fromBuf(do.vm, addrSlice, false)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return 0
+ }
+ addr := common.BytesToAddress(a)
+ return do.db.GetNonce(addr)
+}
+
+func (do *dbObj) GetCode(addrSlice goja.Value) goja.Value {
+ a, err := do.fromBuf(do.vm, addrSlice, false)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ addr := common.BytesToAddress(a)
+ code := do.db.GetCode(addr)
+ res, err := do.toBuf(do.vm, code)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (do *dbObj) GetState(addrSlice goja.Value, hashSlice goja.Value) goja.Value {
+ a, err := do.fromBuf(do.vm, addrSlice, false)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ addr := common.BytesToAddress(a)
+ h, err := do.fromBuf(do.vm, hashSlice, false)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ hash := common.BytesToHash(h)
+ state := do.db.GetState(addr, hash).Bytes()
+ res, err := do.toBuf(do.vm, state)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (do *dbObj) Exists(addrSlice goja.Value) bool {
+ a, err := do.fromBuf(do.vm, addrSlice, false)
+ if err != nil {
+ do.vm.Interrupt(err)
+ return false
+ }
+ addr := common.BytesToAddress(a)
+ return do.db.Exist(addr)
+}
+
+func (do *dbObj) setupObject() *goja.Object {
+ o := do.vm.NewObject()
+ o.Set("getBalance", do.vm.ToValue(do.GetBalance))
+ o.Set("getNonce", do.vm.ToValue(do.GetNonce))
+ o.Set("getCode", do.vm.ToValue(do.GetCode))
+ o.Set("getState", do.vm.ToValue(do.GetState))
+ o.Set("exists", do.vm.ToValue(do.Exists))
+ return o
+}
+
+type contractObj struct {
+ contract *vm.Contract
+ vm *goja.Runtime
+ toBig toBigFn
+ toBuf toBufFn
+}
+
+func (co *contractObj) GetCaller() goja.Value {
+ caller := co.contract.Caller().Bytes()
+ res, err := co.toBuf(co.vm, caller)
+ if err != nil {
+ co.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (co *contractObj) GetAddress() goja.Value {
+ addr := co.contract.Address().Bytes()
+ res, err := co.toBuf(co.vm, addr)
+ if err != nil {
+ co.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (co *contractObj) GetValue() goja.Value {
+ value := co.contract.Value()
+ res, err := co.toBig(co.vm, value.String())
+ if err != nil {
+ co.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (co *contractObj) GetInput() goja.Value {
+ input := common.CopyBytes(co.contract.Input)
+ res, err := co.toBuf(co.vm, input)
+ if err != nil {
+ co.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (c *contractObj) setupObject() *goja.Object {
+ o := c.vm.NewObject()
+ o.Set("getCaller", c.vm.ToValue(c.GetCaller))
+ o.Set("getAddress", c.vm.ToValue(c.GetAddress))
+ o.Set("getValue", c.vm.ToValue(c.GetValue))
+ o.Set("getInput", c.vm.ToValue(c.GetInput))
+ return o
+}
+
+type callframe struct {
+ vm *goja.Runtime
+ toBig toBigFn
+ toBuf toBufFn
+
+ typ string
+ from common.Address
+ to common.Address
+ input []byte
+ gas uint
+ value *big.Int
+}
+
+func (f *callframe) GetType() string {
+ return f.typ
+}
+
+func (f *callframe) GetFrom() goja.Value {
+ from := f.from.Bytes()
+ res, err := f.toBuf(f.vm, from)
+ if err != nil {
+ f.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (f *callframe) GetTo() goja.Value {
+ to := f.to.Bytes()
+ res, err := f.toBuf(f.vm, to)
+ if err != nil {
+ f.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (f *callframe) GetInput() goja.Value {
+ input := f.input
+ res, err := f.toBuf(f.vm, input)
+ if err != nil {
+ f.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (f *callframe) GetGas() uint {
+ return f.gas
+}
+
+func (f *callframe) GetValue() goja.Value {
+ if f.value == nil {
+ return goja.Undefined()
+ }
+ res, err := f.toBig(f.vm, f.value.String())
+ if err != nil {
+ f.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (f *callframe) setupObject() *goja.Object {
+ o := f.vm.NewObject()
+ o.Set("getType", f.vm.ToValue(f.GetType))
+ o.Set("getFrom", f.vm.ToValue(f.GetFrom))
+ o.Set("getTo", f.vm.ToValue(f.GetTo))
+ o.Set("getInput", f.vm.ToValue(f.GetInput))
+ o.Set("getGas", f.vm.ToValue(f.GetGas))
+ o.Set("getValue", f.vm.ToValue(f.GetValue))
+ return o
+}
+
+type callframeResult struct {
+ vm *goja.Runtime
+ toBuf toBufFn
+
+ gasUsed uint
+ output []byte
+ err error
+}
+
+func (r *callframeResult) GetGasUsed() uint {
+ return r.gasUsed
+}
+
+func (r *callframeResult) GetOutput() goja.Value {
+ res, err := r.toBuf(r.vm, r.output)
+ if err != nil {
+ r.vm.Interrupt(err)
+ return nil
+ }
+ return res
+}
+
+func (r *callframeResult) GetError() goja.Value {
+ if r.err != nil {
+ return r.vm.ToValue(r.err.Error())
+ }
+ return goja.Undefined()
+}
+
+func (r *callframeResult) setupObject() *goja.Object {
+ o := r.vm.NewObject()
+ o.Set("getGasUsed", r.vm.ToValue(r.GetGasUsed))
+ o.Set("getOutput", r.vm.ToValue(r.GetOutput))
+ o.Set("getError", r.vm.ToValue(r.GetError))
+ return o
+}
+
+type steplog struct {
+ vm *goja.Runtime
+
+ op *opObj
+ memory *memoryObj
+ stack *stackObj
+ contract *contractObj
+
+ pc uint
+ gas uint
+ cost uint
+ depth uint
+ refund uint
+ err error
+}
+
+func (l *steplog) GetPC() uint {
+ return l.pc
+}
+
+func (l *steplog) GetGas() uint {
+ return l.gas
+}
+
+func (l *steplog) GetCost() uint {
+ return l.cost
+}
+
+func (l *steplog) GetDepth() uint {
+ return l.depth
+}
+
+func (l *steplog) GetRefund() uint {
+ return l.refund
+}
+
+func (l *steplog) GetError() goja.Value {
+ if l.err != nil {
+ return l.vm.ToValue(l.err.Error())
+ }
+ return goja.Undefined()
+}
+
+func (l *steplog) setupObject() *goja.Object {
+ o := l.vm.NewObject()
+ // Setup basic fields.
+ o.Set("getPC", l.vm.ToValue(l.GetPC))
+ o.Set("getGas", l.vm.ToValue(l.GetGas))
+ o.Set("getCost", l.vm.ToValue(l.GetCost))
+ o.Set("getDepth", l.vm.ToValue(l.GetDepth))
+ o.Set("getRefund", l.vm.ToValue(l.GetRefund))
+ o.Set("getError", l.vm.ToValue(l.GetError))
+ // Setup nested objects.
+ o.Set("op", l.op.setupObject())
+ o.Set("stack", l.stack.setupObject())
+ o.Set("memory", l.memory.setupObject())
+ o.Set("contract", l.contract.setupObject())
+ return o
+}
diff --git a/x/evm/tracers/js/internal/tracers/4byte_tracer_legacy.js b/x/evm/tracers/js/internal/tracers/4byte_tracer_legacy.js
new file mode 100644
index 00000000..e4714b8b
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/4byte_tracer_legacy.js
@@ -0,0 +1,86 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// 4byteTracer searches for 4byte-identifiers, and collects them for post-processing.
+// It collects the methods identifiers along with the size of the supplied data, so
+// a reversed signature can be matched against the size of the data.
+//
+// Example:
+// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
+// {
+// 0x27dc297e-128: 1,
+// 0x38cc4831-0: 2,
+// 0x524f3889-96: 1,
+// 0xadf59f99-288: 1,
+// 0xc281d19e-0: 1
+// }
+{
+ // ids aggregates the 4byte ids found.
+ ids : {},
+
+ // callType returns 'false' for non-calls, or the peek-index for the first param
+ // after 'value', i.e. meminstart.
+ callType: function(opstr){
+ switch(opstr){
+ case "CALL": case "CALLCODE":
+ // gas, addr, val, memin, meminsz, memout, memoutsz
+ return 3; // stack ptr to memin
+
+ case "DELEGATECALL": case "STATICCALL":
+ // gas, addr, memin, meminsz, memout, memoutsz
+ return 2; // stack ptr to memin
+ }
+ return false;
+ },
+
+ // store save the given identifier and datasize.
+ store: function(id, size){
+ var key = "" + toHex(id) + "-" + size;
+ this.ids[key] = this.ids[key] + 1 || 1;
+ },
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ // Skip any opcodes that are not internal calls
+ var ct = this.callType(log.op.toString());
+ if (!ct) {
+ return;
+ }
+ // Skip any pre-compile invocations, those are just fancy opcodes
+ if (isPrecompiled(toAddress(log.stack.peek(1).toString(16)))) {
+ return;
+ }
+ // Gather internal call details
+ var inSz = log.stack.peek(ct + 1).valueOf();
+ if (inSz >= 4) {
+ var inOff = log.stack.peek(ct).valueOf();
+ this.store(log.memory.slice(inOff, inOff + 4), inSz-4);
+ }
+ },
+
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) { },
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx) {
+ // Save the outer calldata also
+ if (ctx.input.length >= 4) {
+ this.store(slice(ctx.input, 0, 4), ctx.input.length-4)
+ }
+ return this.ids;
+ },
+}
diff --git a/x/evm/tracers/js/internal/tracers/bigram_tracer.js b/x/evm/tracers/js/internal/tracers/bigram_tracer.js
new file mode 100644
index 00000000..421c360a
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/bigram_tracer.js
@@ -0,0 +1,47 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+{
+ // hist is the counters of opcode bigrams
+ hist: {},
+ // lastOp is last operation
+ lastOp: '',
+ // execution depth of last op
+ lastDepth: 0,
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ var op = log.op.toString();
+ var depth = log.getDepth();
+ if (depth == this.lastDepth){
+ var key = this.lastOp+'-'+op;
+ if (this.hist[key]){
+ this.hist[key]++;
+ }
+ else {
+ this.hist[key] = 1;
+ }
+ }
+ this.lastOp = op;
+ this.lastDepth = depth;
+ },
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) {},
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx) {
+ return this.hist;
+ },
+}
diff --git a/x/evm/tracers/js/internal/tracers/call_tracer_legacy.js b/x/evm/tracers/js/internal/tracers/call_tracer_legacy.js
new file mode 100644
index 00000000..3ca73777
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/call_tracer_legacy.js
@@ -0,0 +1,252 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// callTracer is a full blown transaction tracer that extracts and reports all
+// the internal calls made by a transaction, along with any useful information.
+{
+ // callstack is the current recursive call stack of the EVM execution.
+ callstack: [{}],
+
+ // descended tracks whether we've just descended from an outer transaction into
+ // an inner call.
+ descended: false,
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ // Capture any errors immediately
+ var error = log.getError();
+ if (error !== undefined) {
+ this.fault(log, db);
+ return;
+ }
+ // We only care about system opcodes, faster if we pre-check once
+ var syscall = (log.op.toNumber() & 0xf0) == 0xf0;
+ if (syscall) {
+ var op = log.op.toString();
+ }
+ // If a new contract is being created, add to the call stack
+ if (syscall && (op == 'CREATE' || op == "CREATE2")) {
+ var inOff = log.stack.peek(1).valueOf();
+ var inEnd = inOff + log.stack.peek(2).valueOf();
+
+ // Assemble the internal call report and store for completion
+ var call = {
+ type: op,
+ from: toHex(log.contract.getAddress()),
+ input: toHex(log.memory.slice(inOff, inEnd)),
+ gasIn: log.getGas(),
+ gasCost: log.getCost(),
+ value: '0x' + log.stack.peek(0).toString(16)
+ };
+ this.callstack.push(call);
+ this.descended = true
+ return;
+ }
+ // If a contract is being self destructed, gather that as a subcall too
+ if (syscall && op == 'SELFDESTRUCT') {
+ var left = this.callstack.length;
+ if (this.callstack[left-1].calls === undefined) {
+ this.callstack[left-1].calls = [];
+ }
+ this.callstack[left-1].calls.push({
+ type: op,
+ from: toHex(log.contract.getAddress()),
+ to: toHex(toAddress(log.stack.peek(0).toString(16))),
+ gasIn: log.getGas(),
+ gasCost: log.getCost(),
+ value: '0x' + db.getBalance(log.contract.getAddress()).toString(16)
+ });
+ return
+ }
+ // If a new method invocation is being done, add to the call stack
+ if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) {
+ // Skip any pre-compile invocations, those are just fancy opcodes
+ var to = toAddress(log.stack.peek(1).toString(16));
+ if (isPrecompiled(to)) {
+ return
+ }
+ var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1);
+
+ var inOff = log.stack.peek(2 + off).valueOf();
+ var inEnd = inOff + log.stack.peek(3 + off).valueOf();
+
+ // Assemble the internal call report and store for completion
+ var call = {
+ type: op,
+ from: toHex(log.contract.getAddress()),
+ to: toHex(to),
+ input: toHex(log.memory.slice(inOff, inEnd)),
+ gasIn: log.getGas(),
+ gasCost: log.getCost(),
+ outOff: log.stack.peek(4 + off).valueOf(),
+ outLen: log.stack.peek(5 + off).valueOf()
+ };
+ if (op != 'DELEGATECALL' && op != 'STATICCALL') {
+ call.value = '0x' + log.stack.peek(2).toString(16);
+ }
+ this.callstack.push(call);
+ this.descended = true
+ return;
+ }
+ // If we've just descended into an inner call, retrieve it's true allowance. We
+ // need to extract if from within the call as there may be funky gas dynamics
+ // with regard to requested and actually given gas (2300 stipend, 63/64 rule).
+ if (this.descended) {
+ if (log.getDepth() >= this.callstack.length) {
+ this.callstack[this.callstack.length - 1].gas = log.getGas();
+ } else {
+ // TODO(karalabe): The call was made to a plain account. We currently don't
+ // have access to the true gas amount inside the call and so any amount will
+ // mostly be wrong since it depends on a lot of input args. Skip gas for now.
+ }
+ this.descended = false;
+ }
+ // If an existing call is returning, pop off the call stack
+ if (syscall && op == 'REVERT') {
+ this.callstack[this.callstack.length - 1].error = "execution reverted";
+ return;
+ }
+ if (log.getDepth() == this.callstack.length - 1) {
+ // Pop off the last call and get the execution results
+ var call = this.callstack.pop();
+
+ if (call.type == 'CREATE' || call.type == "CREATE2") {
+ // If the call was a CREATE, retrieve the contract address and output code
+ call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16);
+ delete call.gasIn; delete call.gasCost;
+
+ var ret = log.stack.peek(0);
+ if (!ret.equals(0)) {
+ call.to = toHex(toAddress(ret.toString(16)));
+ call.output = toHex(db.getCode(toAddress(ret.toString(16))));
+ } else if (call.error === undefined) {
+ call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
+ }
+ } else {
+ // If the call was a contract call, retrieve the gas usage and output
+ if (call.gas !== undefined) {
+ call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16);
+ }
+ var ret = log.stack.peek(0);
+ if (!ret.equals(0)) {
+ call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen));
+ } else if (call.error === undefined) {
+ call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
+ }
+ delete call.gasIn; delete call.gasCost;
+ delete call.outOff; delete call.outLen;
+ }
+ if (call.gas !== undefined) {
+ call.gas = '0x' + bigInt(call.gas).toString(16);
+ }
+ // Inject the call into the previous one
+ var left = this.callstack.length;
+ if (this.callstack[left-1].calls === undefined) {
+ this.callstack[left-1].calls = [];
+ }
+ this.callstack[left-1].calls.push(call);
+ }
+ },
+
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) {
+ // If the topmost call already reverted, don't handle the additional fault again
+ if (this.callstack[this.callstack.length - 1].error !== undefined) {
+ return;
+ }
+ // Pop off the just failed call
+ var call = this.callstack.pop();
+ call.error = log.getError();
+
+ // Consume all available gas and clean any leftovers
+ if (call.gas !== undefined) {
+ call.gas = '0x' + bigInt(call.gas).toString(16);
+ call.gasUsed = call.gas
+ }
+ delete call.gasIn; delete call.gasCost;
+ delete call.outOff; delete call.outLen;
+
+ // Flatten the failed call into its parent
+ var left = this.callstack.length;
+ if (left > 0) {
+ if (this.callstack[left-1].calls === undefined) {
+ this.callstack[left-1].calls = [];
+ }
+ this.callstack[left-1].calls.push(call);
+ return;
+ }
+ // Last call failed too, leave it in the stack
+ this.callstack.push(call);
+ },
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx, db) {
+ var result = {
+ type: ctx.type,
+ from: toHex(ctx.from),
+ to: toHex(ctx.to),
+ value: '0x' + ctx.value.toString(16),
+ gas: '0x' + bigInt(ctx.gas).toString(16),
+ gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16),
+ input: toHex(ctx.input),
+ output: toHex(ctx.output),
+ time: ctx.time,
+ };
+ if (this.callstack[0].calls !== undefined) {
+ result.calls = this.callstack[0].calls;
+ }
+ if (this.callstack[0].error !== undefined) {
+ result.error = this.callstack[0].error;
+ } else if (ctx.error !== undefined) {
+ result.error = ctx.error;
+ }
+ if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) {
+ delete result.output;
+ }
+ return this.finalize(result);
+ },
+
+ // finalize recreates a call object using the final desired field oder for json
+ // serialization. This is a nicety feature to pass meaningfully ordered results
+ // to users who don't interpret it, just display it.
+ finalize: function(call) {
+ var sorted = {
+ type: call.type,
+ from: call.from,
+ to: call.to,
+ value: call.value,
+ gas: call.gas,
+ gasUsed: call.gasUsed,
+ input: call.input,
+ output: call.output,
+ error: call.error,
+ time: call.time,
+ calls: call.calls,
+ }
+ for (var key in sorted) {
+ if (sorted[key] === undefined) {
+ delete sorted[key];
+ }
+ }
+ if (sorted.calls !== undefined) {
+ for (var i=0; i.
+
+// evmdisTracer returns sufficient information from a trace to perform evmdis-style
+// disassembly.
+{
+ stack: [{ops: []}],
+
+ npushes: {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 32: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 0, 56: 1, 57: 0, 58: 1, 59: 1, 60: 0, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 80: 0, 81: 1, 82: 0, 83: 0, 84: 1, 85: 0, 86: 0, 87: 0, 88: 1, 89: 1, 90: 1, 91: 0, 96: 1, 97: 1, 98: 1, 99: 1, 100: 1, 101: 1, 102: 1, 103: 1, 104: 1, 105: 1, 106: 1, 107: 1, 108: 1, 109: 1, 110: 1, 111: 1, 112: 1, 113: 1, 114: 1, 115: 1, 116: 1, 117: 1, 118: 1, 119: 1, 120: 1, 121: 1, 122: 1, 123: 1, 124: 1, 125: 1, 126: 1, 127: 1, 128: 2, 129: 3, 130: 4, 131: 5, 132: 6, 133: 7, 134: 8, 135: 9, 136: 10, 137: 11, 138: 12, 139: 13, 140: 14, 141: 15, 142: 16, 143: 17, 144: 2, 145: 3, 146: 4, 147: 5, 148: 6, 149: 7, 150: 8, 151: 9, 152: 10, 153: 11, 154: 12, 155: 13, 156: 14, 157: 15, 158: 16, 159: 17, 160: 0, 161: 0, 162: 0, 163: 0, 164: 0, 240: 1, 241: 1, 242: 1, 243: 0, 244: 0, 255: 0},
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function() { return this.stack[0].ops; },
+
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) { },
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ var frame = this.stack[this.stack.length - 1];
+
+ var error = log.getError();
+ if (error) {
+ frame["error"] = error;
+ } else if (log.getDepth() == this.stack.length) {
+ opinfo = {
+ op: log.op.toNumber(),
+ depth : log.getDepth(),
+ result: [],
+ };
+ if (frame.ops.length > 0) {
+ var prevop = frame.ops[frame.ops.length - 1];
+ for(var i = 0; i < this.npushes[prevop.op]; i++)
+ prevop.result.push(log.stack.peek(i).toString(16));
+ }
+ switch(log.op.toString()) {
+ case "CALL": case "CALLCODE":
+ var instart = log.stack.peek(3).valueOf();
+ var insize = log.stack.peek(4).valueOf();
+ opinfo["gas"] = log.stack.peek(0).valueOf();
+ opinfo["to"] = log.stack.peek(1).toString(16);
+ opinfo["value"] = log.stack.peek(2).toString();
+ opinfo["input"] = log.memory.slice(instart, instart + insize);
+ opinfo["error"] = null;
+ opinfo["return"] = null;
+ opinfo["ops"] = [];
+ this.stack.push(opinfo);
+ break;
+ case "DELEGATECALL": case "STATICCALL":
+ var instart = log.stack.peek(2).valueOf();
+ var insize = log.stack.peek(3).valueOf();
+ opinfo["op"] = log.op.toString();
+ opinfo["gas"] = log.stack.peek(0).valueOf();
+ opinfo["to"] = log.stack.peek(1).toString(16);
+ opinfo["input"] = log.memory.slice(instart, instart + insize);
+ opinfo["error"] = null;
+ opinfo["return"] = null;
+ opinfo["ops"] = [];
+ this.stack.push(opinfo);
+ break;
+ case "RETURN": case "REVERT":
+ var out = log.stack.peek(0).valueOf();
+ var outsize = log.stack.peek(1).valueOf();
+ frame.return = log.memory.slice(out, out + outsize);
+ break;
+ case "STOP": case "SELFDESTRUCT":
+ frame.return = log.memory.slice(0, 0);
+ break;
+ case "JUMPDEST":
+ opinfo["pc"] = log.getPC();
+ }
+ if(log.op.isPush()) {
+ opinfo["len"] = log.op.toNumber() - 0x5e;
+ }
+ frame.ops.push(opinfo);
+ } else {
+ this.stack = this.stack.slice(0, log.getDepth());
+ }
+ }
+}
diff --git a/x/evm/tracers/js/internal/tracers/noop_tracer_legacy.js b/x/evm/tracers/js/internal/tracers/noop_tracer_legacy.js
new file mode 100644
index 00000000..fe7ddc85
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/noop_tracer_legacy.js
@@ -0,0 +1,29 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// noopTracer is just the barebone boilerplate code required from a JavaScript
+// object to be usable as a transaction tracer.
+{
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) { },
+
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) { },
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx, db) { return {}; }
+}
diff --git a/x/evm/tracers/js/internal/tracers/opcount_tracer.js b/x/evm/tracers/js/internal/tracers/opcount_tracer.js
new file mode 100644
index 00000000..f7984c74
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/opcount_tracer.js
@@ -0,0 +1,32 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// opcountTracer is a sample tracer that just counts the number of instructions
+// executed by the EVM before the transaction terminated.
+{
+ // count tracks the number of EVM instructions executed.
+ count: 0,
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) { this.count++ },
+
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) { },
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx, db) { return this.count }
+}
diff --git a/x/evm/tracers/js/internal/tracers/prestate_tracer_legacy.js b/x/evm/tracers/js/internal/tracers/prestate_tracer_legacy.js
new file mode 100644
index 00000000..77f25209
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/prestate_tracer_legacy.js
@@ -0,0 +1,115 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// prestateTracer outputs sufficient information to create a local execution of
+// the transaction from a custom assembled genesis block.
+{
+ // prestate is the genesis that we're building.
+ prestate: null,
+
+ // lookupAccount injects the specified account into the prestate object.
+ lookupAccount: function(addr, db){
+ var acc = toHex(addr);
+ if (this.prestate[acc] === undefined) {
+ this.prestate[acc] = {
+ balance: '0x' + db.getBalance(addr).toString(16),
+ nonce: db.getNonce(addr),
+ code: toHex(db.getCode(addr)),
+ storage: {}
+ };
+ }
+ },
+
+ // lookupStorage injects the specified storage entry of the given account into
+ // the prestate object.
+ lookupStorage: function(addr, key, db){
+ var acc = toHex(addr);
+ var idx = toHex(key);
+
+ if (this.prestate[acc].storage[idx] === undefined) {
+ this.prestate[acc].storage[idx] = toHex(db.getState(addr, key));
+ }
+ },
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx, db) {
+ if (this.prestate === null) {
+ this.prestate = {};
+ // If tx is transfer-only, the recipient account
+ // hasn't been populated.
+ this.lookupAccount(ctx.to, db);
+ }
+
+ // At this point, we need to deduct the 'value' from the
+ // outer transaction, and move it back to the origin
+ this.lookupAccount(ctx.from, db);
+
+ var fromBal = bigInt(this.prestate[toHex(ctx.from)].balance.slice(2), 16);
+ var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16);
+
+ this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16);
+ this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).add((ctx.gasUsed + ctx.intrinsicGas) * ctx.gasPrice).toString(16);
+
+ // Decrement the caller's nonce, and remove empty create targets
+ this.prestate[toHex(ctx.from)].nonce--;
+ if (ctx.type == 'CREATE') {
+ // We can blibdly delete the contract prestate, as any existing state would
+ // have caused the transaction to be rejected as invalid in the first place.
+ delete this.prestate[toHex(ctx.to)];
+ }
+ // Return the assembled allocations (prestate)
+ return this.prestate;
+ },
+
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ // Add the current account if we just started tracing
+ if (this.prestate === null){
+ this.prestate = {};
+ // Balance will potentially be wrong here, since this will include the value
+ // sent along with the message. We fix that in 'result()'.
+ this.lookupAccount(log.contract.getAddress(), db);
+ }
+ // Whenever new state is accessed, add it to the prestate
+ switch (log.op.toString()) {
+ case "EXTCODECOPY": case "EXTCODESIZE": case "EXTCODEHASH": case "BALANCE":
+ this.lookupAccount(toAddress(log.stack.peek(0).toString(16)), db);
+ break;
+ case "CREATE":
+ var from = log.contract.getAddress();
+ this.lookupAccount(toContract(from, db.getNonce(from)), db);
+ break;
+ case "CREATE2":
+ var from = log.contract.getAddress();
+ // stack: salt, size, offset, endowment
+ var offset = log.stack.peek(1).valueOf()
+ var size = log.stack.peek(2).valueOf()
+ var end = offset + size
+ this.lookupAccount(toContract2(from, log.stack.peek(3).toString(16), log.memory.slice(offset, end)), db);
+ break;
+ case "CALL": case "CALLCODE": case "DELEGATECALL": case "STATICCALL":
+ this.lookupAccount(toAddress(log.stack.peek(1).toString(16)), db);
+ break;
+ case 'SSTORE':case 'SLOAD':
+ this.lookupStorage(log.contract.getAddress(), toWord(log.stack.peek(0).toString(16)), db);
+ break;
+ }
+ },
+
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) {}
+}
diff --git a/x/evm/tracers/js/internal/tracers/tracers.go b/x/evm/tracers/js/internal/tracers/tracers.go
new file mode 100644
index 00000000..6547f1b0
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/tracers.go
@@ -0,0 +1,59 @@
+// Copyright 2017 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+// Package tracers contains the actual JavaScript tracer assets.
+package tracers
+
+import (
+ "embed"
+ "io/fs"
+ "strings"
+ "unicode"
+)
+
+//go:embed *.js
+var files embed.FS
+
+// Load reads the built-in JS tracer files embedded in the binary and
+// returns a mapping of tracer name to source.
+func Load() (map[string]string, error) {
+ var assetTracers = make(map[string]string)
+ err := fs.WalkDir(files, ".", func(path string, d fs.DirEntry, err error) error {
+ if err != nil {
+ return err
+ }
+ if d.IsDir() {
+ return nil
+ }
+ b, err := fs.ReadFile(files, path)
+ if err != nil {
+ return err
+ }
+ name := camel(strings.TrimSuffix(path, ".js"))
+ assetTracers[name] = string(b)
+ return nil
+ })
+ return assetTracers, err
+}
+
+// camel converts a snake cased input string into a camel cased output.
+func camel(str string) string {
+ pieces := strings.Split(str, "_")
+ for i := 1; i < len(pieces); i++ {
+ pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:]
+ }
+ return strings.Join(pieces, "")
+}
diff --git a/x/evm/tracers/js/internal/tracers/trigram_tracer.js b/x/evm/tracers/js/internal/tracers/trigram_tracer.js
new file mode 100644
index 00000000..8756490d
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/trigram_tracer.js
@@ -0,0 +1,49 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+{
+ // hist is the map of trigram counters
+ hist: {},
+ // lastOp is last operation
+ lastOps: ['',''],
+ lastDepth: 0,
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ var depth = log.getDepth();
+ if (depth != this.lastDepth){
+ this.lastOps = ['',''];
+ this.lastDepth = depth;
+ return;
+ }
+ var op = log.op.toString();
+ var key = this.lastOps[0]+'-'+this.lastOps[1]+'-'+op;
+ if (this.hist[key]){
+ this.hist[key]++;
+ }
+ else {
+ this.hist[key] = 1;
+ }
+ this.lastOps[0] = this.lastOps[1];
+ this.lastOps[1] = op;
+ },
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) {},
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx) {
+ return this.hist;
+ },
+}
diff --git a/x/evm/tracers/js/internal/tracers/unigram_tracer.js b/x/evm/tracers/js/internal/tracers/unigram_tracer.js
new file mode 100644
index 00000000..51107d8f
--- /dev/null
+++ b/x/evm/tracers/js/internal/tracers/unigram_tracer.js
@@ -0,0 +1,41 @@
+// Copyright 2018 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+{
+ // hist is the map of opcodes to counters
+ hist: {},
+ // nops counts number of ops
+ nops: 0,
+ // step is invoked for every opcode that the VM executes.
+ step: function(log, db) {
+ var op = log.op.toString();
+ if (this.hist[op]){
+ this.hist[op]++;
+ }
+ else {
+ this.hist[op] = 1;
+ }
+ this.nops++;
+ },
+ // fault is invoked when the actual execution of an opcode fails.
+ fault: function(log, db) {},
+
+ // result is invoked when all the opcodes have been iterated over and returns
+ // the final result of the tracing.
+ result: function(ctx) {
+ return this.hist;
+ },
+}
diff --git a/x/evm/tracers/native/4byte.go b/x/evm/tracers/native/4byte.go
new file mode 100644
index 00000000..34e608bf
--- /dev/null
+++ b/x/evm/tracers/native/4byte.go
@@ -0,0 +1,152 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package native
+
+import (
+ "encoding/json"
+ "math/big"
+ "strconv"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+)
+
+func init() {
+ register("4byteTracer", newFourByteTracer)
+}
+
+// fourByteTracer searches for 4byte-identifiers, and collects them for post-processing.
+// It collects the methods identifiers along with the size of the supplied data, so
+// a reversed signature can be matched against the size of the data.
+//
+// Example:
+// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
+// {
+// 0x27dc297e-128: 1,
+// 0x38cc4831-0: 2,
+// 0x524f3889-96: 1,
+// 0xadf59f99-288: 1,
+// 0xc281d19e-0: 1
+// }
+type fourByteTracer struct {
+ env *vm.EVM
+ ids map[string]int // ids aggregates the 4byte ids found
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+ activePrecompiles []common.Address // Updated on CaptureStart based on given rules
+}
+
+// newFourByteTracer returns a native go tracer which collects
+// 4 byte-identifiers of a tx, and implements vm.EVMLogger.
+func newFourByteTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ t := &fourByteTracer{
+ ids: make(map[string]int),
+ }
+ return t, nil
+}
+
+// isPrecompiled returns whether the addr is a precompile. Logic borrowed from newJsTracer in eth/tracers/js/tracer.go
+func (t *fourByteTracer) isPrecompiled(addr common.Address) bool {
+ for _, p := range t.activePrecompiles {
+ if p == addr {
+ return true
+ }
+ }
+ return false
+}
+
+// store saves the given identifier and datasize.
+func (t *fourByteTracer) store(id []byte, size int) {
+ key := bytesToHex(id) + "-" + strconv.Itoa(size)
+ t.ids[key] += 1
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *fourByteTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+
+ // Update list of precompiles based on current block
+ rules := env.ChainConfig().Rules(env.Context.BlockNumber, env.Context.Random != nil)
+ t.activePrecompiles = vm.ActivePrecompiles(rules)
+
+ // Save the outer calldata also
+ if len(input) >= 4 {
+ t.store(input[0:4], len(input)-4)
+ }
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *fourByteTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *fourByteTracer) CaptureEnter(op vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ // Skip if tracing was interrupted
+ if atomic.LoadUint32(&t.interrupt) > 0 {
+ t.env.Cancel()
+ return
+ }
+ if len(input) < 4 {
+ return
+ }
+ // primarily we want to avoid CREATE/CREATE2/SELFDESTRUCT
+ if op != vm.DELEGATECALL && op != vm.STATICCALL &&
+ op != vm.CALL && op != vm.CALLCODE {
+ return
+ }
+ // Skip any pre-compile invocations, those are just fancy opcodes
+ if t.isPrecompiled(to) {
+ return
+ }
+ t.store(input[0:4], len(input)-4)
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *fourByteTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *fourByteTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *fourByteTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+}
+
+func (*fourByteTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (*fourByteTracer) CaptureTxEnd(restGas uint64) {}
+
+// GetResult returns the json-encoded nested list of call traces, and any
+// error arising from the encoding or forceful termination (via `Stop`).
+func (t *fourByteTracer) GetResult() (json.RawMessage, error) {
+ res, err := json.Marshal(t.ids)
+ if err != nil {
+ return nil, err
+ }
+ return res, t.reason
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *fourByteTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
diff --git a/x/evm/tracers/native/call.go b/x/evm/tracers/native/call.go
new file mode 100644
index 00000000..7af0e658
--- /dev/null
+++ b/x/evm/tracers/native/call.go
@@ -0,0 +1,202 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package native
+
+import (
+ "encoding/json"
+ "errors"
+ "math/big"
+ "strconv"
+ "strings"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+)
+
+func init() {
+ register("callTracer", newCallTracer)
+}
+
+type callFrame struct {
+ Type string `json:"type"`
+ From string `json:"from"`
+ To string `json:"to,omitempty"`
+ Value string `json:"value,omitempty"`
+ Gas string `json:"gas"`
+ GasUsed string `json:"gasUsed"`
+ Input string `json:"input"`
+ Output string `json:"output,omitempty"`
+ Error string `json:"error,omitempty"`
+ Calls []callFrame `json:"calls,omitempty"`
+}
+
+type callTracer struct {
+ env *vm.EVM
+ callstack []callFrame
+ config callTracerConfig
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+type callTracerConfig struct {
+ OnlyTopCall bool `json:"onlyTopCall"` // If true, call tracer won't collect any subcalls
+}
+
+// newCallTracer returns a native go tracer which tracks
+// call frames of a tx, and implements vm.EVMLogger.
+func newCallTracer(ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ var config callTracerConfig
+ if cfg != nil {
+ if err := json.Unmarshal(cfg, &config); err != nil {
+ return nil, err
+ }
+ }
+ // First callframe contains tx context info
+ // and is populated on start and end.
+ return &callTracer{callstack: make([]callFrame, 1), config: config}, nil
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *callTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ t.callstack[0] = callFrame{
+ Type: "CALL",
+ From: addrToHex(from),
+ To: addrToHex(to),
+ Input: bytesToHex(input),
+ Gas: uintToHex(gas),
+ Value: bigToHex(value),
+ }
+ if create {
+ t.callstack[0].Type = "CREATE"
+ }
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *callTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+ t.callstack[0].GasUsed = uintToHex(gasUsed)
+ if err != nil {
+ t.callstack[0].Error = err.Error()
+ if err.Error() == "execution reverted" && len(output) > 0 {
+ t.callstack[0].Output = bytesToHex(output)
+ }
+ } else {
+ t.callstack[0].Output = bytesToHex(output)
+ }
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *callTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *callTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *callTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+ if t.config.OnlyTopCall {
+ return
+ }
+ // Skip if tracing was interrupted
+ if atomic.LoadUint32(&t.interrupt) > 0 {
+ t.env.Cancel()
+ return
+ }
+
+ call := callFrame{
+ Type: typ.String(),
+ From: addrToHex(from),
+ To: addrToHex(to),
+ Input: bytesToHex(input),
+ Gas: uintToHex(gas),
+ Value: bigToHex(value),
+ }
+ t.callstack = append(t.callstack, call)
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *callTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+ if t.config.OnlyTopCall {
+ return
+ }
+ size := len(t.callstack)
+ if size <= 1 {
+ return
+ }
+ // pop call
+ call := t.callstack[size-1]
+ t.callstack = t.callstack[:size-1]
+ size -= 1
+
+ call.GasUsed = uintToHex(gasUsed)
+ if err == nil {
+ call.Output = bytesToHex(output)
+ } else {
+ call.Error = err.Error()
+ if call.Type == "CREATE" || call.Type == "CREATE2" {
+ call.To = ""
+ }
+ }
+ t.callstack[size-1].Calls = append(t.callstack[size-1].Calls, call)
+}
+
+func (*callTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (*callTracer) CaptureTxEnd(restGas uint64) {}
+
+// GetResult returns the json-encoded nested list of call traces, and any
+// error arising from the encoding or forceful termination (via `Stop`).
+func (t *callTracer) GetResult() (json.RawMessage, error) {
+ if len(t.callstack) != 1 {
+ return nil, errors.New("incorrect number of top-level calls")
+ }
+ res, err := json.Marshal(t.callstack[0])
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(res), t.reason
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *callTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
+
+func bytesToHex(s []byte) string {
+ return "0x" + common.Bytes2Hex(s)
+}
+
+func bigToHex(n *big.Int) string {
+ if n == nil {
+ return ""
+ }
+ return "0x" + n.Text(16)
+}
+
+func uintToHex(n uint64) string {
+ return "0x" + strconv.FormatUint(n, 16)
+}
+
+func addrToHex(a common.Address) string {
+ return strings.ToLower(a.Hex())
+}
diff --git a/x/evm/tracers/native/noop.go b/x/evm/tracers/native/noop.go
new file mode 100644
index 00000000..c252b240
--- /dev/null
+++ b/x/evm/tracers/native/noop.go
@@ -0,0 +1,78 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package native
+
+import (
+ "encoding/json"
+ "math/big"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+)
+
+func init() {
+ register("noopTracer", newNoopTracer)
+}
+
+// noopTracer is a go implementation of the Tracer interface which
+// performs no action. It's mostly useful for testing purposes.
+type noopTracer struct{}
+
+// newNoopTracer returns a new noop tracer.
+func newNoopTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ return &noopTracer{}, nil
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *noopTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *noopTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *noopTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *noopTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *noopTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *noopTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+}
+
+func (*noopTracer) CaptureTxStart(gasLimit uint64) {}
+
+func (*noopTracer) CaptureTxEnd(restGas uint64) {}
+
+// GetResult returns an empty json object.
+func (t *noopTracer) GetResult() (json.RawMessage, error) {
+ return json.RawMessage(`{}`), nil
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *noopTracer) Stop(err error) {
+}
diff --git a/x/evm/tracers/native/prestate.go b/x/evm/tracers/native/prestate.go
new file mode 100644
index 00000000..b513f383
--- /dev/null
+++ b/x/evm/tracers/native/prestate.go
@@ -0,0 +1,178 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package native
+
+import (
+ "encoding/json"
+ "math/big"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/common/hexutil"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+)
+
+func init() {
+ register("prestateTracer", newPrestateTracer)
+}
+
+type prestate = map[common.Address]*account
+type account struct {
+ Balance string `json:"balance"`
+ Nonce uint64 `json:"nonce"`
+ Code string `json:"code"`
+ Storage map[common.Hash]common.Hash `json:"storage"`
+}
+
+type prestateTracer struct {
+ env *vm.EVM
+ prestate prestate
+ create bool
+ to common.Address
+ gasLimit uint64 // Amount of gas bought for the whole tx
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+func newPrestateTracer(ctx *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ // First callframe contains tx context info
+ // and is populated on start and end.
+ return &prestateTracer{prestate: prestate{}}, nil
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *prestateTracer) CaptureStart(env *vm.EVM, from common.Address, to common.Address, create bool, input []byte, gas uint64, value *big.Int) {
+ t.env = env
+ t.create = create
+ t.to = to
+
+ t.lookupAccount(from)
+ t.lookupAccount(to)
+
+ // The recipient balance includes the value transferred.
+ toBal := hexutil.MustDecodeBig(t.prestate[to].Balance)
+ toBal = new(big.Int).Sub(toBal, value)
+ t.prestate[to].Balance = hexutil.EncodeBig(toBal)
+
+ // The sender balance is after reducing: value and gasLimit.
+ // We need to re-add them to get the pre-tx balance.
+ fromBal := hexutil.MustDecodeBig(t.prestate[from].Balance)
+ gasPrice := env.TxContext.GasPrice
+ consumedGas := new(big.Int).Mul(gasPrice, new(big.Int).SetUint64(t.gasLimit))
+ fromBal.Add(fromBal, new(big.Int).Add(value, consumedGas))
+ t.prestate[from].Balance = hexutil.EncodeBig(fromBal)
+ t.prestate[from].Nonce--
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *prestateTracer) CaptureEnd(output []byte, gasUsed uint64, _ time.Duration, err error) {
+ if t.create {
+ // Exclude created contract.
+ delete(t.prestate, t.to)
+ }
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *prestateTracer) CaptureState(pc uint64, op vm.OpCode, gas, cost uint64, scope *vm.ScopeContext, rData []byte, depth int, err error) {
+ stack := scope.Stack
+ stackData := stack.Data()
+ stackLen := len(stackData)
+ switch {
+ case stackLen >= 1 && (op == vm.SLOAD || op == vm.SSTORE):
+ slot := common.Hash(stackData[stackLen-1].Bytes32())
+ t.lookupStorage(scope.Contract.Address(), slot)
+ case stackLen >= 1 && (op == vm.EXTCODECOPY || op == vm.EXTCODEHASH || op == vm.EXTCODESIZE || op == vm.BALANCE || op == vm.SELFDESTRUCT):
+ addr := common.Address(stackData[stackLen-1].Bytes20())
+ t.lookupAccount(addr)
+ case stackLen >= 5 && (op == vm.DELEGATECALL || op == vm.CALL || op == vm.STATICCALL || op == vm.CALLCODE):
+ addr := common.Address(stackData[stackLen-2].Bytes20())
+ t.lookupAccount(addr)
+ case op == vm.CREATE:
+ addr := scope.Contract.Address()
+ nonce := t.env.StateDB.GetNonce(addr)
+ t.lookupAccount(crypto.CreateAddress(addr, nonce))
+ case stackLen >= 4 && op == vm.CREATE2:
+ offset := stackData[stackLen-2]
+ size := stackData[stackLen-3]
+ init := scope.Memory.GetCopy(int64(offset.Uint64()), int64(size.Uint64()))
+ inithash := crypto.Keccak256(init)
+ salt := stackData[stackLen-4]
+ t.lookupAccount(crypto.CreateAddress2(scope.Contract.Address(), salt.Bytes32(), inithash))
+ }
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *prestateTracer) CaptureFault(pc uint64, op vm.OpCode, gas, cost uint64, _ *vm.ScopeContext, depth int, err error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *prestateTracer) CaptureEnter(typ vm.OpCode, from common.Address, to common.Address, input []byte, gas uint64, value *big.Int) {
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *prestateTracer) CaptureExit(output []byte, gasUsed uint64, err error) {
+}
+
+func (t *prestateTracer) CaptureTxStart(gasLimit uint64) {
+ t.gasLimit = gasLimit
+}
+
+func (t *prestateTracer) CaptureTxEnd(restGas uint64) {}
+
+// GetResult returns the json-encoded nested list of call traces, and any
+// error arising from the encoding or forceful termination (via `Stop`).
+func (t *prestateTracer) GetResult() (json.RawMessage, error) {
+ res, err := json.Marshal(t.prestate)
+ if err != nil {
+ return nil, err
+ }
+ return json.RawMessage(res), t.reason
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *prestateTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
+
+// lookupAccount fetches details of an account and adds it to the prestate
+// if it doesn't exist there.
+func (t *prestateTracer) lookupAccount(addr common.Address) {
+ if _, ok := t.prestate[addr]; ok {
+ return
+ }
+ t.prestate[addr] = &account{
+ Balance: bigToHex(t.env.StateDB.GetBalance(addr)),
+ Nonce: t.env.StateDB.GetNonce(addr),
+ Code: bytesToHex(t.env.StateDB.GetCode(addr)),
+ Storage: make(map[common.Hash]common.Hash),
+ }
+}
+
+// lookupStorage fetches the requested storage slot and adds
+// it to the prestate of the given contract. It assumes `lookupAccount`
+// has been performed on the contract before.
+func (t *prestateTracer) lookupStorage(addr common.Address, key common.Hash) {
+ if _, ok := t.prestate[addr].Storage[key]; ok {
+ return
+ }
+ t.prestate[addr].Storage[key] = t.env.StateDB.GetState(addr, key)
+}
diff --git a/x/evm/tracers/native/revertreason.go b/x/evm/tracers/native/revertreason.go
new file mode 100644
index 00000000..d09b8610
--- /dev/null
+++ b/x/evm/tracers/native/revertreason.go
@@ -0,0 +1,108 @@
+// Copyright 2022 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+package native
+
+import (
+ "bytes"
+ "encoding/json"
+ "math/big"
+ "sync/atomic"
+ "time"
+
+ "github.com/ethereum/go-ethereum/accounts/abi"
+ "github.com/ethereum/go-ethereum/common"
+ "github.com/ethereum/go-ethereum/core/vm"
+ "github.com/ethereum/go-ethereum/crypto"
+ "github.com/ethereum/go-ethereum/eth/tracers"
+)
+
+func init() {
+ register("revertReasonTracer", newRevertReasonTracer)
+}
+
+var revertSelector = crypto.Keccak256([]byte("Error(string)"))[:4]
+
+// revertReasonTracer is a go implementation of the Tracer interface which
+// track the error message or revert reason return by the contract.
+type revertReasonTracer struct {
+ env *vm.EVM
+ revertReason string // The revert reason return from the tx, if tx success, empty string return
+ interrupt uint32 // Atomic flag to signal execution interruption
+ reason error // Textual reason for the interruption
+}
+
+// newRevertReasonTracer returns a new revert reason tracer.
+func newRevertReasonTracer(_ *tracers.Context, _ json.RawMessage) (tracers.Tracer, error) {
+ return &revertReasonTracer{}, nil
+}
+
+// CaptureStart implements the EVMLogger interface to initialize the tracing operation.
+func (t *revertReasonTracer) CaptureStart(env *vm.EVM, _ common.Address, _ common.Address, _ bool, _ []byte, _ uint64, _ *big.Int) {
+ t.env = env
+}
+
+// CaptureEnd is called after the call finishes to finalize the tracing.
+func (t *revertReasonTracer) CaptureEnd(output []byte, _ uint64, _ time.Duration, err error) {
+ if err != nil {
+ if err == vm.ErrExecutionReverted && len(output) > 4 && bytes.Equal(output[:4], revertSelector) {
+ errMsg, _ := abi.UnpackRevert(output)
+ t.revertReason = err.Error() + ": " + errMsg
+ } else {
+ t.revertReason = err.Error()
+ }
+ }
+}
+
+// CaptureState implements the EVMLogger interface to trace a single step of VM execution.
+func (t *revertReasonTracer) CaptureState(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ []byte, _ int, _ error) {
+}
+
+// CaptureFault implements the EVMLogger interface to trace an execution fault.
+func (t *revertReasonTracer) CaptureFault(_ uint64, _ vm.OpCode, _, _ uint64, _ *vm.ScopeContext, _ int, _ error) {
+}
+
+// CaptureEnter is called when EVM enters a new scope (via call, create or selfdestruct).
+func (t *revertReasonTracer) CaptureEnter(_ vm.OpCode, _ common.Address, _ common.Address, _ []byte, _ uint64, _ *big.Int) {
+ // Skip if tracing was interrupted
+ if atomic.LoadUint32(&t.interrupt) > 0 {
+ t.env.Cancel()
+ return
+ }
+}
+
+// CaptureExit is called when EVM exits a scope, even if the scope didn't
+// execute any code.
+func (t *revertReasonTracer) CaptureExit(_ []byte, _ uint64, _ error) {}
+
+func (t *revertReasonTracer) CaptureTxStart(_ uint64) {}
+
+func (t *revertReasonTracer) CaptureTxEnd(_ uint64) {}
+
+// GetResult returns an error message json object.
+func (t *revertReasonTracer) GetResult() (json.RawMessage, error) {
+ res, err := json.Marshal(t.revertReason)
+ if err != nil {
+ return nil, err
+ }
+ return res, t.reason
+}
+
+// Stop terminates execution of the tracer at the first opportune moment.
+func (t *revertReasonTracer) Stop(err error) {
+ t.reason = err
+ atomic.StoreUint32(&t.interrupt, 1)
+}
diff --git a/x/evm/tracers/native/tracer.go b/x/evm/tracers/native/tracer.go
new file mode 100644
index 00000000..fda3e659
--- /dev/null
+++ b/x/evm/tracers/native/tracer.go
@@ -0,0 +1,83 @@
+// Copyright 2021 The go-ethereum Authors
+// This file is part of the go-ethereum library.
+//
+// The go-ethereum library is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Lesser General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// The go-ethereum library is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Lesser General Public License
+// along with the go-ethereum library. If not, see .
+
+/*
+Package native is a collection of tracers written in go.
+
+In order to add a native tracer and have it compiled into the binary, a new
+file needs to be added to this folder, containing an implementation of the
+`eth.tracers.Tracer` interface.
+
+Aside from implementing the tracer, it also needs to register itself, using the
+`register` method -- and this needs to be done in the package initialization.
+
+Example:
+
+```golang
+func init() {
+ register("noopTracerNative", newNoopTracer)
+}
+```
+*/
+package native
+
+import (
+ "encoding/json"
+ "errors"
+
+ "github.com/ethereum/go-ethereum/eth/tracers"
+)
+
+// InitTracer retrieves the Go transaction tracers included in go-ethereum.
+func InitTracer() {
+ tracers.RegisterLookup(false, lookup)
+}
+
+// ctorFn is the constructor signature of a native tracer.
+type ctorFn = func(*tracers.Context, json.RawMessage) (tracers.Tracer, error)
+
+/*
+ctors is a map of package-local tracer constructors.
+
+We cannot be certain about the order of init-functions within a package,
+The go spec (https://golang.org/ref/spec#Package_initialization) says
+
+> To ensure reproducible initialization behavior, build systems
+> are encouraged to present multiple files belonging to the same
+> package in lexical file name order to a compiler.
+
+Hence, we cannot make the map in init, but must make it upon first use.
+*/
+var ctors map[string]ctorFn
+
+// register is used by native tracers to register their presence.
+func register(name string, ctor ctorFn) {
+ if ctors == nil {
+ ctors = make(map[string]ctorFn)
+ }
+ ctors[name] = ctor
+}
+
+// lookup returns a tracer, if one can be matched to the given name.
+func lookup(name string, ctx *tracers.Context, cfg json.RawMessage) (tracers.Tracer, error) {
+ if ctors == nil {
+ ctors = make(map[string]ctorFn)
+ }
+ if ctor, ok := ctors[name]; ok {
+ return ctor(ctx, cfg)
+ }
+ return nil, errors.New("no tracer found")
+}