Merge pull request #488 from cosmos/bez/486-statedb-refactor

R4R: StateDB Integration/EVM Module Start
This commit is contained in:
Alexander Bezobchuk 2018-10-24 16:43:50 -04:00 committed by GitHub
commit abbe17db25
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2108 additions and 1999 deletions

View File

@ -15,4 +15,4 @@ jobs:
- run:
name: "Run tests"
command: make test-lint test-unit
command: make test-lint test-unit test-import

352
Gopkg.lock generated
View File

@ -3,19 +3,27 @@
[[projects]]
branch = "master"
digest = "1:60d3b49df18861c92ac49cce49e49f61b3ec927e5b7f39c5ae1128ec5c197b98"
digest = "1:495c7006c2f48b705f0d89fd8449a2ae70622bb748788d9d17caafa65a6769f9"
name = "github.com/aristanetworks/goarista"
packages = ["monotime"]
pruneopts = "T"
revision = "fb622b9b46608fdb39d36447f4d8ef52fe37fc3d"
revision = "33151c4543a79b013e8e6799ef45b2ba88c3cd1c"
[[projects]]
branch = "master"
digest = "1:b9f5e0f033febe59a62d01e78486c0dd9e4afc9ac5d240aee6ce78a927142e8b"
digest = "1:ad4589ec239820ee99eb01c1ad47ebc5f8e02c4f5103a9b210adff9696d89f36"
name = "github.com/beorn7/perks"
packages = ["quantile"]
pruneopts = "T"
revision = "3a771d992973f24aa725d07868b467d1ddfceafb"
[[projects]]
branch = "master"
digest = "1:0bd9f11575e82b723837f50e170d010ec29a50aa8ca02a962c439146f03aea55"
name = "github.com/btcsuite/btcd"
packages = ["btcec"]
pruneopts = "T"
revision = "79e00513b1011888b1e675157ab89f527f901cae"
revision = "67e573d211ace594f1366b4ce9d39726c4b19bd0"
[[projects]]
digest = "1:d0d998526cfb68788229a31c16a557fdf1fbbb510654be6b3732c2758e06b533"
@ -25,29 +33,30 @@
revision = "d4cc87b860166d00d6b5b9e0d3b3d71d6088d4d4"
[[projects]]
digest = "1:bc28e755cf6a9fd8e65497514d20c4907973e7a6a6409d30ead3fd37bfeb19a9"
digest = "1:912c494215c339688331953ba09cb9698a4797fe647d93a4e0a117c9c7b960a2"
name = "github.com/cosmos/cosmos-sdk"
packages = [
"baseapp",
"codec",
"store",
"types",
"version",
"wire",
"x/auth",
"x/bank",
"x/gov",
"x/gov/tags",
"x/mock",
"x/params",
"x/params/subspace",
"x/slashing",
"x/stake",
"x/stake/keeper",
"x/stake/querier",
"x/stake/tags",
"x/stake/types",
]
pruneopts = "T"
revision = "1c38c70468ec721e3a555ba2f3bf5f9da31f0cc9"
version = "v0.24.2"
revision = "075ddce79acb77fe88f849f93fb3036e48ffb555"
[[projects]]
digest = "1:9f42202ac457c462ad8bb9642806d275af9ab4850cf0b1960b9c6f083d4a309a"
@ -65,6 +74,13 @@
revision = "cbaa98ba5575e67703b32b4b19f73c91f3c4159e"
version = "v1.7.1"
[[projects]]
digest = "1:c7644c73a3d23741fdba8a99b1464e021a224b7e205be497271a8003a15ca41b"
name = "github.com/ebuchman/fail-test"
packages = ["."]
pruneopts = "T"
revision = "95f809107225be108efcf10a3509e4ea6ceef3c4"
[[projects]]
branch = "master"
digest = "1:67d0b50be0549e610017cb91e0b0b745ec0cad7c613bc8e18ff2d1c1fc8825a7"
@ -74,7 +90,8 @@
revision = "0bce6a6887123b67a60366d2c9fe2dfb74289d2e"
[[projects]]
digest = "1:c24d17ef5d37ae7215811cf1cade45822faa232d6bcfbadff30bbeba52225a98"
branch = "ethermint-statedb"
digest = "1:00d8053ec8370727a6ecb12cdbfc0b3ce08bce2f8ac2aa34d57402ff09243517"
name = "github.com/ethereum/go-ethereum"
packages = [
".",
@ -122,8 +139,16 @@
"trie",
]
pruneopts = "T"
revision = "477eb0933b9529f7deeccc233cc815fe34a8ea56"
version = "v1.8.16"
revision = "0a57b29f0c8e6dc27901fae1c91d3758723b81eb"
source = "github.com/alexanderbez/go-ethereum"
[[projects]]
digest = "1:7fc160b460a6fc506b37fcca68332464c3f2cd57b6e3f111f26c5bbfd2d5518e"
name = "github.com/fsnotify/fsnotify"
packages = ["."]
pruneopts = "T"
revision = "c2828203cd70a50dcccfb2761f8b1f8ceef9a8e9"
version = "v1.4.7"
[[projects]]
digest = "1:0b9c3ad6c948d57a379da9c4e1cdd989b1c73ddc5ec8673f52a9539ce60a109b"
@ -132,6 +157,10 @@
"log",
"log/level",
"log/term",
"metrics",
"metrics/discard",
"metrics/internal/lv",
"metrics/prometheus",
]
pruneopts = "T"
revision = "4dc7be5d2d12881735283bcab7352178e190fc71"
@ -192,18 +221,52 @@
revision = "2e65f85255dbc3072edf28d6b5b8efc472979f5a"
[[projects]]
digest = "1:cf296baa185baae04a9a7004efee8511d08e2f5f51d4cbe5375da89722d681db"
digest = "1:3a26588bc48b96825977c1b3df964f8fd842cd6860cc26370588d3563433cf11"
name = "github.com/google/uuid"
packages = ["."]
pruneopts = "T"
revision = "d460ce9f8df2e77fb1ba55ca87fafed96c607494"
version = "v1.0.0"
[[projects]]
digest = "1:0ead695774eaa7bf1a284d246febe82054767941de80ab2328a194b088f07026"
name = "github.com/gorilla/websocket"
packages = ["."]
pruneopts = "T"
revision = "ea4d1f681babbce9545c9c5f3d5194a789c89f5b"
version = "v1.2.0"
[[projects]]
digest = "1:8ec8d88c248041a6df5f6574b87bc00e7e0b493881dad2e7ef47b11dc69093b5"
name = "github.com/hashicorp/golang-lru"
packages = [
".",
"simplelru",
]
pruneopts = "T"
revision = "0fb14efe8c47ae851c0034ed7a448854d3d34cf3"
revision = "20f1fb78b0740ba8c3cb143a61e86ba5c8669768"
version = "v0.5.0"
[[projects]]
branch = "master"
digest = "1:202e4a1a283dd740ca9d131787e73bb9d69611a01ef86e82ed262e035b0dd792"
digest = "1:071bcbf82c289fba4d3f63c876bf4f0ba7eda625cd60795e0a03ccbf949e517a"
name = "github.com/hashicorp/hcl"
packages = [
".",
"hcl/ast",
"hcl/parser",
"hcl/scanner",
"hcl/strconv",
"hcl/token",
"json/parser",
"json/scanner",
"json/token",
]
pruneopts = "T"
revision = "8cb6e5b959231cc1119e43259c4a608f9c51a241"
version = "v1.0.0"
[[projects]]
digest = "1:a33cc2e4fb12c58430d2aae5834ff6e84cb609da97692e1fe2aa0cd5ebc92623"
name = "github.com/huin/goupnp"
packages = [
".",
@ -215,7 +278,8 @@
"ssdp",
]
pruneopts = "T"
revision = "1395d1447324cbea88d249fbfcfd70ea878fdfca"
revision = "656e61dfadd241c7cbdd22a023fa81ecb6860ea8"
version = "v1.0.0"
[[projects]]
digest = "1:870d441fe217b8e689d7949fef6e43efbc787e50f200cb1e70dbca9204a1d6be"
@ -258,12 +322,44 @@
revision = "b84e30acd515aadc4b783ad4ff83aff3299bdfe0"
[[projects]]
digest = "1:361de06aa7ae272616cbe71c3994a654cc6316324e30998e650f7765b20c5b33"
digest = "1:53e8c5c79716437e601696140e8b1801aae4204f4ec54a504333702a49572c4f"
name = "github.com/magiconair/properties"
packages = ["."]
pruneopts = "T"
revision = "c2353362d570a7bfa228149c62842019201cfb71"
version = "v1.8.0"
[[projects]]
digest = "1:a8e3d14801bed585908d130ebfc3b925ba642208e6f30d879437ddfc7bb9b413"
name = "github.com/matttproud/golang_protobuf_extensions"
packages = ["pbutil"]
pruneopts = "T"
revision = "c12348ce28de40eed0136aa2b644d0ee0650e56c"
version = "v1.0.1"
[[projects]]
digest = "1:53bc4cd4914cd7cd52139990d5170d6dc99067ae31c56530621b18b35fc30318"
name = "github.com/mitchellh/mapstructure"
packages = ["."]
pruneopts = "T"
revision = "3536a929edddb9a5b34bd6861dc4a9647cb459fe"
version = "v1.1.2"
[[projects]]
digest = "1:e5d0bd87abc2781d14e274807a470acd180f0499f8bf5bb18606e9ec22ad9de9"
name = "github.com/pborman/uuid"
packages = ["."]
pruneopts = "T"
revision = "e790cca94e6cc75c7064b1332e63811d4aae1a53"
version = "v1.1"
revision = "adf5a7427709b9deb95d29d3fa8a2bf9cfd388f1"
version = "v1.2"
[[projects]]
digest = "1:ccf9949c9c53e85dcb7e2905fc620571422567040925381e6baa62f0b7b850fe"
name = "github.com/pelletier/go-toml"
packages = ["."]
pruneopts = "T"
revision = "c01d1270ff3e442a8a57cddc1c92dc1138598194"
version = "v1.2.0"
[[projects]]
digest = "1:40e195917a951a8bf867cd05de2a46aaf1806c50cf92eebf4c16f78cd196f747"
@ -282,36 +378,121 @@
version = "v1.0.0"
[[projects]]
digest = "1:602081d2a289d1f76ea90b806b0c61c19038d76504e9005ccb969864dbaee339"
digest = "1:f4f3858737fd9db5cf3ef8019c918a798a987d4d11f7e531c54dfe70d4708642"
name = "github.com/prometheus/client_golang"
packages = [
"prometheus",
"prometheus/promhttp",
]
pruneopts = "T"
revision = "ae27198cdd90bf12cd134ad79d1366a6cf49f632"
[[projects]]
branch = "master"
digest = "1:185cf55b1f44a1bf243558901c3f06efa5c64ba62cfdcbb1bf7bbe8c3fb68561"
name = "github.com/prometheus/client_model"
packages = ["go"]
pruneopts = "T"
revision = "5c3871d89910bfb32f5fcab2aa4b9ec68e65a99f"
[[projects]]
branch = "master"
digest = "1:2d9b03513fadf4adf193b3570f5ef65ee57b658d9f11e901a06d17baf2bdc88b"
name = "github.com/prometheus/common"
packages = [
"expfmt",
"internal/bitbucket.org/ww/goautoneg",
"model",
]
pruneopts = "T"
revision = "7e9e6cabbd393fc208072eedef99188d0ce788b6"
[[projects]]
branch = "master"
digest = "1:57bf59ce0c73cef5cc4796a5d64f1ec5b81f6335f242d4a80a62b0a6edc4b77f"
name = "github.com/prometheus/procfs"
packages = [
".",
"internal/util",
"nfs",
"xfs",
]
pruneopts = "T"
revision = "185b4288413d2a0dd0806f78c90dde719829e5ae"
[[projects]]
digest = "1:523d2c2500965d035691347a6d30befd53fde95fad16e0b94bef5d3d2cca8ff7"
name = "github.com/rcrowley/go-metrics"
packages = ["."]
pruneopts = "T"
revision = "e2704e165165ec55d062f5919b4b29494e9fa790"
[[projects]]
digest = "1:9787d2d3220cbfd444596afd03ab0abcf391df169b789fbe3eae27fa2e426cf6"
name = "github.com/rjeczalik/notify"
packages = ["."]
pruneopts = "T"
revision = "0f065fa99b48b842c3fd3e2c8b194c6f2b69f6b8"
version = "v0.9.1"
revision = "69d839f37b13a8cb7a78366f7633a4071cb43be7"
version = "v0.9.2"
[[projects]]
digest = "1:6cae6970d70fc5fe75bf83c48ee33e9c4c561a62d0b033254bee8dd5942b815a"
digest = "1:a8a03bca5a81878daa4958136f3372af00437c61129ca088a430b0b786b9378a"
name = "github.com/rs/cors"
packages = ["."]
pruneopts = "T"
revision = "3fb1b69b103a84de38a19c3c6ec073dd6caa4d3f"
version = "v1.5.0"
revision = "9a47f48565a795472d43519dd49aac781f3034fb"
version = "v1.6.0"
[[projects]]
digest = "1:8be8b3743fc9795ec21bbd3e0fc28ff6234018e1a269b0a7064184be95ac13e0"
digest = "1:b7bf9fd95d38ebe6726a63b7d0320611f7c920c64e2c8313eba0cec51926bf55"
name = "github.com/spf13/afero"
packages = [
".",
"mem",
]
pruneopts = "T"
revision = "d40851caa0d747393da1ffb28f7f9d8b4eeffebd"
version = "v1.1.2"
[[projects]]
digest = "1:516e71bed754268937f57d4ecb190e01958452336fa73dbac880894164e91c1f"
name = "github.com/spf13/cast"
packages = ["."]
pruneopts = "T"
revision = "8965335b8c7107321228e3e3702cab9832751bac"
version = "v1.2.0"
[[projects]]
digest = "1:52565bd966162d1f4579757f66ce6a7ca9054e7f6b662f0c7c96e4dd228fd017"
name = "github.com/spf13/cobra"
packages = ["."]
pruneopts = "T"
revision = "ef82de70bb3f60c65fb8eebacbb2d122ef517385"
version = "v0.0.3"
revision = "7b2c5ac9fc04fc5efafb60700713d4fa609b777b"
version = "v0.0.1"
[[projects]]
digest = "1:9ba911fe3884995431690e7eb180cf848da0d637ba5f61711783b795d031793f"
digest = "1:68ea4e23713989dc20b1bded5d9da2c5f9be14ff9885beef481848edd18c26cb"
name = "github.com/spf13/jwalterweatherman"
packages = ["."]
pruneopts = "T"
revision = "4a4406e478ca629068e7768fc33f3f044173c0a6"
version = "v1.0.0"
[[projects]]
digest = "1:0f775ea7a72e30d5574267692aaa9ff265aafd15214a7ae7db26bc77f2ca04dc"
name = "github.com/spf13/pflag"
packages = ["."]
pruneopts = "T"
revision = "9a97c102cda95a86cec2345a6f09f55a939babf5"
version = "v1.0.2"
revision = "298182f68c66c05229eb03ac171abe6e309ee79a"
version = "v1.0.3"
[[projects]]
digest = "1:a8a1cbf83d6ba47a3421e51b5dd1999e1f64f6175c64295d6b42bdea55312a79"
name = "github.com/spf13/viper"
packages = ["."]
pruneopts = "T"
revision = "25b30aa063fc18e48662b86996252eabdcf2f0c7"
version = "v1.0.0"
[[projects]]
digest = "1:8f39978e4fb2a11d43cc954f2ab458cb38995d4c1557b6d3a7c8cafe0ec2277c"
@ -327,7 +508,7 @@
[[projects]]
branch = "master"
digest = "1:ee395d0d8c1719b5a1407f34af93953b4763bacb19a8961aba5b6d312824da41"
digest = "1:ea4a45f31f55c7a42ba3063baa646ac94eb7ee9afe60c1fd2c8b396930222620"
name = "github.com/syndtr/goleveldb"
packages = [
"leveldb",
@ -344,7 +525,14 @@
"leveldb/util",
]
pruneopts = "T"
revision = "ae2bd5eed72d46b28834ec3f60db3a3ebedd8dbd"
revision = "6b91fda63f2e36186f1c9d0e48578defb69c5d43"
[[projects]]
digest = "1:71ffd1fca92b4972ecd588cf13d9929d4f444659788e9128d055a9126498d41d"
name = "github.com/tendermint/btcd"
packages = ["btcec"]
pruneopts = "T"
revision = "e5840949ff4fff0c56f9b6a541e22b63581ea9df"
[[projects]]
branch = "master"
@ -359,57 +547,110 @@
revision = "d8387025d2b9d158cf4efb07e7ebf814bcce2057"
[[projects]]
digest = "1:0e2addab3f64ece97ca434b2bf2d4e8cb54a4509904a03be8c81da3fc2ddb245"
digest = "1:25c97d29878b5f821bb17a379469f0f923426188241bf2aa81c18728cdc6927c"
name = "github.com/tendermint/go-amino"
packages = ["."]
pruneopts = "T"
revision = "2106ca61d91029c931fd54968c2bb02dc96b1412"
version = "0.10.1"
revision = "faa6e731944e2b7b6a46ad202902851e8ce85bee"
version = "v0.12.0"
[[projects]]
digest = "1:bf042d2f7d1252b9dcae8e694e2f0a9b5294cb357c086fd86dc540d2f32c9fdf"
digest = "1:2ecd824e1615a8becefea26637fe24576f3800260f5dc91ffe44b37bdbd27878"
name = "github.com/tendermint/iavl"
packages = ["."]
pruneopts = "T"
revision = "35f66e53d9b01e83b30de68b931f54b2477a94c9"
version = "v0.9.2"
revision = "3acc91fb8811db2c5409a855ae1f8e441fe98e2d"
version = "v0.11.0"
[[projects]]
digest = "1:5a60cb048b401c0263c227baf8778ecaf038be531707057607949540486874ef"
digest = "1:b8bd45120cbea639592420b1d5363f102d819ea89d6239f4dae2a0814c76a6d2"
name = "github.com/tendermint/tendermint"
packages = [
"abci/client",
"abci/example/code",
"abci/example/kvstore",
"abci/server",
"abci/types",
"blockchain",
"cmd/tendermint/commands",
"config",
"consensus",
"consensus/types",
"crypto",
"crypto/ed25519",
"crypto/encoding/amino",
"crypto/merkle",
"crypto/multisig",
"crypto/multisig/bitarray",
"crypto/secp256k1",
"crypto/tmhash",
"evidence",
"libs/autofile",
"libs/bech32",
"libs/cli",
"libs/cli/flags",
"libs/clist",
"libs/common",
"libs/db",
"libs/errors",
"libs/events",
"libs/flowrate",
"libs/log",
"libs/pubsub",
"libs/pubsub/query",
"lite",
"lite/client",
"lite/errors",
"lite/proxy",
"mempool",
"node",
"p2p",
"p2p/conn",
"p2p/pex",
"p2p/upnp",
"privval",
"proxy",
"rpc/client",
"rpc/core",
"rpc/core/types",
"rpc/grpc",
"rpc/lib",
"rpc/lib/client",
"rpc/lib/server",
"rpc/lib/types",
"state",
"state/txindex",
"state/txindex/kv",
"state/txindex/null",
"types",
"types/time",
"version",
]
pruneopts = "T"
revision = "81df19e68ab1519399fccf0cab81cb75bf9d782e"
version = "v0.23.1-rc0"
revision = "90eda9bfb6e6daeed1c8015df41cb36772d91778"
version = "v0.25.1-rc0"
[[projects]]
branch = "master"
digest = "1:da29cbeb9d244918393b37243c008ab7128688fb017c966aaf876587c010bcdd"
digest = "1:56a43b9f51e5c5ea734e866b82d57c842b022c795a0611ff5f57f3d7c47de45d"
name = "golang.org/x/crypto"
packages = [
"chacha20poly1305",
"curve25519",
"hkdf",
"internal/chacha20",
"internal/subtle",
"nacl/box",
"nacl/secretbox",
"pbkdf2",
"poly1305",
"ripemd160",
"salsa20/salsa",
"scrypt",
"ssh/terminal",
]
pruneopts = "T"
revision = "182538f80094b6a8efaade63a8fd8e0d9d5843dd"
revision = "0c41d7ab0a0ee717d4590a44bcb987dfd9e183eb"
[[projects]]
digest = "1:5fdc7adede42f80d6201258355d478d856778e21d735f14972abd8ff793fdbf7"
@ -424,6 +665,7 @@
"http2/hpack",
"idna",
"internal/timeseries",
"netutil",
"trace",
"websocket",
]
@ -432,7 +674,7 @@
[[projects]]
branch = "master"
digest = "1:bfa444982d49ce4ca1360599270a94de12a573ccd3bf04493c79bee09da3170b"
digest = "1:d6d9bf31934efd450aeb0a07061dce8a0e84bc80e4aae05841dd588325832a40"
name = "golang.org/x/sys"
packages = [
"cpu",
@ -440,7 +682,7 @@
"windows",
]
pruneopts = "T"
revision = "fa5fdf94c78965f1aa8423f0cc50b8b8d728b05a"
revision = "5cd93ef61a7c8f0f858690154eb6de2e69415fa1"
[[projects]]
digest = "1:6164911cb5e94e8d8d5131d646613ff82c14f5a8ce869de2f6d80d9889df8c5a"
@ -479,11 +721,11 @@
[[projects]]
branch = "master"
digest = "1:e43f1cb3f488a0c2be85939c2a594636f60b442a12a196c778bd2d6c9aca3df7"
digest = "1:849525811c9f6ae1f5bd9b866adb4c9436f4a12d767f48e33bf343596d4aafd7"
name = "google.golang.org/genproto"
packages = ["googleapis/rpc/status"]
pruneopts = "T"
revision = "11092d34479b07829b72e10713b159248caf5dad"
revision = "94acd270e44e65579b9ee3cdab25034d33fed608"
[[projects]]
digest = "1:adafc60b1d4688759f3fc8f9089e71dd17abd123f4729de6b913bf08c9143770"
@ -527,14 +769,22 @@
pruneopts = "T"
revision = "c1b8fa8bdccecb0b8db834ee0b92fdbcfa606dd6"
[[projects]]
digest = "1:342378ac4dcb378a5448dd723f0784ae519383532f5e70ade24132c4c8693202"
name = "gopkg.in/yaml.v2"
packages = ["."]
pruneopts = "T"
revision = "5420a8b6744d3b0345ab293f6fcba19c978f1183"
version = "v2.2.1"
[solve-meta]
analyzer-name = "dep"
analyzer-version = 1
input-imports = [
"github.com/cosmos/cosmos-sdk/baseapp",
"github.com/cosmos/cosmos-sdk/codec",
"github.com/cosmos/cosmos-sdk/store",
"github.com/cosmos/cosmos-sdk/types",
"github.com/cosmos/cosmos-sdk/wire",
"github.com/cosmos/cosmos-sdk/x/auth",
"github.com/cosmos/cosmos-sdk/x/bank",
"github.com/cosmos/cosmos-sdk/x/gov",
@ -543,7 +793,6 @@
"github.com/cosmos/cosmos-sdk/x/stake",
"github.com/ethereum/go-ethereum/common",
"github.com/ethereum/go-ethereum/common/hexutil",
"github.com/ethereum/go-ethereum/common/math",
"github.com/ethereum/go-ethereum/consensus",
"github.com/ethereum/go-ethereum/consensus/ethash",
"github.com/ethereum/go-ethereum/consensus/misc",
@ -553,13 +802,10 @@
"github.com/ethereum/go-ethereum/core/vm",
"github.com/ethereum/go-ethereum/crypto",
"github.com/ethereum/go-ethereum/crypto/sha3",
"github.com/ethereum/go-ethereum/ethdb",
"github.com/ethereum/go-ethereum/params",
"github.com/ethereum/go-ethereum/rlp",
"github.com/ethereum/go-ethereum/rpc",
"github.com/ethereum/go-ethereum/signer/core",
"github.com/ethereum/go-ethereum/trie",
"github.com/hashicorp/golang-lru",
"github.com/pkg/errors",
"github.com/stretchr/testify/require",
"github.com/stretchr/testify/suite",

View File

@ -1,10 +1,15 @@
[[constraint]]
name = "github.com/ethereum/go-ethereum"
version = "=1.8.16"
# TODO: Remove this forked source and branch once Turbo-Geth is ready as a
# client.
source = "github.com/alexanderbez/go-ethereum"
branch = "ethermint-statedb"
[[constraint]]
name = "github.com/cosmos/cosmos-sdk"
version = "=0.24.2"
# TODO: Remove this once 0.25 has been released
revision = "075ddce79acb77fe88f849f93fb3036e48ffb555"
# version = "=0.24.2"
[[constraint]]
name = "github.com/hashicorp/golang-lru"
@ -14,14 +19,6 @@
name = "github.com/spf13/cobra"
version = "~0.0.1"
[[override]]
name = "github.com/tendermint/iavl"
version = "=v0.9.2"
[[override]]
name = "github.com/tendermint/tendermint"
version = "=v0.23.1-rc0"
[[constraint]]
name = "github.com/stretchr/testify"
version = "=1.2.1"
@ -30,9 +27,23 @@
name = "github.com/pkg/errors"
version = "=0.8.0"
# dependecy overrides
[[override]]
name = "gopkg.in/fatih/set.v0"
version = "=0.1.0"
[[override]]
name = "github.com/tendermint/iavl"
version = "=v0.11.0"
[[override]]
name = "github.com/tendermint/tendermint"
version = "=0.25.1-rc0"
[[override]]
name = "github.com/tendermint/go-amino"
version = "=v0.12.0"
[prune]
go-tests = true

View File

@ -12,7 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
PACKAGES=$(shell go list ./... | grep -v '/vendor/')
PACKAGES=$(shell go list ./... | grep -Ev 'vendor|importer')
COMMIT_HASH := $(shell git rev-parse --short HEAD)
BUILD_FLAGS = -tags netgo -ldflags "-X github.com/cosmos/ethermint/version.GitCommit=${COMMIT_HASH}"
DOCKER_TAG = unstable
@ -147,6 +147,10 @@ test-lint:
@echo "--> Running gometalinter..."
@gometalinter.v2 --config=gometalinter.json --exclude=vendor ./...
test-import:
@go test ./importer -v --vet=off --run=TestImportBlocks --datadir tmp \
--blockchain blockchain --timeout=5m
godocs:
@echo "--> Wait a few seconds and visit http://localhost:6060/pkg/github.com/cosmos/ethermint"
godoc -http=:6060
@ -162,4 +166,4 @@ format:
@find . -name '*.go' -type f -not -path "./vendor*" -not -path "*.git*" | xargs misspell -w
.PHONY: build install update-tools tools deps godocs clean format test-lint \
test-cli test-race test-unit test
test-cli test-race test-unit test test-import

View File

@ -1,28 +1,32 @@
[![CircleCI](https://circleci.com/gh/cosmos/ethermint.svg?style=svg)](https://circleci.com/gh/cosmos/ethermint)
[![](https://godoc.org/github.com/cosmos/ethermint?status.svg)](http://godoc.org/github.com/cosmos/ethermint) [![Go Report Card](https://goreportcard.com/badge/github.com/cosmos/ethermint)](https://goreportcard.com/report/github.com/cosmos/ethermint)
# Ethermint
__**WARNING:**__ Ethermint is under VERY ACTIVE DEVELOPMENT and should be treated as pre-alpha software. This means it is not meant to be run in production, its APIs are subject to change without warning and should not be relied upon, and it should not be used to hold any value. We will remove this warning when we have a release that is stable, secure, and properly tested.
### What is it?
## What is it?
`ethermint` will be an implementation of the EVM that runs on top of [`tendermint`](https://github.com/tendermint/tendermint) consensus, a Proof of Stake system. This project has as its primary goals:
- [Hard Spoon](https://blog.cosmos.network/introducing-the-hard-spoon-4a9288d3f0df) enablement: This is the ability to take a token from the Ethereum mainnet and "spoon" (shift) the balances over to another network. This feature is intended to make it easy for applications that require more transactions than the Ethereum main chain can provide to move their code over to a compatible chain with much more capacity.
- Web3 Compatibility: In order enable applications to be moved over to an ethermint chain existing tooling (i.e. web3 compatable clients) need to be able to interact with `ethermint`.
- Web3 Compatibility: In order enable applications to be moved over to an ethermint chain existing tooling (i.e. web3 compatible clients) need to be able to interact with `ethermint`.
### Implementation
#### Completed
- Have a working implementation that can parse and validate the existing ETH Chain and persist it in a Tendermint store
- Implement Ethereum transactions in the CosmosSDK
#### Current Work
- Implement web3 compatible API layer
- Implement the EVM as a CosmosSDK module
- Allow the Ethermint EVM to interact with other [Cosmos SDK modules](https://github.com/cosmos/cosmos-sdk/blob/master/docs/core/app3.md)
#### Next Steps
- Hard spoon enablement: The ability to export state from `geth` and import token balances into Ethermint
- Ethermint is a functioning Cosmos SDK application and can be deployed as its own zone
- Full web3 compatibility will enable existing Ethereum applications to use Ethermint
@ -39,15 +43,32 @@ $ make tools deps build
$ make tools deps install
```
### Using Ethermint to parse Mainnet Ethereum blocks
### Tests
There is an included Ethereum Mainnet blockchain file in `data/blockchain` that provides an easy way to run the demo of parsing Mainnet Ethereum blocks. The dump in `data/` only includes up to block `97638`. To run this, type the following command:
Integration tests are invoked via:
```bash
$ go run test/run.go
$ make test
```
By default, state will be dumped into `$HOME/.ethermint`. See `--help` for further usage.
To run CLI tests, execute:
```bash
$ make test-cli
```
#### Ethereum Mainnet Import
There is an included Ethereum mainnet exported blockchain file in `importer/blockchain`
that includes blocks up to height `97638`. To execute and test a full import of
these blocks using the EVM module, execute:
```bash
$ make test-import
```
You may also provide a custom blockchain export file to test importing more blocks
via the `--blockchain` flag. See `TestImportBlocks` for further documentation.
### Community

View File

@ -2,9 +2,9 @@ package app
import (
bam "github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/gov"
@ -14,7 +14,6 @@ import (
"github.com/pkg/errors"
"github.com/cosmos/ethermint/handlers"
"github.com/cosmos/ethermint/state"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
@ -36,8 +35,7 @@ type (
EthermintApp struct {
*bam.BaseApp
codec *wire.Codec
ethDB *state.Database
cdc *codec.Codec
accountKey *sdk.KVStoreKey
storageKey *sdk.KVStoreKey
@ -49,7 +47,7 @@ type (
paramsKey *sdk.KVStoreKey
tParamsKey *sdk.TransientStoreKey
accountMapper auth.AccountMapper
accountKeeper auth.AccountKeeper
feeCollKeeper auth.FeeCollectionKeeper
coinKeeper bank.Keeper
stakeKeeper stake.Keeper
@ -62,17 +60,17 @@ type (
// NewEthermintApp returns a reference to a new initialized Ethermint
// application.
func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *EthermintApp {
codec := CreateCodec()
cdc := CreateCodec()
cms := store.NewCommitMultiStore(db)
baseAppOpts := []func(*bam.BaseApp){
func(bApp *bam.BaseApp) { bApp.SetCMS(cms) },
}
baseApp := bam.NewBaseApp(appName, logger, db, types.TxDecoder(codec, sdkAddr), baseAppOpts...)
baseApp := bam.NewBaseApp(appName, logger, db, types.TxDecoder(cdc, sdkAddr), baseAppOpts...)
app := &EthermintApp{
BaseApp: baseApp,
codec: codec,
cdc: cdc,
accountKey: types.StoreKeyAccount,
storageKey: types.StoreKeyStorage,
mainKey: types.StoreKeyMain,
@ -84,30 +82,10 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
tParamsKey: types.StoreKeyTransParams,
}
// create Ethereum state database
ethDB, err := state.NewDatabase(cms, db, state.DefaultStoreCacheSize)
if err != nil {
tmcmn.Exit(err.Error())
}
app.ethDB = ethDB
// set application keepers and mappers
app.accountMapper = auth.NewAccountMapper(codec, app.accountKey, auth.ProtoBaseAccount)
app.coinKeeper = bank.NewKeeper(app.accountMapper)
app.paramsKeeper = params.NewKeeper(app.codec, app.paramsKey)
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.codec, app.feeCollKey)
app.stakeKeeper = stake.NewKeeper(
app.codec, app.stakeKey, app.coinKeeper, app.RegisterCodespace(stake.DefaultCodespace),
)
app.govKeeper = gov.NewKeeper(
app.codec, app.govKey, app.paramsKeeper.Setter(), app.coinKeeper,
app.stakeKeeper, app.RegisterCodespace(gov.DefaultCodespace),
)
app.slashingKeeper = slashing.NewKeeper(
app.codec, app.slashingKey, app.stakeKeeper,
app.paramsKeeper.Getter(), app.RegisterCodespace(slashing.DefaultCodespace),
)
app.accountKeeper = auth.NewAccountKeeper(app.cdc, app.accountKey, auth.ProtoBaseAccount)
app.paramsKeeper = params.NewKeeper(app.cdc, app.paramsKey, app.tParamsKey)
app.feeCollKeeper = auth.NewFeeCollectionKeeper(app.cdc, app.feeCollKey)
// register message handlers
app.Router().
@ -121,7 +99,7 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
app.SetInitChainer(app.initChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetEndBlocker(app.EndBlocker)
app.SetAnteHandler(handlers.AnteHandler(app.accountMapper, app.feeCollKeeper))
app.SetAnteHandler(handlers.AnteHandler(app.accountKeeper, app.feeCollKeeper))
app.MountStoresIAVL(
app.mainKey, app.accountKey, app.stakeKey, app.slashingKey,
@ -140,70 +118,39 @@ func NewEthermintApp(logger tmlog.Logger, db dbm.DB, sdkAddr ethcmn.Address) *Et
// BeginBlocker signals the beginning of a block. It performs application
// updates on the start of every block.
func (app *EthermintApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper)
return abci.ResponseBeginBlock{
Tags: tags.ToKVPairs(),
}
return abci.ResponseBeginBlock{}
}
// EndBlocker signals the end of a block. It performs application updates on
// the end of every block.
func (app *EthermintApp) EndBlocker(ctx sdk.Context, _ abci.RequestEndBlock) abci.ResponseEndBlock {
tags := gov.EndBlocker(ctx, app.govKeeper)
validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper)
app.slashingKeeper.AddValidators(ctx, validatorUpdates)
return abci.ResponseEndBlock{
ValidatorUpdates: validatorUpdates,
Tags: tags,
}
return abci.ResponseEndBlock{}
}
// initChainer initializes the application blockchain with validators and other
// state data from TendermintCore.
func (app *EthermintApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
func (app *EthermintApp) initChainer(_ sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
var genesisState GenesisState
stateJSON := req.AppStateBytes
err := app.codec.UnmarshalJSON(stateJSON, &genesisState)
err := app.cdc.UnmarshalJSON(stateJSON, &genesisState)
if err != nil {
panic(errors.Wrap(err, "failed to parse application genesis state"))
}
// load the genesis accounts
for _, genAcc := range genesisState.Accounts {
acc := genAcc.ToAccount()
acc.AccountNumber = app.accountMapper.GetNextAccountNumber(ctx)
app.accountMapper.SetAccount(ctx, acc)
}
// TODO: load the genesis accounts
// load the genesis stake information
validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData)
if err != nil {
panic(errors.Wrap(err, "failed to initialize genesis validators"))
}
slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.StakeData)
gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData)
return abci.ResponseInitChain{
Validators: validators,
}
return abci.ResponseInitChain{}
}
// CreateCodec creates a new amino wire codec and registers all the necessary
// concrete types and interfaces needed for the application.
func CreateCodec() *wire.Codec {
codec := wire.NewCodec()
func CreateCodec() *codec.Codec {
cdc := codec.New()
types.RegisterWire(codec)
auth.RegisterWire(codec)
gov.RegisterWire(codec)
slashing.RegisterWire(codec)
stake.RegisterWire(codec)
wire.RegisterCrypto(codec)
types.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return codec
return cdc
}

View File

@ -2,9 +2,6 @@ package app
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/gov"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/ethermint/types"
)
@ -12,9 +9,7 @@ type (
// GenesisState defines the application's genesis state. It contains all the
// information required and accounts to initialize the blockchain.
GenesisState struct {
Accounts []GenesisAccount `json:"accounts"`
StakeData stake.GenesisState `json:"stake"`
GovData gov.GenesisState `json:"gov"`
Accounts []GenesisAccount `json:"accounts"`
}
// GenesisAccount defines an account to be initialized in the genesis state.
@ -25,23 +20,3 @@ type (
Storage types.Storage `json:"storage,omitempty"`
}
)
// NewGenesisAccount returns a reference to a new initialized genesis account.
func NewGenesisAccount(acc *types.Account) GenesisAccount {
return GenesisAccount{
Address: acc.GetAddress(),
Coins: acc.GetCoins(),
Code: acc.Code,
Storage: acc.Storage,
}
}
// ToAccount converts a genesis account to an initialized Ethermint account.
func (ga *GenesisAccount) ToAccount() (acc *types.Account) {
base := auth.BaseAccount{
Address: ga.Address,
Coins: ga.Coins.Sort(),
}
return types.NewAccount(base, ga.Code, ga.Storage)
}

View File

@ -1,5 +1,7 @@
package core
// TODO: This functionality and implementation may be deprecated
import (
"math/big"
@ -84,7 +86,7 @@ func (cc *ChainContext) CalcDifficulty(_ ethcons.ChainReader, _ uint64, _ *ethty
//
// TODO: Figure out if this needs to be hooked up to any part of the ABCI?
func (cc *ChainContext) Finalize(
_ ethcons.ChainReader, _ *ethtypes.Header, _ *ethstate.StateDB,
_ ethcons.ChainReader, _ *ethtypes.Header, _ ethstate.StateDB,
_ []*ethtypes.Transaction, _ []*ethtypes.Header, _ []*ethtypes.Receipt,
) (*ethtypes.Block, error) {
return nil, nil

View File

@ -1,66 +0,0 @@
package core
import (
ethdb "github.com/ethereum/go-ethereum/ethdb"
dbm "github.com/tendermint/tendermint/libs/db"
)
// EthereumDB implements Ethereum's ethdb.Database and ethdb.Batch interfaces.
// It will be used to facilitate persistence of codeHash => code mappings.
type EthereumDB struct {
CodeDB dbm.DB
}
// Put implements Ethereum's ethdb.Putter interface. It wraps the database
// write operation supported by both batches and regular databases.
func (edb *EthereumDB) Put(key []byte, value []byte) error {
edb.CodeDB.Set(key, value)
return nil
}
// Get implements Ethereum's ethdb.Database interface. It returns a value for a
// given key.
func (edb *EthereumDB) Get(key []byte) ([]byte, error) {
return edb.CodeDB.Get(key), nil
}
// Has implements Ethereum's ethdb.Database interface. It returns a boolean
// determining if the underlying database has the given key or not.
func (edb *EthereumDB) Has(key []byte) (bool, error) {
return edb.CodeDB.Has(key), nil
}
// Delete implements Ethereum's ethdb.Database interface. It removes a given
// key from the underlying database.
func (edb *EthereumDB) Delete(key []byte) error {
edb.CodeDB.Delete(key)
return nil
}
// Close implements Ethereum's ethdb.Database interface. It closes the
// underlying database.
func (edb *EthereumDB) Close() {
edb.CodeDB.Close()
}
// NewBatch implements Ethereum's ethdb.Database interface. It returns a new
// Batch object used for batch database operations.
func (edb *EthereumDB) NewBatch() ethdb.Batch {
return edb
}
// ValueSize implements Ethereum's ethdb.Database interface. It performs a
// no-op.
func (edb *EthereumDB) ValueSize() int {
return 0
}
// Write implements Ethereum's ethdb.Database interface. It performs a no-op.
func (edb *EthereumDB) Write() error {
return nil
}
// Reset implements Ethereum's ethdb.Database interface. It performs a no-op.
func (edb *EthereumDB) Reset() {
}

View File

@ -1,147 +0,0 @@
package core
// NOTE: A bulk of these unit tests will change and evolve as the context and
// implementation of ChainConext evolves.
import (
"fmt"
"testing"
ethdb "github.com/ethereum/go-ethereum/ethdb"
"github.com/stretchr/testify/require"
dbm "github.com/tendermint/tendermint/libs/db"
)
type (
kvPair struct {
key, value []byte
}
)
func newEthereumDB() *EthereumDB {
memDB := dbm.NewMemDB()
return &EthereumDB{CodeDB: memDB}
}
func TestEthereumDBInterface(t *testing.T) {
require.Implements(t, (*ethdb.Database)(nil), new(EthereumDB))
require.Implements(t, (*ethdb.Batch)(nil), new(EthereumDB))
}
func TestEthereumDBGet(t *testing.T) {
testEDB := newEthereumDB()
testCases := []struct {
edb *EthereumDB
data *kvPair
key []byte
expectedValue []byte
}{
{
edb: testEDB,
key: []byte("foo"),
expectedValue: nil,
},
{
edb: testEDB,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
expectedValue: []byte("bar"),
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.edb.Put(tc.data.key, tc.data.value)
}
value, err := tc.edb.Get(tc.key)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Equal(t, tc.expectedValue, value, fmt.Sprintf("unexpected result: test case #%d", i))
}
}
func TestEthereumDBHas(t *testing.T) {
testEDB := newEthereumDB()
testCases := []struct {
edb *EthereumDB
data *kvPair
key []byte
expectedValue bool
}{
{
edb: testEDB,
key: []byte("foo"),
expectedValue: false,
},
{
edb: testEDB,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
expectedValue: true,
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.edb.Put(tc.data.key, tc.data.value)
}
ok, err := tc.edb.Has(tc.key)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Equal(t, tc.expectedValue, ok, fmt.Sprintf("unexpected result: test case #%d", i))
}
}
func TestEthereumDBDelete(t *testing.T) {
testEDB := newEthereumDB()
testCases := []struct {
edb *EthereumDB
data *kvPair
key []byte
}{
{
edb: testEDB,
key: []byte("foo"),
},
{
edb: testEDB,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.edb.Put(tc.data.key, tc.data.value)
}
err := tc.edb.Delete(tc.key)
ok, _ := tc.edb.Has(tc.key)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.False(t, ok, fmt.Sprintf("unexpected existence of key: test case #%d", i))
}
}
func TestEthereumDBNewBatch(t *testing.T) {
edb := newEthereumDB()
batch := edb.NewBatch()
require.Equal(t, edb, batch)
}
func TestEthereumDBValueSize(t *testing.T) {
edb := newEthereumDB()
size := edb.ValueSize()
require.Equal(t, 0, size)
}
func TestEthereumDBWrite(t *testing.T) {
edb := newEthereumDB()
err := edb.Write()
require.Nil(t, err)
}

3
docs/spec/evm/README.md Normal file
View File

@ -0,0 +1,3 @@
# EVM
TODO

View File

@ -21,15 +21,15 @@ const (
// must implementing. Internal ante handlers will be dependant upon the
// transaction type.
type internalAnteHandler func(
sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMapper,
sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountKeeper,
) (newCtx sdk.Context, res sdk.Result, abort bool)
// AnteHandler is responsible for attempting to route an Ethereum or SDK
// transaction to an internal ante handler for performing transaction-level
// processing (e.g. fee payment, signature verification) before being passed
// onto it's respective handler.
func AnteHandler(accMapper auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.AnteHandler {
return func(sdkCtx sdk.Context, tx sdk.Tx) (newCtx sdk.Context, res sdk.Result, abort bool) {
func AnteHandler(ak auth.AccountKeeper, _ auth.FeeCollectionKeeper) sdk.AnteHandler {
return func(sdkCtx sdk.Context, tx sdk.Tx, _ bool) (newCtx sdk.Context, res sdk.Result, abort bool) {
var (
handler internalAnteHandler
gasLimit int64
@ -67,16 +67,13 @@ func AnteHandler(accMapper auth.AccountMapper, _ auth.FeeCollectionKeeper) sdk.A
}
}()
return handler(newCtx, tx, accMapper)
return handler(newCtx, tx, ak)
}
}
// handleEthTx implements an ante handler for an Ethereum transaction. It
// validates the signature and if valid returns an OK result.
//
// TODO: Do we need to do any further validation or account manipulation
// (e.g. increment nonce)?
func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMapper) (sdk.Context, sdk.Result, bool) {
func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, ak auth.AccountKeeper) (sdk.Context, sdk.Result, bool) {
ethTx, ok := tx.(types.Transaction)
if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
@ -85,7 +82,6 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMapper) (s
// the SDK chainID is a string representation of integer
chainID, ok := new(big.Int).SetString(sdkCtx.ChainID(), 10)
if !ok {
// TODO: ErrInternal may not be correct error to throw here?
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid chainID: %s", sdkCtx.ChainID())).Result(), true
}
@ -96,7 +92,7 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMapper) (s
return sdkCtx, sdk.ErrUnauthorized("signature verification failed").Result(), true
}
acc := accMapper.GetAccount(sdkCtx, addr.Bytes())
acc := ak.GetAccount(sdkCtx, addr.Bytes())
// validate the account nonce (referred to as sequence in the AccountMapper)
seq := acc.GetSequence()
@ -104,18 +100,19 @@ func handleEthTx(sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMapper) (s
return sdkCtx, sdk.ErrInvalidSequence(fmt.Sprintf("invalid account nonce; expected: %d", seq)).Result(), true
}
err = acc.SetSequence(seq + 1)
if err != nil {
return sdkCtx, sdk.ErrInternal(err.Error()).Result(), true
}
// TODO: The EVM will handle incrementing the nonce (sequence number)
//
// TODO: Investigate gas consumption models as the EVM instruction set has its
// own and we should probably not charge for additional gas where we don't have
// to.
accMapper.SetAccount(sdkCtx, acc)
ak.SetAccount(sdkCtx, acc)
return sdkCtx, sdk.Result{GasWanted: int64(ethTx.Data().GasLimit)}, false
}
// handleEmbeddedTx implements an ante handler for an SDK transaction. It
// validates the signature and if valid returns an OK result.
func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMapper) (sdk.Context, sdk.Result, bool) {
func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, ak auth.AccountKeeper) (sdk.Context, sdk.Result, bool) {
stdTx, ok := tx.(auth.StdTx)
if !ok {
return sdkCtx, sdk.ErrInternal(fmt.Sprintf("invalid transaction: %T", tx)).Result(), true
@ -132,15 +129,14 @@ func handleEmbeddedTx(sdkCtx sdk.Context, tx sdk.Tx, accMapper auth.AccountMappe
for i, sig := range stdTx.Signatures {
signer := ethcmn.BytesToAddress(signerAddrs[i].Bytes())
acc, err := validateSignature(sdkCtx, stdTx, signer, sig, accMapper)
// err.Code() != sdk.CodeOK
acc, err := validateSignature(sdkCtx, stdTx, signer, sig, ak)
if err != nil {
return sdkCtx, err.Result(), true
}
// TODO: Fees!
accMapper.SetAccount(sdkCtx, acc)
ak.SetAccount(sdkCtx, acc)
signerAccs[i] = acc
}
@ -167,12 +163,12 @@ func validateStdTxBasic(stdTx auth.StdTx) (err sdk.Error) {
func validateSignature(
sdkCtx sdk.Context, stdTx auth.StdTx, signer ethcmn.Address,
sig auth.StdSignature, accMapper auth.AccountMapper,
sig auth.StdSignature, ak auth.AccountKeeper,
) (acc auth.Account, sdkErr sdk.Error) {
chainID := sdkCtx.ChainID()
acc = accMapper.GetAccount(sdkCtx, signer.Bytes())
acc = ak.GetAccount(sdkCtx, signer.Bytes())
if acc == nil {
return nil, sdk.ErrUnknownAddress(fmt.Sprintf("no account with address %s found", signer))
}

View File

@ -1,256 +0,0 @@
package handlers
import (
"crypto/ecdsa"
"fmt"
"math/big"
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/stake"
"github.com/cosmos/ethermint/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/libs/log"
)
func TestEthTxBadSig(t *testing.T) {
tx := types.NewTransaction(uint64(0), types.TestAddr1, big.NewInt(10), 1000, big.NewInt(100), []byte{})
// create bad signature
tx.Sign(big.NewInt(100), types.TestPrivKey2)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(types.NewTestCodec(), key, auth.ProtoBaseAccount)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, *tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x10004), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEthTxInsufficientGas(t *testing.T) {
tx := types.NewTransaction(uint64(0), types.TestAddr1, big.NewInt(0), 0, big.NewInt(0), []byte{})
tx.Sign(types.TestChainID, types.TestPrivKey1)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(types.NewTestCodec(), key, auth.ProtoBaseAccount)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, *tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x1000c), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEthTxIncorrectNonce(t *testing.T) {
// create transaction with wrong nonce 12
tx := types.NewTransaction(12, types.TestAddr2, big.NewInt(50), 1000, big.NewInt(1000), []byte("test_bytes"))
tx.Sign(types.TestChainID, types.TestPrivKey1)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, log.NewNopLogger())
accMapper := auth.NewAccountMapper(types.NewTestCodec(), key, auth.ProtoBaseAccount)
// set account in accountMapper
acc := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
accMapper.SetAccount(ctx, acc)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, *tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x10003), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEthTxValidTx(t *testing.T) {
tx := types.NewTransaction(0, types.TestAddr1, big.NewInt(50), 1000, big.NewInt(1000), []byte{})
tx.Sign(types.TestChainID, types.TestPrivKey1)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(types.NewTestCodec(), key, auth.ProtoBaseAccount)
// set account in accountMapper
acc := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
accMapper.SetAccount(ctx, acc)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, *tx)
require.False(t, abort, "expected ante handler to not abort")
require.True(t, res.IsOK(), fmt.Sprintf("result not OK on valid Tx: %s", res.Log))
// ensure account state updated correctly
seq, _ := accMapper.GetSequence(ctx, types.TestAddr1[:])
require.Equal(t, int64(1), seq, "account nonce did not increment correctly")
}
func TestEmbeddedTxBadSig(t *testing.T) {
testCodec := types.NewTestCodec()
testFee := types.NewTestStdFee()
msgs := []sdk.Msg{sdk.NewTestMsg()}
tx := types.NewTestStdTx(
types.TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{types.TestPrivKey1}, testFee,
)
// create bad signature
signBytes := types.GetStdTxSignBytes(big.NewInt(100).String(), 1, 1, testFee, msgs, "")
sig, _ := ethcrypto.Sign(signBytes, types.TestPrivKey1)
(tx.(auth.StdTx)).Signatures[0].Signature = sig
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(testCodec, key, auth.ProtoBaseAccount)
// set account in accountMapper
acc := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
accMapper.SetAccount(ctx, acc)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x10004), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEmbeddedTxInvalidMultiMsg(t *testing.T) {
testCodec := types.NewTestCodec()
testCodec.RegisterConcrete(stake.MsgDelegate{}, "test/MsgDelegate", nil)
msgs := []sdk.Msg{
stake.NewMsgDelegate(types.TestAddr1.Bytes(), types.TestAddr2.Bytes(), sdk.NewCoin("steak", sdk.NewInt(50))),
stake.NewMsgDelegate(types.TestAddr2.Bytes(), types.TestAddr2.Bytes(), sdk.NewCoin("steak", sdk.NewInt(50))),
}
// create transaction with only one signer
tx := types.NewTestStdTx(
types.TestChainID, msgs, []int64{0}, []int64{0}, []*ecdsa.PrivateKey{types.TestPrivKey1}, types.NewTestStdFee(),
)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(testCodec, key, auth.ProtoBaseAccount)
// set accounts in accountMapper
acc1 := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
accMapper.SetAccount(ctx, acc1)
acc2 := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
accMapper.SetAccount(ctx, acc2)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x10004), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEmbeddedTxInvalidAccountNumber(t *testing.T) {
testCodec := types.NewTestCodec()
testCodec.RegisterConcrete(stake.MsgDelegate{}, "test/MsgDelegate", nil)
msgs := []sdk.Msg{
stake.NewMsgDelegate(types.TestAddr1.Bytes(), types.TestAddr2.Bytes(), sdk.NewCoin("steak", sdk.NewInt(50))),
}
// create a transaction with an invalid account number
tx := types.NewTestStdTx(
types.TestChainID, msgs, []int64{3}, []int64{0}, []*ecdsa.PrivateKey{types.TestPrivKey1}, types.NewTestStdFee(),
)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(testCodec, key, auth.ProtoBaseAccount)
// set account in accountMapper
acc := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
acc.SetAccountNumber(4)
accMapper.SetAccount(ctx, acc)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x10003), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEmbeddedTxInvalidSequence(t *testing.T) {
testCodec := types.NewTestCodec()
testCodec.RegisterConcrete(stake.MsgDelegate{}, "test/MsgDelegate", nil)
msgs := []sdk.Msg{
stake.NewMsgDelegate(types.TestAddr1.Bytes(), types.TestAddr2.Bytes(), sdk.NewCoin("steak", sdk.NewInt(50))),
}
// create transaction with an invalid sequence (nonce)
tx := types.NewTestStdTx(
types.TestChainID, msgs, []int64{4}, []int64{2}, []*ecdsa.PrivateKey{types.TestPrivKey1}, types.NewTestStdFee(),
)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(types.NewTestCodec(), key, auth.ProtoBaseAccount)
// set account in accountMapper
acc := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
acc.SetAccountNumber(4)
acc.SetSequence(3)
accMapper.SetAccount(ctx, acc)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, tx)
require.True(t, abort, "expected ante handler to abort")
require.Equal(t, sdk.ABCICodeType(0x10003), res.Code, fmt.Sprintf("invalid code returned on bad tx: %s", res.Log))
}
func TestEmbeddedTxValid(t *testing.T) {
testCodec := types.NewTestCodec()
testCodec.RegisterConcrete(stake.MsgDelegate{}, "test/MsgDelegate", nil)
msgs := []sdk.Msg{
stake.NewMsgDelegate(types.TestAddr1.Bytes(), types.TestAddr2.Bytes(), sdk.NewCoin("steak", sdk.NewInt(50))),
stake.NewMsgDelegate(types.TestAddr2.Bytes(), types.TestAddr2.Bytes(), sdk.NewCoin("steak", sdk.NewInt(50))),
}
// create a valid transaction
tx := types.NewTestStdTx(
types.TestChainID, msgs, []int64{4, 5}, []int64{3, 1},
[]*ecdsa.PrivateKey{types.TestPrivKey1, types.TestPrivKey2}, types.NewTestStdFee(),
)
ms, key := createTestMultiStore()
ctx := sdk.NewContext(ms, abci.Header{ChainID: types.TestChainID.String()}, false, nil)
accMapper := auth.NewAccountMapper(types.NewTestCodec(), key, auth.ProtoBaseAccount)
// set accounts in the accountMapper
acc1 := accMapper.NewAccountWithAddress(ctx, types.TestAddr1.Bytes())
acc1.SetAccountNumber(4)
acc1.SetSequence(3)
accMapper.SetAccount(ctx, acc1)
acc2 := accMapper.NewAccountWithAddress(ctx, types.TestAddr2.Bytes())
acc2.SetAccountNumber(5)
acc2.SetSequence(1)
accMapper.SetAccount(ctx, acc2)
handler := AnteHandler(accMapper, auth.FeeCollectionKeeper{})
_, res, abort := handler(ctx, tx)
require.False(t, abort, "expected ante handler to not abort")
require.True(t, res.IsOK(), fmt.Sprintf("result not OK on valid Tx: %s", res.Log))
// Ensure account state updated correctly
seq1, _ := accMapper.GetSequence(ctx, types.TestAddr1.Bytes())
seq2, _ := accMapper.GetSequence(ctx, types.TestAddr2.Bytes())
require.Equal(t, int64(4), seq1, "account nonce did not increment correctly")
require.Equal(t, int64(2), seq2, "account nonce did not increment correctly")
}

302
importer/importer_test.go Normal file
View File

@ -0,0 +1,302 @@
package importer
import (
"flag"
"fmt"
"io"
"math/big"
"os"
"os/signal"
"runtime/pprof"
"sort"
"syscall"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/core"
"github.com/cosmos/ethermint/types"
evmtypes "github.com/cosmos/ethermint/x/evm/types"
ethcmn "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/ethash"
ethmisc "github.com/ethereum/go-ethereum/consensus/misc"
ethcore "github.com/ethereum/go-ethereum/core"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethvm "github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params"
ethrlp "github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
dbm "github.com/tendermint/tendermint/libs/db"
tmlog "github.com/tendermint/tendermint/libs/log"
)
var (
flagDataDir string
flagBlockchain string
flagCPUProfile string
miner501 = ethcmn.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
genInvestor = ethcmn.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
accKey = sdk.NewKVStoreKey("acc")
storageKey = sdk.NewKVStoreKey("storage")
codeKey = sdk.NewKVStoreKey("code")
logger = tmlog.NewNopLogger()
rewardBig8 = big.NewInt(8)
rewardBig32 = big.NewInt(32)
)
func init() {
flag.StringVar(&flagCPUProfile, "cpu-profile", "", "write CPU profile")
flag.StringVar(&flagDataDir, "datadir", "", "test data directory for state storage")
flag.StringVar(&flagBlockchain, "blockchain", "blockchain", "ethereum block export file (blocks to import)")
flag.Parse()
}
func newTestCodec() *codec.Codec {
cdc := codec.New()
types.RegisterCodec(cdc)
auth.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
}
func cleanup() {
fmt.Println("cleaning up test execution...")
os.RemoveAll(flagDataDir)
if flagCPUProfile != "" {
pprof.StopCPUProfile()
}
}
func trapSignals() {
sigs := make(chan os.Signal, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
cleanup()
os.Exit(1)
}()
}
func createAndTestGenesis(t *testing.T, cms sdk.CommitMultiStore, ak auth.AccountKeeper) {
genBlock := ethcore.DefaultGenesisBlock()
ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
stateDB, err := evmtypes.NewCommitStateDB(ctx, ak, storageKey, codeKey)
require.NoError(t, err, "failed to create a StateDB instance")
// sort the addresses and insertion of key/value pairs matters
genAddrs := make([]string, len(genBlock.Alloc))
i := 0
for addr := range genBlock.Alloc {
genAddrs[i] = addr.String()
i++
}
sort.Strings(genAddrs)
for _, addrStr := range genAddrs {
addr := ethcmn.HexToAddress(addrStr)
acc := genBlock.Alloc[addr]
stateDB.AddBalance(addr, acc.Balance)
stateDB.SetCode(addr, acc.Code)
stateDB.SetNonce(addr, acc.Nonce)
for key, value := range acc.Storage {
stateDB.SetState(addr, key, value)
}
}
// get balance of one of the genesis account having 200 ETH
b := stateDB.GetBalance(genInvestor)
require.Equal(t, "200000000000000000000", b.String())
// commit the stateDB with 'false' to delete empty objects
//
// NOTE: Commit does not yet return the intra merkle root (version)
_, err = stateDB.Commit(false)
require.NoError(t, err)
// persist multi-store cache state
ms.Write()
// persist multi-store root state
commitID := cms.Commit()
require.Equal(t, "12D4DB63083D5B01824A35BB70BF671686D60532", fmt.Sprintf("%X", commitID.Hash))
// verify account mapper state
genAcc := ak.GetAccount(ctx, sdk.AccAddress(genInvestor.Bytes()))
require.NotNil(t, genAcc)
require.Equal(t, sdk.NewIntFromBigInt(b), genAcc.GetCoins().AmountOf(types.DenomDefault))
}
func TestImportBlocks(t *testing.T) {
if flagDataDir == "" {
flagDataDir = os.TempDir()
}
if flagCPUProfile != "" {
f, err := os.Create(flagCPUProfile)
require.NoError(t, err, "failed to create CPU profile")
err = pprof.StartCPUProfile(f)
require.NoError(t, err, "failed to start CPU profile")
}
db := dbm.NewDB("state", dbm.LevelDBBackend, flagDataDir)
defer cleanup()
trapSignals()
// create logger, codec and root multi-store
cdc := newTestCodec()
cms := store.NewCommitMultiStore(db)
// create account mapper
am := auth.NewAccountKeeper(
cdc,
accKey,
types.ProtoBaseAccount,
)
// mount stores
keys := []*sdk.KVStoreKey{accKey, storageKey, codeKey}
for _, key := range keys {
cms.MountStoreWithDB(key, sdk.StoreTypeIAVL, nil)
}
cms.SetPruning(sdk.PruneNothing)
// load latest version (root)
err := cms.LoadLatestVersion()
require.NoError(t, err)
// set and test genesis block
createAndTestGenesis(t, cms, am)
// open blockchain export file
blockchainInput, err := os.Open(flagBlockchain)
require.Nil(t, err)
defer blockchainInput.Close()
// ethereum mainnet config
chainContext := core.NewChainContext()
vmConfig := ethvm.Config{}
chainConfig := ethparams.MainnetChainConfig
// create RLP stream for exported blocks
stream := ethrlp.NewStream(blockchainInput, 0)
startTime := time.Now()
var block ethtypes.Block
for {
err = stream.Decode(&block)
if err == io.EOF {
break
}
require.NoError(t, err, "failed to decode block")
var (
usedGas = new(uint64)
gp = new(ethcore.GasPool).AddGas(block.GasLimit())
)
header := block.Header()
chainContext.Coinbase = header.Coinbase
chainContext.SetHeader(block.NumberU64(), header)
// Create a cached-wrapped multi-store based on the commit multi-store and
// create a new context based off of that.
ms := cms.CacheMultiStore()
ctx := sdk.NewContext(ms, abci.Header{}, false, logger)
ctx = ctx.WithBlockHeight(int64(block.NumberU64()))
stateDB := createStateDB(t, ctx, am)
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
ethmisc.ApplyDAOHardFork(stateDB)
}
for i, tx := range block.Transactions() {
stateDB.Prepare(tx.Hash(), block.Hash(), i)
_, _, err = ethcore.ApplyTransaction(
chainConfig, chainContext, nil, gp, stateDB, header, tx, usedGas, vmConfig,
)
require.NoError(t, err, "failed to apply tx at block %d; tx: %X", block.NumberU64(), tx.Hash())
}
// apply mining rewards
accumulateRewards(chainConfig, stateDB, header, block.Uncles())
// commit stateDB
_, err := stateDB.Commit(chainConfig.IsEIP158(block.Number()))
require.NoError(t, err, "failed to commit StateDB")
// simulate BaseApp EndBlocker commitment
ms.Write()
cms.Commit()
// block debugging output
if block.NumberU64() > 0 && block.NumberU64()%1000 == 0 {
fmt.Printf("processed block: %d (time so far: %v)\n", block.NumberU64(), time.Since(startTime))
}
}
}
func createStateDB(t *testing.T, ctx sdk.Context, ak auth.AccountKeeper) *evmtypes.CommitStateDB {
stateDB, err := evmtypes.NewCommitStateDB(ctx, ak, storageKey, codeKey)
require.NoError(t, err, "failed to create a StateDB instance")
return stateDB
}
// accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(
config *ethparams.ChainConfig, stateDB *evmtypes.CommitStateDB,
header *ethtypes.Header, uncles []*ethtypes.Header,
) {
// select the correct block reward based on chain progression
blockReward := ethash.FrontierBlockReward
if config.IsByzantium(header.Number) {
blockReward = ethash.ByzantiumBlockReward
}
// accumulate the rewards for the miner and any included uncles
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
for _, uncle := range uncles {
r.Add(uncle.Number, rewardBig8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, rewardBig8)
stateDB.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, rewardBig32)
reward.Add(reward, r)
}
stateDB.AddBalance(header.Coinbase, reward)
}

View File

@ -1,207 +0,0 @@
package state
import (
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/core"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtrie "github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru"
dbm "github.com/tendermint/tendermint/libs/db"
)
var (
// CodeKey is the key used for storing Ethereum contract code in the Cosmos
// SDK multi-store.
CodeKey = sdk.NewKVStoreKey("code")
)
const (
// DefaultStoreCacheSize defines the default number of key/value pairs for
// the state stored in memory.
DefaultStoreCacheSize = 1024 * 1024
// codeSizeCacheSize is the number of codehash to size associations to
// keep in cached memory. This is to address any DoS attempts on
// EXTCODESIZE calls.
codeSizeCacheSize = 100000
)
// Database implements the Ethereum state.Database interface.
type Database struct {
// stateStore will be used for the history of accounts (balance, nonce,
// storage root hash, code hash) and for the history of contract data
// (effects of SSTORE instruction).
stateStore store.CommitMultiStore
accountsCache store.CacheKVStore
storageCache store.CacheKVStore
// codeDB contains mappings of codeHash => code
//
// NOTE: This database will store the information in memory until is it
// committed, using the function Commit. This function is called outside of
// the ApplyTransaction function, therefore in Ethermint we need to make
// sure this commit is invoked somewhere after each block or whatever the
// appropriate time for it.
codeDB dbm.DB
ethTrieDB *ethtrie.Database
// codeSizeCache contains an LRU cache of a specified capacity to cache
// EXTCODESIZE calls.
codeSizeCache *lru.Cache
storeCache *lru.Cache
Tracing bool
}
// NewDatabase returns a reference to an initialized Database type which
// implements Ethereum's state.Database interface. An error is returned if the
// latest state failed to load. The underlying storage structure is defined by
// the Cosmos SDK IAVL tree.
func NewDatabase(stateStore store.CommitMultiStore, codeDB dbm.DB, storeCacheSize int) (*Database, error) {
db := &Database{stateStore: stateStore}
// Set the persistent Cosmos SDK Database and initialize an Ethereum
// trie.Database using an EthereumDB as the underlying implementation of
// the ethdb.Database interface. It will be used to facilitate persistence
// of contract byte code when committing state.
db.codeDB = codeDB
db.ethTrieDB = ethtrie.NewDatabase(&core.EthereumDB{CodeDB: codeDB})
var err error
if db.codeSizeCache, err = lru.New(codeSizeCacheSize); err != nil {
return nil, err
}
if db.storeCache, err = lru.New(storeCacheSize); err != nil {
return nil, err
}
return db, nil
}
// LatestVersion returns the latest version of the underlying mult-store.
func (db *Database) LatestVersion() int64 {
return db.stateStore.LastCommitID().Version
}
// OpenTrie implements Ethereum's state.Database interface. It returns a Trie
// type which implements the Ethereum state.Trie interface. It us used for
// storage of accounts. An error is returned if state cannot load for a
// given version. The account cache is reset if the state is successfully
// loaded and the version is not the latest.
//
// CONTRACT: The root parameter is not interpreted as a state root hash, but as
// an encoding of an Cosmos SDK IAVL tree version.
func (db *Database) OpenTrie(root ethcmn.Hash) (ethstate.Trie, error) {
if !isRootEmpty(root) {
version := versionFromRootHash(root)
if db.stateStore.LastCommitID().Version != version {
if err := db.stateStore.LoadVersion(version); err != nil {
return nil, err
}
db.accountsCache = nil
}
}
if db.accountsCache == nil {
db.accountsCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(types.StoreKeyAccount))
db.storageCache = store.NewCacheKVStore(db.stateStore.GetCommitKVStore(types.StoreKeyStorage))
}
return &Trie{
store: db.accountsCache,
accountsCache: db.accountsCache,
storageCache: db.storageCache,
storeCache: db.storeCache,
ethTrieDB: db.ethTrieDB,
empty: isRootEmpty(root),
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
}, nil
}
// OpenStorageTrie implements Ethereum's state.Database interface. It returns
// a Trie type which implements the Ethereum state.Trie interface. It is used
// for storage of contract storage (state). Also, this trie is never committed
// separately as all the data is in a single multi-store and is committed when
// the account IAVL tree is committed.
//
// NOTE: It is assumed that the account state has already been loaded via
// OpenTrie.
//
// CONTRACT: The root parameter is not interpreted as a state root hash, but as
// an encoding of an IAVL tree version.
func (db *Database) OpenStorageTrie(addrHash, root ethcmn.Hash) (ethstate.Trie, error) {
// a contract storage trie does not need an accountCache, storageCache or
// an Ethereum trie because it will not be used upon commitment.
return &Trie{
store: db.storageCache,
storeCache: db.storeCache,
prefix: addrHash.Bytes(),
empty: isRootEmpty(root),
root: rootHashFromVersion(db.stateStore.LastCommitID().Version),
}, nil
}
// CopyTrie implements Ethereum's state.Database interface. For now, it
// performs a no-op as the underlying Cosmos SDK IAVL tree does not support
// such an operation.
//
// TODO: Does the IAVL tree need to support this operation? If so, why and
// how?
func (db *Database) CopyTrie(ethstate.Trie) ethstate.Trie {
return nil
}
// ContractCode implements Ethereum's state.Database interface. It will return
// the contract byte code for a given code hash. It will not return an error.
func (db *Database) ContractCode(addrHash, codeHash ethcmn.Hash) ([]byte, error) {
code := db.codeDB.Get(codeHash[:])
if codeLen := len(code); codeLen != 0 {
db.codeSizeCache.Add(codeHash, codeLen)
}
return code, nil
}
// ContractCodeSize implements Ethereum's state.Database interface. It will
// return the contract byte code size for a given code hash. It will not return
// an error.
func (db *Database) ContractCodeSize(addrHash, codeHash ethcmn.Hash) (int, error) {
if cached, ok := db.codeSizeCache.Get(codeHash); ok {
return cached.(int), nil
}
code, err := db.ContractCode(addrHash, codeHash)
return len(code), err
}
// Commit commits the underlying Cosmos SDK multi-store returning the commit
// ID.
func (db *Database) Commit() sdk.CommitID {
return db.stateStore.Commit()
}
// TrieDB implements Ethereum's state.Database interface. It returns Ethereum's
// trie.Database low level trie database used for contract state storage. In
// the context of Ethermint, it'll be used to solely store mappings of
// codeHash => code.
func (db *Database) TrieDB() *ethtrie.Database {
return db.ethTrieDB
}
// isRootEmpty returns true if a given root hash is empty or false otherwise.
func isRootEmpty(root ethcmn.Hash) bool {
return root == ethcmn.Hash{}
}

View File

@ -1,110 +0,0 @@
package state
import (
"fmt"
"testing"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
"github.com/stretchr/testify/require"
)
func TestDatabaseInterface(t *testing.T) {
require.Implements(t, (*ethstate.Database)(nil), new(Database))
}
func TestDatabaseLatestVersion(t *testing.T) {
var version int64
testDB := newTestDatabase()
version = testDB.LatestVersion()
require.Equal(t, int64(0), version)
testDB.Commit()
version = testDB.LatestVersion()
require.Equal(t, int64(1), version)
}
func TestDatabaseCopyTrie(t *testing.T) {
// TODO: Implement once CopyTrie is implemented
t.SkipNow()
}
func TestDatabaseContractCode(t *testing.T) {
testDB := newTestDatabase()
testCases := []struct {
db *Database
data *code
codeHash ethcmn.Hash
expectedCode []byte
}{
{
db: testDB,
codeHash: ethcmn.BytesToHash([]byte("code hash")),
expectedCode: nil,
},
{
db: testDB,
data: &code{ethcmn.BytesToHash([]byte("code hash")), []byte("some awesome code")},
codeHash: ethcmn.BytesToHash([]byte("code hash")),
expectedCode: []byte("some awesome code"),
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.db.codeDB.Set(tc.data.hash[:], tc.data.blob)
}
code, err := tc.db.ContractCode(ethcmn.Hash{}, tc.codeHash)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Equal(t, tc.expectedCode, code, fmt.Sprintf("unexpected result: test case #%d", i))
}
}
func TestDatabaseContractCodeSize(t *testing.T) {
testDB := newTestDatabase()
testCases := []struct {
db *Database
data *code
codeHash ethcmn.Hash
expectedCodeLen int
}{
{
db: testDB,
codeHash: ethcmn.BytesToHash([]byte("code hash")),
expectedCodeLen: 0,
},
{
db: testDB,
data: &code{ethcmn.BytesToHash([]byte("code hash")), []byte("some awesome code")},
codeHash: ethcmn.BytesToHash([]byte("code hash")),
expectedCodeLen: 17,
},
{
db: testDB,
codeHash: ethcmn.BytesToHash([]byte("code hash")),
expectedCodeLen: 17,
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.db.codeDB.Set(tc.data.hash[:], tc.data.blob)
}
codeLen, err := tc.db.ContractCodeSize(ethcmn.Hash{}, tc.codeHash)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Equal(t, tc.expectedCodeLen, codeLen, fmt.Sprintf("unexpected result: test case #%d", i))
}
}
func TestDatabaseTrieDB(t *testing.T) {
testDB := newTestDatabase()
db := testDB.TrieDB()
require.Equal(t, testDB.ethTrieDB, db)
}

View File

@ -1,47 +0,0 @@
package state
import (
"fmt"
"math/rand"
"time"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
dbm "github.com/tendermint/tendermint/libs/db"
)
type (
kvPair struct {
key, value []byte
}
code struct {
hash ethcmn.Hash
blob []byte
}
)
func init() {
rand.Seed(time.Now().UnixNano())
}
func newTestDatabase() *Database {
memDB := dbm.NewMemDB()
cms := store.NewCommitMultiStore(memDB)
cms.SetPruning(sdk.PruneNothing)
cms.MountStoreWithDB(types.StoreKeyAccount, sdk.StoreTypeIAVL, nil)
cms.MountStoreWithDB(types.StoreKeyStorage, sdk.StoreTypeIAVL, nil)
testDB, err := NewDatabase(cms, memDB, 100)
if err != nil {
panic(fmt.Sprintf("failed to create database: %v", err))
}
testDB.stateStore.LoadLatestVersion()
return testDB
}

View File

@ -1,208 +0,0 @@
package state
import (
"encoding/binary"
"github.com/cosmos/cosmos-sdk/store"
ethcmn "github.com/ethereum/go-ethereum/common"
ethdb "github.com/ethereum/go-ethereum/ethdb"
ethtrie "github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru"
)
const (
versionLen = 8
)
// Trie implements the Ethereum state.Trie interface.
type Trie struct {
// accountsCache contains all the accounts in memory to persit when
// committing the trie. A CacheKVStore is used to provide deterministic
// ordering.
accountsCache store.CacheKVStore
// storageCache contains all the contract storage in memory to persit when
// committing the trie. A CacheKVStore is used to provide deterministic
// ordering.
storageCache store.CacheKVStore
storeCache *lru.Cache
// Store is an IAVL KV store that is part of a larger store except it used
// for a specific prefix. It will either be an accountsCache or a
// storageCache.
store store.KVStore
// prefix is a static prefix used for persistence operations where the
// storage data is a contract state. This is to prevent key collisions
// since the IAVL tree is used for all contract state.
prefix []byte
// empty reflects if there exists any data in the tree
empty bool
// root is the encoding of an IAVL tree root (version)
root ethcmn.Hash
ethTrieDB *ethtrie.Database
}
// prefixKey returns a composite key composed of a static prefix and a given
// key. This is used in situations where the storage data is contract state and
// the underlying structure to store said state is a single IAVL tree. To
// prevent collision, a static prefix is used.
func (t *Trie) prefixKey(key []byte) []byte {
compositeKey := make([]byte, len(t.prefix)+len(key))
copy(compositeKey, t.prefix)
copy(compositeKey[len(t.prefix):], key)
return compositeKey
}
// TryGet implements the Ethereum state.Trie interface. It returns the value
// for key stored in the trie. The value bytes must not be modified by the
// caller.
func (t *Trie) TryGet(key []byte) ([]byte, error) {
if t.IsStorageTrie() {
key = t.prefixKey(key)
}
keyStr := string(key)
if cached, ok := t.storeCache.Get(keyStr); ok {
return cached.([]byte), nil
}
value := t.store.Get(key)
t.storeCache.Add(keyStr, value)
return value, nil
}
// TryUpdate implements the Ethereum state.Trie interface. It associates a
// given key with a value in the trie. Subsequent calls to Get will return a
// value. It also marks the tree as not empty.
//
// CONTRACT: The order of insertions must be deterministic due to the nature of
// the IAVL tree. Since a CacheKVStore is used as the storage type, the keys
// will be sorted giving us a deterministic ordering.
func (t *Trie) TryUpdate(key, value []byte) error {
t.empty = false
if t.IsStorageTrie() {
key = t.prefixKey(key)
}
t.store.Set(key, value)
t.storeCache.Add(string(key), value)
return nil
}
// TryDelete implements the Ethereum state.Trie interface. It removes any
// existing value for a given key from the trie.
//
// CONTRACT: The order of deletions must be deterministic due to the nature of
// the IAVL tree. Since a CacheKVStore is used as the storage type, the keys
// will be sorted giving us a deterministic ordering.
func (t *Trie) TryDelete(key []byte) error {
if t.IsStorageTrie() {
key = t.prefixKey(key)
}
t.store.Delete(key)
t.storeCache.Remove(string(key))
return nil
}
// Commit implements the Ethereum state.Trie interface. It persists transient
// state. State is held by a merkelized multi-store IAVL tree. Commitment will
// only occur through an account trie, in other words, when the prefix of the
// trie is nil. In such a case, if either the accountCache or the storageCache
// are not nil, they are persisted. In addition, all the mappings of
// codeHash => code are also persisted. All these operations are performed in a
// deterministic order. Transient state is built up in a CacheKVStore. Finally,
// a root hash is returned or an error if any operation fails.
//
// CONTRACT: The root is an encoded IAVL tree version and each new commitment
// increments the version by one.
func (t *Trie) Commit(_ ethtrie.LeafCallback) (ethcmn.Hash, error) {
if t.empty {
return ethcmn.Hash{}, nil
}
newRoot := rootHashFromVersion(versionFromRootHash(t.root) + 1)
if !t.IsStorageTrie() {
if t.accountsCache != nil {
t.accountsCache.Write()
t.accountsCache = nil
}
if t.storageCache != nil {
t.storageCache.Write()
t.storageCache = nil
}
// persist the mappings of codeHash => code
for _, n := range t.ethTrieDB.Nodes() {
if err := t.ethTrieDB.Commit(n, false); err != nil {
return ethcmn.Hash{}, err
}
}
}
t.root = newRoot
return newRoot, nil
}
// Hash implements the Ethereum state.Trie interface. It returns the state root
// of the Trie which is an encoding of the underlying IAVL tree.
//
// CONTRACT: The root is an encoded IAVL tree version.
func (t *Trie) Hash() ethcmn.Hash {
return t.root
}
// NodeIterator implements the Ethereum state.Trie interface. Such a node
// iterator is used primarily for the implementation of RPC API functions. It
// performs a no-op.
//
// TODO: Determine if we need to implement such functionality for an IAVL tree.
// This will ultimately be related to if we want to support web3.
func (t *Trie) NodeIterator(startKey []byte) ethtrie.NodeIterator {
return nil
}
// GetKey implements the Ethereum state.Trie interface. Since the IAVL does not
// need to store preimages of keys, a simple identity can be returned.
func (t *Trie) GetKey(key []byte) []byte {
return key
}
// Prove implements the Ethereum state.Trie interface. It writes a Merkle proof
// to a ethdb.Putter, proofDB, for a given key starting at fromLevel.
//
// TODO: Determine how to integrate this with Cosmos SDK to provide such
// proofs.
func (t *Trie) Prove(key []byte, fromLevel uint, proofDB ethdb.Putter) error {
return nil
}
// IsStorageTrie returns a boolean reflecting if the Trie is created for
// contract storage.
func (t *Trie) IsStorageTrie() bool {
return t.prefix != nil
}
// versionFromRootHash returns a Cosmos SDK IAVL version from an Ethereum state
// root hash.
//
// CONTRACT: The encoded version is the eight MSB bytes of the root hash.
func versionFromRootHash(root ethcmn.Hash) int64 {
return int64(binary.BigEndian.Uint64(root[:versionLen]))
}
// rootHashFromVersion returns a state root hash from a Cosmos SDK IAVL
// version.
func rootHashFromVersion(version int64) (root ethcmn.Hash) {
binary.BigEndian.PutUint64(root[:versionLen], uint64(version))
return
}

View File

@ -1,256 +0,0 @@
package state
import (
"fmt"
"math/rand"
"testing"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
"github.com/stretchr/testify/require"
)
func newTestTrie() *Trie {
testDB := newTestDatabase()
testTrie, _ := testDB.OpenTrie(rootHashFromVersion(0))
return testTrie.(*Trie)
}
func newTestPrefixTrie() *Trie {
testDB := newTestDatabase()
prefix := make([]byte, ethcmn.HashLength)
rand.Read(prefix)
testDB.OpenTrie(rootHashFromVersion(0))
testTrie, _ := testDB.OpenStorageTrie(ethcmn.BytesToHash(prefix), rootHashFromVersion(0))
return testTrie.(*Trie)
}
func TestTrieInterface(t *testing.T) {
require.Implements(t, (*ethstate.Trie)(nil), new(Trie))
}
func TestTrieTryGet(t *testing.T) {
testTrie := newTestTrie()
testPrefixTrie := newTestPrefixTrie()
testCases := []struct {
trie *Trie
data *kvPair
key []byte
expectedValue []byte
}{
{
trie: testTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
expectedValue: []byte("bar"),
},
{
trie: testTrie,
key: []byte("baz"),
expectedValue: nil,
},
{
trie: testPrefixTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
expectedValue: []byte("bar"),
},
{
trie: testPrefixTrie,
key: []byte("baz"),
expectedValue: nil,
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.trie.TryUpdate(tc.data.key, tc.data.value)
}
value, err := tc.trie.TryGet(tc.key)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Equal(t, tc.expectedValue, value, fmt.Sprintf("unexpected value: test case #%d", i))
}
}
func TestTrieTryUpdate(t *testing.T) {
testTrie := newTestTrie()
testPrefixTrie := newTestPrefixTrie()
kv := &kvPair{[]byte("foo"), []byte("bar")}
var err error
err = testTrie.TryUpdate(kv.key, kv.value)
require.Nil(t, err)
err = testPrefixTrie.TryUpdate(kv.key, kv.value)
require.Nil(t, err)
}
func TestTrieTryDelete(t *testing.T) {
testTrie := newTestTrie()
testPrefixTrie := newTestPrefixTrie()
testCases := []struct {
trie *Trie
data *kvPair
key []byte
}{
{
trie: testTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
},
{
trie: testTrie,
key: []byte("baz"),
},
{
trie: testPrefixTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
key: []byte("foo"),
},
{
trie: testPrefixTrie,
key: []byte("baz"),
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.trie.TryUpdate(tc.data.key, tc.data.value)
}
err := tc.trie.TryDelete(tc.key)
value, _ := tc.trie.TryGet(tc.key)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Nil(t, value, fmt.Sprintf("unexpected value: test case #%d", i))
}
}
func TestTrieCommit(t *testing.T) {
testTrie := newTestTrie()
testPrefixTrie := newTestPrefixTrie()
testCases := []struct {
trie *Trie
data *kvPair
code *code
expectedRoot ethcmn.Hash
}{
{
trie: &Trie{empty: true},
expectedRoot: ethcmn.Hash{},
},
{
trie: testTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
expectedRoot: rootHashFromVersion(1),
},
{
trie: testTrie,
data: &kvPair{[]byte("baz"), []byte("cat")},
code: &code{ethcmn.BytesToHash([]byte("code hash")), []byte("code hash")},
expectedRoot: rootHashFromVersion(2),
},
{
trie: testTrie,
expectedRoot: rootHashFromVersion(3),
},
{
trie: testPrefixTrie,
expectedRoot: rootHashFromVersion(0),
},
{
trie: testPrefixTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
expectedRoot: rootHashFromVersion(1),
},
{
trie: testPrefixTrie,
expectedRoot: rootHashFromVersion(2),
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.trie.TryUpdate(tc.data.key, tc.data.value)
}
if tc.code != nil {
tc.trie.ethTrieDB.InsertBlob(tc.code.hash, tc.code.blob)
}
root, err := tc.trie.Commit(nil)
require.Nil(t, err, fmt.Sprintf("unexpected error: test case #%d", i))
require.Equal(t, tc.expectedRoot, root, fmt.Sprintf("unexpected root: test case #%d", i))
}
}
func TestTrieHash(t *testing.T) {
testTrie := newTestTrie()
testPrefixTrie := newTestPrefixTrie()
testCases := []struct {
trie *Trie
data *kvPair
expectedRoot ethcmn.Hash
}{
{
trie: testTrie,
expectedRoot: rootHashFromVersion(0),
},
{
trie: testTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
expectedRoot: rootHashFromVersion(1),
},
{
trie: testPrefixTrie,
expectedRoot: rootHashFromVersion(0),
},
{
trie: testPrefixTrie,
data: &kvPair{[]byte("foo"), []byte("bar")},
expectedRoot: rootHashFromVersion(1),
},
}
for i, tc := range testCases {
if tc.data != nil {
tc.trie.TryUpdate(tc.data.key, tc.data.value)
tc.trie.Commit(nil)
}
root := tc.trie.Hash()
require.Equal(t, tc.expectedRoot, root, fmt.Sprintf("unexpected root: test case #%d", i))
}
}
func TestTrieNodeIterator(t *testing.T) {
// TODO: Implement once NodeIterator is implemented
t.SkipNow()
}
func TestTrieGetKey(t *testing.T) {
testTrie := newTestTrie()
testPrefixTrie := newTestPrefixTrie()
var key []byte
expectedKey := []byte("foo")
key = testTrie.GetKey(expectedKey)
require.Equal(t, expectedKey, key)
key = testPrefixTrie.GetKey(expectedKey)
require.Equal(t, expectedKey, key)
}
func TestTrieProve(t *testing.T) {
// TODO: Implement once Prove is implemented
t.SkipNow()
}

View File

@ -1,8 +0,0 @@
// The implementation below is to be considered highly unstable and a continual
// WIP. It is a means to replicate and test replaying Ethereum transactions
// using the Cosmos SDK and the EVM. The ultimate result will be what is known
// as Ethermint.
//
// Note: Some code is directly copied from go-ethereum.
package importer

View File

@ -1,247 +0,0 @@
package importer
import (
"bytes"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"os"
"path"
"time"
"github.com/cosmos/ethermint/core"
"github.com/cosmos/ethermint/state"
ethcommon "github.com/ethereum/go-ethereum/common"
ethmisc "github.com/ethereum/go-ethereum/consensus/misc"
ethcore "github.com/ethereum/go-ethereum/core"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethvm "github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params"
ethrlp "github.com/ethereum/go-ethereum/rlp"
)
var (
miner501 = ethcommon.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
genInvestor = ethcommon.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
)
// Importer implements a structure to facilitate testing of importing mainnet
// Ethereum blocks and transactions using the Cosmos SDK components and the
// EVM.
type Importer struct {
EthermintDB *state.Database
BlockchainFile string
Datadir string
InterruptCh <-chan bool
}
// Import performs an import given an Importer that has a Geth stateDB
// implementation and a blockchain exported file.
// nolint
func (imp *Importer) Import() {
// only create genesis if it is a brand new database
if imp.EthermintDB.LatestVersion() == 0 {
// start with empty root hash (i.e. empty state)
gethStateDB, err := ethstate.New(ethcommon.Hash{}, imp.EthermintDB)
if err != nil {
panic(fmt.Sprintf("failed to instantiate geth state.StateDB: %v", err))
}
genBlock := ethcore.DefaultGenesisBlock()
for addr, account := range genBlock.Alloc {
gethStateDB.AddBalance(addr, account.Balance)
gethStateDB.SetCode(addr, account.Code)
gethStateDB.SetNonce(addr, account.Nonce)
for key, value := range account.Storage {
gethStateDB.SetState(addr, key, value)
}
}
// get balance of one of the genesis account having 200 ETH
b := gethStateDB.GetBalance(genInvestor)
fmt.Printf("balance of %s: %s\n", genInvestor.String(), b)
// commit the geth stateDB with 'false' to delete empty objects
genRoot, err := gethStateDB.Commit(false)
if err != nil {
panic(err)
}
commitID := imp.EthermintDB.Commit()
fmt.Printf("commitID after genesis: %v\n", commitID)
fmt.Printf("genesis state root hash: %x\n", genRoot[:])
}
// file with blockchain data exported from geth by using "geth exportdb"
// command.
input, err := os.Open(imp.BlockchainFile)
if err != nil {
panic(err)
}
defer input.Close()
// ethereum mainnet config
chainConfig := ethparams.MainnetChainConfig
// create RLP stream for exported blocks
stream := ethrlp.NewStream(input, 0)
var (
block ethtypes.Block
root500 ethcommon.Hash // root hash after block 500
root501 ethcommon.Hash // root hash after block 501
)
var prevRoot ethcommon.Hash
binary.BigEndian.PutUint64(prevRoot[:8], uint64(imp.EthermintDB.LatestVersion()))
imp.EthermintDB.Tracing = true
chainContext := core.NewChainContext()
vmConfig := ethvm.Config{}
n := 0
startTime := time.Now()
interrupt := false
var lastSkipped uint64
for !interrupt {
if err = stream.Decode(&block); err == io.EOF {
err = nil
break
} else if err != nil {
panic(fmt.Errorf("failed to decode at block %d: %s", block.NumberU64(), err))
}
// don't import blocks already imported
if block.NumberU64() < uint64(imp.EthermintDB.LatestVersion()) {
lastSkipped = block.NumberU64()
continue
}
if lastSkipped > 0 {
fmt.Printf("skipped blocks up to %d\n", lastSkipped)
lastSkipped = 0
}
header := block.Header()
chainContext.Coinbase = header.Coinbase
chainContext.SetHeader(block.NumberU64(), header)
gethStateDB, err := ethstate.New(prevRoot, imp.EthermintDB)
if err != nil {
panic(fmt.Errorf("failed to instantiate geth state.StateDB at block %d: %v", block.NumberU64(), err))
}
var (
receipts ethtypes.Receipts
usedGas = new(uint64)
allLogs []*ethtypes.Log
gp = new(ethcore.GasPool).AddGas(block.GasLimit())
)
if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 {
ethmisc.ApplyDAOHardFork(gethStateDB)
}
for i, tx := range block.Transactions() {
gethStateDB.Prepare(tx.Hash(), block.Hash(), i)
txHash := tx.Hash()
if bytes.Equal(txHash[:], ethcommon.FromHex("0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717")) {
vmConfig.Tracer = ethvm.NewStructLogger(&ethvm.LogConfig{})
vmConfig.Debug = true
}
receipt, _, err := ethcore.ApplyTransaction(chainConfig, chainContext, nil, gp, gethStateDB, header, tx, usedGas, vmConfig)
if vmConfig.Tracer != nil {
w, err := os.Create(path.Join(imp.Datadir, "structlogs.txt"))
if err != nil {
panic(fmt.Sprintf("failed to create file for VM tracing: %v", err))
}
encoder := json.NewEncoder(w)
logs := FormatLogs(vmConfig.Tracer.(*ethvm.StructLogger).StructLogs())
if err := encoder.Encode(logs); err != nil {
panic(err)
}
if err := w.Close(); err != nil {
panic(err)
}
vmConfig.Debug = false
vmConfig.Tracer = nil
}
if err != nil {
panic(fmt.Errorf("at block %d, tx %x: %v", block.NumberU64(), tx.Hash(), err))
}
receipts = append(receipts, receipt)
allLogs = append(allLogs, receipt.Logs...)
}
// apply mining rewards to the geth stateDB
accumulateRewards(chainConfig, gethStateDB, header, block.Uncles())
// commit block in geth
prevRoot, err = gethStateDB.Commit(chainConfig.IsEIP158(block.Number()))
if err != nil {
panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err))
}
// commit block in Ethermint
imp.EthermintDB.Commit()
switch block.NumberU64() {
case 500:
root500 = prevRoot
case 501:
root501 = prevRoot
}
n++
if (n % 10000) == 0 {
fmt.Printf("processed %d blocks, time so far: %v\n", n, time.Since(startTime))
}
// Check for interrupts
select {
case interrupt = <-imp.InterruptCh:
fmt.Println("interrupted, please wait for cleanup...")
default:
}
}
fmt.Printf("processed %d blocks\n", n)
imp.EthermintDB.Tracing = true
// try to create a new geth stateDB from root of the block 500
fmt.Printf("root at block 500: %x\n", root500[:])
state500, err := ethstate.New(root500, imp.EthermintDB)
if err != nil {
panic(err)
}
miner501BalanceAt500 := state500.GetBalance(miner501)
state501, err := ethstate.New(root501, imp.EthermintDB)
if err != nil {
panic(err)
}
miner501BalanceAt501 := state501.GetBalance(miner501)
fmt.Printf("investor's balance after block 500: %d\n", state500.GetBalance(genInvestor))
fmt.Printf("miner of block 501's balance after block 500: %d\n", miner501BalanceAt500)
fmt.Printf("miner of block 501's balance after block 501: %d\n", miner501BalanceAt501)
}

View File

@ -1,66 +0,0 @@
package importer
import (
"fmt"
ethmath "github.com/ethereum/go-ethereum/common/math"
ethvm "github.com/ethereum/go-ethereum/core/vm"
)
// StructLogRes stores a structured log emitted by the EVM while replaying a
// transaction in debug mode.
type StructLogRes struct {
Pc uint64 `json:"pc"`
Op string `json:"op"`
Gas uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}
// FormatLogs formats EVM returned structured logs for json output.
func FormatLogs(logs []ethvm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = fmt.Sprintf("%x", ethmath.PaddedBigBytes(stackValue, 32))
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}

View File

@ -1,43 +0,0 @@
package importer
import (
"math/big"
"github.com/ethereum/go-ethereum/consensus/ethash"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethparams "github.com/ethereum/go-ethereum/params"
)
// Some weird constants to avoid constant memory allocs for them.
var (
big8 = big.NewInt(8)
big32 = big.NewInt(32)
)
// accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *ethparams.ChainConfig, state *ethstate.StateDB, header *ethtypes.Header, uncles []*ethtypes.Header) {
// select the correct block reward based on chain progression
blockReward := ethash.FrontierBlockReward
if config.IsByzantium(header.Number) {
blockReward = ethash.ByzantiumBlockReward
}
// accumulate the rewards for the miner and any included uncles
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
for _, uncle := range uncles {
r.Add(uncle.Number, big8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32)
reward.Add(reward, r)
}
state.AddBalance(header.Coinbase, reward)
}

View File

@ -1,81 +0,0 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"path"
"runtime/pprof"
"syscall"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/state"
"github.com/cosmos/ethermint/test/importer"
"github.com/cosmos/ethermint/types"
dbm "github.com/tendermint/tendermint/libs/db"
)
var (
cpuprofile = flag.String("cpu-profile", "", "write cpu profile `file`")
blockchain = flag.String("blockchain", "data/blockchain", "file containing blocks to load")
datadir = flag.String("datadir", path.Join(os.Getenv("HOME"), ".ethermint"), "directory for ethermint data")
cachesize = flag.Int("cachesize", 1024*1024, "number of key-value pairs for the state stored in memory")
)
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Printf("could not create CPU profile: %v\n", err)
return
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Printf("could not start CPU profile: %v\n", err)
return
}
defer pprof.StopCPUProfile()
}
sigs := make(chan os.Signal, 1)
interruptCh := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
interruptCh <- true
}()
stateDB := dbm.NewDB("state", dbm.LevelDBBackend, *datadir)
codeDB := dbm.NewDB("code", dbm.LevelDBBackend, *datadir)
cms := store.NewCommitMultiStore(stateDB)
cms.SetPruning(sdk.PruneNothing)
cms.MountStoreWithDB(types.StoreKeyAccount, sdk.StoreTypeIAVL, nil)
cms.MountStoreWithDB(types.StoreKeyStorage, sdk.StoreTypeIAVL, nil)
if err := cms.LoadLatestVersion(); err != nil {
panic(fmt.Sprintf("failed to load state store: %v", err))
}
ethermintDB, err := state.NewDatabase(cms, codeDB, *cachesize)
if err != nil {
panic(fmt.Sprintf("failed to initialize geth Database: %v", err))
}
importer := importer.Importer{
EthermintDB: ethermintDB,
BlockchainFile: *blockchain,
Datadir: *datadir,
InterruptCh: interruptCh,
}
importer.Import()
}

View File

@ -1,8 +1,9 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
@ -10,44 +11,73 @@ import (
var _ auth.Account = (*Account)(nil)
type (
// Storage defines account storage
Storage map[ethcmn.Hash]ethcmn.Hash
// Account defines an auth.BaseAccount extension for Ethermint. It is
// compatible with the auth.AccountMapper.
Account struct {
auth.BaseAccount
Code []byte
Storage Storage
}
const (
// DenomDefault defines the single coin type/denomination supported in
// Ethermint.
DenomDefault = "Photon"
)
// NewAccount returns a reference to a new initialized account.
func NewAccount(base auth.BaseAccount, code []byte, storage Storage) *Account {
return &Account{
BaseAccount: base,
Code: code,
Storage: storage,
}
// ----------------------------------------------------------------------------
// Main Ethermint account
// ----------------------------------------------------------------------------
// BaseAccount implements the auth.Account interface and embeds an
// auth.BaseAccount type. It is compatible with the auth.AccountMapper.
type Account struct {
*auth.BaseAccount
// merkle root of the storage trie
//
// TODO: good chance we may not need this
Root ethcmn.Hash
CodeHash []byte
}
// GetAccountDecoder returns the auth.AccountDecoder function for the custom
// Account type.
func GetAccountDecoder(cdc *wire.Codec) auth.AccountDecoder {
return func(accBytes []byte) (auth.Account, error) {
if len(accBytes) == 0 {
return nil, sdk.ErrTxDecode("account bytes are empty")
}
acc := new(Account)
err := cdc.UnmarshalBinaryBare(accBytes, &acc)
if err != nil {
return nil, sdk.ErrTxDecode("failed to decode account bytes")
}
return acc, err
}
// ProtoBaseAccount defines the prototype function for BaseAccount used for an
// account mapper.
func ProtoBaseAccount() auth.Account {
return &Account{BaseAccount: &auth.BaseAccount{}}
}
// Balance returns the balance of an account.
func (acc Account) Balance() sdk.Int {
return acc.GetCoins().AmountOf(DenomDefault)
}
// SetBalance sets an account's balance.
func (acc Account) SetBalance(amt sdk.Int) {
acc.SetCoins(sdk.Coins{sdk.NewCoin(DenomDefault, amt)})
}
// ----------------------------------------------------------------------------
// Code & Storage
// ----------------------------------------------------------------------------
// Account code and storage type aliases.
type (
Code []byte
Storage map[ethcmn.Hash]ethcmn.Hash
)
func (c Code) String() string {
return string(c)
}
func (c Storage) String() (str string) {
for key, value := range c {
str += fmt.Sprintf("%X : %X\n", key, value)
}
return
}
// Copy returns a copy of storage.
func (c Storage) Copy() Storage {
cpy := make(Storage)
for key, value := range c {
cpy[key] = value
}
return cpy
}

View File

@ -5,14 +5,15 @@ import (
"crypto/ecdsa"
"math/big"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
// test variables
var (
TestSDKAddr = GenerateEthAddress()
TestChainID = big.NewInt(3)
@ -24,15 +25,15 @@ var (
TestAddr2 = PrivKeyToEthAddress(TestPrivKey2)
)
func NewTestCodec() *wire.Codec {
codec := wire.NewCodec()
func NewTestCodec() *codec.Codec {
cdc := codec.New()
RegisterWire(codec)
auth.RegisterWire(codec)
wire.RegisterCrypto(codec)
codec.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
RegisterCodec(cdc)
auth.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
cdc.RegisterConcrete(&sdk.TestMsg{}, "test/TestMsg", nil)
return codec
return cdc
}
func NewTestStdFee() auth.StdFee {

View File

@ -8,8 +8,8 @@ import (
"math/big"
"sync/atomic"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
ethcmn "github.com/ethereum/go-ethereum/common"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
@ -18,9 +18,12 @@ import (
"github.com/pkg/errors"
)
// TODO: Move to the EVM module
// message constants
const (
// TypeTxEthereum reflects an Ethereum Transaction type.
TypeTxEthereum = "Ethereum"
TypeTxEthereum = "Ethereum"
RouteTxEthereum = "evm"
)
// ----------------------------------------------------------------------------
@ -238,9 +241,11 @@ func (tx Transaction) VerifySig(chainID *big.Int) (ethcmn.Address, error) {
// Type implements the sdk.Msg interface. It returns the type of the
// Transaction.
func (tx Transaction) Type() string {
return TypeTxEthereum
}
func (tx Transaction) Type() string { return TypeTxEthereum }
// Route implements the sdk.Msg interface. It returns the route of the
// Transaction.
func (tx Transaction) Route() string { return RouteTxEthereum }
// ValidateBasic implements the sdk.Msg interface. It performs basic validation
// checks of a Transaction. If returns an sdk.Error if validation fails.
@ -283,7 +288,7 @@ func (tx Transaction) hasEmbeddedTx(addr ethcmn.Address) bool {
//
// CONTRACT: The payload field of an Ethereum transaction must contain a valid
// encoded SDK transaction.
func (tx Transaction) GetEmbeddedTx(codec *wire.Codec) (sdk.Tx, sdk.Error) {
func (tx Transaction) GetEmbeddedTx(codec *codec.Codec) (sdk.Tx, sdk.Error) {
var etx sdk.Tx
err := codec.UnmarshalBinary(tx.data.Payload, &etx)
@ -301,7 +306,7 @@ func (tx Transaction) GetEmbeddedTx(codec *wire.Codec) (sdk.Tx, sdk.Error) {
// TxDecoder returns an sdk.TxDecoder that given raw transaction bytes and an
// SDK address, attempts to decode them into a Transaction or an EmbeddedTx or
// returning an error if decoding fails.
func TxDecoder(codec *wire.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder {
func TxDecoder(codec *codec.Codec, sdkAddress ethcmn.Address) sdk.TxDecoder {
return func(txBytes []byte) (sdk.Tx, sdk.Error) {
var tx = Transaction{}

View File

@ -25,7 +25,7 @@ func GenerateEthAddress() ethcmn.Address {
// PrivKeyToEthAddress generates an Ethereum address given an ECDSA private key.
func PrivKeyToEthAddress(p *ecdsa.PrivateKey) ethcmn.Address {
return ethcrypto.PubkeyToAddress(ecdsa.PublicKey(p.PublicKey))
return ethcrypto.PubkeyToAddress(p.PublicKey)
}
// ValidateSigner attempts to validate a signer for a given slice of bytes over

View File

@ -1,20 +1,20 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/wire"
)
var typesCodec = wire.NewCodec()
var typesCodec = codec.New()
func init() {
RegisterWire(typesCodec)
RegisterCodec(typesCodec)
}
// RegisterWire registers all the necessary types with amino for the given
// RegisterCodec registers all the necessary types with amino for the given
// codec.
func RegisterWire(codec *wire.Codec) {
sdk.RegisterWire(codec)
codec.RegisterConcrete(&Transaction{}, "types/Transaction", nil)
codec.RegisterConcrete(&Account{}, "types/Account", nil)
func RegisterCodec(cdc *codec.Codec) {
sdk.RegisterCodec(cdc)
cdc.RegisterConcrete(&Transaction{}, "types/Transaction", nil)
cdc.RegisterConcrete(&Account{}, "types/Account", nil)
}

View File

12
x/evm/types/dump.go Normal file
View File

@ -0,0 +1,12 @@
package types
import (
ethstate "github.com/ethereum/go-ethereum/core/state"
)
// RawDump returns a raw state dump.
//
// TODO: Implement if we need it, especially for the RPC API.
func (csdb *CommitStateDB) RawDump() ethstate.Dump {
return ethstate.Dump{}
}

227
x/evm/types/journal.go Normal file
View File

@ -0,0 +1,227 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
ethcmn "github.com/ethereum/go-ethereum/common"
)
var ripemd = ethcmn.HexToAddress("0000000000000000000000000000000000000003")
// journalEntry is a modification entry in the state change journal that can be
// reverted on demand.
type journalEntry interface {
// revert undoes the changes introduced by this journal entry.
revert(*CommitStateDB)
// dirtied returns the Ethereum address modified by this journal entry.
dirtied() *ethcmn.Address
}
// journal contains the list of state modifications applied since the last state
// commit. These are tracked to be able to be reverted in case of an execution
// exception or revertal request.
type journal struct {
entries []journalEntry // Current changes tracked by the journal
dirties map[ethcmn.Address]int // Dirty accounts and the number of changes
}
// newJournal create a new initialized journal.
func newJournal() *journal {
return &journal{
dirties: make(map[ethcmn.Address]int),
}
}
// append inserts a new modification entry to the end of the change journal.
func (j *journal) append(entry journalEntry) {
j.entries = append(j.entries, entry)
if addr := entry.dirtied(); addr != nil {
j.dirties[*addr]++
}
}
// revert undoes a batch of journalled modifications along with any reverted
// dirty handling too.
func (j *journal) revert(statedb *CommitStateDB, snapshot int) {
for i := len(j.entries) - 1; i >= snapshot; i-- {
// Undo the changes made by the operation
j.entries[i].revert(statedb)
// Drop any dirty tracking induced by the change
if addr := j.entries[i].dirtied(); addr != nil {
if j.dirties[*addr]--; j.dirties[*addr] == 0 {
delete(j.dirties, *addr)
}
}
}
j.entries = j.entries[:snapshot]
}
// dirty explicitly sets an address to dirty, even if the change entries would
// otherwise suggest it as clean. This method is an ugly hack to handle the RIPEMD
// precompile consensus exception.
func (j *journal) dirty(addr ethcmn.Address) {
j.dirties[addr]++
}
// length returns the current number of entries in the journal.
func (j *journal) length() int {
return len(j.entries)
}
type (
// Changes to the account trie.
createObjectChange struct {
account *ethcmn.Address
}
resetObjectChange struct {
prev *stateObject
}
suicideChange struct {
account *ethcmn.Address
prev bool // whether account had already suicided
prevBalance sdk.Int
}
// Changes to individual accounts.
balanceChange struct {
account *ethcmn.Address
prev sdk.Int
}
nonceChange struct {
account *ethcmn.Address
prev int64
}
storageChange struct {
account *ethcmn.Address
key, prevValue ethcmn.Hash
}
codeChange struct {
account *ethcmn.Address
prevCode, prevHash []byte
}
// Changes to other state values.
refundChange struct {
prev uint64
}
addLogChange struct {
txhash ethcmn.Hash
}
addPreimageChange struct {
hash ethcmn.Hash
}
touchChange struct {
account *ethcmn.Address
prev bool
prevDirty bool
}
)
func (ch createObjectChange) revert(s *CommitStateDB) {
delete(s.stateObjects, *ch.account)
delete(s.stateObjectsDirty, *ch.account)
}
func (ch createObjectChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch resetObjectChange) revert(s *CommitStateDB) {
s.setStateObject(ch.prev)
}
func (ch resetObjectChange) dirtied() *ethcmn.Address {
return nil
}
func (ch suicideChange) revert(s *CommitStateDB) {
so := s.getStateObject(*ch.account)
if so != nil {
so.suicided = ch.prev
so.setBalance(ch.prevBalance)
}
}
func (ch suicideChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch touchChange) revert(s *CommitStateDB) {
}
func (ch touchChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch balanceChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setBalance(ch.prev)
}
func (ch balanceChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch nonceChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setNonce(ch.prev)
}
func (ch nonceChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch codeChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setCode(ethcmn.BytesToHash(ch.prevHash), ch.prevCode)
}
func (ch codeChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch storageChange) revert(s *CommitStateDB) {
s.getStateObject(*ch.account).setState(ch.key, ch.prevValue)
}
func (ch storageChange) dirtied() *ethcmn.Address {
return ch.account
}
func (ch refundChange) revert(s *CommitStateDB) {
s.refund = ch.prev
}
func (ch refundChange) dirtied() *ethcmn.Address {
return nil
}
func (ch addLogChange) revert(s *CommitStateDB) {
logs := s.logs[ch.txhash]
if len(logs) == 1 {
delete(s.logs, ch.txhash)
} else {
s.logs[ch.txhash] = logs[:len(logs)-1]
}
s.logSize--
}
func (ch addLogChange) dirtied() *ethcmn.Address {
return nil
}
func (ch addPreimageChange) revert(s *CommitStateDB) {
delete(s.preimages, ch.hash)
}
func (ch addPreimageChange) dirtied() *ethcmn.Address {
return nil
}

366
x/evm/types/state_object.go Normal file
View File

@ -0,0 +1,366 @@
package types
import (
"bytes"
"fmt"
"math/big"
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/ethermint/types"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
var (
_ ethstate.StateObject = (*stateObject)(nil)
emptyCodeHash = ethcrypto.Keccak256(nil)
)
type (
// stateObject represents an Ethereum account which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// Account values can be accessed and modified through the object.
// Finally, call CommitTrie to write the modified storage trie into a database.
stateObject struct {
address ethcmn.Address
stateDB *CommitStateDB
account *types.Account
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by StateDB.Commit.
dbErr error
code types.Code // contract bytecode, which gets set when code is loaded
originStorage types.Storage // Storage cache of original entries to dedup rewrites
dirtyStorage types.Storage // Storage entries that need to be flushed to disk
// cache flags
//
// When an object is marked suicided it will be delete from the trie during
// the "update" phase of the state transition.
dirtyCode bool // true if the code was updated
suicided bool
deleted bool
}
)
func newObject(db *CommitStateDB, accProto auth.Account) *stateObject {
acc, ok := accProto.(*types.Account)
if !ok {
panic(fmt.Sprintf("invalid account type for state object: %T", acc))
}
if acc.CodeHash == nil {
acc.CodeHash = emptyCodeHash
}
return &stateObject{
stateDB: db,
account: acc,
address: ethcmn.BytesToAddress(acc.Address.Bytes()),
originStorage: make(types.Storage),
dirtyStorage: make(types.Storage),
}
}
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
// SetState updates a value in account storage. Note, the key will be prefixed
// with the address of the state object.
func (so *stateObject) SetState(db ethstate.Database, key, value ethcmn.Hash) {
// if the new value is the same as old, don't set
prev := so.GetState(db, key)
if prev == value {
return
}
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// since the new value is different, update and journal the change
so.stateDB.journal.append(storageChange{
account: &so.address,
key: prefixKey,
prevValue: prev,
})
so.setState(prefixKey, value)
}
func (so *stateObject) setState(key, value ethcmn.Hash) {
so.dirtyStorage[key] = value
}
// SetCode sets the state object's code.
func (so *stateObject) SetCode(codeHash ethcmn.Hash, code []byte) {
prevCode := so.Code(nil)
so.stateDB.journal.append(codeChange{
account: &so.address,
prevHash: so.CodeHash(),
prevCode: prevCode,
})
so.setCode(codeHash, code)
}
func (so *stateObject) setCode(codeHash ethcmn.Hash, code []byte) {
so.code = code
so.account.CodeHash = codeHash.Bytes()
so.dirtyCode = true
}
// AddBalance adds an amount to a state object's balance. It is used to add
// funds to the destination account of a transfer.
func (so *stateObject) AddBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
// EIP158: We must check emptiness for the objects such that the account
// clearing (0,0,0 objects) can take effect.
if amt.Sign() == 0 {
if so.empty() {
so.touch()
}
return
}
newBalance := so.account.Balance().Add(amt)
so.SetBalance(newBalance.BigInt())
}
// SubBalance removes an amount from the stateObject's balance. It is used to
// remove funds from the origin account of a transfer.
func (so *stateObject) SubBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
if amt.Sign() == 0 {
return
}
newBalance := so.account.Balance().Sub(amt)
so.SetBalance(newBalance.BigInt())
}
// SetBalance sets the state object's balance.
func (so *stateObject) SetBalance(amount *big.Int) {
amt := sdk.NewIntFromBigInt(amount)
so.stateDB.journal.append(balanceChange{
account: &so.address,
prev: so.account.Balance(),
})
so.setBalance(amt)
}
func (so *stateObject) setBalance(amount sdk.Int) {
so.account.SetBalance(amount)
}
// SetNonce sets the state object's nonce (sequence number).
func (so *stateObject) SetNonce(nonce uint64) {
so.stateDB.journal.append(nonceChange{
account: &so.address,
prev: so.account.Sequence,
})
so.setNonce(int64(nonce))
}
func (so *stateObject) setNonce(nonce int64) {
so.account.Sequence = nonce
}
// setError remembers the first non-nil error it is called with.
func (so *stateObject) setError(err error) {
if so.dbErr == nil {
so.dbErr = err
}
}
func (so *stateObject) markSuicided() {
so.suicided = true
}
// commitState commits all dirty storage to a KVStore.
func (so *stateObject) commitState() {
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storageKey)
for key, value := range so.dirtyStorage {
delete(so.dirtyStorage, key)
// skip no-op changes, persist actual changes
if value == so.originStorage[key] {
continue
}
so.originStorage[key] = value
// delete empty values
if (value == ethcmn.Hash{}) {
store.Delete(key.Bytes())
continue
}
store.Set(key.Bytes(), value.Bytes())
}
// TODO: Set the account (storage) root (but we probably don't need this)
}
// commitCode persists the state object's code to the KVStore.
func (so *stateObject) commitCode() {
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.codeKey)
store.Set(so.CodeHash(), so.code)
}
// ----------------------------------------------------------------------------
// Getters
// ----------------------------------------------------------------------------
// Address returns the address of the state object.
func (so stateObject) Address() ethcmn.Address {
return so.address
}
// Balance returns the state object's current balance.
func (so *stateObject) Balance() *big.Int {
return so.account.Balance().BigInt()
}
// CodeHash returns the state object's code hash.
func (so *stateObject) CodeHash() []byte {
return so.account.CodeHash
}
// Nonce returns the state object's current nonce (sequence number).
func (so *stateObject) Nonce() uint64 {
return uint64(so.account.Sequence)
}
// Code returns the contract code associated with this object, if any.
func (so *stateObject) Code(_ ethstate.Database) []byte {
if so.code != nil {
return so.code
}
if bytes.Equal(so.CodeHash(), emptyCodeHash) {
return nil
}
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.codeKey)
code := store.Get(so.CodeHash())
if len(code) == 0 {
so.setError(fmt.Errorf("failed to get code hash %x for address: %x", so.CodeHash(), so.Address()))
}
so.code = code
return code
}
// GetState retrieves a value from the account storage trie. Note, the key will
// be prefixed with the address of the state object.
func (so *stateObject) GetState(db ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// if we have a dirty value for this state entry, return it
value, dirty := so.dirtyStorage[prefixKey]
if dirty {
return value
}
// otherwise return the entry's original value
return so.GetCommittedState(db, key)
}
// GetCommittedState retrieves a value from the committed account storage trie.
// Note, the key will be prefixed with the address of the state object.
func (so *stateObject) GetCommittedState(_ ethstate.Database, key ethcmn.Hash) ethcmn.Hash {
prefixKey := so.GetStorageByAddressKey(key.Bytes())
// if we have the original value cached, return that
value, cached := so.originStorage[prefixKey]
if cached {
return value
}
// otherwise load the value from the KVStore
ctx := so.stateDB.ctx
store := ctx.KVStore(so.stateDB.storageKey)
rawValue := store.Get(prefixKey.Bytes())
if len(rawValue) > 0 {
value.SetBytes(rawValue)
}
so.originStorage[prefixKey] = value
return value
}
// ----------------------------------------------------------------------------
// Auxiliary
// ----------------------------------------------------------------------------
// ReturnGas returns the gas back to the origin. Used by the Virtual machine or
// Closures. It performs a no-op.
func (so *stateObject) ReturnGas(gas *big.Int) {}
func (so *stateObject) deepCopy(db *CommitStateDB) *stateObject {
newStateObj := newObject(db, so.account)
newStateObj.code = so.code
newStateObj.dirtyStorage = so.dirtyStorage.Copy()
newStateObj.originStorage = so.originStorage.Copy()
newStateObj.suicided = so.suicided
newStateObj.dirtyCode = so.dirtyCode
newStateObj.deleted = so.deleted
return newStateObj
}
// empty returns whether the account is considered empty.
func (so *stateObject) empty() bool {
return so.account.Sequence == 0 &&
so.account.Balance().Sign() == 0 &&
bytes.Equal(so.account.CodeHash, emptyCodeHash)
}
func (so *stateObject) touch() {
so.stateDB.journal.append(touchChange{
account: &so.address,
})
if so.address == ripemd {
// Explicitly put it in the dirty-cache, which is otherwise generated from
// flattened journals.
so.stateDB.journal.dirty(so.address)
}
}
// GetStorageByAddressKey returns a hash of the composite key for a state
// object's storage prefixed with it's address.
func (so stateObject) GetStorageByAddressKey(key []byte) ethcmn.Hash {
prefix := so.Address().Bytes()
compositeKey := make([]byte, len(prefix)+len(key))
copy(compositeKey, prefix)
copy(compositeKey[len(prefix):], key)
return ethcrypto.Keccak256Hash(compositeKey)
}

703
x/evm/types/statedb.go Normal file
View File

@ -0,0 +1,703 @@
package types
import (
"fmt"
"math/big"
"sort"
"sync"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
ethcmn "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethcrypto "github.com/ethereum/go-ethereum/crypto"
)
var (
_ ethstate.StateDB = (*CommitStateDB)(nil)
zeroBalance = sdk.ZeroInt().BigInt()
)
// CommitStateDB implements the Geth state.StateDB interface. Instead of using
// a trie and database for querying and persistence, the Keeper uses KVStores
// and an account mapper is used to facilitate state transitions.
//
// TODO: This implementation is subject to change in regards to its statefull
// manner. In otherwords, how this relates to the keeper in this module.
type CommitStateDB struct {
// TODO: We need to store the context as part of the structure itself opposed
// to being passed as a parameter (as it should be) in order to implement the
// StateDB interface. Perhaps there is a better way.
ctx sdk.Context
ak auth.AccountKeeper
storageKey sdk.StoreKey
codeKey sdk.StoreKey
// maps that hold 'live' objects, which will get modified while processing a
// state transition
stateObjects map[ethcmn.Address]*stateObject
stateObjectsDirty map[ethcmn.Address]struct{}
// The refund counter, also used by state transitioning.
refund uint64
thash, bhash ethcmn.Hash
txIndex int
logs map[ethcmn.Hash][]*ethtypes.Log
logSize uint
// TODO: Determine if we actually need this as we do not need preimages in
// the SDK, but it seems to be used elsewhere in Geth.
preimages map[ethcmn.Hash][]byte
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memo-ized here and will eventually be returned
// by StateDB.Commit.
dbErr error
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal *journal
validRevisions []ethstate.Revision
nextRevisionID int
// mutex for state deep copying
lock sync.Mutex
}
// NewCommitStateDB returns a reference to a newly initialized CommitStateDB
// which implements Geth's state.StateDB interface.
//
// CONTRACT: Stores used for state must be cache-wrapped as the ordering of the
// key/value space matters in determining the merkle root.
func NewCommitStateDB(ctx sdk.Context, ak auth.AccountKeeper, storageKey, codeKey sdk.StoreKey) (*CommitStateDB, error) {
return &CommitStateDB{
ctx: ctx,
ak: ak,
storageKey: storageKey,
codeKey: codeKey,
stateObjects: make(map[ethcmn.Address]*stateObject),
stateObjectsDirty: make(map[ethcmn.Address]struct{}),
logs: make(map[ethcmn.Hash][]*ethtypes.Log),
preimages: make(map[ethcmn.Hash][]byte),
journal: newJournal(),
}, nil
}
// ----------------------------------------------------------------------------
// Setters
// ----------------------------------------------------------------------------
// SetBalance sets the balance of an account.
func (csdb *CommitStateDB) SetBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetBalance(amount)
}
}
// AddBalance adds amount to the account associated with addr.
func (csdb *CommitStateDB) AddBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.AddBalance(amount)
}
}
// SubBalance subtracts amount from the account associated with addr.
func (csdb *CommitStateDB) SubBalance(addr ethcmn.Address, amount *big.Int) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SubBalance(amount)
}
}
// SetNonce sets the nonce (sequence number) of an account.
func (csdb *CommitStateDB) SetNonce(addr ethcmn.Address, nonce uint64) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetNonce(nonce)
}
}
// SetState sets the storage state with a key, value pair for an account.
func (csdb *CommitStateDB) SetState(addr ethcmn.Address, key, value ethcmn.Hash) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetState(nil, key, value)
}
}
// SetCode sets the code for a given account.
func (csdb *CommitStateDB) SetCode(addr ethcmn.Address, code []byte) {
so := csdb.GetOrNewStateObject(addr)
if so != nil {
so.SetCode(ethcrypto.Keccak256Hash(code), code)
}
}
// AddLog adds a new log to the state and sets the log metadata from the state.
func (csdb *CommitStateDB) AddLog(log *ethtypes.Log) {
csdb.journal.append(addLogChange{txhash: csdb.thash})
log.TxHash = csdb.thash
log.BlockHash = csdb.bhash
log.TxIndex = uint(csdb.txIndex)
log.Index = csdb.logSize
csdb.logs[csdb.thash] = append(csdb.logs[csdb.thash], log)
csdb.logSize++
}
// AddPreimage records a SHA3 preimage seen by the VM.
func (csdb *CommitStateDB) AddPreimage(hash ethcmn.Hash, preimage []byte) {
if _, ok := csdb.preimages[hash]; !ok {
csdb.journal.append(addPreimageChange{hash: hash})
pi := make([]byte, len(preimage))
copy(pi, preimage)
csdb.preimages[hash] = pi
}
}
// AddRefund adds gas to the refund counter.
func (csdb *CommitStateDB) AddRefund(gas uint64) {
csdb.journal.append(refundChange{prev: csdb.refund})
csdb.refund += gas
}
// SubRefund removes gas from the refund counter. It will panic if the refund
// counter goes below zero.
func (csdb *CommitStateDB) SubRefund(gas uint64) {
csdb.journal.append(refundChange{prev: csdb.refund})
if gas > csdb.refund {
panic("refund counter below zero")
}
csdb.refund -= gas
}
// ----------------------------------------------------------------------------
// Getters
// ----------------------------------------------------------------------------
// GetBalance retrieves the balance from the given address or 0 if object not
// found.
func (csdb *CommitStateDB) GetBalance(addr ethcmn.Address) *big.Int {
so := csdb.getStateObject(addr)
if so != nil {
return so.Balance()
}
return zeroBalance
}
// GetNonce returns the nonce (sequence number) for a given account.
func (csdb *CommitStateDB) GetNonce(addr ethcmn.Address) uint64 {
so := csdb.getStateObject(addr)
if so != nil {
return so.Nonce()
}
return 0
}
// GetCode returns the code for a given account.
func (csdb *CommitStateDB) GetCode(addr ethcmn.Address) []byte {
so := csdb.getStateObject(addr)
if so != nil {
return so.Code(nil)
}
return nil
}
// GetCodeSize returns the code size for a given account.
func (csdb *CommitStateDB) GetCodeSize(addr ethcmn.Address) int {
so := csdb.getStateObject(addr)
if so == nil {
return 0
}
if so.code != nil {
return len(so.code)
}
// TODO: we may need to cache these lookups directly
return len(so.Code(nil))
}
// GetCodeHash returns the code hash for a given account.
func (csdb *CommitStateDB) GetCodeHash(addr ethcmn.Address) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so == nil {
return ethcmn.Hash{}
}
return ethcmn.BytesToHash(so.CodeHash())
}
// GetState retrieves a value from the given account's storage store.
func (csdb *CommitStateDB) GetState(addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so != nil {
return so.GetState(nil, hash)
}
return ethcmn.Hash{}
}
// GetCommittedState retrieves a value from the given account's committed
// storage.
func (csdb *CommitStateDB) GetCommittedState(addr ethcmn.Address, hash ethcmn.Hash) ethcmn.Hash {
so := csdb.getStateObject(addr)
if so != nil {
return so.GetCommittedState(nil, hash)
}
return ethcmn.Hash{}
}
// GetLogs returns the current logs for a given hash in the state.
func (csdb *CommitStateDB) GetLogs(hash ethcmn.Hash) []*ethtypes.Log {
return csdb.logs[hash]
}
// Logs returns all the current logs in the state.
func (csdb *CommitStateDB) Logs() []*ethtypes.Log {
var logs []*ethtypes.Log
for _, lgs := range csdb.logs {
logs = append(logs, lgs...)
}
return logs
}
// GetRefund returns the current value of the refund counter.
func (csdb *CommitStateDB) GetRefund() uint64 {
return csdb.refund
}
// Preimages returns a list of SHA3 preimages that have been submitted.
func (csdb *CommitStateDB) Preimages() map[ethcmn.Hash][]byte {
return csdb.preimages
}
// HasSuicided returns if the given account for the specified address has been
// killed.
func (csdb *CommitStateDB) HasSuicided(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
if so != nil {
return so.suicided
}
return false
}
// StorageTrie returns nil as the state in Ethermint does not use a direct
// storage trie.
func (csdb *CommitStateDB) StorageTrie(addr ethcmn.Address) ethstate.Trie {
return nil
}
// ----------------------------------------------------------------------------
// Persistence
// ----------------------------------------------------------------------------
// Commit writes the state to the appropriate KVStores. For each state object
// in the cache, it will either be removed, or have it's code set and/or it's
// state (storage) updated. In addition, the state object (account) itself will
// be written. Finally, the root hash (version) will be returned.
func (csdb *CommitStateDB) Commit(deleteEmptyObjects bool) (root ethcmn.Hash, err error) {
defer csdb.clearJournalAndRefund()
// remove dirty state object entries based on the journal
for addr := range csdb.journal.dirties {
csdb.stateObjectsDirty[addr] = struct{}{}
}
// set the state objects
for addr, so := range csdb.stateObjects {
_, isDirty := csdb.stateObjectsDirty[addr]
switch {
case so.suicided || (isDirty && deleteEmptyObjects && so.empty()):
// If the state object has been removed, don't bother syncing it and just
// remove it from the store.
csdb.deleteStateObject(so)
case isDirty:
// write any contract code associated with the state object
if so.code != nil && so.dirtyCode {
so.commitCode()
so.dirtyCode = false
}
// update the object in the KVStore
csdb.updateStateObject(so)
}
delete(csdb.stateObjectsDirty, addr)
}
// NOTE: Ethereum returns the trie merkle root here, but as commitment
// actually happens in the BaseApp at EndBlocker, we do not know the root at
// this time.
return
}
// Finalize finalizes the state objects (accounts) state by setting their state,
// removing the csdb destructed objects and clearing the journal as well as the
// refunds.
func (csdb *CommitStateDB) Finalize(deleteEmptyObjects bool) {
for addr := range csdb.journal.dirties {
so, exist := csdb.stateObjects[addr]
if !exist {
// ripeMD is 'touched' at block 1714175, in tx:
// 0x1237f737031e40bcde4a8b7e717b2d15e3ecadfe49bb1bbc71ee9deb09c6fcf2
//
// That tx goes out of gas, and although the notion of 'touched' does not
// exist there, the touch-event will still be recorded in the journal.
// Since ripeMD is a special snowflake, it will persist in the journal even
// though the journal is reverted. In this special circumstance, it may
// exist in journal.dirties but not in stateObjects. Thus, we can safely
// ignore it here.
continue
}
if so.suicided || (deleteEmptyObjects && so.empty()) {
csdb.deleteStateObject(so)
} else {
// Set all the dirty state storage items for the state object in the
// KVStore and finally set the account in the account mapper.
so.commitState()
csdb.updateStateObject(so)
}
csdb.stateObjectsDirty[addr] = struct{}{}
}
// invalidate journal because reverting across transactions is not allowed
csdb.clearJournalAndRefund()
}
// IntermediateRoot returns the current root hash of the state. It is called in
// between transactions to get the root hash that goes into transaction
// receipts.
//
// NOTE: The SDK has not concept or method of getting any intermediate merkle
// root as commitment of the merkle-ized tree doesn't happen until the
// BaseApps' EndBlocker.
func (csdb *CommitStateDB) IntermediateRoot(deleteEmptyObjects bool) ethcmn.Hash {
csdb.Finalize(deleteEmptyObjects)
return ethcmn.Hash{}
}
// updateStateObject writes the given state object to the store.
func (csdb *CommitStateDB) updateStateObject(so *stateObject) {
csdb.ak.SetAccount(csdb.ctx, so.account)
}
// deleteStateObject removes the given state object from the state store.
func (csdb *CommitStateDB) deleteStateObject(so *stateObject) {
so.deleted = true
csdb.ak.RemoveAccount(csdb.ctx, so.account)
}
// ----------------------------------------------------------------------------
// Snapshotting
// ----------------------------------------------------------------------------
// Snapshot returns an identifier for the current revision of the state.
func (csdb *CommitStateDB) Snapshot() int {
id := csdb.nextRevisionID
csdb.nextRevisionID++
csdb.validRevisions = append(
csdb.validRevisions,
ethstate.Revision{
ID: id,
JournalIndex: csdb.journal.length(),
},
)
return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
func (csdb *CommitStateDB) RevertToSnapshot(revID int) {
// find the snapshot in the stack of valid snapshots
idx := sort.Search(len(csdb.validRevisions), func(i int) bool {
return csdb.validRevisions[i].ID >= revID
})
if idx == len(csdb.validRevisions) || csdb.validRevisions[idx].ID != revID {
panic(fmt.Errorf("revision ID %v cannot be reverted", revID))
}
snapshot := csdb.validRevisions[idx].JournalIndex
// replay the journal to undo changes and remove invalidated snapshots
csdb.journal.revert(csdb, snapshot)
csdb.validRevisions = csdb.validRevisions[:idx]
}
// ----------------------------------------------------------------------------
// Auxiliary
// ----------------------------------------------------------------------------
// Database retrieves the low level database supporting the lower level trie
// ops. It is not used in Ethermint, so it returns nil.
func (csdb *CommitStateDB) Database() ethstate.Database {
return nil
}
// Empty returns whether the state object is either non-existent or empty
// according to the EIP161 specification (balance = nonce = code = 0).
func (csdb *CommitStateDB) Empty(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
return so == nil || so.empty()
}
// Exist reports whether the given account address exists in the state. Notably,
// this also returns true for suicided accounts.
func (csdb *CommitStateDB) Exist(addr ethcmn.Address) bool {
return csdb.getStateObject(addr) != nil
}
// Error returns the first non-nil error the StateDB encountered.
func (csdb *CommitStateDB) Error() error {
return csdb.dbErr
}
// Suicide marks the given account as suicided and clears the account balance.
//
// The account's state object is still available until the state is committed,
// getStateObject will return a non-nil account after Suicide.
func (csdb *CommitStateDB) Suicide(addr ethcmn.Address) bool {
so := csdb.getStateObject(addr)
if so == nil {
return false
}
csdb.journal.append(suicideChange{
account: &addr,
prev: so.suicided,
prevBalance: sdk.NewIntFromBigInt(so.Balance()),
})
so.markSuicided()
so.SetBalance(new(big.Int))
return true
}
// Reset clears out all ephemeral state objects from the state db, but keeps
// the underlying account mapper and store keys to avoid reloading data for the
// next operations.
func (csdb *CommitStateDB) Reset(root ethcmn.Hash) error {
csdb.stateObjects = make(map[ethcmn.Address]*stateObject)
csdb.stateObjectsDirty = make(map[ethcmn.Address]struct{})
csdb.thash = ethcmn.Hash{}
csdb.bhash = ethcmn.Hash{}
csdb.txIndex = 0
csdb.logs = make(map[ethcmn.Hash][]*ethtypes.Log)
csdb.logSize = 0
csdb.preimages = make(map[ethcmn.Hash][]byte)
csdb.clearJournalAndRefund()
return nil
}
func (csdb *CommitStateDB) clearJournalAndRefund() {
csdb.journal = newJournal()
csdb.validRevisions = csdb.validRevisions[:0]
csdb.refund = 0
}
// Prepare sets the current transaction hash and index and block hash which is
// used when the EVM emits new state logs.
func (csdb *CommitStateDB) Prepare(thash, bhash ethcmn.Hash, txi int) {
csdb.thash = thash
csdb.bhash = bhash
csdb.txIndex = txi
}
// CreateAccount explicitly creates a state object. If a state object with the
// address already exists the balance is carried over to the new account.
//
// CreateAccount is called during the EVM CREATE operation. The situation might
// arise that a contract does the following:
//
// 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
//
// Carrying over the balance ensures that Ether doesn't disappear.
func (csdb *CommitStateDB) CreateAccount(addr ethcmn.Address) {
newobj, prevobj := csdb.createObject(addr)
if prevobj != nil {
newobj.setBalance(sdk.NewIntFromBigInt(prevobj.Balance()))
}
}
// Copy creates a deep, independent copy of the state.
//
// NOTE: Snapshots of the copied state cannot be applied to the copy.
func (csdb *CommitStateDB) Copy() ethstate.StateDB {
csdb.lock.Lock()
defer csdb.lock.Unlock()
// copy all the basic fields, initialize the memory ones
state := &CommitStateDB{
ctx: csdb.ctx,
ak: csdb.ak,
storageKey: csdb.storageKey,
codeKey: csdb.codeKey,
stateObjects: make(map[ethcmn.Address]*stateObject, len(csdb.journal.dirties)),
stateObjectsDirty: make(map[ethcmn.Address]struct{}, len(csdb.journal.dirties)),
refund: csdb.refund,
logs: make(map[ethcmn.Hash][]*ethtypes.Log, len(csdb.logs)),
logSize: csdb.logSize,
preimages: make(map[ethcmn.Hash][]byte),
journal: newJournal(),
}
// copy the dirty states, logs, and preimages
for addr := range csdb.journal.dirties {
// There is a case where an object is in the journal but not in the
// stateObjects: OOG after touch on ripeMD prior to Byzantium. Thus, we
// need to check for nil.
//
// Ref: https://github.com/ethereum/go-ethereum/pull/16485#issuecomment-380438527
if object, exist := csdb.stateObjects[addr]; exist {
state.stateObjects[addr] = object.deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{}
}
}
// Above, we don't copy the actual journal. This means that if the copy is
// copied, the loop above will be a no-op, since the copy's journal is empty.
// Thus, here we iterate over stateObjects, to enable copies of copies.
for addr := range csdb.stateObjectsDirty {
if _, exist := state.stateObjects[addr]; !exist {
state.stateObjects[addr] = csdb.stateObjects[addr].deepCopy(state)
state.stateObjectsDirty[addr] = struct{}{}
}
}
// copy logs
for hash, logs := range csdb.logs {
cpy := make([]*ethtypes.Log, len(logs))
for i, l := range logs {
cpy[i] = new(ethtypes.Log)
*cpy[i] = *l
}
state.logs[hash] = cpy
}
// copy pre-images
for hash, preimage := range csdb.preimages {
state.preimages[hash] = preimage
}
return state
}
// ForEachStorage iterates over each storage items, all invokes the provided
// callback on each key, value pair .
func (csdb *CommitStateDB) ForEachStorage(addr ethcmn.Address, cb func(key, value ethcmn.Hash) bool) {
so := csdb.getStateObject(addr)
if so == nil {
return
}
store := csdb.ctx.KVStore(csdb.storageKey)
iter := sdk.KVStorePrefixIterator(store, so.Address().Bytes())
for ; iter.Valid(); iter.Next() {
key := ethcmn.BytesToHash(iter.Key())
value := iter.Value()
if value, dirty := so.dirtyStorage[key]; dirty {
cb(key, value)
continue
}
cb(key, ethcmn.BytesToHash(value))
}
iter.Close()
}
// GetOrNewStateObject retrieves a state object or create a new state object if
// nil.
func (csdb *CommitStateDB) GetOrNewStateObject(addr ethcmn.Address) ethstate.StateObject {
so := csdb.getStateObject(addr)
if so == nil || so.deleted {
so, _ = csdb.createObject(addr)
}
return so
}
// createObject creates a new state object. If there is an existing account with
// the given address, it is overwritten and returned as the second return value.
func (csdb *CommitStateDB) createObject(addr ethcmn.Address) (newObj, prevObj *stateObject) {
prevObj = csdb.getStateObject(addr)
acc := csdb.ak.NewAccountWithAddress(csdb.ctx, sdk.AccAddress(addr.Bytes()))
newObj = newObject(csdb, acc)
newObj.setNonce(0) // sets the object to dirty
if prevObj == nil {
csdb.journal.append(createObjectChange{account: &addr})
} else {
csdb.journal.append(resetObjectChange{prev: prevObj})
}
csdb.setStateObject(newObj)
return newObj, prevObj
}
// setError remembers the first non-nil error it is called with.
func (csdb *CommitStateDB) setError(err error) {
if csdb.dbErr == nil {
csdb.dbErr = err
}
}
// getStateObject attempts to retrieve a state object given by the address.
// Returns nil and sets an error if not found.
func (csdb *CommitStateDB) getStateObject(addr ethcmn.Address) (stateObject *stateObject) {
// prefer 'live' (cached) objects
if so := csdb.stateObjects[addr]; so != nil {
if so.deleted {
return nil
}
return so
}
// otherwise, attempt to fetch the account from the account mapper
acc := csdb.ak.GetAccount(csdb.ctx, addr.Bytes())
if acc == nil {
csdb.setError(fmt.Errorf("no account found for address: %X", addr.Bytes()))
return nil
}
// insert the state object into the live set
so := newObject(csdb, acc)
csdb.setStateObject(so)
return so
}
func (csdb *CommitStateDB) setStateObject(so *stateObject) {
csdb.stateObjects[so.Address()] = so
}