From c1dd0cf4550a67aa1ab0c7987109d495e7e39f81 Mon Sep 17 00:00:00 2001 From: Kachinsky <22611640+kachinskyinsc2@users.noreply.github.com> Date: Wed, 8 Nov 2023 09:38:10 -0600 Subject: [PATCH] remove mysql and redis (#119) * refactor sequencer, accepting commands from sidecar directly * extract scanner/committer from connector * replace mysql with rocksdb * update README.md --------- Co-authored-by: Cyberaurora <22611640+cyberaurora@users.noreply.github.com> --- Cargo.lock | 839 +++------- README.md | 54 +- bin/Cargo.toml | 6 +- bin/src/galois.rs | 72 +- bin/src/sidecar.rs | 2 +- engine/Cargo.toml | 11 +- engine/src/config.rs | 92 +- engine/src/core.rs | 27 +- engine/src/db.rs | 21 - engine/src/executor/assets.rs | 3 +- engine/src/executor/clearing.rs | 3 +- engine/src/executor/mod.rs | 262 ++-- engine/src/fusotao/committer.rs | 114 ++ engine/src/fusotao/connector.rs | 399 +---- engine/src/fusotao/mod.rs | 26 +- engine/src/fusotao/persistence.rs | 97 -- engine/src/fusotao/prover.rs | 2350 ++++++++++++++--------------- engine/src/fusotao/scanner.rs | 194 +++ engine/src/input/mod.rs | 304 +++- engine/src/input/sequence.rs | 514 ------- engine/src/input/sequencer.rs | 171 +++ engine/src/input/server.rs | 68 +- engine/src/input/whistle.rs | 120 -- engine/src/lib.rs | 5 +- engine/src/output/legacy.rs | 39 - engine/src/output/mod.rs | 151 +- engine/src/snapshot.rs | 51 +- galois.toml.example | 13 +- sidecar/Cargo.toml | 2 +- sidecar/src/backend.rs | 74 +- sidecar/src/db.rs | 79 - sidecar/src/endpoint.rs | 9 +- sql/init.sql | 106 -- 33 files changed, 2601 insertions(+), 3677 deletions(-) delete mode 100644 engine/src/db.rs create mode 100644 engine/src/fusotao/committer.rs delete mode 100644 engine/src/fusotao/persistence.rs create mode 100644 engine/src/fusotao/scanner.rs delete mode 100644 engine/src/input/sequence.rs create mode 100644 engine/src/input/sequencer.rs delete mode 100644 engine/src/input/whistle.rs delete mode 100644 engine/src/output/legacy.rs delete mode 100644 sql/init.sql diff --git a/Cargo.lock b/Cargo.lock index 6ff3d2c..6e0bda7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -184,7 +184,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3203e79f4dd9bdda415ed03cf14dae5a2bf775c683a00f94e9cd1faf0f596e5" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -225,7 +225,7 @@ dependencies = [ "blocking", "futures-lite", "once_cell", - "tokio 1.24.2", + "tokio", ] [[package]] @@ -258,18 +258,6 @@ dependencies = [ "futures-lite", ] -[[package]] -name = "async-native-tls" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e9e7a929bd34c68a82d58a4de7f86fffdaf97fb2af850162a7bb19dd7269b33" -dependencies = [ - "async-std", - "native-tls", - "thiserror", - "url", -] - [[package]] name = "async-std" version = "1.12.0" @@ -291,7 +279,7 @@ dependencies = [ "log", "memchr", "once_cell", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", "wasm-bindgen-futures", @@ -311,7 +299,7 @@ checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -400,17 +388,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bigdecimal" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aaf33151a6429fe9211d1b276eafdf70cdff28b071e76c0b0e1503221ea3744" -dependencies = [ - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "bincode" version = "1.3.3" @@ -422,9 +399,9 @@ dependencies = [ [[package]] name = "bindgen" -version = "0.59.2" +version = "0.65.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bd2a9a458e8f4304c52c43ebb0cfbd520289f8379a52e329a38afda99bf8eb8" +checksum = "cfdf7b466f9a4903edc73f95d6d2bcd5baf8ae620638762244d3f60143643cc5" dependencies = [ "bitflags", "cexpr", @@ -432,11 +409,13 @@ dependencies = [ "lazy_static", "lazycell", "peeking_take_while", + "prettyplease", "proc-macro2", "quote", "regex", "rustc-hash", "shlex", + "syn 2.0.39", ] [[package]] @@ -556,7 +535,7 @@ dependencies = [ "borsh-schema-derive-internal", "proc-macro-crate 0.1.5", "proc-macro2", - "syn", + "syn 1.0.109", ] [[package]] @@ -567,7 +546,7 @@ checksum = "5449c28a7b352f2d1e592a8a28bf139bc71afb0764a14f3c02500935d8c44065" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -578,7 +557,7 @@ checksum = "cdbd5696d8bfa21d53d9fe39a714a18538bad11492a42d066dbbc395fb1951c0" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -591,12 +570,6 @@ dependencies = [ "serde", ] -[[package]] -name = "bufstream" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8" - [[package]] name = "bumpalo" version = "3.12.0" @@ -633,7 +606,7 @@ checksum = "13e576ebe98e605500b3c8041bb888e966653577172df6dd97398714eb30b9bf" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -654,21 +627,29 @@ dependencies = [ [[package]] name = "bytes" -version = "0.5.6" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" +checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" [[package]] -name = "bytes" -version = "1.3.0" +name = "bzip2-sys" +version = "0.1.11+1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c" +checksum = "736a955f3fa7875102d57c82b8cac37ec45224a07fd32d58f9f7a186b6cd4cdc" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] name = "cc" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +dependencies = [ + "jobserver", +] [[package]] name = "cexpr" @@ -720,7 +701,7 @@ dependencies = [ "js-sys", "num-integer", "num-traits", - "time 0.1.45", + "time", "wasm-bindgen", "winapi 0.3.9", ] @@ -785,7 +766,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -797,15 +778,6 @@ dependencies = [ "os_str_bytes", ] -[[package]] -name = "cmake" -version = "0.1.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db34956e100b30725f2eb215f90d4871051239535632f84fea3bc92722c66b7c" -dependencies = [ - "cc", -] - [[package]] name = "codespan-reporting" version = "0.11.1" @@ -816,20 +788,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "combine" -version = "4.6.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35ed6e9d84f0b51a7f52daf1c7d71dd136fd7a3f41a8462b8cdb8c78d920fad4" -dependencies = [ - "bytes 0.5.6", - "bytes 1.3.0", - "futures-core", - "memchr", - "pin-project-lite 0.2.9", - "tokio 0.2.25", -] - [[package]] name = "concurrent-queue" version = "2.1.0" @@ -903,54 +861,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crossbeam" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2801af0d36612ae591caa9568261fddce32ce6e08a7275ea334a06a4ad021a2c" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" -dependencies = [ - "cfg-if 1.0.0", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" -dependencies = [ - "autocfg", - "cfg-if 1.0.0", - "crossbeam-utils", - "memoffset", - "scopeguard", -] - [[package]] name = "crossbeam-queue" version = "0.3.8" @@ -1025,7 +935,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1078,7 +988,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.109", ] [[package]] @@ -1095,7 +1005,7 @@ checksum = "65e07508b90551e610910fa648a1878991d367064997a596135b86df30daf07e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1136,7 +1046,7 @@ checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1147,18 +1057,7 @@ checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "derive_utils" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7590f99468735a318c254ca9158d0c065aa9b5312896b5a043b5e39bc96f5fa2" -dependencies = [ - "proc-macro2", - "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1219,12 +1118,6 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ea835d29036a4087793836fa931b08837ad5e957da9e23886b29586fb9b6650" -[[package]] -name = "dtoa" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" - [[package]] name = "dyn-clonable" version = "0.9.0" @@ -1243,7 +1136,7 @@ checksum = "558e40ea573c374cf53507fd240b7ee2f5477df7cfebdb97323ec61c719399c5" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1504,7 +1397,7 @@ dependencies = [ "itertools", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1516,7 +1409,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1526,7 +1419,7 @@ source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1547,70 +1440,6 @@ dependencies = [ "sp-weights", ] -[[package]] -name = "frunk" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a89c703bf50009f383a0873845357cc400a95fc535f836feddfe015d7df6e1e0" -dependencies = [ - "frunk_core", - "frunk_derives", - "frunk_proc_macros", -] - -[[package]] -name = "frunk_core" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a446d01a558301dca28ef43222864a9fa2bd9a2e71370f769d5d5d5ec9f3537" - -[[package]] -name = "frunk_derives" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b83164912bb4c97cfe0772913c7af7387ee2e00cb6d4636fb65a35b3d0c8f173" -dependencies = [ - "frunk_proc_macro_helpers", - "quote", - "syn", -] - -[[package]] -name = "frunk_proc_macro_helpers" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "015425591bbeb0f5b8a75593340f1789af428e9f887a4f1e36c0c471f067ef50" -dependencies = [ - "frunk_core", - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "frunk_proc_macros" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea01524f285deab48affffb342b97f186e657b119c3f1821ac531780e0fbfae0" -dependencies = [ - "frunk_core", - "frunk_proc_macros_impl", - "proc-macro-hack", -] - -[[package]] -name = "frunk_proc_macros_impl" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0a802d974cc18ee7fe1a7868fc9ce31086294fd96ba62f8da64ecb44e92a2653" -dependencies = [ - "frunk_core", - "frunk_proc_macro_helpers", - "proc-macro-hack", - "quote", - "syn", -] - [[package]] name = "fuchsia-cprng" version = "0.1.1" @@ -1710,7 +1539,7 @@ dependencies = [ "futures-io", "memchr", "parking", - "pin-project-lite 0.2.9", + "pin-project-lite", "waker-fn", ] @@ -1722,7 +1551,7 @@ checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -1760,14 +1589,14 @@ dependencies = [ "futures-sink", "futures-task", "memchr", - "pin-project-lite 0.2.9", + "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "galois-bin" -version = "0.5.0-dev" +version = "0.7.0-dev" dependencies = [ "anyhow", "clap 4.1.7", @@ -1777,13 +1606,13 @@ dependencies = [ "jsonrpsee 0.16.2", "lazy_static", "log", - "tokio 1.24.2", + "tokio", "tower", ] [[package]] name = "galois-engine" -version = "0.5.0-dev" +version = "0.7.0-dev" dependencies = [ "ac-node-api", "anyhow", @@ -1809,10 +1638,9 @@ dependencies = [ "lz4_flex", "magic-crypt", "memmap", - "mysql", "parity-scale-codec", "rand 0.8.5", - "redis", + "rocksdb", "rust_decimal", "rust_decimal_macros", "serde", @@ -1822,7 +1650,7 @@ dependencies = [ "sparse-merkle-tree", "structopt", "substrate-api-client", - "syn", + "syn 1.0.109", "tempdir", "thiserror", "toml", @@ -1830,7 +1658,7 @@ dependencies = [ [[package]] name = "galois-sidecar" -version = "0.5.0-dev" +version = "0.7.0-dev" dependencies = [ "anyhow", "clap 4.1.7", @@ -1856,7 +1684,7 @@ dependencies = [ "sp-keyring", "sqlx", "thiserror", - "tokio 1.24.2", + "tokio", "tokio-stream", "toml", "tower", @@ -2001,8 +1829,8 @@ dependencies = [ "http", "indexmap", "slab", - "tokio 1.24.2", - "tokio-util 0.7.7", + "tokio", + "tokio-util", "tracing", ] @@ -2150,7 +1978,7 @@ checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482" dependencies = [ "bytes 1.3.0", "fnv", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -2161,7 +1989,7 @@ checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes 1.3.0", "http", - "pin-project-lite 0.2.9", + "pin-project-lite", ] [[package]] @@ -2197,10 +2025,10 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa 1.0.5", - "pin-project-lite 0.2.9", + "itoa", + "pin-project-lite", "socket2", - "tokio 1.24.2", + "tokio", "tower-service", "tracing", "want", @@ -2217,7 +2045,7 @@ dependencies = [ "log", "rustls", "rustls-native-certs", - "tokio 1.24.2", + "tokio", "tokio-rustls", "webpki-roots", ] @@ -2230,7 +2058,7 @@ checksum = "880b8b1c98a5ec2a505c7c90db6d3f6f1f480af5655d9c5b55facc9382a5a5b5" dependencies = [ "hyper", "pin-project", - "tokio 1.24.2", + "tokio", "tokio-tungstenite", "tungstenite", ] @@ -2295,7 +2123,7 @@ checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2326,17 +2154,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "io-enum" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e4b0d47a958cb166282b4dc4840a35783e861c2b39080af846e6481ebe145eee" -dependencies = [ - "derive_utils", - "quote", - "syn", -] - [[package]] name = "io-lifetimes" version = "1.0.5" @@ -2379,15 +2196,18 @@ dependencies = [ [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" +checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" [[package]] -name = "itoa" -version = "1.0.5" +name = "jobserver" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fad582f4b9e86b6caa621cabeb0963332d92eea04729ab12892c2533951e6440" +checksum = "8c37f63953c4c63420ed5fd3d6d398c719489b9f872b9fa683262f8edd363c7d" +dependencies = [ + "libc", +] [[package]] name = "js-sys" @@ -2443,9 +2263,9 @@ dependencies = [ "rustls-native-certs", "soketto", "thiserror", - "tokio 1.24.2", + "tokio", "tokio-rustls", - "tokio-util 0.7.7", + "tokio-util", "tracing", "webpki-roots", ] @@ -2468,9 +2288,9 @@ dependencies = [ "rustls-native-certs", "soketto", "thiserror", - "tokio 1.24.2", + "tokio", "tokio-rustls", - "tokio-util 0.7.7", + "tokio-util", "tracing", "webpki-roots", ] @@ -2493,7 +2313,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "tokio 1.24.2", + "tokio", "tracing", "tracing-futures", ] @@ -2522,7 +2342,7 @@ dependencies = [ "serde_json", "soketto", "thiserror", - "tokio 1.24.2", + "tokio", "tracing", "wasm-bindgen-futures", ] @@ -2542,7 +2362,7 @@ dependencies = [ "serde", "serde_json", "thiserror", - "tokio 1.24.2", + "tokio", "tracing", ] @@ -2555,7 +2375,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2568,7 +2388,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -2586,9 +2406,9 @@ dependencies = [ "serde", "serde_json", "soketto", - "tokio 1.24.2", + "tokio", "tokio-stream", - "tokio-util 0.7.7", + "tokio-util", "tower", "tracing", ] @@ -2711,79 +2531,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" -[[package]] -name = "lexical" -version = "6.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7aefb36fd43fef7003334742cbf77b243fcd36418a1d1bdd480d613a67968f6" -dependencies = [ - "lexical-core", -] - -[[package]] -name = "lexical-core" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2cde5de06e8d4c2faabc400238f9ae1c74d5412d03a7bd067645ccbc47070e46" -dependencies = [ - "lexical-parse-float", - "lexical-parse-integer", - "lexical-util", - "lexical-write-float", - "lexical-write-integer", -] - -[[package]] -name = "lexical-parse-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "683b3a5ebd0130b8fb52ba0bdc718cc56815b6a097e28ae5a6997d0ad17dc05f" -dependencies = [ - "lexical-parse-integer", - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-parse-integer" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d0994485ed0c312f6d965766754ea177d07f9c00c9b82a5ee62ed5b47945ee9" -dependencies = [ - "lexical-util", - "static_assertions", -] - -[[package]] -name = "lexical-util" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5255b9ff16ff898710eb9eb63cb39248ea8a5bb036bea8085b1a767ff6c4e3fc" -dependencies = [ - "static_assertions", -] - -[[package]] -name = "lexical-write-float" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "accabaa1c4581f05a3923d1b4cfd124c329352288b7b9da09e766b0668116862" -dependencies = [ - "lexical-util", - "lexical-write-integer", - "static_assertions", -] - -[[package]] -name = "lexical-write-integer" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b6f3d1f4422866b68192d62f77bc5c700bee84f3069f2469d7bc8c77852446" -dependencies = [ - "lexical-util", - "static_assertions", -] - [[package]] name = "libc" version = "0.2.139" @@ -2806,6 +2553,22 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "348108ab3fba42ec82ff6e9564fc4ca0247bdccdc68dd8af9764bbc79c3c8ffb" +[[package]] +name = "librocksdb-sys" +version = "0.11.0+8.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3386f101bcb4bd252d8e9d2fb41ec3b0862a15a62b478c355b2982efa469e3e" +dependencies = [ + "bindgen", + "bzip2-sys", + "cc", + "glob", + "libc", + "libz-sys", + "lz4-sys", + "zstd-sys", +] + [[package]] name = "libsecp256k1" version = "0.7.1" @@ -2963,12 +2726,13 @@ dependencies = [ ] [[package]] -name = "lru" -version = "0.8.1" +name = "lz4-sys" +version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e8aaa3f231bb4bd57b84b2d5dc3ae7f350265df8aa96492e0bc394a1571909" +checksum = "57d27b317e207b10f69f5e75494119e391a96f48861ae870d1da6edac98ca900" dependencies = [ - "hashbrown 0.12.3", + "cc", + "libc", ] [[package]] @@ -3048,15 +2812,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - [[package]] name = "memory-db" version = "0.29.0" @@ -3144,17 +2899,6 @@ dependencies = [ "slab", ] -[[package]] -name = "mio-uds" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0" -dependencies = [ - "iovec", - "libc", - "mio 0.6.23", -] - [[package]] name = "miow" version = "0.2.2" @@ -3167,69 +2911,6 @@ dependencies = [ "ws2_32-sys", ] -[[package]] -name = "mysql" -version = "23.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f11339ca5c251941805d51362a07823605a80586ced92914ab7de84fba813f" -dependencies = [ - "bufstream", - "bytes 1.3.0", - "crossbeam", - "flate2", - "io-enum", - "libc", - "lru 0.8.1", - "mysql_common", - "named_pipe", - "native-tls", - "once_cell", - "pem", - "percent-encoding", - "serde", - "serde_json", - "socket2", - "twox-hash", - "url", -] - -[[package]] -name = "mysql_common" -version = "0.29.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9006c95034ccf7b903d955f210469119f6c3477fc9c9e7a7845ce38a3e665c2a" -dependencies = [ - "base64 0.13.1", - "bigdecimal", - "bindgen", - "bitflags", - "bitvec", - "byteorder", - "bytes 1.3.0", - "cc", - "cmake", - "crc32fast", - "flate2", - "frunk", - "lazy_static", - "lexical", - "num-bigint", - "num-traits", - "rand 0.8.5", - "regex", - "rust_decimal", - "saturating", - "serde", - "serde_json", - "sha1 0.10.5", - "sha2 0.10.6", - "smallvec 1.10.0", - "subprocess", - "thiserror", - "time 0.3.20", - "uuid", -] - [[package]] name = "nalgebra" version = "0.27.1" @@ -3256,34 +2937,7 @@ checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" dependencies = [ "proc-macro2", "quote", - "syn", -] - -[[package]] -name = "named_pipe" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad9c443cce91fc3e12f017290db75dde490d685cdaaf508d7159d7cf41f0eb2b" -dependencies = [ - "winapi 0.3.9", -] - -[[package]] -name = "native-tls" -version = "0.2.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07226173c32f2926027b63cce4bcd8076c3552846cbe7925f3aaffeac0a3b92e" -dependencies = [ - "lazy_static", - "libc", - "log", - "openssl", - "openssl-probe", - "openssl-sys", - "schannel", - "security-framework", - "security-framework-sys", - "tempfile", + "syn 1.0.109", ] [[package]] @@ -3366,7 +3020,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a652d9771a63711fd3c3deb670acfbe5c30a4072e664d7a3bf5a9e1056ac72c3" dependencies = [ "arrayvec 0.7.2", - "itoa 1.0.5", + "itoa", ] [[package]] @@ -3472,7 +3126,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3573,7 +3227,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -3598,7 +3252,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f557c32c6d268a07c921471619c0295f5efad3a0e76d4f97a05c091a51d110b2" dependencies = [ "proc-macro2", - "syn", + "syn 1.0.109", "synstructure", ] @@ -3714,15 +3368,6 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" -[[package]] -name = "pem" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8835c273a76a90455d7344889b0964598e3316e2a79ede8e36f16bdcf2228b8" -dependencies = [ - "base64 0.13.1", -] - [[package]] name = "pem-rfc7468" version = "0.3.1" @@ -3755,15 +3400,9 @@ checksum = "069bdb1e05adc7a8990dce9cc75370895fbe4e3d58b9b73bf1aee56359344a55" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] -[[package]] -name = "pin-project-lite" -version = "0.1.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "257b64915a082f7811703966789728173279bdebb956b143dbcd23f6f970a777" - [[package]] name = "pin-project-lite" version = "0.2.9" @@ -3824,6 +3463,16 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "prettyplease" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae005bd773ab59b4725093fd7df83fd7892f7d8eafb48dbd7de6e024e4215f9d" +dependencies = [ + "proc-macro2", + "syn 2.0.39", +] + [[package]] name = "primitive-types" version = "0.11.1" @@ -3865,7 +3514,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "version_check", ] @@ -3880,17 +3529,11 @@ dependencies = [ "version_check", ] -[[package]] -name = "proc-macro-hack" -version = "0.5.20+deprecated" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" - [[package]] name = "proc-macro2" -version = "1.0.50" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] @@ -3912,14 +3555,14 @@ checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -4064,30 +3707,6 @@ dependencies = [ "rand_core 0.3.1", ] -[[package]] -name = "redis" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95357caf2640abc54651b93c98a8df4fe1ccbf44b8e601ccdf43d5c1451f29ac" -dependencies = [ - "async-native-tls", - "async-std", - "async-trait", - "bytes 0.5.6", - "combine", - "dtoa", - "futures-util", - "itoa 0.4.8", - "native-tls", - "percent-encoding", - "pin-project-lite 0.1.12", - "sha1 0.6.1", - "tokio 0.2.25", - "tokio-tls", - "tokio-util 0.3.1", - "url", -] - [[package]] name = "redox_syscall" version = "0.2.16" @@ -4114,7 +3733,7 @@ checksum = "9f9c0c92af03644e4806106281fe2e068ac5bc0ae74a707266d06ea27bccee5f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4209,7 +3828,17 @@ checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", +] + +[[package]] +name = "rocksdb" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb6f170a4041d50a0ce04b0d2e14916d6ca863ea2e422689a5b694395d299ffe" +dependencies = [ + "libc", + "librocksdb-sys", ] [[package]] @@ -4337,12 +3966,6 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b4b9743ed687d4b4bcedf9ff5eaa7398495ae14e61cba0a295704edbc7decde" -[[package]] -name = "saturating" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ece8e78b2f38ec51c51f5d475df0a7187ba5111b2a28bdc761ee05b075d40a71" - [[package]] name = "scale-info" version = "2.3.1" @@ -4366,7 +3989,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4520,7 +4143,7 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4529,7 +4152,7 @@ version = "1.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "877c235533714907a8c2464236f5c4b2a17262ef1bd71f38f35ea592c8da6883" dependencies = [ - "itoa 1.0.5", + "itoa", "ryu", "serde", ] @@ -4571,15 +4194,6 @@ dependencies = [ "opaque-debug 0.3.0", ] -[[package]] -name = "sha1" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1da05c97445caa12d05e848c4a4fcbbea29e748ac28f7e80e9b010392063770" -dependencies = [ - "sha1_smol", -] - [[package]] name = "sha1" version = "0.10.5" @@ -4591,12 +4205,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "sha1_smol" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae1a47186c03a32177042e55dbc5fd5aee900b8e0069a8d70fba96a9375cd012" - [[package]] name = "sha2" version = "0.8.2" @@ -4766,7 +4374,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -4865,7 +4473,7 @@ dependencies = [ "proc-macro2", "quote", "sp-core-hashing", - "syn", + "syn 1.0.109", ] [[package]] @@ -4875,7 +4483,7 @@ source = "git+https://github.com/paritytech/substrate.git?branch=polkadot-v0.9.3 dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5026,7 +4634,7 @@ dependencies = [ "proc-macro-crate 1.3.0", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5101,7 +4709,7 @@ dependencies = [ "hash-db", "hashbrown 0.12.3", "lazy_static", - "lru 0.7.8", + "lru", "memory-db", "nohash-hasher", "parity-scale-codec", @@ -5140,7 +4748,7 @@ dependencies = [ "parity-scale-codec", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5245,7 +4853,7 @@ dependencies = [ "hashlink", "hex", "indexmap", - "itoa 1.0.5", + "itoa", "libc", "log", "memchr", @@ -5258,7 +4866,7 @@ dependencies = [ "rust_decimal", "rustls", "rustls-pemfile", - "sha1 0.10.5", + "sha1", "sha2 0.10.6", "smallvec 1.10.0", "sqlformat", @@ -5285,7 +4893,7 @@ dependencies = [ "sha2 0.10.6", "sqlx-core", "sqlx-rt", - "syn", + "syn 1.0.109", "url", ] @@ -5296,7 +4904,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "804d3f245f894e61b1e6263c84b23ca675d96753b5abfd5cc8597d86806e8024" dependencies = [ "once_cell", - "tokio 1.24.2", + "tokio", "tokio-rustls", ] @@ -5383,7 +4991,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5405,17 +5013,7 @@ dependencies = [ "proc-macro2", "quote", "rustversion", - "syn", -] - -[[package]] -name = "subprocess" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c2e86926081dda636c546d8c5e641661049d7562a68f5488be4a1f7f66f6086" -dependencies = [ - "libc", - "winapi 0.3.9", + "syn 1.0.109", ] [[package]] @@ -5478,6 +5076,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "synstructure" version = "0.12.6" @@ -5486,7 +5095,7 @@ checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "unicode-xid", ] @@ -5506,20 +5115,6 @@ dependencies = [ "remove_dir_all", ] -[[package]] -name = "tempfile" -version = "3.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" -dependencies = [ - "cfg-if 1.0.0", - "fastrand", - "libc", - "redox_syscall", - "remove_dir_all", - "winapi 0.3.9", -] - [[package]] name = "termcolor" version = "1.2.0" @@ -5555,7 +5150,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5600,31 +5195,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "time" -version = "0.3.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" -dependencies = [ - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" - -[[package]] -name = "time-macros" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" -dependencies = [ - "time-core", -] - [[package]] name = "tiny-bip39" version = "0.8.2" @@ -5659,25 +5229,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" -[[package]] -name = "tokio" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6703a273949a90131b290be1fe7b039d0fc884aa1935860dfcbe056f28cd8092" -dependencies = [ - "bytes 0.5.6", - "fnv", - "futures-core", - "iovec", - "lazy_static", - "libc", - "memchr", - "mio 0.6.23", - "mio-uds", - "pin-project-lite 0.1.12", - "slab", -] - [[package]] name = "tokio" version = "1.24.2" @@ -5691,7 +5242,7 @@ dependencies = [ "mio 0.8.6", "num_cpus", "parking_lot 0.12.1", - "pin-project-lite 0.2.9", + "pin-project-lite", "signal-hook-registry", "socket2", "tokio-macros", @@ -5706,7 +5257,7 @@ checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5716,7 +5267,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c43ee83903113e03984cb9e5cebe6c04a5116269e900e3ddba8f068a62adda59" dependencies = [ "rustls", - "tokio 1.24.2", + "tokio", "webpki", ] @@ -5727,19 +5278,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8fb52b74f05dbf495a8fba459fdc331812b96aa086d9eb78101fa0d4569c3313" dependencies = [ "futures-core", - "pin-project-lite 0.2.9", - "tokio 1.24.2", - "tokio-util 0.7.7", -] - -[[package]] -name = "tokio-tls" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a70f4fcd7b3b24fb194f837560168208f669ca8cb70d0c4b862944452396343" -dependencies = [ - "native-tls", - "tokio 0.2.25", + "pin-project-lite", + "tokio", + "tokio-util", ] [[package]] @@ -5750,24 +5291,10 @@ checksum = "54319c93411147bced34cb5609a80e0a8e44c5999c93903a81cd866630ec0bfd" dependencies = [ "futures-util", "log", - "tokio 1.24.2", + "tokio", "tungstenite", ] -[[package]] -name = "tokio-util" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499" -dependencies = [ - "bytes 0.5.6", - "futures-core", - "futures-sink", - "log", - "pin-project-lite 0.1.12", - "tokio 0.2.25", -] - [[package]] name = "tokio-util" version = "0.7.7" @@ -5778,8 +5305,8 @@ dependencies = [ "futures-core", "futures-io", "futures-sink", - "pin-project-lite 0.2.9", - "tokio 1.24.2", + "pin-project-lite", + "tokio", "tracing", ] @@ -5840,7 +5367,7 @@ checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ "cfg-if 1.0.0", "log", - "pin-project-lite 0.2.9", + "pin-project-lite", "tracing-attributes", "tracing-core", ] @@ -5853,7 +5380,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", ] [[package]] @@ -5966,7 +5493,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha1 0.10.5", + "sha1", "thiserror", "url", "utf-8", @@ -5978,9 +5505,9 @@ version = "1.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97fee6b57c6a41524a810daee9286c02d7752c4253064d0b05472833a438f675" dependencies = [ - "cfg-if 0.1.10", + "cfg-if 1.0.0", "digest 0.10.6", - "rand 0.4.6", + "rand 0.8.5", "static_assertions", ] @@ -6088,12 +5615,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" -[[package]] -name = "uuid" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" - [[package]] name = "valuable" version = "0.1.0" @@ -6183,7 +5704,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-shared", ] @@ -6217,7 +5738,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -6498,6 +6019,16 @@ checksum = "44bf07cb3e50ea2003396695d58bf46bc9887a1f362260446fad6bc4e79bd36c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.109", "synstructure", ] + +[[package]] +name = "zstd-sys" +version = "2.0.9+zstd.1.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e16efa8a874a0481a574084d34cc26fdb3b99627480f785888deb6386506656" +dependencies = [ + "cc", + "pkg-config", +] diff --git a/README.md b/README.md index 1f979d6..a60b4b4 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,45 @@ -[![SAY NO TO THE TRADEMARK POLICY ](https://gist.githubusercontent.com/blyxyas/8f17fbe1cafdeff65bbe6b332d4f4723/raw/715a24df3ad74b838c6b0ff8079d3f7f9172b0db/banner.svg)](https://github.com/blyxyas/no-rust-policy-change) # Galois [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg)](#LICENSE) [![GitHub Workflow Status (branch)](https://github.com/uinb/galois/actions/workflows/build.yml/badge.svg)](https://github.com/uinb/galois/actions?query=branch%3Amaster) - ## Introduction Galois is an extremely high performance matching engine written in Rust which uses event-sourcing pattern to handle tens of thousands of orders per second or even better, depending on the performance of persistence. -Basic architecture is shown below. ``` - core dump(disk) - ^ - ^ - +----------+ -events(mysql) >> | galois | >> match results(mysql)/best n price(redis) -will be replaced +----------+ will be replaced - ^ - ^ - query requests(TCP) - -``` - -Galois works as the prover(a.k.a Proof of Matches) component of [Fusotao](https://github.com/uinb/fusotao). From v0.4.0, we don't support running Galois in standalone mode anymore. -According to the Roadmap 2023, the UINB team is working on implementing the Proof of Order Relay which enables users to run [Fusotao Node](https://github.com/uinb/fusotao) as an order relayer(a.k.a broker) rather than supporting multiple prover instances in the network. In the near future, galois will be recoverable from anywhere by pulling the core data and sequences from [Arweave](https://arweave.org/) and under management of the FusoDAO. - -## Getting Started + sidecar chain <-+ + | | \ + | | \ + v v \ + +-----> server scanner + + | \ / | + |\ \ / | + | \ \ / | + | +-<- sequencer | + + | | + |\ | | + | \ v | + | +-<- executor | + + / \ + + \ / \ / + \ / \ / + +-<- output committer -+ -[Fusotao Docs](https://docs.fusotao.org/) +``` -### Dependencies +Galois works as the prover of [Fusotao](https://github.com/uinb/fusotao)(a.k.a Proof of Matches). From v0.4, we don't support running Galois in standalone mode anymore. -- MySQL(will be replaced with RocksDB): persist the events and output the match result -- Redis(will be removed): output the best n price of the orderbook +NOTICE: The v0.7 is still under heavy development. -### Quick Start +## Documents -TODO, or refer to the old version. +See [Fusotao Docs](https://docs.fusotao.org/). -### Instructions +## How it works -TODO, or refer to the old version. +See [Fusotao Greebook](https://www.fusotao.org/fusotao-greenbook.pdf). ## License -Galois is licensed under [Apache 2.0](LICENSE). + +[Apache 2.0](LICENSE). diff --git a/bin/Cargo.toml b/bin/Cargo.toml index 5abbfd6..9d9e3bc 100644 --- a/bin/Cargo.toml +++ b/bin/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "galois-bin" -version = "0.5.0-dev" +version = "0.7.0-dev" authors = ["UINB Technologies"] edition = "2021" license = "Apache-2.0" @@ -15,8 +15,8 @@ name = "sidecar" path = "src/sidecar.rs" [dependencies] -galois-engine = { path = "../engine" } -galois-sidecar = { path = "../sidecar" } +engine = { path = "../engine", package = "galois-engine" } +sidecar = { path = "../sidecar", package = "galois-sidecar" } jsonrpsee = { version = "0.16.2", features = ["full"] } tokio = { version = "1.16", features = ["full"] } tower = "0.4.13" diff --git a/bin/src/galois.rs b/bin/src/galois.rs index 65363e1..78e0e94 100644 --- a/bin/src/galois.rs +++ b/bin/src/galois.rs @@ -13,32 +13,74 @@ // limitations under the License. use clap::Parser; -use galois_engine::{ - config, executor, fusotao, output, sequence, server, shared::Shared, snapshot, -}; -use std::sync::{atomic, mpsc, Arc}; +use engine::*; +fn print_banner() { + println!( + r#" + ** ** + ******* ****** ** ** ****** + *** ** ** ***** ** ** * + ** ***** ** *** *** ** + ** ******* ** ** ** ** ** + ** ***** ** ** ** * * ** ***** + ** *** ** ** ** ** ** ** ** + ********* ** **** ** ******* ** ** ** + * * **** * ** *** ** ****"# + ); +} + +/// Overview: +/// +/// sidecar chain <-+ +/// | | \ +/// | | \ +/// v v \ +/// +----> server scanner + +/// | \ / | +/// |\ \ / | +/// | \ \ / | +/// | +-- sequencer | +/// + | | +/// |\ | | +/// | \ v | +/// | +-- executor | +/// | | | +/// | | | +/// | v | +/// + storage | +/// \ / \ + +/// \ / \ / +/// \ / \ / +/// +-- replyer committer -+ +/// fn start() { let (id, coredump) = snapshot::load().unwrap(); - let (output_tx, output_rx) = mpsc::channel(); - let (event_tx, event_rx) = mpsc::channel(); - let (proof_tx, proof_rx) = mpsc::channel(); - let (msg_tx, msg_rx) = mpsc::channel(); + let (connector, state) = fusotao::sync().unwrap(); + let shared = Shared::new(state.clone(), C.fusotao.get_x25519()); + let (output_tx, output_rx) = std::sync::mpsc::channel(); + let (event_tx, event_rx) = std::sync::mpsc::channel(); + let (input_tx, input_rx) = std::sync::mpsc::channel(); + let (reply_tx, reply_rx) = std::sync::mpsc::channel(); output::init(output_rx); - let fuso = fusotao::init(proof_rx); - let shared = Shared::new(fuso.state, config::C.fusotao.get_x25519_prikey().unwrap()); - executor::init(event_rx, output_tx, proof_tx, msg_tx, coredump); - let ready = Arc::new(atomic::AtomicBool::new(false)); - sequence::init(event_tx.clone(), id, ready.clone()); - server::init(event_tx, msg_rx, shared, ready); + committer::init(connector.clone(), state.clone()); + executor::init(event_rx, output_tx, reply_tx.clone(), coredump); + sequencer::init(input_rx, event_tx, reply_tx, id); + scanner::init(input_tx.clone(), connector, state); + server::init(reply_rx, input_tx, shared); } fn main() { + env_logger::init(); let opts = config::GaloisCli::parse(); match opts.sub { Some(config::SubCmd::EncryptConfig) => config::print_config(&opts.file).unwrap(), None => { - lazy_static::initialize(&config::C); + print_banner(); + lazy_static::initialize(&C); + if C.dry_run.is_some() { + log::info!("running in dry-run mode"); + } start(); } } diff --git a/bin/src/sidecar.rs b/bin/src/sidecar.rs index 0a7f607..237af6e 100644 --- a/bin/src/sidecar.rs +++ b/bin/src/sidecar.rs @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use galois_sidecar::*; +use sidecar::*; #[tokio::main] async fn main() { diff --git a/engine/Cargo.toml b/engine/Cargo.toml index 49ac4fa..4491896 100644 --- a/engine/Cargo.toml +++ b/engine/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "galois-engine" -version = "0.5.0-dev" +version = "0.7.0-dev" authors = ["UINB Technologies"] edition = "2021" license = "Apache-2.0" @@ -14,13 +14,14 @@ serde = { version = "1.0", features = ["derive"] } hashbrown = "0.13.2" async-trait = "0.1.63" serde_json = "1.0" +rocksdb = "0.21" flate2 = { version = "1.0", features = ["zlib"], default-features = false } -mysql = "23.0" -redis = { version = "0.17", features = ["tls", "tokio-rt-core", "tokio-tls-comp", "native-tls","async-native-tls", "async-std-tls-comp"] } +# mysql = "23.0" +# redis = { version = "0.17", features = ["tls", "tokio-rt-core", "tokio-tls-comp", "native-tls","async-native-tls", "async-std-tls-comp"] } toml = "0.5" lazy_static = "1.4" linked-hash-map = { version = "0.5.3", features = ["serde_impl"] } -async-std = { version = "1.12", default-features = false, features = ["std", "attributes", "tokio1"] } +async-std = { version = "1.12", default-features = false, features = ["std", "attributes", "tokio1", "default"] } futures = "0.3" chashmap = "2.2" syn = "1.0.107" @@ -44,8 +45,6 @@ rand = "0.8.5" smt = { git = "https://github.com/uinb/sparse-merkle-tree", tag = "v0.1.8", package = "sparse-merkle-tree", features = ["serde-rs", "blake2b"] } sub-api = { package = "substrate-api-client", git = "https://github.com/uinb/fusotao-rust-client.git", branch = "master" } node-api = { package = "ac-node-api", git = "https://github.com/uinb/fusotao-rust-client.git", branch = "master" } -#sub-api = { path = "../fusotao-rust-client", package = "substrate-api-client" } -#node-api = { path = "../fusotao-rust-client/node-api", package = "ac-node-api" } parity-scale-codec = { version = "3", features = ["derive"] } sp-core = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.30", package = "sp-core" } sp-runtime = { git = "https://github.com/paritytech/substrate.git", branch = "polkadot-v0.9.30", package = "sp-runtime" } diff --git a/engine/src/config.rs b/engine/src/config.rs index 2307a68..b230830 100644 --- a/engine/src/config.rs +++ b/engine/src/config.rs @@ -13,21 +13,10 @@ // limitations under the License. use clap::Parser; -use log4rs::config::{Logger, RawConfig as LogConfig}; use serde::{Deserialize, Serialize}; #[derive(Debug, Parser)] -#[command(author, version, about = r#" - ** ** - ******* ****** ** ** ****** - *** ** ** ***** ** ** * - ** ***** ** *** *** ** - ** ******* ** ** ** ** ** - ** ***** ** ** ** * * ** ***** - ** *** ** ** ** ** ** ** ** - ********* ** **** ** ******* ** ** ** - * * **** * ** *** ** ****"#, -long_about = None)] +#[command(author, version)] pub struct GaloisCli { #[arg(short('c'), long("config"), required = true, value_name = "FILE")] pub file: std::path::PathBuf, @@ -60,12 +49,8 @@ pub struct RunCmd { pub struct Config { pub server: ServerConfig, pub sequence: SequenceConfig, - pub mysql: MysqlConfig, - pub redis: RedisConfig, pub fusotao: FusotaoConfig, #[serde(skip_serializing)] - pub log: LogConfig, - #[serde(skip_serializing)] pub dry_run: Option, } @@ -77,15 +62,22 @@ pub trait EncryptedConfig { #[derive(Debug, Deserialize, Serialize, Clone)] pub struct ServerConfig { pub bind_addr: String, + pub data_home: String, +} + +impl ServerConfig { + pub fn get_coredump_path(&self) -> String { + format!("{}/coredump/", self.data_home) + } + + pub fn get_storage_path(&self) -> String { + format!("{}/storage/", self.data_home) + } } #[derive(Debug, Deserialize, Serialize, Clone)] pub struct SequenceConfig { - pub coredump_dir: String, - pub checkpoint: usize, - pub batch_size: usize, - pub dump_mode: String, - pub fetch_intervel_ms: u64, + pub checkpoint: u64, pub enable_from_genesis: bool, } @@ -118,13 +110,12 @@ pub struct FusotaoConfig { pub key_seed: String, pub claim_block: u32, pub proof_batch_limit: usize, - pub compress_proofs: bool, pub x25519_priv: String, } impl FusotaoConfig { - pub fn get_x25519_prikey(&self) -> anyhow::Result { - Ok(self.x25519_priv.clone()) + pub fn get_x25519(&self) -> String { + self.x25519_priv.clone() } } @@ -202,8 +193,6 @@ pub fn print_config(f: &std::path::PathBuf) -> anyhow::Result<()> { .ok_or(anyhow::anyhow!("env MAGIC_KEY not set"))?; let toml = std::fs::read_to_string(f)?; let mut cfg: Config = toml::from_str(&toml)?; - cfg.mysql.encrypt(&key)?; - cfg.redis.encrypt(&key)?; cfg.fusotao.encrypt(&key)?; println!("{}", toml::to_string(&cfg)?); Ok(()) @@ -212,68 +201,29 @@ pub fn print_config(f: &std::path::PathBuf) -> anyhow::Result<()> { fn init_config(toml: &str, key: Option) -> anyhow::Result { let mut cfg: Config = toml::from_str(toml)?; if let Some(key) = key { - cfg.mysql.decrypt(&key)?; - cfg.redis.decrypt(&key)?; cfg.fusotao.decrypt(&key)?; } - // TODO replace with env_log - let mut loggers = cfg - .log - .loggers() - .iter() - .map(|l| (l.name().to_string(), l.clone())) - .collect::>(); - loggers - .entry("ws".to_string()) - .or_insert_with(|| Logger::builder().build("ws".to_string(), log::LevelFilter::Error)); - loggers.entry("ac_node_api".to_string()).or_insert_with(|| { - Logger::builder().build("ac_node_api".to_string(), log::LevelFilter::Error) - }); - loggers - .entry("fusotao_rust_client".to_string()) - .or_insert_with(|| { - Logger::builder().build("fusotao_rust_client".to_string(), log::LevelFilter::Error) - }); - let log = log4rs::Config::builder() - .loggers::>(loggers.into_values().collect()) - .appenders(cfg.log.appenders_lossy(&Default::default()).0) - .build(cfg.log.root())?; - log4rs::init_config(log)?; Ok(cfg) } #[test] -pub fn test_default() { +pub fn test_config() { let toml = r#" [server] bind_addr = "127.0.0.1:8097" - [mysql] - url = "mysql://username:password@localhost:3306/galois" - [redis] - url = "redis://localhost:6379/0" + data_home = "/tmp/galois" + [sequence] checkpoint = 100000 - coredump_dir = "/tmp/snapshot" - batch_size = 1000 - dump_mode = "disk" - fetch_intervel_ms = 5 enable_from_genesis = true - [log] - [log.appenders.console] - kind = "console" - [log.root] - level = "info" - appenders = ["console"] + [fusotao] node_url = "ws://localhost:9944" key_seed = "//Alice" x25519_priv = "0xedcff0c69e4c0fa7e9a36e2e6d07f2cc355c8d25907a0ad2ab7e03b24f8e90f3" proof_batch_limit = 20 claim_block = 1 - compress_proofs = true - fee_adjust_threshold = 1000 "#; - let config = init_config(&toml, None).unwrap(); - let mysql_opts = mysql::Opts::from_url(&config.mysql.url).unwrap(); - assert_eq!("password", mysql_opts.get_pass().unwrap()); + let config = init_config(&toml, None); + assert!(config.is_ok()); } diff --git a/engine/src/core.rs b/engine/src/core.rs index 49be50f..07d7237 100644 --- a/engine/src/core.rs +++ b/engine/src/core.rs @@ -14,11 +14,12 @@ pub use crate::{ assets::Balance, + fusotao::GlobalStates, + input::InOrOut, matcher::{Role, State as OrderState}, orderbook::AskOrBid, - sequence::InOrOut, + orderbook::OrderBook, }; -use crate::{fusotao::GlobalStates, orderbook::OrderBook}; use flate2::{read::ZlibDecoder, write::ZlibEncoder, Compression}; use indexmap::IndexSet; use rust_decimal::{prelude::Zero, Decimal}; @@ -30,6 +31,10 @@ use std::{ io::{BufReader, BufWriter}, }; +lazy_static::lazy_static! { + pub static ref STORAGE: rocksdb::DB = rocksdb::DB::open_default(&crate::C.server.get_storage_path()).unwrap(); +} + pub type Base = u32; pub type Quote = u32; pub type Price = Decimal; @@ -158,16 +163,34 @@ pub fn max_number() -> Amount { // we only keep the last 1000 transfer_in/out receipts to remove duplicates const RECEIPTS_RECORDS_CAPACITY: usize = 1000; +const MAX_OPEN_ORDERS_PER_SYMBOL: usize = 100; + +#[derive(Clone, Debug)] +pub struct PendingOrder { + order_id: u64, + symbol: Symbol, + direction: u8, + create_timestamp: u64, + amount: String, + price: String, + status: u8, + matched_quote_amount: String, + matched_base_amount: String, + base_fee: Decimal, + quote_fee: Decimal, +} #[derive(Clone, Debug)] pub struct Ephemeral { onchain_receipt_records: IndexSet<(u32, UserId)>, + user_pending_orders: HashMap<(Symbol, UserId), HashMap>, } impl Ephemeral { pub fn new() -> Self { Self { onchain_receipt_records: IndexSet::with_capacity(RECEIPTS_RECORDS_CAPACITY), + user_pending_orders: HashMap::new(), } } diff --git a/engine/src/db.rs b/engine/src/db.rs deleted file mode 100644 index 54be231..0000000 --- a/engine/src/db.rs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright 2021-2023 UINB Technologies Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::config::C; - -lazy_static::lazy_static! { - pub static ref DB: mysql::Pool = - mysql::Pool::new(mysql::Opts::from_url(&C.mysql.url).unwrap()).unwrap(); - pub static ref REDIS: redis::Client = redis::Client::open((&C.redis.url).to_string()).unwrap(); -} diff --git a/engine/src/executor/assets.rs b/engine/src/executor/assets.rs index c8b5e37..398084b 100644 --- a/engine/src/executor/assets.rs +++ b/engine/src/executor/assets.rs @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::{core::*, orderbook::*}; use anyhow::{anyhow, ensure}; use rust_decimal::prelude::Zero; use serde::{Deserialize, Serialize}; -use crate::{core::*, orderbook::AskOrBid}; - #[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Default)] pub struct Balance { pub available: Amount, diff --git a/engine/src/executor/clearing.rs b/engine/src/executor/clearing.rs index 0326c9d..f622e41 100644 --- a/engine/src/executor/clearing.rs +++ b/engine/src/executor/clearing.rs @@ -12,8 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -use rust_decimal::{prelude::Zero, Decimal}; - use crate::{ assets, core::*, @@ -21,6 +19,7 @@ use crate::{ orderbook::AskOrBid, output::Output, }; +use rust_decimal::{prelude::Zero, Decimal}; pub fn clear( accounts: &mut Accounts, diff --git a/engine/src/executor/mod.rs b/engine/src/executor/mod.rs index 14b5fd4..1889113 100644 --- a/engine/src/executor/mod.rs +++ b/engine/src/executor/mod.rs @@ -19,86 +19,74 @@ pub mod orderbook; use crate::{ core::*, - fusotao::{Proof, Prover}, - input::{Event, EventsError, Input, Inspection, Message}, + input::{Event, Message}, orderbook::*, - output::{self, Output}, - sequence, snapshot, + output::Output, + prover, snapshot, }; use anyhow::anyhow; use rust_decimal::{prelude::*, Decimal}; use std::{ collections::HashMap, - convert::TryInto, sync::mpsc::{Receiver, Sender}, }; +use thiserror::Error; -type EventExecutionResult = Result<(), EventsError>; type OutputChannel = Sender>; -type DriverChannel = Receiver; -type ProofChannel = Sender; -type BackToServer = Sender<(u64, Message)>; +type DriverChannel = Receiver; +type ResponseChannel = Sender<(u64, Message)>; -pub fn init( - recv: DriverChannel, - sender: OutputChannel, - proofs: ProofChannel, - messages: BackToServer, - mut data: Data, -) { +#[derive(Debug, Error)] +pub enum EventsError { + #[error("event {0} interrupted")] + Interrupted(u64), + /// event id, session id, request id, error + #[error("event {0} executed failed, {3}")] + EventRejected(u64, u64, u64, anyhow::Error), + /// event id, error + #[error("event {0} executed failed, {1}")] + EventIgnored(u64, anyhow::Error), +} + +pub type ExecutionResult = Result<(), EventsError>; + +pub fn init(recv: DriverChannel, output: OutputChannel, response: ResponseChannel, mut data: Data) { std::thread::spawn(move || { - let prover = Prover::new(proofs); let mut ephemeral = Ephemeral::new(); log::info!("executor initialized"); loop { - let fusion = recv.recv().unwrap(); - match fusion { - Input::NonModifier(whistle) => { - let (s, r) = (whistle.session, whistle.req_id); - if let Ok(inspection) = whistle.try_into() { - do_inspect(inspection, &data, &messages).unwrap(); - } else { - let r = messages.send((s, Message::new(r, vec![]))); - log::error!("{:?}", r); - } + let event = recv.recv().unwrap(); + match do_execute(event, &mut data, &mut ephemeral, &output, &response) { + Ok(_) => {} + Err(EventsError::EventRejected(id, session, req_id, e)) => { + log::debug!("event {} rejected: {}", id, e); + let msg = serde_json::json!({"error": e.to_string()}); + let v = serde_json::to_vec(&msg).unwrap_or_default(); + let _ = response.send((session, Message::new(req_id, v))); } - Input::Modifier(seq) => { - let id = seq.id; - match seq.try_into() { - Ok(event) => { - let r = do_event(event, &mut data, &mut ephemeral, &sender, &prover); - match r { - Err(EventsError::EventRejected(id, msg)) => { - log::info!("Error occur in sequence {}: {:?}", id, msg); - sequence::update_sequence_status(id, sequence::ERROR).unwrap(); - } - Err(EventsError::Interrupted) => { - panic!("sequence thread panic"); - } - Ok(()) => {} - } - } - Err(e) => { - log::info!("Error occur in sequence {}: {:?}", id, e); - sequence::update_sequence_status(id, sequence::ERROR).unwrap(); - } - } + Err(EventsError::EventIgnored(id, e)) => { + log::info!("event {} ignored: {}", id, e); + } + Err(EventsError::Interrupted(id)) => { + log::info!("executor thread interrupted at {}", id); + break; } } } }); } -fn do_event( +// TODO update pending orders +fn do_execute( event: Event, data: &mut Data, ephemeral: &mut Ephemeral, - sender: &OutputChannel, - prover: &Prover, -) -> EventExecutionResult { - data.current_event_id = event.get_id(); + output: &OutputChannel, + response: &ResponseChannel, +) -> ExecutionResult { match event { - Event::Limit(id, cmd, time) => { + Event::Limit(id, cmd, time, session, req_id) => { + data.current_event_id = id; let orderbook = data .orderbooks .get_mut(&cmd.symbol) @@ -106,6 +94,8 @@ fn do_event( .filter(|b| b.find_order(cmd.order_id).is_none()) .ok_or(EventsError::EventRejected( id, + session, + req_id, anyhow!("order can't be accepted"), ))?; log::debug!( @@ -121,7 +111,7 @@ fn do_event( assets::get_balance_to_owned(&data.accounts, &cmd.user_id, cmd.symbol.1); let (c, val) = assets::freeze_if(&cmd.symbol, cmd.ask_or_bid, cmd.price, cmd.amount); assets::try_freeze(&mut data.accounts, &cmd.user_id, c, val) - .map_err(|e| EventsError::EventRejected(id, e))?; + .map_err(|e| EventsError::EventRejected(id, session, req_id, e))?; let mr = matcher::execute_limit( orderbook, cmd.user_id, @@ -140,7 +130,7 @@ fn do_event( time, ); let (maker_fee, taker_fee) = (orderbook.maker_fee, orderbook.taker_fee); - prover.prove_trade_cmd( + let proof = prover::prove_trade_cmd( data, cmd.nonce, cmd.signature.clone(), @@ -154,10 +144,13 @@ fn do_event( &out, &mr, ); - sender.send(out).map_err(|_| EventsError::Interrupted)?; - Ok(()) + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; + output.send(out).map_err(|_| EventsError::Interrupted(id)) } - Event::Cancel(id, cmd, time) => { + Event::Cancel(id, cmd, time, session, req_id) => { + data.current_event_id = id; // 0. symbol exsits // 1. check order's owner let orderbook = @@ -165,12 +158,19 @@ fn do_event( .get_mut(&cmd.symbol) .ok_or(EventsError::EventRejected( id, - anyhow!("orderbook not exists"), + session, + req_id, + anyhow!("orderbook not found"), ))?; orderbook .find_order(cmd.order_id) .filter(|o| o.user == cmd.user_id) - .ok_or(EventsError::EventRejected(id, anyhow!("order not exists")))?; + .ok_or(EventsError::EventRejected( + id, + session, + req_id, + anyhow!("order doesn't exist"), + ))?; log::debug!( "predicate root=0x{} before applying {}", hex::encode(data.merkle_tree.root()), @@ -182,9 +182,12 @@ fn do_event( assets::get_balance_to_owned(&data.accounts, &cmd.user_id, cmd.symbol.0); let taker_quote_before = assets::get_balance_to_owned(&data.accounts, &cmd.user_id, cmd.symbol.1); - - let mr = matcher::cancel(orderbook, cmd.order_id) - .ok_or(EventsError::EventRejected(id, anyhow!("")))?; + let mr = matcher::cancel(orderbook, cmd.order_id).ok_or(EventsError::EventRejected( + id, + session, + req_id, + anyhow!("order doesn't exist"), + ))?; let out = clearing::clear( &mut data.accounts, id, @@ -194,7 +197,7 @@ fn do_event( &mr, time, ); - prover.prove_trade_cmd( + let proof = prover::prove_trade_cmd( data, cmd.nonce, cmd.signature.clone(), @@ -208,40 +211,15 @@ fn do_event( &out, &mr, ); - sender.send(out).map_err(|_| EventsError::Interrupted)?; - Ok(()) - } - Event::CancelAll(..) => { - // let orderbook = data - // .orderbooks - // .get_mut(&symbol) - // .ok_or(EventsError::EventRejected( - // id, - // anyhow!("orderbook not exists"), - // ))?; - // let ids = orderbook.indices.keys().copied().collect::>(); - // let matches = ids - // .into_iter() - // .filter_map(|id| matcher::cancel(orderbook, id)) - // .collect::>(); - // let (taker_fee, maker_fee) = (orderbook.taker_fee, orderbook.maker_fee); - // matches.iter().for_each(|mr| { - // let out = clearing::clear( - // &mut data.accounts, - // id, - // &symbol, - // taker_fee, - // maker_fee, - // mr, - // time, - // ); - // sender.send(out).unwrap(); - // }); - Ok(()) + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; + output.send(out).map_err(|_| EventsError::Interrupted(id)) } - Event::TransferOut(id, cmd, _) => { + Event::TransferOut(id, cmd) => { + data.current_event_id = id; if !ephemeral.save_receipt((cmd.block_number, cmd.user_id)) { - return Err(EventsError::EventRejected( + return Err(EventsError::EventIgnored( id, anyhow!("Duplicated transfer_out extrinsic"), )); @@ -253,9 +231,12 @@ fn do_event( ); let before = assets::get_balance_to_owned(&data.accounts, &cmd.user_id, cmd.currency); if data.tvl < cmd.amount { - prover.prove_cmd_rejected(&mut data.merkle_tree, id, cmd, &before); + let proof = prover::prove_cmd_rejected(&mut data.merkle_tree, id, cmd, &before); log::error!("TVL less than transfer_out amount, event={}", id); - return Err(EventsError::EventRejected(id, anyhow!("LessThanTVL"))); + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; + return Err(EventsError::EventIgnored(id, anyhow!("TVL not enough"))); } match assets::deduct_available( &mut data.accounts, @@ -265,18 +246,26 @@ fn do_event( ) { Ok(after) => { data.tvl -= cmd.amount; - prover.prove_assets_cmd(&mut data.merkle_tree, id, cmd, &before, &after); + let proof = + prover::prove_assets_cmd(&mut data.merkle_tree, id, cmd, &before, &after); + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; Ok(()) } Err(e) => { - prover.prove_cmd_rejected(&mut data.merkle_tree, id, cmd, &before); - Err(EventsError::EventRejected(id, e)) + let proof = prover::prove_cmd_rejected(&mut data.merkle_tree, id, cmd, &before); + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; + Err(EventsError::EventIgnored(id, e)) } } } - Event::TransferIn(id, cmd, _) => { + Event::TransferIn(id, cmd) => { + data.current_event_id = id; if !ephemeral.save_receipt((cmd.block_number, cmd.user_id)) { - return Err(EventsError::EventRejected( + return Err(EventsError::EventIgnored( id, anyhow!("Duplicated transfer_in extrinsic"), )); @@ -284,9 +273,13 @@ fn do_event( if data.tvl + cmd.amount >= crate::core::max_number() { let before = assets::get_balance_to_owned(&data.accounts, &cmd.user_id, cmd.currency); - prover.prove_rejecting_no_reason(&mut data.merkle_tree, id, cmd, &before); + let proof = + prover::prove_rejecting_no_reason(&mut data.merkle_tree, id, cmd, &before); + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; log::error!("TVL out of limit, event={}", id); - return Err(EventsError::EventRejected(id, anyhow!("TVLOutOfLimit"))); + return Err(EventsError::EventIgnored(id, anyhow!("TVL out of limit"))); } log::debug!( "predicate root=0x{} before applying {}", @@ -300,12 +293,16 @@ fn do_event( cmd.currency, cmd.amount, ) - .map_err(|e| EventsError::EventRejected(id, e))?; + .map_err(|e| EventsError::EventIgnored(id, e))?; data.tvl = data.tvl + cmd.amount; - prover.prove_assets_cmd(&mut data.merkle_tree, id, cmd, &before, &after); + let proof = prover::prove_assets_cmd(&mut data.merkle_tree, id, cmd, &before, &after); + prover::save_proof(proof) + .inspect_err(|e| log::error!("{}", e)) + .map_err(|_| EventsError::Interrupted(id))?; Ok(()) } - Event::UpdateSymbol(_, cmd, _) => { + Event::UpdateSymbol(id, cmd) => { + data.current_event_id = id; if !data.orderbooks.contains_key(&cmd.symbol) { let orderbook = OrderBook::new( cmd.base_scale, @@ -337,35 +334,29 @@ fn do_event( } Ok(()) } - } -} - -fn do_inspect( - inspection: Inspection, - data: &Data, - messages: &BackToServer, -) -> EventExecutionResult { - match inspection { - Inspection::QueryOrder(symbol, order_id, session, req_id) => { + Event::QueryOrder(symbol, order_id, session, req_id) => { let v = match data.orderbooks.get(&symbol) { Some(orderbook) => orderbook.find_order(order_id).map_or(vec![], |order| { serde_json::to_vec(order).unwrap_or_default() }), None => vec![], }; - let _ = messages.send((session, Message::new(req_id, v))); + let _ = response.send((session, Message::new(req_id, v))); + Ok(()) } - Inspection::QueryBalance(user_id, currency, session, req_id) => { + Event::QueryBalance(user_id, currency, session, req_id) => { let a = assets::get_balance_to_owned(&data.accounts, &user_id, currency); let v = serde_json::to_vec(&a).unwrap_or_default(); - let _ = messages.send((session, Message::new(req_id, v))); + let _ = response.send((session, Message::new(req_id, v))); + Ok(()) } - Inspection::QueryAccounts(user_id, session, req_id) => { + Event::QueryAccounts(user_id, session, req_id) => { let a = assets::get_account_to_owned(&data.accounts, &user_id); let v = serde_json::to_vec(&a).unwrap_or_default(); - let _ = messages.send((session, Message::new(req_id, v))); + let _ = response.send((session, Message::new(req_id, v))); + Ok(()) } - Inspection::QueryExchangeFee(symbol, session, req_id) => { + Event::QueryExchangeFee(symbol, session, req_id) => { let mut v: HashMap = HashMap::new(); let orderbook = data.orderbooks.get(&symbol); match orderbook { @@ -379,22 +370,13 @@ fn do_inspect( } } let v = serde_json::to_vec(&v).unwrap_or_default(); - let _ = messages.send((session, Message::new(req_id, v))); - } - Inspection::UpdateDepth => { - let writing = data - .orderbooks - .iter() - .map(|(k, v)| v.as_depth(32, *k)) - .collect::>(); - output::write_depth(writing); - } - Inspection::ConfirmAll(from, exclude) => { - sequence::confirm(from, exclude).map_err(|_| EventsError::Interrupted)?; + let _ = response.send((session, Message::new(req_id, v))); + Ok(()) } - Inspection::Dump(id, time) => { - snapshot::dump(id, time, data); + Event::Dump(id) => { + // TODO erase some old events after dump + snapshot::dump(id, data); + Ok(()) } } - Ok(()) } diff --git a/engine/src/fusotao/committer.rs b/engine/src/fusotao/committer.rs new file mode 100644 index 0000000..08f49a0 --- /dev/null +++ b/engine/src/fusotao/committer.rs @@ -0,0 +1,114 @@ +// Copyright 2021-2023 UINB Technologies Pte. Ltd. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{config::C, fusotao::*}; +use sp_core::Pair; +use std::time::Duration; + +/// since we won't wait for the proofs to be `Finalized`, we must add a watchdog to revert `proved_event_id` +pub fn init(connector: FusoConnector, progress: Arc) { + if C.dry_run.is_some() { + return; + } + let progress = progress.proved_event_id.clone(); + loop { + let proved_id = progress.load(Ordering::Relaxed); + let v = prover::fetch_raw_ge(proved_id + 1); + if v.is_empty() { + break; + } + match submit(&connector, v, true) { + Ok(n) if n > proved_id => { + progress.store(n, Ordering::Relaxed); + } + Ok(n) => { + log::error!("expecting proof {} to be verified but failed", n); + panic!("initializing committer failed"); + } + Err(e) => { + log::error!("submitting proofs failed due to {}, retrying...", e); + } + } + } + let local = progress.clone(); + let conn = connector.clone(); + std::thread::spawn(move || -> anyhow::Result<()> { + loop { + let id = local.load(Ordering::Relaxed); + let v = prover::fetch_raw_ge(id + 1); + if v.is_empty() { + std::thread::sleep(Duration::from_millis(3000)); + continue; + } + match submit(&conn, v, false) { + Ok(n) => local.store(n, Ordering::Relaxed), + Err(e) => { + log::error!("submitting proofs failed due to {}, retrying...", e); + } + } + } + }); + std::thread::spawn(move || -> anyhow::Result<()> { + loop { + std::thread::sleep(Duration::from_secs(60)); + if let Ok(remote) = connector.sync_progress() { + let local = progress.load(Ordering::Relaxed); + if remote < local { + progress.store(remote, Ordering::Relaxed); + } + let _ = prover::remove_before(remote); + } + } + }); +} + +fn compress_proofs(raws: Vec) -> Vec { + let r = raws.encode(); + let origin_size = r.len(); + let compressed_proofs = lz4_flex::compress_prepend_size(r.as_ref()); + let compressed_size = compressed_proofs.len(); + log::info!( + "compressing proofs: origin size = {}, compressed size = {}", + origin_size, + compressed_size + ); + compressed_proofs +} + +fn submit( + connector: &FusoConnector, + batch: Vec<(u64, RawParameter)>, + finalized: bool, +) -> anyhow::Result { + anyhow::ensure!(!batch.is_empty(), "empty batch is not allowed"); + let (id, proofs): (Vec, Vec) = batch.into_iter().unzip(); + log::debug!("submitting proofs at {}", chrono::Local::now()); + let payload: sub_api::UncheckedExtrinsicV4<_> = sub_api::compose_extrinsic!( + connector.api, + "Verifier", + "verify_compress_v2", + compress_proofs(proofs) + ); + if finalized { + connector + .api + .send_extrinsic(payload.hex_encode(), sub_api::XtStatus::Finalized)?; + connector.sync_progress() + } else { + connector + .api + .send_extrinsic(payload.hex_encode(), sub_api::XtStatus::InBlock)?; + Ok(id.last().copied().unwrap_or_default()) + } +} diff --git a/engine/src/fusotao/connector.rs b/engine/src/fusotao/connector.rs index 5176caf..f78b7d0 100644 --- a/engine/src/fusotao/connector.rs +++ b/engine/src/fusotao/connector.rs @@ -13,23 +13,22 @@ // limitations under the License. use super::*; -use crate::{config::C, input::Command, sequence}; +use crate::config::C; use anyhow::anyhow; -use chrono::Local; -use node_api::decoder::{Raw, RuntimeDecoder, StorageHasher}; +use node_api::decoder::{RuntimeDecoder, StorageHasher}; use parity_scale_codec::{Decode, Error as CodecError}; use sp_core::{sr25519::Public, Pair}; -use std::{sync::atomic::Ordering, time::Duration}; +use std::sync::atomic::Ordering; use sub_api::{rpc::WsRpcClient, Hash}; +#[derive(Clone)] pub struct FusoConnector { pub api: FusoApi, pub signer: Sr25519Key, - pub state: Arc, } impl FusoConnector { - pub fn new(dry_run: bool) -> anyhow::Result { + pub fn new() -> anyhow::Result { let signer = Sr25519Key::from_string(&C.fusotao.key_seed, None) .map_err(|e| anyhow!("invalid fusotao config: {:?}", e))?; let client = WsRpcClient::new(&C.fusotao.node_url); @@ -37,125 +36,63 @@ impl FusoConnector { .map(|api| api.set_signer(signer.clone())) .inspect_err(|e| log::error!("{:?}", e)) .map_err(|_| anyhow!("fusotao node not available or metadata check failed."))?; - let state = if dry_run { - Arc::new(Default::default()) - } else { - let (block_number, hash) = Self::get_finalized_block(&api)?; - let state = Arc::new(Self::fully_sync_chain( - &signer.public(), - &api, - hash, - block_number, - )?); - Self::start_submitting(api.clone(), state.proved_event_id.clone()); - Self::start_scanning(api.clone(), signer.public().clone(), state.clone()); - state - }; - Ok(Self { api, signer, state }) + Ok(Self { api, signer }) } - fn start_submitting(api: FusoApi, proving_progress: Arc) { - let api = api.clone(); - log::info!( - "submitting proofs from {}", - proving_progress.load(Ordering::Relaxed) - ); - std::thread::spawn(move || loop { - let start_from = proving_progress.load(Ordering::Relaxed); - let new_max_submitted = std::panic::catch_unwind(|| -> u64 { - let (end_to, truncated) = Self::fetch_proofs(start_from); - if start_from == end_to { - return end_to; - } - log::info!("[+] unsubmitted proofs [{}:{}] found", start_from, end_to); - let submit_result = Self::submit_batch(&api, truncated); - Self::handle_submit_result(submit_result, (start_from, end_to)) - }) - .unwrap_or(start_from); - if start_from == new_max_submitted { - std::thread::sleep(Duration::from_millis(1000)); - continue; - } - proving_progress.store(new_max_submitted, Ordering::Relaxed); - }); + pub fn get_pubkey(&self) -> Public { + self.signer.public().clone() } - fn start_scanning(api: FusoApi, signer: Public, fuso_state: Arc) { - let decoder = RuntimeDecoder::new(api.metadata.clone()); - log::info!( - "scanning blocks from {}", - fuso_state.scanning_progress.load(Ordering::Relaxed) - ); - std::thread::spawn(move || loop { - let at = fuso_state.scanning_progress.load(Ordering::Relaxed); - if let Ok((finalized, _)) = Self::get_finalized_block(&api) { - log::info!( - "[*] block {} finalized, current scanning progress={}", - finalized, - at - ); - fuso_state.chain_height.store(finalized, Ordering::Relaxed); - if finalized >= at { - match Self::handle_finalized_block(&api, &signer, at, &decoder, &fuso_state) { - Ok(()) => { - fuso_state.scanning_progress.fetch_add(1, Ordering::Relaxed); - log::info!("[*] handled block {}", at); - } - Err(e) => log::error!("[*] {:?}", e), - } - } else { - std::thread::sleep(Duration::from_millis(6000)); - } - } else { - log::error!("[*] scanning connection temporarily unavailable, retrying..."); - std::thread::sleep(Duration::from_millis(1000)); - } - }); - } - - fn fully_sync_chain( - who: &Public, - api: &FusoApi, - hash: Hash, - util: u32, - ) -> anyhow::Result { - let state = FusoState::default(); - let decoder = RuntimeDecoder::new(api.metadata.clone()); - - // proving progress, map AccountId -> Dominator - let key = api - .metadata - .storage_map_key::("Verifier", "Dominators", *who)?; - let payload = api + pub fn sync_progress(&self) -> anyhow::Result { + let (_, hash) = self.get_finalized_block()?; + let key = self.api.metadata.storage_map_key::( + "Verifier", + "Dominators", + self.get_pubkey(), + )?; + let payload = self + .api .get_opaque_storage_by_key_hash(key, Some(hash))? - .ok_or(anyhow!(""))?; + .ok_or(anyhow!("{} isn't the prover", self.get_pubkey()))?; let result = Dominator::decode(&mut payload.as_slice())?; - state - .proved_event_id - .store(result.sequence.0, Ordering::Relaxed); + Ok(result.sequence.0) + } + + pub fn fully_sync_chain(&self, state: Arc) -> anyhow::Result> { + let (block, hash) = self.get_finalized_block()?; + let decoder = RuntimeDecoder::new(self.api.metadata.clone()); // market list, double map AccountId, Symbol -> Market - let key = api + let key = self + .api .metadata - .storage_double_map_partial_key::("Market", "Markets", who)?; - let payload = api + .storage_double_map_partial_key::( + "Market", + "Markets", + &self.get_pubkey(), + )?; + let payload = self + .api .get_opaque_storage_pairs_by_key_hash(key, Some(hash))? .ok_or(anyhow!(""))?; for (k, v) in payload.into_iter() { let symbol = RuntimeDecoder::extract_double_map_identifier::<(u32, u32), FusoAccountId>( StorageHasher::Blake2_128Concat, StorageHasher::Blake2_128Concat, - who, + &self.get_pubkey(), &mut k.as_slice(), )?; let market = OnchainSymbol::decode(&mut v.as_slice())?; - crate::output::legacy::create_mysql_table(symbol.clone())?; state.symbols.insert(symbol, market); } // token list, map TokenId -> Token - let key = api.metadata.storage_map_key_prefix("Token", "Tokens")?; - let payload = api + let key = self + .api + .metadata + .storage_map_key_prefix("Token", "Tokens")?; + let payload = self + .api .get_opaque_storage_pairs_by_key_hash(key, Some(hash))? .ok_or(anyhow!(""))?; for (k, v) in payload.into_iter() { @@ -168,8 +105,11 @@ impl FusoConnector { } // broker list, map AccountId -> Broker - let key = api.metadata.storage_map_key_prefix("Market", "Brokers")?; - let payload = api.get_keys(key, Some(hash))?.ok_or(anyhow!(""))?; + let key = self + .api + .metadata + .storage_map_key_prefix("Market", "Brokers")?; + let payload = self.api.get_keys(key, Some(hash))?.ok_or(anyhow!(""))?; for k in payload.into_iter() { let broker: FusoAccountId = RuntimeDecoder::extract_map_identifier( StorageHasher::Blake2_128Concat, @@ -179,10 +119,16 @@ impl FusoConnector { } // pending receipts, double map AccountId, AccountId -> Receipt - let key = api + let key = self + .api .metadata - .storage_double_map_partial_key::("Verifier", "Receipts", who)?; - let payload = api + .storage_double_map_partial_key::( + "Verifier", + "Receipts", + &self.get_pubkey(), + )?; + let payload = self + .api .get_opaque_storage_pairs_by_key_hash(key, Some(hash))? .ok_or(anyhow!(""))?; let mut commands = vec![]; @@ -190,7 +136,7 @@ impl FusoConnector { let user = RuntimeDecoder::extract_double_map_identifier::( StorageHasher::Blake2_128Concat, StorageHasher::Blake2_128Concat, - who, + &self.get_pubkey(), &mut k.as_slice(), )?; let unexecuted = decoder.decode_raw_enum( @@ -223,240 +169,21 @@ impl FusoConnector { if !commands.is_empty() { log::info!("pending receipts detected: {:?}", commands); } - sequence::insert_sequences(&commands)?; - state.scanning_progress.store(util + 1, Ordering::Relaxed); - state.chain_height.store(util, Ordering::Relaxed); - Ok(state) - } - - fn handle_submit_result(result: anyhow::Result<()>, (start_from, end_to): (u64, u64)) -> u64 { - match result { - Ok(()) => { - log::info!("[+] rotating proved event to {}", end_to); - end_to - } - Err(e) => { - log::error!("[-] error occured while submitting proofs, {:?}", e); - start_from - } - } + state.scanning_progress.store(block + 1, Ordering::Relaxed); + state.chain_height.store(block, Ordering::Relaxed); + Ok(commands) } - fn handle_finalized_block( - api: &FusoApi, - signer: &FusoAccountId, - at: u32, - decoder: &RuntimeDecoder, - state: &Arc, - ) -> anyhow::Result<()> { - use hex::ToHex; - let hash = api - .get_block_hash(Some(at))? - .ok_or(anyhow!("block {} not ready", at))?; - let key = api - .metadata - .storage_value_key("System", "Events") - .map_err(|e| anyhow!("Read storage failed: {:?}", e))?; - let payload = api.get_opaque_storage_by_key_hash(key, Some(hash))?; - let events = decoder - .decode_events(&mut payload.unwrap_or(vec![]).as_slice()) - .unwrap_or(vec![]); - for (_, event) in events.into_iter() { - if let Raw::Event(raw) = event { - match (raw.pallet.as_ref(), raw.variant.as_ref()) { - ("Verifier", "TokenHosted") => { - let decoded = TokenHostedEvent::decode(&mut &raw.data[..])?; - if &decoded.dominator == signer { - let mut cmd = Command::default(); - cmd.cmd = crate::cmd::TRANSFER_IN; - cmd.currency = Some(decoded.token_id); - cmd.amount = to_decimal_represent(decoded.amount); - cmd.user_id = Some(format!("{}", decoded.fund_owner)); - cmd.block_number = Some(at); - cmd.extrinsic_hash = Some(hash.encode_hex()); - sequence::insert_sequences(&mut vec![cmd])?; - } - } - ("Verifier", "TokenRevoked") => { - let decoded = TokenHostedEvent::decode(&mut &raw.data[..])?; - if &decoded.dominator == signer { - let mut cmd = Command::default(); - cmd.cmd = crate::cmd::TRANSFER_OUT; - cmd.currency = Some(decoded.token_id); - cmd.amount = to_decimal_represent(decoded.amount); - cmd.user_id = Some(format!("{}", decoded.fund_owner)); - cmd.block_number = Some(at); - cmd.extrinsic_hash = Some(hash.encode_hex()); - sequence::insert_sequences(&mut vec![cmd])?; - } - } - ("Token", "TokenIssued") => { - let decoded = TokenIssuedEvent::decode(&mut &raw.data[..])?; - let key = api - .metadata - .storage_map_key::("Token", "Tokens", decoded.token_id) - .map_err(|e| anyhow!("Read storage failed: {:?}", e))?; - let payload = api - .get_opaque_storage_by_key_hash(key, Some(hash))? - .ok_or(anyhow::anyhow!(""))?; - let token = OnchainToken::decode(&mut payload.as_slice())?; - state.currencies.insert(decoded.token_id, token); - } - ("Market", "BrokerRegistered") => { - let decoded = BrokerRegisteredEvent::decode(&mut &raw.data[..])?; - state.brokers.insert(decoded.broker_account, rand::random()); - } - ("Market", "MarketOpened") => { - let decoded = MarketOpenedEvent::decode(&mut &raw.data[..])?; - if &decoded.dominator == signer { - // TODO impl Into for SymbolCmd - let mut cmd = Command::default(); - let milli = Decimal::from_str("0.001").unwrap(); - cmd.cmd = crate::cmd::UPDATE_SYMBOL; - cmd.base = Some(decoded.base); - cmd.quote = Some(decoded.quote); - cmd.open = Some(true); - cmd.base_scale = Some(decoded.base_scale.into()); - cmd.quote_scale = Some(decoded.quote_scale.into()); - cmd.taker_fee = Some(milli); - cmd.maker_fee = Some(milli); - cmd.min_amount = to_decimal_represent(decoded.min_base); - // DEPRECATED - cmd.base_maker_fee = Some(milli); - cmd.base_taker_fee = Some(milli); - cmd.fee_times = Some(1); - cmd.min_vol = Some(Decimal::from_str("10").unwrap()); - cmd.enable_market_order = Some(false); - crate::output::legacy::create_mysql_table(( - decoded.base, - decoded.quote, - ))?; - sequence::insert_sequences(&mut vec![cmd])?; - state.symbols.insert( - (decoded.base, decoded.quote), - OnchainSymbol { - min_base: decoded.min_base, - base_scale: decoded.base_scale, - quote_scale: decoded.quote_scale, - status: MarketStatus::Open, - trading_rewards: true, - liquidity_rewards: true, - unavailable_after: None, - }, - ); - } - } - ("Market", "MarketClosed") => { - let decoded = MarketClosedEvent::decode(&mut &raw.data[..])?; - if &decoded.dominator == signer { - let market = state.symbols.remove(&(decoded.base, decoded.quote)); - let mut cmd = Command::default(); - let milli = Decimal::from_str("0.001").unwrap(); - cmd.cmd = crate::cmd::UPDATE_SYMBOL; - cmd.base = Some(decoded.base); - cmd.quote = Some(decoded.quote); - cmd.open = Some(false); - cmd.taker_fee = Some(milli); - cmd.maker_fee = Some(milli); - let (base_scale, quote_scale, min_amount) = market - .map(|(_, m)| (m.base_scale, m.quote_scale, m.min_base)) - .ok_or(anyhow!(""))?; - cmd.base_scale = Some(base_scale.into()); - cmd.quote_scale = Some(quote_scale.into()); - cmd.min_amount = to_decimal_represent(min_amount); - // DEPRECATED - cmd.base_maker_fee = Some(milli); - cmd.base_taker_fee = Some(milli); - cmd.fee_times = Some(1); - cmd.min_vol = Some(Decimal::from_str("10").unwrap()); - cmd.enable_market_order = Some(false); - sequence::insert_sequences(&mut vec![cmd])?; - } - } - _ => {} - } - } - } - Ok(()) - } - - fn fetch_proofs(start_from: u64) -> (u64, Vec) { - let proofs = persistence::fetch_raw_after(start_from); - let mut total_size = 0usize; - let mut last_submit = start_from; - let mut truncated = vec![]; - for (event_id, proof) in proofs.into_iter() { - if total_size + proof.0.len() >= super::MAX_EXTRINSIC_SIZE { - break; - } - total_size += proof.0.len(); - last_submit = event_id; - truncated.push(proof); - } - (last_submit, truncated) - } - - fn get_finalized_block(api: &FusoApi) -> anyhow::Result<(u32, Hash)> { - let hash = api + pub fn get_finalized_block(&self) -> anyhow::Result<(u32, Hash)> { + let hash = self + .api .get_finalized_head()? .ok_or(anyhow!("finalized headers cant be found"))?; - let block_number = api + let block_number = self + .api .get_signed_block(Some(hash))? .ok_or(anyhow!("signed block {} can't be found", hash)) .map(|b: sub_api::SignedBlock| b.block.header.number)?; Ok((block_number, hash)) } - - fn compress_proofs(raws: Vec) -> Vec { - let r = raws.encode(); - let uncompress_size = r.len(); - let compressed_proofs = lz4_flex::compress_prepend_size(r.as_ref()); - let compressed_size = compressed_proofs.len(); - log::info!( - "proof compress: uncompress size = {}, compressed size = {}", - uncompress_size, - compressed_size - ); - compressed_proofs - } - - fn submit_batch(api: &FusoApi, batch: Vec) -> anyhow::Result<()> { - if batch.is_empty() { - return Ok(()); - } - log::info!( - "[+] starting to submit_proofs at {}", - Local::now().timestamp_millis() - ); - let hash = if C.fusotao.compress_proofs { - let xt: sub_api::UncheckedExtrinsicV4<_> = sub_api::compose_extrinsic!( - api, - "Verifier", - "verify_compress_v2", - Self::compress_proofs(batch) - ); - api.send_extrinsic(xt.hex_encode(), sub_api::XtStatus::InBlock) - .map_err(|e| anyhow!("[-] submitting proofs failed, {:?}", e))? - } else { - let xt: sub_api::UncheckedExtrinsicV4<_> = - sub_api::compose_extrinsic!(api, "Verifier", "verify_v2", batch); - api.send_extrinsic(xt.hex_encode(), sub_api::XtStatus::InBlock) - .map_err(|e| anyhow!("[-] submitting proofs failed, {:?}", e))? - }; - log::info!( - "[+] ending submit_proofs at {}", - Local::now().timestamp_millis() - ); - if hash.is_none() { - Err(anyhow!( - "[-] verify extrinsic executed failed, no extrinsic returns" - )) - } else { - log::info!( - "[+] submitting proofs ok, extrinsic hash: {:?}", - hex::encode(hash.unwrap()) - ); - Ok(()) - } - } } diff --git a/engine/src/fusotao/mod.rs b/engine/src/fusotao/mod.rs index 6c7d46e..eebeebb 100644 --- a/engine/src/fusotao/mod.rs +++ b/engine/src/fusotao/mod.rs @@ -12,11 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::{config::C, core::*, sequence::*}; +use crate::{config::C, core::*, input::*}; use connector::FusoConnector; use dashmap::DashMap; use parity_scale_codec::{Compact, Decode, Encode, WrapperTypeDecode, WrapperTypeEncode}; -pub use prover::Prover; use rust_decimal::{prelude::*, Decimal}; use serde::{Deserialize, Serialize}; use smt::{blake2b::Blake2bHasher, default_store::DefaultStore, SparseMerkleTree, H256}; @@ -24,21 +23,21 @@ use std::{ convert::TryInto, sync::{ atomic::{AtomicU32, AtomicU64, Ordering}, - mpsc::{Receiver, RecvTimeoutError}, Arc, }, }; -mod connector; -mod persistence; -mod prover; +pub mod committer; +pub mod connector; +pub mod prover; +pub mod scanner; +pub type BlockNumber = u32; pub type GlobalStates = SparseMerkleTree>; pub type Sr25519Key = sp_core::sr25519::Pair; pub type FusoAccountId = ::Public; pub type FusoAddress = sp_runtime::MultiAddress; pub type FusoHash = sp_runtime::traits::BlakeTwo256; -pub type BlockNumber = u32; pub type FusoHeader = sp_runtime::generic::Header; pub type FusoExtrinsic = sp_runtime::OpaqueExtrinsic; pub type FusoBlock = sp_runtime::generic::Block; @@ -52,11 +51,13 @@ const MAX_EXTRINSIC_SIZE: usize = 3 * 1024 * 1024; /// AccountId of chain = MultiAddress::Id = GenericAddress::Id /// 1. from_ss58check() or from_ss58check_with_version() /// 2. new or from public -pub fn init(rx: Receiver) -> FusoConnector { - persistence::init(rx); - let connector = FusoConnector::new(C.dry_run.is_some()).unwrap(); - log::info!("prover initialized"); - connector +pub fn sync() -> anyhow::Result<(FusoConnector, Arc)> { + let connector = FusoConnector::new()?; + let progress = connector.sync_progress()?; + let state = FusoState::default(); + state.proved_event_id.store(progress, Ordering::Relaxed); + log::info!("proving progress synchronized"); + Ok((connector, Arc::new(state))) } /// tracking essential onchain states @@ -66,7 +67,6 @@ pub struct FusoState { pub proved_event_id: Arc, pub scanning_progress: Arc, pub symbols: DashMap, - // TODO transform OnchainCurrency to offchain currency pub currencies: DashMap, pub brokers: DashMap, } diff --git a/engine/src/fusotao/persistence.rs b/engine/src/fusotao/persistence.rs deleted file mode 100644 index 46ffd76..0000000 --- a/engine/src/fusotao/persistence.rs +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2021-2023 UINB Technologies Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{config::C, db::DB, fusotao::*}; -use mysql::{prelude::*, *}; -use std::{sync::mpsc::Receiver, time::Duration}; - -pub fn init(rx: Receiver) { - let mut pending = Vec::with_capacity(100); - std::thread::spawn(move || loop { - let proof = match rx.recv_timeout(Duration::from_millis(10_000)) { - Ok(p) => Some(p), - Err(RecvTimeoutError::Timeout) => None, - Err(RecvTimeoutError::Disconnected) => { - log::error!("Proof persistence thread interrupted!"); - break; - } - }; - if C.dry_run.is_some() { - if let Some(p) = proof { - log::info!("{}(dry-run) => 0x{}", p.event_id, &hex::encode(p.root)); - } - continue; - } else { - append(proof, &mut pending); - } - }); -} - -pub fn fetch_raw_after(event_id: u64) -> Vec<(u64, RawParameter)> { - let sql = "SELECT f_event_id,f_proof FROM t_proof WHERE f_event_id>? LIMIT ?"; - let conn = DB.get_conn(); - if conn.is_err() { - log::error!("retrieve mysql connection failed while fetch_proofs"); - return vec![]; - } - let mut conn = conn.unwrap(); - conn.exec_map( - sql, - // TODO calculate max_weight and max_length - (event_id, C.fusotao.proof_batch_limit), - |(f_event_id, f_proof): (u64, Vec)| (f_event_id, RawParameter(f_proof)), - ) - .unwrap_or_default() -} - -fn flush(pending: &mut Vec) { - let sql = r#"INSERT IGNORE INTO t_proof(f_event_id,f_proof) VALUES (:event_id,:proof)"#; - let conn = DB.get_conn(); - if conn.is_err() { - log::error!("Error: acquire mysql connection failed, {:?}", conn); - return; - } - let mut conn = conn.unwrap(); - let r = conn.exec_batch( - sql, - pending.iter().map(|p| { - params! { - "event_id" => p.event_id, - "proof" => p.encode(), - } - }), - ); - match r { - Ok(_) => pending.clear(), - Err(err) => { - log::error!("Error: writing proofs to mysql failed, {:?}", err); - } - } -} - -fn append(proof: Option, pending: &mut Vec) { - match proof { - None => { - if !pending.is_empty() { - flush(pending); - } - } - Some(p) => { - pending.push(p); - if pending.len() >= 50 { - flush(pending); - } - } - } -} diff --git a/engine/src/fusotao/prover.rs b/engine/src/fusotao/prover.rs index 440fe6c..f754dc9 100644 --- a/engine/src/fusotao/prover.rs +++ b/engine/src/fusotao/prover.rs @@ -16,7 +16,8 @@ use super::*; use crate::{assets::Balance, matcher::*, orderbook::AskOrBid, output::Output}; use blake2::{Blake2b, Digest}; use generic_array::typenum::U32; -use std::{collections::HashMap, sync::mpsc::Sender}; +use rocksdb::{Direction, IteratorMode, WriteBatchWithTransaction}; +use std::collections::HashMap; pub type BlakeTwo256 = Blake2b; @@ -25,125 +26,115 @@ const ORDERBOOK_KEY: u8 = 0x01; const BESTPRICE_KEY: u8 = 0x02; const ORDERPAGE_KEY: u8 = 0x03; -pub struct Prover { - pub sender: Sender, -} - -impl Prover { - pub fn new(tx: Sender) -> Self { - Self { sender: tx } - } - - pub fn prove_trade_cmd( - &self, - data: &mut Data, - _nonce: u32, - _signature: Vec, - encoded_cmd: FusoCommand, - ask_size_before: Amount, - bid_size_before: Amount, - best_ask_before: (Price, Amount), - best_bid_before: (Price, Amount), - taker_base_before: &Balance, - taker_quote_before: &Balance, - outputs: &[Output], - matches: &Match, - ) { - let mut leaves = vec![]; - let taker = outputs.last().unwrap(); - let symbol = taker.symbol.clone(); - let event_id = taker.event_id; - let user_id = taker.user_id; - let orderbook = data.orderbooks.get(&symbol).unwrap(); - let size = orderbook.size(); - log::debug!( - "generating merkle leaf of {:?}: orderbook = ({:?}, {:?}) -> ({:?}, {:?})", - taker.event_id, - ask_size_before, - bid_size_before, - size.0, - size.1, - ); - let (old_ask_size, old_bid_size, new_ask_size, new_bid_size) = ( - ask_size_before.to_amount(), - bid_size_before.to_amount(), - size.0.to_amount(), - size.1.to_amount(), +pub fn prove_trade_cmd( + data: &mut Data, + _nonce: u32, + _signature: Vec, + encoded_cmd: FusoCommand, + ask_size_before: Amount, + bid_size_before: Amount, + best_ask_before: (Price, Amount), + best_bid_before: (Price, Amount), + taker_base_before: &Balance, + taker_quote_before: &Balance, + outputs: &[Output], + matches: &Match, +) -> Proof { + let mut leaves = vec![]; + let taker = outputs.last().unwrap(); + let symbol = taker.symbol.clone(); + let event_id = taker.event_id; + let user_id = taker.user_id; + let orderbook = data.orderbooks.get(&symbol).unwrap(); + let size = orderbook.size(); + log::debug!( + "generating merkle leaf of {:?}: orderbook = ({:?}, {:?}) -> ({:?}, {:?})", + taker.event_id, + ask_size_before, + bid_size_before, + size.0, + size.1, + ); + let (old_ask_size, old_bid_size, new_ask_size, new_bid_size) = ( + ask_size_before.to_amount(), + bid_size_before.to_amount(), + size.0.to_amount(), + size.1.to_amount(), + ); + leaves.push(new_orderbook_merkle_leaf( + symbol, + old_ask_size, + old_bid_size, + new_ask_size, + new_bid_size, + )); + let mut maker_accounts = HashMap::::new(); + outputs + .iter() + .take_while(|o| o.role == Role::Maker) + .for_each(|r| { + maker_accounts + .entry(r.user_id.clone()) + .and_modify(|out| { + out.quote_charge += r.quote_charge; + out.quote_delta += r.quote_delta; + out.quote_available = r.quote_available; + out.quote_frozen = r.quote_frozen; + out.base_charge += r.base_charge; + out.base_delta += r.base_delta; + out.base_available = r.base_available; + out.base_frozen = r.base_frozen; + }) + .or_insert_with(|| r.clone()); + }); + maker_accounts.values().for_each(|r| { + log::debug!("{:?}", r); + let (ba, bf, qa, qf) = match r.ask_or_bid { + // -base_frozen, +quote_available + // base_frozen0 + r.base_delta = base_frozen + // qa - q0 + abs(r.quote_charge) = abs(quote_delta) + AskOrBid::Ask => ( + r.base_available, + r.base_frozen + r.base_delta.abs(), + r.quote_available + r.quote_charge.abs() - r.quote_delta.abs(), + r.quote_frozen, + ), + // +base_available, -quote_frozen + // quote_frozen0 + r.quote_delta = quote_frozen + // ba0 - ba + abs(r.base_charge) = abs(base_delta) + AskOrBid::Bid => ( + r.base_available + r.base_charge.abs() - r.base_delta.abs(), + r.base_frozen, + r.quote_available, + r.quote_frozen + r.quote_delta.abs(), + ), + }; + let (new_ba, new_bf, old_ba, old_bf) = ( + r.base_available.to_amount(), + r.base_frozen.to_amount(), + ba.to_amount(), + bf.to_amount(), ); - leaves.push(new_orderbook_merkle_leaf( - symbol, - old_ask_size, - old_bid_size, - new_ask_size, - new_bid_size, + leaves.push(new_account_merkle_leaf( + &r.user_id, symbol.0, old_ba, old_bf, new_ba, new_bf, )); - let mut maker_accounts = HashMap::::new(); - outputs - .iter() - .take_while(|o| o.role == Role::Maker) - .for_each(|r| { - maker_accounts - .entry(r.user_id.clone()) - .and_modify(|out| { - out.quote_charge += r.quote_charge; - out.quote_delta += r.quote_delta; - out.quote_available = r.quote_available; - out.quote_frozen = r.quote_frozen; - out.base_charge += r.base_charge; - out.base_delta += r.base_delta; - out.base_available = r.base_available; - out.base_frozen = r.base_frozen; - }) - .or_insert_with(|| r.clone()); - }); - maker_accounts.values().for_each(|r| { - log::debug!("{:?}", r); - let (ba, bf, qa, qf) = match r.ask_or_bid { - // -base_frozen, +quote_available - // base_frozen0 + r.base_delta = base_frozen - // qa - q0 + abs(r.quote_charge) = abs(quote_delta) - AskOrBid::Ask => ( - r.base_available, - r.base_frozen + r.base_delta.abs(), - r.quote_available + r.quote_charge.abs() - r.quote_delta.abs(), - r.quote_frozen, - ), - // +base_available, -quote_frozen - // quote_frozen0 + r.quote_delta = quote_frozen - // ba0 - ba + abs(r.base_charge) = abs(base_delta) - AskOrBid::Bid => ( - r.base_available + r.base_charge.abs() - r.base_delta.abs(), - r.base_frozen, - r.quote_available, - r.quote_frozen + r.quote_delta.abs(), - ), - }; - let (new_ba, new_bf, old_ba, old_bf) = ( - r.base_available.to_amount(), - r.base_frozen.to_amount(), - ba.to_amount(), - bf.to_amount(), - ); - leaves.push(new_account_merkle_leaf( - &r.user_id, symbol.0, old_ba, old_bf, new_ba, new_bf, - )); - let (new_qa, new_qf, old_qa, old_qf) = ( - r.quote_available.to_amount(), - r.quote_frozen.to_amount(), - qa.to_amount(), - qf.to_amount(), - ); - leaves.push(new_account_merkle_leaf( - &r.user_id, symbol.1, old_qa, old_qf, new_qa, new_qf, - )); - }); - let (new_taker_ba, new_taker_bf, old_taker_ba, old_taker_bf) = ( - taker.base_available.to_amount(), - taker.base_frozen.to_amount(), - taker_base_before.available.to_amount(), - taker_base_before.frozen.to_amount(), + let (new_qa, new_qf, old_qa, old_qf) = ( + r.quote_available.to_amount(), + r.quote_frozen.to_amount(), + qa.to_amount(), + qf.to_amount(), ); - log::debug!( + leaves.push(new_account_merkle_leaf( + &r.user_id, symbol.1, old_qa, old_qf, new_qa, new_qf, + )); + }); + let (new_taker_ba, new_taker_bf, old_taker_ba, old_taker_bf) = ( + taker.base_available.to_amount(), + taker.base_frozen.to_amount(), + taker_base_before.available.to_amount(), + taker_base_before.frozen.to_amount(), + ); + log::debug!( "generating merkle leaf of {:?}: taker base = [{:?}({:?}), {:?}({:?})] -> [{:?}({:?}), {:?}({:?})]", taker.event_id, old_taker_ba, @@ -155,21 +146,21 @@ impl Prover { new_taker_bf, taker.base_frozen, ); - leaves.push(new_account_merkle_leaf( - &user_id, - symbol.0, - old_taker_ba, - old_taker_bf, - new_taker_ba, - new_taker_bf, - )); - let (new_taker_qa, new_taker_qf, old_taker_qa, old_taker_qf) = ( - taker.quote_available.to_amount(), - taker.quote_frozen.to_amount(), - taker_quote_before.available.to_amount(), - taker_quote_before.frozen.to_amount(), - ); - log::debug!( + leaves.push(new_account_merkle_leaf( + &user_id, + symbol.0, + old_taker_ba, + old_taker_bf, + new_taker_ba, + new_taker_bf, + )); + let (new_taker_qa, new_taker_qf, old_taker_qa, old_taker_qf) = ( + taker.quote_available.to_amount(), + taker.quote_frozen.to_amount(), + taker_quote_before.available.to_amount(), + taker_quote_before.frozen.to_amount(), + ); + log::debug!( "generating merkle leaf of {:?}: taker quote = [{:?}({:?}), {:?}({:?})] -> [{:?}({:?}), {:?}({:?})]", taker.event_id, old_taker_qa, @@ -181,152 +172,190 @@ impl Prover { new_taker_qf, taker.quote_frozen, ); - leaves.push(new_account_merkle_leaf( - &user_id, - symbol.1, - old_taker_qa, - old_taker_qf, - new_taker_qa, - new_taker_qf, - )); - let (best_ask, best_bid) = orderbook.get_size_of_best(); - leaves.push(new_bestprice_merkle_leaf( - symbol, - best_ask_before.0.to_amount(), - best_bid_before.0.to_amount(), - best_ask.map(|a| a.0).unwrap_or(Amount::zero()).to_amount(), - best_bid.map(|b| b.0).unwrap_or(Amount::zero()).to_amount(), - )); - let mut pages = matches - .page_delta - .iter() - .map(|(k, v)| { - new_orderpage_merkle_leaf(symbol, k.to_amount(), v.0.to_amount(), v.1.to_amount()) - }) - .collect::>(); - if taker.ask_or_bid == AskOrBid::Ask && !pages.is_empty() { - pages.reverse(); - } - leaves.append(&mut pages); - let merkle_proof = gen_proofs(&mut data.merkle_tree, &leaves); - self.sender - .send(Proof { - event_id, - user_id, - cmd: encoded_cmd, - leaves, - maker_page_delta: matches.page_delta.len() as u8, - maker_account_delta: maker_accounts.len() as u8 * 2, - merkle_proof, - root: data.merkle_tree.root().clone().into(), - }) - .unwrap(); + leaves.push(new_account_merkle_leaf( + &user_id, + symbol.1, + old_taker_qa, + old_taker_qf, + new_taker_qa, + new_taker_qf, + )); + let (best_ask, best_bid) = orderbook.get_size_of_best(); + leaves.push(new_bestprice_merkle_leaf( + symbol, + best_ask_before.0.to_amount(), + best_bid_before.0.to_amount(), + best_ask.map(|a| a.0).unwrap_or(Amount::zero()).to_amount(), + best_bid.map(|b| b.0).unwrap_or(Amount::zero()).to_amount(), + )); + let mut pages = matches + .page_delta + .iter() + .map(|(k, v)| { + new_orderpage_merkle_leaf(symbol, k.to_amount(), v.0.to_amount(), v.1.to_amount()) + }) + .collect::>(); + if taker.ask_or_bid == AskOrBid::Ask && !pages.is_empty() { + pages.reverse(); + } + leaves.append(&mut pages); + let merkle_proof = gen_proofs(&mut data.merkle_tree, &leaves); + Proof { + event_id, + user_id, + cmd: encoded_cmd, + leaves, + maker_page_delta: matches.page_delta.len() as u8, + maker_account_delta: maker_accounts.len() as u8 * 2, + merkle_proof, + root: data.merkle_tree.root().clone().into(), } +} - pub fn prove_assets_cmd( - &self, - merkle_tree: &mut GlobalStates, - event_id: u64, - cmd: AssetsCmd, - account_before: &Balance, - account_after: &Balance, - ) { - let (new_available, new_frozen, old_available, old_frozen) = ( - account_after.available.to_amount(), - account_after.frozen.to_amount(), - account_before.available.to_amount(), - account_before.frozen.to_amount(), - ); - let leaves = vec![new_account_merkle_leaf( - &cmd.user_id, - cmd.currency, - old_available, - old_frozen, - new_available, - new_frozen, - )]; - let merkle_proof = gen_proofs(merkle_tree, &leaves); - self.sender - .send(Proof { - event_id, - user_id: cmd.user_id, - cmd: (cmd, true).into(), - leaves, - maker_page_delta: 0, - maker_account_delta: 0, - merkle_proof, - root: merkle_tree.root().clone().into(), - }) - .unwrap(); +pub fn prove_assets_cmd( + merkle_tree: &mut GlobalStates, + event_id: u64, + cmd: AssetsCmd, + account_before: &Balance, + account_after: &Balance, +) -> Proof { + let (new_available, new_frozen, old_available, old_frozen) = ( + account_after.available.to_amount(), + account_after.frozen.to_amount(), + account_before.available.to_amount(), + account_before.frozen.to_amount(), + ); + let leaves = vec![new_account_merkle_leaf( + &cmd.user_id, + cmd.currency, + old_available, + old_frozen, + new_available, + new_frozen, + )]; + let merkle_proof = gen_proofs(merkle_tree, &leaves); + Proof { + event_id, + user_id: cmd.user_id, + cmd: (cmd, true).into(), + leaves, + maker_page_delta: 0, + maker_account_delta: 0, + merkle_proof, + root: merkle_tree.root().clone().into(), } +} - pub fn prove_cmd_rejected( - &self, - merkle_tree: &mut GlobalStates, - event_id: u64, - cmd: AssetsCmd, - account_before: &Balance, - ) { - let (old_available, old_frozen) = ( - account_before.available.to_amount(), - account_before.frozen.to_amount(), - ); - let leaves = vec![new_account_merkle_leaf( - &cmd.user_id, - cmd.currency, - old_available, - old_frozen, - old_available, - old_frozen, - )]; - let merkle_proof = gen_proofs(merkle_tree, &leaves); - self.sender - .send(Proof { - event_id, - user_id: cmd.user_id, - cmd: (cmd, false).into(), - leaves, - maker_page_delta: 0, - maker_account_delta: 0, - merkle_proof, - root: merkle_tree.root().clone().into(), - }) - .unwrap(); +pub fn prove_cmd_rejected( + merkle_tree: &mut GlobalStates, + event_id: u64, + cmd: AssetsCmd, + account_before: &Balance, +) -> Proof { + let (old_available, old_frozen) = ( + account_before.available.to_amount(), + account_before.frozen.to_amount(), + ); + let leaves = vec![new_account_merkle_leaf( + &cmd.user_id, + cmd.currency, + old_available, + old_frozen, + old_available, + old_frozen, + )]; + let merkle_proof = gen_proofs(merkle_tree, &leaves); + Proof { + event_id, + user_id: cmd.user_id, + cmd: (cmd, false).into(), + leaves, + maker_page_delta: 0, + maker_account_delta: 0, + merkle_proof, + root: merkle_tree.root().clone().into(), } +} - pub fn prove_rejecting_no_reason( - &self, - merkle_tree: &mut GlobalStates, - event_id: u64, - cmd: AssetsCmd, - account_before: &Balance, - ) { - let (old_available, old_frozen) = ( - account_before.available.to_amount(), - account_before.frozen.to_amount(), - ); - let leaves = vec![new_account_merkle_leaf( - &cmd.user_id, - cmd.currency, - old_available, - old_frozen, - old_available, - old_frozen, - )]; - let merkle_proof = gen_proofs(merkle_tree, &leaves); - self.sender - .send(Proof { - event_id, - user_id: cmd.user_id, - cmd: (cmd, false).into(), - leaves: vec![], - maker_page_delta: 0, - maker_account_delta: 0, - merkle_proof, - root: merkle_tree.root().clone().into(), - }) - .unwrap(); +pub fn prove_rejecting_no_reason( + merkle_tree: &mut GlobalStates, + event_id: u64, + cmd: AssetsCmd, + account_before: &Balance, +) -> Proof { + let (old_available, old_frozen) = ( + account_before.available.to_amount(), + account_before.frozen.to_amount(), + ); + let leaves = vec![new_account_merkle_leaf( + &cmd.user_id, + cmd.currency, + old_available, + old_frozen, + old_available, + old_frozen, + )]; + let merkle_proof = gen_proofs(merkle_tree, &leaves); + Proof { + event_id, + user_id: cmd.user_id, + cmd: (cmd, false).into(), + leaves: vec![], + maker_page_delta: 0, + maker_account_delta: 0, + merkle_proof, + root: merkle_tree.root().clone().into(), + } +} + +pub fn save_proof(proof: Proof) -> anyhow::Result<()> { + match C.dry_run { + Some(n) if n >= proof.event_id => { + log::info!( + "dry-run: event-{} => 0x{}", + proof.event_id, + &hex::encode(proof.root) + ); + } + _ => {} + } + STORAGE.put(id_to_key(proof.event_id), proof.encode())?; + Ok(()) +} + +pub fn remove_before(id: u64) -> anyhow::Result<()> { + let mut batch = WriteBatchWithTransaction::::default(); + batch.delete_range(id_to_key(1), id_to_key(id)); + STORAGE.write(batch)?; + Ok(()) +} + +pub fn fetch_raw_ge(id: u64) -> Vec<(u64, RawParameter)> { + if C.dry_run.is_some() { + return vec![]; } + let mut ret = vec![]; + let mut total_size = 0usize; + let iter = STORAGE.iterator(IteratorMode::From(&id_to_key(id), Direction::Forward)); + for item in iter { + let (key, value) = item.unwrap(); + total_size += value.len(); + ret.push((key_to_id(&key), RawParameter(value.to_vec()))); + if ret.len() >= C.fusotao.proof_batch_limit || total_size >= MAX_EXTRINSIC_SIZE { + break; + } + } + ret +} + +fn id_to_key(id: u64) -> [u8; 16] { + unsafe { std::mem::transmute::<[[u8; 8]; 2], [u8; 16]>([*b"rawproof", id.to_be_bytes()]) } +} + +fn key_to_id(key: &[u8]) -> u64 { + let mut id = [0u8; 8]; + id.copy_from_slice(&key[8..]); + u64::from_be_bytes(id) } fn gen_proofs(merkle_tree: &mut GlobalStates, leaves: &Vec) -> Vec { @@ -544,36 +573,27 @@ mod test { #[test] pub fn test_transfer_in() { - let (tx, rx) = std::sync::mpsc::channel(); - std::thread::spawn(move || { - let mut merkle_tree = GlobalStates::default(); - let pp = Prover::new(tx); - let mut all = Accounts::new(); - let cmd0 = AssetsCmd { - user_id: UserId::from_low_u64_be(1), - in_or_out: InOrOut::In, - currency: 1, - amount: dec!(1.11111), - block_number: 1, - extrinsic_hash: vec![0], - }; - let after = - assets::add_to_available(&mut all, &cmd0.user_id, cmd0.currency, cmd0.amount) - .unwrap(); - let cmd1 = cmd0.clone(); - pp.prove_assets_cmd( - &mut merkle_tree, - 1, - cmd0, - &assets::Balance::default(), - &after, - ); - let transfer_again = - assets::add_to_available(&mut all, &cmd1.user_id, cmd1.currency, cmd1.amount) - .unwrap(); - pp.prove_assets_cmd(&mut merkle_tree, 1, cmd1, &after, &transfer_again); - }); - let proof = rx.recv().unwrap(); + let mut merkle_tree = GlobalStates::default(); + let mut all = Accounts::new(); + let cmd0 = AssetsCmd { + user_id: UserId::from_low_u64_be(1), + in_or_out: InOrOut::In, + currency: 1, + amount: dec!(1.11111), + block_number: 1, + extrinsic_hash: vec![0], + }; + let after = + assets::add_to_available(&mut all, &cmd0.user_id, cmd0.currency, cmd0.amount).unwrap(); + let cmd1 = cmd0.clone(); + let proof = prover::prove_assets_cmd( + &mut merkle_tree, + 1, + cmd0, + &assets::Balance::default(), + &after, + ); + let mp = CompiledMerkleProof(proof.merkle_proof.clone()); let old = proof .leaves @@ -595,7 +615,9 @@ mod test { ); assert_eq!(split_h256_u128(&proof.leaves[0].old_v), (0, 0)); let new_root = proof.root.clone(); - let proof = rx.recv().unwrap(); + let transfer_again = + assets::add_to_available(&mut all, &cmd1.user_id, cmd1.currency, cmd1.amount).unwrap(); + let proof = prover::prove_assets_cmd(&mut merkle_tree, 1, cmd1, &after, &transfer_again); let mp = CompiledMerkleProof(proof.merkle_proof.clone()); let old = proof .leaves @@ -623,336 +645,192 @@ mod test { #[test] pub fn test_trade() { - let (tx, rx) = std::sync::mpsc::channel(); - std::thread::spawn(move || { - let merkle_tree = GlobalStates::default(); - let all = Accounts::new(); - let orderbook = construct_pair(); - let mut orderbooks = std::collections::HashMap::new(); - let (mf, tf) = (orderbook.maker_fee, orderbook.taker_fee); - orderbooks.insert((1, 0), orderbook); - let mut data = Data { - orderbooks, - accounts: all, - merkle_tree, - current_event_id: 0, - tvl: Amount::zero(), - }; - let pp = Prover::new(tx); - let cmd0 = AssetsCmd { - user_id: UserId::from_low_u64_be(1), - in_or_out: InOrOut::In, - currency: 1, - amount: dec!(1.11111), - block_number: 1, - extrinsic_hash: vec![0], - }; - let after = assets::add_to_available( - &mut data.accounts, - &cmd0.user_id, - cmd0.currency, - cmd0.amount, - ) - .unwrap(); - pp.prove_assets_cmd( - &mut data.merkle_tree, - 1, - cmd0, - &assets::Balance::default(), - &after, - ); - let cmd1 = AssetsCmd { - user_id: UserId::from_low_u64_be(2), - in_or_out: InOrOut::In, - currency: 0, - amount: dec!(99.99), - block_number: 1, - extrinsic_hash: vec![0], - }; - let transfer_again = assets::add_to_available( - &mut data.accounts, - &cmd1.user_id, - cmd1.currency, - cmd1.amount, - ) - .unwrap(); - pp.prove_assets_cmd(&mut data.merkle_tree, 1, cmd1, &after, &transfer_again); + let merkle_tree = GlobalStates::default(); + let all = Accounts::new(); + let orderbook = construct_pair(); + let mut orderbooks = std::collections::HashMap::new(); + let (mf, tf) = (orderbook.maker_fee, orderbook.taker_fee); + orderbooks.insert((1, 0), orderbook); + let mut data = Data { + orderbooks, + accounts: all, + merkle_tree, + current_event_id: 0, + tvl: Amount::zero(), + }; + let cmd0 = AssetsCmd { + user_id: UserId::from_low_u64_be(1), + in_or_out: InOrOut::In, + currency: 1, + amount: dec!(1.11111), + block_number: 1, + extrinsic_hash: vec![0], + }; + let after = assets::add_to_available( + &mut data.accounts, + &cmd0.user_id, + cmd0.currency, + cmd0.amount, + ) + .unwrap(); + prover::prove_assets_cmd( + &mut data.merkle_tree, + 1, + cmd0, + &assets::Balance::default(), + &after, + ); - let size = data.orderbooks.get(&(1, 0)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (1, 0), - user_id: UserId::from_low_u64_be(1), - order_id: 1, - price: dec!(100), - amount: dec!(0.11), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(1, 0)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, + let cmd1 = AssetsCmd { + user_id: UserId::from_low_u64_be(2), + in_or_out: InOrOut::In, + currency: 0, + amount: dec!(99.99), + block_number: 1, + extrinsic_hash: vec![0], + }; + let transfer_again = assets::add_to_available( + &mut data.accounts, + &cmd1.user_id, + cmd1.currency, + cmd1.amount, + ) + .unwrap(); + + prover::prove_assets_cmd(&mut data.merkle_tree, 1, cmd1, &after, &transfer_again); + + let size = data.orderbooks.get(&(1, 0)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (1, 0), + user_id: UserId::from_low_u64_be(1), + order_id: 1, + price: dec!(100), + amount: dec!(0.11), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, + }; + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(1, 0)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 3, &(1, 0), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + + // ask 0.11, 100 + { + assert_eq!(proof.leaves.len(), 5); + // ask,bid + assert_eq!(split_h256_u128(&proof.leaves[0].old_v), (0, 0)); + assert_eq!( + split_h256_u128(&proof.leaves[0].new_v), + (110000000000000000, 0) ); - let cr = clearing::clear(&mut data.accounts, 3, &(1, 0), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, + // base + assert_eq!( + split_h256_u128(&proof.leaves[1].old_v), + (1111110000000000000, 0) ); - - let size = data.orderbooks.get(&(1, 0)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (1, 0), - user_id: UserId::from_low_u64_be(2), - order_id: 3, - price: dec!(90), - amount: dec!(0.01), - ask_or_bid: AskOrBid::Bid, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(1, 0)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, + assert_eq!( + split_h256_u128(&proof.leaves[1].new_v), + (1001110000000000000, 110000000000000000) ); - let cr = clearing::clear(&mut data.accounts, 5, &(1, 0), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, + // quote + assert_eq!(split_h256_u128(&proof.leaves[2].old_v), (0, 0)); + assert_eq!(split_h256_u128(&proof.leaves[2].new_v), (0, 0)); + // best price a,b + assert_eq!(split_h256_u128(&proof.leaves[3].old_v), (0, 0)); + assert_eq!( + split_h256_u128(&proof.leaves[3].new_v), + (100_000000000000000000, 0) + ); + // orderpage at 100 = 0.11 + assert_eq!(split_h256_u128_sum(&proof.leaves[4].old_v), 0); + assert_eq!( + split_h256_u128_sum(&proof.leaves[4].new_v), + 110000000000000000 ); + } - let size = data.orderbooks.get(&(1, 0)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (1, 0), - user_id: UserId::from_low_u64_be(1), - order_id: 4, - price: dec!(100), - amount: dec!(0.11), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(1, 0)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, + let size = data.orderbooks.get(&(1, 0)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (1, 0), + user_id: UserId::from_low_u64_be(2), + order_id: 3, + price: dec!(90), + amount: dec!(0.01), + ask_or_bid: AskOrBid::Bid, + nonce: 1, + signature: vec![0], + broker: None, + }; + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(1, 0)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 5, &(1, 0), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + // bid 0.01, 90 + { + // ask,bid + assert_eq!( + split_h256_u128(&proof.leaves[0].old_v), + (110000000000000000, 0) ); - let cr = clearing::clear(&mut data.accounts, 6, &(1, 0), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, - ); - - let size = data.orderbooks.get(&(1, 0)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (1, 0), - user_id: UserId::from_low_u64_be(2), - order_id: 5, - price: dec!(110), - amount: dec!(0.5), - ask_or_bid: AskOrBid::Bid, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(1, 0)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, - ); - let cr = clearing::clear(&mut data.accounts, 7, &(1, 0), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, - ); - println!("{:?}", cr.last().unwrap()); - - let size = data.orderbooks.get(&(1, 0)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (1, 0), - user_id: UserId::from_low_u64_be(1), - order_id: 6, - price: dec!(88), - amount: dec!(0.3), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(1, 0)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, - ); - let cr = clearing::clear(&mut data.accounts, 8, &(1, 0), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, - ); - }); - // ignore transfer in - rx.recv().unwrap(); - rx.recv().unwrap(); - // ask 0.11, 100 - { - let proof = rx.recv().unwrap(); - assert_eq!(proof.leaves.len(), 5); - // ask,bid - assert_eq!(split_h256_u128(&proof.leaves[0].old_v), (0, 0)); - assert_eq!( - split_h256_u128(&proof.leaves[0].new_v), - (110000000000000000, 0) - ); - // base - assert_eq!( - split_h256_u128(&proof.leaves[1].old_v), - (1111110000000000000, 0) - ); - assert_eq!( - split_h256_u128(&proof.leaves[1].new_v), - (1001110000000000000, 110000000000000000) - ); - // quote - assert_eq!(split_h256_u128(&proof.leaves[2].old_v), (0, 0)); - assert_eq!(split_h256_u128(&proof.leaves[2].new_v), (0, 0)); - // best price a,b - assert_eq!(split_h256_u128(&proof.leaves[3].old_v), (0, 0)); - assert_eq!( - split_h256_u128(&proof.leaves[3].new_v), - (100_000000000000000000, 0) - ); - // orderpage at 100 = 0.11 - assert_eq!(split_h256_u128_sum(&proof.leaves[4].old_v), 0); - assert_eq!( - split_h256_u128_sum(&proof.leaves[4].new_v), - 110000000000000000 - ); - } - // bid 0.01, 90 - { - let proof = rx.recv().unwrap(); - // ask,bid - assert_eq!( - split_h256_u128(&proof.leaves[0].old_v), - (110000000000000000, 0) - ); - assert_eq!( - split_h256_u128(&proof.leaves[0].new_v), - (110000000000000000, 10000000000000000) + assert_eq!( + split_h256_u128(&proof.leaves[0].new_v), + (110000000000000000, 10000000000000000) ); // base assert_eq!(split_h256_u128(&proof.leaves[1].old_v), (0, 0)); @@ -967,9 +845,52 @@ mod test { (99090000000000000000, 900000000000000000) ); } + + let size = data.orderbooks.get(&(1, 0)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (1, 0), + user_id: UserId::from_low_u64_be(1), + order_id: 4, + price: dec!(100), + amount: dec!(0.11), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, + }; + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(1, 0)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 6, &(1, 0), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); // ask 0.11, 100 { - let proof = rx.recv().unwrap(); // ask,bid assert_eq!( split_h256_u128(&proof.leaves[0].old_v), @@ -992,9 +913,53 @@ mod test { assert_eq!(split_h256_u128(&proof.leaves[2].old_v), (0, 0)); assert_eq!(split_h256_u128(&proof.leaves[2].new_v), (0, 0)); } + + let size = data.orderbooks.get(&(1, 0)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (1, 0), + user_id: UserId::from_low_u64_be(2), + order_id: 5, + price: dec!(110), + amount: dec!(0.5), + ask_or_bid: AskOrBid::Bid, + nonce: 1, + signature: vec![0], + broker: None, + }; + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(1, 0)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 7, &(1, 0), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + println!("{:?}", cr.last().unwrap()); // bid 0.5, 110 { - let proof = rx.recv().unwrap(); // ask,bid assert_eq!( split_h256_u128(&proof.leaves[0].old_v), @@ -1033,9 +998,52 @@ mod test { let (na, nf) = split_h256_u128(&proof.leaves[4].new_v); assert_eq!(na + nf + 22000000000000000000, 99990000000000000000); } + + let size = data.orderbooks.get(&(1, 0)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (1, 0), + user_id: UserId::from_low_u64_be(1), + order_id: 6, + price: dec!(88), + amount: dec!(0.3), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, + }; + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(1, 0)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(1, 0)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 8, &(1, 0), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); // ask 0.3, 88 { - let proof = rx.recv().unwrap(); // ask,bid assert_eq!( split_h256_u128(&proof.leaves[0].old_v), @@ -1073,358 +1081,345 @@ mod test { #[test] pub fn test_price() { - let (tx, rx) = std::sync::mpsc::channel(); - std::thread::spawn(move || { - let mut merkle_tree = GlobalStates::default(); - let pp = Prover::new(tx); - let mut all = Accounts::new(); - let orderbook = construct_pair(); - let cmd0 = AssetsCmd { + let mut merkle_tree = GlobalStates::default(); + let mut all = Accounts::new(); + let orderbook = construct_pair(); + let cmd0 = AssetsCmd { + user_id: UserId::from_low_u64_be(1), + in_or_out: InOrOut::In, + currency: 0, + amount: dec!(100), + block_number: 1, + extrinsic_hash: vec![0], + }; + let after = + assets::add_to_available(&mut all, &cmd0.user_id, cmd0.currency, cmd0.amount).unwrap(); + prover::prove_assets_cmd( + &mut merkle_tree, + 1, + cmd0, + &assets::Balance::default(), + &after, + ); + let cmd1 = AssetsCmd { + user_id: UserId::from_low_u64_be(2), + in_or_out: InOrOut::In, + currency: 1, + amount: dec!(1000), + block_number: 1, + extrinsic_hash: vec![0], + }; + let transfer_again = + assets::add_to_available(&mut all, &cmd1.user_id, cmd1.currency, cmd1.amount).unwrap(); + prover::prove_assets_cmd(&mut merkle_tree, 1, cmd1, &after, &transfer_again); + + let mut orderbooks = std::collections::HashMap::new(); + let (mf, tf) = (orderbook.maker_fee, orderbook.taker_fee); + orderbooks.insert((0, 1), orderbook); + let mut data = Data { + orderbooks, + accounts: all, + merkle_tree, + current_event_id: 0, + tvl: Amount::zero(), + }; + + // alice ask p=10, a=0.5 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (0, 1), user_id: UserId::from_low_u64_be(1), - in_or_out: InOrOut::In, - currency: 0, - amount: dec!(100), - block_number: 1, - extrinsic_hash: vec![0], + order_id: 1, + price: dec!(10), + amount: dec!(0.5), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, }; - let after = - assets::add_to_available(&mut all, &cmd0.user_id, cmd0.currency, cmd0.amount) - .unwrap(); - pp.prove_assets_cmd( - &mut merkle_tree, - 1, - cmd0, - &assets::Balance::default(), - &after, + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = + assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(0, 1)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, ); - let cmd1 = AssetsCmd { - user_id: UserId::from_low_u64_be(2), - in_or_out: InOrOut::In, - currency: 1, - amount: dec!(1000), - block_number: 1, - extrinsic_hash: vec![0], + let cr = clearing::clear(&mut data.accounts, 3, &(0, 1), tf, mf, &mr, 0); + prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + } + // alice ask p=10, a=0.6 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (0, 1), + user_id: UserId::from_low_u64_be(1), + order_id: 2, + price: dec!(10), + amount: dec!(0.6), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, + }; + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = + assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(0, 1)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 4, &(0, 1), tf, mf, &mr, 0); + prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + } + // alice ask p=10, a=0.6 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (0, 1), + user_id: UserId::from_low_u64_be(1), + order_id: 3, + price: dec!(9.9), + amount: dec!(0.1), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, }; - let transfer_again = - assets::add_to_available(&mut all, &cmd1.user_id, cmd1.currency, cmd1.amount) - .unwrap(); - pp.prove_assets_cmd(&mut merkle_tree, 1, cmd1, &after, &transfer_again); - - let mut orderbooks = std::collections::HashMap::new(); - let (mf, tf) = (orderbook.maker_fee, orderbook.taker_fee); - orderbooks.insert((0, 1), orderbook); - let mut data = Data { - orderbooks, - accounts: all, - merkle_tree, - current_event_id: 0, - tvl: Amount::zero(), + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = + assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(0, 1)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 5, &(0, 1), tf, mf, &mr, 0); + prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + } + // bob p=9.9, a=0.5 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (0, 1), + user_id: UserId::from_low_u64_be(2), + order_id: 4, + price: dec!(9.9), + amount: dec!(0.5), + ask_or_bid: AskOrBid::Bid, + nonce: 1, + signature: vec![0], + broker: None, }; - - // alice ask p=10, a=0.5 - { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(1), - order_id: 1, - price: dec!(10), - amount: dec!(0.5), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(0, 1)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, - ); - let cr = clearing::clear(&mut data.accounts, 3, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, - ); - } - // alice ask p=10, a=0.6 + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = + assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(0, 1)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 6, &(0, 1), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + // bid p=9.9, a=0.5 { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(1), - order_id: 2, - price: dec!(10), - amount: dec!(0.6), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(0, 1)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, + assert_eq!(proof.leaves.len(), 7); + // best price a,b + assert_eq!( + split_h256_u128(&proof.leaves[5].old_v), + (dec!(9.9).to_amount(), 0) ); - let cr = clearing::clear(&mut data.accounts, 4, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, + assert_eq!( + split_h256_u128(&proof.leaves[5].new_v), + (dec!(10).to_amount(), dec!(9.9).to_amount()) ); - } - // alice ask p=10, a=0.6 - { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(1), - order_id: 3, - price: dec!(9.9), - amount: dec!(0.1), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(0, 1)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, + assert_eq!( + &proof.leaves[5].old_v, + &hex::decode( + "00005e2c8cdd6389000000000000000000000000000000000000000000000000" + ) + .unwrap()[..] ); - let cr = clearing::clear(&mut data.accounts, 5, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, + assert_eq!( + &proof.leaves[5].new_v, + &hex::decode( + "0000e8890423c78a000000000000000000005e2c8cdd63890000000000000000" + ) + .unwrap()[..] ); - } - // bob p=9.9, a=0.5 - { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(2), - order_id: 4, - price: dec!(9.9), - amount: dec!(0.5), - ask_or_bid: AskOrBid::Bid, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(0, 1)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, + // p=9.9 + assert_eq!( + split_h256_u128_sum(&proof.leaves[6].old_v), + dec!(0.1).to_amount() ); - let cr = clearing::clear(&mut data.accounts, 6, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, + assert_eq!( + split_h256_u128_sum(&proof.leaves[6].new_v), + dec!(0.4).to_amount() ); - } - }); - // ignore transfer in - rx.recv().unwrap(); - rx.recv().unwrap(); - /* - * ignore ask - * 1. p=10, a=0.5 - * 2. p=10, a=0.6 - * 3. p=9.9, a=0.1 - */ - rx.recv().unwrap(); - rx.recv().unwrap(); - rx.recv().unwrap(); - // bid p=9.9, a=0.5 - { - let proof = rx.recv().unwrap(); - assert_eq!(proof.leaves.len(), 7); - // best price a,b - assert_eq!( - split_h256_u128(&proof.leaves[5].old_v), - (dec!(9.9).to_amount(), 0) - ); - assert_eq!( - split_h256_u128(&proof.leaves[5].new_v), - (dec!(10).to_amount(), dec!(9.9).to_amount()) - ); - assert_eq!( - &proof.leaves[5].old_v, - &hex::decode("00005e2c8cdd6389000000000000000000000000000000000000000000000000") - .unwrap()[..] - ); - assert_eq!( - &proof.leaves[5].new_v, - &hex::decode("0000e8890423c78a000000000000000000005e2c8cdd63890000000000000000") - .unwrap()[..] - ); - // p=9.9 - assert_eq!( - split_h256_u128_sum(&proof.leaves[6].old_v), - dec!(0.1).to_amount() - ); - assert_eq!( - split_h256_u128_sum(&proof.leaves[6].new_v), - dec!(0.4).to_amount() - ); - let maker_accounts = 2u8; - let pages = 1u8; - let (base, quote) = (0, 1); - let leaves_count = (4u8 + maker_accounts + pages) as usize; - assert!(proof.leaves.len() == leaves_count); - assert!(maker_accounts % 2 == 0); - let price = dec!(9.9).to_amount(); - let amount = dec!(0.5).to_amount(); - let base_charged = dec!(0.0001).to_amount(); - - let (ask0, bid0) = proof.leaves[0].split_old_to_u128(); - let (ask1, bid1) = proof.leaves[0].split_new_to_u128(); - let ask_delta = ask0 - ask1; - let bid_delta = bid1 - bid0; - let taker_base = &proof.leaves[maker_accounts as usize + 1]; - let (tba0, tbf0) = taker_base.split_old_to_u128(); - let (tba1, tbf1) = taker_base.split_new_to_u128(); - let tb_delta = (tba1 + tbf1) - (tba0 + tbf0); - - let best_price = &proof.leaves[maker_accounts as usize + 3]; - let (b, q) = best_price.try_get_symbol().unwrap(); - assert!(b == base && q == quote); - let (best_ask0, best_bid0) = best_price.split_old_to_u128(); - let (best_ask1, best_bid1) = best_price.split_new_to_u128(); - - if ask_delta != 0 { - // trading happened - assert!(pages > 0 && price >= best_ask0,); - // best_ask0 <= page0 < page1 < .. < pagen <= best_ask1 - let mut pre_best = best_ask0; - let mut taken_asks = 0u128; - for i in 0..pages as usize - 1 { - let page = &proof.leaves[maker_accounts as usize + 4 + i]; - let (b, q, p) = page.try_get_orderpage().unwrap(); - assert!(b == base && q == quote,); - assert!(pre_best <= p,); - pre_best = p; - assert!(page.split_new_to_sum() == 0); - taken_asks += page.split_old_to_sum(); - } - if bid_delta != 0 { - // partial_filled - let taker_price_page = proof.leaves.last().unwrap(); - let (b, q, p) = taker_price_page.try_get_orderpage().unwrap(); - assert!(b == base && q == quote && p == price,); - assert!(best_bid1 == price,); - let prv_is_maker = taker_price_page.split_old_to_sum(); - let now_is_taker = taker_price_page.split_new_to_sum(); - assert!(taken_asks + prv_is_maker + now_is_taker == amount,); + let maker_accounts = 2u8; + let pages = 1u8; + let (base, quote) = (0, 1); + let leaves_count = (4u8 + maker_accounts + pages) as usize; + assert!(proof.leaves.len() == leaves_count); + assert!(maker_accounts % 2 == 0); + let price = dec!(9.9).to_amount(); + let amount = dec!(0.5).to_amount(); + let base_charged = dec!(0.0001).to_amount(); + + let (ask0, bid0) = proof.leaves[0].split_old_to_u128(); + let (ask1, bid1) = proof.leaves[0].split_new_to_u128(); + let ask_delta = ask0 - ask1; + let bid_delta = bid1 - bid0; + let taker_base = &proof.leaves[maker_accounts as usize + 1]; + let (tba0, tbf0) = taker_base.split_old_to_u128(); + let (tba1, tbf1) = taker_base.split_new_to_u128(); + let tb_delta = (tba1 + tbf1) - (tba0 + tbf0); + + let best_price = &proof.leaves[maker_accounts as usize + 3]; + let (b, q) = best_price.try_get_symbol().unwrap(); + assert!(b == base && q == quote); + let (best_ask0, best_bid0) = best_price.split_old_to_u128(); + let (best_ask1, best_bid1) = best_price.split_new_to_u128(); + + if ask_delta != 0 { + // trading happened + assert!(pages > 0 && price >= best_ask0,); + // best_ask0 <= page0 < page1 < .. < pagen <= best_ask1 + let mut pre_best = best_ask0; + let mut taken_asks = 0u128; + for i in 0..pages as usize - 1 { + let page = &proof.leaves[maker_accounts as usize + 4 + i]; + let (b, q, p) = page.try_get_orderpage().unwrap(); + assert!(b == base && q == quote,); + assert!(pre_best <= p,); + pre_best = p; + assert!(page.split_new_to_sum() == 0); + taken_asks += page.split_old_to_sum(); + } + if bid_delta != 0 { + // partial_filled + let taker_price_page = proof.leaves.last().unwrap(); + let (b, q, p) = taker_price_page.try_get_orderpage().unwrap(); + assert!(b == base && q == quote && p == price,); + assert!(best_bid1 == price,); + let prv_is_maker = taker_price_page.split_old_to_sum(); + let now_is_taker = taker_price_page.split_new_to_sum(); + assert!(taken_asks + prv_is_maker + now_is_taker == amount,); + } else { + // filled or conditional_canceled + let vanity_maker = proof.leaves.last().unwrap(); + let (b, q, p) = vanity_maker.try_get_orderpage().unwrap(); + assert!(b == base && q == quote && p == price,); + assert!(best_bid1 == best_bid0,); + let prv_is_maker = vanity_maker.split_old_to_sum(); + let now_is_maker = vanity_maker.split_new_to_sum(); + assert!( + tb_delta + base_charged == taken_asks + prv_is_maker - now_is_maker, + ); + } } else { - // filled or conditional_canceled - let vanity_maker = proof.leaves.last().unwrap(); - let (b, q, p) = vanity_maker.try_get_orderpage().unwrap(); - assert!(b == base && q == quote && p == price,); - assert!(best_bid1 == best_bid0,); - let prv_is_maker = vanity_maker.split_old_to_sum(); - let now_is_maker = vanity_maker.split_new_to_sum(); - assert!(tb_delta + base_charged == taken_asks + prv_is_maker - now_is_maker,); - } - } else { - // no trading - assert!(best_ask1 == best_ask0,); - if bid_delta != 0 { - // placed - let taker_price_page = proof.leaves.last().unwrap(); - let (b, q, p) = taker_price_page.try_get_orderpage().unwrap(); - assert!(b == base && q == quote && p == price,); - let prv_is_maker = taker_price_page.split_old_to_sum(); - let now_is_maker = taker_price_page.split_new_to_sum(); - assert!(amount == now_is_maker - prv_is_maker,); + // no trading + assert!(best_ask1 == best_ask0,); + if bid_delta != 0 { + // placed + let taker_price_page = proof.leaves.last().unwrap(); + let (b, q, p) = taker_price_page.try_get_orderpage().unwrap(); + assert!(b == base && q == quote && p == price,); + let prv_is_maker = taker_price_page.split_old_to_sum(); + let now_is_maker = taker_price_page.split_new_to_sum(); + assert!(amount == now_is_maker - prv_is_maker,); + } } } } @@ -1432,232 +1427,217 @@ mod test { #[test] pub fn test_canceling() { - let (tx, rx) = std::sync::mpsc::channel(); - std::thread::spawn(move || { - let mut merkle_tree = GlobalStates::default(); - let pp = Prover::new(tx); - let mut all = Accounts::new(); - let orderbook = construct_pair(); - let cmd0 = AssetsCmd { + let mut merkle_tree = GlobalStates::default(); + let mut all = Accounts::new(); + let orderbook = construct_pair(); + let cmd0 = AssetsCmd { + user_id: UserId::from_low_u64_be(1), + in_or_out: InOrOut::In, + currency: 0, + amount: dec!(100), + block_number: 1, + extrinsic_hash: vec![0], + }; + let after = + assets::add_to_available(&mut all, &cmd0.user_id, cmd0.currency, cmd0.amount).unwrap(); + prover::prove_assets_cmd( + &mut merkle_tree, + 1, + cmd0, + &assets::Balance::default(), + &after, + ); + let cmd1 = AssetsCmd { + user_id: UserId::from_low_u64_be(2), + in_or_out: InOrOut::In, + currency: 1, + amount: dec!(1000), + block_number: 1, + extrinsic_hash: vec![0], + }; + let transfer_again = + assets::add_to_available(&mut all, &cmd1.user_id, cmd1.currency, cmd1.amount).unwrap(); + prover::prove_assets_cmd(&mut merkle_tree, 1, cmd1, &after, &transfer_again); + + let mut orderbooks = std::collections::HashMap::new(); + let (mf, tf) = (orderbook.maker_fee, orderbook.taker_fee); + orderbooks.insert((0, 1), orderbook); + let mut data = Data { + orderbooks, + accounts: all, + merkle_tree, + current_event_id: 0, + tvl: Amount::zero(), + }; + + // alice ask p=10, a=1.1 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (0, 1), user_id: UserId::from_low_u64_be(1), - in_or_out: InOrOut::In, - currency: 0, - amount: dec!(100), - block_number: 1, - extrinsic_hash: vec![0], + order_id: 1, + price: dec!(10), + amount: dec!(1.1), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, }; - let after = - assets::add_to_available(&mut all, &cmd0.user_id, cmd0.currency, cmd0.amount) - .unwrap(); - pp.prove_assets_cmd( - &mut merkle_tree, - 1, - cmd0, - &assets::Balance::default(), - &after, + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = + assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(0, 1)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, ); - let cmd1 = AssetsCmd { - user_id: UserId::from_low_u64_be(2), - in_or_out: InOrOut::In, - currency: 1, - amount: dec!(1000), - block_number: 1, - extrinsic_hash: vec![0], + let cr = clearing::clear(&mut data.accounts, 3, &(0, 1), tf, mf, &mr, 0); + prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + } + // alice ask p=5, a=7.6 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = LimitCmd { + symbol: (0, 1), + user_id: UserId::from_low_u64_be(1), + order_id: 2, + price: dec!(5), + amount: dec!(7.6), + ask_or_bid: AskOrBid::Ask, + nonce: 1, + signature: vec![0], + broker: None, }; - let transfer_again = - assets::add_to_available(&mut all, &cmd1.user_id, cmd1.currency, cmd1.amount) - .unwrap(); - pp.prove_assets_cmd(&mut merkle_tree, 1, cmd1, &after, &transfer_again); - - let mut orderbooks = std::collections::HashMap::new(); - let (mf, tf) = (orderbook.maker_fee, orderbook.taker_fee); - orderbooks.insert((0, 1), orderbook); - let mut data = Data { - orderbooks, - accounts: all, - merkle_tree, - current_event_id: 0, - tvl: Amount::zero(), + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let (c, val) = + assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); + assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); + let mr = matcher::execute_limit( + data.orderbooks.get_mut(&(0, 1)).unwrap(), + cmd2.user_id, + cmd2.order_id, + cmd2.price, + cmd2.amount, + cmd2.ask_or_bid, + ); + let cr = clearing::clear(&mut data.accounts, 4, &(0, 1), tf, mf, &mr, 0); + prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + (cmd2, mf, tf).into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + } + // alice cancel 2 + { + let size = data.orderbooks.get(&(0, 1)).unwrap().size(); + let cmd2 = CancelCmd { + symbol: (0, 1), + user_id: UserId::from_low_u64_be(1), + order_id: 2, + nonce: 1, + signature: vec![0], }; - - // alice ask p=10, a=1.1 - { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(1), - order_id: 1, - price: dec!(10), - amount: dec!(1.1), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(0, 1)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, - ); - let cr = clearing::clear(&mut data.accounts, 3, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, - ); - } - // alice ask p=5, a=7.6 - { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = LimitCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(1), - order_id: 2, - price: dec!(5), - amount: dec!(7.6), - ask_or_bid: AskOrBid::Ask, - nonce: 1, - signature: vec![0], - broker: None, - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let (c, val) = - assets::freeze_if(&cmd2.symbol, cmd2.ask_or_bid, cmd2.price, cmd2.amount); - assets::try_freeze(&mut data.accounts, &cmd2.user_id, c, val).unwrap(); - let mr = matcher::execute_limit( - data.orderbooks.get_mut(&(0, 1)).unwrap(), - cmd2.user_id, - cmd2.order_id, - cmd2.price, - cmd2.amount, - cmd2.ask_or_bid, - ); - let cr = clearing::clear(&mut data.accounts, 4, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - (cmd2, mf, tf).into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, - ); - } - // alice cancel 2 + let (best_ask_before, best_bid_before) = + data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); + let taker_base_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); + let taker_quote_before = + assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); + let mr = + matcher::cancel(data.orderbooks.get_mut(&(0, 1)).unwrap(), cmd2.order_id).unwrap(); + let cr = clearing::clear(&mut data.accounts, 5, &(0, 1), tf, mf, &mr, 0); + let proof = prover::prove_trade_cmd( + &mut data, + cmd2.nonce, + cmd2.signature.clone(), + cmd2.into(), + size.0, + size.1, + best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), + best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), + &taker_base_before, + &taker_quote_before, + &cr, + &mr, + ); + // cancel p=5, a=7.6 { - let size = data.orderbooks.get(&(0, 1)).unwrap().size(); - let cmd2 = CancelCmd { - symbol: (0, 1), - user_id: UserId::from_low_u64_be(1), - order_id: 2, - nonce: 1, - signature: vec![0], - }; - let (best_ask_before, best_bid_before) = - data.orderbooks.get(&(0, 1)).unwrap().get_size_of_best(); - let taker_base_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.0); - let taker_quote_before = - assets::get_balance_to_owned(&data.accounts, &cmd2.user_id, cmd2.symbol.1); - let mr = matcher::cancel(data.orderbooks.get_mut(&(0, 1)).unwrap(), cmd2.order_id) - .unwrap(); - let cr = clearing::clear(&mut data.accounts, 5, &(0, 1), tf, mf, &mr, 0); - pp.prove_trade_cmd( - &mut data, - cmd2.nonce, - cmd2.signature.clone(), - cmd2.into(), - size.0, - size.1, - best_ask_before.unwrap_or((Decimal::zero(), Decimal::zero())), - best_bid_before.unwrap_or((Decimal::zero(), Decimal::zero())), - &taker_base_before, - &taker_quote_before, - &cr, - &mr, + let (base, quote) = (0, 1); + let leaves = proof.leaves; + let account = UserId::from_low_u64_be(1); + assert!(leaves.len() == 5,); + let (b, q) = leaves[0].try_get_symbol().unwrap(); + assert!(b == base && q == quote,); + let (ask0, bid0) = leaves[0].split_old_to_u128(); + let (ask1, bid1) = leaves[0].split_new_to_u128(); + let ask_delta = ask0 - ask1; + let bid_delta = bid0 - bid1; + assert!(ask_delta + bid_delta != 0,); + assert!(ask_delta & bid_delta == 0,); + + let (b, id) = leaves[1].try_get_account().unwrap(); + assert!(b == base,); + assert!(>::as_ref(&account) == &id); + let (ba0, bf0) = leaves[1].split_old_to_u128(); + let (ba1, bf1) = leaves[1].split_new_to_u128(); + assert!(ba0 + bf0 == ba1 + bf1,); + + let (q, id) = leaves[2].try_get_account().unwrap(); + assert!(q == quote,); + assert!(>::as_ref(&account) == &id); + let (qa0, qf0) = leaves[2].split_old_to_u128(); + let (qa1, qf1) = leaves[2].split_new_to_u128(); + assert!(qa0 + qf0 == qa1 + qf1,); + + let (best_ask0, best_bid0) = leaves[3].split_old_to_u128(); + let (b, q, cancel_at) = leaves[4].try_get_orderpage().unwrap(); + assert!( + b == base && q == quote && (cancel_at >= best_ask0 || cancel_at <= best_bid0), ); - } - }); - // ignore transfer in - rx.recv().unwrap(); - rx.recv().unwrap(); - /* - * ignore ask - * 1. p=10, a=1.1 - * 2. p=5, a=7.6 - */ - rx.recv().unwrap(); - rx.recv().unwrap(); - // cancel p=5, a=7.6 - { - let proof = rx.recv().unwrap(); - let (base, quote) = (0, 1); - let leaves = proof.leaves; - let account = UserId::from_low_u64_be(1); - assert!(leaves.len() == 5,); - let (b, q) = leaves[0].try_get_symbol().unwrap(); - assert!(b == base && q == quote,); - let (ask0, bid0) = leaves[0].split_old_to_u128(); - let (ask1, bid1) = leaves[0].split_new_to_u128(); - let ask_delta = ask0 - ask1; - let bid_delta = bid0 - bid1; - assert!(ask_delta + bid_delta != 0,); - assert!(ask_delta & bid_delta == 0,); - - let (b, id) = leaves[1].try_get_account().unwrap(); - assert!(b == base,); - assert!(>::as_ref(&account) == &id); - let (ba0, bf0) = leaves[1].split_old_to_u128(); - let (ba1, bf1) = leaves[1].split_new_to_u128(); - assert!(ba0 + bf0 == ba1 + bf1,); - - let (q, id) = leaves[2].try_get_account().unwrap(); - assert!(q == quote,); - assert!(>::as_ref(&account) == &id); - let (qa0, qf0) = leaves[2].split_old_to_u128(); - let (qa1, qf1) = leaves[2].split_new_to_u128(); - assert!(qa0 + qf0 == qa1 + qf1,); - - let (best_ask0, best_bid0) = leaves[3].split_old_to_u128(); - let (b, q, cancel_at) = leaves[4].try_get_orderpage().unwrap(); - assert!(b == base && q == quote && (cancel_at >= best_ask0 || cancel_at <= best_bid0),); - let before_cancel = leaves[4].split_old_to_sum(); - let after_cancel = leaves[4].split_new_to_sum(); - if cancel_at >= best_ask0 { - assert!(ask_delta == before_cancel - after_cancel,); - } else { - assert!(bid_delta == before_cancel - after_cancel,); + let before_cancel = leaves[4].split_old_to_sum(); + let after_cancel = leaves[4].split_new_to_sum(); + if cancel_at >= best_ask0 { + assert!(ask_delta == before_cancel - after_cancel,); + } else { + assert!(bid_delta == before_cancel - after_cancel,); + } } } } diff --git a/engine/src/fusotao/scanner.rs b/engine/src/fusotao/scanner.rs new file mode 100644 index 0000000..7ceef0d --- /dev/null +++ b/engine/src/fusotao/scanner.rs @@ -0,0 +1,194 @@ +// Copyright 2021-2023 UINB Technologies Pte. Ltd. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use super::*; +use crate::input::Command; +use anyhow::anyhow; +use node_api::decoder::{Raw, RuntimeDecoder}; +use parity_scale_codec::Decode; +use std::{sync::atomic::Ordering, sync::mpsc::Sender, thread, time::Duration}; + +pub fn init(tx: Sender, connector: FusoConnector, state: Arc) { + if C.dry_run.is_some() { + return; + } + let decoder = RuntimeDecoder::new(connector.api.metadata.clone()); + let receipts = connector.fully_sync_chain(state.clone()).unwrap(); + for cmd in receipts.into_iter() { + tx.send(Input::new(cmd)).unwrap(); + } + thread::spawn(move || loop { + let at = state.scanning_progress.load(Ordering::Relaxed); + if let Ok((finalized, _)) = connector.get_finalized_block() { + log::info!("block {} finalized, ours {}", finalized, at); + state.chain_height.store(finalized, Ordering::Relaxed); + if finalized >= at { + match handle_finalized_block(&connector, at, &decoder, &state, &tx) { + Ok(()) => { + state.scanning_progress.fetch_add(1, Ordering::Relaxed); + log::info!("block {} finalized", at); + } + Err(e) => log::error!("{:?}", e), + } + } else { + thread::sleep(Duration::from_millis(6000)); + } + } else { + log::error!("scanning connection temporarily lost, retrying..."); + thread::sleep(Duration::from_millis(1000)); + } + }); +} + +fn handle_finalized_block( + connector: &FusoConnector, + at: u32, + decoder: &RuntimeDecoder, + state: &Arc, + to_seq: &Sender, +) -> anyhow::Result<()> { + use hex::ToHex; + let hash = connector + .api + .get_block_hash(Some(at))? + .ok_or(anyhow!("block {} not ready", at))?; + let key = connector + .api + .metadata + .storage_value_key("System", "Events") + .map_err(|e| anyhow!("Read storage failed: {:?}", e))?; + let payload = connector + .api + .get_opaque_storage_by_key_hash(key, Some(hash))?; + let events = decoder + .decode_events(&mut payload.unwrap_or(vec![]).as_slice()) + .unwrap_or(vec![]); + for (_, event) in events.into_iter() { + if let Raw::Event(raw) = event { + match (raw.pallet.as_ref(), raw.variant.as_ref()) { + ("Verifier", "TokenHosted") => { + let decoded = TokenHostedEvent::decode(&mut &raw.data[..])?; + if decoded.dominator == connector.get_pubkey() { + let mut cmd = Command::default(); + cmd.cmd = crate::cmd::TRANSFER_IN; + cmd.currency = Some(decoded.token_id); + cmd.amount = to_decimal_represent(decoded.amount); + cmd.user_id = Some(format!("{}", decoded.fund_owner)); + cmd.block_number = Some(at); + cmd.extrinsic_hash = Some(hash.encode_hex()); + to_seq.send(Input::new(cmd))?; + } + } + ("Verifier", "TokenRevoked") => { + let decoded = TokenHostedEvent::decode(&mut &raw.data[..])?; + if decoded.dominator == connector.get_pubkey() { + let mut cmd = Command::default(); + cmd.cmd = crate::cmd::TRANSFER_OUT; + cmd.currency = Some(decoded.token_id); + cmd.amount = to_decimal_represent(decoded.amount); + cmd.user_id = Some(format!("{}", decoded.fund_owner)); + cmd.block_number = Some(at); + cmd.extrinsic_hash = Some(hash.encode_hex()); + to_seq.send(Input::new(cmd))?; + } + } + ("Token", "TokenIssued") => { + let decoded = TokenIssuedEvent::decode(&mut &raw.data[..])?; + let key = connector + .api + .metadata + .storage_map_key::("Token", "Tokens", decoded.token_id) + .map_err(|e| anyhow!("Read storage failed: {:?}", e))?; + let payload = connector + .api + .get_opaque_storage_by_key_hash(key, Some(hash))? + .ok_or(anyhow::anyhow!(""))?; + let token = OnchainToken::decode(&mut payload.as_slice())?; + state.currencies.insert(decoded.token_id, token); + } + ("Market", "BrokerRegistered") => { + let decoded = BrokerRegisteredEvent::decode(&mut &raw.data[..])?; + state.brokers.insert(decoded.broker_account, rand::random()); + } + ("Market", "MarketOpened") => { + let decoded = MarketOpenedEvent::decode(&mut &raw.data[..])?; + if decoded.dominator == connector.get_pubkey() { + let mut cmd = Command::default(); + let milli = Decimal::from_str("0.001").unwrap(); + cmd.cmd = crate::cmd::UPDATE_SYMBOL; + cmd.base = Some(decoded.base); + cmd.quote = Some(decoded.quote); + cmd.open = Some(true); + cmd.base_scale = Some(decoded.base_scale.into()); + cmd.quote_scale = Some(decoded.quote_scale.into()); + cmd.taker_fee = Some(milli); + cmd.maker_fee = Some(milli); + cmd.min_amount = to_decimal_represent(decoded.min_base); + // DEPRECATED + cmd.base_maker_fee = Some(milli); + cmd.base_taker_fee = Some(milli); + // useless + cmd.fee_times = Some(1); + // useless + cmd.min_vol = Some(Decimal::from_str("10").unwrap()); + cmd.enable_market_order = Some(false); + to_seq.send(Input::new(cmd))?; + state.symbols.insert( + (decoded.base, decoded.quote), + OnchainSymbol { + min_base: decoded.min_base, + base_scale: decoded.base_scale, + quote_scale: decoded.quote_scale, + status: MarketStatus::Open, + trading_rewards: true, + liquidity_rewards: true, + unavailable_after: None, + }, + ); + } + } + ("Market", "MarketClosed") => { + let decoded = MarketClosedEvent::decode(&mut &raw.data[..])?; + if decoded.dominator == connector.get_pubkey() { + let market = state.symbols.remove(&(decoded.base, decoded.quote)); + let mut cmd = Command::default(); + let milli = Decimal::from_str("0.001").unwrap(); + cmd.cmd = crate::cmd::UPDATE_SYMBOL; + cmd.base = Some(decoded.base); + cmd.quote = Some(decoded.quote); + cmd.open = Some(false); + cmd.taker_fee = Some(milli); + cmd.maker_fee = Some(milli); + let (base_scale, quote_scale, min_amount) = market + .map(|(_, m)| (m.base_scale, m.quote_scale, m.min_base)) + .ok_or(anyhow!(""))?; + cmd.base_scale = Some(base_scale.into()); + cmd.quote_scale = Some(quote_scale.into()); + cmd.min_amount = to_decimal_represent(min_amount); + // DEPRECATED + cmd.base_maker_fee = Some(milli); + cmd.base_taker_fee = Some(milli); + cmd.fee_times = Some(1); + // useless + cmd.min_vol = Some(Decimal::from_str("10").unwrap()); + cmd.enable_market_order = Some(false); + to_seq.send(Input::new(cmd))?; + } + } + _ => {} + } + } + } + Ok(()) +} diff --git a/engine/src/input/mod.rs b/engine/src/input/mod.rs index 5d39bd3..b786db3 100644 --- a/engine/src/input/mod.rs +++ b/engine/src/input/mod.rs @@ -12,50 +12,310 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::core::*; +use crate::{core::*, fusotao::ToBlockChainNumeric}; +use anyhow::{anyhow, ensure}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; +use std::str::FromStr; -pub mod sequence; +pub mod sequencer; pub mod server; -pub mod whistle; -pub use sequence::*; -pub use whistle::*; - -/// Input -/// sequence = command + database_header -> event -/// whistle = command + network_header -> inspection #[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] -pub enum Input { - Modifier(Sequence), - NonModifier(Whistle), +pub struct Input { + pub session: u64, + pub req_id: u64, + pub sequence: u64, + pub cmd: Command, +} + +impl Input { + pub fn new_with_req(cmd: Command, session: u64, req_id: u64) -> Self { + Self { + session, + req_id, + sequence: 0, + cmd, + } + } + + pub fn new(cmd: Command) -> Self { + Self { + session: 0, + req_id: 0, + sequence: 0, + cmd, + } + } +} + +impl TryInto for Input { + type Error = anyhow::Error; + + fn try_into(self) -> anyhow::Result { + match self.cmd.cmd { + ASK_LIMIT | BID_LIMIT => { + let amount = self.cmd.amount.ok_or(anyhow!(""))?; + let price = self.cmd.price.ok_or(anyhow!(""))?; + ensure!( + price.is_sign_positive() && price.scale() <= 7, + "invalid price numeric" + ); + ensure!( + amount.is_sign_positive() && amount.scale() <= 7, + "invalid amount numeric" + ); + let vol = amount.checked_mul(price).ok_or(anyhow!(""))?; + ensure!(vol.validate(), "overflow"); + let cmd = LimitCmd { + symbol: self.cmd.symbol().ok_or(anyhow!(""))?, + user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, + order_id: self.cmd.order_id.ok_or(anyhow!(""))?, + price, + amount, + ask_or_bid: AskOrBid::try_from(self.cmd.cmd)?, + nonce: self.cmd.nonce.ok_or(anyhow!(""))?, + signature: hex::decode(self.cmd.signature.ok_or(anyhow!(""))?)?, + broker: self + .cmd + .broker + .map(|b| UserId::from_str(b.as_ref())) + .transpose()?, + }; + Ok(Event::Limit( + self.sequence, + cmd, + self.cmd.timestamp.unwrap_or_default(), + self.session, + self.req_id, + )) + } + CANCEL => Ok(Event::Cancel( + self.sequence, + CancelCmd { + symbol: self.cmd.symbol().ok_or(anyhow!(""))?, + user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, + order_id: self.cmd.order_id.ok_or(anyhow!(""))?, + nonce: self.cmd.nonce.ok_or(anyhow!(""))?, + signature: hex::decode(self.cmd.signature.ok_or(anyhow!(""))?)?, + }, + self.cmd.timestamp.unwrap_or_default(), + self.session, + self.req_id, + )), + TRANSFER_OUT => Ok(Event::TransferOut( + self.sequence, + AssetsCmd { + user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, + in_or_out: InOrOut::Out, + currency: self.cmd.currency.ok_or(anyhow!(""))?, + amount: self + .cmd + .amount + .filter(|a| a.is_sign_positive()) + .ok_or(anyhow!(""))?, + block_number: self.cmd.block_number.ok_or(anyhow!(""))?, + extrinsic_hash: hex::decode(self.cmd.extrinsic_hash.ok_or(anyhow!(""))?)?, + }, + )), + TRANSFER_IN => Ok(Event::TransferIn( + self.sequence, + AssetsCmd { + user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, + in_or_out: InOrOut::In, + currency: self.cmd.currency.ok_or(anyhow!(""))?, + amount: self + .cmd + .amount + .filter(|a| a.is_sign_positive()) + .ok_or(anyhow!(""))?, + block_number: self.cmd.block_number.ok_or(anyhow!(""))?, + extrinsic_hash: hex::decode(self.cmd.extrinsic_hash.ok_or(anyhow!(""))?)?, + }, + )), + UPDATE_SYMBOL => Ok(Event::UpdateSymbol( + self.sequence, + SymbolCmd { + symbol: self.cmd.symbol().ok_or(anyhow!(""))?, + open: self.cmd.open.ok_or(anyhow!(""))?, + base_scale: self.cmd.base_scale.filter(|b| *b <= 7).ok_or(anyhow!(""))?, + quote_scale: self + .cmd + .quote_scale + .filter(|q| *q <= 7) + .ok_or(anyhow!(""))?, + taker_fee: self + .cmd + .taker_fee + .filter(|f| f.is_sign_positive()) + .ok_or(anyhow!(""))?, + maker_fee: self + .cmd + .maker_fee + .filter(|f| f.is_sign_positive()) + .ok_or(anyhow!(""))?, + base_maker_fee: self + .cmd + .base_maker_fee + .filter(|f| f.is_sign_positive()) + .or(self.cmd.maker_fee) + .filter(|f| f.is_sign_positive()) + .ok_or(anyhow!(""))?, + base_taker_fee: self + .cmd + .base_taker_fee + .filter(|f| f.is_sign_positive()) + .or(self.cmd.taker_fee) + .filter(|f| f.is_sign_positive()) + .ok_or(anyhow!(""))?, + fee_times: self.cmd.fee_times.unwrap_or(1), + min_amount: self + .cmd + .min_amount + .filter(|f| f.is_sign_positive()) + .ok_or(anyhow!(""))?, + min_vol: self + .cmd + .min_vol + .filter(|f| f.is_sign_positive()) + .ok_or(anyhow!(""))?, + enable_market_order: self.cmd.enable_market_order.ok_or(anyhow!(""))?, + }, + )), + QUERY_ORDER => Ok(Event::QueryOrder( + self.cmd.symbol().ok_or(anyhow!(""))?, + self.cmd.order_id.ok_or(anyhow!(""))?, + self.session, + self.req_id, + )), + QUERY_BALANCE => Ok(Event::QueryBalance( + UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, + self.cmd.currency.ok_or(anyhow!(""))?, + self.session, + self.req_id, + )), + QUERY_ACCOUNTS => Ok(Event::QueryAccounts( + UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, + self.session, + self.req_id, + )), + QUERY_EXCHANGE_FEE => Ok(Event::QueryExchangeFee( + self.cmd.symbol().ok_or(anyhow!(""))?, + self.session, + self.req_id, + )), + DUMP => Ok(Event::Dump(self.cmd.event_id.ok_or(anyhow!(""))?)), + _ => Err(anyhow!("Unsupported Command")), + } + } } unsafe impl Send for Input {} +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum Event { + // write + Limit(EventId, LimitCmd, Timestamp, u64, u64), + Cancel(EventId, CancelCmd, Timestamp, u64, u64), + TransferOut(EventId, AssetsCmd), + TransferIn(EventId, AssetsCmd), + UpdateSymbol(EventId, SymbolCmd), + // read + QueryOrder(Symbol, OrderId, u64, u64), + QueryBalance(UserId, Currency, u64, u64), + QueryAccounts(UserId, u64, u64), + QueryExchangeFee(Symbol, u64, u64), + // the `EventId` has been executed + Dump(EventId), +} + +impl Event {} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct LimitCmd { + pub symbol: Symbol, + pub user_id: UserId, + // TODO this field is deprecated + pub order_id: OrderId, + pub price: Price, + pub amount: Amount, + pub ask_or_bid: AskOrBid, + pub nonce: u32, + pub signature: Vec, + pub broker: Option, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct CancelCmd { + pub symbol: Symbol, + pub user_id: UserId, + pub order_id: OrderId, + pub nonce: u32, + pub signature: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum InOrOut { + In, + Out, +} + +impl std::convert::TryFrom for InOrOut { + type Error = anyhow::Error; + + fn try_from(x: u32) -> anyhow::Result { + match x { + cmd::TRANSFER_IN => Ok(InOrOut::In), + cmd::TRANSFER_OUT => Ok(InOrOut::Out), + _ => Err(anyhow::anyhow!("")), + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct AssetsCmd { + pub user_id: UserId, + pub in_or_out: InOrOut, + pub currency: Currency, + pub amount: Amount, + pub block_number: u32, + pub extrinsic_hash: Vec, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct SymbolCmd { + pub symbol: Symbol, + pub open: bool, + pub base_scale: Scale, + pub quote_scale: Scale, + pub taker_fee: Fee, + pub maker_fee: Fee, + pub base_maker_fee: Fee, + pub base_taker_fee: Fee, + pub fee_times: u32, + pub min_amount: Amount, + pub min_vol: Vol, + pub enable_market_order: bool, +} + pub mod cmd { - // from db to core pub const ASK_LIMIT: u32 = 0; pub const BID_LIMIT: u32 = 1; pub const CANCEL: u32 = 4; - pub const CANCEL_ALL: u32 = 5; + pub const CANCEL_ALL: u32 = 5; /* DEPRECATED */ pub const TRANSFER_OUT: u32 = 10; pub const TRANSFER_IN: u32 = 11; pub const UPDATE_SYMBOL: u32 = 13; - // from tcp to core pub const QUERY_ORDER: u32 = 14; pub const QUERY_BALANCE: u32 = 15; pub const QUERY_ACCOUNTS: u32 = 16; pub const QUERY_EXCHANGE_FEE: u32 = 21; - // from timer to core pub const DUMP: u32 = 17; - pub const UPDATE_DEPTH: u32 = 18; - pub const CONFIRM_ALL: u32 = 19; + pub const UPDATE_DEPTH: u32 = 18; /* DEPRECATED */ + pub const CONFIRM_ALL: u32 = 19; /* DEPRECATED */ - // from tcp to shared pub const QUERY_PROVING_PERF_INDEX: u32 = 22; /* DEPRECATED */ pub const QUERY_SCAN_HEIGHT: u32 = 23; /* DEPRECATED */ pub const QUERY_OPEN_MARKETS: u32 = 24; @@ -134,7 +394,6 @@ impl Command { Some((self.base?, self.quote?)) } - #[must_use] pub const fn is_querying_core_data(&self) -> bool { matches!( self.cmd, @@ -142,7 +401,6 @@ impl Command { ) } - #[must_use] pub const fn is_querying_share_data(&self) -> bool { matches!( self.cmd, @@ -155,7 +413,6 @@ impl Command { ) } - #[must_use] pub const fn is_internally_generated(&self) -> bool { matches!(self.cmd, UPDATE_DEPTH | CONFIRM_ALL | DUMP) } @@ -172,8 +429,9 @@ const _PAYLOAD_MASK: u64 = 0x0000_ffff_0000_0000; const _CHK_SUM_MASK: u64 = 0x0000_0000_ffff_0000; const _ERR_RSP_MASK: u64 = 0x0000_0000_0000_0001; const _NXT_FRM_MASK: u64 = 0x0000_0000_0000_0002; -pub const MAX_FRAME_SIZE: usize = 64 * 1024; + /// header = 0x0316<2bytes payload len><2bytes cheskcum><2bytes flag> +pub const MAX_FRAME_SIZE: usize = 64 * 1024; impl Message { pub fn new(req_id: u64, payload: Vec) -> Self { diff --git a/engine/src/input/sequence.rs b/engine/src/input/sequence.rs deleted file mode 100644 index 5d1a528..0000000 --- a/engine/src/input/sequence.rs +++ /dev/null @@ -1,514 +0,0 @@ -// Copyright 2021-2023 UINB Technologies Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::fusotao::ToBlockChainNumeric; -use crate::{cmd::*, config::C, core::*, db::DB, input::*, orderbook::AskOrBid}; -use anyhow::{anyhow, ensure}; -use mysql::{prelude::*, *}; -use serde::{Deserialize, Serialize}; -use std::{ - convert::{TryFrom, TryInto}, - str::FromStr, - sync::{ - atomic::{AtomicBool, Ordering}, - mpsc::Sender, - Arc, - }, - time::{Duration, SystemTime}, -}; -use thiserror::Error; - -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct Sequence { - pub id: u64, - pub cmd: Command, - pub status: u32, - pub timestamp: u64, -} - -impl TryInto for Sequence { - type Error = anyhow::Error; - - fn try_into(self) -> anyhow::Result { - match self.cmd.cmd { - ASK_LIMIT | BID_LIMIT => { - let amount = self.cmd.amount.ok_or(anyhow!(""))?; - let price = self.cmd.price.ok_or(anyhow!(""))?; - ensure!( - price.is_sign_positive() && price.scale() <= 7, - "invalid price numeric" - ); - ensure!( - amount.is_sign_positive() && amount.scale() <= 7, - "invalid amount numeric" - ); - let vol = amount.checked_mul(price).ok_or(anyhow!(""))?; - ensure!(vol.validate(), "overflow"); - let cmd = LimitCmd { - symbol: self.cmd.symbol().ok_or(anyhow!(""))?, - user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, - order_id: self.cmd.order_id.ok_or(anyhow!(""))?, - price, - amount, - ask_or_bid: AskOrBid::try_from(self.cmd.cmd)?, - nonce: self.cmd.nonce.ok_or(anyhow!(""))?, - signature: hex::decode(self.cmd.signature.ok_or(anyhow!(""))?)?, - broker: self - .cmd - .broker - .map(|b| UserId::from_str(b.as_ref())) - .transpose()?, - }; - Ok(Event::Limit(self.id, cmd, self.timestamp)) - } - CANCEL => Ok(Event::Cancel( - self.id, - CancelCmd { - symbol: self.cmd.symbol().ok_or(anyhow!(""))?, - user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, - order_id: self.cmd.order_id.ok_or(anyhow!(""))?, - nonce: self.cmd.nonce.ok_or(anyhow!(""))?, - signature: hex::decode(self.cmd.signature.ok_or(anyhow!(""))?)?, - }, - self.timestamp, - )), - TRANSFER_OUT => Ok(Event::TransferOut( - self.id, - AssetsCmd { - user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, - in_or_out: InOrOut::Out, - currency: self.cmd.currency.ok_or(anyhow!(""))?, - amount: self - .cmd - .amount - .filter(|a| a.is_sign_positive()) - .ok_or(anyhow!(""))?, - block_number: self.cmd.block_number.ok_or(anyhow!(""))?, - extrinsic_hash: hex::decode(self.cmd.extrinsic_hash.ok_or(anyhow!(""))?)?, - }, - self.timestamp, - )), - TRANSFER_IN => Ok(Event::TransferIn( - self.id, - AssetsCmd { - user_id: UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, - in_or_out: InOrOut::In, - currency: self.cmd.currency.ok_or(anyhow!(""))?, - amount: self - .cmd - .amount - .filter(|a| a.is_sign_positive()) - .ok_or(anyhow!(""))?, - block_number: self.cmd.block_number.ok_or(anyhow!(""))?, - extrinsic_hash: hex::decode(self.cmd.extrinsic_hash.ok_or(anyhow!(""))?)?, - }, - self.timestamp, - )), - UPDATE_SYMBOL => Ok(Event::UpdateSymbol( - self.id, - SymbolCmd { - symbol: self.cmd.symbol().ok_or(anyhow!(""))?, - open: self.cmd.open.ok_or(anyhow!(""))?, - base_scale: self.cmd.base_scale.filter(|b| *b <= 7).ok_or(anyhow!(""))?, - quote_scale: self - .cmd - .quote_scale - .filter(|q| *q <= 7) - .ok_or(anyhow!(""))?, - taker_fee: self - .cmd - .taker_fee - .filter(|f| f.is_sign_positive()) - .ok_or(anyhow!(""))?, - maker_fee: self - .cmd - .maker_fee - .filter(|f| f.is_sign_positive()) - .ok_or(anyhow!(""))?, - base_maker_fee: self - .cmd - .base_maker_fee - .filter(|f| f.is_sign_positive()) - .or(self.cmd.maker_fee) - .filter(|f| f.is_sign_positive()) - .ok_or(anyhow!(""))?, - base_taker_fee: self - .cmd - .base_taker_fee - .filter(|f| f.is_sign_positive()) - .or(self.cmd.taker_fee) - .filter(|f| f.is_sign_positive()) - .ok_or(anyhow!(""))?, - fee_times: self.cmd.fee_times.unwrap_or(1), - min_amount: self - .cmd - .min_amount - .filter(|f| f.is_sign_positive()) - .ok_or(anyhow!(""))?, - min_vol: self - .cmd - .min_vol - .filter(|f| f.is_sign_positive()) - .ok_or(anyhow!(""))?, - enable_market_order: self.cmd.enable_market_order.ok_or(anyhow!(""))?, - }, - self.timestamp, - )), - // CANCEL_ALL => Ok(Event::CancelAll( - // self.id, - // self.cmd.symbol().ok_or(anyhow!(""))?, - // self.timestamp, - // )), - _ => Err(anyhow!("Unsupported Command")), - } - } -} - -impl Sequence { - #[must_use] - pub const fn rejected(&self) -> bool { - self.status == ERROR - } -} - -unsafe impl Send for Sequence {} - -pub fn init(sender: Sender, id: u64, startup: Arc) { - let mut id = id; - let mut counter = 0_usize; - let event_sender = sender.clone(); - log::info!("sequencer initialized"); - std::thread::spawn(move || loop { - let seq = fetch_sequence_from(id); - if seq.is_empty() { - startup.store(true, Ordering::Relaxed); - std::thread::sleep(Duration::from_millis(C.sequence.fetch_intervel_ms)); - } else { - let from = if id == 0 { 0 } else { id - 1 }; - for s in seq.into_iter() { - // found break point - if id != s.id { - log::info!("expecting {}, but {} found", id, s.id); - let rs = insert_nop(id); - match rs { - // it means sequence rollback, {id} is void, adjust id = id + 1 - Some(true) => { - id += 1; - } - // it means sequence commit, abort current batch and retry - Some(false) => {} - // other error - None => {} - } - break; - } - if s.rejected() { - id += 1; - continue; - } - match C.dry_run { - Some(0) => {} - Some(n) => { - if id > n { - std::thread::park(); - } - } - None => {} - } - event_sender.send(Input::Modifier(s)).unwrap(); - counter += 1; - if counter >= C.sequence.checkpoint { - counter = 0; - let t = SystemTime::now() - .duration_since(SystemTime::UNIX_EPOCH) - .unwrap() - .as_secs(); - event_sender - .send(Input::NonModifier(Whistle::new_dump_whistle(id, t))) - .unwrap(); - } - id += 1; - } - event_sender - .send(Input::NonModifier(Whistle::new_confirm_whistle(from, id))) - .unwrap(); - } - }); - std::thread::spawn(move || loop { - if C.dry_run.is_some() { - break; - } - std::thread::sleep(Duration::from_millis(500)); - let whistle = Whistle::new_update_depth_whistle(); - sender.send(Input::NonModifier(whistle)).unwrap(); - }); -} - -fn fetch_sequence_from(id: u64) -> Vec { - let sql = "SELECT f_id,f_cmd,f_status,UNIX_TIMESTAMP(f_timestamp) as f_timestamp FROM t_sequence WHERE f_id>=? LIMIT ?"; - let conn = DB.get_conn(); - if conn.is_err() { - log::error!("retrieve mysql connection failed while fetch_sequence"); - return vec![]; - } - let mut conn = conn.unwrap(); - conn.exec_map( - sql, - (id, C.sequence.batch_size), - |(f_id, f_cmd, f_status, f_timestamp): (u64, String, u32, u64)| Sequence { - id: f_id, - cmd: serde_json::from_str(&f_cmd) - .unwrap_or_else(|_| serde_json::from_str(r#"{"cmd":999999}"#).unwrap()), - status: f_status, - timestamp: f_timestamp, - }, - ) - .unwrap_or_default() -} - -pub fn insert_nop(id: u64) -> Option { - let sql = "INSERT INTO t_sequence(f_id,f_cmd,f_status) VALUES(?,?,?)"; - let conn = DB.get_conn(); - if conn.is_err() { - log::error!("retrieve mysql connection failed while insert_nop"); - return None; - } - let mut conn = conn.unwrap(); - match conn.exec_drop(sql, (id, r#"{"cmd":999999}"#, ERROR)) { - Ok(()) => Some(true), - Err(err) => { - if let mysql::error::Error::MySqlError(e) = err { - // FIXME better way to determine duplicated entry - if e.code == 1062 && e.message.contains("Duplicate entry") { - return Some(false); - } - } - None - } - } -} - -pub fn update_sequence_status(id: u64, status: u32) -> anyhow::Result<()> { - let sql = "UPDATE t_sequence SET f_status=? WHERE f_id=?"; - let mut conn = DB.get_conn()?; - conn.exec_drop(sql, (status, id)) - .map_err(|_| anyhow!("retrieve mysql connection failed while update_sequence_status")) -} - -pub fn insert_sequences(seq: &Vec) -> anyhow::Result<()> { - if seq.is_empty() { - return Ok(()); - } - let sql = r#"INSERT INTO t_sequence(f_cmd) VALUES (:cmd)"#; - let mut conn = DB.get_conn()?; - conn.exec_batch( - sql, - seq.iter().map(|s| { - params! { - "cmd" => serde_json::to_string(s).unwrap(), - } - }), - ) - .map_err(|e| anyhow!("Error: writing sequence to mysql failed, {:?}", e)) -} - -pub fn confirm(from: u64, exclude: u64) -> anyhow::Result<()> { - if crate::config::C.dry_run.is_some() { - return Ok(()); - } - let sql = "UPDATE t_sequence SET f_status=? WHERE f_status=? AND f_id>=? AND f_id bool { - matches!(self, Event::Limit(_, _, _)) || matches!(self, Event::Cancel(_, _, _)) - } - - pub fn is_assets_cmd(&self) -> bool { - matches!(self, Event::TransferIn(_, _, _)) || matches!(self, Event::TransferOut(_, _, _)) - } - - pub fn get_id(&self) -> u64 { - match self { - Event::Limit(id, _, _) => *id, - Event::Cancel(id, _, _) => *id, - Event::TransferOut(id, _, _) => *id, - Event::TransferIn(id, _, _) => *id, - Event::UpdateSymbol(id, _, _) => *id, - Event::CancelAll(id, _, _) => *id, - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct LimitCmd { - pub symbol: Symbol, - pub user_id: UserId, - pub order_id: OrderId, - pub price: Price, - pub amount: Amount, - pub ask_or_bid: AskOrBid, - pub nonce: u32, - pub signature: Vec, - pub broker: Option, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct CancelCmd { - pub symbol: Symbol, - pub user_id: UserId, - pub order_id: OrderId, - pub nonce: u32, - pub signature: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub enum InOrOut { - In, - Out, -} - -impl std::convert::TryFrom for InOrOut { - type Error = anyhow::Error; - - fn try_from(x: u32) -> anyhow::Result { - match x { - crate::cmd::TRANSFER_IN => Ok(InOrOut::In), - crate::cmd::TRANSFER_OUT => Ok(InOrOut::Out), - _ => Err(anyhow::anyhow!("")), - } - } -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct AssetsCmd { - pub user_id: UserId, - pub in_or_out: InOrOut, - pub currency: Currency, - pub amount: Amount, - pub block_number: u32, - pub extrinsic_hash: Vec, -} - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SymbolCmd { - pub symbol: Symbol, - pub open: bool, - pub base_scale: Scale, - pub quote_scale: Scale, - pub taker_fee: Fee, - pub maker_fee: Fee, - pub base_maker_fee: Fee, - pub base_taker_fee: Fee, - pub fee_times: u32, - pub min_amount: Amount, - pub min_vol: Vol, - pub enable_market_order: bool, -} - -#[derive(Debug, Error)] -pub enum EventsError { - #[error("Events execution thread interrupted")] - Interrupted, - #[error("Error occurs in sequence {0}: {1}")] - EventRejected(u64, anyhow::Error), -} - -#[cfg(test)] -mod test { - use super::*; - use rust_decimal_macros::dec; - - #[test] - pub fn test_serialize() { - assert_eq!("{}", serde_json::to_string(&Accounts::new()).unwrap()); - let mut account = Account::default(); - account.insert( - 100, - crate::assets::Balance { - available: Amount::new(200, 1), - frozen: Amount::new(0, 0), - }, - ); - assert_eq!( - r#"{"100":{"available":"20.0","frozen":"0"}}"#, - serde_json::to_string(&account).unwrap() - ); - assert_eq!( - r#"{"available":"0","frozen":"0"}"#, - serde_json::to_string(&crate::assets::Balance { - available: Amount::new(0, 0), - frozen: Amount::new(0, 0), - }) - .unwrap() - ); - - let mut data = Data::new(); - let orderbook = crate::orderbook::OrderBook::new( - 8, - 8, - dec!(0.001), - dec!(0.001), - dec!(0.001), - dec!(0.001), - 1, - dec!(0.1), - dec!(0.1), - true, - true, - ); - data.orderbooks.insert((0, 1), orderbook); - } - - #[test] - pub fn test_deserialize_cmd() { - let transfer_in = r#"{"currency":100, "amount":"100.0", "user_id":"5Ccr8Qcp6NBMCvdUHSoqDaQMJHnA5PAC879NbWkzaiUwBdMm", "cmd":11, "block_number":1000, "extrinsic_hash":""}"#; - let e = serde_json::from_str::(transfer_in).unwrap(); - let s: anyhow::Result = Sequence { - id: 1, - cmd: e, - status: 0, - timestamp: 0, - } - .try_into(); - assert!(s.is_ok()); - let bid_limit = r#"{"quote":100, "base":101, "cmd":1, "price":"10.0", "amount":"0.5", "order_id":1, "user_id":"5Ccr8Qcp6NBMCvdUHSoqDaQMJHnA5PAC879NbWkzaiUwBdMm","nonce":1,"signature":""}"#; - let e = serde_json::from_str::(bid_limit).unwrap(); - let s: anyhow::Result = Sequence { - id: 2, - cmd: e, - status: 0, - timestamp: 0, - } - .try_into(); - assert!(s.is_ok()); - } -} diff --git a/engine/src/input/sequencer.rs b/engine/src/input/sequencer.rs new file mode 100644 index 0000000..acc3ea5 --- /dev/null +++ b/engine/src/input/sequencer.rs @@ -0,0 +1,171 @@ +// Copyright 2021-2023 UINB Technologies Pte. Ltd. + +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use crate::{config::C, input::*}; +use rocksdb::{Direction, IteratorMode, WriteBatchWithTransaction}; +use std::{convert::TryInto, sync::mpsc::*}; + +pub fn init( + rx: Receiver, + to_executor: Sender, + to_server: Sender<(u64, Message)>, + init_at: u64, +) { + let recovery = ensure_fully_loaded(init_at, to_executor.clone()).unwrap(); + if C.dry_run.is_some() { + return; + } + std::thread::spawn(move || -> anyhow::Result<()> { + let mut current_id = recovery; + loop { + let mut input = rx.recv()?; + let (session, req_id) = (input.session, input.req_id); + input.sequence = current_id; + let cmd = serde_json::to_vec(&input.cmd)?; + if let Ok(event) = input.try_into() { + save(current_id, cmd)?; + to_executor.send(event)?; + if current_id % C.sequence.checkpoint == 0 { + to_executor.send(Event::Dump(current_id))?; + } + current_id += 1; + } else { + to_server.send((session, Message::new(req_id, vec![])))?; + } + } + }); +} + +fn save(id: u64, cmd: Vec) -> anyhow::Result<()> { + STORAGE.put(id_to_key(id), cmd)?; + Ok(()) +} + +fn ensure_fully_loaded(init_at: u64, tx: Sender) -> anyhow::Result { + let mut current_id = init_at; + let iter = STORAGE.iterator(IteratorMode::From(&id_to_key(init_at), Direction::Forward)); + for item in iter { + let (key, value) = item?; + current_id = key_to_id(&key); + let input = Input { + session: 0, + req_id: 0, + sequence: current_id, + cmd: value_to_cmd(&value) + .map_err(|_| anyhow::anyhow!("id {} is invalid", current_id))?, + }; + let event = input + .try_into() + .map_err(|_| anyhow::anyhow!("id {} is invalid", current_id))?; + match C.dry_run { + Some(n) if n >= current_id => tx.send(event)?, + None => tx.send(event)?, + _ => break, + } + } + Ok(current_id + 1) +} + +pub fn remove_before(id: u64) -> anyhow::Result<()> { + let mut batch = WriteBatchWithTransaction::::default(); + batch.delete_range(id_to_key(1), id_to_key(id)); + STORAGE.write(batch)?; + Ok(()) +} + +fn id_to_key(id: u64) -> [u8; 16] { + unsafe { std::mem::transmute::<[[u8; 8]; 2], [u8; 16]>([*b"sequence", id.to_be_bytes()]) } +} + +fn key_to_id(key: &[u8]) -> u64 { + let mut id = [0u8; 8]; + id.copy_from_slice(&key[8..]); + u64::from_be_bytes(id) +} + +fn value_to_cmd(value: &[u8]) -> anyhow::Result { + let cmd = serde_json::from_slice(value)?; + Ok(cmd) +} + +#[cfg(test)] +mod test { + use super::*; + use rust_decimal_macros::dec; + + #[test] + pub fn test_serialize() { + assert_eq!("{}", serde_json::to_string(&Accounts::new()).unwrap()); + let mut account = Account::default(); + account.insert( + 100, + crate::assets::Balance { + available: Amount::new(200, 1), + frozen: Amount::new(0, 0), + }, + ); + assert_eq!( + r#"{"100":{"available":"20.0","frozen":"0"}}"#, + serde_json::to_string(&account).unwrap() + ); + assert_eq!( + r#"{"available":"0","frozen":"0"}"#, + serde_json::to_string(&crate::assets::Balance { + available: Amount::new(0, 0), + frozen: Amount::new(0, 0), + }) + .unwrap() + ); + + let mut data = Data::new(); + let orderbook = crate::orderbook::OrderBook::new( + 8, + 8, + dec!(0.001), + dec!(0.001), + dec!(0.001), + dec!(0.001), + 1, + dec!(0.1), + dec!(0.1), + true, + true, + ); + data.orderbooks.insert((0, 1), orderbook); + } + + #[test] + pub fn test_deserialize_cmd() { + let transfer_in = r#"{"currency":100, "amount":"100.0", "user_id":"5Ccr8Qcp6NBMCvdUHSoqDaQMJHnA5PAC879NbWkzaiUwBdMm", "cmd":11, "block_number":1000, "extrinsic_hash":""}"#; + let e = serde_json::from_str::(transfer_in).unwrap(); + let s: anyhow::Result = Input { + cmd: e, + sequence: 0, + session: 0, + req_id: 0, + } + .try_into(); + assert!(s.is_ok()); + let bid_limit = r#"{"quote":100, "base":101, "cmd":1, "price":"10.0", "amount":"0.5", "order_id":1, "user_id":"5Ccr8Qcp6NBMCvdUHSoqDaQMJHnA5PAC879NbWkzaiUwBdMm","nonce":1,"signature":""}"#; + let e = serde_json::from_str::(bid_limit).unwrap(); + let s: anyhow::Result = Input { + cmd: e, + sequence: 0, + session: 1, + req_id: 0, + } + .try_into(); + assert!(s.is_ok()); + } +} diff --git a/engine/src/input/server.rs b/engine/src/input/server.rs index 0c1d8f8..005b31d 100644 --- a/engine/src/input/server.rs +++ b/engine/src/input/server.rs @@ -16,7 +16,6 @@ use crate::{ config::C, input::{Command, Input, Message}, shared::Shared, - whistle::Whistle, }; use async_std::{ net::{TcpListener, TcpStream}, @@ -31,7 +30,6 @@ use futures::{ use std::{ net::Shutdown, sync::{ - atomic::{AtomicBool, Ordering}, mpsc::{Receiver, Sender}, Arc, }, @@ -43,7 +41,10 @@ type FromSession = UnboundedReceiver; type ToBackend = Sender; type FromBackend = Receiver<(u64, Message)>; -pub fn init(sender: ToBackend, receiver: FromBackend, shared: Shared, ready: Arc) { +pub fn init(receiver: FromBackend, sender: ToBackend, shared: Shared) { + if C.dry_run.is_some() { + return; + } let listener = task::block_on(async { TcpListener::bind(&C.server.bind_addr).await }).unwrap(); let sessions = Arc::new(DashMap::::new()); let sx = sessions.clone(); @@ -51,7 +52,7 @@ pub fn init(sender: ToBackend, receiver: FromBackend, shared: Shared, ready: Arc log::error!("session relayer interrupted, {:?}", relay(receiver, sx)); }); log::info!("server initialized"); - let future = accept(listener, sender, shared, sessions, ready); + let future = accept(listener, sender, shared, sessions); let _ = task::block_on(future); log::info!("bye!"); } @@ -63,7 +64,7 @@ fn relay(receiver: FromBackend, sessions: Arc>) -> Resul // relay the messages from backend to session, need to switch the runtime using async if let Some(mut session) = sessions.get_mut(&session_id) { let _ = task::block_on(session.send(msg)); - } else { + } else if session_id != 0 { log::error!( "received reply from executor, but session {} not found", session_id @@ -77,22 +78,20 @@ async fn accept( to_backend: ToBackend, shared: Shared, sessions: Arc>, - ready: Arc, ) -> Result<()> { let mut incoming = listener.incoming(); - let mut session_id = 0_u64; + // NOTICE: session id must be started from 1 + let mut session_id = 1_u64; while let Some(stream) = incoming.next().await { - if ready.load(Ordering::Relaxed) { - let stream = stream?; - register( - session_id, - stream, - to_backend.clone(), - shared.clone(), - &sessions, - ); - session_id += 1; - } + let stream = stream?; + register( + session_id, + stream, + to_backend.clone(), + shared.clone(), + sessions.clone(), + ); + session_id += 1; } Ok(()) } @@ -102,7 +101,7 @@ fn register( stream: TcpStream, to_backend: ToBackend, shared: Shared, - sessions: &Arc>, + sessions: Arc>, ) { match stream.set_nodelay(true) { Ok(_) => {} @@ -117,7 +116,7 @@ fn register( shared, session_id, stream, - sessions.clone(), + sessions, )); } @@ -198,23 +197,18 @@ async fn handle_req( to_back: &mut ToBackend, to_session: &mut ToSession, shared: &Shared, - session_id: u64, + session: u64, req_id: u64, - json: String, + body: String, ) -> Result<()> { - let cmd: Command = serde_json::from_str(&json) + let mut cmd: Command = serde_json::from_str(&body) .map_err(|e| anyhow::anyhow!("deser command failed, {:?}", e))?; - if cmd.is_querying_core_data() { - let w = Input::NonModifier(Whistle { - session: session_id, - req_id, - cmd, - }); - to_back - .send(w) - .map_err(|e| anyhow::anyhow!("read loop -> executor -> {:?}", e))?; - Ok(()) - } else if cmd.is_querying_share_data() { + let timestamp = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .unwrap() + .as_secs(); + cmd.timestamp = Some(timestamp); + if cmd.is_querying_share_data() { let w = shared.handle_req(&cmd)?; to_session .send(Message::new(req_id, w)) @@ -222,6 +216,10 @@ async fn handle_req( .map_err(|e| anyhow::anyhow!("read loop -> write loop -> {:?}", e))?; Ok(()) } else { - Err(anyhow::anyhow!("unsupported command {} from sidecar", cmd.cmd).into()) + let input = Input::new_with_req(cmd, session, req_id); + to_back + .send(input) + .map_err(|e| anyhow::anyhow!("read loop -> executor -> {:?}", e))?; + Ok(()) } } diff --git a/engine/src/input/whistle.rs b/engine/src/input/whistle.rs deleted file mode 100644 index fc8ab17..0000000 --- a/engine/src/input/whistle.rs +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 2021-2023 UINB Technologies Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::{cmd::*, core::*, Command}; -use anyhow::anyhow; -use serde::{Deserialize, Serialize}; -use std::str::FromStr; - -#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Clone)] -pub struct Whistle { - pub session: u64, - pub req_id: u64, - pub cmd: Command, -} - -impl TryInto for Whistle { - type Error = anyhow::Error; - - fn try_into(self) -> anyhow::Result { - match self.cmd.cmd { - QUERY_ORDER => Ok(Inspection::QueryOrder( - self.cmd.symbol().ok_or(anyhow!(""))?, - self.cmd.order_id.ok_or(anyhow!(""))?, - self.session, - self.req_id, - )), - QUERY_BALANCE => Ok(Inspection::QueryBalance( - UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, - self.cmd.currency.ok_or(anyhow!(""))?, - self.session, - self.req_id, - )), - QUERY_ACCOUNTS => Ok(Inspection::QueryAccounts( - UserId::from_str(self.cmd.user_id.as_ref().ok_or(anyhow!(""))?)?, - self.session, - self.req_id, - )), - UPDATE_DEPTH => Ok(Inspection::UpdateDepth), - CONFIRM_ALL => Ok(Inspection::ConfirmAll( - self.cmd.from.ok_or(anyhow!(""))?, - self.cmd.exclude.ok_or(anyhow!(""))?, - )), - QUERY_EXCHANGE_FEE => Ok(Inspection::QueryExchangeFee( - self.cmd.symbol().ok_or(anyhow!(""))?, - self.session, - self.req_id, - )), - DUMP => Ok(Inspection::Dump( - self.cmd.event_id.ok_or(anyhow!(""))?, - self.cmd.timestamp.ok_or(anyhow!(""))?, - )), - _ => Err(anyhow!("Invalid Inspection")), - } - } -} - -impl Whistle { - pub fn new_update_depth_whistle() -> Self { - let mut cmd = Command::default(); - cmd.cmd = UPDATE_DEPTH; - Self { - session: 0, - req_id: 0, - cmd, - } - } - - pub fn new_dump_whistle(at: u64, time: u64) -> Self { - let mut cmd = Command::default(); - cmd.cmd = DUMP; - cmd.event_id = Some(at); - cmd.timestamp = Some(time); - Self { - session: 0, - req_id: 0, - cmd, - } - } - - pub fn new_confirm_whistle(from: u64, exclude: u64) -> Self { - let mut cmd = Command::default(); - cmd.cmd = CONFIRM_ALL; - cmd.from.replace(from); - cmd.exclude.replace(exclude); - Self { - session: 0, - req_id: 0, - cmd, - } - } -} - -#[derive(Debug, Eq, PartialEq, Clone, Deserialize, Serialize, Copy)] -pub enum Inspection { - ConfirmAll(u64, u64), - UpdateDepth, - QueryOrder(Symbol, OrderId, u64, u64), - QueryBalance(UserId, Currency, u64, u64), - QueryAccounts(UserId, u64, u64), - QueryExchangeFee(Symbol, u64, u64), - // special: `EventId` means dump at `EventId` - Dump(EventId, Timestamp), -} - -impl Default for Inspection { - fn default() -> Self { - Self::UpdateDepth - } -} diff --git a/engine/src/lib.rs b/engine/src/lib.rs index 988488e..267803a 100644 --- a/engine/src/lib.rs +++ b/engine/src/lib.rs @@ -22,7 +22,6 @@ pub mod config; pub mod core; -pub mod db; pub mod executor; pub mod fusotao; pub mod input; @@ -30,6 +29,10 @@ pub mod output; pub mod shared; pub mod snapshot; +pub use ::core::*; +pub use config::C; pub use executor::*; +pub use fusotao::*; pub use input::*; pub use output::*; +pub use shared::*; diff --git a/engine/src/output/legacy.rs b/engine/src/output/legacy.rs deleted file mode 100644 index 55ee8c8..0000000 --- a/engine/src/output/legacy.rs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2021-2023 UINB Technologies Pte. Ltd. - -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -use crate::core::Symbol; -use crate::db::DB; -use mysql::prelude::*; - -pub fn create_mysql_table(symbol: Symbol) -> anyhow::Result<()> { - let sql_cr = format!( - "create table if not exists t_clearing_result_{}_{} like t_clearing_result", - symbol.0, symbol.1 - ); - let sql_order = format!( - "create table if not exists t_order_{}_{} like t_order", - symbol.0, symbol.1 - ); - let sql_stick = format!( - "create table if not exists t_stick_{}_{} like t_stick", - symbol.0, symbol.1 - ); - let mut conn = DB - .get_conn() - .map_err(|_| anyhow::anyhow!("mysql not available"))?; - conn.query_drop(sql_cr)?; - conn.query_drop(sql_order)?; - conn.query_drop(sql_stick)?; - Ok(()) -} diff --git a/engine/src/output/mod.rs b/engine/src/output/mod.rs index 1b89dc4..9d56247 100644 --- a/engine/src/output/mod.rs +++ b/engine/src/output/mod.rs @@ -12,16 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -pub mod legacy; - use crate::{ core::*, - db::{DB, REDIS}, matcher::*, orderbook::{AskOrBid, Depth}, }; -use mysql::{prelude::*, *}; -use redis::Commands; use std::{ collections::HashMap, convert::Into, @@ -50,28 +45,28 @@ pub struct Output { pub timestamp: u64, } -pub fn write_depth(depth: Vec) { - if crate::config::C.dry_run.is_some() { - return; - } - let redis = REDIS.get_connection(); - match redis { - Ok(mut conn) => { - depth.iter().for_each(|d| { - let r: redis::RedisResult<()> = conn.set( - format!("V2_DEPTH_L{}_{}_{}", d.depth, d.symbol.0, d.symbol.1), - serde_json::to_string(d).unwrap(), - ); - if r.is_err() { - log::error!("{:?}", r); - } - }); - } - Err(_) => { - log::error!("connect redis failed"); - } - } -} +// pub fn write_depth(depth: Vec) { +// if crate::config::C.dry_run.is_some() { +// return; +// } +// let redis = REDIS.get_connection(); +// match redis { +// Ok(mut conn) => { +// depth.iter().for_each(|d| { +// let r: redis::RedisResult<()> = conn.set( +// format!("V2_DEPTH_L{}_{}_{}", d.depth, d.symbol.0, d.symbol.1), +// serde_json::to_string(d).unwrap(), +// ); +// if r.is_err() { +// log::error!("{:?}", r); +// } +// }); +// } +// Err(_) => { +// log::error!("connect redis failed"); +// } +// } +// } pub fn init(rx: Receiver>) { let mut buf = HashMap::)>::new(); @@ -84,101 +79,13 @@ pub fn init(rx: Receiver>) { break; } }; - if crate::config::C.dry_run.is_none() { - if cr.is_empty() { - flush_all(&mut buf); - } else { - write(cr, &mut buf); - } - } + // if crate::config::C.dry_run.is_none() { + // if cr.is_empty() { + // flush_all(&mut buf); + // } else { + // write(cr, &mut buf); + // } + // } }); log::info!("dumper initialized"); } - -fn get_max_record(symbol: Symbol) -> u64 { - let sql = format!( - "SELECT coalesce(MAX(f_event_id), 0) from t_clearing_result_{}_{}", - symbol.0, symbol.1 - ); - let conn = DB.get_conn(); - if conn.is_err() { - log::error!("Error: acquire mysql connection failed, {:?}", conn); - return 0; - } - let mut conn = conn.unwrap(); - let id = conn.query_first(sql).unwrap(); - id.or(Some(0)).unwrap() -} - -fn flush(symbol: Symbol, pending: &mut Vec) { - if pending.is_empty() { - return; - } - let sql = format!( - r#"INSERT IGNORE INTO t_clearing_result_{}_{} -(f_event_id,f_order_id,f_user_id,f_status,f_role,f_ask_or_bid,f_price,f_quote_delta,f_base_delta,f_quote_charge,f_base_charge,f_quote_available,f_base_available,f_quote_frozen,f_base_frozen,f_timestamp) -VALUES -(:event_id,:order_id,:user_id,:state,:role,:ask_or_bid,:price,:quote_delta,:base_delta,:quote_charge,:base_charge,:quote_available,:base_available,:quote_frozen,:base_frozen,FROM_UNIXTIME(:timestamp))"#, - symbol.0, symbol.1 - ); - let conn = DB.get_conn(); - if conn.is_err() { - log::error!("Error: acquire mysql connection failed, {:?}", conn); - return; - } - let mut conn = conn.unwrap(); - let r = conn.exec_batch( - sql, - pending.iter().map(|p| { - params! { - "event_id" => p.event_id, - "order_id" => p.order_id, - "user_id" => format!("{:?}", p.user_id), - "state" => p.state.into(): u32, - "role" => p.role.into(): u32, - "ask_or_bid" => p.ask_or_bid.into(): u32, - "price" => p.price, - "quote_delta" => p.quote_delta, - "base_delta" => p.base_delta, - "quote_charge" => p.quote_charge, - "base_charge" => p.base_charge, - "quote_available" => p.quote_available, - "base_available" => p.base_available, - "quote_frozen" => p.quote_frozen, - "base_frozen" => p.base_frozen, - "timestamp" => p.timestamp, - } - }), - ); - match r { - Ok(_) => pending.clear(), - Err(err) => { - log::error!("Error: writing clearing result to mysql failed, {:?}", err); - } - } -} - -fn flush_all(buf: &mut HashMap)>) { - for (symbol, pending) in buf.iter_mut() { - flush(*symbol, &mut pending.1); - } -} - -fn write(mut cr: Vec, buf: &mut HashMap)>) { - let symbol = cr.first().unwrap().symbol; - let pending = buf.get_mut(&symbol); - if pending.is_none() { - buf.insert(symbol, (get_max_record(symbol), cr)); - return; - } - let pending = pending.unwrap(); - let prepare_write_event_id = cr.last().unwrap().event_id; - if prepare_write_event_id < pending.0 { - return; - } - pending.0 = prepare_write_event_id; - pending.1.append(&mut cr); - if pending.1.len() >= 100 { - flush(symbol, &mut pending.1); - } -} diff --git a/engine/src/snapshot.rs b/engine/src/snapshot.rs index c07ef24..1ca56fc 100644 --- a/engine/src/snapshot.rs +++ b/engine/src/snapshot.rs @@ -12,40 +12,31 @@ // See the License for the specific language governing permissions and // limitations under the License. -use std::{ - fs, path, thread, - time::{Duration, UNIX_EPOCH}, -}; - -use chrono::{prelude::DateTime, Utc}; - -use crate::{config, core}; +use crate::{config, core, sequencer}; /// dump snapshot at id(executed) -pub fn dump(id: u64, time: u64, data: &core::Data) { +pub fn dump(id: u64, data: &core::Data) { if config::C.dry_run.is_some() { return; } let data = data.clone(); - let timestamp = UNIX_EPOCH + Duration::from_secs(time); - let datetime = DateTime::::from(timestamp); - let format = datetime.format("%Y-%m-%dT%H:%M:%S").to_string(); - thread::spawn(move || -> anyhow::Result<()> { - let f = path::Path::new(&config::C.sequence.coredump_dir) + std::thread::spawn(move || -> anyhow::Result<()> { + let f = std::path::Path::new(&config::C.server.get_coredump_path()) .join(id.to_string()) - .with_extension(format!("{}.gz", format)); - let file = fs::OpenOptions::new() + .with_extension("gz"); + let file = std::fs::OpenOptions::new() .write(true) .create_new(true) .open(f)?; data.into_raw(file)?; + sequencer::remove_before(id)?; log::info!("snapshot dumped at sequence {}", id); Ok(()) }); } -fn get_id(path: &path::Path) -> u64 { - let file_stem = path::Path::new(path.file_stem().unwrap()) +fn get_id(path: &std::path::Path) -> u64 { + let file_stem = std::path::Path::new(path.file_stem().unwrap()) .file_stem() .unwrap() .to_str() @@ -55,7 +46,7 @@ fn get_id(path: &path::Path) -> u64 { /// return the id(not executed yet), and the snapshot pub fn load() -> anyhow::Result<(u64, core::Data)> { - let dir = fs::read_dir(&config::C.sequence.coredump_dir)?; + let dir = std::fs::read_dir(&config::C.server.get_coredump_path())?; let file_path = dir .map(|e| e.unwrap()) .filter(|f| f.file_type().unwrap().is_file()) @@ -70,7 +61,7 @@ pub fn load() -> anyhow::Result<(u64, core::Data)> { event_id, event_id + 1 ); - let data = core::Data::from_raw(fs::File::open(f)?)?; + let data = core::Data::from_raw(std::fs::File::open(f)?)?; print_symbols(&data); Ok((event_id + 1, data)) } @@ -99,21 +90,12 @@ fn print_symbols(data: &core::Data) { #[cfg(test)] mod test { - use std::{ - path::Path, - time::{Duration, UNIX_EPOCH}, - }; - - use chrono::{prelude::DateTime, Utc}; + use std::path::Path; #[test] pub fn test_syspath() { - let timestamp = UNIX_EPOCH + Duration::from_secs(1524885322); - let datetime = DateTime::::from(timestamp); - let format = datetime.format("%Y-%m-%dT%H:%M:%S").to_string(); let f = Path::new("/tmp/snapshot/") .join("2980") - .with_extension(format) .with_extension("gz"); assert_eq!("gz", f.extension().unwrap()); let filename = Path::new(f.file_stem().unwrap()).file_stem().unwrap(); @@ -123,17 +105,10 @@ mod test { #[test] pub fn test_max_seq() { - let timestamp = UNIX_EPOCH + Duration::from_secs(1524885322); - let datetime = DateTime::::from(timestamp); - let format = datetime.format("%Y-%m-%dT%H:%M:%S").to_string(); let f0 = Path::new("/tmp/snapshot/") .join("2980") - .with_extension(&format) - .with_extension("gz"); - let f1 = Path::new("/tmp/snapshot/") - .join("310") - .with_extension(&format) .with_extension("gz"); + let f1 = Path::new("/tmp/snapshot/").join("310").with_extension("gz"); assert_eq!( std::cmp::Ordering::Greater, super::get_id(&f0).cmp(&super::get_id(&f1)) diff --git a/galois.toml.example b/galois.toml.example index aaddba1..32e4beb 100644 --- a/galois.toml.example +++ b/galois.toml.example @@ -1,23 +1,14 @@ [server] bind_addr = "127.0.0.1:8097" - -[mysql] -url = "mysql://username:password@localhost:3306/galois" - -[redis] -url = "redis://localhost:6379/0" +data_home = "/tmp/galois" [sequence] checkpoint = 100000 -coredump_dir = "/tmp/snapshot" -batch_size = 1000 -dump_mode = "disk" -fetch_intervel_ms = 5 +enable_from_genesis = true [fusotao] node_url = "ws://localhost:9944" key_seed = "//Alice" proof_batch_limit = 20 claim_block = 1 -compress_proofs = true x25519_priv = "0xedcff0c69e4c0fa7e9a36e2e6d07f2cc355c8d25907a0ad2ab7e03b24f8e90f3" diff --git a/sidecar/Cargo.toml b/sidecar/Cargo.toml index 6703576..dce4d19 100644 --- a/sidecar/Cargo.toml +++ b/sidecar/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "galois-sidecar" -version = "0.5.0-dev" +version = "0.7.0-dev" authors = ["UINB Technologies"] edition = "2021" license = "Apache-2.0" diff --git a/sidecar/src/backend.rs b/sidecar/src/backend.rs index 0e8ff4d..250efb2 100644 --- a/sidecar/src/backend.rs +++ b/sidecar/src/backend.rs @@ -12,15 +12,18 @@ // See the License for the specific language governing permissions and // limitations under the License. +use crate::endpoint::TradingCommand; use dashmap::DashMap; use galois_engine::{ core::*, fusotao::OffchainSymbol, - input::{cmd, Message}, + input::{cmd::*, Command, Message}, orderbook::Order as CoreOrder, }; +use rust_decimal::Decimal; use serde_json::{json, to_vec, Value as JsonValue}; use std::collections::BTreeMap; +use std::str::FromStr; use std::sync::Arc; use tokio::io::{AsyncReadExt, AsyncWriteExt}; use tokio::net::{ @@ -153,9 +156,68 @@ impl BackendConnection { .ok_or(anyhow::anyhow!("fail to read from backend")) } + pub async fn submit_trading_command( + &self, + user_id: impl ToString, + cmd: TradingCommand, + relayer: impl ToString, + ) -> anyhow::Result { + // TODO we may require user to sign the payload + let fix_cmd_signature = "169d796416023558ef5c2580ef38c1c4f43f3c06f76ceab2412e6fc5d486a36eb0a9cb808dd4eb72f6264b4113c1a722479be205edc84d6ac5403d33d09b0087"; + let fix_cmd_nonce = 40020u32; + let direction = cmd.get_direction_if_trade(); + let payload = match cmd { + TradingCommand::Cancel { + base, + quote, + order_id, + } => { + let mut cancel = Command::default(); + cancel.order_id = Some(order_id); + cancel.base = Some(base); + cancel.cmd = CANCEL; + cancel.quote = Some(quote); + cancel.user_id = Some(user_id.to_string()); + cancel.signature = Some(fix_cmd_signature.to_string()); + cancel.nonce = Some(fix_cmd_nonce); + cancel + } + TradingCommand::Ask { + base, + quote, + amount, + price, + } + | TradingCommand::Bid { + base, + quote, + amount, + price, + } => { + let mut place = Command::default(); + place.cmd = direction.expect("ask_or_bid;qed").into(); + place.base = Some(base); + place.quote = Some(quote); + place.signature = Some(fix_cmd_signature.to_string()); + place.user_id = Some(user_id.to_string()); + place.price = Decimal::from_str(&price).ok(); + place.amount = Decimal::from_str(&amount).ok(); + place.nonce = Some(fix_cmd_nonce); + place.broker = Some(relayer.to_string()); + place + } + }; + let r = self + .request(to_vec(&payload)?) + .await + .inspect_err(|e| log::debug!("{:?}", e))?; + // TODO + Ok(0) + } + pub async fn get_nonce(&self, broker: &str) -> Option { let r = self - .request(to_vec(&json!({ "cmd": cmd::GET_NONCE_FOR_BROKER, "user_id": broker })).ok()?) + .request(to_vec(&json!({ "cmd": GET_NONCE_FOR_BROKER, "user_id": broker })).ok()?) .await .inspect_err(|e| log::debug!("{:?}", e)) .ok()?; @@ -171,7 +233,7 @@ impl BackendConnection { ) -> anyhow::Result> { let r = self .request( - to_vec(&json!({"cmd": cmd::QUERY_ACCOUNTS, "user_id": user_id.as_ref()})) + to_vec(&json!({"cmd": QUERY_ACCOUNTS, "user_id": user_id.as_ref()})) .expect("jsonser;qed"), ) .await @@ -188,7 +250,7 @@ impl BackendConnection { let r = self .request( to_vec(&json!({ - "cmd": cmd::QUERY_ORDER, + "cmd": QUERY_ORDER, "base": symbol.0, "quote": symbol.1, "order_id": order_id, @@ -203,7 +265,7 @@ impl BackendConnection { pub async fn get_markets(&self) -> anyhow::Result> { let r = self - .request(to_vec(&json!({ "cmd": cmd::QUERY_OPEN_MARKETS })).expect("jsonser;qed")) + .request(to_vec(&json!({ "cmd": QUERY_OPEN_MARKETS })).expect("jsonser;qed")) .await .inspect_err(|e| log::debug!("{:?}", e)) .map_err(|_| anyhow::anyhow!("Galois not available"))?; @@ -212,7 +274,7 @@ impl BackendConnection { pub async fn get_x25519(&self) -> anyhow::Result { let r = self - .request(to_vec(&json!({ "cmd": cmd::GET_X25519_KEY })).expect("jsonser;qed")) + .request(to_vec(&json!({ "cmd": GET_X25519_KEY })).expect("jsonser;qed")) .await .inspect_err(|e| log::debug!("{:?}", e)) .map_err(|_| anyhow::anyhow!("Galois not available"))?; diff --git a/sidecar/src/db.rs b/sidecar/src/db.rs index 125f9ee..5a14ab3 100644 --- a/sidecar/src/db.rs +++ b/sidecar/src/db.rs @@ -135,82 +135,3 @@ pub async fn query_pending_orders( .map(|o| (symbol.clone(), o).into()) .collect::>()) } - -// TODO save the relayer -pub async fn save_trading_command( - pool: &Pool, - user_id: impl ToString, - cmd: TradingCommand, - relayer: impl ToString, -) -> anyhow::Result { - // TODO - let fix_cmd_signature = "169d796416023558ef5c2580ef38c1c4f43f3c06f76ceab2412e6fc5d486a36eb0a9cb808dd4eb72f6264b4113c1a722479be205edc84d6ac5403d33d09b0087"; - let fix_cmd_nonce = 40020u32; - let direction = cmd.get_direction_if_trade(); - match cmd { - TradingCommand::Cancel { - base, - quote, - order_id, - } => { - let mut cancel = Command::default(); - cancel.order_id = Some(order_id); - cancel.base = Some(base); - cancel.cmd = CANCEL; - cancel.quote = Some(quote); - cancel.user_id = Some(user_id.to_string()); - cancel.signature = Some(fix_cmd_signature.to_string()); - cancel.nonce = Some(fix_cmd_nonce); - sqlx::query("insert into t_sequence(f_cmd) values(?)") - .bind(serde_json::to_string(&cancel).expect("jsonser;qed")) - .execute(pool) - .await?; - Ok(order_id) - } - TradingCommand::Ask { - base, - quote, - amount, - price, - } - | TradingCommand::Bid { - base, - quote, - amount, - price, - } => { - let mut tx = pool.begin().await?; - let sql = format!("insert into t_order_{}_{}(f_user_id,f_amount,f_price,f_order_type) values(?,?,?,?)", - base, quote - ); - sqlx::query(sql.as_str()) - .bind(user_id.to_string()) - .bind(amount.clone()) - .bind(price.clone()) - .bind(direction) - .execute(&mut tx) - .await?; - let result = sqlx::query("select LAST_INSERT_ID() as id") - .fetch_one(&mut tx) - .await?; - let id: u64 = result.get("id"); - let mut place = Command::default(); - place.cmd = direction.expect("ask_or_bid;qed").into(); - place.order_id = Some(id); - place.base = Some(base); - place.quote = Some(quote); - place.signature = Some(fix_cmd_signature.to_string()); - place.user_id = Some(user_id.to_string()); - place.price = Decimal::from_str(&price).ok(); - place.amount = Decimal::from_str(&amount).ok(); - place.nonce = Some(fix_cmd_nonce); - place.broker = Some(relayer.to_string()); - sqlx::query("insert into t_sequence(f_cmd) values(?)") - .bind(serde_json::to_string(&place).expect("jsonser;qed")) - .execute(&mut tx) - .await?; - tx.commit().await?; - Ok(id) - } - } -} diff --git a/sidecar/src/endpoint.rs b/sidecar/src/endpoint.rs index effb71f..ae65806 100644 --- a/sidecar/src/endpoint.rs +++ b/sidecar/src/endpoint.rs @@ -84,7 +84,8 @@ pub fn export_rpc(context: Context) -> RpcModule { ctx.validate_cmd(&user_id, &cmd) .await .map_err(handle_error)?; - db::save_trading_command(&ctx.db, user_id, cmd, relayer) + ctx.backend + .submit_trading_command(user_id, cmd, relayer) .await .map(|id| crate::to_hexstr(id)) .map_err(handle_error) @@ -140,11 +141,7 @@ pub fn export_rpc(context: Context) -> RpcModule { ); let user_id = crate::try_into_account(user_id)?; let bot_id = crate::try_into_account(bot_id)?; - let sub_id = crate::derive_sub_account( - &user_id, - &bot_id, - token, - ); + let sub_id = crate::derive_sub_account(&user_id, &bot_id, token); let bot_x25519_pub_vec = crate::hexstr_to_vec(&bot_x25519_pub)?; let raw_sig = crate::hexstr_to_vec(&sig)?; let message = format!("{}", bot_x25519_pub); diff --git a/sql/init.sql b/sql/init.sql deleted file mode 100644 index ec0ea9e..0000000 --- a/sql/init.sql +++ /dev/null @@ -1,106 +0,0 @@ -CREATE TABLE `t_sequence` ( - `f_id` bigint unsigned NOT NULL AUTO_INCREMENT, - `f_cmd` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL, - `f_status` int unsigned NOT NULL DEFAULT '0' COMMENT '0:pending,1: accept,2:reject', - `f_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`f_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `t_proof` ( - `f_event_id` bigint unsigned NOT NULL, - `f_proof` blob NOT NULL, - PRIMARY KEY (`f_event_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- DEPRECATED -- - -CREATE TABLE `t_trading_key`( - `f_user_id` varchar(66) NOT NULL primary key, - `f_trading_key` varchar(66) NOT NULL -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE `t_clearing_result` ( - `f_id` bigint unsigned NOT NULL AUTO_INCREMENT, - `f_event_id` bigint unsigned NOT NULL, - `f_order_id` bigint unsigned NOT NULL, - `f_user_id` varchar(66) NOT NULL, - `f_status` int unsigned NOT NULL, - `f_role` int unsigned NOT NULL, - `f_ask_or_bid` int unsigned NOT NULL, - `f_price` varchar(20) NOT NULL, - `f_quote_delta` varchar(20) NOT NULL, - `f_base_delta` varchar(20) NOT NULL, - `f_quote_available` varchar(20) NOT NULL, - `f_base_available` varchar(20) NOT NULL, - `f_quote_frozen` varchar(20) NOT NULL, - `f_base_frozen` varchar(20) NOT NULL, - `f_quote_charge` varchar(18) NOT NULL, - `f_base_charge` varchar(18) NOT NULL, - `f_timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, - PRIMARY KEY (`f_id`), - UNIQUE KEY `f_event_id` (`f_event_id`,`f_order_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `t_stick` ( - `f_id` bigint NOT NULL, - `f_open` varchar(20) NOT NULL, - `f_close` varchar(20) NOT NULL, - `f_high` varchar(20) NOT NULL, - `f_low` varchar(20) NOT NULL, - `f_amount` varchar(32) NOT NULL, - `f_vol` varchar(36) NOT NULL, - `f_last_cr` bigint NOT NULL, - PRIMARY KEY (`f_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - -CREATE TABLE IF NOT EXISTS `t_order` ( - `f_id` bigint unsigned NOT NULL AUTO_INCREMENT, - `f_version` bigint unsigned NOT NULL default 0, - `f_user_id` varchar(66) NOT NULL, - `f_amount` decimal(20,8) NOT NULL, - `f_price` decimal(20,8) NOT NULL, - `f_order_type` int unsigned NOT NULL, - `f_timestamp` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `f_status` int NOT NULL DEFAULT '0', - `f_base_fee` decimal(5,4) NOT NULL DEFAULT '0.0000', - `f_quote_fee` decimal(5,4) NOT NULL DEFAULT '0.0000', - `f_last_cr` bigint NOT NULL DEFAULT 0, - `f_matched_quote_amount` decimal(20,8) NOT NULL DEFAULT '0.00000000', - `f_matched_base_amount` decimal(20,8) NOT NULL DEFAULT '0.00000000', - PRIMARY KEY (`f_id`), - KEY `idx_user_id` (`f_user_id`), - KEY `idx_user_id_and_status` (`f_user_id`, `f_status`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci; - --- initializing a new trading pair should create a table like t_clearing_result --- CREATE TABLE `t_clearing_result_{base_currency_code}_{quote_currency_code}` like `t_clearing_result`; - -alter table t_order modify f_status int(11) unsigned not null default 0; -alter table t_order modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_0_1 modify f_status int(11) unsigned not null default 0; -alter table t_order_0_1 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_0_11 modify f_status int(11) unsigned not null default 0; -alter table t_order_0_11 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_0_8 modify f_status int(11) unsigned not null default 0; -alter table t_order_0_8 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_12_11 modify f_status int(11) unsigned not null default 0; -alter table t_order_12_11 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_2_1 modify f_status int(11) unsigned not null default 0; -alter table t_order_2_1 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_3_1 modify f_status int(11) unsigned not null default 0; -alter table t_order_3_1 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_4_1 modify f_status int(11) unsigned not null default 0; -alter table t_order_4_1 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_6_1 modify f_status int(11) unsigned not null default 0; -alter table t_order_6_1 modify f_last_cr bigint(20) unsigned not null default 0; - -alter table t_order_7_8 modify f_status int(11) unsigned not null default 0; -alter table t_order_7_8 modify f_last_cr bigint(20) unsigned not null default 0;