Merge pull request #488 from cosmos/bez/486-statedb-refactor
R4R: StateDB Integration/EVM Module Start
This commit is contained in:
commit
abbe17db25
@ -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
352
Gopkg.lock
generated
@ -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",
|
||||
|
31
Gopkg.toml
31
Gopkg.toml
@ -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
|
||||
|
8
Makefile
8
Makefile
@ -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
|
||||
|
33
README.md
33
README.md
@ -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
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
}
|
@ -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
3
docs/spec/evm/README.md
Normal file
@ -0,0 +1,3 @@
|
||||
# EVM
|
||||
|
||||
TODO
|
@ -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))
|
||||
}
|
||||
|
@ -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
302
importer/importer_test.go
Normal 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)
|
||||
}
|
@ -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{}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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
|
||||
}
|
208
state/trie.go
208
state/trie.go
@ -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
|
||||
}
|
@ -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()
|
||||
}
|
@ -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
|
@ -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(ðvm.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)
|
||||
}
|
@ -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
|
||||
}
|
@ -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)
|
||||
}
|
81
test/run.go
81
test/run.go
@ -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()
|
||||
}
|
104
types/account.go
104
types/account.go
@ -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
|
||||
}
|
||||
|
@ -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 {
|
||||
|
21
types/tx.go
21
types/tx.go
@ -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{}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
|
12
x/evm/types/dump.go
Normal file
12
x/evm/types/dump.go
Normal 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
227
x/evm/types/journal.go
Normal 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
366
x/evm/types/state_object.go
Normal 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
703
x/evm/types/statedb.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user