diff --git a/Cargo.lock b/Cargo.lock index 47ca4e48ad9..205eb7f5ae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10,6 +10,15 @@ dependencies = [ "regex", ] +[[package]] +name = "addr2line" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a49806b9dadc843c61e7c97e72490ad7f7220ae249012fbda9ad0609457c0543" +dependencies = [ + "gimli", +] + [[package]] name = "aho-corasick" version = "0.7.10" @@ -28,6 +37,12 @@ dependencies = [ "winapi 0.3.8", ] +[[package]] +name = "anyhow" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85bb70cc08ec97ca5450e6eba421deeea5f172c0fc61f78b5357b2a8e8be195f" + [[package]] name = "arc-swap" version = "0.4.5" @@ -108,26 +123,17 @@ checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" [[package]] name = "backtrace" -version = "0.3.46" +version = "0.3.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e" +checksum = "0df2f85c8a2abbe3b7d7e748052fdd9b76a0458fdeb16ad4223f5eca78c7c130" dependencies = [ - "backtrace-sys", + "addr2line", "cfg-if", "libc", + "object 0.19.0", "rustc-demangle", ] -[[package]] -name = "backtrace-sys" -version = "0.1.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "base64" version = "0.6.0" @@ -172,6 +178,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bincode" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5753e2a71534719bf3f4e57006c3a4f0d2c672a4b676eec84161f763eca87dbf" +dependencies = [ + "byteorder", + "serde", +] + [[package]] name = "bitflags" version = "0.9.1" @@ -326,6 +342,9 @@ name = "cc" version = "1.0.53" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "404b1fe4f65288577753b17e3b36a04596ee784493ec249bf81c7f2d2acd751c" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -427,6 +446,90 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" +[[package]] +name = "cranelift-bforest" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "byteorder", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "gimli", + "log 0.4.8", + "regalloc", + "serde", + "smallvec 1.2.0", + "target-lexicon", + "thiserror", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "cranelift-codegen-shared", + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" + +[[package]] +name = "cranelift-entity" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "cranelift-codegen", + "log 0.4.8", + "smallvec 1.2.0", + "target-lexicon", +] + +[[package]] +name = "cranelift-native" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "cranelift-codegen", + "raw-cpuid", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.64.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "log 0.4.8", + "serde", + "thiserror", + "wasmparser", +] + [[package]] name = "crossbeam" version = "0.7.3" @@ -574,6 +677,12 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11c0346158a19b3627234e15596f5e465c360fcdb97d817bcb255e0510f5a788" +[[package]] +name = "defer" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "647605a6345d5e89c3950a36a638c56478af9b414c55c6f2477c73b115f9acde" + [[package]] name = "derive_more" version = "0.99.7" @@ -682,6 +791,16 @@ dependencies = [ "generic-array 0.12.3", ] +[[package]] +name = "directories" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "551a778172a450d7fc12e629ca3b0428d00f6afa9a43da1b630d54604e97371c" +dependencies = [ + "cfg-if", + "dirs-sys", +] + [[package]] name = "dirs" version = "2.0.2" @@ -762,6 +881,27 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f4b14e20978669064c33b4c1e0fb4083412e40fe56cbea2eae80fd7591503ee" +[[package]] +name = "errno" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b480f641ccf0faf324e20c1d3e53d81b7484c698b42ea677f6907ae4db195371" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi 0.3.8", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14ca354e36190500e1e1fb267c647932382b54053c50b14970856c0b00a35067" +dependencies = [ + "gcc", + "libc", +] + [[package]] name = "ethabi" version = "12.0.0" @@ -816,6 +956,21 @@ dependencies = [ "uint", ] +[[package]] +name = "faerie" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfef65b0e94693295c5d2fe2506f0ee6f43465342d4b5331659936aee8b16084" +dependencies = [ + "goblin", + "indexmap", + "log 0.4.8", + "scroll", + "string-interner", + "target-lexicon", + "thiserror", +] + [[package]] name = "failure" version = "0.1.7" @@ -850,6 +1005,22 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb7217124812dc5672b7476d0c2d20cfe9f7c0f1ba0904b674a9762a0212f72e" +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "file-per-thread-logger" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b3937f028664bd0e13df401ba49a4567ccda587420365823242977f06609ed1" +dependencies = [ + "env_logger", + "log 0.4.8", +] + [[package]] name = "fixed-hash" version = "0.6.1" @@ -1040,6 +1211,12 @@ dependencies = [ "slab 0.4.2", ] +[[package]] +name = "gcc" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" + [[package]] name = "generic-array" version = "0.9.0" @@ -1069,6 +1246,17 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" +dependencies = [ + "fallible-iterator 0.2.0", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "git-testament" version = "0.1.9" @@ -1091,6 +1279,12 @@ dependencies = [ "syn 1.0.17", ] +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + [[package]] name = "globset" version = "0.4.5" @@ -1104,11 +1298,23 @@ dependencies = [ "regex", ] +[[package]] +name = "goblin" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3081214398d39e4bd7f2c1975f0488ed04614ffdd976c6fc7a0708278552c0da" +dependencies = [ + "log 0.4.8", + "plain", + "scroll", +] + [[package]] name = "graph" version = "0.18.0" dependencies = [ "Inflector", + "anyhow", "async-trait", "atomic_refcell", "bigdecimal", @@ -1284,6 +1490,7 @@ dependencies = [ "atomic_refcell", "bs58", "bytes 0.5.4", + "defer", "ethabi 12.0.0 (git+https://github.com/graphprotocol/ethabi.git)", "futures 0.1.29", "graph", @@ -1297,14 +1504,13 @@ dependencies = [ "ipfs-api", "lazy_static", "maybe-owned", - "parity-wasm", "pwasm-utils", "semver", "strum", "strum_macros", "test-store", "uuid 0.8.1", - "wasmi", + "wasmtime", ] [[package]] @@ -1390,7 +1596,7 @@ dependencies = [ "diesel-dynamic-schema", "diesel_migrations", "failure", - "fallible-iterator", + "fallible-iterator 0.1.6", "futures 0.1.29", "graph", "graph-chain-ethereum", @@ -1402,7 +1608,6 @@ dependencies = [ "lazy_static", "lru_time_cache 0.9.0", "maybe-owned", - "parity-wasm", "postgres", "serde", "stable-hash", @@ -1818,6 +2023,15 @@ version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e" +[[package]] +name = "jobserver" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c71313ebb9439f74b00d9d2dcec36440beaf57a6aa0623068441dd7cd81a7f2" +dependencies = [ + "libc", +] + [[package]] name = "js-sys" version = "0.3.37" @@ -1907,9 +2121,9 @@ checksum = "3576a87f2ba00f6f106fdfcd16db1d698d648a26ad8e0573cad8537c3c362d2a" [[package]] name = "libc" -version = "0.2.68" +version = "0.2.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0" +checksum = "3baa92041a6fec78c687fa0cc2b3fae8884f743d672cf551bed1d6dac6988d0f" [[package]] name = "linked-hash-map" @@ -1956,6 +2170,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb241df5c4caeb888755363fc95f8a896618dc0d435e9e775f7930cb099beab" +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -2009,12 +2232,6 @@ dependencies = [ "autocfg 1.0.0", ] -[[package]] -name = "memory_units" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71d96e3f3c0b6325d8ccd83c33b28acb183edcb6c67938ba104ec546854b0882" - [[package]] name = "migrations_internals" version = "1.4.1" @@ -2130,6 +2347,12 @@ dependencies = [ "syn 1.0.17", ] +[[package]] +name = "more-asserts" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0debeb9fcf88823ea64d64e4a815ab1643f33127d995978e099942ce38f25238" + [[package]] name = "native-tls" version = "0.2.4" @@ -2187,18 +2410,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-rational" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" -dependencies = [ - "autocfg 1.0.0", - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.11" @@ -2218,6 +2429,21 @@ dependencies = [ "libc", ] +[[package]] +name = "object" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5666bbb90bc4d1e5bdcb26c0afda1822d25928341e9384ab187a9b37ab69e36" +dependencies = [ + "target-lexicon", +] + +[[package]] +name = "object" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cbca9424c482ee628fa549d9c812e2cd22f1180b9222c9200fdfa6eb31aecb2" + [[package]] name = "once_cell" version = "1.3.1" @@ -2477,6 +2703,12 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "postgres" version = "0.15.2" @@ -2484,7 +2716,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115dde90ef51af573580c035857badbece2aa5cde3de1dfb3c932969ca92a6c5" dependencies = [ "bytes 0.4.12", - "fallible-iterator", + "fallible-iterator 0.1.6", "log 0.4.8", "postgres-protocol", "postgres-shared", @@ -2500,7 +2732,7 @@ dependencies = [ "base64 0.6.0", "byteorder", "bytes 0.4.12", - "fallible-iterator", + "fallible-iterator 0.1.6", "generic-array 0.9.0", "hmac", "md5", @@ -2516,7 +2748,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ffac35b3e0029b404c24a3b82149b4e904f293e8ca4a327eefa24d3ca50df36f" dependencies = [ - "fallible-iterator", + "fallible-iterator 0.1.6", "hex 0.2.0", "phf", "postgres-protocol", @@ -2880,6 +3112,41 @@ dependencies = [ "rand_core 0.3.1", ] +[[package]] +name = "raw-cpuid" +version = "7.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4a349ca83373cfa5d6dbb66fd76e58b2cca08da71a5f6400de0a0a6a9bceeaf" +dependencies = [ + "bitflags 1.2.1", + "cc", + "rustc_version", +] + +[[package]] +name = "rayon" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098" +dependencies = [ + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9" +dependencies = [ + "crossbeam-deque", + "crossbeam-queue", + "crossbeam-utils", + "lazy_static", + "num_cpus", +] + [[package]] name = "rdrand" version = "0.4.0" @@ -2906,6 +3173,17 @@ dependencies = [ "rust-argon2", ] +[[package]] +name = "regalloc" +version = "0.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cca5b48c9db66c5ba084e4660b4c0cfe8b551a96074bc04b7c11de86ad0bf1f9" +dependencies = [ + "log 0.4.8", + "rustc-hash", + "smallvec 1.2.0", +] + [[package]] name = "regex" version = "1.3.6" @@ -2924,6 +3202,18 @@ version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags 1.2.1", + "libc", + "mach", + "winapi 0.3.8", +] + [[package]] name = "remove_dir_all" version = "0.5.2" @@ -3001,6 +3291,12 @@ version = "0.1.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc-hex" version = "2.1.0" @@ -3074,6 +3370,26 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scroll" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb2332cb595d33f7edd5700f4cbf94892e680c7f0ae56adab58a35190b66cb1" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e367622f934864ffa1c704ba2b82280aab856e3d8213c84c5720257eb34b15b9" +dependencies = [ + "proc-macro2 1.0.9", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "secp256k1" version = "0.17.2" @@ -3379,6 +3695,12 @@ dependencies = [ "num-traits", ] +[[package]] +name = "stable_deref_trait" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" + [[package]] name = "state_machine_future" version = "0.2.0" @@ -3405,6 +3727,15 @@ dependencies = [ "bytes 0.4.12", ] +[[package]] +name = "string-interner" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd710eadff449a1531351b0e43eb81ea404336fa2f56c777427ab0e32a4cf183" +dependencies = [ + "serde", +] + [[package]] name = "stringprep" version = "0.1.2" @@ -3485,6 +3816,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f764005d11ee5f36500a149ace24e00e3da98b0158b3e2d53a7495660d3f4d60" +[[package]] +name = "target-lexicon" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab0e7238dcc7b40a7be719a25365910f6807bd864f4cce6b2e6b873658e2b19d" + [[package]] name = "tempfile" version = "3.1.0" @@ -3541,6 +3878,26 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "thiserror" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5976891d6950b4f68477850b5b9e5aa64d955961466f9e174363f573e54e8ca7" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81dbd1cd69cd2ce22ecfbdd3bdb73334ba25350649408cc6c085f46d89573d" +dependencies = [ + "proc-macro2 1.0.9", + "quote 1.0.3", + "syn 1.0.17", +] + [[package]] name = "thread_local" version = "1.0.1" @@ -3909,6 +4266,15 @@ dependencies = [ "tokio 0.2.21", ] +[[package]] +name = "toml" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" +dependencies = [ + "serde", +] + [[package]] name = "tower-service" version = "0.3.0" @@ -4234,26 +4600,156 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] -name = "wasmi" -version = "0.5.1" +name = "wasmparser" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31d26deb2d9a37e6cfed420edce3ed604eab49735ba89035e13c98f9a528313" +checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" + +[[package]] +name = "wasmtime" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ + "anyhow", + "backtrace", + "cfg-if", + "lazy_static", "libc", - "memory_units", - "num-rational", - "num-traits", - "parity-wasm", - "wasmi-validation", + "log 0.4.8", + "region", + "rustc-demangle", + "target-lexicon", + "wasmparser", + "wasmtime-environ", + "wasmtime-jit", + "wasmtime-profiling", + "wasmtime-runtime", + "wat", + "winapi 0.3.8", ] [[package]] -name = "wasmi-validation" -version = "0.2.0" +name = "wasmtime-debug" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "anyhow", + "faerie", + "gimli", + "more-asserts", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "anyhow", + "base64 0.12.1", + "bincode", + "cranelift-codegen", + "cranelift-entity", + "cranelift-wasm", + "directories", + "errno", + "file-per-thread-logger", + "indexmap", + "libc", + "log 0.4.8", + "more-asserts", + "rayon", + "serde", + "sha2 0.8.1", + "thiserror", + "toml", + "wasmparser", + "winapi 0.3.8", + "zstd", +] + +[[package]] +name = "wasmtime-jit" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "anyhow", + "cfg-if", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log 0.4.8", + "more-asserts", + "region", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-debug", + "wasmtime-environ", + "wasmtime-profiling", + "wasmtime-runtime", + "winapi 0.3.8", +] + +[[package]] +name = "wasmtime-profiling" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "anyhow", + "cfg-if", + "gimli", + "lazy_static", + "libc", + "object 0.18.0", + "scroll", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-runtime", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.17.0" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" +dependencies = [ + "backtrace", + "cc", + "cfg-if", + "indexmap", + "lazy_static", + "libc", + "memoffset", + "more-asserts", + "region", + "thiserror", + "wasmtime-environ", + "winapi 0.3.8", +] + +[[package]] +name = "wast" +version = "17.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bc0356e3df56e639fc7f7d8a99741915531e27ed735d911ed83d7e1339c8188" +checksum = "5a0e1c36b928fca33dbaf96235188f5fad22ee87100e26cc606bd0fbabdf1932" dependencies = [ - "parity-wasm", + "leb128", +] + +[[package]] +name = "wat" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b50f9e5e5c81e6fd987ae6997a9f4bbb751df2dec1d8cadb0b5778f1ec13bbe" +dependencies = [ + "wast", ] [[package]] @@ -4396,3 +4892,33 @@ name = "zeroize" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3cbac2ed2ba24cc90f5e06485ac8c7c1e5449fe8911aef4d8877218af021a5b8" + +[[package]] +name = "zstd" +version = "0.5.1+zstd.1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c5d978b793ae64375b80baf652919b148f6a496ac8802922d9999f5a553194f" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "2.0.3+zstd.1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee25eac9753cfedd48133fa1736cbd23b774e253d89badbeac7d12b23848d3f" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "1.4.15+zstd.1.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89719b034dc22d240d5b407fb0a3fe6d29952c181cff9a9f95c0bd40b4f8f7d8" +dependencies = [ + "cc", + "glob", + "libc", +] diff --git a/core/src/subgraph/instance.rs b/core/src/subgraph/instance.rs index dd199b7cc2b..e27c8580586 100644 --- a/core/src/subgraph/instance.rs +++ b/core/src/subgraph/instance.rs @@ -92,7 +92,7 @@ where data_source: DataSource, top_level_templates: Arc>, host_metrics: Arc, - ) -> Result { + ) -> Result { let mapping_request_sender = { let module_bytes = data_source.mapping.runtime.as_ref(); let module_hash = tiny_keccak::keccak256(module_bytes); @@ -100,7 +100,7 @@ where sender.clone() } else { let sender = T::spawn_mapping( - module_bytes, + module_bytes.clone(), logger, self.subgraph_id.clone(), host_metrics.clone(), @@ -109,14 +109,16 @@ where sender } }; - self.host_builder.build( - self.network.clone(), - self.subgraph_id.clone(), - data_source, - top_level_templates, - mapping_request_sender, - host_metrics, - ) + self.host_builder + .build( + self.network.clone(), + self.subgraph_id.clone(), + data_source, + top_level_templates, + mapping_request_sender, + host_metrics, + ) + .compat_err() } } @@ -137,7 +139,7 @@ where trigger: EthereumTrigger, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { Self::process_trigger_in_runtime_hosts( logger, &self.hosts, @@ -156,7 +158,7 @@ where trigger: EthereumTrigger, mut state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { match trigger { EthereumTrigger::Log(log) => { let log = Arc::new(log); @@ -164,7 +166,7 @@ where let transaction = block .transaction_for_log(&log) .map(Arc::new) - .ok_or_else(|| format_err!("Found no transaction for event"))?; + .context("Found no transaction for event")?; let matching_hosts = hosts.iter().filter(|host| host.matches_log(&log)); // Process the log in each host in the same order the corresponding data // sources appear in the subgraph manifest @@ -187,7 +189,7 @@ where let transaction = block .transaction_for_call(&call) - .ok_or_else(|| format_err!("Found no transaction for call"))?; + .context("Found no transaction for call")?; let transaction = Arc::new(transaction); let matching_hosts = hosts.iter().filter(|host| host.matches_call(&call)); @@ -230,14 +232,14 @@ where data_source: DataSource, top_level_templates: Arc>, metrics: Arc, - ) -> Result, Error> { + ) -> Result, anyhow::Error> { // Protect against creating more than the allowed maximum number of data sources if let Some(max_data_sources) = *MAX_DATA_SOURCES { if self.hosts.len() >= max_data_sources { - return Err(format_err!( + anyhow::bail!( "Limit of {} data sources per subgraph exceeded", - max_data_sources - )); + max_data_sources, + ); } } diff --git a/core/src/subgraph/instance_manager.rs b/core/src/subgraph/instance_manager.rs index 1b21499f521..bc3c840ce7c 100644 --- a/core/src/subgraph/instance_manager.rs +++ b/core/src/subgraph/instance_manager.rs @@ -692,7 +692,8 @@ where &mut ctx, host_metrics.clone(), block_state.created_data_sources.drain(..), - )?; + ) + .compat_err()?; // Reprocess the triggers from this block that match the new data sources let block_with_triggers = triggers_in_block( @@ -734,7 +735,6 @@ where // Process the triggers in each host in the same order the // corresponding data sources have been created. - for trigger in triggers.into_iter() { block_state = SubgraphInstance::::process_trigger_in_runtime_hosts( &logger, @@ -744,7 +744,8 @@ where block_state, proof_of_indexing.cheap_clone(), ) - .await?; + .await + .compat_err()?; } } @@ -908,12 +909,12 @@ async fn process_triggers format_err!( - "Failed to process trigger in block {}, transaction {:x}: {}", + "Failed to process trigger in block {}, transaction {:x}: {:#}", block_ptr, tx_hash, e ), - None => format_err!("Failed to process trigger: {}", e), + None => format_err!("Failed to process trigger: {:#}", e), })?; let elapsed = start.elapsed().as_secs_f64(); subgraph_metrics.observe_trigger_processing_duration(elapsed, trigger_type); @@ -926,7 +927,7 @@ fn create_dynamic_data_sources( ctx: &mut IndexingContext, host_metrics: Arc, created_data_sources: impl Iterator, -) -> Result<(Vec, Vec>), Error> +) -> Result<(Vec, Vec>), anyhow::Error> where B: BlockStreamBuilder, S: ChainStore + Store + SubgraphDeploymentStore + EthereumCallCache, diff --git a/graph/Cargo.toml b/graph/Cargo.toml index 6a5ca73e960..8890ea3471c 100644 --- a/graph/Cargo.toml +++ b/graph/Cargo.toml @@ -4,6 +4,7 @@ version = "0.18.0" edition = "2018" [dependencies] +anyhow = "1.0" async-trait = "0.1.31" atomic_refcell = "0.1.6" bigdecimal = { version = "0.1.0", features = ["serde"] } diff --git a/graph/src/components/subgraph/host.rs b/graph/src/components/subgraph/host.rs index 75d5771126f..11b61093923 100644 --- a/graph/src/components/subgraph/host.rs +++ b/graph/src/components/subgraph/host.rs @@ -32,7 +32,7 @@ pub trait RuntimeHost: Send + Sync + Debug + 'static { log: &Arc, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result; + ) -> Result; /// Process an Ethereum call and return a vector of entity operations async fn process_call( @@ -43,7 +43,7 @@ pub trait RuntimeHost: Send + Sync + Debug + 'static { call: &Arc, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result; + ) -> Result; /// Process an Ethereum block and return a vector of entity operations async fn process_block( @@ -53,7 +53,7 @@ pub trait RuntimeHost: Send + Sync + Debug + 'static { trigger_type: &EthereumBlockTriggerType, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result; + ) -> Result; } pub struct HostMetrics { @@ -131,9 +131,9 @@ pub trait RuntimeHostBuilder: Clone + Send + Sync + 'static { /// Spawn a mapping and return a channel for mapping requests. The sender should be able to be /// cached and shared among mappings that use the same wasm file. fn spawn_mapping( - raw_module: &[u8], + raw_module: Vec, logger: Logger, subgraph_id: SubgraphDeploymentId, metrics: Arc, - ) -> Result, Error>; + ) -> Result, anyhow::Error>; } diff --git a/graph/src/components/subgraph/instance.rs b/graph/src/components/subgraph/instance.rs index 82f7d677616..1b48a638f16 100644 --- a/graph/src/components/subgraph/instance.rs +++ b/graph/src/components/subgraph/instance.rs @@ -42,7 +42,7 @@ pub trait SubgraphInstance { trigger: EthereumTrigger, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result; + ) -> Result; /// Like `process_trigger` but processes an Ethereum event in a given list of hosts. async fn process_trigger_in_runtime_hosts( @@ -52,7 +52,7 @@ pub trait SubgraphInstance { trigger: EthereumTrigger, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result; + ) -> Result; /// Adds dynamic data sources to the subgraph. fn add_dynamic_data_source( @@ -61,5 +61,5 @@ pub trait SubgraphInstance { data_source: DataSource, top_level_templates: Arc>, metrics: Arc, - ) -> Result, Error>; + ) -> Result, anyhow::Error>; } diff --git a/graph/src/data/subgraph/mod.rs b/graph/src/data/subgraph/mod.rs index 58148bd0807..5516ca90ce9 100644 --- a/graph/src/data/subgraph/mod.rs +++ b/graph/src/data/subgraph/mod.rs @@ -27,7 +27,10 @@ use crate::data::subgraph::schema::{ EthereumContractEventHandlerEntity, EthereumContractMappingEntity, EthereumContractSourceEntity, SUBGRAPHS_ID, }; -use crate::prelude::{format_err, impl_slog_value, BlockNumber, Deserialize, Fail, Serialize}; +use crate::prelude::{ + anyhow::{self, Context}, + format_err, impl_slog_value, BlockNumber, Deserialize, Fail, Serialize, +}; use crate::util::ethereum::string_to_h256; use graphql_parser::query as q; @@ -722,9 +725,9 @@ impl UnresolvedDataSource { } impl TryFrom for DataSource { - type Error = failure::Error; + type Error = anyhow::Error; - fn try_from(info: DataSourceTemplateInfo) -> Result { + fn try_from(info: DataSourceTemplateInfo) -> Result { let DataSourceTemplateInfo { data_source: _, template, @@ -735,19 +738,18 @@ impl TryFrom for DataSource { // Obtain the address from the parameters let string = params .get(0) - .ok_or_else(|| { - format_err!( + .with_context(|| { + format!( "Failed to create data source from template `{}`: address parameter is missing", template.name ) })? .trim_start_matches("0x"); - let address = Address::from_str(string).map_err(|e| { - format_err!( - "Failed to create data source from template `{}`: invalid address provided: {}", - template.name, - e + let address = Address::from_str(string).with_context(|| { + format!( + "Failed to create data source from template `{}`, invalid address provided", + template.name ) })?; diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 0c77b822ef8..59bddf8af0c 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -41,10 +41,11 @@ pub use url; /// ``` pub mod prelude { pub use super::entity; + pub use anyhow::{self, Context as _}; pub use async_trait::async_trait; pub use bigdecimal; pub use ethabi; - pub use failure::{self, bail, err_msg, format_err, Error, Fail, SyncFailure}; + pub use failure::{self, err_msg, format_err, Error, Fail, SyncFailure}; pub use futures::future; pub use futures::prelude::*; pub use futures::stream; @@ -146,5 +147,6 @@ pub mod prelude { ComponentLoggerConfig, ElasticComponentLoggerConfig, LoggerFactory, }; pub use crate::log::split::split_logger; + pub use crate::util::error::CompatErr; pub use crate::util::futures::{retry, TimeoutError}; } diff --git a/graph/src/task_spawn.rs b/graph/src/task_spawn.rs index b340c29cbd4..dbd4eec9a9f 100644 --- a/graph/src/task_spawn.rs +++ b/graph/src/task_spawn.rs @@ -1,4 +1,15 @@ -use futures03::executor::block_on; +//! The functions in this module should be used to execute futures, serving as a facade to the +//! underlying executor implementation which currently is tokio. This serves a few purposes: +//! - Avoid depending directly on tokio APIs, making upgrades or a potential switch easier. +//! - Reflect our chosen default semantics of aborting on task panic, offering `*_allow_panic` +//! functions to opt out of that. +//! - Reflect that historically we've used blocking futures due to making DB calls directly within +//! futures. This point should go away once https://github.com/graphprotocol/graph-node/issues/905 +//! is resolved. Then the blocking flavors should no longer accept futures but closures. +//! +//! These should not be called from within executors other than tokio, particularly the blocking +//! functions will panic in that case. We should generally avoid mixing executors whenever possible. + use futures03::future::{FutureExt, TryFutureExt}; use std::future::Future as Future03; use std::panic::AssertUnwindSafe; @@ -14,6 +25,11 @@ fn abort_on_panic( }) } +/// Runs the future on the current thread. Panics if not within a tokio runtime. +fn block_on(f: impl Future03) -> T { + tokio::runtime::Handle::current().block_on(f) +} + /// Aborts on panic. pub fn spawn(f: impl Future03 + Send + 'static) -> JoinHandle { tokio::spawn(abort_on_panic(f)) @@ -46,30 +62,6 @@ pub async fn spawn_blocking_async_allow_panic( tokio::task::spawn_blocking(f).await.unwrap() } -/// Panics if there is no current tokio::Runtime -pub fn block_on_allow_panic(f: impl Future03 + Send) -> T { - let rt = tokio::runtime::Handle::current(); - - rt.enter(move || { - // TODO: It should be possible to just call block_on directly, but we - // ran into this bug https://github.com/rust-lang/futures-rs/issues/2090 - // which will panic if we're already in block_on. So, detect if this - // function would panic. Once we fix this, we can remove the requirement - // of + Send for f (and few functions that call this that had +Send - // added) The test which ran into this bug was - // runtime::wasm::test::ipfs_fail - let enter = futures03::executor::enter(); - let oh_no = enter.is_err(); - drop(enter); - - // If the function would panic, fallback to the old ways. - if oh_no { - use futures::future::Future as _; - let compat = async { Result::::Ok(f.await) }.boxed().compat(); - // This unwrap does not panic because of the above line always returning Ok - compat.wait().unwrap() - } else { - block_on(f) - } - }) +pub fn block_on_allow_panic(f: impl Future03) -> T { + block_on(f) } diff --git a/graph/src/util/error.rs b/graph/src/util/error.rs new file mode 100644 index 00000000000..dfcb7f2c6e2 --- /dev/null +++ b/graph/src/util/error.rs @@ -0,0 +1,31 @@ +// Converts back and forth between `failure::Error` and `anyhow::Error` +// while we don't migrate fully to `anyhow`. +pub trait CompatErr { + type Other; + fn compat_err(self) -> Self::Other; +} + +impl CompatErr for failure::Error { + type Other = anyhow::Error; + + fn compat_err(self) -> anyhow::Error { + anyhow::Error::from(self.compat()) + } +} + +impl CompatErr for anyhow::Error { + type Other = failure::Error; + + fn compat_err(self) -> failure::Error { + // Convert as a single error containing all the causes. + failure::err_msg(format!("{:#}", self)) + } +} + +impl CompatErr for Result { + type Other = Result; + + fn compat_err(self) -> Self::Other { + self.map_err(CompatErr::compat_err) + } +} diff --git a/graph/src/util/mod.rs b/graph/src/util/mod.rs index a6ea2c8dede..0204952de7b 100644 --- a/graph/src/util/mod.rs +++ b/graph/src/util/mod.rs @@ -8,3 +8,5 @@ pub mod ethereum; pub mod security; pub mod lfu_cache; + +pub mod error; diff --git a/graphql/src/schema/ast.rs b/graphql/src/schema/ast.rs index 8811ace9a49..e9e51ef5a40 100644 --- a/graphql/src/schema/ast.rs +++ b/graphql/src/schema/ast.rs @@ -478,17 +478,15 @@ pub fn validate_entity( schema: &Document, key: &EntityKey, entity: &Entity, -) -> Result<(), graph::prelude::Error> { +) -> Result<(), anyhow::Error> { let object_type_definitions = get_object_type_definitions(schema); let object_type = object_type_definitions .iter() .find(|object_type| object_type.name == key.entity_type) - .ok_or_else(|| { - format_err!( + .with_context(|| { + format!( "Entity {}[{}]: unknown entity type `{}`", - key.entity_type, - key.entity_id, - key.entity_type + key.entity_type, key.entity_id, key.entity_type ) })?; @@ -504,21 +502,23 @@ pub fn validate_entity( if let store::Value::List(elts) = value { for (index, elt) in elts.iter().enumerate() { if !is_assignable(elt, &scalar_type, false) { - return Err(format_err!("Entity {}[{}]: field `{}` is of type {}, but the value `{}` contains a {} at index {}", - key.entity_type, - key.entity_id, - field.name, - &field.field_type, - value, - elt.type_name(), - index - )); + anyhow::bail!( + "Entity {}[{}]: field `{}` is of type {}, but the value `{}` \ + contains a {} at index {}", + key.entity_type, + key.entity_id, + field.name, + &field.field_type, + value, + elt.type_name(), + index + ); } } } } if !is_assignable(value, &scalar_type, is_list(&field.field_type)) { - return Err(format_err!( + anyhow::bail!( "Entity {}[{}]: the value `{}` for field `{}` must have type {} but has type {}", key.entity_type, key.entity_id, @@ -526,26 +526,26 @@ pub fn validate_entity( field.name, &field.field_type, value.type_name() - )); + ); } } (None, false) => { if is_non_null_type(&field.field_type) { - return Err(format_err!( + anyhow::bail!( "Entity {}[{}]: missing value for non-nullable field `{}`", key.entity_type, key.entity_id, - field.name - )); + field.name, + ); } } (Some(_), true) => { - return Err(format_err!( + anyhow::bail!( "Entity {}[{}]: field `{}` is derived and can not be set", key.entity_type, key.entity_id, field.name, - )); + ); } (None, true) => { // derived fields should not be set diff --git a/graphql/src/subscription/mod.rs b/graphql/src/subscription/mod.rs index 23d7c8d320d..c97bb5bb752 100644 --- a/graphql/src/subscription/mod.rs +++ b/graphql/src/subscription/mod.rs @@ -167,7 +167,7 @@ fn map_source_to_response_stream( resolver.clone(), query.clone(), event, - timeout.clone(), + timeout, max_first, ) .boxed(), diff --git a/runtime/wasm/Cargo.toml b/runtime/wasm/Cargo.toml index 068a85b2eac..19d0c6b9290 100644 --- a/runtime/wasm/Cargo.toml +++ b/runtime/wasm/Cargo.toml @@ -11,12 +11,10 @@ futures = "0.1.21" hex = "0.4.2" graph = { path = "../../graph" } graph-graphql = { path = "../../graphql" } -wasmi = "0.5.1" pwasm-utils = "0.11" bs58 = "0.3.1" graph-runtime-derive = { path = "../derive" } semver = "0.9.0" -parity-wasm = "0.40" lazy_static = "1.4" uuid = { version = "0.8.1", features = ["v4"] } strum = "0.18.0" @@ -27,6 +25,11 @@ bytes = "0.5" # See also 92cd8019-0136-4011-96a0-40b3eec37f73 maybe-owned = { git = "https://github.com/rustonaut/maybe-owned", branch = "master" } +# Use a released version once 0.18 is released. +wasmtime = { git = "https://github.com/bytecodealliance/wasmtime" } + +defer = "0.1" + [dev-dependencies] graphql-parser = "0.2.3" graph-core = { path = "../../core" } diff --git a/runtime/wasm/src/asc_abi/asc_ptr.rs b/runtime/wasm/src/asc_abi/asc_ptr.rs index 5ccccefd08e..c3c62438249 100644 --- a/runtime/wasm/src/asc_abi/asc_ptr.rs +++ b/runtime/wasm/src/asc_abi/asc_ptr.rs @@ -2,7 +2,6 @@ use super::{class::EnumPayload, AscHeap, AscType, AscValue}; use std::fmt; use std::marker::PhantomData; use std::mem::size_of; -use wasmi::{FromRuntimeValue, RuntimeValue}; /// A pointer to an object in the Asc heap. pub struct AscPtr(u32, PhantomData); @@ -27,6 +26,13 @@ impl fmt::Debug for AscPtr { } } +impl AscPtr { + /// A raw pointer to be passed to Wasm. + pub(crate) fn wasm_ptr(self) -> u32 { + self.0 + } +} + impl AscPtr { /// Create a pointer that is equivalent to AssemblyScript's `null`. pub(crate) fn null() -> Self { @@ -35,18 +41,18 @@ impl AscPtr { /// Read from `self` into the Rust struct `C`. pub(super) fn read_ptr(self, heap: &H) -> C { - C::from_asc_bytes(&heap.get(self.0, C::asc_size(self, heap)).unwrap()) + C::from_asc_bytes(&heap.get(self.0, C::asc_size(self, heap))) } /// Allocate `asc_obj` as an Asc object of class `C`. pub(super) fn alloc_obj(asc_obj: &C, heap: &mut H) -> AscPtr { - AscPtr(heap.raw_new(&asc_obj.to_asc_bytes()).unwrap(), PhantomData) + AscPtr(heap.raw_new(&asc_obj.to_asc_bytes()), PhantomData) } /// Helper used by arrays and strings to read their length. pub(super) fn read_u32(&self, heap: &H) -> u32 { // Read the bytes pointed to by `self` as the bytes of a `u32`. - let raw_bytes = heap.get(self.0, size_of::() as u32).unwrap(); + let raw_bytes = heap.get(self.0, size_of::() as u32); let mut u32_bytes: [u8; size_of::()] = [0; size_of::()]; u32_bytes.copy_from_slice(&raw_bytes); u32::from_le_bytes(u32_bytes) @@ -61,17 +67,16 @@ impl AscPtr { pub(crate) fn is_null(&self) -> bool { self.0 == 0 } -} -impl From> for RuntimeValue { - fn from(ptr: AscPtr) -> RuntimeValue { - RuntimeValue::from(ptr.0) + // Erase type information. + pub(crate) fn erase(self) -> AscPtr<()> { + AscPtr(self.0, PhantomData) } } -impl FromRuntimeValue for AscPtr { - fn from_runtime_value(val: RuntimeValue) -> Option { - u32::from_runtime_value(val).map(|ptr| AscPtr(ptr, PhantomData)) +impl From for AscPtr { + fn from(ptr: u32) -> Self { + AscPtr(ptr, PhantomData) } } diff --git a/runtime/wasm/src/asc_abi/mod.rs b/runtime/wasm/src/asc_abi/mod.rs index 39163102bbc..e3736c371bb 100644 --- a/runtime/wasm/src/asc_abi/mod.rs +++ b/runtime/wasm/src/asc_abi/mod.rs @@ -6,8 +6,8 @@ //! Implementations of `To`/`FromAscObj` live in the `to_from` module. pub use self::asc_ptr::AscPtr; +use graph::prelude::anyhow; use std::mem::size_of; -use wasmi; pub mod asc_ptr; pub mod class; @@ -23,10 +23,9 @@ compile_error!("big-endian targets are currently unsupported"); /// The implementor must provide the direct Asc interface with `raw_new` and `get`. pub trait AscHeap: Sized { /// Allocate new space and write `bytes`, return the allocated address. - fn raw_new(&mut self, bytes: &[u8]) -> Result; + fn raw_new(&mut self, bytes: &[u8]) -> u32; - /// Just like `wasmi::MemoryInstance::get`. - fn get(&self, offset: u32, size: u32) -> Result, wasmi::Error>; + fn get(&self, offset: u32, size: u32) -> Vec; /// Instatiate `rust_obj` as an Asc object of class `C`. /// Returns a pointer to the Asc heap. @@ -53,7 +52,7 @@ pub trait AscHeap: Sized { T::from_asc_obj(asc_ptr.read_ptr(self), self) } - fn try_asc_get(&self, asc_ptr: AscPtr) -> Result + fn try_asc_get(&self, asc_ptr: AscPtr) -> Result where C: AscType, T: TryFromAscObj, @@ -73,7 +72,7 @@ pub trait FromAscObj { } pub trait TryFromAscObj: Sized { - fn try_from_asc_obj(obj: C, heap: &H) -> Result; + fn try_from_asc_obj(obj: C, heap: &H) -> Result; } // `AscType` is not really public, implementors should live inside the `class` module. diff --git a/runtime/wasm/src/host.rs b/runtime/wasm/src/host.rs index 747a9cacdd0..19683496df3 100644 --- a/runtime/wasm/src/host.rs +++ b/runtime/wasm/src/host.rs @@ -2,6 +2,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::time::{Duration, Instant}; +use anyhow::ensure; use async_trait::async_trait; use ethabi::{LogParam, RawLog}; use futures::sync::mpsc::Sender; @@ -90,17 +91,23 @@ where type Req = MappingRequest; fn spawn_mapping( - raw_module: &[u8], + raw_module: Vec, logger: Logger, subgraph_id: SubgraphDeploymentId, metrics: Arc, - ) -> Result, Error> { + ) -> Result, anyhow::Error> { + let timeout = std::env::var(TIMEOUT_ENV_VAR) + .ok() + .and_then(|s| u64::from_str(&s).ok()) + .map(Duration::from_secs); + crate::mapping::spawn_module( raw_module, logger, subgraph_id, metrics, tokio::runtime::Handle::current(), + timeout, ) } @@ -205,10 +212,6 @@ impl RuntimeHost { .clone(); let data_source_name = config.data_source_name; - let timeout = std::env::var(TIMEOUT_ENV_VAR) - .ok() - .and_then(|s| u64::from_str(&s).ok()) - .map(Duration::from_secs); // Create new instance of externally hosted functions invoker. The `Arc` is simply to avoid // implementing `Clone` for `HostExports`. @@ -225,7 +228,6 @@ impl RuntimeHost { link_resolver, store, call_cache, - timeout, arweave_adapter, three_box_adapter, )); @@ -294,12 +296,9 @@ impl RuntimeHost { source_address_matches && self.handler_for_block(block_trigger_type).is_ok() } - fn handlers_for_log(&self, log: &Arc) -> Result, Error> { + fn handlers_for_log(&self, log: &Arc) -> Result, anyhow::Error> { // Get signature from the log - let topic0 = match log.topics.iter().next() { - Some(topic0) => topic0, - None => return Err(format_err!("Ethereum event has no topics")), - }; + let topic0 = log.topics.get(0).context("Ethereum event has no topics")?; let handlers = self .data_source_event_handlers @@ -308,24 +307,22 @@ impl RuntimeHost { .cloned() .collect::>(); - if !handlers.is_empty() { - Ok(handlers) - } else { - Err(format_err!( - "No event handler found for event in data source \"{}\"", - self.data_source_name, - )) - } + ensure!( + !handlers.is_empty(), + "No event handler found for event in data source \"{}\"", + self.data_source_name, + ); + + Ok(handlers) } - fn handler_for_call(&self, call: &EthereumCall) -> Result { + fn handler_for_call(&self, call: &EthereumCall) -> Result { // First four bytes of the input for the call are the first four // bytes of hash of the function signature - if call.input.0.len() < 4 { - return Err(format_err!( - "Ethereum call has input with less than 4 bytes" - )); - } + ensure!( + call.input.0.len() >= 4, + "Ethereum call has input with less than 4 bytes" + ); let target_method_id = &call.input.0[..4]; @@ -337,7 +334,7 @@ impl RuntimeHost { target_method_id == actual_method_id }) .cloned() - .ok_or_else(|| { + .with_context(|| { format_err!( "No call handler found for call in data source \"{}\"", self.data_source_name, @@ -348,14 +345,14 @@ impl RuntimeHost { fn handler_for_block( &self, trigger_type: &EthereumBlockTriggerType, - ) -> Result { + ) -> Result { match trigger_type { EthereumBlockTriggerType::Every => self .data_source_block_handlers .iter() .find(move |handler| handler.filter == None) .cloned() - .ok_or_else(|| { + .with_context(|| { format_err!( "No block handler for `Every` block trigger \ type found in data source \"{}\"", @@ -370,7 +367,7 @@ impl RuntimeHost { && handler.filter.clone().unwrap() == BlockHandlerFilter::Call }) .cloned() - .ok_or_else(|| { + .with_context(|| { format_err!( "No block handler for `WithCallTo` block trigger \ type found in data source \"{}\"", @@ -391,7 +388,7 @@ impl RuntimeHost { trigger: MappingTrigger, block: &Arc, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { let trigger_type = trigger.as_static(); debug!( logger, "Start processing Ethereum trigger"; @@ -418,13 +415,13 @@ impl RuntimeHost { trigger, result_sender, }) - .map_err(|_| format_err!("Mapping terminated before passing in trigger")) .compat() - .await?; + .await + .context("Mapping terminated before passing in trigger")?; let (result, send_time) = result_receiver .await - .map_err(|_| format_err!("Mapping terminated before handling trigger"))?; + .context("Mapping terminated before handling trigger")?; let elapsed = start_time.elapsed(); metrics.observe_handler_execution_time(elapsed.as_secs_f64(), handler); @@ -482,7 +479,7 @@ impl RuntimeHostTrait for RuntimeHost { call: &Arc, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { // Identify the call handler for this call let call_handler = self.handler_for_call(&call)?; @@ -491,7 +488,7 @@ impl RuntimeHostTrait for RuntimeHost { &self.data_source_contract_abi.contract, call_handler.function.as_str(), ) - .ok_or_else(|| { + .with_context(|| { format_err!( "Function with the signature \"{}\" not found in \ contract \"{}\" of data source \"{}\"", @@ -508,19 +505,13 @@ impl RuntimeHostTrait for RuntimeHost { // with the `Param`s in `function.inputs` to create a `Vec`. let tokens = function_abi .decode_input(&call.input.0[4..]) - .map_err(|err| { - format_err!( - "Generating function inputs for an Ethereum call failed = {}", - err, - ) - })?; + .context("Generating function inputs for an Ethereum call failed")?; - if tokens.len() != function_abi.inputs.len() { - return Err(format_err!( - "Number of arguments in call does not match \ + ensure!( + tokens.len() == function_abi.inputs.len(), + "Number of arguments in call does not match \ number of inputs in function signature." - )); - } + ); let inputs = tokens .into_iter() @@ -536,19 +527,15 @@ impl RuntimeHostTrait for RuntimeHost { // Take the output for the call, then call `function.decode_output` to // get a vector of `Token`s. Match the `Token`s with the `Param`s in // `function.outputs` to create a `Vec`. - let tokens = function_abi.decode_output(&call.output.0).map_err(|err| { - format_err!( - "Generating function outputs for an Ethereum call failed = {}", - err, - ) - })?; + let tokens = function_abi + .decode_output(&call.output.0) + .context("Generating function outputs for an Ethereum call failed")?; - if tokens.len() != function_abi.outputs.len() { - return Err(format_err!( - "Number of parameters in the call output does not match \ + ensure!( + tokens.len() == function_abi.outputs.len(), + "Number of parameters in the call output does not match \ number of outputs in the function signature." - )); - } + ); let outputs = tokens .into_iter() @@ -587,7 +574,7 @@ impl RuntimeHostTrait for RuntimeHost { trigger_type: &EthereumBlockTriggerType, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { let block_handler = self.handler_for_block(trigger_type)?; self.send_mapping_request( logger, @@ -614,7 +601,7 @@ impl RuntimeHostTrait for RuntimeHost { log: &Arc, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { let data_source_name = &self.data_source_name; let abi_name = &self.data_source_contract_abi.name; let contract = &self.data_source_contract_abi.contract; @@ -632,7 +619,7 @@ impl RuntimeHostTrait for RuntimeHost { contract, event_handler.event.as_str(), ) - .ok_or_else(|| { + .with_context(|| { format_err!( "Event with the signature \"{}\" not found in \ contract \"{}\" of data source \"{}\"", @@ -643,7 +630,7 @@ impl RuntimeHostTrait for RuntimeHost { })?; Ok((event_handler, event_abi)) }) - .collect::, failure::Error>>()?; + .collect::, anyhow::Error>>()?; // Filter out handlers whose corresponding event ABIs cannot decode the // params (this is common for overloaded events that have the same topic0 @@ -692,12 +679,13 @@ impl RuntimeHostTrait for RuntimeHost { // Process the event with the matching handler let (event_handler, params) = matching_handlers.pop().unwrap(); - if !matching_handlers.is_empty() { - return Err(format_err!( + ensure!( + matching_handlers.is_empty(), + format!( "Multiple handlers defined for event `{}`, only one is supported", &event_handler.event - )); - } + ) + ); self.send_mapping_request( logger, diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 88d1e7fcff5..50c853737bd 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -11,7 +11,6 @@ use graph::prelude::serde_json; use graph::prelude::{slog::b, slog::record_static, *}; use semver::Version; use std::collections::HashMap; -use std::fmt; use std::ops::Deref; use std::str::FromStr; use std::time::{Duration, Instant}; @@ -19,27 +18,7 @@ use web3::types::H160; use graph_graphql::prelude::validate_entity; -use crate::module::WasmiModule; - -pub(crate) trait ExportError: fmt::Debug + fmt::Display + Send + Sync + 'static {} - -impl ExportError for E where E: fmt::Debug + fmt::Display + Send + Sync + 'static {} - -/// Error raised in host functions. -#[derive(Debug)] -pub(crate) struct HostExportError(pub(crate) E); - -impl fmt::Display for HostExportError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -impl From for HostExportError { - fn from(e: graph::prelude::Error) -> Self { - HostExportError(e.to_string()) - } -} +use crate::module::WasmInstance; pub(crate) struct HostExports { subgraph_id: SubgraphDeploymentId, @@ -59,7 +38,6 @@ pub(crate) struct HostExports { pub(crate) link_resolver: Arc, call_cache: Arc, store: Arc, - handler_timeout: Option, arweave_adapter: Arc, three_box_adapter: Arc, } @@ -85,7 +63,6 @@ impl HostExports { link_resolver: Arc, store: Arc, call_cache: Arc, - handler_timeout: Option, arweave_adapter: Arc, three_box_adapter: Arc, ) -> Self { @@ -105,7 +82,6 @@ impl HostExports { link_resolver, call_cache, store, - handler_timeout, arweave_adapter, three_box_adapter, } @@ -117,7 +93,7 @@ impl HostExports { file_name: Option, line_number: Option, column_number: Option, - ) -> Result<(), HostExportError> { + ) -> Result<(), anyhow::Error> { let message = message .map(|message| format!("message: {}", message)) .unwrap_or_else(|| "no message".into()); @@ -133,10 +109,11 @@ impl HostExports { ), _ => unreachable!(), }; - Err(HostExportError(format!( + Err(anyhow::anyhow!( "Mapping aborted at {}, with {}", - location, message - ))) + location, + message + )) } pub(crate) fn store_set( @@ -147,7 +124,9 @@ impl HostExports { entity_type: String, entity_id: String, mut data: HashMap, - ) -> Result<(), HostExportError> { + ) -> Result<(), anyhow::Error> { + use graph::prelude::failure::ResultExt; + if let Some(proof_of_indexing) = proof_of_indexing { let mut proof_of_indexing = proof_of_indexing.deref().borrow_mut(); proof_of_indexing.write( @@ -164,11 +143,13 @@ impl HostExports { // Automatically add an "id" value match data.insert("id".to_string(), Value::String(entity_id.clone())) { Some(ref v) if v != &Value::String(entity_id.clone()) => { - return Err(HostExportError(format!( + anyhow::bail!( "Value of {} attribute 'id' conflicts with ID passed to `store.set()`: \ {} != {}", - entity_type, v, entity_id, - ))); + entity_type, + v, + entity_id, + ); } _ => (), } @@ -179,20 +160,22 @@ impl HostExports { entity_id, }; let entity = Entity::from(data); - let schema = self.store.input_schema(&self.subgraph_id)?; + let schema = self.store.input_schema(&self.subgraph_id).compat()?; let is_valid = validate_entity(&schema.document, &key, &entity).is_ok(); - state - .entity_cache - .set(key.clone(), entity) - .map_err(|e| HostExportError(e.to_string()))?; + state.entity_cache.set(key.clone(), entity).compat()?; // Validate the changes against the subgraph schema. // If the set of fields we have is already valid, avoid hitting the DB. - if !is_valid && self.store.uses_relational_schema(&self.subgraph_id)? { + if !is_valid + && self + .store + .uses_relational_schema(&self.subgraph_id) + .compat()? + { let entity = state .entity_cache .get(&key) - .map_err(|e| HostExportError(e.to_string()))? + .compat()? .expect("we just stored this entity"); validate_entity(&schema.document, &key, &entity)?; } @@ -231,20 +214,14 @@ impl HostExports { state: &mut BlockState, entity_type: String, entity_id: String, - ) -> Result, HostExportError> { + ) -> Result, anyhow::Error> { let store_key = EntityKey { subgraph_id: self.subgraph_id.clone(), entity_type: entity_type.clone(), entity_id: entity_id.clone(), }; - let result = state - .entity_cache - .get(&store_key) - .map_err(HostExportError) - .map(|ok| ok.to_owned()); - - result + Ok(state.entity_cache.get(&store_key)?) } /// Returns `Ok(None)` if the call was reverted. @@ -253,7 +230,7 @@ impl HostExports { logger: &Logger, block: &LightEthereumBlock, unresolved_call: UnresolvedContractCall, - ) -> Result>, HostExportError> { + ) -> Result>, anyhow::Error> { let start_time = Instant::now(); // Obtain the path to the contract ABI @@ -261,12 +238,12 @@ impl HostExports { .abis .iter() .find(|abi| abi.name == unresolved_call.contract_name) - .ok_or_else(|| { - HostExportError(format!( + .with_context(|| { + format!( "Could not find ABI for contract \"{}\", try adding it to the 'abis' section \ of the subgraph manifest", unresolved_call.contract_name - )) + ) })? .contract .clone(); @@ -277,11 +254,11 @@ impl HostExports { // and may lead to encoding/decoding errors None => contract .function(unresolved_call.function_name.as_str()) - .map_err(|e| { - HostExportError(format!( - "Unknown function \"{}::{}\" called from WASM runtime: {}", - unresolved_call.contract_name, unresolved_call.function_name, e - )) + .with_context(|| { + format!( + "Unknown function \"{}::{}\" called from WASM runtime", + unresolved_call.contract_name, unresolved_call.function_name + ) })?, // Behavior for apiVersion >= 0.0.04: look up function by signature of @@ -289,22 +266,22 @@ impl HostExports { // correctly picks the correct variant of an overloaded function Some(ref function_signature) => contract .functions_by_name(unresolved_call.function_name.as_str()) - .map_err(|e| { - HostExportError(format!( - "Unknown function \"{}::{}\" called from WASM runtime: {}", - unresolved_call.contract_name, unresolved_call.function_name, e - )) + .with_context(|| { + format!( + "Unknown function \"{}::{}\" called from WASM runtime", + unresolved_call.contract_name, unresolved_call.function_name + ) })? .iter() .find(|f| function_signature == &f.signature()) - .ok_or_else(|| { - HostExportError(format!( + .with_context(|| { + format!( "Unknown function \"{}::{}\" with signature `{}` \ called from WASM runtime", unresolved_call.contract_name, unresolved_call.function_name, function_signature, - )) + ) })?, }; @@ -327,10 +304,12 @@ impl HostExports { info!(logger, "Contract call reverted"; "reason" => reason); Ok(None) } - Err(e) => Err(HostExportError(format!( + Err(e) => Err(anyhow::anyhow!( "Failed to call function \"{}\" of contract \"{}\": {}", - unresolved_call.function_name, unresolved_call.contract_name, e - ))), + unresolved_call.function_name, + unresolved_call.contract_name, + e + )), }; debug!(logger, "Contract call finished"; @@ -357,34 +336,10 @@ impl HostExports { format!("0x{}", ::hex::encode(bytes).trim_start_matches('0')) } - pub(crate) fn big_int_to_i32( - &self, - n: BigInt, - ) -> Result> { - if n >= i32::min_value().into() && n <= i32::max_value().into() { - let n_bytes = n.to_signed_bytes_le(); - let mut i_bytes: [u8; 4] = if n < 0.into() { [255; 4] } else { [0; 4] }; - i_bytes[..n_bytes.len()].copy_from_slice(&n_bytes); - let i = i32::from_le_bytes(i_bytes); - Ok(i) - } else { - Err(HostExportError(format!( - "BigInt value does not fit into i32: {}", - n - ))) - } - } + pub(crate) fn ipfs_cat(&self, logger: &Logger, link: String) -> Result, anyhow::Error> { + use graph::prelude::failure::ResultExt; - pub(crate) fn ipfs_cat( - &self, - logger: &Logger, - link: String, - ) -> Result, HostExportError> { - block_on03( - self.link_resolver - .cat(logger, &Link { link }) - .map_err(HostExportError), - ) + Ok(block_on03(self.link_resolver.cat(logger, &Link { link })).compat()?) } // Read the IPFS file `link`, split it into JSON objects, and invoke the @@ -396,16 +351,19 @@ impl HostExports { // parameter is passed to the callback without any changes pub(crate) fn ipfs_map( link_resolver: &Arc, - module: &mut WasmiModule, + module: &mut WasmInstance, link: String, callback: &str, user_data: store::Value, flags: Vec, - ) -> Result, HostExportError> { + ) -> Result, anyhow::Error> { + use graph::prelude::failure::ResultExt; + const JSON_FLAG: &str = "json"; - if !flags.contains(&JSON_FLAG.to_string()) { - return Err(HostExportError(format!("Flags must contain 'json'"))); - } + anyhow::ensure!( + flags.contains(&JSON_FLAG.to_string()), + "Flags must contain 'json'" + ); let host_metrics = module.host_metrics.clone(); let valid_module = module.valid_module.clone(); @@ -421,16 +379,17 @@ impl HostExports { let mut last_log = start; let logger = ctx.logger.new(o!("ipfs_map" => link.clone())); - let result = block_on03(async move { + let result = { let mut stream: JsonValueStream = - link_resolver.json_stream(&logger, &Link { link }).await?; + block_on03(link_resolver.json_stream(&logger, &Link { link })).compat()?; let mut v = Vec::new(); - while let Some(sv) = stream.next().await { - let sv = sv?; - let module = WasmiModule::from_valid_module_with_ctx( + while let Some(sv) = block_on03(stream.next()) { + let sv = sv.compat()?; + let module = WasmInstance::from_valid_module_with_ctx( valid_module.clone(), ctx.derive_with_empty_block_state(), host_metrics.clone(), + module.timeout, )?; let result = module.handle_json_callback(&callback, &sv.value, &user_data)?; // Log progress every 15s @@ -446,44 +405,29 @@ impl HostExports { v.push(result) } Ok(v) - }); - result.map_err(move |e: Error| HostExportError(format!("{}: {}", errmsg, e.to_string()))) + }; + result.map_err(move |e: Error| anyhow::anyhow!("{}: {}", errmsg, e.to_string())) } /// Expects a decimal string. - pub(crate) fn json_to_i64( - &self, - json: String, - ) -> Result> { - i64::from_str(&json) - .map_err(|_| HostExportError(format!("JSON `{}` cannot be parsed as i64", json))) + pub(crate) fn json_to_i64(&self, json: String) -> Result { + i64::from_str(&json).with_context(|| format!("JSON `{}` cannot be parsed as i64", json)) } /// Expects a decimal string. - pub(crate) fn json_to_u64( - &self, - json: String, - ) -> Result> { - u64::from_str(&json) - .map_err(|_| HostExportError(format!("JSON `{}` cannot be parsed as u64", json))) + pub(crate) fn json_to_u64(&self, json: String) -> Result { + u64::from_str(&json).with_context(|| format!("JSON `{}` cannot be parsed as u64", json)) } /// Expects a decimal string. - pub(crate) fn json_to_f64( - &self, - json: String, - ) -> Result> { - f64::from_str(&json) - .map_err(|_| HostExportError(format!("JSON `{}` cannot be parsed as f64", json))) + pub(crate) fn json_to_f64(&self, json: String) -> Result { + f64::from_str(&json).with_context(|| format!("JSON `{}` cannot be parsed as f64", json)) } /// Expects a decimal string. - pub(crate) fn json_to_big_int( - &self, - json: String, - ) -> Result, HostExportError> { + pub(crate) fn json_to_big_int(&self, json: String) -> Result, anyhow::Error> { let big_int = BigInt::from_str(&json) - .map_err(|_| HostExportError(format!("JSON `{}` is not a decimal string", json)))?; + .with_context(|| format!("JSON `{}` is not a decimal string", json))?; Ok(big_int.to_signed_bytes_le()) } @@ -503,17 +447,8 @@ impl HostExports { x * y } - pub(crate) fn big_int_divided_by( - &self, - x: BigInt, - y: BigInt, - ) -> Result> { - if y == 0.into() { - return Err(HostExportError(format!( - "attempted to divide BigInt `{}` by zero", - x - ))); - } + pub(crate) fn big_int_divided_by(&self, x: BigInt, y: BigInt) -> Result { + anyhow::ensure!(y != 0.into(), "attempted to divide BigInt `{}` by zero", x); Ok(x / y) } @@ -526,18 +461,6 @@ impl HostExports { x.pow(exponent) } - pub(crate) fn check_timeout( - &self, - start_time: Instant, - ) -> Result<(), HostExportError> { - if let Some(timeout) = self.handler_timeout { - if start_time.elapsed() > timeout { - return Err(HostExportError(format!("Mapping handler timed out"))); - } - } - Ok(()) - } - /// Useful for IPFS hashes stored as bytes pub(crate) fn bytes_to_base58(&self, bytes: Vec) -> String { ::bs58::encode(&bytes).into_string() @@ -560,13 +483,11 @@ impl HostExports { &self, x: BigDecimal, y: BigDecimal, - ) -> Result> { - if y == 0.into() { - return Err(HostExportError(format!( - "attempted to divide BigDecimal `{}` by zero", - x - ))); - } + ) -> Result { + anyhow::ensure!( + y != 0.into(), + format!("attempted to divide BigDecimal `{}` by zero", x) + ); Ok(x / y) } @@ -579,12 +500,8 @@ impl HostExports { x.to_string() } - pub(crate) fn big_decimal_from_string( - &self, - s: String, - ) -> Result> { - BigDecimal::from_str(&s) - .map_err(|e| HostExportError(format!("failed to parse BigDecimal: {}", e))) + pub(crate) fn big_decimal_from_string(&self, s: String) -> Result { + BigDecimal::from_str(&s).with_context(|| format!("string is not a BigDecimal: '{}'", s)) } pub(crate) fn data_source_create( @@ -594,7 +511,7 @@ impl HostExports { name: String, params: Vec, context: Option, - ) -> Result<(), HostExportError> { + ) -> Result<(), anyhow::Error> { info!( logger, "Create data source"; @@ -607,8 +524,8 @@ impl HostExports { .templates .iter() .find(|template| template.name == name) - .ok_or_else(|| { - HostExportError(format!( + .with_context(|| { + format!( "Failed to create data source from name `{}`: \ No template with this name in parent data source `{}`. \ Available names: {}.", @@ -619,7 +536,7 @@ impl HostExports { .map(|template| template.name.clone()) .collect::>() .join(", ") - )) + ) })? .clone(); @@ -634,11 +551,10 @@ impl HostExports { Ok(()) } - pub(crate) fn ens_name_by_hash( - &self, - hash: &str, - ) -> Result, HostExportError> { - self.store.find_ens_name(hash).map_err(HostExportError) + pub(crate) fn ens_name_by_hash(&self, hash: &str) -> Result, anyhow::Error> { + use graph::prelude::failure::ResultExt; + + Ok(self.store.find_ens_name(hash).compat()?) } pub(crate) fn log_log(&self, logger: &Logger, level: slog::Level, msg: String) { @@ -679,11 +595,14 @@ impl HostExports { } } -pub(crate) fn string_to_h160(string: &str) -> Result> { +pub(crate) fn json_from_bytes(bytes: &Vec) -> Result { + serde_json::from_reader(bytes.as_slice()) +} + +pub(crate) fn string_to_h160(string: &str) -> Result { // `H160::from_str` takes a hex string with no leading `0x`. - let string = string.trim_start_matches("0x"); - H160::from_str(string) - .map_err(|e| HostExportError(format!("Failed to convert string to Address/H160: {}", e))) + let s = string.trim_start_matches("0x"); + H160::from_str(s).with_context(|| format!("Failed to convert string to Address/H160: '{}'", s)) } pub(crate) fn bytes_to_string(logger: &Logger, bytes: Vec) -> String { diff --git a/runtime/wasm/src/lib.rs b/runtime/wasm/src/lib.rs index 43b61bf3721..be6242e987c 100644 --- a/runtime/wasm/src/lib.rs +++ b/runtime/wasm/src/lib.rs @@ -8,7 +8,7 @@ pub use host::RuntimeHostBuilder; /// Pre-processes modules and manages their threads. Serves as an interface from `host` to `module`. mod mapping; -/// Deals with wasmi. +/// WASM module instance. mod module; /// Runtime-agnostic implementation of exports to WASM. diff --git a/runtime/wasm/src/mapping.rs b/runtime/wasm/src/mapping.rs index 2b22f518250..50937843f84 100644 --- a/runtime/wasm/src/mapping.rs +++ b/runtime/wasm/src/mapping.rs @@ -1,10 +1,11 @@ -use crate::module::WasmiModule; +use crate::module::WasmInstance; use ethabi::LogParam; use futures::sync::mpsc; use futures03::channel::oneshot::Sender; use graph::components::ethereum::*; use graph::components::subgraph::SharedProofOfIndexing; use graph::prelude::*; +use std::collections::BTreeMap; use std::sync::Arc; use std::thread; use std::time::Instant; @@ -13,19 +14,19 @@ use web3::types::{Log, Transaction}; /// Spawn a wasm module in its own thread. pub fn spawn_module( - raw_module: &[u8], + raw_module: Vec, logger: Logger, subgraph_id: SubgraphDeploymentId, host_metrics: Arc, runtime: tokio::runtime::Handle, -) -> Result, Error> { - let parsed_module = parity_wasm::deserialize_buffer(raw_module)?; - let valid_module = Arc::new(ValidModule::new(parsed_module)?); + timeout: Option, +) -> Result, anyhow::Error> { + let valid_module = Arc::new(ValidModule::new(&raw_module)?); // Create channel for event handling requests let (mapping_request_sender, mapping_request_receiver) = mpsc::channel(100); - // wasmi modules are not `Send` therefore they cannot be scheduled by + // wasmtime instances are not `Send` therefore they cannot be scheduled by // the regular tokio executor, so we create a dedicated thread. // // In case of failure, this thread may panic or simply terminate, @@ -39,19 +40,20 @@ pub fn spawn_module( // Stop when canceled because all RuntimeHosts and their senders were dropped. match mapping_request_receiver .map_err(|()| unreachable!()) - .for_each(move |request| -> Result<(), Error> { + .for_each(move |request| { let MappingRequest { ctx, trigger, result_sender, } = request; - // Start the WASMI module runtime. + // Start the WASM module runtime. let section = host_metrics.stopwatch.start_section("module_init"); - let module = WasmiModule::from_valid_module_with_ctx( + let module = WasmInstance::from_valid_module_with_ctx( valid_module.clone(), ctx, host_metrics.clone(), + timeout, )?; section.end(); @@ -89,7 +91,7 @@ pub fn spawn_module( result_sender .send((result, future::ok(Instant::now()))) - .map_err(|_| err_msg("WASM module result receiver dropped.")) + .map_err(|_| anyhow::anyhow!("WASM module result receiver dropped.")) }) .wait() { @@ -100,7 +102,7 @@ pub fn spawn_module( }) }) .map(|_| ()) - .map_err(|e| format_err!("Spawning WASM runtime thread failed: {}", e))?; + .context("Spawning WASM runtime thread failed")?; Ok(mapping_request_sender) } @@ -125,7 +127,10 @@ pub(crate) enum MappingTrigger { }, } -type MappingResponse = (Result, futures::Finished); +type MappingResponse = ( + Result, + futures::Finished, +); #[derive(Debug)] pub struct MappingRequest { @@ -155,36 +160,49 @@ impl MappingContext { } } -/// A pre-processed and valid WASM module, ready to be started as a WasmiModule. +/// A pre-processed and valid WASM module, ready to be started as a WasmModule. pub(crate) struct ValidModule { - pub(super) module: wasmi::Module, - pub(super) host_module_names: Vec, + pub(super) module: wasmtime::Module, + + // A wasm import consists of a `module` and a `name`. AS will generate imports such that they + // have `module` set to the name of the file it is imported from and `name` set to the imported + // function name or `namespace.function` if inside a namespace. We'd rather not specify names of + // source files, so we consider that the import `name` uniquely identifies an import. Still we + // need to know the `module` to properly link it, so here we map import names to modules. + // + // AS now has an `@external("module", "name")` decorator which would make things cleaner, but + // the ship has sailed. + pub(super) import_name_to_modules: BTreeMap>, } impl ValidModule { /// Pre-process and validate the module. - pub fn new(parsed_module: parity_wasm::elements::Module) -> Result { - // Inject metering calls, which are used for checking timeouts. - let parsed_module = pwasm_utils::inject_gas_counter(parsed_module, &Default::default()) - .map_err(|_| err_msg("failed to inject gas counter"))?; - - // `inject_gas_counter` injects an import so the section must exist. - let import_section = parsed_module.import_section().unwrap().clone(); - - // Collect the names of all host modules used in the WASM module - let mut host_module_names: Vec<_> = import_section - .entries() - .into_iter() - .map(|import| import.module().to_owned()) - .collect(); - host_module_names.dedup(); - - let module = wasmi::Module::from_parity_wasm_module(parsed_module) - .map_err(|e| format_err!("Invalid WASM module: {}", e))?; + pub fn new(raw_module: &[u8]) -> Result { + // We currently use Cranelift as a compilation engine. Cranelift is an optimizing compiler, + // but that should not cause determinism issues since it adheres to the Wasm spec. Still we + // turn off optional optimizations to be conservative. + let mut config = wasmtime::Config::new(); + config.strategy(wasmtime::Strategy::Cranelift).unwrap(); + config.interruptable(true); // For timeouts. + config.cranelift_nan_canonicalization(true); // For NaN determinism. + config.cranelift_opt_level(wasmtime::OptLevel::None); + let engine = &wasmtime::Engine::new(&config); + let module = wasmtime::Module::from_binary(&engine, raw_module)?; + + let mut import_name_to_modules: BTreeMap> = BTreeMap::new(); + for (name, module) in module + .imports() + .map(|import| (import.name(), import.module())) + { + import_name_to_modules + .entry(name.to_string()) + .or_default() + .push(module.to_string()); + } Ok(ValidModule { module, - host_module_names, + import_name_to_modules, }) } } diff --git a/runtime/wasm/src/module/into_wasm_ret.rs b/runtime/wasm/src/module/into_wasm_ret.rs new file mode 100644 index 00000000000..d3ac92c5ab5 --- /dev/null +++ b/runtime/wasm/src/module/into_wasm_ret.rs @@ -0,0 +1,69 @@ +use crate::asc_abi::AscPtr; +use wasmtime::Trap; + +/// Helper trait for the `link!` macro. +pub(crate) trait IntoWasmRet { + type Ret: wasmtime::WasmRet; + + fn into_wasm_ret(self) -> Self::Ret; +} + +impl IntoWasmRet for () { + type Ret = Self; + fn into_wasm_ret(self) -> Self { + self + } +} + +impl IntoWasmRet for i32 { + type Ret = Self; + fn into_wasm_ret(self) -> Self { + self + } +} + +impl IntoWasmRet for i64 { + type Ret = Self; + fn into_wasm_ret(self) -> Self { + self + } +} + +impl IntoWasmRet for f64 { + type Ret = Self; + fn into_wasm_ret(self) -> Self { + self + } +} + +impl IntoWasmRet for u64 { + type Ret = u64; + fn into_wasm_ret(self) -> u64 { + self + } +} + +impl IntoWasmRet for bool { + type Ret = i32; + fn into_wasm_ret(self) -> i32 { + self.into() + } +} + +impl IntoWasmRet for AscPtr { + type Ret = u32; + fn into_wasm_ret(self) -> u32 { + self.wasm_ptr() + } +} + +impl IntoWasmRet for Result +where + T: IntoWasmRet, + T::Ret: wasmtime::WasmTy, +{ + type Ret = Result; + fn into_wasm_ret(self) -> Self::Ret { + self.map(|x| x.into_wasm_ret()) + } +} diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 1b7e9a6dd6a..09ae87a041c 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -1,22 +1,19 @@ +use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::convert::TryFrom; -use std::fmt; use std::ops::Deref; +use std::rc::Rc; use std::time::Instant; use semver::Version; -use wasmi::{ - nan_preserving_float::F64, Error, Externals, FuncInstance, FuncRef, HostError, ImportsBuilder, - MemoryRef, ModuleImportResolver, ModuleInstance, ModuleRef, RuntimeArgs, RuntimeValue, - Signature, Trap, -}; +use wasmtime::{Memory, Trap}; -use crate::host_exports::{self, HostExportError}; +use crate::host_exports; use crate::mapping::MappingContext; use ethabi::LogParam; use graph::components::ethereum::*; use graph::data::store; -use graph::prelude::{Error as FailureError, *}; +use graph::prelude::*; use web3::types::{Log, Transaction, U256}; use crate::asc_abi::asc_ptr::*; @@ -26,182 +23,55 @@ use crate::host_exports::HostExports; use crate::mapping::ValidModule; use crate::UnresolvedContractCall; +mod into_wasm_ret; +mod stopwatch; + +use into_wasm_ret::IntoWasmRet; +use stopwatch::TimeoutStopwatch; + #[cfg(test)] mod test; -// Indexes for exported host functions -const ABORT_FUNC_INDEX: usize = 0; -const STORE_SET_FUNC_INDEX: usize = 1; -const STORE_REMOVE_FUNC_INDEX: usize = 2; -const ETHEREUM_CALL_FUNC_INDEX: usize = 3; -const TYPE_CONVERSION_BYTES_TO_STRING_FUNC_INDEX: usize = 4; -const TYPE_CONVERSION_BYTES_TO_HEX_FUNC_INDEX: usize = 5; -const TYPE_CONVERSION_BIG_INT_TO_STRING_FUNC_INDEX: usize = 6; -const TYPE_CONVERSION_BIG_INT_TO_HEX_FUNC_INDEX: usize = 7; -const TYPE_CONVERSION_STRING_TO_H160_FUNC_INDEX: usize = 8; -const TYPE_CONVERSION_I32_TO_BIG_INT_FUNC_INDEX: usize = 9; -const TYPE_CONVERSION_BIG_INT_TO_I32_FUNC_INDEX: usize = 10; -const JSON_FROM_BYTES_FUNC_INDEX: usize = 11; -const JSON_TO_I64_FUNC_INDEX: usize = 12; -const JSON_TO_U64_FUNC_INDEX: usize = 13; -const JSON_TO_F64_FUNC_INDEX: usize = 14; -const JSON_TO_BIG_INT_FUNC_INDEX: usize = 15; -const IPFS_CAT_FUNC_INDEX: usize = 16; -const STORE_GET_FUNC_INDEX: usize = 17; -const CRYPTO_KECCAK_256_INDEX: usize = 18; -const BIG_INT_PLUS: usize = 19; -const BIG_INT_MINUS: usize = 20; -const BIG_INT_TIMES: usize = 21; -const BIG_INT_DIVIDED_BY: usize = 22; -const BIG_INT_MOD: usize = 23; -const GAS_FUNC_INDEX: usize = 24; -const TYPE_CONVERSION_BYTES_TO_BASE_58_INDEX: usize = 25; -const BIG_INT_DIVIDED_BY_DECIMAL: usize = 26; -const BIG_DECIMAL_PLUS: usize = 27; -const BIG_DECIMAL_MINUS: usize = 28; -const BIG_DECIMAL_TIMES: usize = 29; -const BIG_DECIMAL_DIVIDED_BY: usize = 30; -const BIG_DECIMAL_EQUALS: usize = 31; -const BIG_DECIMAL_TO_STRING: usize = 32; -const BIG_DECIMAL_FROM_STRING: usize = 33; -const IPFS_MAP_FUNC_INDEX: usize = 34; -const DATA_SOURCE_CREATE_INDEX: usize = 35; -const ENS_NAME_BY_HASH: usize = 36; -const LOG_LOG: usize = 37; -const BIG_INT_POW: usize = 38; -const DATA_SOURCE_ADDRESS: usize = 39; -const DATA_SOURCE_NETWORK: usize = 40; -const DATA_SOURCE_CREATE_WITH_CONTEXT: usize = 41; -const DATA_SOURCE_CONTEXT: usize = 42; -const JSON_TRY_FROM_BYTES_FUNC_INDEX: usize = 43; -const ARWEAVE_TRANSACTION_DATA: usize = 44; -const BOX_PROFILE: usize = 45; - -/// Transform function index into the function name string -fn fn_index_to_metrics_string(index: usize) -> Option<&'static str> { - match index { - STORE_GET_FUNC_INDEX => Some("store_get"), - ETHEREUM_CALL_FUNC_INDEX => Some("ethereum_call"), - IPFS_MAP_FUNC_INDEX => Some("ipfs_map"), - IPFS_CAT_FUNC_INDEX => Some("ipfs_cat"), - _ => None, - } -} +const TRAP_TIMEOUT: &str = "trap: interrupt"; -/// A common error is a trap in the host, so simplify the message in that case. -fn format_wasmi_error(e: Error) -> String { - match e { - Error::Trap(trap) => match trap.kind() { - wasmi::TrapKind::Host(host_error) => host_error.to_string(), - _ => trap.to_string(), - }, - _ => e.to_string(), - } +/// Handle to a WASM instance, which is terminated if and only if this is dropped. +pub(crate) struct WasmInstanceHandle { + // This is the only reference to `WasmInstance` that's not within the instance itself, so we can + // always borrow the `RefCell` with no concern for race conditions. + // + // Also this is the only strong reference, so the instance will be dropped once this is dropped. + // The weak references are circulary held by instance itself through host exports. + instance: Rc>>, } -/// A WASM module based on wasmi that powers a subgraph runtime. -pub(crate) struct WasmiModule { - pub module: ModuleRef, - memory: MemoryRef, - - pub ctx: MappingContext, - pub(crate) valid_module: Arc, - pub(crate) host_metrics: Arc, - - // Time when the current handler began processing. - start_time: Instant, - - // True if `run_start` has not yet been called on the module. - // This is used to prevent mutating store state in start. - running_start: bool, - - // First free byte in the current arena. - arena_start_ptr: u32, - - // Number of free bytes starting from `arena_start_ptr`. - arena_free_size: u32, - - // How many times we've passed a timeout checkpoint during execution. - timeout_checkpoint_count: u64, -} - -impl WasmiModule { - /// Creates a new wasmi module - pub fn from_valid_module_with_ctx( - valid_module: Arc, - ctx: MappingContext, - host_metrics: Arc, - ) -> Result { - // Build import resolver - let mut imports = ImportsBuilder::new(); - - for host_module_name in valid_module.host_module_names.iter() { - if host_module_name.as_str() == "env" { - imports.push_resolver(host_module_name.clone(), &EnvModuleResolver); - } else { - imports.push_resolver(host_module_name.clone(), &ModuleResolver); - } - } - - // Instantiate the runtime module using hosted functions and import resolver - let module = ModuleInstance::new(&valid_module.module, &imports) - .map_err(|e| format_err!("Failed to instantiate WASM module: {}", e))?; - - // Provide access to the WASM runtime linear memory - let not_started_module = module.not_started_instance().clone(); - let memory = not_started_module - .export_by_name("memory") - .ok_or_else(|| format_err!("Failed to find memory export in the WASM module"))? - .as_memory() - .ok_or_else(|| format_err!("Export \"memory\" has an invalid type"))? - .clone(); - - let mut this = WasmiModule { - module: not_started_module, - memory, - ctx, - valid_module: valid_module.clone(), - host_metrics, - start_time: Instant::now(), - running_start: true, - - // `arena_start_ptr` will be set on the first call to `raw_new`. - arena_free_size: 0, - arena_start_ptr: 0, - timeout_checkpoint_count: 0, - }; - - this.module = module - .run_start(&mut this) - .map_err(|e| format_err!("Failed to start WASM module instance: {}", e))?; - this.running_start = false; - - Ok(this) +impl Drop for WasmInstanceHandle { + fn drop(&mut self) { + // Assert that the instance will be dropped. + assert_eq!(Rc::strong_count(&self.instance), 1); } +} +impl WasmInstanceHandle { pub(crate) fn handle_json_callback( mut self, handler_name: &str, value: &serde_json::Value, user_data: &store::Value, - ) -> Result { - let value = RuntimeValue::from(self.asc_new(value)); - let user_data = RuntimeValue::from(self.asc_new(user_data)); + ) -> Result { + let value = self.instance_mut().asc_new(value); + let user_data = self.instance_mut().asc_new(user_data); // Invoke the callback - let result = - self.module - .clone() - .invoke_export(handler_name, &[value, user_data], &mut self); - - // Return either the collected entity operations or an error - result.map(|_| self.ctx.state).map_err(|e| { - format_err!( - "Failed to handle callback with handler \"{}\": {}", - handler_name, - format_wasmi_error(e), - ) - }) + let func = self + .instance() + .instance + .get_func(handler_name) + .with_context(|| format!("function {} not found", handler_name))? + .get2()?; + func(value.wasm_ptr(), user_data.wasm_ptr()) + .with_context(|| format!("Failed to handle callback '{}'", handler_name))?; + + Ok(self.take_instance().ctx.state) } pub(crate) fn handle_ethereum_log( @@ -210,31 +80,27 @@ impl WasmiModule { transaction: Arc, log: Arc, params: Vec, - ) -> Result { - self.start_time = Instant::now(); - - let block = self.ctx.block.clone(); + ) -> Result { + let block = self.instance().ctx.block.clone(); // Prepare an EthereumEvent for the WASM runtime // Decide on the destination type using the mapping // api version provided in the subgraph manifest - let event = if self.ctx.host_exports.api_version >= Version::new(0, 0, 2) { - RuntimeValue::from( - self.asc_new::, _>( - &EthereumEventData { - block: EthereumBlockData::from(block.as_ref()), - transaction: EthereumTransactionData::from(transaction.deref()), - address: log.address, - log_index: log.log_index.unwrap_or(U256::zero()), - transaction_log_index: log.log_index.unwrap_or(U256::zero()), - log_type: log.log_type.clone(), - params, - }, - ), - ) + let event = if self.instance().ctx.host_exports.api_version >= Version::new(0, 0, 2) { + self.instance_mut() + .asc_new::, _>(&EthereumEventData { + block: EthereumBlockData::from(block.as_ref()), + transaction: EthereumTransactionData::from(transaction.deref()), + address: log.address, + log_index: log.log_index.unwrap_or(U256::zero()), + transaction_log_index: log.log_index.unwrap_or(U256::zero()), + log_type: log.log_type.clone(), + params, + }) + .erase() } else { - RuntimeValue::from(self.asc_new::, _>( - &EthereumEventData { + self.instance_mut() + .asc_new::, _>(&EthereumEventData { block: EthereumBlockData::from(block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), address: log.address, @@ -242,24 +108,15 @@ impl WasmiModule { transaction_log_index: log.log_index.unwrap_or(U256::zero()), log_type: log.log_type.clone(), params, - }, - )) + }) + .erase() }; // Invoke the event handler - let result = self - .module - .clone() - .invoke_export(handler_name, &[event], &mut self); - - // Return either the output state (collected entity operations etc.) or an error - result.map(|_| self.ctx.state).map_err(|e| { - format_err!( - "Failed to handle Ethereum event with handler \"{}\": {}", - handler_name, - format_wasmi_error(e) - ) - }) + self.invoke_handler(handler_name, event)?; + + // Return the output state + Ok(self.take_instance().ctx.state) } pub(crate) fn handle_ethereum_call( @@ -269,126 +126,401 @@ impl WasmiModule { call: Arc, inputs: Vec, outputs: Vec, - ) -> Result { - self.start_time = Instant::now(); - + ) -> Result { let call = EthereumCallData { to: call.to, from: call.from, - block: EthereumBlockData::from(self.ctx.block.as_ref()), + block: EthereumBlockData::from(self.instance().ctx.block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), inputs, outputs, }; - let arg = if self.ctx.host_exports.api_version >= Version::new(0, 0, 3) { - RuntimeValue::from(self.asc_new::(&call)) + let arg = if self.instance().ctx.host_exports.api_version >= Version::new(0, 0, 3) { + self.instance_mut() + .asc_new::(&call) + .erase() } else { - RuntimeValue::from(self.asc_new::(&call)) + self.instance_mut() + .asc_new::(&call) + .erase() }; - let result = self - .module - .clone() - .invoke_export(handler_name, &[arg], &mut self); - - result.map(|_| self.ctx.state).map_err(|err| { - format_err!( - "Failed to handle Ethereum call with handler \"{}\": {}", - handler_name, - format_wasmi_error(err), - ) - }) + self.invoke_handler(handler_name, arg)?; + + Ok(self.take_instance().ctx.state) } pub(crate) fn handle_ethereum_block( mut self, handler_name: &str, - ) -> Result { - self.start_time = Instant::now(); + ) -> Result { + let block = EthereumBlockData::from(self.instance().ctx.block.as_ref()); // Prepare an EthereumBlock for the WASM runtime - let arg = EthereumBlockData::from(self.ctx.block.as_ref()); + let arg = self.instance_mut().asc_new(&block); + + self.invoke_handler(handler_name, arg)?; + + Ok(self.take_instance().ctx.state) + } + + pub(crate) fn instance_mut(&mut self) -> RefMut<'_, WasmInstance> { + RefMut::map(self.instance.borrow_mut(), |i| i.as_mut().unwrap()) + } + + pub(crate) fn take_instance(&mut self) -> WasmInstance { + self.instance.borrow_mut().take().unwrap() + } + + pub(crate) fn instance(&self) -> std::cell::Ref<'_, WasmInstance> { + std::cell::Ref::map(self.instance.borrow(), |i| i.as_ref().unwrap()) + } + + fn invoke_handler(&mut self, handler: &str, arg: AscPtr) -> Result<(), anyhow::Error> { + let func = self + .instance() + .instance + .get_func(handler) + .with_context(|| format!("function {} not found", handler))?; + + func.get1()?(arg.wasm_ptr()).map_err(|e| { + if e.to_string().contains(TRAP_TIMEOUT) { + anyhow::Error::context( + e.into(), + format!( + "Handler '{}' hit the timeout of '{}' seconds", + handler, + self.instance().timeout.unwrap().as_secs() + ), + ) + } else { + anyhow::Error::context(e.into(), format!("Failed to invoke handler '{}'", handler)) + } + }) + } +} + +/// Our usage of the unsafe `wastime::Memory` API relies on the `WasmInstance` being `!Sync`. +/// +/// ```compile_fail +/// fn assert_sync() {} +/// assert_sync::(); +/// ``` +pub(crate) struct WasmInstance { + instance: wasmtime::Instance, + + // In the future there may be multiple memories, but currently there is only one memory per + // module. And at least AS calls it "memory". There is no uninitialized memory in Wasm, memory + // is zeroed when initialized or grown. + memory: Memory, + memory_allocate: Box Result>, - let result = self.module.clone().invoke_export( - handler_name, - &[RuntimeValue::from(self.asc_new(&arg))], - &mut self, + pub ctx: MappingContext, + pub(crate) valid_module: Arc, + pub(crate) host_metrics: Arc, + pub(crate) timeout: Option, + + // Used by ipfs.map. + pub(crate) timeout_stopwatch: Arc>, + + // First free byte in the current arena. + arena_start_ptr: i32, + + // Number of free bytes starting from `arena_start_ptr`. + arena_free_size: i32, +} + +impl WasmInstance { + /// Instantiates the module and sets it to be interrupted after `timeout`. + pub fn from_valid_module_with_ctx( + valid_module: Arc, + ctx: MappingContext, + host_metrics: Arc, + timeout: Option, + ) -> Result { + let mut linker = wasmtime::Linker::new(&wasmtime::Store::new(valid_module.module.engine())); + + // Used by exports to access the instance context. It is `None` while the module is not yet + // instantiated. A desirable consequence is that start function cannot access host exports. + let shared_instance: Rc>> = Rc::new(RefCell::new(None)); + + macro_rules! link { + ($wasm_name:expr, $rust_name:ident, $($param:ident),*) => { + link!($wasm_name, $rust_name, "host_export_other", $($param),*) + }; + + ($wasm_name:expr, $rust_name:ident, $section:expr, $($param:ident),*) => { + let modules = valid_module + .import_name_to_modules + .get($wasm_name) + .into_iter() + .flatten(); + + // link an import with all the modules that require it. + for module in modules { + let func_shared_instance = Rc::downgrade(&shared_instance); + linker.func( + module, + $wasm_name, + move |$($param: u32),*| { + let instance = func_shared_instance.upgrade().unwrap(); + let mut instance = instance.borrow_mut(); + let instance = instance.as_mut().unwrap(); + let _section = instance.host_metrics.stopwatch.start_section($section); + instance.$rust_name( + $($param.into()),* + ).into_wasm_ret() + } + )?; + } + }; + } + + let modules = valid_module + .import_name_to_modules + .get("store.get") + .into_iter() + .flatten(); + + for module in modules { + let func_shared_instance = Rc::downgrade(&shared_instance); + linker.func(module, "store.get", move |entity_ptr: u32, id_ptr: u32| { + let start = Instant::now(); + let instance = func_shared_instance.upgrade().unwrap(); + let mut instance = instance.borrow_mut(); + let instance = instance.as_mut().unwrap(); + let stopwatch = &instance.host_metrics.stopwatch; + let _section = stopwatch.start_section("host_export_store_get"); + let ret = instance + .store_get(entity_ptr.into(), id_ptr.into())? + .wasm_ptr(); + instance + .host_metrics + .observe_host_fn_execution_time(start.elapsed().as_secs_f64(), "store_get"); + Ok(ret) + })?; + } + + let modules = valid_module + .import_name_to_modules + .get("ethereum.call") + .into_iter() + .flatten(); + + for module in modules { + let func_shared_instance = Rc::downgrade(&shared_instance); + linker.func(module, "ethereum.call", move |call_ptr: u32| { + let start = Instant::now(); + let instance = func_shared_instance.upgrade().unwrap(); + let mut instance = instance.borrow_mut(); + let instance = instance.as_mut().unwrap(); + let stopwatch = &instance.host_metrics.stopwatch; + let _section = stopwatch.start_section("host_export_ethereum_call"); + + // For apiVersion >= 0.0.4 the call passed from the mapping includes the + // function signature; subgraphs using an apiVersion < 0.0.4 don't pass + // the the signature along with the call. + let arg = if instance.ctx.host_exports.api_version >= Version::new(0, 0, 4) { + instance.asc_get::<_, AscUnresolvedContractCall_0_0_4>(call_ptr.into()) + } else { + instance.asc_get::<_, AscUnresolvedContractCall>(call_ptr.into()) + }; + + let ret = instance.ethereum_call(arg)?.wasm_ptr(); + instance + .host_metrics + .observe_host_fn_execution_time(start.elapsed().as_secs_f64(), "ethereum_call"); + Ok(ret) + })?; + } + + link!("abort", abort, message_ptr, file_name_ptr, line, column); + link!( + "store.set", + store_set, + "host_export_store_set", + entity, + id, + data + ); + + link!("ipfs.cat", ipfs_cat, "host_export_ipfs_cat", hash_ptr); + link!( + "ipfs.map", + ipfs_map, + "host_export_ipfs_map", + link_ptr, + callback, + user_data, + flags + ); + + link!("store.remove", store_remove, entity_ptr, id_ptr); + + link!("typeConversion.bytesToString", bytes_to_string, ptr); + link!("typeConversion.bytesToHex", bytes_to_hex, ptr); + link!("typeConversion.bigIntToString", big_int_to_string, ptr); + link!("typeConversion.bigIntToHex", big_int_to_hex, ptr); + link!("typeConversion.stringToH160", string_to_h160, ptr); + link!("typeConversion.bytesToBase58", bytes_to_base58, ptr); + + link!("json.fromBytes", json_from_bytes, ptr); + link!("json.try_fromBytes", json_try_from_bytes, ptr); + link!("json.toI64", json_to_i64, ptr); + link!("json.toU64", json_to_u64, ptr); + link!("json.toF64", json_to_f64, ptr); + link!("json.toBigInt", json_to_big_int, ptr); + + link!("crypto.keccak256", crypto_keccak_256, ptr); + + link!("bigInt.plus", big_int_plus, x_ptr, y_ptr); + link!("bigInt.minus", big_int_minus, x_ptr, y_ptr); + link!("bigInt.times", big_int_times, x_ptr, y_ptr); + link!("bigInt.dividedBy", big_int_divided_by, x_ptr, y_ptr); + link!("bigInt.dividedByDecimal", big_int_divided_by_decimal, x, y); + link!("bigInt.mod", big_int_mod, x_ptr, y_ptr); + link!("bigInt.pow", big_int_pow, x_ptr, exp); + + link!("bigDecimal.toString", big_decimal_to_string, ptr); + link!("bigDecimal.fromString", big_decimal_from_string, ptr); + link!("bigDecimal.plus", big_decimal_plus, x_ptr, y_ptr); + link!("bigDecimal.minus", big_decimal_minus, x_ptr, y_ptr); + link!("bigDecimal.times", big_decimal_times, x_ptr, y_ptr); + link!("bigDecimal.divided_by", big_decimal_divided_by, x, y); + link!("bigDecimal.equals", big_decimal_equals, x_ptr, y_ptr); + + link!("dataSource.create", data_source_create, name, params); + link!( + "dataSource.createWithContext", + data_source_create_with_context, + name, + params, + context ); + link!("dataSource.address", data_source_address,); + link!("dataSource.network", data_source_network,); + link!("dataSource.context", data_source_context,); + + link!("ens.nameByHash", ens_name_by_hash, ptr); + + link!("log.log", log_log, level, msg_ptr); + + link!("arweave.transactionData", arweave_transaction_data, ptr); + + link!("box.profile", box_profile, ptr); + + let instance = linker.instantiate(&valid_module.module)?; + + // Provide access to the WASM runtime linear memory + let memory = instance + .get_memory("memory") + .context("Failed to find memory export in the WASM module")?; + + let memory_allocate = instance + .get_func("memory.allocate") + .context("`memory.allocate` function not found")? + .get1()?; + + let timeout_stopwatch = Arc::new(std::sync::Mutex::new(TimeoutStopwatch::start_new())); + if let Some(timeout) = timeout { + // This task is likely to outlive the instance, which is fine. + let interrupt_handle = instance.store().interrupt_handle().unwrap(); + let timeout_stopwatch = timeout_stopwatch.clone(); + graph::spawn_allow_panic(async move { + let minimum_wait = Duration::from_secs(1); + loop { + let time_left = + timeout.checked_sub(timeout_stopwatch.lock().unwrap().elapsed()); + match time_left { + None => break interrupt_handle.interrupt(), // Timed out. + + Some(time) if time < minimum_wait => break interrupt_handle.interrupt(), + Some(time) => tokio::time::delay_for(time).await, + } + } + }); + } + + *shared_instance.borrow_mut() = Some(WasmInstance { + instance, + memory_allocate: Box::new(memory_allocate), + memory, + ctx, + valid_module, + host_metrics, + timeout, + timeout_stopwatch, - result.map(|_| self.ctx.state).map_err(|err| { - format_err!( - "Failed to handle Ethereum block with handler \"{}\": {}", - handler_name, - format_wasmi_error(err) - ) + // `arena_start_ptr` will be set on the first call to `raw_new`. + arena_free_size: 0, + arena_start_ptr: 0, + }); + + Ok(WasmInstanceHandle { + instance: shared_instance.clone(), }) } } -impl AscHeap for WasmiModule { - fn raw_new(&mut self, bytes: &[u8]) -> Result { +impl AscHeap for WasmInstance { + fn raw_new(&mut self, bytes: &[u8]) -> u32 { // We request large chunks from the AssemblyScript allocator to use as arenas that we // manage directly. - static MIN_ARENA_SIZE: u32 = 10_000; + static MIN_ARENA_SIZE: i32 = 10_000; - let size = u32::try_from(bytes.len()).unwrap(); + let size = i32::try_from(bytes.len()).unwrap(); if size > self.arena_free_size { // Allocate a new arena. Any free space left in the previous arena is left unused. This // causes at most half of memory to be wasted, which is acceptable. let arena_size = size.max(MIN_ARENA_SIZE); - let allocated_ptr = self - .module - .clone() - .invoke_export("memory.allocate", &[RuntimeValue::from(arena_size)], self) - .expect("Failed to invoke memory allocation function") - .expect("Function did not return a value") - .try_into::() - .expect("Function did not return u32"); - self.arena_start_ptr = allocated_ptr; + self.arena_start_ptr = (self.memory_allocate)(arena_size).unwrap(); self.arena_free_size = arena_size; }; - let ptr = self.arena_start_ptr; - self.memory.set(ptr, bytes)?; + let ptr = self.arena_start_ptr as usize; + + // Safety: + // First `wasmtime::Memory` is `!Sync`, so two threads cannot simultaneously hold a + // reference into it. Given that, accessing the memory is only unsound if a reference into + // the memory is exists at this point [1]. Since we are in safe code up to this point, that + // reference can only exist if it originated in a previously executed unsafe block. + // Therefore: + // - If no unsafe block exposes references into memory to safe code and each individual + // unsafe block does not cause unsoundness by itself, then the entire program is sound. + // [1] - https://docs.rs/wasmtime/0.17.0/wasmtime/struct.Memory.html + // + // This unsafe block has been checked to not cause unsoundness by itself. + // See also 2155cdca-dfaa-4fba-86e4-289e7683c1bf + unsafe { self.memory.data_unchecked_mut()[ptr..(ptr + bytes.len())].copy_from_slice(bytes) } self.arena_start_ptr += size; self.arena_free_size -= size; - Ok(ptr) + ptr as u32 } - fn get(&self, offset: u32, size: u32) -> Result, Error> { - self.memory.get(offset, size as usize) - } -} + fn get(&self, offset: u32, size: u32) -> Vec { + let offset = offset as usize; + let size = size as usize; -impl HostError for HostExportError where E: fmt::Debug + fmt::Display + Send + Sync + 'static {} - -fn json_from_bytes(bytes: &Vec) -> Result { - serde_json::from_reader(bytes.as_slice()) + // Safety: + // This unsafe block has been checked to not cause unsoundness by itself. + // See 2155cdca-dfaa-4fba-86e4-289e7683c1bf for why this is sufficient. + unsafe { self.memory.data_unchecked()[offset..(offset + size)].to_vec() } + } } // Implementation of externals. -impl WasmiModule { - fn gas(&mut self) -> Result, Trap> { - // This function is called so often that the overhead of calling `Instant::now()` every - // time would be significant, so we spread out the checks. - if self.timeout_checkpoint_count % 100 == 0 { - self.ctx.host_exports.check_timeout(self.start_time)?; - } - self.timeout_checkpoint_count += 1; - Ok(None) - } - +impl WasmInstance { /// function abort(message?: string | null, fileName?: string | null, lineNumber?: u32, columnNumber?: u32): void /// Always returns a trap. fn abort( - &mut self, + &self, message_ptr: AscPtr, file_name_ptr: AscPtr, line_number: u32, column_number: u32, - ) -> Result, Trap> { + ) -> Result<(), Trap> { let message = match message_ptr.is_null() { false => Some(self.asc_get(message_ptr)), true => None, @@ -419,13 +551,10 @@ impl WasmiModule { entity_ptr: AscPtr, id_ptr: AscPtr, data_ptr: AscPtr, - ) -> Result, Trap> { - if self.running_start { - return Err(HostExportError("store.set may not be called in start function").into()); - } + ) -> Result<(), Trap> { let entity = self.asc_get(entity_ptr); let id = self.asc_get(id_ptr); - let data = self.try_asc_get(data_ptr).map_err(HostExportError::from)?; + let data = self.try_asc_get(data_ptr)?; self.ctx.host_exports.store_set( &self.ctx.logger, &mut self.ctx.state, @@ -434,18 +563,11 @@ impl WasmiModule { id, data, )?; - Ok(None) + Ok(()) } /// function store.remove(entity: string, id: string): void - fn store_remove( - &mut self, - entity_ptr: AscPtr, - id_ptr: AscPtr, - ) -> Result, Trap> { - if self.running_start { - return Err(HostExportError("store.remove may not be called in start function").into()); - } + fn store_remove(&mut self, entity_ptr: AscPtr, id_ptr: AscPtr) { let entity = self.asc_get(entity_ptr); let id = self.asc_get(id_ptr); self.ctx.host_exports.store_remove( @@ -455,7 +577,6 @@ impl WasmiModule { entity, id, ); - Ok(None) } /// function store.get(entity: string, id: string): Entity | null @@ -463,7 +584,7 @@ impl WasmiModule { &mut self, entity_ptr: AscPtr, id_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let entity_ptr = self.asc_get(entity_ptr); let id_ptr = self.asc_get(id_ptr); let entity_option = @@ -471,40 +592,37 @@ impl WasmiModule { .host_exports .store_get(&mut self.ctx.state, entity_ptr, id_ptr)?; - Ok(Some(match entity_option { + Ok(match entity_option { Some(entity) => { let _section = self .host_metrics .stopwatch .start_section("store_get_asc_new"); - RuntimeValue::from(self.asc_new(&entity)) + self.asc_new(&entity) } - None => RuntimeValue::from(0), - })) + None => AscPtr::null(), + }) } /// function ethereum.call(call: SmartContractCall): Array | null fn ethereum_call( &mut self, call: UnresolvedContractCall, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self.ctx .host_exports .ethereum_call(&self.ctx.logger, &self.ctx.block, call)?; - Ok(Some(match result { - Some(tokens) => RuntimeValue::from(self.asc_new(tokens.as_slice())), - None => RuntimeValue::from(0), - })) + Ok(match result { + Some(tokens) => self.asc_new(tokens.as_slice()), + None => AscPtr::null(), + }) } /// function typeConversion.bytesToString(bytes: Bytes): string - fn bytes_to_string( - &mut self, - bytes_ptr: AscPtr, - ) -> Result, Trap> { + fn bytes_to_string(&mut self, bytes_ptr: AscPtr) -> AscPtr { let string = host_exports::bytes_to_string(&self.ctx.logger, self.asc_get(bytes_ptr)); - Ok(Some(RuntimeValue::from(self.asc_new(&string)))) + self.asc_new(&string) } /// Converts bytes to a hex string. @@ -512,82 +630,55 @@ impl WasmiModule { /// References: /// https://godoc.org/github.com/ethereum/go-ethereum/common/hexutil#hdr-Encoding_Rules /// https://github.com/ethereum/web3.js/blob/f98fe1462625a6c865125fecc9cb6b414f0a5e83/packages/web3-utils/src/utils.js#L283 - fn bytes_to_hex( - &mut self, - bytes_ptr: AscPtr, - ) -> Result, Trap> { + fn bytes_to_hex(&mut self, bytes_ptr: AscPtr) -> AscPtr { let bytes: Vec = self.asc_get(bytes_ptr); // Even an empty string must be prefixed with `0x`. // Encodes each byte as a two hex digits. - let result = format!("0x{}", hex::encode(bytes)); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + let hex = format!("0x{}", hex::encode(bytes)); + self.asc_new(&hex) } /// function typeConversion.bigIntToString(n: Uint8Array): string - fn big_int_to_string( - &mut self, - big_int_ptr: AscPtr, - ) -> Result, Trap> { - let bytes: Vec = self.asc_get(big_int_ptr); - let n = BigInt::from_signed_bytes_le(&*bytes); - let result = format!("{}", n); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + fn big_int_to_string(&mut self, big_int_ptr: AscPtr) -> AscPtr { + let n: BigInt = self.asc_get(big_int_ptr); + self.asc_new(&n.to_string()) } /// function typeConversion.bigIntToHex(n: Uint8Array): string - fn big_int_to_hex( - &mut self, - big_int_ptr: AscPtr, - ) -> Result, Trap> { + fn big_int_to_hex(&mut self, big_int_ptr: AscPtr) -> AscPtr { let n: BigInt = self.asc_get(big_int_ptr); - let result = self.ctx.host_exports.big_int_to_hex(n); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + let hex = self.ctx.host_exports.big_int_to_hex(n); + self.asc_new(&hex) } /// function typeConversion.stringToH160(s: String): H160 - fn string_to_h160(&mut self, str_ptr: AscPtr) -> Result, Trap> { + fn string_to_h160(&mut self, str_ptr: AscPtr) -> Result, Trap> { let s: String = self.asc_get(str_ptr); let h160 = host_exports::string_to_h160(&s)?; let h160_obj: AscPtr = self.asc_new(&h160); - Ok(Some(RuntimeValue::from(h160_obj))) - } - - /// function typeConversion.i32ToBigInt(i: i32): Uint64Array - fn i32_to_big_int(&mut self, i: i32) -> Result, Trap> { - let bytes = BigInt::from(i).to_signed_bytes_le(); - Ok(Some(RuntimeValue::from(self.asc_new(&*bytes)))) - } - - /// function typeConversion.i32ToBigInt(i: i32): Uint64Array - fn big_int_to_i32(&mut self, n_ptr: AscPtr) -> Result, Trap> { - let n: BigInt = self.asc_get(n_ptr); - let i = self.ctx.host_exports.big_int_to_i32(n)?; - Ok(Some(RuntimeValue::from(i))) + Ok(h160_obj) } /// function json.fromBytes(bytes: Bytes): JSONValue fn json_from_bytes( &mut self, bytes_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result>, Trap> { let bytes: Vec = self.asc_get(bytes_ptr); - let result = json_from_bytes(&bytes).map_err(|e| { - HostExportError(format!( - "Failed to parse JSON from byte array. Bytes: `{bytes:?}`. Error: {error}", - bytes = bytes, - error = e, - )) + + let result = host_exports::json_from_bytes(&bytes).with_context(|| { + format!("Failed to parse JSON from byte array. Bytes: `{:?}`", bytes,) })?; - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + Ok(self.asc_new(&result)) } /// function json.try_fromBytes(bytes: Bytes): Result fn json_try_from_bytes( &mut self, bytes_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, bool>>, Trap> { let bytes: Vec = self.asc_get(bytes_ptr); - let result = json_from_bytes(&bytes).map_err(|e| { + let result = host_exports::json_from_bytes(&bytes).map_err(|e| { warn!( &self.ctx.logger, "Failed to parse JSON from byte array"; @@ -599,17 +690,17 @@ impl WasmiModule { // result type expected by mappings true }); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + Ok(self.asc_new(&result)) } /// function ipfs.cat(link: String): Bytes - fn ipfs_cat(&mut self, link_ptr: AscPtr) -> Result, Trap> { + fn ipfs_cat(&mut self, link_ptr: AscPtr) -> Result, Trap> { let link = self.asc_get(link_ptr); let ipfs_res = self.ctx.host_exports.ipfs_cat(&self.ctx.logger, link); match ipfs_res { Ok(bytes) => { let bytes_obj: AscPtr = self.asc_new(&*bytes); - Ok(Some(RuntimeValue::from(bytes_obj))) + Ok(bytes_obj) } // Return null in case of error. @@ -617,7 +708,7 @@ impl WasmiModule { info!(&self.ctx.logger, "Failed ipfs.cat, returning `null`"; "link" => self.asc_get::(link_ptr), "error" => e.to_string()); - Ok(Some(RuntimeValue::from(0))) + Ok(AscPtr::null()) } } } @@ -629,99 +720,92 @@ impl WasmiModule { callback: AscPtr, user_data: AscPtr>, flags: AscPtr>>, - ) -> Result, Trap> { + ) -> Result<(), Trap> { let link: String = self.asc_get(link_ptr); let callback: String = self.asc_get(callback); - let user_data: store::Value = self.try_asc_get(user_data).map_err(HostExportError::from)?; + let user_data: store::Value = self.try_asc_get(user_data)?; let flags = self.asc_get(flags); + + // Pause the timeout while running ipfs_map, ensure it will be restarted by using a guard. + self.timeout_stopwatch.lock().unwrap().stop(); + let defer_stopwatch = self.timeout_stopwatch.clone(); + let _stopwatch_guard = defer::defer(|| defer_stopwatch.lock().unwrap().start()); + let start_time = Instant::now(); - let result = match HostExports::ipfs_map( + let output_states = HostExports::ipfs_map( &self.ctx.host_exports.link_resolver.clone(), self, link.clone(), &*callback, user_data, flags, - ) { - Ok(output_states) => { - debug!( - &self.ctx.logger, - "Successfully processed file with ipfs.map"; - "link" => &link, - "callback" => &*callback, - "n_calls" => output_states.len(), - "time" => format!("{}ms", start_time.elapsed().as_millis()) - ); - for output_state in output_states { - self.ctx - .state - .entity_cache - .extend(output_state.entity_cache) - .map_err(|e| HostExportError(e.to_string()))?; - self.ctx - .state - .created_data_sources - .extend(output_state.created_data_sources); - } - Ok(None) - } - Err(e) => Err(e.into()), - }; + )?; + + debug!( + &self.ctx.logger, + "Successfully processed file with ipfs.map"; + "link" => &link, + "callback" => &*callback, + "n_calls" => output_states.len(), + "time" => format!("{}ms", start_time.elapsed().as_millis()) + ); + for output_state in output_states { + self.ctx + .state + .entity_cache + .extend(output_state.entity_cache) + .map_err(anyhow::Error::from)?; + self.ctx + .state + .created_data_sources + .extend(output_state.created_data_sources); + } - // Advance this module's start time by the time it took to run the entire - // ipfs_map. This has the effect of not charging this module for the time - // spent running the callback on every JSON object in the IPFS file - self.start_time += start_time.elapsed(); - result + Ok(()) } /// Expects a decimal string. /// function json.toI64(json: String): i64 - fn json_to_i64(&mut self, json_ptr: AscPtr) -> Result, Trap> { + fn json_to_i64(&mut self, json_ptr: AscPtr) -> Result { let number = self.ctx.host_exports.json_to_i64(self.asc_get(json_ptr))?; - Ok(Some(RuntimeValue::from(number))) + Ok(number) } /// Expects a decimal string. /// function json.toU64(json: String): u64 - fn json_to_u64(&mut self, json_ptr: AscPtr) -> Result, Trap> { - let number = self.ctx.host_exports.json_to_u64(self.asc_get(json_ptr))?; - Ok(Some(RuntimeValue::from(number))) + fn json_to_u64(&mut self, json_ptr: AscPtr) -> Result { + Ok(self.ctx.host_exports.json_to_u64(self.asc_get(json_ptr))?) } /// Expects a decimal string. /// function json.toF64(json: String): f64 - fn json_to_f64(&mut self, json_ptr: AscPtr) -> Result, Trap> { - let number = self.ctx.host_exports.json_to_f64(self.asc_get(json_ptr))?; - Ok(Some(RuntimeValue::from(F64::from(number)))) + fn json_to_f64(&mut self, json_ptr: AscPtr) -> Result { + Ok(self.ctx.host_exports.json_to_f64(self.asc_get(json_ptr))?) } /// Expects a decimal string. /// function json.toBigInt(json: String): BigInt - fn json_to_big_int( - &mut self, - json_ptr: AscPtr, - ) -> Result, Trap> { + fn json_to_big_int(&mut self, json_ptr: AscPtr) -> Result, Trap> { let big_int = self .ctx .host_exports .json_to_big_int(self.asc_get(json_ptr))?; let big_int_ptr: AscPtr = self.asc_new(&*big_int); - Ok(Some(RuntimeValue::from(big_int_ptr))) + Ok(big_int_ptr) } /// function crypto.keccak256(input: Bytes): Bytes fn crypto_keccak_256( &mut self, input_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let input = self .ctx .host_exports .crypto_keccak_256(self.asc_get(input_ptr)); let hash_ptr: AscPtr = self.asc_new(input.as_ref()); - Ok(Some(RuntimeValue::from(hash_ptr))) + Ok(hash_ptr) } /// function bigInt.plus(x: BigInt, y: BigInt): BigInt @@ -729,13 +813,13 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .big_int_plus(self.asc_get(x_ptr), self.asc_get(y_ptr)); let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function bigInt.minus(x: BigInt, y: BigInt): BigInt @@ -743,13 +827,13 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .big_int_minus(self.asc_get(x_ptr), self.asc_get(y_ptr)); let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function bigInt.times(x: BigInt, y: BigInt): BigInt @@ -757,13 +841,13 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .big_int_times(self.asc_get(x_ptr), self.asc_get(y_ptr)); let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function bigInt.dividedBy(x: BigInt, y: BigInt): BigInt @@ -771,13 +855,13 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .big_int_divided_by(self.asc_get(x_ptr), self.asc_get(y_ptr))?; let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function bigInt.dividedByDecimal(x: BigInt, y: BigDecimal): BigDecimal @@ -785,13 +869,13 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let x = BigDecimal::new(self.asc_get::(x_ptr), 0); let result = self .ctx .host_exports - .big_decimal_divided_by(x, self.try_asc_get(y_ptr).map_err(HostExportError::from)?)?; - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + .big_decimal_divided_by(x, self.try_asc_get(y_ptr)?)?; + Ok(self.asc_new(&result)) } /// function bigInt.mod(x: BigInt, y: BigInt): BigInt @@ -799,61 +883,62 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .big_int_mod(self.asc_get(x_ptr), self.asc_get(y_ptr)); let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function bigInt.pow(x: BigInt, exp: u8): BigInt fn big_int_pow( &mut self, x_ptr: AscPtr, - exp: u8, - ) -> Result, Trap> { + exp: u32, + ) -> Result, Trap> { + let exp = u8::try_from(exp).map_err(anyhow::Error::from)?; let result = self.ctx.host_exports.big_int_pow(self.asc_get(x_ptr), exp); let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function typeConversion.bytesToBase58(bytes: Bytes): string fn bytes_to_base58( &mut self, bytes_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .bytes_to_base58(self.asc_get(bytes_ptr)); let result_ptr: AscPtr = self.asc_new(&result); - Ok(Some(RuntimeValue::from(result_ptr))) + Ok(result_ptr) } /// function bigDecimal.toString(x: BigDecimal): string fn big_decimal_to_string( &mut self, big_decimal_ptr: AscPtr, - ) -> Result, Trap> { - let result = self.ctx.host_exports.big_decimal_to_string( - self.try_asc_get(big_decimal_ptr) - .map_err(HostExportError::from)?, - ); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + ) -> Result, Trap> { + let result = self + .ctx + .host_exports + .big_decimal_to_string(self.try_asc_get(big_decimal_ptr)?); + Ok(self.asc_new(&result)) } /// function bigDecimal.fromString(x: string): BigDecimal fn big_decimal_from_string( &mut self, string_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let result = self .ctx .host_exports .big_decimal_from_string(self.asc_get(string_ptr))?; - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + Ok(self.asc_new(&result)) } /// function bigDecimal.plus(x: BigDecimal, y: BigDecimal): BigDecimal @@ -861,12 +946,12 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { - let result = self.ctx.host_exports.big_decimal_plus( - self.try_asc_get(x_ptr).map_err(HostExportError::from)?, - self.try_asc_get(y_ptr).map_err(HostExportError::from)?, - ); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + ) -> Result, Trap> { + let result = self + .ctx + .host_exports + .big_decimal_plus(self.try_asc_get(x_ptr)?, self.try_asc_get(y_ptr)?); + Ok(self.asc_new(&result)) } /// function bigDecimal.minus(x: BigDecimal, y: BigDecimal): BigDecimal @@ -874,12 +959,12 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { - let result = self.ctx.host_exports.big_decimal_minus( - self.try_asc_get(x_ptr).map_err(HostExportError::from)?, - self.try_asc_get(y_ptr).map_err(HostExportError::from)?, - ); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + ) -> Result, Trap> { + let result = self + .ctx + .host_exports + .big_decimal_minus(self.try_asc_get(x_ptr)?, self.try_asc_get(y_ptr)?); + Ok(self.asc_new(&result)) } /// function bigDecimal.times(x: BigDecimal, y: BigDecimal): BigDecimal @@ -887,12 +972,12 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { - let result = self.ctx.host_exports.big_decimal_times( - self.try_asc_get(x_ptr).map_err(HostExportError::from)?, - self.try_asc_get(y_ptr).map_err(HostExportError::from)?, - ); - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + ) -> Result, Trap> { + let result = self + .ctx + .host_exports + .big_decimal_times(self.try_asc_get(x_ptr)?, self.try_asc_get(y_ptr)?); + Ok(self.asc_new(&result)) } /// function bigDecimal.dividedBy(x: BigDecimal, y: BigDecimal): BigDecimal @@ -900,12 +985,12 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { - let result = self.ctx.host_exports.big_decimal_divided_by( - self.try_asc_get(x_ptr).map_err(HostExportError::from)?, - self.try_asc_get(y_ptr).map_err(HostExportError::from)?, - )?; - Ok(Some(RuntimeValue::from(self.asc_new(&result)))) + ) -> Result, Trap> { + let result = self + .ctx + .host_exports + .big_decimal_divided_by(self.try_asc_get(x_ptr)?, self.try_asc_get(y_ptr)?)?; + Ok(self.asc_new(&result)) } /// function bigDecimal.equals(x: BigDecimal, y: BigDecimal): bool @@ -913,12 +998,11 @@ impl WasmiModule { &mut self, x_ptr: AscPtr, y_ptr: AscPtr, - ) -> Result, Trap> { - let equals = self.ctx.host_exports.big_decimal_equals( - self.try_asc_get(x_ptr).map_err(HostExportError::from)?, - self.try_asc_get(y_ptr).map_err(HostExportError::from)?, - ); - Ok(Some(RuntimeValue::I32(if equals { 1 } else { 0 }))) + ) -> Result { + Ok(self + .ctx + .host_exports + .big_decimal_equals(self.try_asc_get(x_ptr)?, self.try_asc_get(y_ptr)?)) } /// function dataSource.create(name: string, params: Array): void @@ -926,7 +1010,7 @@ impl WasmiModule { &mut self, name_ptr: AscPtr, params_ptr: AscPtr>>, - ) -> Result, Trap> { + ) -> Result<(), Trap> { let name: String = self.asc_get(name_ptr); let params: Vec = self.asc_get(params_ptr); self.ctx.host_exports.data_source_create( @@ -936,7 +1020,7 @@ impl WasmiModule { params, None, )?; - Ok(None) + Ok(()) } /// function createWithContext(name: string, params: Array, context: DataSourceContext): void @@ -945,12 +1029,10 @@ impl WasmiModule { name_ptr: AscPtr, params_ptr: AscPtr>>, context_ptr: AscPtr, - ) -> Result, Trap> { + ) -> Result<(), Trap> { let name: String = self.asc_get(name_ptr); let params: Vec = self.asc_get(params_ptr); - let context: HashMap<_, _> = self - .try_asc_get(context_ptr) - .map_err(HostExportError::from)?; + let context: HashMap<_, _> = self.try_asc_get(context_ptr)?; self.ctx.host_exports.data_source_create( &self.ctx.logger, &mut self.ctx.state, @@ -958,328 +1040,57 @@ impl WasmiModule { params, Some(context.into()), )?; - Ok(None) + Ok(()) } /// function dataSource.address(): Bytes - fn data_source_address(&mut self) -> Result, Trap> { - Ok(Some(RuntimeValue::from( - self.asc_new(&self.ctx.host_exports.data_source_address()), - ))) + fn data_source_address(&mut self) -> AscPtr { + self.asc_new(&self.ctx.host_exports.data_source_address()) } /// function dataSource.network(): String - fn data_source_network(&mut self) -> Result, Trap> { - Ok(Some(RuntimeValue::from( - self.asc_new(&self.ctx.host_exports.data_source_network()), - ))) + fn data_source_network(&mut self) -> AscPtr { + self.asc_new(&self.ctx.host_exports.data_source_network()) } /// function dataSource.context(): DataSourceContext - fn data_source_context(&mut self) -> Result, Trap> { - Ok(Some(RuntimeValue::from( - self.asc_new(&self.ctx.host_exports.data_source_context()), - ))) + fn data_source_context(&mut self) -> AscPtr { + self.asc_new(&self.ctx.host_exports.data_source_context()) } - fn ens_name_by_hash( - &mut self, - hash_ptr: AscPtr, - ) -> Result, Trap> { + fn ens_name_by_hash(&mut self, hash_ptr: AscPtr) -> Result, Trap> { let hash: String = self.asc_get(hash_ptr); let name = self.ctx.host_exports.ens_name_by_hash(&*hash)?; // map `None` to `null`, and `Some(s)` to a runtime string Ok(name - .map(|name| RuntimeValue::from(self.asc_new(&*name))) - .or(Some(RuntimeValue::from(0)))) + .map(|name| self.asc_new(&*name)) + .unwrap_or(AscPtr::null())) } - fn log_log( - &mut self, - level: i32, - msg: AscPtr, - ) -> Result, Trap> { + fn log_log(&mut self, level: u32, msg: AscPtr) { let level = LogLevel::from(level).into(); let msg: String = self.asc_get(msg); self.ctx.host_exports.log_log(&self.ctx.logger, level, msg); - Ok(None) } /// function arweave.transactionData(txId: string): Bytes | null fn arweave_transaction_data( &mut self, tx_id: AscPtr, - ) -> Result, Trap> { + ) -> Result, Trap> { let tx_id: String = self.asc_get(tx_id); let data = self.ctx.host_exports.arweave_transaction_data(&tx_id); Ok(data - .map(|data| RuntimeValue::from(self.asc_new(&*data))) - .or(Some(RuntimeValue::from(0)))) + .map(|data| self.asc_new(&*data)) + .unwrap_or(AscPtr::null())) } /// function box.profile(address: string): JSONValue | null - fn box_profile(&mut self, address: AscPtr) -> Result, Trap> { + fn box_profile(&mut self, address: AscPtr) -> Result, Trap> { let address: String = self.asc_get(address); let profile = self.ctx.host_exports.box_profile(&address); Ok(profile - .map(|profile| RuntimeValue::from(self.asc_new(&profile))) - .or(Some(RuntimeValue::from(0)))) - } -} - -impl Externals for WasmiModule { - fn invoke_index( - &mut self, - index: usize, - args: RuntimeArgs, - ) -> Result, Trap> { - // This function is hot, so avoid the cost of registering metrics. - if index == GAS_FUNC_INDEX { - return self.gas(); - } - - // Start a catch-all section for exports that don't have their own section. - let stopwatch = self.host_metrics.stopwatch.clone(); - let _section = stopwatch.start_section("host_export_other"); - let start = Instant::now(); - let res = match index { - ABORT_FUNC_INDEX => self.abort( - args.nth_checked(0)?, - args.nth_checked(1)?, - args.nth_checked(2)?, - args.nth_checked(3)?, - ), - STORE_SET_FUNC_INDEX => { - let _section = stopwatch.start_section("host_export_store_set"); - self.store_set( - args.nth_checked(0)?, - args.nth_checked(1)?, - args.nth_checked(2)?, - ) - } - STORE_GET_FUNC_INDEX => { - let _section = stopwatch.start_section("host_export_store_get"); - self.store_get(args.nth_checked(0)?, args.nth_checked(1)?) - } - STORE_REMOVE_FUNC_INDEX => { - self.store_remove(args.nth_checked(0)?, args.nth_checked(1)?) - } - ETHEREUM_CALL_FUNC_INDEX => { - let _section = stopwatch.start_section("host_export_ethereum_call"); - - // For apiVersion >= 0.0.4 the call passed from the mapping includes the - // function signature; subgraphs using an apiVersion < 0.0.4 don't pass - // the the signature along with the call. - let arg = if self.ctx.host_exports.api_version >= Version::new(0, 0, 4) { - self.asc_get::<_, AscUnresolvedContractCall_0_0_4>(args.nth_checked(0)?) - } else { - self.asc_get::<_, AscUnresolvedContractCall>(args.nth_checked(0)?) - }; - - self.ethereum_call(arg) - } - TYPE_CONVERSION_BYTES_TO_STRING_FUNC_INDEX => { - self.bytes_to_string(args.nth_checked(0)?) - } - TYPE_CONVERSION_BYTES_TO_HEX_FUNC_INDEX => self.bytes_to_hex(args.nth_checked(0)?), - TYPE_CONVERSION_BIG_INT_TO_STRING_FUNC_INDEX => { - self.big_int_to_string(args.nth_checked(0)?) - } - TYPE_CONVERSION_BIG_INT_TO_HEX_FUNC_INDEX => self.big_int_to_hex(args.nth_checked(0)?), - TYPE_CONVERSION_STRING_TO_H160_FUNC_INDEX => self.string_to_h160(args.nth_checked(0)?), - TYPE_CONVERSION_I32_TO_BIG_INT_FUNC_INDEX => self.i32_to_big_int(args.nth_checked(0)?), - TYPE_CONVERSION_BIG_INT_TO_I32_FUNC_INDEX => self.big_int_to_i32(args.nth_checked(0)?), - JSON_FROM_BYTES_FUNC_INDEX => self.json_from_bytes(args.nth_checked(0)?), - JSON_TO_I64_FUNC_INDEX => self.json_to_i64(args.nth_checked(0)?), - JSON_TO_U64_FUNC_INDEX => self.json_to_u64(args.nth_checked(0)?), - JSON_TO_F64_FUNC_INDEX => self.json_to_f64(args.nth_checked(0)?), - JSON_TO_BIG_INT_FUNC_INDEX => self.json_to_big_int(args.nth_checked(0)?), - IPFS_CAT_FUNC_INDEX => { - let _section = stopwatch.start_section("host_export_ipfs_cat"); - self.ipfs_cat(args.nth_checked(0)?) - } - CRYPTO_KECCAK_256_INDEX => self.crypto_keccak_256(args.nth_checked(0)?), - BIG_INT_PLUS => self.big_int_plus(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_INT_MINUS => self.big_int_minus(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_INT_TIMES => self.big_int_times(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_INT_DIVIDED_BY => { - self.big_int_divided_by(args.nth_checked(0)?, args.nth_checked(1)?) - } - BIG_INT_DIVIDED_BY_DECIMAL => { - self.big_int_divided_by_decimal(args.nth_checked(0)?, args.nth_checked(1)?) - } - BIG_INT_MOD => self.big_int_mod(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_INT_POW => self.big_int_pow(args.nth_checked(0)?, args.nth_checked(1)?), - TYPE_CONVERSION_BYTES_TO_BASE_58_INDEX => self.bytes_to_base58(args.nth_checked(0)?), - BIG_DECIMAL_PLUS => self.big_decimal_plus(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_DECIMAL_MINUS => self.big_decimal_minus(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_DECIMAL_TIMES => self.big_decimal_times(args.nth_checked(0)?, args.nth_checked(1)?), - BIG_DECIMAL_DIVIDED_BY => { - self.big_decimal_divided_by(args.nth_checked(0)?, args.nth_checked(1)?) - } - BIG_DECIMAL_EQUALS => { - self.big_decimal_equals(args.nth_checked(0)?, args.nth_checked(1)?) - } - BIG_DECIMAL_TO_STRING => self.big_decimal_to_string(args.nth_checked(0)?), - BIG_DECIMAL_FROM_STRING => self.big_decimal_from_string(args.nth_checked(0)?), - IPFS_MAP_FUNC_INDEX => { - let _section = stopwatch.start_section("host_export_ipfs_map"); - self.ipfs_map( - args.nth_checked(0)?, - args.nth_checked(1)?, - args.nth_checked(2)?, - args.nth_checked(3)?, - ) - } - DATA_SOURCE_CREATE_INDEX => { - self.data_source_create(args.nth_checked(0)?, args.nth_checked(1)?) - } - ENS_NAME_BY_HASH => self.ens_name_by_hash(args.nth_checked(0)?), - LOG_LOG => self.log_log(args.nth_checked(0)?, args.nth_checked(1)?), - DATA_SOURCE_ADDRESS => self.data_source_address(), - DATA_SOURCE_NETWORK => self.data_source_network(), - DATA_SOURCE_CREATE_WITH_CONTEXT => self.data_source_create_with_context( - args.nth_checked(0)?, - args.nth_checked(1)?, - args.nth_checked(2)?, - ), - DATA_SOURCE_CONTEXT => self.data_source_context(), - JSON_TRY_FROM_BYTES_FUNC_INDEX => self.json_try_from_bytes(args.nth_checked(0)?), - ARWEAVE_TRANSACTION_DATA => self.arweave_transaction_data(args.nth_checked(0)?), - BOX_PROFILE => self.box_profile(args.nth_checked(0)?), - _ => panic!("Unimplemented function at {}", index), - }; - // Record execution time - fn_index_to_metrics_string(index).map(|name| { - self.host_metrics - .observe_host_fn_execution_time(start.elapsed().as_secs_f64(), name); - }); - res - } -} - -/// Env module resolver -pub struct EnvModuleResolver; - -impl ModuleImportResolver for EnvModuleResolver { - fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { - Ok(match field_name { - "gas" => FuncInstance::alloc_host(signature.clone(), GAS_FUNC_INDEX), - "abort" => FuncInstance::alloc_host(signature.clone(), ABORT_FUNC_INDEX), - _ => { - return Err(Error::Instantiation(format!( - "Export '{}' not found", - field_name - ))); - } - }) - } -} - -pub struct ModuleResolver; - -impl ModuleImportResolver for ModuleResolver { - fn resolve_func(&self, field_name: &str, signature: &Signature) -> Result { - let signature = signature.clone(); - Ok(match field_name { - // store - "store.set" => FuncInstance::alloc_host(signature, STORE_SET_FUNC_INDEX), - "store.remove" => FuncInstance::alloc_host(signature, STORE_REMOVE_FUNC_INDEX), - "store.get" => FuncInstance::alloc_host(signature, STORE_GET_FUNC_INDEX), - - // ethereum - "ethereum.call" => FuncInstance::alloc_host(signature, ETHEREUM_CALL_FUNC_INDEX), - - // typeConversion - "typeConversion.bytesToString" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_BYTES_TO_STRING_FUNC_INDEX) - } - "typeConversion.bytesToHex" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_BYTES_TO_HEX_FUNC_INDEX) - } - "typeConversion.bigIntToString" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_BIG_INT_TO_STRING_FUNC_INDEX) - } - "typeConversion.bigIntToHex" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_BIG_INT_TO_HEX_FUNC_INDEX) - } - "typeConversion.stringToH160" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_STRING_TO_H160_FUNC_INDEX) - } - "typeConversion.i32ToBigInt" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_I32_TO_BIG_INT_FUNC_INDEX) - } - "typeConversion.bigIntToI32" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_BIG_INT_TO_I32_FUNC_INDEX) - } - "typeConversion.bytesToBase58" => { - FuncInstance::alloc_host(signature, TYPE_CONVERSION_BYTES_TO_BASE_58_INDEX) - } - - // json - "json.fromBytes" => FuncInstance::alloc_host(signature, JSON_FROM_BYTES_FUNC_INDEX), - "json.try_fromBytes" => { - FuncInstance::alloc_host(signature, JSON_TRY_FROM_BYTES_FUNC_INDEX) - } - "json.toI64" => FuncInstance::alloc_host(signature, JSON_TO_I64_FUNC_INDEX), - "json.toU64" => FuncInstance::alloc_host(signature, JSON_TO_U64_FUNC_INDEX), - "json.toF64" => FuncInstance::alloc_host(signature, JSON_TO_F64_FUNC_INDEX), - "json.toBigInt" => FuncInstance::alloc_host(signature, JSON_TO_BIG_INT_FUNC_INDEX), - - // ipfs - "ipfs.cat" => FuncInstance::alloc_host(signature, IPFS_CAT_FUNC_INDEX), - "ipfs.map" => FuncInstance::alloc_host(signature, IPFS_MAP_FUNC_INDEX), - - // crypto - "crypto.keccak256" => FuncInstance::alloc_host(signature, CRYPTO_KECCAK_256_INDEX), - - // bigInt - "bigInt.plus" => FuncInstance::alloc_host(signature, BIG_INT_PLUS), - "bigInt.minus" => FuncInstance::alloc_host(signature, BIG_INT_MINUS), - "bigInt.times" => FuncInstance::alloc_host(signature, BIG_INT_TIMES), - "bigInt.dividedBy" => FuncInstance::alloc_host(signature, BIG_INT_DIVIDED_BY), - "bigInt.dividedByDecimal" => { - FuncInstance::alloc_host(signature, BIG_INT_DIVIDED_BY_DECIMAL) - } - "bigInt.mod" => FuncInstance::alloc_host(signature, BIG_INT_MOD), - "bigInt.pow" => FuncInstance::alloc_host(signature, BIG_INT_POW), - - // bigDecimal - "bigDecimal.plus" => FuncInstance::alloc_host(signature, BIG_DECIMAL_PLUS), - "bigDecimal.minus" => FuncInstance::alloc_host(signature, BIG_DECIMAL_MINUS), - "bigDecimal.times" => FuncInstance::alloc_host(signature, BIG_DECIMAL_TIMES), - "bigDecimal.dividedBy" => FuncInstance::alloc_host(signature, BIG_DECIMAL_DIVIDED_BY), - "bigDecimal.equals" => FuncInstance::alloc_host(signature, BIG_DECIMAL_EQUALS), - "bigDecimal.toString" => FuncInstance::alloc_host(signature, BIG_DECIMAL_TO_STRING), - "bigDecimal.fromString" => FuncInstance::alloc_host(signature, BIG_DECIMAL_FROM_STRING), - - // dataSource - "dataSource.create" => FuncInstance::alloc_host(signature, DATA_SOURCE_CREATE_INDEX), - "dataSource.address" => FuncInstance::alloc_host(signature, DATA_SOURCE_ADDRESS), - "dataSource.network" => FuncInstance::alloc_host(signature, DATA_SOURCE_NETWORK), - "dataSource.createWithContext" => { - FuncInstance::alloc_host(signature, DATA_SOURCE_CREATE_WITH_CONTEXT) - } - "dataSource.context" => FuncInstance::alloc_host(signature, DATA_SOURCE_CONTEXT), - - // ens.nameByHash - "ens.nameByHash" => FuncInstance::alloc_host(signature, ENS_NAME_BY_HASH), - - // log.log - "log.log" => FuncInstance::alloc_host(signature, LOG_LOG), - - "arweave.transactionData" => { - FuncInstance::alloc_host(signature, ARWEAVE_TRANSACTION_DATA) - } - "box.profile" => FuncInstance::alloc_host(signature, BOX_PROFILE), - - // Unknown export - _ => { - return Err(Error::Instantiation(format!( - "Export '{}' not found", - field_name - ))); - } - }) + .map(|profile| self.asc_new(&profile)) + .unwrap_or(AscPtr::null())) } } diff --git a/runtime/wasm/src/module/stopwatch.rs b/runtime/wasm/src/module/stopwatch.rs new file mode 100644 index 00000000000..2db203e32c3 --- /dev/null +++ b/runtime/wasm/src/module/stopwatch.rs @@ -0,0 +1,62 @@ +// Copied from https://github.com/ellisonch/rust-stopwatch +// Copyright (c) 2014 Chucky Ellison under MIT license + +use std::default::Default; +use std::time::{Duration, Instant}; + +#[derive(Clone, Copy)] +pub struct TimeoutStopwatch { + /// The time the stopwatch was started last, if ever. + start_time: Option, + /// The time elapsed while the stopwatch was running (between start() and stop()). + elapsed: Duration, +} + +impl Default for TimeoutStopwatch { + fn default() -> TimeoutStopwatch { + TimeoutStopwatch { + start_time: None, + elapsed: Duration::from_secs(0), + } + } +} + +impl TimeoutStopwatch { + /// Returns a new stopwatch. + pub fn new() -> TimeoutStopwatch { + let sw: TimeoutStopwatch = Default::default(); + return sw; + } + + /// Returns a new stopwatch which will immediately be started. + pub fn start_new() -> TimeoutStopwatch { + let mut sw = TimeoutStopwatch::new(); + sw.start(); + return sw; + } + + /// Starts the stopwatch. + pub fn start(&mut self) { + self.start_time = Some(Instant::now()); + } + + /// Stops the stopwatch. + pub fn stop(&mut self) { + self.elapsed = self.elapsed(); + self.start_time = None; + } + + /// Returns the elapsed time since the start of the stopwatch. + pub fn elapsed(&self) -> Duration { + match self.start_time { + // stopwatch is running + Some(t1) => { + return t1.elapsed() + self.elapsed; + } + // stopwatch is not running + None => { + return self.elapsed; + } + } + } +} diff --git a/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index 7a22928cc7e..411b4b5b5ad 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -1,17 +1,14 @@ use ethabi::Token; use hex; use std::collections::{BTreeMap, HashMap}; -use std::env; use std::io::Cursor; use std::str::FromStr; -use wasmi::nan_preserving_float::F64; use crate::host_exports::HostExports; use graph::components::store::*; use graph::data::store::scalar; use graph::data::subgraph::*; use graph::mock::MockEthereumAdapter; -use graph::prelude::Error; use graph_chain_arweave::adapter::ArweaveAdapter; use graph_core; use graph_core::three_box::ThreeBoxAdapter; @@ -28,7 +25,7 @@ fn test_valid_module_and_store( subgraph_id: &str, data_source: DataSource, ) -> ( - WasmiModule, + WasmInstanceHandle, Arc, ) { let store = STORE.clone(); @@ -58,22 +55,21 @@ fn test_valid_module_and_store( stopwatch_metrics, )); - let module = WasmiModule::from_valid_module_with_ctx( - Arc::new( - ValidModule::new( - parity_wasm::deserialize_buffer(data_source.mapping.runtime.as_ref()).unwrap(), - ) - .unwrap(), - ), + let module = WasmInstance::from_valid_module_with_ctx( + Arc::new(ValidModule::new(data_source.mapping.runtime.as_ref()).unwrap()), mock_context(deployment_id, data_source, store.clone()), host_metrics, + std::env::var(crate::host::TIMEOUT_ENV_VAR) + .ok() + .and_then(|s| u64::from_str(&s).ok()) + .map(std::time::Duration::from_secs), ) .unwrap(); (module, store) } -fn test_module(subgraph_id: &str, data_source: DataSource) -> WasmiModule { +fn test_module(subgraph_id: &str, data_source: DataSource) -> WasmInstanceHandle { test_valid_module_and_store(subgraph_id, data_source).0 } @@ -153,10 +149,6 @@ fn mock_host_exports( )), store.clone(), store, - std::env::var(crate::host::TIMEOUT_ENV_VAR) - .ok() - .and_then(|s| u64::from_str(&s).ok()) - .map(std::time::Duration::from_secs), arweave_adapter, three_box_adapter, ) @@ -176,49 +168,57 @@ fn mock_context( } } -impl WasmiModule { - fn takes_val_returns_ptr

(&mut self, fn_name: &str, val: RuntimeValue) -> AscPtr

{ - self.module - .clone() - .invoke_export(fn_name, &[val], self) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer") +impl WasmInstanceHandle { + fn get_func(&self, name: &str) -> wasmtime::Func { + self.instance().instance.get_func(name).unwrap() } - fn takes_ptr_returns_ptr(&mut self, fn_name: &str, arg: AscPtr

) -> AscPtr { - self.module - .clone() - .invoke_export(fn_name, &[RuntimeValue::from(arg)], self) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer") + fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr { + let func = self.get_func(f).get1().unwrap(); + let ptr: u32 = func(arg.wasm_ptr()).unwrap(); + ptr.into() } - fn takes_ptr_ptr_returns_ptr( - &mut self, - fn_name: &str, - arg1: AscPtr

, - arg2: AscPtr, - ) -> AscPtr { - self.module - .clone() - .invoke_export( - fn_name, - &[RuntimeValue::from(arg1), RuntimeValue::from(arg2)], - self, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer") + fn invoke_export2(&self, f: &str, arg0: AscPtr, arg1: AscPtr) -> AscPtr { + let func = self.get_func(f).get2().unwrap(); + let ptr: u32 = func(arg0.wasm_ptr(), arg1.wasm_ptr()).unwrap(); + ptr.into() + } + + fn invoke_export2_void( + &self, + f: &str, + arg0: AscPtr, + arg1: AscPtr, + ) -> Result<(), wasmtime::Trap> { + let func = self.get_func(f).get2().unwrap(); + func(arg0.wasm_ptr(), arg1.wasm_ptr()) + } + + fn takes_ptr_returns_val(&mut self, fn_name: &str, v: AscPtr

) -> V { + let func = self.get_func(fn_name).get1().unwrap(); + func(v.wasm_ptr()).unwrap() + } + + fn takes_val_returns_ptr

(&mut self, fn_name: &str, val: impl wasmtime::WasmTy) -> AscPtr

{ + let func = self.get_func(fn_name).get1().unwrap(); + let ptr: u32 = func(val).unwrap(); + ptr.into() + } +} + +impl AscHeap for WasmInstanceHandle { + fn raw_new(&mut self, bytes: &[u8]) -> u32 { + self.instance_mut().raw_new(bytes) + } + + fn get(&self, offset: u32, size: u32) -> Vec { + self.instance().get(offset, size) } } -#[test] -fn json_conversions() { +#[tokio::test] +async fn json_conversions() { let mut module = test_module( "jsonConversions", mock_data_source("wasm_test/string_to_number.wasm"), @@ -227,57 +227,25 @@ fn json_conversions() { // test u64 conversion let number = 9223372036850770800; let number_ptr = module.asc_new(&number.to_string()); - let converted: u64 = module - .module - .clone() - .invoke_export("testToU64", &[RuntimeValue::from(number_ptr)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return I64"); - assert_eq!(number, converted); + let converted: i64 = module.takes_ptr_returns_val("testToU64", number_ptr); + assert_eq!(number, u64::from_le_bytes(converted.to_le_bytes())); // test i64 conversion let number = -9223372036850770800; let number_ptr = module.asc_new(&number.to_string()); - let converted: i64 = module - .module - .clone() - .invoke_export("testToI64", &[RuntimeValue::from(number_ptr)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return I64"); + let converted: i64 = module.takes_ptr_returns_val("testToI64", number_ptr); assert_eq!(number, converted); // test f64 conversion - let number = F64::from(-9223372036850770.92345034); - let number_ptr = module.asc_new(&number.to_float().to_string()); - let converted: F64 = module - .module - .clone() - .invoke_export("testToF64", &[RuntimeValue::from(number_ptr)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return F64"); + let number = -9223372036850770.92345034; + let number_ptr = module.asc_new(&number.to_string()); + let converted: f64 = module.takes_ptr_returns_val("testToF64", number_ptr); assert_eq!(number, converted); // test BigInt conversion let number = "-922337203685077092345034"; let number_ptr = module.asc_new(number); - let big_int_obj: AscPtr = module - .module - .clone() - .invoke_export( - "testToBigInt", - &[RuntimeValue::from(number_ptr)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let big_int_obj: AscPtr = module.invoke_export("testToBigInt", number_ptr); let bytes: Vec = module.asc_get(big_int_obj); assert_eq!( scalar::BigInt::from_str(number).unwrap(), @@ -285,8 +253,8 @@ fn json_conversions() { ); } -#[test] -fn json_parsing() { +#[tokio::test] +async fn json_parsing() { let mut module = test_module( "jsonParsing", mock_data_source("wasm_test/json_parsing.wasm"), @@ -296,18 +264,7 @@ fn json_parsing() { let s = "foo"; // Invalid because there are no quotes around `foo` let bytes: &[u8] = s.as_ref(); let bytes_ptr = module.asc_new(bytes); - let return_value: AscPtr = module - .module - .clone() - .invoke_export( - "handleJsonError", - &[RuntimeValue::from(bytes_ptr)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return a string"); + let return_value: AscPtr = module.invoke_export("handleJsonError", bytes_ptr); let output: String = module.asc_get(return_value); assert_eq!(output, "ERROR: true"); @@ -315,46 +272,30 @@ fn json_parsing() { let s = "\"foo\""; // Valid because there are quotes around `foo` let bytes: &[u8] = s.as_ref(); let bytes_ptr = module.asc_new(bytes); - let return_value: AscPtr = module - .module - .clone() - .invoke_export( - "handleJsonError", - &[RuntimeValue::from(bytes_ptr)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return a string"); - let output: String = module.asc_get(return_value); + let return_value: AscPtr = module.invoke_export("handleJsonError", bytes_ptr); + let output: String = module.instance().asc_get(return_value); assert_eq!(output, "OK: foo"); } -#[tokio::test] +#[tokio::test(threaded_scheduler)] async fn ipfs_cat() { - graph::spawn_blocking(async { - let ipfs = Arc::new(ipfs_api::IpfsClient::default()); - let hash = ipfs.add(Cursor::new("42")).await.unwrap().hash; - - let mut module = test_module("ipfsCat", mock_data_source("wasm_test/ipfs_cat.wasm")); - let converted: AscPtr = module - .module - .clone() - .invoke_export( - "ipfsCatString", - &[RuntimeValue::from(module.asc_new(&hash))], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); - let data: String = module.asc_get(converted); - assert_eq!(data, "42"); + let ipfs = Arc::new(ipfs_api::IpfsClient::default()); + let hash = ipfs.add(Cursor::new("42")).await.unwrap().hash; + + // Ipfs host functions use `block_on` which must be called from a sync context, + // so we replicate what we do `spawn_module`. + let runtime = tokio::runtime::Handle::current(); + std::thread::spawn(move || { + runtime.enter(|| { + let mut module = test_module("ipfsCat", mock_data_source("wasm_test/ipfs_cat.wasm")); + let arg = module.asc_new(&hash); + let converted: AscPtr = module.invoke_export("ipfsCatString", arg); + let data: String = module.instance().asc_get(converted); + assert_eq!(data, "42"); + }) }) - .await - .unwrap(); + .join() + .unwrap() } // The user_data value we use with calls to ipfs_map @@ -387,36 +328,54 @@ async fn ipfs_map() { ipfs: Arc, subgraph_id: &'static str, json_string: String, - ) -> Result, Error> { - let (mut module, store) = - test_valid_module_and_store(subgraph_id, mock_data_source("wasm_test/ipfs_map.wasm")); + ) -> Result, anyhow::Error> { let hash = if json_string == BAD_IPFS_HASH { "Qm".to_string() } else { ipfs.add(Cursor::new(json_string)).await.unwrap().hash }; - let user_data = RuntimeValue::from(module.asc_new(USER_DATA)); - let converted = module.module.clone().invoke_export( - "ipfsMap", - &[RuntimeValue::from(module.asc_new(&hash)), user_data], - &mut module, - )?; - assert_eq!(None, converted); - let mut mods = module - .ctx - .state - .entity_cache - .as_modifications(store.as_ref())? - .modifications; - - // Bring the modifications into a predictable order (by entity_id) - mods.sort_by(|a, b| { - a.entity_key() - .entity_id - .partial_cmp(&b.entity_key().entity_id) - .unwrap() - }); - Ok(mods) + + // Ipfs host functions use `block_on` which must be called from a sync context, + // so we replicate what we do `spawn_module`. + let runtime = tokio::runtime::Handle::current(); + std::thread::spawn(move || { + runtime.enter(|| { + let (mut module, store) = test_valid_module_and_store( + subgraph_id, + mock_data_source("wasm_test/ipfs_map.wasm"), + ); + let value = module.instance_mut().asc_new(&hash); + let user_data = module.instance_mut().asc_new(USER_DATA); + + // Invoke the callback + let func = module + .instance() + .instance + .get_func("ipfsMap") + .unwrap() + .get2() + .unwrap(); + let _: () = func(value.wasm_ptr(), user_data.wasm_ptr())?; + let mut mods = module + .take_instance() + .ctx + .state + .entity_cache + .as_modifications(store.as_ref())? + .modifications; + + // Bring the modifications into a predictable order (by entity_id) + mods.sort_by(|a, b| { + a.entity_key() + .entity_id + .partial_cmp(&b.entity_key().entity_id) + .unwrap() + }); + Ok(mods) + }) + }) + .join() + .unwrap() }; // Try it with two valid objects @@ -430,10 +389,13 @@ async fn ipfs_map() { // Valid JSON, but not what the callback expected; it will // fail on an assertion - let err: Error = run_ipfs_map(ipfs.clone(), subgraph_id, format!("{}\n[1,2]", str1)) + let err = run_ipfs_map(ipfs.clone(), subgraph_id, format!("{}\n[1,2]", str1)) .await .unwrap_err(); - assert!(err.to_string().contains("JSON value is not an object.")); + assert!( + format!("{:#}", err).contains("JSON value is not an object."), + format!("{:#}", err) + ); // Malformed JSON let errmsg = run_ipfs_map(ipfs.clone(), subgraph_id, format!("{}\n[", str1)) @@ -449,14 +411,16 @@ async fn ipfs_map() { assert_eq!(0, ops.len()); // Missing entry in the JSON object - let errmsg = run_ipfs_map( - ipfs.clone(), - subgraph_id, - "{\"value\": \"drei\"}".to_string(), - ) - .await - .unwrap_err() - .to_string(); + let errmsg = format!( + "{:#}", + run_ipfs_map( + ipfs.clone(), + subgraph_id, + "{\"value\": \"drei\"}".to_string(), + ) + .await + .unwrap_err() + ); assert!(errmsg.contains("JSON value is not a string.")); // Bad IPFS hash. @@ -467,34 +431,33 @@ async fn ipfs_map() { assert!(errmsg.contains("ApiError")); } -#[tokio::test] +#[tokio::test(threaded_scheduler)] async fn ipfs_fail() { - graph::spawn_blocking(async { - let mut module = test_module("ipfsFail", mock_data_source("wasm_test/ipfs_cat.wasm")); - - let hash = module.asc_new("invalid hash"); - assert!(module - .takes_ptr_returns_ptr::<_, AscString>("ipfsCat", hash,) - .is_null()); + let runtime = tokio::runtime::Handle::current(); + + // Ipfs host functions use `block_on` which must be called from a sync context, + // so we replicate what we do `spawn_module`. + std::thread::spawn(move || { + runtime.enter(|| { + let mut module = test_module("ipfsFail", mock_data_source("wasm_test/ipfs_cat.wasm")); + + let hash = module.instance_mut().asc_new("invalid hash"); + assert!(module + .invoke_export::<_, AscString>("ipfsCat", hash,) + .is_null()); + }) }) - .await - .unwrap(); + .join() + .unwrap() } -#[test] -fn crypto_keccak256() { +#[tokio::test] +async fn crypto_keccak256() { let mut module = test_module("cryptoKeccak256", mock_data_source("wasm_test/crypto.wasm")); let input: &[u8] = "eth".as_ref(); let input: AscPtr = module.asc_new(input); - let hash: AscPtr = module - .module - .clone() - .invoke_export("hash", &[RuntimeValue::from(input)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let hash: AscPtr = module.invoke_export("hash", input); let hash: Vec = module.asc_get(hash); assert_eq!( hex::encode(hash), @@ -502,72 +465,8 @@ fn crypto_keccak256() { ); } -#[test] -fn token_numeric_conversion() { - let mut module = test_module( - "TestNumericConversion", - mock_data_source("wasm_test/token_to_numeric.wasm"), - ); - - // Convert numeric to token and back. - let num = i32::min_value(); - let token_ptr: AscPtr> = - module.takes_val_returns_ptr("token_from_i32", RuntimeValue::from(num)); - let num_return = module - .module - .clone() - .invoke_export( - "token_to_i32", - &[RuntimeValue::from(token_ptr)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into::() - .expect("call did not return i32"); - assert_eq!(num, num_return); -} - -#[test] -fn big_int_to_from_i32() { - let mut module = test_module( - "BigIntToFromI32", - mock_data_source("wasm_test/big_int_to_from_i32.wasm"), - ); - - // Convert i32 to BigInt - let input: i32 = -157; - let output_ptr: AscPtr = module - .module - .clone() - .invoke_export("i32_to_big_int", &[RuntimeValue::from(input)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); - let output: BigInt = module.asc_get(output_ptr); - assert_eq!(output, BigInt::from(-157 as i32)); - - // Convert BigInt to i32 - let input = BigInt::from(-50 as i32); - let input_ptr: AscPtr = module.asc_new(&input); - let output: i32 = module - .module - .clone() - .invoke_export( - "big_int_to_i32", - &[RuntimeValue::from(input_ptr)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); - assert_eq!(output, -50 as i32); -} - -#[test] -fn big_int_to_hex() { +#[tokio::test] +async fn big_int_to_hex() { let mut module = test_module( "BigIntToHex", mock_data_source("wasm_test/big_int_to_hex.wasm"), @@ -576,46 +475,21 @@ fn big_int_to_hex() { // Convert zero to hex let zero = BigInt::from_unsigned_u256(&U256::zero()); let zero: AscPtr = module.asc_new(&zero); - let zero_hex_ptr: AscPtr = module - .module - .clone() - .invoke_export("big_int_to_hex", &[RuntimeValue::from(zero)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let zero_hex_ptr: AscPtr = module.invoke_export("big_int_to_hex", zero); let zero_hex_str: String = module.asc_get(zero_hex_ptr); assert_eq!(zero_hex_str, "0x0"); // Convert 1 to hex let one = BigInt::from_unsigned_u256(&U256::one()); let one: AscPtr = module.asc_new(&one); - let one_hex_ptr: AscPtr = module - .module - .clone() - .invoke_export("big_int_to_hex", &[RuntimeValue::from(one)], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let one_hex_ptr: AscPtr = module.invoke_export("big_int_to_hex", one); let one_hex_str: String = module.asc_get(one_hex_ptr); assert_eq!(one_hex_str, "0x1"); // Convert U256::max_value() to hex let u256_max = BigInt::from_unsigned_u256(&U256::max_value()); let u256_max: AscPtr = module.asc_new(&u256_max); - let u256_max_hex_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "big_int_to_hex", - &[RuntimeValue::from(u256_max)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let u256_max_hex_ptr: AscPtr = module.invoke_export("big_int_to_hex", u256_max); let u256_max_hex_str: String = module.asc_get(u256_max_hex_ptr); assert_eq!( u256_max_hex_str, @@ -623,8 +497,8 @@ fn big_int_to_hex() { ); } -#[test] -fn big_int_arithmetic() { +#[tokio::test] +async fn big_int_arithmetic() { let mut module = test_module( "BigIntArithmetic", mock_data_source("wasm_test/big_int_arithmetic.wasm"), @@ -635,18 +509,7 @@ fn big_int_arithmetic() { let zero: AscPtr = module.asc_new(&zero); let one = BigInt::from(1); let one: AscPtr = module.asc_new(&one); - let result_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "plus", - &[RuntimeValue::from(zero), RuntimeValue::from(one)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let result_ptr: AscPtr = module.invoke_export2("plus", zero, one); let result: BigInt = module.asc_get(result_ptr); assert_eq!(result, BigInt::from(1)); @@ -655,18 +518,7 @@ fn big_int_arithmetic() { let zero: AscPtr = module.asc_new(&zero); let one = BigInt::from(1); let one: AscPtr = module.asc_new(&one); - let result_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "plus", - &[RuntimeValue::from(zero), RuntimeValue::from(one)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let result_ptr: AscPtr = module.invoke_export2("plus", zero, one); let result: BigInt = module.asc_get(result_ptr); assert_eq!(result, BigInt::from(128)); @@ -675,18 +527,7 @@ fn big_int_arithmetic() { let five: AscPtr = module.asc_new(&five); let ten = BigInt::from(10); let ten: AscPtr = module.asc_new(&ten); - let result_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "minus", - &[RuntimeValue::from(five), RuntimeValue::from(ten)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let result_ptr: AscPtr = module.invoke_export2("minus", five, ten); let result: BigInt = module.asc_get(result_ptr); assert_eq!(result, BigInt::from(-5)); @@ -695,18 +536,7 @@ fn big_int_arithmetic() { let minus_twenty: AscPtr = module.asc_new(&minus_twenty); let five = BigInt::from(5); let five: AscPtr = module.asc_new(&five); - let result_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "times", - &[RuntimeValue::from(minus_twenty), RuntimeValue::from(five)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let result_ptr: AscPtr = module.invoke_export2("times", minus_twenty, five); let result: BigInt = module.asc_get(result_ptr); assert_eq!(result, BigInt::from(-100)); @@ -715,18 +545,7 @@ fn big_int_arithmetic() { let five: AscPtr = module.asc_new(&five); let two = BigInt::from(2); let two: AscPtr = module.asc_new(&two); - let result_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "dividedBy", - &[RuntimeValue::from(five), RuntimeValue::from(two)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let result_ptr: AscPtr = module.invoke_export2("dividedBy", five, two); let result: BigInt = module.asc_get(result_ptr); assert_eq!(result, BigInt::from(2)); @@ -735,35 +554,24 @@ fn big_int_arithmetic() { let five: AscPtr = module.asc_new(&five); let two = BigInt::from(2); let two: AscPtr = module.asc_new(&two); - let result_ptr: AscPtr = module - .module - .clone() - .invoke_export( - "mod", - &[RuntimeValue::from(five), RuntimeValue::from(two)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let result_ptr: AscPtr = module.invoke_export2("mod", five, two); let result: BigInt = module.asc_get(result_ptr); assert_eq!(result, BigInt::from(1)); } -#[test] -fn abort() { - let mut module = test_module("abort", mock_data_source("wasm_test/abort.wasm")); - let err = module - .module - .clone() - .invoke_export("abort", &[], &mut module) - .unwrap_err(); - assert_eq!(err.to_string(), "Trap: Trap { kind: Host(HostExportError(\"Mapping aborted at abort.ts, line 6, column 2, with message: not true\")) }"); +#[tokio::test] +async fn abort() { + let module = test_module("abort", mock_data_source("wasm_test/abort.wasm")); + let func = module.get_func("abort").get0().unwrap(); + let res: Result<(), _> = func(); + assert!(res + .unwrap_err() + .to_string() + .contains("line 6, column 2, with message: not true")); } -#[test] -fn bytes_to_base58() { +#[tokio::test] +async fn bytes_to_base58() { let mut module = test_module( "bytesToBase58", mock_data_source("wasm_test/bytes_to_base58.wasm"), @@ -771,28 +579,25 @@ fn bytes_to_base58() { let bytes = hex::decode("12207D5A99F603F231D53A4F39D1521F98D2E8BB279CF29BEBFD0687DC98458E7F89") .unwrap(); let bytes_ptr = module.asc_new(bytes.as_slice()); - let result_ptr: AscPtr = module.takes_ptr_returns_ptr("bytes_to_base58", bytes_ptr); + let result_ptr: AscPtr = module.invoke_export("bytes_to_base58", bytes_ptr); let base58: String = module.asc_get(result_ptr); assert_eq!(base58, "QmWmyoMoctfbAaiEs2G46gpeUmhqFRDW6KWo64y5r581Vz"); } -#[test] -fn data_source_create() { +#[tokio::test] +async fn data_source_create() { let run_data_source_create = move |name: String, params: Vec| - -> Result, Error> { + -> Result, wasmtime::Trap> { let mut module = test_module( "DataSourceCreate", mock_data_source("wasm_test/data_source_create.wasm"), ); - let name = RuntimeValue::from(module.asc_new(&name)); - let params = RuntimeValue::from(module.asc_new(&*params)); - module - .module - .clone() - .invoke_export("dataSourceCreate", &[name, params], &mut module)?; - Ok(module.ctx.state.created_data_sources) + let name = module.asc_new(&name); + let params = module.asc_new(&*params); + module.invoke_export2_void("dataSourceCreate", name, params)?; + Ok(module.take_instance().ctx.state.created_data_sources) }; // Test with a valid template @@ -810,19 +615,16 @@ fn data_source_create() { let params = vec![String::from("0xc000000000000000000000000000000000000000")]; match run_data_source_create(template.clone(), params.clone()) { Ok(_) => panic!("expected an error because the template does not exist"), - Err(e) => assert_eq!( - e.to_string(), - "Trap: Trap { kind: Host(HostExportError(\ - \"Failed to create data source from name `nonexistent template`: \ + Err(e) => assert!(e.to_string().contains( + "Failed to create data source from name `nonexistent template`: \ No template with this name in parent data source `example data source`. \ - Available names: example template.\"\ - )) }" - ), + Available names: example template." + )), }; } -#[test] -fn ens_name_by_hash() { +#[tokio::test] +async fn ens_name_by_hash() { let mut module = test_module( "EnsNameByHash", mock_data_source("wasm_test/ens_name_by_hash.wasm"), @@ -831,29 +633,19 @@ fn ens_name_by_hash() { let hash = "0x7f0c1b04d1a4926f9c635a030eeb611d4c26e5e73291b32a1c7a4ac56935b5b3"; let name = "dealdrafts"; test_store::insert_ens_name(hash, name); - let converted: AscPtr = module - .module - .clone() - .invoke_export( - "nameByHash", - &[RuntimeValue::from(module.asc_new(hash))], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let val = module.asc_new(hash); + let converted: AscPtr = module.invoke_export("nameByHash", val); let data: String = module.asc_get(converted); assert_eq!(data, name); let hash = module.asc_new("impossible keccak hash"); assert!(module - .takes_ptr_returns_ptr::<_, AscString>("nameByHash", hash) + .invoke_export::<_, AscString>("nameByHash", hash) .is_null()); } -#[test] -fn entity_store() { +#[tokio::test] +async fn entity_store() { let (mut module, store) = test_valid_module_and_store("entityStore", mock_data_source("wasm_test/store.wasm")); @@ -866,15 +658,9 @@ fn entity_store() { let subgraph_id = SubgraphDeploymentId::new("entityStore").unwrap(); test_store::insert_entities(subgraph_id, vec![("User", alex), ("User", steve)]).unwrap(); - let get_user = move |module: &mut WasmiModule, id: &str| -> Option { - let entity_ptr: AscPtr = module - .module - .clone() - .invoke_export("getUser", &[RuntimeValue::from(module.asc_new(id))], module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return pointer"); + let get_user = move |module: &mut WasmInstanceHandle, id: &str| -> Option { + let id = module.asc_new(id); + let entity_ptr: AscPtr = module.invoke_export("getUser", id); if entity_ptr.is_null() { None } else { @@ -886,19 +672,12 @@ fn entity_store() { } }; - let load_and_set_user_name = |module: &mut WasmiModule, id: &str, name: &str| { + let load_and_set_user_name = |module: &mut WasmInstanceHandle, id: &str, name: &str| { + let id_ptr = module.asc_new(id); + let name_ptr = module.asc_new(name); module - .module - .clone() - .invoke_export( - "loadAndSetUserName", - &[ - RuntimeValue::from(module.asc_new(id)), - RuntimeValue::from(module.asc_new(name)), - ], - module, - ) - .expect("call failed"); + .invoke_export2_void("loadAndSetUserName", id_ptr, name_ptr) + .unwrap(); }; // store.get of a nonexistent user @@ -909,10 +688,13 @@ fn entity_store() { // Load, set, save cycle for an existing entity load_and_set_user_name(&mut module, "steve", "Steve-O"); - let mut mods = module - .ctx - .state - .entity_cache + + // We need to empty the cache for the next test + let cache = std::mem::replace( + &mut module.instance_mut().ctx.state.entity_cache, + EntityCache::new(store.clone()), + ); + let mut mods = cache .as_modifications(store.as_ref()) .unwrap() .modifications; @@ -926,13 +708,13 @@ fn entity_store() { } // Load, set, save cycle for a new entity with fulltext API - module.ctx.state.entity_cache = EntityCache::new(store.clone()); load_and_set_user_name(&mut module, "herobrine", "Brine-O"); let mut fulltext_entities = BTreeMap::new(); let mut fulltext_fields = BTreeMap::new(); fulltext_fields.insert("name".to_string(), vec!["search".to_string()]); fulltext_entities.insert("User".to_string(), fulltext_fields); let mut mods = module + .take_instance() .ctx .state .entity_cache diff --git a/runtime/wasm/src/module/test/abi.rs b/runtime/wasm/src/module/test/abi.rs index dc713a21bfb..7bfb5145c8c 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -1,41 +1,33 @@ use super::*; +use std::env; -#[test] -fn unbounded_loop() { +#[tokio::test(threaded_scheduler)] +async fn unbounded_loop() { // Set handler timeout to 3 seconds. env::set_var(crate::host::TIMEOUT_ENV_VAR, "3"); - let mut module = test_module( + let module = test_module( "unboundedLoop", mock_data_source("wasm_test/non_terminating.wasm"), ); - module.start_time = Instant::now(); - let err = module - .module - .clone() - .invoke_export("loop", &[], &mut module) - .unwrap_err(); - assert_eq!( - err.to_string(), - "Trap: Trap { kind: Host(HostExportError(\"Mapping handler timed out\")) }" - ); + let func = module.get_func("loop").get0().unwrap(); + let res: Result<(), _> = func(); + assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); } -#[test] -fn unbounded_recursion() { - let mut module = test_module( +#[tokio::test] +async fn unbounded_recursion() { + let module = test_module( "unboundedRecursion", mock_data_source("wasm_test/non_terminating.wasm"), ); - let err = module - .module - .clone() - .invoke_export("rabbit_hole", &[], &mut module) - .unwrap_err(); - assert_eq!(err.to_string(), "Trap: Trap { kind: StackOverflow }"); + let func = module.get_func("rabbit_hole").get0().unwrap(); + let res: Result<(), _> = func(); + let err_msg = format!("{}", res.unwrap_err().to_string()); + assert!(err_msg.contains("call stack exhausted"), err_msg); } -#[test] -fn abi_array() { +#[tokio::test] +async fn abi_array() { let mut module = test_module("abiArray", mock_data_source("wasm_test/abi_classes.wasm")); let vec = vec![ @@ -46,8 +38,7 @@ fn abi_array() { ]; let vec_obj: AscPtr>> = module.asc_new(&*vec); - let new_vec_obj: AscPtr>> = - module.takes_ptr_returns_ptr("test_array", vec_obj); + let new_vec_obj: AscPtr>> = module.invoke_export("test_array", vec_obj); let new_vec: Vec = module.asc_get(new_vec_obj); assert_eq!( @@ -62,8 +53,8 @@ fn abi_array() { ) } -#[test] -fn abi_subarray() { +#[tokio::test] +async fn abi_subarray() { let mut module = test_module( "abiSubarray", mock_data_source("wasm_test/abi_classes.wasm"), @@ -73,14 +64,14 @@ fn abi_subarray() { let vec_obj: AscPtr> = module.asc_new(&*vec); let new_vec_obj: AscPtr> = - module.takes_ptr_returns_ptr("byte_array_third_quarter", vec_obj); + module.invoke_export("byte_array_third_quarter", vec_obj); let new_vec: Vec = module.asc_get(new_vec_obj); assert_eq!(new_vec, vec![3]) } -#[test] -fn abi_bytes_and_fixed_bytes() { +#[tokio::test] +async fn abi_bytes_and_fixed_bytes() { let mut module = test_module( "abiBytesAndFixedBytes", mock_data_source("wasm_test/abi_classes.wasm"), @@ -90,8 +81,7 @@ fn abi_bytes_and_fixed_bytes() { let bytes1_ptr = module.asc_new::(&*bytes1); let bytes2_ptr = module.asc_new::(&*bytes2); - let new_vec_obj: AscPtr = - module.takes_ptr_ptr_returns_ptr("concat", bytes1_ptr, bytes2_ptr); + let new_vec_obj: AscPtr = module.invoke_export2("concat", bytes1_ptr, bytes2_ptr); // This should be bytes1 and bytes2 concatenated. let new_vec: Vec = module.asc_get(new_vec_obj); @@ -103,8 +93,8 @@ fn abi_bytes_and_fixed_bytes() { /// Test a roundtrip Token -> Payload -> Token identity conversion through asc, /// and assert the final token is the same as the starting one. -#[test] -fn abi_ethabi_token_identity() { +#[tokio::test] +async fn abi_ethabi_token_identity() { let mut module = test_module( "abiEthabiTokenIdentity", mock_data_source("wasm_test/abi_token.wasm"), @@ -116,9 +106,9 @@ fn abi_ethabi_token_identity() { let token_address_ptr = module.asc_new(&token_address); let new_address_obj: AscPtr> = - module.takes_ptr_returns_ptr("token_to_address", token_address_ptr); + module.invoke_export("token_to_address", token_address_ptr); - let new_token_ptr = module.takes_ptr_returns_ptr("token_from_address", new_address_obj); + let new_token_ptr = module.invoke_export("token_from_address", new_address_obj); let new_token = module.asc_get(new_token_ptr); assert_eq!(token_address, new_token); @@ -128,9 +118,9 @@ fn abi_ethabi_token_identity() { let token_bytes_ptr = module.asc_new(&token_bytes); let new_bytes_obj: AscPtr> = - module.takes_ptr_returns_ptr("token_to_bytes", token_bytes_ptr); + module.invoke_export("token_to_bytes", token_bytes_ptr); - let new_token_ptr = module.takes_ptr_returns_ptr("token_from_bytes", new_bytes_obj); + let new_token_ptr = module.invoke_export("token_from_bytes", new_bytes_obj); let new_token = module.asc_get(new_token_ptr); assert_eq!(token_bytes, new_token); @@ -139,10 +129,9 @@ fn abi_ethabi_token_identity() { let int_token = Token::Int(U256([256, 453452345, 0, 42])); let int_token_ptr = module.asc_new(&int_token); - let new_int_obj: AscPtr> = - module.takes_ptr_returns_ptr("token_to_int", int_token_ptr); + let new_int_obj: AscPtr> = module.invoke_export("token_to_int", int_token_ptr); - let new_token_ptr = module.takes_ptr_returns_ptr("token_from_int", new_int_obj); + let new_token_ptr = module.invoke_export("token_from_int", new_int_obj); let new_token = module.asc_get(new_token_ptr); assert_eq!(int_token, new_token); @@ -152,9 +141,9 @@ fn abi_ethabi_token_identity() { let uint_token_ptr = module.asc_new(&uint_token); let new_uint_obj: AscPtr> = - module.takes_ptr_returns_ptr("token_to_uint", uint_token_ptr); + module.invoke_export("token_to_uint", uint_token_ptr); - let new_token_ptr = module.takes_ptr_returns_ptr("token_from_uint", new_uint_obj); + let new_token_ptr = module.invoke_export("token_from_uint", new_uint_obj); let new_token = module.asc_get(new_token_ptr); assert_eq!(uint_token, new_token); @@ -164,21 +153,10 @@ fn abi_ethabi_token_identity() { let token_bool = Token::Bool(true); let token_bool_ptr = module.asc_new(&token_bool); - let boolean: bool = module - .module - .clone() - .invoke_export( - "token_to_bool", - &[RuntimeValue::from(token_bool_ptr)], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into::() - .expect("call did not return bool"); - - let new_token_ptr = - module.takes_val_returns_ptr("token_from_bool", RuntimeValue::from(boolean as u32)); + let func = module.get_func("token_to_bool").get1().unwrap(); + let boolean: i32 = func(token_bool_ptr.wasm_ptr()).unwrap(); + + let new_token_ptr = module.takes_val_returns_ptr("token_from_bool", boolean); let new_token = module.asc_get(new_token_ptr); assert_eq!(token_bool, new_token); @@ -188,9 +166,9 @@ fn abi_ethabi_token_identity() { let token_string_ptr = module.asc_new(&token_string); let new_string_obj: AscPtr = - module.takes_ptr_returns_ptr("token_to_string", token_string_ptr); + module.invoke_export("token_to_string", token_string_ptr); - let new_token_ptr = module.takes_ptr_returns_ptr("token_from_string", new_string_obj); + let new_token_ptr = module.invoke_export("token_from_string", new_string_obj); let new_token = module.asc_get(new_token_ptr); assert_eq!(token_string, new_token); @@ -201,16 +179,16 @@ fn abi_ethabi_token_identity() { let new_array_ptr = module.asc_new(&token_array_nested); let new_array_obj: AscEnumArray = - module.takes_ptr_returns_ptr("token_to_array", new_array_ptr); + module.invoke_export("token_to_array", new_array_ptr); - let new_token_ptr = module.takes_ptr_returns_ptr("token_from_array", new_array_obj); + let new_token_ptr = module.invoke_export("token_from_array", new_array_obj); let new_token: Token = module.asc_get(new_token_ptr); assert_eq!(new_token, token_array_nested); } -#[test] -fn abi_store_value() { +#[tokio::test] +async fn abi_store_value() { use graph::data::store::Value; let mut module = test_module( @@ -219,68 +197,49 @@ fn abi_store_value() { ); // Value::Null - let null_value_ptr: AscPtr> = module - .module - .clone() - .invoke_export("value_null", &[], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return ptr"); + let func = module.get_func("value_null").get0().unwrap(); + let ptr: u32 = func().unwrap(); + let null_value_ptr: AscPtr> = ptr.into(); let null_value: Value = module.try_asc_get(null_value_ptr).unwrap(); assert_eq!(null_value, Value::Null); // Value::String let string = "some string"; let string_ptr = module.asc_new(string); - let new_value_ptr = module.takes_ptr_returns_ptr("value_from_string", string_ptr); + let new_value_ptr = module.invoke_export("value_from_string", string_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::from(string)); // Value::Int let int = i32::min_value(); - let new_value_ptr = module.takes_val_returns_ptr("value_from_int", RuntimeValue::from(int)); + let new_value_ptr = module.takes_val_returns_ptr("value_from_int", int); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Int(int)); // Value::BigDecimal let big_decimal = BigDecimal::from_str("3.14159001").unwrap(); let big_decimal_ptr = module.asc_new(&big_decimal); - let new_value_ptr = module.takes_ptr_returns_ptr("value_from_big_decimal", big_decimal_ptr); + let new_value_ptr = module.invoke_export("value_from_big_decimal", big_decimal_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::BigDecimal(big_decimal)); let big_decimal = BigDecimal::new(10.into(), 5); let big_decimal_ptr = module.asc_new(&big_decimal); - let new_value_ptr = module.takes_ptr_returns_ptr("value_from_big_decimal", big_decimal_ptr); + let new_value_ptr = module.invoke_export("value_from_big_decimal", big_decimal_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::BigDecimal(1_000_000.into())); // Value::Bool let boolean = true; - let new_value_ptr = module.takes_val_returns_ptr( - "value_from_bool", - RuntimeValue::I32(if boolean { 1 } else { 0 }), - ); + let new_value_ptr = + module.takes_val_returns_ptr("value_from_bool", if boolean { 1 } else { 0 }); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Bool(boolean)); // Value::List - let new_value_ptr = module - .module - .clone() - .invoke_export( - "array_from_values", - &[ - RuntimeValue::from(module.asc_new(string)), - RuntimeValue::from(int), - ], - &mut module, - ) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return ptr"); + let func = module.get_func("array_from_values").get2().unwrap(); + let new_value_ptr: u32 = func(module.asc_new(string).wasm_ptr(), int).unwrap(); + let new_value_ptr = AscPtr::from(new_value_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!( new_value, @@ -292,7 +251,7 @@ fn abi_store_value() { Value::String("bar".to_owned()), ]; let array_ptr = module.asc_new(array); - let new_value_ptr = module.takes_ptr_returns_ptr("value_from_array", array_ptr); + let new_value_ptr = module.invoke_export("value_from_array", array_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!( new_value, @@ -305,14 +264,14 @@ fn abi_store_value() { // Value::Bytes let bytes: &[u8] = &[0, 2, 5]; let bytes_ptr: AscPtr = module.asc_new(bytes); - let new_value_ptr = module.takes_ptr_returns_ptr("value_from_bytes", bytes_ptr); + let new_value_ptr = module.invoke_export("value_from_bytes", bytes_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!(new_value, Value::Bytes(bytes.into())); // Value::BigInt let bytes: &[u8] = &[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1]; let bytes_ptr: AscPtr = module.asc_new(bytes); - let new_value_ptr = module.takes_ptr_returns_ptr("value_from_bigint", bytes_ptr); + let new_value_ptr = module.invoke_export("value_from_bigint", bytes_ptr); let new_value: Value = module.try_asc_get(new_value_ptr).unwrap(); assert_eq!( new_value, @@ -320,15 +279,14 @@ fn abi_store_value() { ); } -#[test] -fn abi_h160() { +#[tokio::test] +async fn abi_h160() { let mut module = test_module("abiH160", mock_data_source("wasm_test/abi_classes.wasm")); let address = H160::zero(); // As an `Uint8Array` let array_buffer: AscPtr = module.asc_new(&address); - let new_address_obj: AscPtr = - module.takes_ptr_returns_ptr("test_address", array_buffer); + let new_address_obj: AscPtr = module.invoke_export("test_address", array_buffer); // This should have 1 added to the first and last byte. let new_address: H160 = module.asc_get(new_address_obj); @@ -339,25 +297,25 @@ fn abi_h160() { ) } -#[test] -fn string() { +#[tokio::test] +async fn string() { let mut module = test_module("string", mock_data_source("wasm_test/abi_classes.wasm")); let string = " 漢字Double_Me🇧🇷 "; let trimmed_string_ptr = module.asc_new(string); let trimmed_string_obj: AscPtr = - module.takes_ptr_returns_ptr("repeat_twice", trimmed_string_ptr); + module.invoke_export("repeat_twice", trimmed_string_ptr); let doubled_string: String = module.asc_get(trimmed_string_obj); assert_eq!(doubled_string, string.repeat(2)) } -#[test] -fn abi_big_int() { +#[tokio::test] +async fn abi_big_int() { let mut module = test_module("abiBigInt", mock_data_source("wasm_test/abi_classes.wasm")); // Test passing in 0 and increment it by 1 let old_uint = U256::zero(); let array_buffer: AscPtr = module.asc_new(&BigInt::from_unsigned_u256(&old_uint)); - let new_uint_obj: AscPtr = module.takes_ptr_returns_ptr("test_uint", array_buffer); + let new_uint_obj: AscPtr = module.invoke_export("test_uint", array_buffer); let new_uint: BigInt = module.asc_get(new_uint_obj); assert_eq!(new_uint, BigInt::from(1 as i32)); let new_uint = new_uint.to_unsigned_u256(); @@ -366,15 +324,15 @@ fn abi_big_int() { // Test passing in -50 and increment it by 1 let old_uint = BigInt::from(-50); let array_buffer: AscPtr = module.asc_new(&old_uint); - let new_uint_obj: AscPtr = module.takes_ptr_returns_ptr("test_uint", array_buffer); + let new_uint_obj: AscPtr = module.invoke_export("test_uint", array_buffer); let new_uint: BigInt = module.asc_get(new_uint_obj); assert_eq!(new_uint, BigInt::from(-49 as i32)); let new_uint_from_u256 = BigInt::from_signed_u256(&new_uint.to_signed_u256()); assert_eq!(new_uint, new_uint_from_u256); } -#[test] -fn big_int_to_string() { +#[tokio::test] +async fn big_int_to_string() { let mut module = test_module( "bigIntToString", mock_data_source("wasm_test/big_int_to_string.wasm"), @@ -383,28 +341,22 @@ fn big_int_to_string() { let big_int_str = "30145144166666665000000000000000000"; let big_int = BigInt::from_str(big_int_str).unwrap(); let ptr: AscPtr = module.asc_new(&big_int); - let string_obj: AscPtr = module.takes_ptr_returns_ptr("big_int_to_string", ptr); + let string_obj: AscPtr = module.invoke_export("big_int_to_string", ptr); let string: String = module.asc_get(string_obj); assert_eq!(string, big_int_str); } // This should panic rather than exhibiting UB. It's hard to test for UB, but // when reproducing a SIGILL was observed which would be caught by this. -#[test] +#[tokio::test] #[should_panic] -fn invalid_discriminant() { - let mut module = test_module( +async fn invalid_discriminant() { + let module = test_module( "invalidDiscriminant", mock_data_source("wasm_test/abi_store_value.wasm"), ); - let value_ptr = module - .module - .clone() - .invoke_export("invalid_discriminant", &[], &mut module) - .expect("call failed") - .expect("call returned nothing") - .try_into() - .expect("call did not return ptr"); - let _value: Value = module.try_asc_get(value_ptr).unwrap(); + let func = module.get_func("invalid_discriminant").get0().unwrap(); + let ptr: u32 = func().unwrap(); + let _value: Value = module.try_asc_get(ptr.into()).unwrap(); } diff --git a/runtime/wasm/src/to_from/external.rs b/runtime/wasm/src/to_from/external.rs index 2378ace6cd2..4d0eab0d2eb 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -5,9 +5,9 @@ use graph::components::ethereum::{ EthereumBlockData, EthereumCallData, EthereumEventData, EthereumTransactionData, }; use graph::data::store; +use graph::prelude::anyhow::{ensure, Error}; use graph::prelude::serde_json; use graph::prelude::web3::types as web3; -use graph::prelude::{format_err, Error}; use graph::prelude::{BigDecimal, BigInt}; use crate::asc_abi::class::*; @@ -85,14 +85,15 @@ impl TryFromAscObj for BigDecimal { // Validate the exponent. let exp = -big_decimal.as_bigint_and_exponent().1; - if exp < BigDecimal::MIN_EXP.into() || exp > BigDecimal::MAX_EXP.into() { - return Err(format_err!( + let min_exp: i64 = BigDecimal::MIN_EXP.into(); + let max_exp: i64 = BigDecimal::MAX_EXP.into(); + ensure!( + min_exp <= exp && exp <= max_exp, + format!( "big decimal exponent `{}` is outside the `{}` to `{}` range", - exp, - BigDecimal::MIN_EXP, - BigDecimal::MAX_EXP - )); - } + exp, min_exp, max_exp + ) + ); Ok(big_decimal) } } @@ -429,8 +430,8 @@ impl FromAscObj for UnresolvedContractCall { } } -impl From for LogLevel { - fn from(i: i32) -> Self { +impl From for LogLevel { + fn from(i: u32) -> Self { match i { 0 => LogLevel::Critical, 1 => LogLevel::Error, diff --git a/runtime/wasm/src/to_from/mod.rs b/runtime/wasm/src/to_from/mod.rs index a4ff950647a..5651cb0f988 100644 --- a/runtime/wasm/src/to_from/mod.rs +++ b/runtime/wasm/src/to_from/mod.rs @@ -1,4 +1,4 @@ -use graph::prelude::Error; +use graph::prelude::anyhow::Error; use std::collections::HashMap; use std::hash::Hash; use std::iter::FromIterator; diff --git a/runtime/wasm/wasm_test/big_int_to_from_i32.ts b/runtime/wasm/wasm_test/big_int_to_from_i32.ts deleted file mode 100644 index bdc191c4291..00000000000 --- a/runtime/wasm/wasm_test/big_int_to_from_i32.ts +++ /dev/null @@ -1,16 +0,0 @@ -import "allocator/arena"; - -export { memory }; - -declare namespace typeConversion { - function i32ToBigInt(i: i32): Uint8Array - function bigIntToI32(n: Uint8Array): i32 -} - -export function big_int_to_i32(n: Uint8Array): i32 { - return typeConversion.bigIntToI32(n) -} - -export function i32_to_big_int(i: i32): Uint8Array { - return typeConversion.i32ToBigInt(i) -} diff --git a/runtime/wasm/wasm_test/big_int_to_from_i32.wasm b/runtime/wasm/wasm_test/big_int_to_from_i32.wasm deleted file mode 100644 index 5410076d8e2..00000000000 Binary files a/runtime/wasm/wasm_test/big_int_to_from_i32.wasm and /dev/null differ diff --git a/runtime/wasm/wasm_test/token_to_numeric.ts b/runtime/wasm/wasm_test/token_to_numeric.ts deleted file mode 100644 index 0c9e176fb6c..00000000000 --- a/runtime/wasm/wasm_test/token_to_numeric.ts +++ /dev/null @@ -1,39 +0,0 @@ -import "allocator/arena"; - -export { memory }; - -enum TokenKind { - ADDRESS = 0, - FIXED_BYTES = 1, - BYTES = 2, - INT = 3, - UINT = 4, - BOOL = 5, - STRING = 6, - FIXED_ARRAY = 7, - ARRAY = 8 -} - -type Payload = u64 - -export class Token { - kind: TokenKind - data: Payload -} - -declare namespace typeConversion { - function i32ToBigInt(x: i32): Uint8Array - function bigIntToI32(x: Uint8Array): i32 -} - -export function token_from_i32(int: i32): Token { - let token: Token; - token.kind = TokenKind.INT; - token.data = typeConversion.i32ToBigInt(int) as u64; - return token -} - -export function token_to_i32(token: Token): i32 { - assert(token.kind == TokenKind.INT, "Token is not an int.") - return typeConversion.bigIntToI32(changetype(token.data as u32)) -} diff --git a/runtime/wasm/wasm_test/token_to_numeric.wasm b/runtime/wasm/wasm_test/token_to_numeric.wasm deleted file mode 100644 index e35f574cb1d..00000000000 Binary files a/runtime/wasm/wasm_test/token_to_numeric.wasm and /dev/null differ diff --git a/store/postgres/Cargo.toml b/store/postgres/Cargo.toml index 3251d6f65ac..b3b42d21511 100644 --- a/store/postgres/Cargo.toml +++ b/store/postgres/Cargo.toml @@ -35,7 +35,6 @@ stable-hash = { git = "https://github.com/graphprotocol/stable-hash" } clap = "2.33.1" graphql-parser = "0.2.3" hex = "0.4.2" -parity-wasm = "0.40" test-store = { path = "../test-store" } hex-literal = "0.2" graph-mock = { path = "../../mock" } diff --git a/store/postgres/src/store.rs b/store/postgres/src/store.rs index 5413c389221..0f31785e7c0 100644 --- a/store/postgres/src/store.rs +++ b/store/postgres/src/store.rs @@ -22,8 +22,8 @@ use graph::data::subgraph::schema::{ SubgraphDeploymentEntity, TypedEntity as _, POI_OBJECT, SUBGRAPHS_ID, }; use graph::prelude::{ - bail, debug, ethabi, format_err, futures03, info, o, serde_json, tiny_keccak, tokio, trace, - warn, web3, AttributeIndexDefinition, BigInt, BlockNumber, ChainHeadUpdateListener as _, + debug, ethabi, format_err, futures03, info, o, serde_json, tiny_keccak, tokio, trace, warn, + web3, AttributeIndexDefinition, BigInt, BlockNumber, ChainHeadUpdateListener as _, ChainHeadUpdateStream, ChainStore, CheapClone, DynTryFuture, Entity, EntityKey, EntityModification, EntityOrder, EntityQuery, EntityRange, Error, EthereumBlock, EthereumBlockPointer, EthereumCallCache, EthereumNetworkIdentifier, Future, LightEthereumBlock, @@ -1461,7 +1461,7 @@ impl ChainStore for Store { offset: u64, ) -> Result, Error> { if block_ptr.number < offset { - bail!("block offset points to before genesis block"); + failure::bail!("block offset points to before genesis block"); } select(lookup_ancestor_block(block_ptr.hash_hex(), offset as i64)) diff --git a/store/test-store/src/lib.rs b/store/test-store/src/lib.rs index 523b1056915..876e166f70a 100644 --- a/store/test-store/src/lib.rs +++ b/store/test-store/src/lib.rs @@ -38,44 +38,47 @@ lazy_static! { // Create Store instance once for use with each of the tests. pub static ref STORE: Arc = { - STORE_RUNTIME.lock().unwrap().block_on(async { - // Set up Store - let logger = &*LOGGER; - let postgres_url = postgres_test_url(); - let net_identifiers = EthereumNetworkIdentifier { - net_version: NETWORK_VERSION.to_owned(), - genesis_block_hash: GENESIS_PTR.hash, - }; - let conn_pool_size: u32 = 20; - let postgres_conn_pool = create_connection_pool( - postgres_url.clone(), - conn_pool_size, - &logger, - Arc::new(MockMetricsRegistry::new()), - ); - let registry = Arc::new(MockMetricsRegistry::new()); - let chain_head_update_listener = Arc::new(ChainHeadUpdateListener::new( - &logger, - registry.clone(), - postgres_url.clone(), - )); - let subscriptions = Arc::new(SubscriptionManager::new( - logger.clone(), - postgres_url.clone(), - )); - Arc::new(Store::new( - StoreConfig { - postgres_url, - network_name: NETWORK_NAME.to_owned(), - }, - &logger, - net_identifiers, - chain_head_update_listener, - subscriptions, - postgres_conn_pool, - registry.clone(), - )) - }) + // Use a separate thread to work around issues with recursive `block_on`. + std::thread::spawn(move || { + STORE_RUNTIME.lock().unwrap().block_on(async { + // Set up Store + let logger = &*LOGGER; + let postgres_url = postgres_test_url(); + let net_identifiers = EthereumNetworkIdentifier { + net_version: NETWORK_VERSION.to_owned(), + genesis_block_hash: GENESIS_PTR.hash, + }; + let conn_pool_size: u32 = 20; + let postgres_conn_pool = create_connection_pool( + postgres_url.clone(), + conn_pool_size, + &logger, + Arc::new(MockMetricsRegistry::new()), + ); + let registry = Arc::new(MockMetricsRegistry::new()); + let chain_head_update_listener = Arc::new(ChainHeadUpdateListener::new( + &logger, + registry.clone(), + postgres_url.clone(), + )); + let subscriptions = Arc::new(SubscriptionManager::new( + logger.clone(), + postgres_url.clone(), + )); + Arc::new(Store::new( + StoreConfig { + postgres_url, + network_name: NETWORK_NAME.to_owned(), + }, + &logger, + net_identifiers, + chain_head_update_listener, + subscriptions, + postgres_conn_pool, + registry.clone(), + )) + }) + }).join().unwrap() }; pub static ref GENESIS_PTR: EthereumBlockPointer = (