From fc4a420276498d6d17bca7f59be2eb498549e9e9 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Mon, 1 Jun 2020 21:20:43 -0300 Subject: [PATCH 01/22] runtime: Switch to wasmtime --- Cargo.lock | 593 +++++++- core/src/subgraph/instance.rs | 30 +- core/src/subgraph/instance_manager.rs | 5 +- graph/Cargo.toml | 1 + graph/src/components/subgraph/host.rs | 4 +- graph/src/components/subgraph/instance.rs | 2 +- graph/src/data/subgraph/mod.rs | 22 +- graph/src/lib.rs | 1 + graphql/src/schema/ast.rs | 28 +- runtime/wasm/Cargo.toml | 3 +- runtime/wasm/src/asc_abi/asc_ptr.rs | 27 +- runtime/wasm/src/asc_abi/mod.rs | 11 +- runtime/wasm/src/host.rs | 26 +- runtime/wasm/src/host_exports.rs | 274 ++-- runtime/wasm/src/lib.rs | 2 +- runtime/wasm/src/mapping.rs | 84 +- runtime/wasm/src/module/into_wasm_ret.rs | 69 + runtime/wasm/src/module/mod.rs | 1260 +++++++---------- runtime/wasm/src/module/test.rs | 561 +++----- runtime/wasm/src/module/test/abi.rs | 202 +-- runtime/wasm/src/to_from/external.rs | 6 +- runtime/wasm/src/to_from/mod.rs | 2 +- runtime/wasm/wasm_test/big_int_to_from_i32.ts | 16 - .../wasm/wasm_test/big_int_to_from_i32.wasm | Bin 628 -> 0 bytes runtime/wasm/wasm_test/token_to_numeric.ts | 39 - runtime/wasm/wasm_test/token_to_numeric.wasm | Bin 952 -> 0 bytes store/postgres/Cargo.toml | 1 - store/test-store/src/lib.rs | 79 +- 28 files changed, 1649 insertions(+), 1699 deletions(-) create mode 100644 runtime/wasm/src/module/into_wasm_ret.rs delete mode 100644 runtime/wasm/wasm_test/big_int_to_from_i32.ts delete mode 100644 runtime/wasm/wasm_test/big_int_to_from_i32.wasm delete mode 100644 runtime/wasm/wasm_test/token_to_numeric.ts delete mode 100644 runtime/wasm/wasm_test/token_to_numeric.wasm diff --git a/Cargo.lock b/Cargo.lock index 47ca4e48ad9..544fdf40b88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -28,6 +28,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" @@ -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.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +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.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "cranelift-codegen-shared", + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" + +[[package]] +name = "cranelift-entity" +version = "0.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "cranelift-codegen", + "log 0.4.8", + "smallvec 1.2.0", + "target-lexicon", +] + +[[package]] +name = "cranelift-native" +version = "0.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "cranelift-codegen", + "raw-cpuid", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.63.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "log 0.4.8", + "serde", + "thiserror", + "wasmparser", +] + [[package]] name = "crossbeam" version = "0.7.3" @@ -682,6 +785,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 +875,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 +950,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 +999,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 +1205,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 +1240,20 @@ dependencies = [ "wasi", ] +[[package]] +name = "gimli" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +dependencies = [ + "arrayvec", + "byteorder", + "fallible-iterator 0.2.0", + "indexmap", + "smallvec 1.2.0", + "stable_deref_trait", +] + [[package]] name = "git-testament" version = "0.1.9" @@ -1091,6 +1276,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 +1295,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", @@ -1297,14 +1500,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 +1592,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 +1604,6 @@ dependencies = [ "lazy_static", "lru_time_cache 0.9.0", "maybe-owned", - "parity-wasm", "postgres", "serde", "stable-hash", @@ -1818,6 +2019,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 +2117,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 +2166,15 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "adb241df5c4caeb888755363fc95f8a896618dc0d435e9e775f7930cb099beab" +[[package]] +name = "mach" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" +dependencies = [ + "libc", +] + [[package]] name = "maplit" version = "1.0.2" @@ -2009,12 +2228,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 +2343,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 +2406,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 +2425,15 @@ 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 = "once_cell" version = "1.3.1" @@ -2477,6 +2693,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 +2706,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 +2722,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 +2738,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 +3102,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 +3163,17 @@ dependencies = [ "rust-argon2", ] +[[package]] +name = "regalloc" +version = "0.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5842bece8a4b1690ffa6d9d959081c1d5d851ee4337a36c0a121fafe8c16add2" +dependencies = [ + "log 0.4.8", + "rustc-hash", + "smallvec 1.2.0", +] + [[package]] name = "regex" version = "1.3.6" @@ -2924,6 +3192,18 @@ version = "0.6.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" +[[package]] +name = "region" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "448e868c6e4cfddfa49b6a72c95906c04e8547465e9536575b95c70a4044f856" +dependencies = [ + "bitflags 1.2.1", + "libc", + "mach", + "winapi 0.3.8", +] + [[package]] name = "remove_dir_all" version = "0.5.2" @@ -3001,6 +3281,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 +3360,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 +3685,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 +3717,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 +3806,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 +3868,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 +4256,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 +4590,155 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] -name = "wasmi" -version = "0.5.1" +name = "wasmparser" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31d26deb2d9a37e6cfed420edce3ed604eab49735ba89035e13c98f9a528313" +checksum = "af931e2e1960c53f4a28b063fec4cacd036f35acbec8ff3a4739125b17382a87" + +[[package]] +name = "wasmtime" +version = "0.16.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" 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.16.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "anyhow", + "faerie", + "gimli", + "more-asserts", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.16.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +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.16.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +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.16.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "anyhow", + "cfg-if", + "gimli", + "lazy_static", + "libc", + "object", + "scroll", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-runtime", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.16.0" +source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +dependencies = [ + "backtrace", + "cc", + "cfg-if", + "indexmap", + "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 +4881,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..28eb29f1216 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, + ) + .map_err(|e| e.compat().into()) } } @@ -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..7b09532b509 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(..), - )?; + ) + .map_err(err_msg)?; // Reprocess the triggers from this block that match the new data sources let block_with_triggers = triggers_in_block( @@ -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..d7f7d6a0848 100644 --- a/graph/src/components/subgraph/host.rs +++ b/graph/src/components/subgraph/host.rs @@ -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..926f1316f70 100644 --- a/graph/src/components/subgraph/instance.rs +++ b/graph/src/components/subgraph/instance.rs @@ -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..5062d17e75d 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -41,6 +41,7 @@ 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; diff --git a/graphql/src/schema/ast.rs b/graphql/src/schema/ast.rs index 8811ace9a49..52dcc34bbf5 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,7 +502,7 @@ 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 {}", + anyhow::bail!("Entity {}[{}]: field `{}` is of type {}, but the value `{}` contains a {} at index {}", key.entity_type, key.entity_id, field.name, @@ -512,13 +510,13 @@ pub fn validate_entity( 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 +524,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/runtime/wasm/Cargo.toml b/runtime/wasm/Cargo.toml index 068a85b2eac..6110e62c18a 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" @@ -26,6 +24,7 @@ bytes = "0.5" # PR is merged. https://github.com/rustonaut/maybe-owned/pull/9 # See also 92cd8019-0136-4011-96a0-40b3eec37f73 maybe-owned = { git = "https://github.com/rustonaut/maybe-owned", branch = "master" } +wasmtime = { git = "https://github.com/leoyvens/wasmtime.git", branch = "from-error-for-trap" } [dev-dependencies] graphql-parser = "0.2.3" diff --git a/runtime/wasm/src/asc_abi/asc_ptr.rs b/runtime/wasm/src/asc_abi/asc_ptr.rs index 5ccccefd08e..80141a75638 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) -> i32 { + i32::from_le_bytes(self.0.to_le_bytes()) + } +} + 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: i32) -> Self { + AscPtr(u32::from_le_bytes(ptr.to_le_bytes()), 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..3c526bbdbaf 100644 --- a/runtime/wasm/src/host.rs +++ b/runtime/wasm/src/host.rs @@ -90,17 +90,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 +211,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 +227,6 @@ impl RuntimeHost { link_resolver, store, call_cache, - timeout, arweave_adapter, three_box_adapter, )); @@ -391,7 +392,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 +419,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); @@ -578,6 +579,7 @@ impl RuntimeHostTrait for RuntimeHost { proof_of_indexing, ) .await + .map_err(err_msg) } async fn process_block( @@ -604,6 +606,7 @@ impl RuntimeHostTrait for RuntimeHost { proof_of_indexing, ) .await + .map_err(err_msg) } async fn process_log( @@ -717,5 +720,6 @@ impl RuntimeHostTrait for RuntimeHost { proof_of_indexing, ) .await + .map_err(err_msg) } } diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 88d1e7fcff5..3e5c3007f2d 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, } @@ -115,9 +91,9 @@ impl HostExports { &self, message: Option, file_name: Option, - line_number: Option, - column_number: Option, - ) -> Result<(), HostExportError> { + line_number: Option, + column_number: Option, + ) -> 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.clone(), )?; 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,12 +483,9 @@ impl HostExports { &self, x: BigDecimal, y: BigDecimal, - ) -> Result> { + ) -> Result { if y == 0.into() { - return Err(HostExportError(format!( - "attempted to divide BigDecimal `{}` by zero", - x - ))); + anyhow::bail!("attempted to divide BigDecimal `{}` by zero", x); } Ok(x / y) @@ -579,12 +499,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).context("failed to parse BigDecimal") } pub(crate) fn data_source_create( @@ -594,7 +510,7 @@ impl HostExports { name: String, params: Vec, context: Option, - ) -> Result<(), HostExportError> { + ) -> Result<(), anyhow::Error> { info!( logger, "Create data source"; @@ -607,8 +523,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 +535,7 @@ impl HostExports { .map(|template| template.name.clone()) .collect::>() .join(", ") - )) + ) })? .clone(); @@ -634,11 +550,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 +594,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))) + H160::from_str(string).context("Failed to convert string to Address/H160") } 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..ff43b549a2a 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,17 @@ 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> { // 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, @@ -34,24 +33,26 @@ pub fn spawn_module( let conf = thread::Builder::new().name(format!("mapping-{}-{}", &subgraph_id, uuid::Uuid::new_v4())); conf.spawn(move || { + let valid_module = Arc::new(ValidModule::new(&raw_module).unwrap()); runtime.enter(|| { // Pass incoming triggers to the WASM module and return entity changes; // 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 +90,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 +101,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 +126,10 @@ pub(crate) enum MappingTrigger { }, } -type MappingResponse = (Result, futures::Finished); +type MappingResponse = ( + Result, + futures::Finished, +); #[derive(Debug)] pub struct MappingRequest { @@ -155,36 +159,46 @@ 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 generates 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 { + 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. + + let store = wasmtime::Store::new(&wasmtime::Engine::new(&config)); + let module = wasmtime::Module::from_binary(&store, 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..2749498e8b4 --- /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 = i64; + fn into_wasm_ret(self) -> i64 { + i64::from_le_bytes(self.to_le_bytes()) + } +} + +impl IntoWasmRet for bool { + type Ret = i32; + fn into_wasm_ret(self) -> i32 { + self as i32 + } +} + +impl IntoWasmRet for AscPtr { + type Ret = i32; + fn into_wasm_ret(self) -> i32 { + 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..462b8c29455 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -1,22 +1,21 @@ +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::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; 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 +25,48 @@ use crate::host_exports::HostExports; use crate::mapping::ValidModule; use crate::UnresolvedContractCall; +mod into_wasm_ret; +use into_wasm_ret::IntoWasmRet; + #[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, - } +/// 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. + instance: Rc>>, } -/// 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(), +impl Drop for WasmInstanceHandle { + fn drop(&mut self) { + // `WasmInstance` is in a reference cycle with `wasmtime::Instance`, more precisely + // `wasmtime::Store`. Drop the `WasmInstance` to avoid leaks. + self.instance.borrow_mut().take(); } } -/// 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 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().asc_new(value); + let user_data = self.instance().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 +75,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() + .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() + .asc_new::, _>(&EthereumEventData { block: EthereumBlockData::from(block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), address: log.address, @@ -242,24 +103,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 +121,355 @@ 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() + .asc_new::(&call) + .erase() } else { - RuntimeValue::from(self.asc_new::(&call)) + self.instance().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().asc_new(&block); + + self.invoke_handler(handler_name, arg)?; + + Ok(self.take_instance().ctx.state) + } + + pub(crate) fn instance(&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() + } + + #[cfg(test)] + pub(crate) fn borrow_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))?; - let result = self.module.clone().invoke_export( - handler_name, - &[RuntimeValue::from(self.asc_new(&arg))], - &mut self, + func.get1()?(arg.wasm_ptr()) + .with_context(|| format!("Failed to invoke handler '{}'", handler)) + } +} + +pub(crate) struct WasmInstance { + instance: wasmtime::Instance, + memory: Memory, + memory_allocate: Box Result>, + + pub ctx: MappingContext, + pub(crate) valid_module: Arc, + pub(crate) host_metrics: Arc, + pub(crate) timeout: Option, + + // Used by ipfs.map to turn off interrupts. + should_interrupt: 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(valid_module.module.store()); + + // 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 = shared_instance.clone(); + linker.func( + module, + $wasm_name, + move |$($param: i32),*| { + let mut instance = func_shared_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 = shared_instance.clone(); + linker.func(module, "store.get", move |entity_ptr: i32, id_ptr: i32| { + let start = Instant::now(); + let mut instance = func_shared_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 = shared_instance.clone(); + linker.func(module, "ethereum.call", move |call_ptr: i32| { + let start = Instant::now(); + let mut instance = func_shared_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 should_interrupt = Arc::new(AtomicBool::new(true)); + if let Some(timeout) = timeout.clone() { + // This task is likely to outlive the instance, which is fine. + let interrupt_handle = instance.store().interrupt_handle().unwrap(); + let should_interrupt = should_interrupt.clone(); + graph::spawn(async move { + tokio::time::delay_for(timeout).await; + if should_interrupt.load(Ordering::SeqCst) { + interrupt_handle.interrupt() + } + }); + } + + *shared_instance.borrow_mut() = Some(WasmInstance { + instance, + memory_allocate: Box::new(memory_allocate), + memory, + ctx, + valid_module, + host_metrics, + timeout, + should_interrupt, + + // `arena_start_ptr` will be set on the first call to `raw_new`. + arena_free_size: 0, + arena_start_ptr: 0, + }); - result.map(|_| self.ctx.state).map_err(|err| { - format_err!( - "Failed to handle Ethereum block with handler \"{}\": {}", - handler_name, - format_wasmi_error(err) - ) + 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; + + // Safe because we are accessing and immediately dropping the reference to the data. + 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) - } - - fn get(&self, offset: u32, size: u32) -> Result, Error> { - self.memory.get(offset, size as usize) + ptr as u32 } -} -impl HostError for HostExportError where E: fmt::Debug + fmt::Display + Send + Sync + 'static {} + fn get(&self, offset: u32, size: u32) -> Vec { + let offset = offset as usize; + let size = size as usize; -fn json_from_bytes(bytes: &Vec) -> Result { - serde_json::from_reader(bytes.as_slice()) + // Safe because we are accessing and immediately dropping the reference to the data. + 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> { + line_number: i32, + column_number: i32, + ) -> Result<(), Trap> { let message = match message_ptr.is_null() { false => Some(self.asc_get(message_ptr)), true => None, @@ -419,13 +500,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 +512,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 +526,6 @@ impl WasmiModule { entity, id, ); - Ok(None) } /// function store.get(entity: string, id: string): Entity | null @@ -463,7 +533,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 +541,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 +579,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 +639,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 +657,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 +669,94 @@ 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)?; + // `ipfs_map` can take a long time to process, so disable the timeout. + self.should_interrupt.store(false, Ordering::SeqCst); let flags = self.asc_get(flags); 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 + + // TODO + // self.start_time += start_time.elapsed(); + 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 +764,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 +778,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 +792,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 +806,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 +820,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 +834,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: i32, + ) -> 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 +897,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 +910,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 +923,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 +936,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 +949,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 +961,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 +971,7 @@ impl WasmiModule { params, None, )?; - Ok(None) + Ok(()) } /// function createWithContext(name: string, params: Array, context: DataSourceContext): void @@ -945,12 +980,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 +991,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: i32, 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/test.rs b/runtime/wasm/src/module/test.rs index 7a22928cc7e..0c7c6bd2a7f 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -4,14 +4,12 @@ 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 +26,7 @@ fn test_valid_module_and_store( subgraph_id: &str, data_source: DataSource, ) -> ( - WasmiModule, + WasmInstanceHandle, Arc, ) { let store = STORE.clone(); @@ -58,22 +56,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 +150,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 +169,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.borrow_instance().instance.get_func(name).unwrap() + } + + fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr { + let func = self.get_func(f).get1().unwrap(); + let ptr: i32 = func(arg.wasm_ptr()).unwrap(); + ptr.into() } - 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_export2(&self, f: &str, arg0: AscPtr, arg1: AscPtr) -> AscPtr { + let func = self.get_func(f).get2().unwrap(); + let ptr: i32 = 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: i32 = func(val).unwrap(); + ptr.into() + } +} + +impl AscHeap for WasmInstanceHandle { + fn raw_new(&mut self, bytes: &[u8]) -> u32 { + self.instance().raw_new(bytes) } - 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 get(&self, offset: u32, size: u32) -> Vec { + self.borrow_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 +228,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 +254,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 +265,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,19 +273,8 @@ 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"); } @@ -338,19 +285,9 @@ async fn ipfs_cat() { 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); + 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 @@ -387,7 +324,7 @@ async fn ipfs_map() { ipfs: Arc, subgraph_id: &'static str, json_string: String, - ) -> Result, Error> { + ) -> Result, anyhow::Error> { let (mut module, store) = test_valid_module_and_store(subgraph_id, mock_data_source("wasm_test/ipfs_map.wasm")); let hash = if json_string == BAD_IPFS_HASH { @@ -395,14 +332,21 @@ async fn ipfs_map() { } 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 value = module.instance().asc_new(&hash); + let user_data = module.instance().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 @@ -430,10 +374,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 +396,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. @@ -472,29 +421,22 @@ 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"); + let hash = module.instance().asc_new("invalid hash"); assert!(module - .takes_ptr_returns_ptr::<_, AscString>("ipfsCat", hash,) + .invoke_export::<_, AscString>("ipfsCat", hash,) .is_null()); }) .await .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 +444,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 +454,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 +476,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 +488,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 +497,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 +506,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 +515,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 +524,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 +533,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 +558,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 +594,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 +612,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 +637,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 +651,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 +667,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().ctx.state.entity_cache, + EntityCache::new(store.clone()), + ); + let mut mods = cache .as_modifications(store.as_ref()) .unwrap() .modifications; @@ -926,13 +687,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..1be0fb34f97 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -1,41 +1,33 @@ use super::*; -#[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\")) }" - ); + //module.start_time = Instant::now(); + let func = module.get_func("loop").get0().unwrap(); + let res: Result<(), _> = func(); + assert!(res.unwrap_err().to_string().contains("interrupt")); } -#[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: i32 = 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: i32 = 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: i32 = 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..54d64054ba4 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::{self, 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::*; @@ -86,12 +86,12 @@ 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!( + anyhow::bail!( "big decimal exponent `{}` is outside the `{}` to `{}` range", exp, BigDecimal::MIN_EXP, BigDecimal::MAX_EXP - )); + ); } Ok(big_decimal) } 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 5410076d8e232ea3649e4895c3294e2b4f15f1b8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 628 zcma)&zi!(=48}j+og~L}B?0NwEGOG*Gb9gCT??d5vt})9#kdsyBg>T!0WzuRgZ0%? zD%Oy(8c2%dNB)kejx_mfpnJ2eo~4^Ye9iAbO1$KxZ+%QYU!>S5;z=^I8F4U>xPfrmVz^dz}lHT9utJ(6rlRJ&cXs)ENE51Gf- zd+=oVYyHs&yctY%ZHzDCrZRJ?t+M*=LU;${!hAvzSz{xY2kDoHw(jLcZ+g=|%H>Z&67|w) wY_*NQUliKvQf}}~`qdB!23s?iRh;xsA7!jD#N$lv@OqjE`6TNPq*;Og0e232jQ{`u 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 e35f574cb1d15db21fca3ccfe082530d24ac64dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 952 zcmah{O>Yx15Pf5NKU#-mX(}Pni<1-yD(In=pyB{Ah(hT#SCnj1w@TC9Et_qnUK+#? z;BRo^$}i%7lyS0Dz2U?2%=5hQ_{m6T83CZJcm#}SPVh}? znN-Ep<-@8N=9if(C*#M}^@V$1+c! zwKjU%MrSs%^V!!;^sq@`EhyJlx>7)z9mwx99{$nZPC$ z*KbJbUfKs0tJ_Iw?{00pLS$hm%k8Dq{&#d}(F gIcM8|-P2ob7F2hF;UXS#*(EY=QF4PZmsZ^7FK1i2MF0Q* 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/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 = ( From cba9444587720d653c03bf54100898c12f78ca63 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Tue, 2 Jun 2020 10:22:47 -0300 Subject: [PATCH 02/22] graph: unconditionally use `futures::block_on`in `block_on_allow_panic` --- graph/src/task_spawn.rs | 28 ++------------------ runtime/wasm/src/module/test.rs | 40 ++++++++++++----------------- runtime/wasm/src/module/test/abi.rs | 3 +-- 3 files changed, 19 insertions(+), 52 deletions(-) diff --git a/graph/src/task_spawn.rs b/graph/src/task_spawn.rs index b340c29cbd4..9dc58bfbc24 100644 --- a/graph/src/task_spawn.rs +++ b/graph/src/task_spawn.rs @@ -46,30 +46,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/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index 0c7c6bd2a7f..81f0bb58541 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -278,20 +278,16 @@ async fn json_parsing() { 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 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(); + 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 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"); } // The user_data value we use with calls to ipfs_map @@ -416,18 +412,14 @@ 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.instance().asc_new("invalid hash"); - assert!(module - .invoke_export::<_, AscString>("ipfsCat", hash,) - .is_null()); - }) - .await - .unwrap(); + let mut module = test_module("ipfsFail", mock_data_source("wasm_test/ipfs_cat.wasm")); + + let hash = module.instance().asc_new("invalid hash"); + assert!(module + .invoke_export::<_, AscString>("ipfsCat", hash,) + .is_null()); } #[tokio::test] diff --git a/runtime/wasm/src/module/test/abi.rs b/runtime/wasm/src/module/test/abi.rs index 1be0fb34f97..fcba95091ee 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -8,7 +8,6 @@ async fn unbounded_loop() { "unboundedLoop", mock_data_source("wasm_test/non_terminating.wasm"), ); - //module.start_time = Instant::now(); let func = module.get_func("loop").get0().unwrap(); let res: Result<(), _> = func(); assert!(res.unwrap_err().to_string().contains("interrupt")); @@ -22,7 +21,7 @@ async fn unbounded_recursion() { ); let func = module.get_func("rabbit_hole").get0().unwrap(); let res: Result<(), _> = func(); - let err_msg = format!("{:#}", res.unwrap_err().to_string()); + let err_msg = format!("{}", res.unwrap_err().to_string()); assert!(err_msg.contains("call stack exhausted"), err_msg); } From 54fa31422cdaa991a1e8a43b471e6a5560f7a465 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Tue, 2 Jun 2020 11:02:49 -0300 Subject: [PATCH 03/22] *: Use more `anyhow::Error` --- core/src/subgraph/instance.rs | 8 +-- core/src/subgraph/instance_manager.rs | 4 +- graph/src/components/subgraph/host.rs | 6 +- graph/src/components/subgraph/instance.rs | 4 +- graph/src/lib.rs | 2 +- runtime/wasm/src/host.rs | 75 +++++++++-------------- store/postgres/src/store.rs | 6 +- 7 files changed, 45 insertions(+), 60 deletions(-) diff --git a/core/src/subgraph/instance.rs b/core/src/subgraph/instance.rs index 28eb29f1216..ebfb336c8e8 100644 --- a/core/src/subgraph/instance.rs +++ b/core/src/subgraph/instance.rs @@ -139,7 +139,7 @@ where trigger: EthereumTrigger, state: BlockState, proof_of_indexing: SharedProofOfIndexing, - ) -> Result { + ) -> Result { Self::process_trigger_in_runtime_hosts( logger, &self.hosts, @@ -158,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); @@ -166,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 @@ -189,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)); diff --git a/core/src/subgraph/instance_manager.rs b/core/src/subgraph/instance_manager.rs index 7b09532b509..f081919c860 100644 --- a/core/src/subgraph/instance_manager.rs +++ b/core/src/subgraph/instance_manager.rs @@ -735,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, @@ -745,7 +744,8 @@ where block_state, proof_of_indexing.cheap_clone(), ) - .await?; + .await + .map_err(|e| format_err!("{:#}", e))?; } } diff --git a/graph/src/components/subgraph/host.rs b/graph/src/components/subgraph/host.rs index d7f7d6a0848..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 { diff --git a/graph/src/components/subgraph/instance.rs b/graph/src/components/subgraph/instance.rs index 926f1316f70..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( diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 5062d17e75d..50e6bd65fac 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -45,7 +45,7 @@ pub mod prelude { 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; diff --git a/runtime/wasm/src/host.rs b/runtime/wasm/src/host.rs index 3c526bbdbaf..25b88c812de 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::bail; use async_trait::async_trait; use ethabi::{LogParam, RawLog}; use futures::sync::mpsc::Sender; @@ -295,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 @@ -309,23 +307,21 @@ impl RuntimeHost { .cloned() .collect::>(); - if !handlers.is_empty() { - Ok(handlers) - } else { - Err(format_err!( + if handlers.is_empty() { + bail!( "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" - )); + bail!("Ethereum call has input with less than 4 bytes"); } let target_method_id = &call.input.0[..4]; @@ -338,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, @@ -349,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 \"{}\"", @@ -371,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 \"{}\"", @@ -483,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)?; @@ -492,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 \"{}\"", @@ -509,18 +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!( + bail!( "Number of arguments in call does not match \ number of inputs in function signature." - )); + ); } let inputs = tokens @@ -537,18 +528,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!( + bail!( "Number of parameters in the call output does not match \ number of outputs in the function signature." - )); + ); } let outputs = tokens @@ -579,7 +567,6 @@ impl RuntimeHostTrait for RuntimeHost { proof_of_indexing, ) .await - .map_err(err_msg) } async fn process_block( @@ -589,7 +576,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, @@ -606,7 +593,6 @@ impl RuntimeHostTrait for RuntimeHost { proof_of_indexing, ) .await - .map_err(err_msg) } async fn process_log( @@ -617,7 +603,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; @@ -635,7 +621,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 \"{}\"", @@ -646,7 +632,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 @@ -696,10 +682,10 @@ impl RuntimeHostTrait for RuntimeHost { let (event_handler, params) = matching_handlers.pop().unwrap(); if !matching_handlers.is_empty() { - return Err(format_err!( + bail!( "Multiple handlers defined for event `{}`, only one is supported", &event_handler.event - )); + ); } self.send_mapping_request( @@ -720,6 +706,5 @@ impl RuntimeHostTrait for RuntimeHost { proof_of_indexing, ) .await - .map_err(err_msg) } } 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)) From 93dc84e190ace3e1a90c1be5b1c398d1ee96d728 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Tue, 2 Jun 2020 13:34:53 -0300 Subject: [PATCH 04/22] runtime: Depend on wasmtime master --- Cargo.lock | 37 ++++++++++++++------------- core/src/subgraph/instance_manager.rs | 4 +-- graphql/src/subscription/mod.rs | 2 +- runtime/wasm/Cargo.toml | 4 ++- runtime/wasm/src/host_exports.rs | 2 +- runtime/wasm/src/mapping.rs | 8 +++--- runtime/wasm/src/module/mod.rs | 22 +++++++++++++--- runtime/wasm/src/module/test/abi.rs | 2 +- 8 files changed, 49 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 544fdf40b88..24e1f91aa1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -449,7 +449,7 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "cranelift-bforest" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "cranelift-entity", ] @@ -457,7 +457,7 @@ dependencies = [ [[package]] name = "cranelift-codegen" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "byteorder", "cranelift-bforest", @@ -476,7 +476,7 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -485,12 +485,12 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" [[package]] name = "cranelift-entity" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "serde", ] @@ -498,7 +498,7 @@ dependencies = [ [[package]] name = "cranelift-frontend" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "cranelift-codegen", "log 0.4.8", @@ -509,7 +509,7 @@ dependencies = [ [[package]] name = "cranelift-native" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "cranelift-codegen", "raw-cpuid", @@ -519,7 +519,7 @@ dependencies = [ [[package]] name = "cranelift-wasm" version = "0.63.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -3165,9 +3165,9 @@ dependencies = [ [[package]] name = "regalloc" -version = "0.0.24" +version = "0.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5842bece8a4b1690ffa6d9d959081c1d5d851ee4337a36c0a121fafe8c16add2" +checksum = "cca5b48c9db66c5ba084e4660b4c0cfe8b551a96074bc04b7c11de86ad0bf1f9" dependencies = [ "log 0.4.8", "rustc-hash", @@ -4591,14 +4591,14 @@ checksum = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639" [[package]] name = "wasmparser" -version = "0.55.0" +version = "0.57.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af931e2e1960c53f4a28b063fec4cacd036f35acbec8ff3a4739125b17382a87" +checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" [[package]] name = "wasmtime" version = "0.16.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "anyhow", "backtrace", @@ -4621,7 +4621,7 @@ dependencies = [ [[package]] name = "wasmtime-debug" version = "0.16.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "anyhow", "faerie", @@ -4636,7 +4636,7 @@ dependencies = [ [[package]] name = "wasmtime-environ" version = "0.16.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "anyhow", "base64 0.12.1", @@ -4664,7 +4664,7 @@ dependencies = [ [[package]] name = "wasmtime-jit" version = "0.16.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "anyhow", "cfg-if", @@ -4690,7 +4690,7 @@ dependencies = [ [[package]] name = "wasmtime-profiling" version = "0.16.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "anyhow", "cfg-if", @@ -4708,12 +4708,13 @@ dependencies = [ [[package]] name = "wasmtime-runtime" version = "0.16.0" -source = "git+https://github.com/leoyvens/wasmtime.git?branch=from-error-for-trap#26edb6de9bfa986a5acb8b29924912388e68929e" +source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" dependencies = [ "backtrace", "cc", "cfg-if", "indexmap", + "lazy_static", "libc", "memoffset", "more-asserts", diff --git a/core/src/subgraph/instance_manager.rs b/core/src/subgraph/instance_manager.rs index f081919c860..f6d671be552 100644 --- a/core/src/subgraph/instance_manager.rs +++ b/core/src/subgraph/instance_manager.rs @@ -909,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); 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 6110e62c18a..02baee21ac4 100644 --- a/runtime/wasm/Cargo.toml +++ b/runtime/wasm/Cargo.toml @@ -24,7 +24,9 @@ bytes = "0.5" # PR is merged. https://github.com/rustonaut/maybe-owned/pull/9 # See also 92cd8019-0136-4011-96a0-40b3eec37f73 maybe-owned = { git = "https://github.com/rustonaut/maybe-owned", branch = "master" } -wasmtime = { git = "https://github.com/leoyvens/wasmtime.git", branch = "from-error-for-trap" } + +# We can depend on the released version once 0.17 is released. +wasmtime = { git = "https://github.com/bytecodealliance/wasmtime.git" } [dev-dependencies] graphql-parser = "0.2.3" diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 3e5c3007f2d..fe06733e732 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -389,7 +389,7 @@ impl HostExports { valid_module.clone(), ctx.derive_with_empty_block_state(), host_metrics.clone(), - module.timeout.clone(), + module.timeout, )?; let result = module.handle_json_callback(&callback, &sv.value, &user_data)?; // Log progress every 15s diff --git a/runtime/wasm/src/mapping.rs b/runtime/wasm/src/mapping.rs index ff43b549a2a..0cb500270ac 100644 --- a/runtime/wasm/src/mapping.rs +++ b/runtime/wasm/src/mapping.rs @@ -21,6 +21,8 @@ pub fn spawn_module( runtime: tokio::runtime::Handle, timeout: Option, ) -> Result, anyhow::Error> { + let valid_module = Arc::new(ValidModule::new(&raw_module).unwrap()); + // Create channel for event handling requests let (mapping_request_sender, mapping_request_receiver) = mpsc::channel(100); @@ -33,7 +35,6 @@ pub fn spawn_module( let conf = thread::Builder::new().name(format!("mapping-{}-{}", &subgraph_id, uuid::Uuid::new_v4())); conf.spawn(move || { - let valid_module = Arc::new(ValidModule::new(&raw_module).unwrap()); runtime.enter(|| { // Pass incoming triggers to the WASM module and return entity changes; // Stop when canceled because all RuntimeHosts and their senders were dropped. @@ -181,9 +182,8 @@ impl ValidModule { config.strategy(wasmtime::Strategy::Cranelift).unwrap(); config.interruptable(true); // For timeouts. config.cranelift_nan_canonicalization(true); // For NaN determinism. - - let store = wasmtime::Store::new(&wasmtime::Engine::new(&config)); - let module = wasmtime::Module::from_binary(&store, raw_module)?; + 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 diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 462b8c29455..fe221a0966b 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -31,6 +31,8 @@ use into_wasm_ret::IntoWasmRet; #[cfg(test)] mod test; +const TRAP_TIMEOUT: &str = "trap: interrupt"; + /// 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, @@ -177,8 +179,20 @@ impl WasmInstanceHandle { .get_func(handler) .with_context(|| format!("function {} not found", handler))?; - func.get1()?(arg.wasm_ptr()) - .with_context(|| format!("Failed to invoke handler '{}'", 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)) + } + }) } } @@ -210,7 +224,7 @@ impl WasmInstance { host_metrics: Arc, timeout: Option, ) -> Result { - let mut linker = wasmtime::Linker::new(valid_module.module.store()); + 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. @@ -391,7 +405,7 @@ impl WasmInstance { .get1()?; let should_interrupt = Arc::new(AtomicBool::new(true)); - if let Some(timeout) = timeout.clone() { + 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 should_interrupt = should_interrupt.clone(); diff --git a/runtime/wasm/src/module/test/abi.rs b/runtime/wasm/src/module/test/abi.rs index fcba95091ee..55ae646f2b5 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -10,7 +10,7 @@ async fn unbounded_loop() { ); let func = module.get_func("loop").get0().unwrap(); let res: Result<(), _> = func(); - assert!(res.unwrap_err().to_string().contains("interrupt")); + assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); } #[tokio::test] From eff818baaf909f405bfd49b035a7121111d43582 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 3 Jun 2020 10:22:52 -0300 Subject: [PATCH 05/22] graph: Add `CompatErr` --- core/src/subgraph/instance.rs | 2 +- core/src/subgraph/instance_manager.rs | 4 ++-- graph/src/lib.rs | 1 + graph/src/util/error.rs | 31 +++++++++++++++++++++++++++ graph/src/util/mod.rs | 2 ++ 5 files changed, 37 insertions(+), 3 deletions(-) create mode 100644 graph/src/util/error.rs diff --git a/core/src/subgraph/instance.rs b/core/src/subgraph/instance.rs index ebfb336c8e8..e27c8580586 100644 --- a/core/src/subgraph/instance.rs +++ b/core/src/subgraph/instance.rs @@ -118,7 +118,7 @@ where mapping_request_sender, host_metrics, ) - .map_err(|e| e.compat().into()) + .compat_err() } } diff --git a/core/src/subgraph/instance_manager.rs b/core/src/subgraph/instance_manager.rs index f6d671be552..bc3c840ce7c 100644 --- a/core/src/subgraph/instance_manager.rs +++ b/core/src/subgraph/instance_manager.rs @@ -693,7 +693,7 @@ where host_metrics.clone(), block_state.created_data_sources.drain(..), ) - .map_err(err_msg)?; + .compat_err()?; // Reprocess the triggers from this block that match the new data sources let block_with_triggers = triggers_in_block( @@ -745,7 +745,7 @@ where proof_of_indexing.cheap_clone(), ) .await - .map_err(|e| format_err!("{:#}", e))?; + .compat_err()?; } } diff --git a/graph/src/lib.rs b/graph/src/lib.rs index 50e6bd65fac..59bddf8af0c 100644 --- a/graph/src/lib.rs +++ b/graph/src/lib.rs @@ -147,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/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; From cddb1e44549372573f1bd2388e1093c29d946df4 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 3 Jun 2020 11:16:16 -0300 Subject: [PATCH 06/22] runtime, graphql: Address review --- graphql/src/schema/ast.rs | 20 +++++++++++--------- runtime/wasm/src/asc_abi/asc_ptr.rs | 3 ++- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/graphql/src/schema/ast.rs b/graphql/src/schema/ast.rs index 52dcc34bbf5..e9e51ef5a40 100644 --- a/graphql/src/schema/ast.rs +++ b/graphql/src/schema/ast.rs @@ -502,15 +502,17 @@ 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) { - 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 - ); + 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 + ); } } } diff --git a/runtime/wasm/src/asc_abi/asc_ptr.rs b/runtime/wasm/src/asc_abi/asc_ptr.rs index 80141a75638..aa41a19f6b5 100644 --- a/runtime/wasm/src/asc_abi/asc_ptr.rs +++ b/runtime/wasm/src/asc_abi/asc_ptr.rs @@ -27,7 +27,7 @@ impl fmt::Debug for AscPtr { } impl AscPtr { - // A raw pointer to be passed to wasm. + /// A raw pointer to be passed to wasm. Wasmtime uses `i32` for 32 bits wasm integers. pub(crate) fn wasm_ptr(self) -> i32 { i32::from_le_bytes(self.0.to_le_bytes()) } @@ -74,6 +74,7 @@ impl AscPtr { } } +/// WASM integers do not carry sign information, but wasmtime uses `i32` for them. impl From for AscPtr { fn from(ptr: i32) -> Self { AscPtr(u32::from_le_bytes(ptr.to_le_bytes()), PhantomData) From ecdfb696d933bd84b5a67cf32c45cdde32f721d9 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 3 Jun 2020 12:26:57 -0300 Subject: [PATCH 07/22] runtime: Use released wasmtime version --- Cargo.lock | 70 ++++++++++++++++++++++++----------------- runtime/wasm/Cargo.toml | 3 +- 2 files changed, 43 insertions(+), 30 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 24e1f91aa1f..ed1aceb3741 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -448,16 +448,18 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "cranelift-bforest" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9e870ce52c4b39ba80fb0b04a4843223a2a33cd491473f67c7bf7a66512c5a7" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9007dac19ec2395d23f3966166a7777b24ac214b0e0766e15c0f33b98e781b4" dependencies = [ "byteorder", "cranelift-bforest", @@ -475,8 +477,9 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e7ff7c9ee40573b55f63d9bc973d1da54464b7686923981f0aafbde8ef78c7" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -484,21 +487,24 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75d5765a961b8d1c6468f230c634ea6dc35b76c6ab9069c04a84ac1b67149168" [[package]] name = "cranelift-entity" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfa6ac15138da9983259dd257c8573d3ccf75c7fb9ed06708b38b43a3c090326" dependencies = [ "serde", ] [[package]] name = "cranelift-frontend" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3062a6fce384623ac2d19e98e7c774c944584bb5a8e42910e93dcebed7c44d87" dependencies = [ "cranelift-codegen", "log 0.4.8", @@ -508,8 +514,9 @@ dependencies = [ [[package]] name = "cranelift-native" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecc239b00b0ac7655dc91419c537ce5a69535c82854b55fcc357a1e2c3d0d91" dependencies = [ "cranelift-codegen", "raw-cpuid", @@ -518,8 +525,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.63.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.64.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b31a4cd32099bc8c7b340a4a9f9bbb2925e29cc45509a0088330558244c5f04a" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -4597,8 +4605,9 @@ checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" [[package]] name = "wasmtime" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7907161766b4de8c1970d6a4849018d1ab004f65fefacee1dfb8a86f1b20fcd" dependencies = [ "anyhow", "backtrace", @@ -4620,8 +4629,9 @@ dependencies = [ [[package]] name = "wasmtime-debug" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7aad5fdc88752c633bfe23c1089e29d7178e0d58ba94f07848a83df88a89b03" dependencies = [ "anyhow", "faerie", @@ -4635,8 +4645,9 @@ dependencies = [ [[package]] name = "wasmtime-environ" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb6609a337b680a95c9b39f2a0225faab4bf34f47dc9bb2eed34bd7a9d5904c" dependencies = [ "anyhow", "base64 0.12.1", @@ -4663,8 +4674,9 @@ dependencies = [ [[package]] name = "wasmtime-jit" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8959505970c254707e9e765940ef23b58d23211b80d40593a6a96870f57bdd42" dependencies = [ "anyhow", "cfg-if", @@ -4689,8 +4701,9 @@ dependencies = [ [[package]] name = "wasmtime-profiling" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f621d2c66e0bc74e6569c41aeb84a6e92a9620fecedad08c34c0bec4f5d2b1e" dependencies = [ "anyhow", "cfg-if", @@ -4707,8 +4720,9 @@ dependencies = [ [[package]] name = "wasmtime-runtime" -version = "0.16.0" -source = "git+https://github.com/bytecodealliance/wasmtime.git#15c68f2cc15709c65a7838c3c3641f716373d01c" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "304b3f8ef4d2132f7366d008b614650165b766640b4351c6dafb9882b86f41f3" dependencies = [ "backtrace", "cc", diff --git a/runtime/wasm/Cargo.toml b/runtime/wasm/Cargo.toml index 02baee21ac4..37c7eb5f8d7 100644 --- a/runtime/wasm/Cargo.toml +++ b/runtime/wasm/Cargo.toml @@ -25,8 +25,7 @@ bytes = "0.5" # See also 92cd8019-0136-4011-96a0-40b3eec37f73 maybe-owned = { git = "https://github.com/rustonaut/maybe-owned", branch = "master" } -# We can depend on the released version once 0.17 is released. -wasmtime = { git = "https://github.com/bytecodealliance/wasmtime.git" } +wasmtime = "0.17" [dev-dependencies] graphql-parser = "0.2.3" From 058ea6c39cc7f5aef666233b9888a7535978652e Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 3 Jun 2020 13:16:55 -0300 Subject: [PATCH 08/22] runtime: Better `i32` to/from `u32` conversion --- runtime/wasm/src/asc_abi/asc_ptr.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/runtime/wasm/src/asc_abi/asc_ptr.rs b/runtime/wasm/src/asc_abi/asc_ptr.rs index aa41a19f6b5..d3a52575407 100644 --- a/runtime/wasm/src/asc_abi/asc_ptr.rs +++ b/runtime/wasm/src/asc_abi/asc_ptr.rs @@ -1,4 +1,5 @@ use super::{class::EnumPayload, AscHeap, AscType, AscValue}; +use std::convert::TryFrom; use std::fmt; use std::marker::PhantomData; use std::mem::size_of; @@ -29,7 +30,7 @@ impl fmt::Debug for AscPtr { impl AscPtr { /// A raw pointer to be passed to wasm. Wasmtime uses `i32` for 32 bits wasm integers. pub(crate) fn wasm_ptr(self) -> i32 { - i32::from_le_bytes(self.0.to_le_bytes()) + self.0 as i32 } } @@ -77,7 +78,7 @@ impl AscPtr { /// WASM integers do not carry sign information, but wasmtime uses `i32` for them. impl From for AscPtr { fn from(ptr: i32) -> Self { - AscPtr(u32::from_le_bytes(ptr.to_le_bytes()), PhantomData) + AscPtr(u32::try_from(ptr).unwrap(), PhantomData) } } From a42b393f873c0e89e6a63288397cddca5b860ee0 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 3 Jun 2020 19:40:32 -0300 Subject: [PATCH 09/22] runtime: Use weak references to the instance in host exports --- runtime/wasm/src/module/mod.rs | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index fe221a0966b..0195a364789 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -35,16 +35,18 @@ const TRAP_TIMEOUT: &str = "trap: interrupt"; /// 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. + // 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>>, } impl Drop for WasmInstanceHandle { fn drop(&mut self) { - // `WasmInstance` is in a reference cycle with `wasmtime::Instance`, more precisely - // `wasmtime::Store`. Drop the `WasmInstance` to avoid leaks. - self.instance.borrow_mut().take(); + // Assert that the instance will be dropped. + assert_eq!(Rc::strong_count(&self.instance), 1); } } @@ -244,12 +246,13 @@ impl WasmInstance { // link an import with all the modules that require it. for module in modules { - let func_shared_instance = shared_instance.clone(); + let func_shared_instance = Rc::downgrade(&shared_instance); linker.func( module, $wasm_name, move |$($param: i32),*| { - let mut instance = func_shared_instance.borrow_mut(); + 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( @@ -268,10 +271,11 @@ impl WasmInstance { .flatten(); for module in modules { - let func_shared_instance = shared_instance.clone(); + let func_shared_instance = Rc::downgrade(&shared_instance); linker.func(module, "store.get", move |entity_ptr: i32, id_ptr: i32| { let start = Instant::now(); - let mut instance = func_shared_instance.borrow_mut(); + 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"); @@ -292,10 +296,11 @@ impl WasmInstance { .flatten(); for module in modules { - let func_shared_instance = shared_instance.clone(); + let func_shared_instance = Rc::downgrade(&shared_instance); linker.func(module, "ethereum.call", move |call_ptr: i32| { let start = Instant::now(); - let mut instance = func_shared_instance.borrow_mut(); + 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"); From 8e2caf5b2fe68b613d554aaa0c80a1fd9129166f Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Thu, 4 Jun 2020 10:43:30 -0300 Subject: [PATCH 10/22] runtime: Use `anyhow::ensure` --- runtime/wasm/src/host.rs | 43 ++++++++++++++-------------- runtime/wasm/src/host_exports.rs | 13 +++++---- runtime/wasm/src/mapping.rs | 4 +-- runtime/wasm/src/module/mod.rs | 8 +++--- runtime/wasm/src/to_from/external.rs | 17 +++++------ 5 files changed, 44 insertions(+), 41 deletions(-) diff --git a/runtime/wasm/src/host.rs b/runtime/wasm/src/host.rs index 25b88c812de..f7d930a1726 100644 --- a/runtime/wasm/src/host.rs +++ b/runtime/wasm/src/host.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use std::str::FromStr; use std::time::{Duration, Instant}; -use anyhow::bail; +use anyhow::ensure; use async_trait::async_trait; use ethabi::{LogParam, RawLog}; use futures::sync::mpsc::Sender; @@ -307,12 +307,13 @@ impl RuntimeHost { .cloned() .collect::>(); - if handlers.is_empty() { - bail!( + ensure!( + !handlers.is_empty(), + format!( "No event handler found for event in data source \"{}\"", self.data_source_name, ) - } + ); Ok(handlers) } @@ -320,9 +321,10 @@ impl RuntimeHost { 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 { - bail!("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]; @@ -507,12 +509,11 @@ impl RuntimeHostTrait for RuntimeHost { .decode_input(&call.input.0[4..]) .context("Generating function inputs for an Ethereum call failed")?; - if tokens.len() != function_abi.inputs.len() { - bail!( - "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() @@ -532,12 +533,11 @@ impl RuntimeHostTrait for RuntimeHost { .decode_output(&call.output.0) .context("Generating function outputs for an Ethereum call failed")?; - if tokens.len() != function_abi.outputs.len() { - bail!( - "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() @@ -681,12 +681,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() { - bail!( + 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 fe06733e732..12bc6aa9c70 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -484,9 +484,10 @@ impl HostExports { x: BigDecimal, y: BigDecimal, ) -> Result { - if y == 0.into() { - anyhow::bail!("attempted to divide BigDecimal `{}` by zero", x); - } + anyhow::ensure!( + y != 0.into(), + format!("attempted to divide BigDecimal `{}` by zero", x) + ); Ok(x / y) } @@ -500,7 +501,7 @@ impl HostExports { } pub(crate) fn big_decimal_from_string(&self, s: String) -> Result { - BigDecimal::from_str(&s).context("failed to parse BigDecimal") + BigDecimal::from_str(&s).with_context(|| format!("string is not a BigDecimal: '{}'", s)) } pub(crate) fn data_source_create( @@ -600,8 +601,8 @@ pub(crate) fn json_from_bytes(bytes: &Vec) -> Result Result { // `H160::from_str` takes a hex string with no leading `0x`. - let string = string.trim_start_matches("0x"); - H160::from_str(string).context("Failed to convert string to Address/H160") + 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/mapping.rs b/runtime/wasm/src/mapping.rs index 0cb500270ac..b51b4caeaa9 100644 --- a/runtime/wasm/src/mapping.rs +++ b/runtime/wasm/src/mapping.rs @@ -21,7 +21,7 @@ pub fn spawn_module( runtime: tokio::runtime::Handle, timeout: Option, ) -> Result, anyhow::Error> { - let valid_module = Arc::new(ValidModule::new(&raw_module).unwrap()); + 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); @@ -164,7 +164,7 @@ impl MappingContext { pub(crate) struct ValidModule { pub(super) module: wasmtime::Module, - // A wasm import consists of a `module` and a `name`. AS will generates imports such that they + // 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 diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 0195a364789..e0695ff5b33 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -239,10 +239,10 @@ impl WasmInstance { ($wasm_name:expr, $rust_name:ident, $section:expr, $($param:ident),*) => { let modules = valid_module - .import_name_to_modules - .get($wasm_name) - .into_iter() - .flatten(); + .import_name_to_modules + .get($wasm_name) + .into_iter() + .flatten(); // link an import with all the modules that require it. for module in modules { diff --git a/runtime/wasm/src/to_from/external.rs b/runtime/wasm/src/to_from/external.rs index 54d64054ba4..2762ffe075c 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -5,7 +5,7 @@ use graph::components::ethereum::{ EthereumBlockData, EthereumCallData, EthereumEventData, EthereumTransactionData, }; use graph::data::store; -use graph::prelude::anyhow::{self, Error}; +use graph::prelude::anyhow::{ensure, Error}; use graph::prelude::serde_json; use graph::prelude::web3::types as web3; use graph::prelude::{BigDecimal, BigInt}; @@ -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() { - anyhow::bail!( + 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) } } From 21f9c26ada379a97be621949684feb226adec241 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Thu, 4 Jun 2020 18:04:21 -0300 Subject: [PATCH 11/22] runtime: Properly explain unsafe usage --- runtime/wasm/src/module/mod.rs | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index e0695ff5b33..33856798e75 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -198,6 +198,12 @@ impl WasmInstanceHandle { } } +/// 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, memory: Memory, @@ -461,7 +467,18 @@ impl AscHeap for WasmInstance { let ptr = self.arena_start_ptr as usize; - // Safe because we are accessing and immediately dropping the reference to the data. + // 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; @@ -473,7 +490,9 @@ impl AscHeap for WasmInstance { let offset = offset as usize; let size = size as usize; - // Safe because we are accessing and immediately dropping the reference to the data. + // 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() } } } From 740f03d938e30d34e68870758bc180a14ae9d0c8 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Fri, 5 Jun 2020 16:13:02 -0300 Subject: [PATCH 12/22] runtime: Expand comments --- runtime/wasm/src/mapping.rs | 4 ++++ runtime/wasm/src/module/mod.rs | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/runtime/wasm/src/mapping.rs b/runtime/wasm/src/mapping.rs index b51b4caeaa9..50937843f84 100644 --- a/runtime/wasm/src/mapping.rs +++ b/runtime/wasm/src/mapping.rs @@ -178,10 +178,14 @@ pub(crate) struct ValidModule { impl ValidModule { /// Pre-process and validate the module. 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)?; diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 33856798e75..93ade48da01 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -206,6 +206,10 @@ impl WasmInstanceHandle { /// ``` 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>, From f815c8b9623d49e37d1fb8598fc48e74ec67aeec Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Fri, 5 Jun 2020 16:17:04 -0300 Subject: [PATCH 13/22] runtime: Rename `instance` and `borrow_instance` --- runtime/wasm/src/module/mod.rs | 21 +++++++++++---------- runtime/wasm/src/module/test.rs | 14 +++++++------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 93ade48da01..921fd50f556 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -57,8 +57,8 @@ impl WasmInstanceHandle { value: &serde_json::Value, user_data: &store::Value, ) -> Result { - let value = self.instance().asc_new(value); - let user_data = self.instance().asc_new(user_data); + let value = self.instance_mut().asc_new(value); + let user_data = self.instance_mut().asc_new(user_data); // Invoke the callback let func = self @@ -86,7 +86,7 @@ impl WasmInstanceHandle { // Decide on the destination type using the mapping // api version provided in the subgraph manifest let event = if self.instance().ctx.host_exports.api_version >= Version::new(0, 0, 2) { - self.instance() + self.instance_mut() .asc_new::, _>(&EthereumEventData { block: EthereumBlockData::from(block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), @@ -98,7 +98,7 @@ impl WasmInstanceHandle { }) .erase() } else { - self.instance() + self.instance_mut() .asc_new::, _>(&EthereumEventData { block: EthereumBlockData::from(block.as_ref()), transaction: EthereumTransactionData::from(transaction.deref()), @@ -135,11 +135,13 @@ impl WasmInstanceHandle { outputs, }; let arg = if self.instance().ctx.host_exports.api_version >= Version::new(0, 0, 3) { - self.instance() + self.instance_mut() .asc_new::(&call) .erase() } else { - self.instance().asc_new::(&call).erase() + self.instance_mut() + .asc_new::(&call) + .erase() }; self.invoke_handler(handler_name, arg)?; @@ -154,14 +156,14 @@ impl WasmInstanceHandle { let block = EthereumBlockData::from(self.instance().ctx.block.as_ref()); // Prepare an EthereumBlock for the WASM runtime - let arg = self.instance().asc_new(&block); + 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 self) -> RefMut<'_, WasmInstance> { + pub(crate) fn instance_mut(&mut self) -> RefMut<'_, WasmInstance> { RefMut::map(self.instance.borrow_mut(), |i| i.as_mut().unwrap()) } @@ -169,8 +171,7 @@ impl WasmInstanceHandle { self.instance.borrow_mut().take().unwrap() } - #[cfg(test)] - pub(crate) fn borrow_instance(&self) -> std::cell::Ref<'_, WasmInstance> { + pub(crate) fn instance(&self) -> std::cell::Ref<'_, WasmInstance> { std::cell::Ref::map(self.instance.borrow(), |i| i.as_ref().unwrap()) } diff --git a/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index 81f0bb58541..f181db3d718 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -171,7 +171,7 @@ fn mock_context( impl WasmInstanceHandle { fn get_func(&self, name: &str) -> wasmtime::Func { - self.borrow_instance().instance.get_func(name).unwrap() + self.instance().instance.get_func(name).unwrap() } fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr { @@ -210,11 +210,11 @@ impl WasmInstanceHandle { impl AscHeap for WasmInstanceHandle { fn raw_new(&mut self, bytes: &[u8]) -> u32 { - self.instance().raw_new(bytes) + self.instance_mut().raw_new(bytes) } fn get(&self, offset: u32, size: u32) -> Vec { - self.borrow_instance().get(offset, size) + self.instance().get(offset, size) } } @@ -328,8 +328,8 @@ async fn ipfs_map() { } else { ipfs.add(Cursor::new(json_string)).await.unwrap().hash }; - let value = module.instance().asc_new(&hash); - let user_data = module.instance().asc_new(USER_DATA); + let value = module.instance_mut().asc_new(&hash); + let user_data = module.instance_mut().asc_new(USER_DATA); // Invoke the callback let func = module @@ -416,7 +416,7 @@ async fn ipfs_map() { async fn ipfs_fail() { let mut module = test_module("ipfsFail", mock_data_source("wasm_test/ipfs_cat.wasm")); - let hash = module.instance().asc_new("invalid hash"); + let hash = module.instance_mut().asc_new("invalid hash"); assert!(module .invoke_export::<_, AscString>("ipfsCat", hash,) .is_null()); @@ -662,7 +662,7 @@ async fn entity_store() { // We need to empty the cache for the next test let cache = std::mem::replace( - &mut module.instance().ctx.state.entity_cache, + &mut module.instance_mut().ctx.state.entity_cache, EntityCache::new(store.clone()), ); let mut mods = cache From d98c0cc335a50e7e0faa57ca73c51fd6dd32a640 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Fri, 5 Jun 2020 18:10:35 -0300 Subject: [PATCH 14/22] runtime: depend on wasmtime master, use u32 where possible --- Cargo.lock | 91 +++++++++++------------- runtime/wasm/Cargo.toml | 3 +- runtime/wasm/src/asc_abi/asc_ptr.rs | 14 ++-- runtime/wasm/src/host_exports.rs | 4 +- runtime/wasm/src/module/into_wasm_ret.rs | 12 ++-- runtime/wasm/src/module/mod.rs | 14 ++-- runtime/wasm/src/module/test.rs | 6 +- runtime/wasm/src/module/test/abi.rs | 6 +- runtime/wasm/src/to_from/external.rs | 4 +- 9 files changed, 71 insertions(+), 83 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ed1aceb3741..b2b20575e37 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" @@ -114,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" @@ -449,8 +449,7 @@ checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac" [[package]] name = "cranelift-bforest" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9e870ce52c4b39ba80fb0b04a4843223a2a33cd491473f67c7bf7a66512c5a7" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "cranelift-entity", ] @@ -458,8 +457,7 @@ dependencies = [ [[package]] name = "cranelift-codegen" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9007dac19ec2395d23f3966166a7777b24ac214b0e0766e15c0f33b98e781b4" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "byteorder", "cranelift-bforest", @@ -478,8 +476,7 @@ dependencies = [ [[package]] name = "cranelift-codegen-meta" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70e7ff7c9ee40573b55f63d9bc973d1da54464b7686923981f0aafbde8ef78c7" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "cranelift-codegen-shared", "cranelift-entity", @@ -488,14 +485,12 @@ dependencies = [ [[package]] name = "cranelift-codegen-shared" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75d5765a961b8d1c6468f230c634ea6dc35b76c6ab9069c04a84ac1b67149168" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" [[package]] name = "cranelift-entity" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfa6ac15138da9983259dd257c8573d3ccf75c7fb9ed06708b38b43a3c090326" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "serde", ] @@ -503,8 +498,7 @@ dependencies = [ [[package]] name = "cranelift-frontend" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3062a6fce384623ac2d19e98e7c774c944584bb5a8e42910e93dcebed7c44d87" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "cranelift-codegen", "log 0.4.8", @@ -515,8 +509,7 @@ dependencies = [ [[package]] name = "cranelift-native" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aecc239b00b0ac7655dc91419c537ce5a69535c82854b55fcc357a1e2c3d0d91" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "cranelift-codegen", "raw-cpuid", @@ -526,8 +519,7 @@ dependencies = [ [[package]] name = "cranelift-wasm" version = "0.64.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b31a4cd32099bc8c7b340a4a9f9bbb2925e29cc45509a0088330558244c5f04a" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -1250,15 +1242,12 @@ dependencies = [ [[package]] name = "gimli" -version = "0.20.0" +version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81dd6190aad0f05ddbbf3245c54ed14ca4aa6dd32f22312b70d8f168c3e3e633" +checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" dependencies = [ - "arrayvec", - "byteorder", "fallible-iterator 0.2.0", "indexmap", - "smallvec 1.2.0", "stable_deref_trait", ] @@ -2176,9 +2165,9 @@ checksum = "adb241df5c4caeb888755363fc95f8a896618dc0d435e9e775f7930cb099beab" [[package]] name = "mach" -version = "0.2.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86dd2487cdfea56def77b88438a2c915fb45113c5319bfe7e14306ca4cd0b0e1" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" dependencies = [ "libc", ] @@ -2442,6 +2431,12 @@ 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" @@ -3202,9 +3197,9 @@ checksum = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae" [[package]] name = "region" -version = "2.1.2" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "448e868c6e4cfddfa49b6a72c95906c04e8547465e9536575b95c70a4044f856" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" dependencies = [ "bitflags 1.2.1", "libc", @@ -4606,8 +4601,7 @@ checksum = "32fddd575d477c6e9702484139cf9f23dcd554b06d185ed0f56c857dd3a47aa6" [[package]] name = "wasmtime" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7907161766b4de8c1970d6a4849018d1ab004f65fefacee1dfb8a86f1b20fcd" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "anyhow", "backtrace", @@ -4630,8 +4624,7 @@ dependencies = [ [[package]] name = "wasmtime-debug" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7aad5fdc88752c633bfe23c1089e29d7178e0d58ba94f07848a83df88a89b03" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "anyhow", "faerie", @@ -4646,8 +4639,7 @@ dependencies = [ [[package]] name = "wasmtime-environ" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4cb6609a337b680a95c9b39f2a0225faab4bf34f47dc9bb2eed34bd7a9d5904c" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "anyhow", "base64 0.12.1", @@ -4675,8 +4667,7 @@ dependencies = [ [[package]] name = "wasmtime-jit" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8959505970c254707e9e765940ef23b58d23211b80d40593a6a96870f57bdd42" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "anyhow", "cfg-if", @@ -4702,15 +4693,14 @@ dependencies = [ [[package]] name = "wasmtime-profiling" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f621d2c66e0bc74e6569c41aeb84a6e92a9620fecedad08c34c0bec4f5d2b1e" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "anyhow", "cfg-if", "gimli", "lazy_static", "libc", - "object", + "object 0.18.0", "scroll", "serde", "target-lexicon", @@ -4721,8 +4711,7 @@ dependencies = [ [[package]] name = "wasmtime-runtime" version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304b3f8ef4d2132f7366d008b614650165b766640b4351c6dafb9882b86f41f3" +source = "git+https://github.com/bytecodealliance/wasmtime#bc555468a7503c356001142d84326390a51c3385" dependencies = [ "backtrace", "cc", diff --git a/runtime/wasm/Cargo.toml b/runtime/wasm/Cargo.toml index 37c7eb5f8d7..e7eac11c132 100644 --- a/runtime/wasm/Cargo.toml +++ b/runtime/wasm/Cargo.toml @@ -25,7 +25,8 @@ bytes = "0.5" # See also 92cd8019-0136-4011-96a0-40b3eec37f73 maybe-owned = { git = "https://github.com/rustonaut/maybe-owned", branch = "master" } -wasmtime = "0.17" +# Use a released version once 0.18 is released. +wasmtime = { git = "https://github.com/bytecodealliance/wasmtime" } [dev-dependencies] graphql-parser = "0.2.3" diff --git a/runtime/wasm/src/asc_abi/asc_ptr.rs b/runtime/wasm/src/asc_abi/asc_ptr.rs index d3a52575407..c3c62438249 100644 --- a/runtime/wasm/src/asc_abi/asc_ptr.rs +++ b/runtime/wasm/src/asc_abi/asc_ptr.rs @@ -1,5 +1,4 @@ use super::{class::EnumPayload, AscHeap, AscType, AscValue}; -use std::convert::TryFrom; use std::fmt; use std::marker::PhantomData; use std::mem::size_of; @@ -28,9 +27,9 @@ impl fmt::Debug for AscPtr { } impl AscPtr { - /// A raw pointer to be passed to wasm. Wasmtime uses `i32` for 32 bits wasm integers. - pub(crate) fn wasm_ptr(self) -> i32 { - self.0 as i32 + /// A raw pointer to be passed to Wasm. + pub(crate) fn wasm_ptr(self) -> u32 { + self.0 } } @@ -75,10 +74,9 @@ impl AscPtr { } } -/// WASM integers do not carry sign information, but wasmtime uses `i32` for them. -impl From for AscPtr { - fn from(ptr: i32) -> Self { - AscPtr(u32::try_from(ptr).unwrap(), PhantomData) +impl From for AscPtr { + fn from(ptr: u32) -> Self { + AscPtr(ptr, PhantomData) } } diff --git a/runtime/wasm/src/host_exports.rs b/runtime/wasm/src/host_exports.rs index 12bc6aa9c70..50c853737bd 100644 --- a/runtime/wasm/src/host_exports.rs +++ b/runtime/wasm/src/host_exports.rs @@ -91,8 +91,8 @@ impl HostExports { &self, message: Option, file_name: Option, - line_number: Option, - column_number: Option, + line_number: Option, + column_number: Option, ) -> Result<(), anyhow::Error> { let message = message .map(|message| format!("message: {}", message)) diff --git a/runtime/wasm/src/module/into_wasm_ret.rs b/runtime/wasm/src/module/into_wasm_ret.rs index 2749498e8b4..d3ac92c5ab5 100644 --- a/runtime/wasm/src/module/into_wasm_ret.rs +++ b/runtime/wasm/src/module/into_wasm_ret.rs @@ -37,22 +37,22 @@ impl IntoWasmRet for f64 { } impl IntoWasmRet for u64 { - type Ret = i64; - fn into_wasm_ret(self) -> i64 { - i64::from_le_bytes(self.to_le_bytes()) + type Ret = u64; + fn into_wasm_ret(self) -> u64 { + self } } impl IntoWasmRet for bool { type Ret = i32; fn into_wasm_ret(self) -> i32 { - self as i32 + self.into() } } impl IntoWasmRet for AscPtr { - type Ret = i32; - fn into_wasm_ret(self) -> i32 { + type Ret = u32; + fn into_wasm_ret(self) -> u32 { self.wasm_ptr() } } diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 921fd50f556..28652e15ac2 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -261,7 +261,7 @@ impl WasmInstance { linker.func( module, $wasm_name, - move |$($param: i32),*| { + move |$($param: u32),*| { let instance = func_shared_instance.upgrade().unwrap(); let mut instance = instance.borrow_mut(); let instance = instance.as_mut().unwrap(); @@ -283,7 +283,7 @@ impl WasmInstance { for module in modules { let func_shared_instance = Rc::downgrade(&shared_instance); - linker.func(module, "store.get", move |entity_ptr: i32, id_ptr: i32| { + 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(); @@ -308,7 +308,7 @@ impl WasmInstance { for module in modules { let func_shared_instance = Rc::downgrade(&shared_instance); - linker.func(module, "ethereum.call", move |call_ptr: i32| { + 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(); @@ -510,8 +510,8 @@ impl WasmInstance { &self, message_ptr: AscPtr, file_name_ptr: AscPtr, - line_number: i32, - column_number: i32, + line_number: u32, + column_number: u32, ) -> Result<(), Trap> { let message = match message_ptr.is_null() { false => Some(self.asc_get(message_ptr)), @@ -890,7 +890,7 @@ impl WasmInstance { fn big_int_pow( &mut self, x_ptr: AscPtr, - exp: i32, + 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); @@ -1061,7 +1061,7 @@ impl WasmInstance { .unwrap_or(AscPtr::null())) } - fn log_log(&mut self, level: i32, msg: AscPtr) { + 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); diff --git a/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index f181db3d718..945ce552e88 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -176,13 +176,13 @@ impl WasmInstanceHandle { fn invoke_export(&self, f: &str, arg: AscPtr) -> AscPtr { let func = self.get_func(f).get1().unwrap(); - let ptr: i32 = func(arg.wasm_ptr()).unwrap(); + let ptr: u32 = func(arg.wasm_ptr()).unwrap(); ptr.into() } fn invoke_export2(&self, f: &str, arg0: AscPtr, arg1: AscPtr) -> AscPtr { let func = self.get_func(f).get2().unwrap(); - let ptr: i32 = func(arg0.wasm_ptr(), arg1.wasm_ptr()).unwrap(); + let ptr: u32 = func(arg0.wasm_ptr(), arg1.wasm_ptr()).unwrap(); ptr.into() } @@ -203,7 +203,7 @@ impl WasmInstanceHandle { 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: i32 = func(val).unwrap(); + let ptr: u32 = func(val).unwrap(); ptr.into() } } diff --git a/runtime/wasm/src/module/test/abi.rs b/runtime/wasm/src/module/test/abi.rs index 55ae646f2b5..21b683c8bd1 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -197,7 +197,7 @@ async fn abi_store_value() { // Value::Null let func = module.get_func("value_null").get0().unwrap(); - let ptr: i32 = func().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); @@ -237,7 +237,7 @@ async fn abi_store_value() { // Value::List let func = module.get_func("array_from_values").get2().unwrap(); - let new_value_ptr: i32 = func(module.asc_new(string).wasm_ptr(), int).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!( @@ -356,6 +356,6 @@ async fn invalid_discriminant() { ); let func = module.get_func("invalid_discriminant").get0().unwrap(); - let ptr: i32 = func().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 2762ffe075c..4d0eab0d2eb 100644 --- a/runtime/wasm/src/to_from/external.rs +++ b/runtime/wasm/src/to_from/external.rs @@ -430,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, From c090839d51b0d10654079bc14c772338f5eaafaf Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Mon, 8 Jun 2020 16:13:00 -0300 Subject: [PATCH 15/22] runtime: Don't disable timeout due to ipfs.map --- runtime/wasm/src/module/mod.rs | 38 ++++++++-------- runtime/wasm/src/module/stopwatch.rs | 66 ++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 18 deletions(-) create mode 100644 runtime/wasm/src/module/stopwatch.rs diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index 28652e15ac2..dd887354f57 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -3,8 +3,6 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::ops::Deref; use std::rc::Rc; -use std::sync::atomic::AtomicBool; -use std::sync::atomic::Ordering; use std::time::Instant; use semver::Version; @@ -26,7 +24,10 @@ 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; @@ -219,8 +220,8 @@ pub(crate) struct WasmInstance { pub(crate) host_metrics: Arc, pub(crate) timeout: Option, - // Used by ipfs.map to turn off interrupts. - should_interrupt: Arc, + // Used by ipfs.map. + pub(crate) timeout_stopwatch: Arc>, // First free byte in the current arena. arena_start_ptr: i32, @@ -420,15 +421,19 @@ impl WasmInstance { .context("`memory.allocate` function not found")? .get1()?; - let should_interrupt = Arc::new(AtomicBool::new(true)); + 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 should_interrupt = should_interrupt.clone(); - graph::spawn(async move { - tokio::time::delay_for(timeout).await; - if should_interrupt.load(Ordering::SeqCst) { - interrupt_handle.interrupt() + let timeout_stopwatch = timeout_stopwatch.clone(); + graph::spawn_allow_panic(async move { + loop { + let time_left = + timeout.checked_sub(timeout_stopwatch.lock().unwrap().elapsed()); + match time_left { + None => break interrupt_handle.interrupt(), // Timed out. + Some(time) => tokio::time::delay_for(time).await, + } } }); } @@ -441,7 +446,7 @@ impl WasmInstance { valid_module, host_metrics, timeout, - should_interrupt, + timeout_stopwatch, // `arena_start_ptr` will be set on the first call to `raw_new`. arena_free_size: 0, @@ -717,9 +722,10 @@ impl WasmInstance { let callback: String = self.asc_get(callback); let user_data: store::Value = self.try_asc_get(user_data)?; - // `ipfs_map` can take a long time to process, so disable the timeout. - self.should_interrupt.store(false, Ordering::SeqCst); let flags = self.asc_get(flags); + + // Pause the timeout while running ipfs_map + self.timeout_stopwatch.lock().unwrap().stop(); let start_time = Instant::now(); let output_states = HostExports::ipfs_map( &self.ctx.host_exports.link_resolver.clone(), @@ -750,12 +756,8 @@ impl WasmInstance { .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.timeout_stopwatch.lock().unwrap().start(); - // TODO - // self.start_time += start_time.elapsed(); Ok(()) } diff --git a/runtime/wasm/src/module/stopwatch.rs b/runtime/wasm/src/module/stopwatch.rs new file mode 100644 index 00000000000..26c37291c92 --- /dev/null +++ b/runtime/wasm/src/module/stopwatch.rs @@ -0,0 +1,66 @@ +// 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 the stopwatch was split last, if ever. + split_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, + split_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; + self.split_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; + } + } + } +} From 7d57905d787469dd8ea22c2150a4341a6deb3258 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 10:49:42 -0300 Subject: [PATCH 16/22] runtime: Address review --- Cargo.lock | 7 +++++++ runtime/wasm/Cargo.toml | 2 ++ runtime/wasm/src/host.rs | 6 ++---- runtime/wasm/src/module/mod.rs | 10 +++++++--- runtime/wasm/src/module/stopwatch.rs | 4 ---- 5 files changed, 18 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2b20575e37..205eb7f5ae7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -677,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" @@ -1484,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", diff --git a/runtime/wasm/Cargo.toml b/runtime/wasm/Cargo.toml index e7eac11c132..19d0c6b9290 100644 --- a/runtime/wasm/Cargo.toml +++ b/runtime/wasm/Cargo.toml @@ -28,6 +28,8 @@ maybe-owned = { git = "https://github.com/rustonaut/maybe-owned", branch = "mast # 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/host.rs b/runtime/wasm/src/host.rs index f7d930a1726..19683496df3 100644 --- a/runtime/wasm/src/host.rs +++ b/runtime/wasm/src/host.rs @@ -309,10 +309,8 @@ impl RuntimeHost { ensure!( !handlers.is_empty(), - format!( - "No event handler found for event in data source \"{}\"", - self.data_source_name, - ) + "No event handler found for event in data source \"{}\"", + self.data_source_name, ); Ok(handlers) diff --git a/runtime/wasm/src/module/mod.rs b/runtime/wasm/src/module/mod.rs index dd887354f57..09ae87a041c 100644 --- a/runtime/wasm/src/module/mod.rs +++ b/runtime/wasm/src/module/mod.rs @@ -427,11 +427,14 @@ impl WasmInstance { 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, } } @@ -724,8 +727,11 @@ impl WasmInstance { let flags = self.asc_get(flags); - // Pause the timeout while running ipfs_map + // 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 output_states = HostExports::ipfs_map( &self.ctx.host_exports.link_resolver.clone(), @@ -756,8 +762,6 @@ impl WasmInstance { .extend(output_state.created_data_sources); } - self.timeout_stopwatch.lock().unwrap().start(); - Ok(()) } diff --git a/runtime/wasm/src/module/stopwatch.rs b/runtime/wasm/src/module/stopwatch.rs index 26c37291c92..2db203e32c3 100644 --- a/runtime/wasm/src/module/stopwatch.rs +++ b/runtime/wasm/src/module/stopwatch.rs @@ -8,8 +8,6 @@ use std::time::{Duration, Instant}; pub struct TimeoutStopwatch { /// The time the stopwatch was started last, if ever. start_time: Option, - /// The time the stopwatch was split last, if ever. - split_time: Option, /// The time elapsed while the stopwatch was running (between start() and stop()). elapsed: Duration, } @@ -18,7 +16,6 @@ impl Default for TimeoutStopwatch { fn default() -> TimeoutStopwatch { TimeoutStopwatch { start_time: None, - split_time: None, elapsed: Duration::from_secs(0), } } @@ -47,7 +44,6 @@ impl TimeoutStopwatch { pub fn stop(&mut self) { self.elapsed = self.elapsed(); self.start_time = None; - self.split_time = None; } /// Returns the elapsed time since the start of the stopwatch. From f8f3116b78bba484847ed77ac9627052becd428f Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 14:03:45 -0300 Subject: [PATCH 17/22] runtime: Fix flaky tests --- runtime/wasm/src/module/test.rs | 45 ++++++++++++++++++++------------- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index 945ce552e88..0ea0bf3d6d6 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -283,11 +283,14 @@ async fn ipfs_cat() { 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 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"); + // Ipfs host functions use `block_on` which must be called from a sync context. + tokio::task::block_in_place(|| { + 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"); + }) } // The user_data value we use with calls to ipfs_map @@ -331,15 +334,18 @@ async fn ipfs_map() { 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())?; + // Ipfs host functions use `block_on` which must be called from a sync context. + tokio::task::block_in_place(|| { + // Invoke the callback + let func = module + .instance() + .instance + .get_func("ipfsMap") + .unwrap() + .get2() + .unwrap(); + func(value.wasm_ptr(), user_data.wasm_ptr()) + })?; let mut mods = module .take_instance() @@ -416,10 +422,13 @@ async fn ipfs_map() { async fn ipfs_fail() { 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()); + // Ipfs host functions use `block_on` which must be called from a sync context. + tokio::task::block_in_place(|| { + let hash = module.instance_mut().asc_new("invalid hash"); + assert!(module + .invoke_export::<_, AscString>("ipfsCat", hash,) + .is_null()); + }) } #[tokio::test] From 33ebb5c404c94dee0015fd8f2b9747026445efeb Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 16:20:52 -0300 Subject: [PATCH 18/22] runtime: Spawn test modules that use block_on in their own thread --- runtime/wasm/src/module/test.rs | 124 +++++++++++++++++----------- runtime/wasm/src/module/test/abi.rs | 24 +++--- 2 files changed, 86 insertions(+), 62 deletions(-) diff --git a/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index 0ea0bf3d6d6..7f75b7d6136 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -1,7 +1,6 @@ use ethabi::Token; use hex; use std::collections::{BTreeMap, HashMap}; -use std::env; use std::io::Cursor; use std::str::FromStr; @@ -283,14 +282,24 @@ async fn ipfs_cat() { 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. - tokio::task::block_in_place(|| { - 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"); + // 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(|| { + // Ipfs host functions use `block_on` which must be called from a sync context. + tokio::task::block_in_place(|| { + 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"); + }) + }) }) + .join() + .unwrap() } // The user_data value we use with calls to ipfs_map @@ -324,45 +333,53 @@ async fn ipfs_map() { subgraph_id: &'static str, json_string: String, ) -> Result, anyhow::Error> { - let (mut module, store) = - test_valid_module_and_store(subgraph_id, mock_data_source("wasm_test/ipfs_map.wasm")); let hash = if json_string == BAD_IPFS_HASH { "Qm".to_string() } else { ipfs.add(Cursor::new(json_string)).await.unwrap().hash }; - let value = module.instance_mut().asc_new(&hash); - let user_data = module.instance_mut().asc_new(USER_DATA); - - // Ipfs host functions use `block_on` which must be called from a sync context. - tokio::task::block_in_place(|| { - // Invoke the callback - let func = module - .instance() - .instance - .get_func("ipfsMap") - .unwrap() - .get2() - .unwrap(); - 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) + + // 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 @@ -420,15 +437,22 @@ async fn ipfs_map() { #[tokio::test(threaded_scheduler)] async fn ipfs_fail() { - let mut module = test_module("ipfsFail", mock_data_source("wasm_test/ipfs_cat.wasm")); - - // Ipfs host functions use `block_on` which must be called from a sync context. - tokio::task::block_in_place(|| { - let hash = module.instance_mut().asc_new("invalid hash"); - assert!(module - .invoke_export::<_, 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()); + }) }) + .join() + .unwrap() } #[tokio::test] diff --git a/runtime/wasm/src/module/test/abi.rs b/runtime/wasm/src/module/test/abi.rs index 21b683c8bd1..f44a0ccbaad 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -1,17 +1,17 @@ use super::*; -#[tokio::test(threaded_scheduler)] -async fn unbounded_loop() { - // Set handler timeout to 3 seconds. - env::set_var(crate::host::TIMEOUT_ENV_VAR, "3"); - let module = test_module( - "unboundedLoop", - mock_data_source("wasm_test/non_terminating.wasm"), - ); - let func = module.get_func("loop").get0().unwrap(); - let res: Result<(), _> = func(); - assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); -} +// #[tokio::test(threaded_scheduler)] +// async fn unbounded_loop() { +// // Set handler timeout to 3 seconds. +// env::set_var(crate::host::TIMEOUT_ENV_VAR, "3"); +// let module = test_module( +// "unboundedLoop", +// mock_data_source("wasm_test/non_terminating.wasm"), +// ); +// let func = module.get_func("loop").get0().unwrap(); +// let res: Result<(), _> = func(); +// assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); +// } #[tokio::test] async fn unbounded_recursion() { From fd616a0b632ff73a16579ce668ee55be5b9ee221 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 17:05:18 -0300 Subject: [PATCH 19/22] graph: Use tokio block_on instead of futures block_on --- graph/src/task_spawn.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/graph/src/task_spawn.rs b/graph/src/task_spawn.rs index 9dc58bfbc24..5ff3e9d9f2c 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` +//! functoins 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)) From 1d0811577c88d29df92250b6f8c3aff8c72b4519 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 17:41:19 -0300 Subject: [PATCH 20/22] runtime: Remove redundant `block_in_place` in test --- runtime/wasm/src/module/test.rs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/runtime/wasm/src/module/test.rs b/runtime/wasm/src/module/test.rs index 7f75b7d6136..411b4b5b5ad 100644 --- a/runtime/wasm/src/module/test.rs +++ b/runtime/wasm/src/module/test.rs @@ -287,15 +287,11 @@ async fn ipfs_cat() { let runtime = tokio::runtime::Handle::current(); std::thread::spawn(move || { runtime.enter(|| { - // Ipfs host functions use `block_on` which must be called from a sync context. - tokio::task::block_in_place(|| { - 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"); - }) + 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"); }) }) .join() From 36da2e14c67b2c8b32e31a203c366793f1cb4e28 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 17:49:54 -0300 Subject: [PATCH 21/22] graph: Fix typo --- graph/src/task_spawn.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/src/task_spawn.rs b/graph/src/task_spawn.rs index 5ff3e9d9f2c..dbd4eec9a9f 100644 --- a/graph/src/task_spawn.rs +++ b/graph/src/task_spawn.rs @@ -2,7 +2,7 @@ //! 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` -//! functoins to opt out of that. +//! 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. From 65a2183ad7906604364c57d7f0de008391e5ad34 Mon Sep 17 00:00:00 2001 From: Leonardo Yvens Date: Wed, 10 Jun 2020 17:55:12 -0300 Subject: [PATCH 22/22] runtime: Uncomment test --- runtime/wasm/src/module/test/abi.rs | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/runtime/wasm/src/module/test/abi.rs b/runtime/wasm/src/module/test/abi.rs index f44a0ccbaad..7bfb5145c8c 100644 --- a/runtime/wasm/src/module/test/abi.rs +++ b/runtime/wasm/src/module/test/abi.rs @@ -1,17 +1,18 @@ use super::*; +use std::env; -// #[tokio::test(threaded_scheduler)] -// async fn unbounded_loop() { -// // Set handler timeout to 3 seconds. -// env::set_var(crate::host::TIMEOUT_ENV_VAR, "3"); -// let module = test_module( -// "unboundedLoop", -// mock_data_source("wasm_test/non_terminating.wasm"), -// ); -// let func = module.get_func("loop").get0().unwrap(); -// let res: Result<(), _> = func(); -// assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); -// } +#[tokio::test(threaded_scheduler)] +async fn unbounded_loop() { + // Set handler timeout to 3 seconds. + env::set_var(crate::host::TIMEOUT_ENV_VAR, "3"); + let module = test_module( + "unboundedLoop", + mock_data_source("wasm_test/non_terminating.wasm"), + ); + let func = module.get_func("loop").get0().unwrap(); + let res: Result<(), _> = func(); + assert!(res.unwrap_err().to_string().contains(TRAP_TIMEOUT)); +} #[tokio::test] async fn unbounded_recursion() {