diff --git a/.dockerignore b/.dockerignore index 83178d0..f4ab4e5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -5,7 +5,7 @@ serum_crank.tar.gz Makefile README.md my-crank.log - +crank # regexs **/*.txt **/yarn.lock diff --git a/Cargo.lock b/Cargo.lock index b9bba62..b3d68b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -441,43 +441,12 @@ dependencies = [ "ansi_term", "atty", "bitflags", - "strsim 0.8.0", - "textwrap 0.11.0", + "strsim", + "textwrap", "unicode-width", "vec_map", ] -[[package]] -name = "clap" -version = "3.0.0-beta.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcd70aa5597dbc42f7217a543f9ef2768b2ef823ba29036072d30e1d88e98406" -dependencies = [ - "atty", - "bitflags", - "clap_derive", - "indexmap", - "lazy_static", - "os_str_bytes", - "strsim 0.10.0", - "termcolor", - "textwrap 0.14.2", - "vec_map", -] - -[[package]] -name = "clap_derive" -version = "3.0.0-beta.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b5bb0d655624a0b8770d1c178fb8ffcb1f91cc722cb08f451e3dc72465421ac" -dependencies = [ - "heck", - "proc-macro-error", - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", -] - [[package]] name = "cloudabi" version = "0.0.3" @@ -555,15 +524,24 @@ name = "crank" version = "0.2.0" dependencies = [ "anyhow", - "clap 3.0.0-beta.4", + "clap", + "crossbeam", + "crossbeam-channel 0.5.1", + "crossbeam-queue 0.3.2", + "crossbeam-utils 0.8.5", "debug_print", "enumflags2", "jemallocator", "log", "rand 0.7.3", "safe-transmute", + "serde", + "serde_json", + "serde_yaml", "serum-common", "serum_dex", + "signal-hook", + "simplelog", "slog-scope", "slog-stdlog", "sloggers", @@ -571,7 +549,7 @@ dependencies = [ "solana-sdk", "spl-token 3.2.0", "threadpool", - "tokio 0.2.25", + "tokio 1.10.0", "warp", ] @@ -584,6 +562,20 @@ dependencies = [ "cfg-if 1.0.0", ] +[[package]] +name = "crossbeam" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae5588f6b3c3cb05239e90bd110f257254aecd01e4635400391aeae07497845" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-channel 0.5.1", + "crossbeam-deque 0.8.1", + "crossbeam-epoch 0.9.5", + "crossbeam-queue 0.3.2", + "crossbeam-utils 0.8.5", +] + [[package]] name = "crossbeam-channel" version = "0.4.4" @@ -665,6 +657,16 @@ dependencies = [ "maybe-uninit", ] +[[package]] +name = "crossbeam-queue" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b10ddc024425c88c2ad148c1b0fd53f4c6d38db9697c9f1588381212fa657c9" +dependencies = [ + "cfg-if 1.0.0", + "crossbeam-utils 0.8.5", +] + [[package]] name = "crossbeam-utils" version = "0.7.2" @@ -1306,15 +1308,6 @@ dependencies = [ "http", ] -[[package]] -name = "heck" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] - [[package]] name = "hermit-abi" version = "0.1.19" @@ -1881,18 +1874,6 @@ dependencies = [ "winapi 0.3.9", ] -[[package]] -name = "mio-named-pipes" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0840c1c50fd55e521b247f949c241c9997709f23bd7f023b9762cd561e935656" -dependencies = [ - "log", - "mio 0.6.23", - "miow 0.3.7", - "winapi 0.3.9", -] - [[package]] name = "mio-uds" version = "0.6.8" @@ -2124,12 +2105,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "os_str_bytes" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6acbef58a60fe69ab50510a55bc8cdd4d6cf2283d27ad338f54cb52747a9cf2d" - [[package]] name = "ouroboros" version = "0.10.1" @@ -3004,6 +2979,16 @@ dependencies = [ "opaque-debug 0.3.0", ] +[[package]] +name = "signal-hook" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "470c5a6397076fae0094aaf06a08e6ba6f37acb77d3b1b91ea92b4d6c8650c39" +dependencies = [ + "libc", + "signal-hook-registry", +] + [[package]] name = "signal-hook-registry" version = "1.4.0" @@ -3019,6 +3004,17 @@ version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c19772be3c4dd2ceaacf03cb41d5885f2a02c4d8804884918e3a258480803335" +[[package]] +name = "simplelog" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59d0fe306a0ced1c88a58042dc22fc2ddd000982c26d75f6aa09a394547c41e0" +dependencies = [ + "chrono", + "log", + "termcolor", +] + [[package]] name = "slab" version = "0.4.4" @@ -3173,7 +3169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c57b6f6857ecf2c404cfb550419f67350b716eae49ede1f456d5b5de5b043540" dependencies = [ "chrono", - "clap 2.33.3", + "clap", "rpassword", "solana-remote-wallet", "solana-sdk", @@ -3206,7 +3202,7 @@ dependencies = [ "base64 0.13.0", "bincode", "bs58", - "clap 2.33.3", + "clap", "indicatif", "jsonrpc-core", "log", @@ -3279,7 +3275,7 @@ checksum = "dca9b774547ae7eab0e20817c5179892ce9a9e9fa572d951aa31d01c5641bd71" dependencies = [ "bincode", "byteorder", - "clap 2.33.3", + "clap", "log", "serde", "serde_derive", @@ -3369,7 +3365,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f826d57f78d3b40fe6c008813367ce70c44e1b2488270b6c68cc050a75d8bb6b" dependencies = [ "bincode", - "clap 2.33.3", + "clap", "log", "nix", "rand 0.7.3", @@ -3727,12 +3723,6 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - [[package]] name = "subtle" version = "2.4.1" @@ -3858,15 +3848,6 @@ dependencies = [ "unicode-width", ] -[[package]] -name = "textwrap" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0066c8d12af8b5acd21e00547c3797fde4e8677254a7ee429176ccebbe93dd80" -dependencies = [ - "unicode-width", -] - [[package]] name = "thiserror" version = "1.0.26" @@ -3983,17 +3964,10 @@ dependencies = [ "futures-core", "iovec", "lazy_static", - "libc", "memchr", "mio 0.6.23", - "mio-named-pipes", - "mio-uds", - "num_cpus", "pin-project-lite 0.1.12", - "signal-hook-registry", "slab", - "tokio-macros 0.2.6", - "winapi 0.3.9", ] [[package]] @@ -4012,7 +3986,7 @@ dependencies = [ "parking_lot 0.11.1", "pin-project-lite 0.2.7", "signal-hook-registry", - "tokio-macros 1.3.0", + "tokio-macros", "winapi 0.3.9", ] @@ -4069,17 +4043,6 @@ dependencies = [ "log", ] -[[package]] -name = "tokio-macros" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e44da00bfc73a25f814cd8d7e57a68a5c31b74b3152a0a1d1f590c97ed06265a" -dependencies = [ - "proc-macro2 1.0.28", - "quote 1.0.9", - "syn 1.0.74", -] - [[package]] name = "tokio-macros" version = "1.3.0" @@ -4152,7 +4115,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df720b6581784c118f0eb4310796b12b1d242a7eb95f716a8367855325c25f89" dependencies = [ "crossbeam-deque 0.7.4", - "crossbeam-queue", + "crossbeam-queue 0.2.3", "crossbeam-utils 0.7.2", "futures 0.1.31", "lazy_static", @@ -4403,12 +4366,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8895849a949e7845e06bd6dc1aa51731a103c42707010a5b591c0038fb73385b" - [[package]] name = "unicode-width" version = "0.1.8" diff --git a/Cargo.toml b/Cargo.toml index 327b601..d826087 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,18 +5,18 @@ edition = "2018" [[bin]] name = "crank" -path = "src/bin/main.rs" +path = "src/main.rs" [dependencies] # 1be91f2863d8ecede32daaae7e768034e24bbc79 == v0.4.0 commit serum_dex = { git = "https://github.com/project-serum/serum-dex.git", rev = "1be91f2863d8ecede32daaae7e768034e24bbc79", default-features = false, features = ["client"] } serum-common = { git = "https://github.com/project-serum/serum-dex.git", rev ="1be91f2863d8ecede32daaae7e768034e24bbc79", features = ["client"] } spl-token = { version = "3.0.0-pre1", features = ["no-entrypoint"], default-features = false } -clap = "3.0.0-beta.1" +clap = "2.33.3" enumflags2 = "0.6.4" solana-client = "1.6.18" solana-sdk = "1.6.18" -anyhow = "1.0.32" +anyhow = "1.0.43" rand = "0.7.3" safe-transmute = "0.11.0" threadpool = "1.8.1" @@ -25,14 +25,22 @@ slog-scope = "4.3" slog-stdlog = "4" log = "0.4" debug_print = "1.0.0" -tokio = {version = "0.2", features = ["full"]} +tokio = { version = "1.8.0", features = ["rt-multi-thread"] } warp = "0.2" jemallocator = "0.3.2" - +serde_json = "1.0.64" +serde = { version = "1.0", features = ["derive"] } +serde_yaml = "0.8" +simplelog = "0.10.0" +crossbeam = "0.8.1" +crossbeam-channel = "0.5.1" +crossbeam-utils = "0.8.5" +crossbeam-queue = "0.3.2" +signal-hook = "0.3.9" [profile.release] lto = "fat" codegen-units = 1 [profile.release.build-override] opt-level = 3 incremental = false -codegen-units = 1 \ No newline at end of file +codegen-units = 1 diff --git a/Dockerfile b/Dockerfile index 067b216..2fa5d19 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,5 +27,4 @@ RUN --mount=type=cache,target=/home/root/.cache/sccache cargo build --release -- # RUN shred /root/.ssh/id_rsa && shred /root/.ssh/id_rsa.pub && rm -rf /root/.ssh FROM rust:1.54.0 as runtime COPY --from=BUILDER /tmp/crank /usr/local/bin -COPY --from=BUILDER /home/root/app/entrypoint.sh /usr/local/bin/entrypoint.sh -ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] \ No newline at end of file +ENTRYPOINT ["/usr/local/bin/crank"] \ No newline at end of file diff --git a/Makefile b/Makefile index 4cba4ce..03fc0c4 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,7 @@ .PHONY: build-cli build-cli: cargo build --release + cp target/release/crank crank .PHONY: build-docker @@ -11,3 +12,7 @@ build-docker: --squash . docker image save serum-crank:latest -o serum_crank.tar pigz -f -9 serum_crank.tar + +.PHONY: fmt +fmt: + find -type f -name "*.rs" -not -path "*target*" -exec rustfmt --edition 2018 {} \; diff --git a/README.md b/README.md index 2b7be46..2016e73 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,11 @@ # serum-crank -Docker image for serum crank using maximal rust compiler optimizations. See [serum's licese file](https://github.com/project-serum/serum-dex/blob/v0.4.0/LICENSE) for the license of the source code. - -# Differences From Upstream - -* Specifies `--url` as the RPC to connect to instead of network which ends up using public rpc infra -* Forces global allocator to use jemalloc +A performance and cost optimized serum crank based on the one from [serum-dex](https://github.com/project-serum/serum-dex/tree/master/dex/crank), that allows cranking multiple markets in a single transaction. # Docker Image A docker image that allows running the crank service through a docker container. Image is designed to be used with docker buildkit to enable as much caching as possible to reduce build times. For those wishing to crank multiple markets at once, a docker compose file may be used. + ## Requirements * Requires a compatible docker installation with the "build kit" features enabled @@ -24,18 +20,7 @@ $> make build-docker Docker image will be available locally as `serum-crank:latest` or as a gzip compressed file in the current working directory as `serum_crank.tar.gz` -## Configuration - -* `NUMWORKERS` - defines the maximum workers for a crank -* `LOGDIRECTORY` - defines the file to store logs in, not the actual directory - ## Docker Compose For use with docker compose and cranking multiple markets you can use `docker-compose.yml` as a base. - -# Crank Docs - -* anytime a series of events are received, one of the workers will "consume the events" - * read keypair, clone the program id, get a client, clone accoune metas - * optimizations: read keypair at startup and wrap it in an `Arc` diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..c3c0163 --- /dev/null +++ b/config.yaml @@ -0,0 +1,11 @@ +--- +http_rpc_url: "https://api.devnet.solana.com" +ws_rpc_url: "ws://api.devnet.solana.com" +key_path: ~/.config/solana/id.json +log_file: liquidator.log +debug_log: false +markets: + - name: TULIP-USDC + market_account: somekey + coin_wallet: somewallet + pc_wallet: some_pc_wallet diff --git a/docker-compose.yml b/docker-compose.yml index 1ba9322..fca12b5 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,14 +2,7 @@ version: "3.5" services: tulip: image: serum-crank:latest - environment: - COINWALLET: "DhLZthVn5sr8K16TLDAKZdAPv9VXwhKxJzRizaYgp614" - PCWALLET: "DhLZthVn5sr8K16TLDAKZdAPv9VXwhKxJzRizaYgp614" - EVENTSPERWORKER: 5 - LOGDIR: /tmp/crank.log - MARKET: "8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW" - NUMACCOUNTS: 5 - NUMWORKERS: 1 - RPC_URL: "https://solana-api.projectserum.com" + command: --config /tmp/config.yaml run volumes: - - ./payer.json:/tmp/payer.json \ No newline at end of file + - ./payer.json:/tmp/payer.json + - ./config.yaml:/tmp/config.yaml \ No newline at end of file diff --git a/my-crank.log b/my-crank.log new file mode 100644 index 0000000..c4ce4d5 --- /dev/null +++ b/my-crank.log @@ -0,0 +1,191 @@ +Aug 22 19:59:43.512 INFO Getting market keys ..., module: crank:311 +Aug 22 19:59:46.336 INFO MarketPubkeys { + market: 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, + req_q: DAntkL9MxXEKad58DMmDsjwPgztFmkgPeucuNCCjUBb1, + event_q: ExbLY71YpFaAGKuHjJKXSsWLA8hf1hGLoUYHNtzvbpGJ, + bids: 69W6zLetZ7FgXPXgHRp4i4wNd422tXeZzDuBzdkjgoBW, + asks: 42RcphsKYsVWDhaqJRETmx74RHXtHJDjZLFeeDrEL2F9, + coin_vault: 6qH3FNTSGKw34SEEj7GXbQ6kMQXHwuyGsAAeV5hLPhJc, + pc_vault: 6AdJbeH76BBSJ34DeQ6LLdauF6W8fZRrMKEfLt3YcMcT, + vault_signer_key: 5uJEd4wfVH84HyFEBf5chfJMTTPHBddXi1S7GmBE6x14, +}, module: crank:314 +Aug 22 20:02:50.202 INFO Getting market keys ..., module: crank:311 +Aug 22 20:02:50.752 INFO MarketPubkeys { + market: 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, + req_q: DAntkL9MxXEKad58DMmDsjwPgztFmkgPeucuNCCjUBb1, + event_q: ExbLY71YpFaAGKuHjJKXSsWLA8hf1hGLoUYHNtzvbpGJ, + bids: 69W6zLetZ7FgXPXgHRp4i4wNd422tXeZzDuBzdkjgoBW, + asks: 42RcphsKYsVWDhaqJRETmx74RHXtHJDjZLFeeDrEL2F9, + coin_vault: 6qH3FNTSGKw34SEEj7GXbQ6kMQXHwuyGsAAeV5hLPhJc, + pc_vault: 6AdJbeH76BBSJ34DeQ6LLdauF6W8fZRrMKEfLt3YcMcT, + vault_signer_key: 5uJEd4wfVH84HyFEBf5chfJMTTPHBddXi1S7GmBE6x14, +}, module: crank:314 +Aug 22 20:02:52.493 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:02:52.493 INFO Total event queue length: 6, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:02:52.493 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:02:52.494 INFO First 5 accounts: [3297932972331208619], module: crank:393 +Aug 22 20:02:52.494 INFO Fetching 6 events from the queue took 741, module: crank:419 +Aug 22 20:02:53.008 INFO Consuming events ..., module: crank:522 +Aug 22 20:02:53.415 INFO [thread 0] Successfully consumed events after 919.121213ms: 2BtAttLjPNK5DoD2Jqhz5TzeMWty9LEZh719NJ3mcdWznWPb1X7FMUfYs7YeZnp3YXUjT8TpZ1gxmdTBoVWrnKdg., module: crank:478 +Aug 22 20:02:53.415 INFO Total loop time took 1663, module: crank:447 +Aug 22 20:02:54.974 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916605, Max seen slot: 92916605, module: crank:331 +Aug 22 20:02:56.553 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916596, Max seen slot: 92916605, module: crank:331 +Aug 22 20:02:58.245 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916603, Max seen slot: 92916605, module: crank:331 +Aug 22 20:02:59.793 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916603, Max seen slot: 92916605, module: crank:331 +Aug 22 20:03:01.585 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:01.586 INFO Total event queue length: 10, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:03:01.586 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:03:01.586 INFO First 5 accounts: [3297932972331208619], module: crank:393 +Aug 22 20:03:01.586 INFO Fetching 10 events from the queue took 792, module: crank:419 +Aug 22 20:03:02.295 INFO Consuming events ..., module: crank:522 +Aug 22 20:03:02.567 INFO [thread 0] Successfully consumed events after 979.685141ms: 3odVYBJ5w5TwycDrjQK22R1cPGbm7sSqWJpx5BembE6gPFP2UHeZks1fgGnaq2VSQ3cwDCBjLvieibzjiixixmev., module: crank:478 +Aug 22 20:03:02.568 INFO Total loop time took 1775, module: crank:447 +Aug 22 20:03:04.169 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916601, Max seen slot: 92916611, module: crank:331 +Aug 22 20:03:05.573 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916601, Max seen slot: 92916611, module: crank:331 +Aug 22 20:03:07.104 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916605, Max seen slot: 92916611, module: crank:331 +Aug 22 20:03:09.594 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:11.642 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:15.021 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:16.457 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916605, Max seen slot: 92916611, module: crank:331 +Aug 22 20:03:17.964 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916605, Max seen slot: 92916611, module: crank:331 +Aug 22 20:03:19.712 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:19.712 INFO Total event queue length: 12, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:03:19.712 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:03:19.712 INFO First 5 accounts: [3297932972331208619], module: crank:393 +Aug 22 20:03:19.712 INFO Fetching 12 events from the queue took 747, module: crank:419 +Aug 22 20:03:20.653 INFO Consuming events ..., module: crank:522 +Aug 22 20:03:22.109 INFO [thread 0] Successfully consumed events after 2.395158157s: 5ttZjedWYHt6z6LxvxKauSkst6Yb78Joi7WLpdaChqtepLPR34VxHCjyWYgkX14rgPsosJxP6U7B7JU1iPSbTcB2., module: crank:478 +Aug 22 20:03:22.110 INFO Total loop time took 3145, module: crank:447 +Aug 22 20:03:23.832 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916605, Max seen slot: 92916691, module: crank:331 +Aug 22 20:03:25.264 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916691, module: crank:331 +Aug 22 20:03:27.136 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:27.136 INFO Total event queue length: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:03:27.136 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:03:27.136 INFO First 5 accounts: [6316613602174617701], module: crank:393 +Aug 22 20:03:27.136 INFO Fetching 1 events from the queue took 872, module: crank:419 +Aug 22 20:03:27.903 INFO Consuming events ..., module: crank:522 +Aug 22 20:03:28.161 INFO [thread 0] Successfully consumed events after 1.024487746s: 5Wfevf8ZnykQVJEucn7wx7L5ZFxpzS1QzkvykcsVV2H3T2F3CTSXkXg9hC15UUF5fsYuvuQXLMt6iVWcKgzvBim9., module: crank:478 +Aug 22 20:03:28.162 INFO Total loop time took 1897, module: crank:447 +Aug 22 20:03:29.702 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916601, Max seen slot: 92916706, module: crank:331 +Aug 22 20:03:31.218 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916622, Max seen slot: 92916706, module: crank:331 +Aug 22 20:03:33.146 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:34.674 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916601, Max seen slot: 92916706, module: crank:331 +Aug 22 20:03:36.183 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916605, Max seen slot: 92916706, module: crank:331 +Aug 22 20:03:38.164 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:38.164 INFO Total event queue length: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:03:38.164 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:03:38.164 INFO First 5 accounts: [6316613602174617701], module: crank:393 +Aug 22 20:03:38.164 INFO Fetching 1 events from the queue took 981, module: crank:419 +Aug 22 20:03:38.881 INFO Consuming events ..., module: crank:522 +Aug 22 20:03:39.261 INFO [thread 0] Successfully consumed events after 1.095340091s: 4jKomAyzcEH24eNfV1omDpcUH3ZN7vNhi7vKN4LSYdcvi2c637aE1f8yuQrjFpwUSuV3hdFDJCmHAsMFXx8jMrhp., module: crank:478 +Aug 22 20:03:39.262 INFO Total loop time took 2079, module: crank:447 +Aug 22 20:03:41.131 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:43.184 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:44.927 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916702, Max seen slot: 92916708, module: crank:331 +Aug 22 20:03:46.668 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:48.609 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:50.143 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916702, Max seen slot: 92916708, module: crank:331 +Aug 22 20:03:51.641 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916708, module: crank:331 +Aug 22 20:03:53.832 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:55.860 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:57.432 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:03:58.819 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916708, module: crank:331 +Aug 22 20:04:00.543 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:03.446 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:05.485 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:07.622 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:09.028 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916708, module: crank:331 +Aug 22 20:04:11.336 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:13.162 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:14.928 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:16.681 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:18.817 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:18.817 INFO Total event queue length: 2, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:04:18.822 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:04:18.822 INFO First 5 accounts: [11284759293402717712], module: crank:393 +Aug 22 20:04:18.822 INFO Fetching 2 events from the queue took 1135, module: crank:419 +Aug 22 20:04:19.489 INFO Consuming events ..., module: crank:522 +Aug 22 20:04:19.697 INFO [thread 0] Successfully consumed events after 878.778914ms: 2bK2f2JCQShqJnP3VvrnzWaKAbY3FRqcjRy682h7BeKHwuEfBRuYgqaP7Je4eyE6Lao9r2EuzQhGTbUTRAkMcaGn., module: crank:478 +Aug 22 20:04:19.698 INFO Total loop time took 2017, module: crank:447 +Aug 22 20:04:21.474 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:21.474 INFO Total event queue length: 2, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:04:21.474 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:04:21.474 INFO First 5 accounts: [11284759293402717712], module: crank:393 +Aug 22 20:04:21.474 INFO Fetching 2 events from the queue took 775, module: crank:419 +Aug 22 20:04:22.006 INFO Consuming events ..., module: crank:522 +Aug 22 20:04:22.227 INFO [thread 0] Successfully consumed events after 750.706865ms: 36cfVH9JesEJSysRgPpVoHmDm92qYXsmhUxrRoGC7yT6BzordW5gC2hTYMNBPeyXSkZHk1EzvVrxKLswvaKZeiik., module: crank:478 +Aug 22 20:04:22.227 INFO Total loop time took 1529, module: crank:447 +Aug 22 20:04:24.027 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:25.439 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916758, Max seen slot: 92916764, module: crank:331 +Aug 22 20:04:27.600 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:29.750 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:29.750 INFO Total event queue length: 5, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:04:29.750 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:04:29.750 INFO First 5 accounts: [3297932972331208619], module: crank:393 +Aug 22 20:04:29.750 INFO Fetching 5 events from the queue took 1150, module: crank:419 +Aug 22 20:04:30.320 INFO Consuming events ..., module: crank:522 +Aug 22 20:04:30.900 INFO [thread 0] Successfully consumed events after 1.148546481s: 643ejMkYXMMfoaignqX1MMabxRAxc4H9fmMFQMUe3ZLLoCC5ZJPLx1NrSunrYYjARUonnKWAUQYgfx5vdMP4cr4L., module: crank:478 +Aug 22 20:04:30.901 INFO Total loop time took 2301, module: crank:447 +Aug 22 20:04:32.928 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:34.565 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916760, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:36.324 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916763, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:38.266 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916763, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:40.014 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:41.451 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916758, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:43.521 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:45.034 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916761, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:47.259 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:48.874 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:50.257 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916760, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:51.632 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916763, Max seen slot: 92916774, module: crank:331 +Aug 22 20:04:53.278 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:55.582 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:04:58.157 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:00.084 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:01.466 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916774, module: crank:331 +Aug 22 20:05:03.695 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:05.156 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916758, Max seen slot: 92916774, module: crank:331 +Aug 22 20:05:06.946 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:08.641 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:10.320 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:12.164 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:14.115 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:16.570 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:18.344 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:20.360 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:22.500 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:24.075 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916774, module: crank:331 +Aug 22 20:05:25.787 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:27.542 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:30.088 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:32.034 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:33.657 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:35.614 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:37.770 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:40.537 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:42.416 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:43.806 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916760, Max seen slot: 92916774, module: crank:331 +Aug 22 20:05:46.116 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:47.847 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:50.266 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:50.267 INFO Total event queue length: 2, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:373 +Aug 22 20:05:50.267 INFO Number of unique order accounts: 1, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:386 +Aug 22 20:05:50.267 INFO First 5 accounts: [10494282572836102747], module: crank:393 +Aug 22 20:05:50.267 INFO Fetching 2 events from the queue took 1419, module: crank:419 +Aug 22 20:05:51.399 INFO Consuming events ..., module: crank:522 +Aug 22 20:05:51.797 INFO [thread 0] Successfully consumed events after 1.528884165s: KXTL1eAUpjenta47UtkqQHfUDfU5sDTrHoYTqWUTi2WKcLUYS6XxDqeJ36b5SR4Zi9SzsNT8g12u25N257beW4a., module: crank:478 +Aug 22 20:05:51.798 INFO Total loop time took 2950, module: crank:447 +Aug 22 20:05:53.335 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916954, module: crank:331 +Aug 22 20:05:55.484 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:05:57.186 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916757, Max seen slot: 92916954, module: crank:331 +Aug 22 20:05:59.068 INFO Skipping crank. Already cranked for slot. Event queue slot: 92626386, Max seen slot: 92916954, module: crank:331 +Aug 22 20:06:00.991 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:02.675 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:04.295 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:05.827 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916870, Max seen slot: 92916954, module: crank:331 +Aug 22 20:06:07.214 INFO Skipping crank. Already cranked for slot. Event queue slot: 92916758, Max seen slot: 92916954, module: crank:331 +Aug 22 20:06:09.717 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:13.301 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:14.878 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:16.462 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 +Aug 22 20:06:18.560 INFO Size of request queue is 0, market 8GufnKq7YnXKhnB3WNhgy5PzU9uvHbaaRrZWQK6ixPxW, coin 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, pc 9UJ1AYv6rko5xu4Z7DnA5hLaNZFxDu9BxqKp1LULivqC, module: crank:353 diff --git a/src/bin/main.rs b/src/bin/main.rs deleted file mode 100644 index ee71336..0000000 --- a/src/bin/main.rs +++ /dev/null @@ -1,11 +0,0 @@ -#[global_allocator] -static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; - -use anyhow::Result; -use clap::Clap; -use crank::Opts; - -fn main() -> Result<()> { - let opts = Opts::parse(); - crank::start(opts) -} diff --git a/src/config.rs b/src/config.rs new file mode 100644 index 0000000..ad95e14 --- /dev/null +++ b/src/config.rs @@ -0,0 +1,193 @@ +use crate::crank::{get_keys_for_market, MarketPubkeys}; +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use simplelog::*; +use solana_client::rpc_client::RpcClient; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::{read_keypair_file, Keypair}; +use std::fs::File; +use std::sync::Arc; +use std::{fs, str::FromStr}; + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Configuration { + pub http_rpc_url: String, + pub ws_rpc_url: String, + pub key_path: String, + pub log_file: String, + pub debug_log: bool, + pub crank: Crank, +} + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Crank { + pub markets: Vec, + pub dex_program: String, + pub max_wait_for_events_delay: u64, + pub num_accounts: usize, + pub events_per_worker: usize, + pub max_markets_per_tx: usize, +} + +#[derive(Clone, Default, Debug, PartialEq, Serialize, Deserialize)] +pub struct Market { + pub name: String, + pub market_account: String, + pub coin_wallet: String, + pub pc_wallet: String, +} + +#[derive(Clone, Default, Debug, PartialEq)] +pub struct ParsedMarketKeys { + pub keys: MarketPubkeys, + pub coin_wallet: Pubkey, + pub pc_wallet: Pubkey, +} + +impl Crank { + pub fn market_keys( + &self, + rpc: &Arc, + program_id: Pubkey, + ) -> Result> { + let mut markets = vec![]; + for market in self.markets.iter() { + let market_keys = get_keys_for_market( + &rpc, + &program_id, + &Pubkey::from_str(market.market_account.as_str()).unwrap(), + )?; + markets.push(ParsedMarketKeys { + keys: market_keys, + coin_wallet: Pubkey::from_str(market.coin_wallet.as_str()).unwrap(), + pc_wallet: Pubkey::from_str(market.pc_wallet.as_str()).unwrap(), + }) + } + Ok(markets) + } +} + +impl Configuration { + pub fn new(path: &str, as_json: bool) -> Result<()> { + let config = Configuration::default(); + config.save(path, as_json) + } + pub fn save(&self, path: &str, as_json: bool) -> Result<()> { + let data = if as_json { + serde_json::to_string_pretty(&self)? + } else { + serde_yaml::to_string(&self)? + }; + fs::write(path, data).expect("failed to write to file"); + Ok(()) + } + pub fn load(path: &str, from_json: bool) -> Result { + let data = fs::read(path).expect("failed to read file"); + let config: Configuration = if from_json { + serde_json::from_slice(data.as_slice())? + } else { + serde_yaml::from_slice(data.as_slice())? + }; + Ok(config) + } + pub fn payer(&self) -> Keypair { + read_keypair_file(self.key_path.clone()).expect("failed to read keypair file") + } + /// if file_log is true, log to both file and stdout + /// otherwise just log to stdout + pub fn init_log(&self, file_log: bool) -> Result<()> { + if !file_log { + if self.debug_log { + TermLogger::init( + LevelFilter::Debug, + ConfigBuilder::new() + .set_location_level(LevelFilter::Debug) + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + )?; + return Ok(()); + } else { + TermLogger::init( + LevelFilter::Info, + ConfigBuilder::new() + .set_location_level(LevelFilter::Error) + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + )?; + return Ok(()); + } + } + if self.debug_log { + CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Debug, + ConfigBuilder::new() + .set_location_level(LevelFilter::Debug) + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + ), + WriteLogger::new( + LevelFilter::Debug, + ConfigBuilder::new() + .set_location_level(LevelFilter::Debug) + .build(), + File::create(self.log_file.as_str()).unwrap(), + ), + ])?; + } else { + CombinedLogger::init(vec![ + TermLogger::new( + LevelFilter::Info, + ConfigBuilder::new() + .set_location_level(LevelFilter::Error) + .build(), + TerminalMode::Mixed, + ColorChoice::Auto, + ), + WriteLogger::new( + LevelFilter::Info, + ConfigBuilder::new() + .set_location_level(LevelFilter::Error) + .build(), + File::create(self.log_file.as_str()).unwrap(), + ), + ])?; + } + + Ok(()) + } +} + +impl Default for Configuration { + fn default() -> Self { + Self { + http_rpc_url: "https://api.devnet.solana.com".to_string(), + ws_rpc_url: "ws://api.devnet.solana.com".to_string(), + key_path: "~/.config/solana/id.json".to_string(), + log_file: "liquidator.log".to_string(), + debug_log: false, + crank: Crank::default(), + } + } +} + +impl Default for Crank { + fn default() -> Self { + Self { + dex_program: "9xQeWvG816bUx9EPjHmaT23yvVM2ZWbrrpZb9PusVFin".to_string(), + markets: vec![Market { + name: "TULIP-USDC".to_string(), + market_account: "somekey".to_string(), + coin_wallet: "somewallet".to_string(), + pc_wallet: "some_pc_wallet".to_string(), + }], + max_wait_for_events_delay: 60, + num_accounts: 32, + events_per_worker: 5, + max_markets_per_tx: 6, + } + } +} diff --git a/src/crank.rs b/src/crank.rs new file mode 100644 index 0000000..33c2429 --- /dev/null +++ b/src/crank.rs @@ -0,0 +1,453 @@ +use crate::{ + config::{Configuration, ParsedMarketKeys}, +}; +use anyhow::{anyhow, format_err, Result}; +use crossbeam::{select, sync::WaitGroup}; +use crossbeam_channel::Receiver; +use crossbeam_queue::ArrayQueue; +use log::{debug, error, info, warn}; +use safe_transmute::{ + guard::SingleManyGuard, + to_bytes::{transmute_one_to_bytes, transmute_to_bytes}, + transmute_many, transmute_many_pedantic, transmute_one_pedantic, +}; +use serum_dex::instruction::MarketInstruction; +use serum_dex::state::gen_vault_signer_key; +use serum_dex::state::Event; +use serum_dex::state::EventQueueHeader; +use serum_dex::state::QueueHeader; +use serum_dex::state::{AccountFlag, Market, MarketState, MarketStateV2}; +use solana_client::rpc_config::RpcSendTransactionConfig; +use solana_client::{self, rpc_client::RpcClient}; +use solana_sdk::commitment_config::CommitmentConfig; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::signature::Signature; +use solana_sdk::signature::Signer; +use solana_sdk::signer::keypair::Keypair; +use solana_sdk::transaction::Transaction; +use solana_sdk::{self, pubkey::Pubkey, signature::read_keypair_file}; +use std::collections::BTreeSet; +use std::convert::identity; +use std::mem::size_of; +use std::str::FromStr; +use std::sync::Arc; +use std::sync::Mutex; +use std::{borrow::Cow, collections::HashMap, sync::RwLock}; +use std::{ + cmp::{max, min}, + ops::Deref, +}; +pub struct Crank { + pub config: Arc, +} + +impl Crank { + pub fn new(config: Arc) -> Arc { + Arc::new(Self { config }) + } + pub fn start(self: &Arc, exit_chan: Receiver) -> Result<()> { + let rpc_client = Arc::new(RpcClient::new(self.config.http_rpc_url.clone())); + let payer = Arc::new(self.config.payer()); + let dex_program = Pubkey::from_str(self.config.crank.dex_program.as_str()).unwrap(); + let market_keys = self.config.crank.market_keys(&rpc_client, dex_program)?; + let slot_height_map: Arc>> = + Arc::new(RwLock::new(HashMap::new())); + loop { + select! { + recv(exit_chan) -> _msg => { + warn!("caught exit signal"); + return Ok(()); + }, + default => {} + } + let work_loop = + |market_key: &ParsedMarketKeys| -> Result>> { + let event_q_value_and_context = rpc_client.get_account_with_commitment( + &market_key.keys.event_q, + CommitmentConfig::processed(), + )?; + let event_q_slot = event_q_value_and_context.context.slot; + { + if let Ok(max_height) = slot_height_map.try_read() { + if let Some(height) = + max_height.get(&market_key.keys.market.to_string()) + { + if event_q_slot <= *height { + info!( + "Skipping crank. Already cranked for slot. Event queue slot: {}, Max seen slot: {}", + event_q_slot, height + ); + return Ok(None); + } + } + } else { + return Ok(None); + } + } + let event_q_data = match event_q_value_and_context.value { + Some(event_q) => event_q.data, + None => { + return Err(anyhow!( + "{} event q value and context is none, skipping....", + market_key.keys.market + )); + } + }; + let req_q_data = rpc_client.get_account_with_commitment( + &market_key.keys.req_q, + CommitmentConfig::processed(), + ); + let req_q_data = { + match req_q_data { + Ok(req_q_data) => match req_q_data.value { + Some(acct) => acct.data, + None => { + return Err(anyhow!( + "failed to retrieve market {} request q", + market_key.keys.market + )); + } + }, + Err(err) => { + return Err(anyhow!( + "failed to retrieve market {} request q {:#?}", + market_key.keys.market, + err + )); + } + } + }; + let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?; + let (_header, seg0, seg1) = parse_event_queue(&inner)?; + let req_inner: Cow<[u64]> = remove_dex_account_padding(&req_q_data)?; + let (_req_header, req_seg0, req_seg1) = parse_event_queue(&req_inner)?; + let event_q_len = seg0.len() + seg1.len(); + let req_q_len = req_seg0.len() + req_seg1.len(); + info!( + "Size of request queue is {}, market {}, coin {}, pc {}", + req_q_len, + market_key.keys.market, + market_key.coin_wallet, + market_key.pc_wallet + ); + if event_q_len == 0 { + return Ok(None); + } + info!( + "Total event queue length: {}, market {}, coin {}, pc {}", + event_q_len, + market_key.keys.market, + market_key.coin_wallet, + market_key.pc_wallet + ); + let accounts = seg0.iter().chain(seg1.iter()).map(|event| event.owner); + let mut used_accounts = BTreeSet::new(); + for account in accounts { + used_accounts.insert(account); + if used_accounts.len() >= self.config.crank.num_accounts { + break; + } + } + let orders_accounts: Vec<_> = used_accounts.into_iter().collect(); + info!( + "Number of unique order accounts: {}, market {}, coin {}, pc {}", + orders_accounts.len(), + market_key.keys.market, + market_key.coin_wallet, + market_key.pc_wallet + ); + info!( + "First 5 accounts: {:?}", + orders_accounts + .iter() + .take(5) + .map(hash_accounts) + .collect::>() + ); + + let mut account_metas = Vec::with_capacity(orders_accounts.len() + 4); + for pubkey_words in orders_accounts { + let pubkey = Pubkey::new(transmute_to_bytes(&pubkey_words)); + account_metas.push(AccountMeta::new(pubkey, false)); + } + for pubkey in [ + &market_key.keys.market, + &market_key.keys.event_q, + &market_key.coin_wallet, + &market_key.pc_wallet, + ] + .iter() + { + account_metas.push(AccountMeta::new(**pubkey, false)); + } + let instructions = consume_events_ix( + &dex_program, + &payer, + account_metas, + self.config.crank.events_per_worker, + )?; + Ok(Some(instructions)) + }; + info!("starting crank run"); + { + let market_keys = market_keys.clone(); + let res = crossbeam::thread::scope(|s| { + let q: Arc, Pubkey)>> = + Arc::new(ArrayQueue::new(market_keys.len())); + let wg = WaitGroup::new(); + for market_key in market_keys.iter() { + let wg = wg.clone(); + let q = Arc::clone(&q); + let market_key = market_key.clone(); + s.spawn(move |_| { + let ixs = work_loop(&market_key); + match ixs { + Ok(ixs) => match ixs { + Some(ixs) => { + let res = q.push((ixs, *market_key.keys.market.clone().deref())); + if res.is_err() { + error!("failed to push instruction set onto stack for market {}: {:#?}", market_key.keys.market, res.err()); + } + } + None => { + warn!( + "found no instructions for market {}", + market_key.keys.market + ); + } + }, + Err(err) => { + error!( + "failed to run work loop for market {}: {:#?}", + market_key.keys.market, err + ); + } + } + drop(wg); + }); + } + info!("waiting for spawned threads to finish"); + wg.wait(); + info!("collecting instructions"); + let mut instructions = vec![]; + let mut instructions_markets = vec![]; + loop { + let ix_set = q.pop(); + if ix_set.is_none() { + break; + } + let ix_set = ix_set.unwrap(); + instructions.extend_from_slice(&ix_set.0); + instructions_markets.push(ix_set.1); + } + if instructions.len() > 0 { + info!( + "found instructions for {} markets: {:#?}", + instructions.len() / 2, + instructions_markets + ); + let run_loop = |instructions: &Vec| -> Result { + let (recent_hash, _fee_calc) = rpc_client.get_recent_blockhash()?; + let txn = Transaction::new_signed_with_payer( + &instructions[..], + Some(&payer.pubkey()), + &[payer.deref()], + recent_hash, + ); + info!("sending crank instructions"); + let signature = rpc_client.send_transaction_with_config( + &txn, + RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + }, + )?; + Ok(signature) + }; + if instructions_markets.len() > 6 { + // 6 is theoretically the maximum cranks we can fit into one transactions + // so split it up + let instructions_chunks = instructions.chunks(instructions.len() / 2); + info!("starting chunked crank instruction processing"); + for (_idx, chunk) in instructions_chunks.enumerate() { + let res = run_loop(&chunk.to_vec()); + if res.is_err() { + error!( + "failed to send chunked crank instruction {:#?}", + res.err() + ); + } else { + info!("processed chunked instruction {}", res.unwrap(),); + } + } + info!("finished chunked crank instruction processing") + } else { + let res = run_loop(&instructions); + if res.is_err() { + error!("failed to send crank instructions {:#?}", res.err()); + } else { + info!( + "crank ran {} processed {} instructions for {} markets: {:#?}", + res.unwrap(), + instructions.len(), + instructions_markets.len(), + instructions_markets, + ); + } + } + let slot_number = rpc_client.get_slot(); + match slot_number { + Ok(slot_number) => { + match slot_height_map.try_write() { + Ok(mut height_writer) => { + for market in instructions_markets.iter() { + height_writer.insert(market.to_string(), slot_number); + } + }, + Err(err) => { + error!("failed to claim lock on slot height map {:#?}", err); + } + } + }, + Err(err) => { + error!("failed to retrieve slot number {:#?}", err); + } + } + } + }); + if res.is_err() { + error!("failed to run crossbeam scope {:#?}", res.err()); + } + } + info!("finished crank run"); + std::thread::sleep(std::time::Duration::from_secs(self.config.crank.max_wait_for_events_delay)); + } + } +} + +fn consume_events_ix( + program_id: &Pubkey, + payer: &Keypair, + account_metas: Vec, + to_consume: usize, +) -> Result> { + let instruction_data: Vec = MarketInstruction::ConsumeEvents(to_consume as u16).pack(); + let instruction = Instruction { + program_id: *program_id, + accounts: account_metas, + data: instruction_data, + }; + let random_instruction = solana_sdk::system_instruction::transfer( + &payer.pubkey(), + &payer.pubkey(), + rand::random::() % 10000 + 1, + ); + Ok(vec![instruction, random_instruction]) +} + +#[cfg(target_endian = "little")] +pub fn get_keys_for_market<'a>( + client: &'a RpcClient, + program_id: &'a Pubkey, + market: &'a Pubkey, +) -> Result { + let account_data: Vec = client.get_account_data(&market)?; + let words: Cow<[u64]> = remove_dex_account_padding(&account_data)?; + let market_state: MarketState = { + let account_flags = Market::account_flags(&account_data)?; + if account_flags.intersects(AccountFlag::Permissioned) { + let state = transmute_one_pedantic::(transmute_to_bytes(&words)) + .map_err(|e| e.without_src())?; + state.inner + } else { + transmute_one_pedantic::(transmute_to_bytes(&words)) + .map_err(|e| e.without_src())? + } + }; + market_state.check_flags()?; + let vault_signer_key = + gen_vault_signer_key(market_state.vault_signer_nonce, market, program_id)?; + assert_eq!( + transmute_to_bytes(&identity(market_state.own_address)), + market.as_ref() + ); + Ok(MarketPubkeys { + market: Box::new(*market), + req_q: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( + market_state.req_q, + )))), + event_q: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( + market_state.event_q, + )))), + bids: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( + market_state.bids, + )))), + asks: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( + market_state.asks, + )))), + coin_vault: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( + market_state.coin_vault, + )))), + pc_vault: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( + market_state.pc_vault, + )))), + vault_signer_key: Box::new(vault_signer_key), + }) +} + +#[cfg(target_endian = "little")] +fn remove_dex_account_padding<'a>(data: &'a [u8]) -> Result> { + use serum_dex::state::{ACCOUNT_HEAD_PADDING, ACCOUNT_TAIL_PADDING}; + let head = &data[..ACCOUNT_HEAD_PADDING.len()]; + if data.len() < ACCOUNT_HEAD_PADDING.len() + ACCOUNT_TAIL_PADDING.len() { + return Err(format_err!( + "dex account length {} is too small to contain valid padding", + data.len() + )); + } + if head != ACCOUNT_HEAD_PADDING { + return Err(format_err!("dex account head padding mismatch")); + } + let tail = &data[data.len() - ACCOUNT_TAIL_PADDING.len()..]; + if tail != ACCOUNT_TAIL_PADDING { + return Err(format_err!("dex account tail padding mismatch")); + } + let inner_data_range = ACCOUNT_HEAD_PADDING.len()..(data.len() - ACCOUNT_TAIL_PADDING.len()); + let inner: &'a [u8] = &data[inner_data_range]; + let words: Cow<'a, [u64]> = match transmute_many_pedantic::(inner) { + Ok(word_slice) => Cow::Borrowed(word_slice), + Err(transmute_error) => { + let word_vec = transmute_error.copy().map_err(|e| e.without_src())?; + Cow::Owned(word_vec) + } + }; + Ok(words) +} + +pub fn parse_event_queue(data_words: &[u64]) -> Result<(EventQueueHeader, &[Event], &[Event])> { + let (header_words, event_words) = data_words.split_at(size_of::() >> 3); + let header: EventQueueHeader = + transmute_one_pedantic(transmute_to_bytes(header_words)).map_err(|e| e.without_src())?; + let events: &[Event] = transmute_many::<_, SingleManyGuard>(transmute_to_bytes(event_words)) + .map_err(|e| e.without_src())?; + let (tail_seg, head_seg) = events.split_at(header.head() as usize); + let head_len = head_seg.len().min(header.count() as usize); + let tail_len = header.count() as usize - head_len; + Ok((header, &head_seg[..head_len], &tail_seg[..tail_len])) +} + +fn hash_accounts(val: &[u64; 4]) -> u64 { + val.iter().fold(0, |a, b| b.wrapping_add(a)) +} + +#[derive(Clone, Default, Debug, PartialEq)] +pub struct MarketPubkeys { + pub market: Box, + pub req_q: Box, + pub event_q: Box, + pub bids: Box, + pub asks: Box, + pub coin_vault: Box, + pub pc_vault: Box, + pub vault_signer_key: Box, +} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index ad96a69..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,1514 +0,0 @@ -#![deny(safe_packed_borrows)] -#![allow(dead_code)] - -use std::borrow::Cow; -use std::cmp::{max, min}; -use std::collections::BTreeSet; -use std::convert::identity; -use std::mem::size_of; -use std::num::NonZeroU64; -use std::sync::{Arc, Mutex}; -use std::{thread, time}; - -use anyhow::{format_err, Result}; -use clap::Clap; -use debug_print::debug_println; -use enumflags2::BitFlags; -use log::{error, info}; -use rand::rngs::OsRng; -use safe_transmute::{ - guard::SingleManyGuard, - to_bytes::{transmute_one_to_bytes, transmute_to_bytes}, - transmute_many, transmute_many_pedantic, transmute_one_pedantic, -}; -use sloggers::file::FileLoggerBuilder; -use sloggers::types::Severity; -use sloggers::Build; -use solana_client::rpc_client::RpcClient; -use solana_client::rpc_config::RpcSendTransactionConfig; -use solana_sdk::commitment_config::CommitmentConfig; -use solana_sdk::instruction::{AccountMeta, Instruction}; -use solana_sdk::program_pack::Pack; -use solana_sdk::pubkey::Pubkey; -use solana_sdk::signature::Signature; -use solana_sdk::signature::{Keypair, Signer}; -use solana_sdk::transaction::Transaction; -use spl_token::instruction as token_instruction; -use warp::Filter; - -use serum_common::client::rpc::{ - create_and_init_mint, create_token_account, mint_to_new_account, send_txn, simulate_transaction, -}; -use serum_common::client::Cluster; -use serum_dex::instruction::{ - cancel_order_by_client_order_id as cancel_order_by_client_order_id_ix, - close_open_orders as close_open_orders_ix, init_open_orders as init_open_orders_ix, - MarketInstruction, NewOrderInstructionV3, SelfTradeBehavior, -}; -use serum_dex::matching::{OrderType, Side}; -use serum_dex::state::gen_vault_signer_key; -use serum_dex::state::Event; -use serum_dex::state::EventQueueHeader; -use serum_dex::state::QueueHeader; -use serum_dex::state::Request; -use serum_dex::state::RequestQueueHeader; -use serum_dex::state::{AccountFlag, Market, MarketState, MarketStateV2}; - -pub fn with_logging(_to: &str, fnc: F) { - fnc(); -} - -fn read_keypair_file(s: &str) -> Result { - solana_sdk::signature::read_keypair_file(s) - .map_err(|_| format_err!("failed to read keypair from {}", s)) -} - -#[derive(Clap, Debug)] -pub struct Opts { - #[clap(short, long, default_value = "https://solana-api.projectserum.com")] - pub url: String, - #[clap(subcommand)] - pub command: Command, -} - -impl Opts { - fn client(&self) -> RpcClient { - RpcClient::new(self.url.to_string()) - } -} - -#[derive(Clap, Debug)] -pub enum Command { - Genesis { - #[clap(long, short)] - payer: String, - - #[clap(long, short)] - mint: String, - - #[clap(long, short)] - owner_pubkey: Pubkey, - - #[clap(long, short)] - decimals: u8, - }, - Mint { - #[clap(long, short)] - payer: String, - - #[clap(long, short)] - signer: String, - - #[clap(long, short)] - mint_pubkey: Pubkey, - - #[clap(long, short)] - recipient: Option, - - #[clap(long, short)] - quantity: u64, - }, - CreateAccount { - mint_pubkey: Pubkey, - owner_pubkey: Pubkey, - payer: String, - }, - ConsumeEvents { - #[clap(long, short)] - dex_program_id: Pubkey, - #[clap(long)] - payer: String, - #[clap(long, short)] - market: Pubkey, - #[clap(long, short)] - coin_wallet: Pubkey, - #[clap(long, short)] - pc_wallet: Pubkey, - #[clap(long, short)] - num_workers: usize, - #[clap(long, short)] - events_per_worker: usize, - #[clap(long)] - num_accounts: Option, - #[clap(long)] - log_directory: String, - #[clap(long)] - max_q_length: Option, - #[clap(long)] - max_wait_for_events_delay: Option, - }, - MatchOrders { - #[clap(long, short)] - dex_program_id: Pubkey, - - #[clap(long)] - payer: String, - - #[clap(long, short)] - market: Pubkey, - - #[clap(long, short)] - coin_wallet: Pubkey, - - #[clap(long, short)] - pc_wallet: Pubkey, - }, - MonitorQueue { - #[clap(long, short)] - dex_program_id: Pubkey, - - #[clap(long, short)] - market: Pubkey, - - #[clap(long)] - port: u16, - }, - PrintEventQueue { - dex_program_id: Pubkey, - market: Pubkey, - }, - WholeShebang { - payer: String, - dex_program_id: Pubkey, - }, - SettleFunds { - payer: String, - dex_program_id: Pubkey, - market: Pubkey, - orders: Pubkey, - coin_wallet: Pubkey, - pc_wallet: Pubkey, - #[clap(long, short)] - signer: Option, - }, - ListMarket { - payer: String, - dex_program_id: Pubkey, - #[clap(long, short)] - coin_mint: Pubkey, - #[clap(long, short)] - pc_mint: Pubkey, - #[clap(long)] - coin_lot_size: Option, - #[clap(long)] - pc_lot_size: Option, - }, - InitializeTokenAccount { - mint: Pubkey, - owner_account: String, - }, -} - -pub fn start(opts: Opts) -> Result<()> { - let client = opts.client(); - - match opts.command { - Command::Genesis { - payer, - mint, - owner_pubkey, - decimals, - } => { - let payer = read_keypair_file(&payer)?; - let mint = read_keypair_file(&mint)?; - create_and_init_mint(&client, &payer, &mint, &owner_pubkey, decimals)?; - } - Command::Mint { - payer, - signer, - mint_pubkey, - recipient, - quantity, - } => { - let payer = read_keypair_file(&payer)?; - let minter = read_keypair_file(&signer)?; - match recipient.as_ref() { - Some(recipient) => { - mint_to_existing_account( - &client, - &payer, - &minter, - &mint_pubkey, - recipient, - quantity, - )?; - } - None => { - mint_to_new_account(&client, &payer, &minter, &mint_pubkey, quantity)?; - } - }; - } - Command::CreateAccount { .. } => unimplemented!(), - Command::MatchOrders { - ref dex_program_id, - ref payer, - ref market, - ref coin_wallet, - ref pc_wallet, - } => { - let payer = read_keypair_file(&payer)?; - - debug_println!("Getting market keys ..."); - let market_keys = get_keys_for_market(&client, dex_program_id, &market)?; - debug_println!("{:#?}", market_keys); - match_orders( - &client, - dex_program_id, - &payer, - &market_keys, - coin_wallet, - pc_wallet, - )?; - } - Command::ConsumeEvents { - ref dex_program_id, - ref payer, - ref market, - ref coin_wallet, - ref pc_wallet, - num_workers, - events_per_worker, - ref num_accounts, - ref log_directory, - ref max_q_length, - ref max_wait_for_events_delay, - } => { - init_logger(log_directory); - consume_events_loop( - &opts, - &dex_program_id, - &payer, - &market, - &coin_wallet, - &pc_wallet, - num_workers, - events_per_worker, - num_accounts.unwrap_or(32), - max_q_length.unwrap_or(1), - max_wait_for_events_delay.unwrap_or(60), - )?; - } - Command::MonitorQueue { - dex_program_id, - market, - port, - } => { - let client = opts.client(); - let mut runtime = tokio::runtime::Builder::new() - .basic_scheduler() - .build() - .unwrap(); - runtime - .block_on(read_queue_length_loop(client, dex_program_id, market, port)) - .unwrap(); - } - Command::PrintEventQueue { - ref dex_program_id, - ref market, - } => { - let market_keys = get_keys_for_market(&client, dex_program_id, &market)?; - let event_q_data = client.get_account_data(&market_keys.event_q)?; - let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?; - let (header, events_seg0, events_seg1) = parse_event_queue(&inner)?; - debug_println!("Header:\n{:#x?}", header); - debug_println!("Seg0:\n{:#x?}", events_seg0); - debug_println!("Seg1:\n{:#x?}", events_seg1); - } - Command::WholeShebang { - ref dex_program_id, - ref payer, - } => { - let payer = read_keypair_file(payer)?; - whole_shebang(&client, dex_program_id, &payer)?; - } - Command::SettleFunds { - ref payer, - ref dex_program_id, - ref market, - ref orders, - ref coin_wallet, - ref pc_wallet, - ref signer, - } => { - let payer = read_keypair_file(payer)?; - let signer = signer.as_ref().map(|s| read_keypair_file(&s)).transpose()?; - let market_keys = get_keys_for_market(&client, dex_program_id, &market)?; - settle_funds( - &client, - dex_program_id, - &payer, - &market_keys, - signer.as_ref(), - orders, - coin_wallet, - pc_wallet, - )?; - } - Command::ListMarket { - ref payer, - ref dex_program_id, - ref coin_mint, - ref pc_mint, - coin_lot_size, - pc_lot_size, - } => { - let payer = read_keypair_file(payer)?; - let market_keys = list_market( - &client, - dex_program_id, - &payer, - coin_mint, - pc_mint, - coin_lot_size.unwrap_or(1_000_000), - pc_lot_size.unwrap_or(10_000), - )?; - println!("Listed market: {:#?}", market_keys); - } - Command::InitializeTokenAccount { - ref mint, - ref owner_account, - } => { - let owner = read_keypair_file(owner_account)?; - let initialized_account = initialize_token_account(&client, mint, &owner)?; - debug_println!("Initialized account: {}", initialized_account.pubkey()); - } - } - Ok(()) -} - -#[derive(Debug)] -pub struct MarketPubkeys { - pub market: Box, - pub req_q: Box, - pub event_q: Box, - pub bids: Box, - pub asks: Box, - pub coin_vault: Box, - pub pc_vault: Box, - pub vault_signer_key: Box, -} - -#[cfg(target_endian = "little")] -fn remove_dex_account_padding<'a>(data: &'a [u8]) -> Result> { - use serum_dex::state::{ACCOUNT_HEAD_PADDING, ACCOUNT_TAIL_PADDING}; - let head = &data[..ACCOUNT_HEAD_PADDING.len()]; - if data.len() < ACCOUNT_HEAD_PADDING.len() + ACCOUNT_TAIL_PADDING.len() { - return Err(format_err!( - "dex account length {} is too small to contain valid padding", - data.len() - )); - } - if head != ACCOUNT_HEAD_PADDING { - return Err(format_err!("dex account head padding mismatch")); - } - let tail = &data[data.len() - ACCOUNT_TAIL_PADDING.len()..]; - if tail != ACCOUNT_TAIL_PADDING { - return Err(format_err!("dex account tail padding mismatch")); - } - let inner_data_range = ACCOUNT_HEAD_PADDING.len()..(data.len() - ACCOUNT_TAIL_PADDING.len()); - let inner: &'a [u8] = &data[inner_data_range]; - let words: Cow<'a, [u64]> = match transmute_many_pedantic::(inner) { - Ok(word_slice) => Cow::Borrowed(word_slice), - Err(transmute_error) => { - let word_vec = transmute_error.copy().map_err(|e| e.without_src())?; - Cow::Owned(word_vec) - } - }; - Ok(words) -} - -#[cfg(target_endian = "little")] -fn get_keys_for_market<'a>( - client: &'a RpcClient, - program_id: &'a Pubkey, - market: &'a Pubkey, -) -> Result { - let account_data: Vec = client.get_account_data(&market)?; - let words: Cow<[u64]> = remove_dex_account_padding(&account_data)?; - let market_state: MarketState = { - let account_flags = Market::account_flags(&account_data)?; - if account_flags.intersects(AccountFlag::Permissioned) { - let state = transmute_one_pedantic::(transmute_to_bytes(&words)) - .map_err(|e| e.without_src())?; - state.inner - } else { - transmute_one_pedantic::(transmute_to_bytes(&words)) - .map_err(|e| e.without_src())? - } - }; - market_state.check_flags()?; - let vault_signer_key = - gen_vault_signer_key(market_state.vault_signer_nonce, market, program_id)?; - assert_eq!( - transmute_to_bytes(&identity(market_state.own_address)), - market.as_ref() - ); - Ok(MarketPubkeys { - market: Box::new(*market), - req_q: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( - market_state.req_q, - )))), - event_q: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( - market_state.event_q, - )))), - bids: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( - market_state.bids, - )))), - asks: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( - market_state.asks, - )))), - coin_vault: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( - market_state.coin_vault, - )))), - pc_vault: Box::new(Pubkey::new(transmute_one_to_bytes(&identity( - market_state.pc_vault, - )))), - vault_signer_key: Box::new(vault_signer_key), - }) -} - -fn parse_event_queue(data_words: &[u64]) -> Result<(EventQueueHeader, &[Event], &[Event])> { - let (header_words, event_words) = data_words.split_at(size_of::() >> 3); - let header: EventQueueHeader = - transmute_one_pedantic(transmute_to_bytes(header_words)).map_err(|e| e.without_src())?; - let events: &[Event] = transmute_many::<_, SingleManyGuard>(transmute_to_bytes(event_words)) - .map_err(|e| e.without_src())?; - let (tail_seg, head_seg) = events.split_at(header.head() as usize); - let head_len = head_seg.len().min(header.count() as usize); - let tail_len = header.count() as usize - head_len; - Ok((header, &head_seg[..head_len], &tail_seg[..tail_len])) -} - -fn parse_req_queue(data_words: &[u64]) -> Result<(RequestQueueHeader, &[Request], &[Request])> { - let (header_words, request_words) = data_words.split_at(size_of::() >> 3); - let header: RequestQueueHeader = - transmute_one_pedantic(transmute_to_bytes(header_words)).map_err(|e| e.without_src())?; - let request: &[Request] = - transmute_many::<_, SingleManyGuard>(transmute_to_bytes(request_words)) - .map_err(|e| e.without_src())?; - let (tail_seg, head_seg) = request.split_at(header.head() as usize); - let head_len = head_seg.len().min(header.count() as usize); - let tail_len = header.count() as usize - head_len; - Ok((header, &head_seg[..head_len], &tail_seg[..tail_len])) -} - -fn hash_accounts(val: &[u64; 4]) -> u64 { - val.iter().fold(0, |a, b| b.wrapping_add(a)) -} - -fn init_logger(log_directory: &str) { - let path = std::path::Path::new(log_directory); - let parent = path.parent().unwrap(); - std::fs::create_dir_all(parent).unwrap(); - let mut builder = FileLoggerBuilder::new(log_directory); - builder.level(Severity::Info).rotate_size(8 * 1024 * 1024); - let log = builder.build().unwrap(); - let _guard = slog_scope::set_global_logger(log); - _guard.cancel_reset(); - slog_stdlog::init().unwrap(); -} - -fn consume_events_loop( - opts: &Opts, - program_id: &Pubkey, - payer_path: &String, - market: &Pubkey, - coin_wallet: &Pubkey, - pc_wallet: &Pubkey, - num_workers: usize, - events_per_worker: usize, - num_accounts: usize, - max_q_length: u64, - max_wait_for_events_delay: u64, -) -> Result<()> { - info!("Getting market keys ..."); - let client = opts.client(); - let market_keys = get_keys_for_market(&client, &program_id, &market)?; - info!("{:#?}", market_keys); - let pool = threadpool::ThreadPool::new(num_workers); - let max_slot_height_mutex = Arc::new(Mutex::new(0_u64)); - let mut last_cranked_at = std::time::Instant::now() - .checked_sub(std::time::Duration::from_secs(max_wait_for_events_delay)) - .unwrap_or(std::time::Instant::now()); - - loop { - thread::sleep(time::Duration::from_millis(1000)); - - let loop_start = std::time::Instant::now(); - let start_time = std::time::Instant::now(); - let event_q_value_and_context = - client.get_account_with_commitment(&market_keys.event_q, CommitmentConfig::recent())?; - let event_q_slot = event_q_value_and_context.context.slot; - let max_slot_height = max_slot_height_mutex.lock().unwrap(); - if event_q_slot <= *max_slot_height { - info!( - "Skipping crank. Already cranked for slot. Event queue slot: {}, Max seen slot: {}", - event_q_slot, max_slot_height - ); - continue; - } - drop(max_slot_height); - let event_q_data = event_q_value_and_context - .value - .ok_or(format_err!("Failed to retrieve account"))? - .data; - let req_q_data = client - .get_account_with_commitment(&market_keys.req_q, CommitmentConfig::recent())? - .value - .ok_or(format_err!("Failed to retrieve account"))? - .data; - let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?; - let (_header, seg0, seg1) = parse_event_queue(&inner)?; - let req_inner: Cow<[u64]> = remove_dex_account_padding(&req_q_data)?; - let (_req_header, req_seg0, req_seg1) = parse_event_queue(&req_inner)?; - let event_q_len = seg0.len() + seg1.len(); - let req_q_len = req_seg0.len() + req_seg1.len(); - info!( - "Size of request queue is {}, market {}, coin {}, pc {}", - req_q_len, market, coin_wallet, pc_wallet - ); - - if event_q_len == 0 { - continue; - } else if std::time::Duration::from_secs(max_wait_for_events_delay) - .gt(&last_cranked_at.elapsed()) - && (event_q_len as u64) < max_q_length - { - info!( - "Skipping crank. Last cranked {} seconds ago and queue only has {} events. \ - Event queue slot: {}", - last_cranked_at.elapsed().as_secs(), - event_q_len, - event_q_slot - ); - continue; - } else { - info!( - "Total event queue length: {}, market {}, coin {}, pc {}", - event_q_len, market, coin_wallet, pc_wallet - ); - let accounts = seg0.iter().chain(seg1.iter()).map(|event| event.owner); - let mut used_accounts = BTreeSet::new(); - for account in accounts { - used_accounts.insert(account); - if used_accounts.len() >= num_accounts { - break; - } - } - let orders_accounts: Vec<_> = used_accounts.into_iter().collect(); - info!( - "Number of unique order accounts: {}, market {}, coin {}, pc {}", - orders_accounts.len(), - market, - coin_wallet, - pc_wallet - ); - info!( - "First 5 accounts: {:?}", - orders_accounts - .iter() - .take(5) - .map(hash_accounts) - .collect::>() - ); - - let mut account_metas = Vec::with_capacity(orders_accounts.len() + 4); - for pubkey_words in orders_accounts { - let pubkey = Pubkey::new(transmute_to_bytes(&pubkey_words)); - account_metas.push(AccountMeta::new(pubkey, false)); - } - for pubkey in [ - &market_keys.market, - &market_keys.event_q, - coin_wallet, - pc_wallet, - ] - .iter() - { - account_metas.push(AccountMeta::new(**pubkey, false)); - } - debug_println!("Number of workers: {}", num_workers); - let end_time = std::time::Instant::now(); - info!( - "Fetching {} events from the queue took {}", - event_q_len, - end_time.duration_since(start_time).as_millis() - ); - for thread_num in 0..min(num_workers, 2 * event_q_len / events_per_worker + 1) { - let payer = read_keypair_file(&payer_path)?; - let program_id = program_id.clone(); - let client = opts.client(); - let account_metas = account_metas.clone(); - let event_q = *market_keys.event_q; - let max_slot_height_mutex_clone = Arc::clone(&max_slot_height_mutex); - pool.execute(move || { - consume_events_wrapper( - &client, - &program_id, - &payer, - account_metas, - thread_num, - events_per_worker, - event_q, - max_slot_height_mutex_clone, - event_q_slot, - ) - }); - } - pool.join(); - last_cranked_at = std::time::Instant::now(); - info!( - "Total loop time took {}", - last_cranked_at.duration_since(loop_start).as_millis() - ); - } - } -} - -fn consume_events_wrapper( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - account_metas: Vec, - thread_num: usize, - to_consume: usize, - event_q: Pubkey, - max_slot_height_mutex: Arc>, - slot: u64, -) { - let start = std::time::Instant::now(); - let result = consume_events_once( - &client, - program_id, - &payer, - account_metas, - to_consume, - thread_num, - event_q, - ); - match result { - Ok(signature) => { - info!( - "[thread {}] Successfully consumed events after {:?}: {}.", - thread_num, - start.elapsed(), - signature - ); - let mut max_slot_height = max_slot_height_mutex.lock().unwrap(); - *max_slot_height = max(slot, *max_slot_height); - } - Err(err) => { - error!("[thread {}] Received error: {:?}", thread_num, err); - } - }; -} - -fn consume_events_once( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - account_metas: Vec, - to_consume: usize, - _thread_number: usize, - event_q: Pubkey, -) -> Result { - let _start = std::time::Instant::now(); - let instruction_data: Vec = MarketInstruction::ConsumeEvents(to_consume as u16).pack(); - let instruction = Instruction { - program_id: *program_id, - accounts: account_metas, - data: instruction_data, - }; - let random_instruction = solana_sdk::system_instruction::transfer( - &payer.pubkey(), - &payer.pubkey(), - rand::random::() % 10000 + 1, - ); - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &[instruction, random_instruction], - Some(&payer.pubkey()), - &[payer], - recent_hash, - ); - - info!("Consuming events ..."); - let signature = client.send_transaction_with_config( - &txn, - RpcSendTransactionConfig { - skip_preflight: true, - ..RpcSendTransactionConfig::default() - }, - )?; - Ok(signature) -} - -#[cfg(target_endian = "little")] -fn consume_events( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - state: &MarketPubkeys, - coin_wallet: &Pubkey, - pc_wallet: &Pubkey, -) -> Result<()> { - let instruction = { - let i = consume_events_instruction(client, program_id, state, coin_wallet, pc_wallet)?; - match i { - None => return Ok(()), - Some(i) => i, - } - }; - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - info!("Consuming events ..."); - let txn = Transaction::new_signed_with_payer( - std::slice::from_ref(&instruction), - Some(&payer.pubkey()), - &[payer], - recent_hash, - ); - info!("Consuming events ..."); - send_txn(client, &txn, false)?; - Ok(()) -} - -pub fn consume_events_instruction( - client: &RpcClient, - program_id: &Pubkey, - state: &MarketPubkeys, - coin_wallet: &Pubkey, - pc_wallet: &Pubkey, -) -> Result> { - let event_q_data = client.get_account_data(&state.event_q)?; - let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data)?; - let (_header, seg0, seg1) = parse_event_queue(&inner)?; - - if seg0.len() + seg1.len() == 0 { - info!("Total event queue length: 0, returning early"); - return Ok(None); - } else { - info!("Total event queue length: {}", seg0.len() + seg1.len()); - } - let accounts = seg0.iter().chain(seg1.iter()).map(|event| event.owner); - let mut orders_accounts: Vec<_> = accounts.collect(); - orders_accounts.sort_unstable(); - orders_accounts.dedup(); - // todo: Shuffle the accounts before truncating, to avoid favoring low sort order accounts - orders_accounts.truncate(32); - info!("Number of unique order accounts: {}", orders_accounts.len()); - - let mut account_metas = Vec::with_capacity(orders_accounts.len() + 4); - for pubkey_words in orders_accounts { - let pubkey = Pubkey::new(transmute_to_bytes(&pubkey_words)); - account_metas.push(AccountMeta::new(pubkey, false)); - } - for pubkey in [&state.market, &state.event_q, coin_wallet, pc_wallet].iter() { - account_metas.push(AccountMeta::new(**pubkey, false)); - } - - let instruction_data: Vec = - MarketInstruction::ConsumeEvents(account_metas.len() as u16).pack(); - - let instruction = Instruction { - program_id: *program_id, - accounts: account_metas, - data: instruction_data, - }; - - Ok(Some(instruction)) -} - -fn whole_shebang(client: &RpcClient, program_id: &Pubkey, payer: &Keypair) -> Result<()> { - let coin_mint = Keypair::generate(&mut OsRng); - debug_println!("Coin mint: {}", coin_mint.pubkey()); - create_and_init_mint(client, payer, &coin_mint, &payer.pubkey(), 3)?; - - let pc_mint = Keypair::generate(&mut OsRng); - debug_println!("Pc mint: {}", pc_mint.pubkey()); - create_and_init_mint(client, payer, &pc_mint, &payer.pubkey(), 3)?; - - let market_keys = list_market( - client, - program_id, - payer, - &coin_mint.pubkey(), - &pc_mint.pubkey(), - 1_000_000, - 10_000, - )?; - debug_println!("Market keys: {:#?}", market_keys); - - debug_println!("Minting coin..."); - let coin_wallet = mint_to_new_account( - client, - payer, - payer, - &coin_mint.pubkey(), - 1_000_000_000_000_000, - )?; - debug_println!("Minted {}", coin_wallet.pubkey()); - - debug_println!("Minting price currency..."); - let pc_wallet = mint_to_new_account( - client, - payer, - payer, - &pc_mint.pubkey(), - 1_000_000_000_000_000, - )?; - debug_println!("Minted {}", pc_wallet.pubkey()); - - let mut orders = None; - - debug_println!("Initializing open orders"); - init_open_orders(client, program_id, payer, &market_keys, &mut orders)?; - - debug_println!("Placing bid..."); - place_order( - client, - program_id, - payer, - &pc_wallet.pubkey(), - &market_keys, - &mut orders, - NewOrderInstructionV3 { - side: Side::Bid, - limit_price: NonZeroU64::new(500).unwrap(), - max_coin_qty: NonZeroU64::new(1_000).unwrap(), - max_native_pc_qty_including_fees: NonZeroU64::new(500_000).unwrap(), - order_type: OrderType::Limit, - client_order_id: 019269, - self_trade_behavior: SelfTradeBehavior::DecrementTake, - limit: std::u16::MAX, - }, - )?; - - debug_println!("Bid account: {}", orders.unwrap()); - - debug_println!("Placing offer..."); - let mut orders = None; - place_order( - client, - program_id, - payer, - &coin_wallet.pubkey(), - &market_keys, - &mut orders, - NewOrderInstructionV3 { - side: Side::Ask, - limit_price: NonZeroU64::new(499).unwrap(), - max_coin_qty: NonZeroU64::new(1_000).unwrap(), - max_native_pc_qty_including_fees: NonZeroU64::new(std::u64::MAX).unwrap(), - order_type: OrderType::Limit, - limit: std::u16::MAX, - self_trade_behavior: SelfTradeBehavior::DecrementTake, - client_order_id: 985982, - }, - )?; - - // Cancel the open order so that we can close it later. - cancel_order_by_client_order_id( - client, - program_id, - payer, - &market_keys, - &orders.unwrap(), - 985982, - )?; - - debug_println!("Ask account: {}", orders.unwrap()); - - debug_println!("Consuming events in 15s ..."); - std::thread::sleep(std::time::Duration::new(15, 0)); - consume_events( - client, - program_id, - payer, - &market_keys, - &coin_wallet.pubkey(), - &pc_wallet.pubkey(), - )?; - settle_funds( - client, - program_id, - payer, - &market_keys, - Some(payer), - &orders.unwrap(), - &coin_wallet.pubkey(), - &pc_wallet.pubkey(), - )?; - close_open_orders( - client, - program_id, - payer, - &market_keys, - orders.as_ref().unwrap(), - )?; - Ok(()) -} - -pub fn cancel_order_by_client_order_id( - client: &RpcClient, - program_id: &Pubkey, - owner: &Keypair, - state: &MarketPubkeys, - orders: &Pubkey, - client_order_id: u64, -) -> Result<()> { - let ixs = &[cancel_order_by_client_order_id_ix( - program_id, - &state.market, - &state.bids, - &state.asks, - orders, - &owner.pubkey(), - &state.event_q, - client_order_id, - )?]; - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer(ixs, Some(&owner.pubkey()), &[owner], recent_hash); - - debug_println!("Canceling order by client order id instruction ..."); - let result = simulate_transaction(client, &txn, true, CommitmentConfig::confirmed())?; - if let Some(e) = result.value.err { - debug_println!("{:#?}", result.value.logs); - return Err(format_err!("simulate_transaction error: {:?}", e)); - } - - send_txn(client, &txn, false)?; - Ok(()) -} - -pub fn close_open_orders( - client: &RpcClient, - program_id: &Pubkey, - owner: &Keypair, - state: &MarketPubkeys, - orders: &Pubkey, -) -> Result<()> { - debug_println!("Closing open orders..."); - let ixs = &[close_open_orders_ix( - program_id, - orders, - &owner.pubkey(), - &owner.pubkey(), - &state.market, - )?]; - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer(ixs, Some(&owner.pubkey()), &[owner], recent_hash); - - debug_println!("Simulating close open orders instruction ..."); - let result = simulate_transaction(client, &txn, true, CommitmentConfig::confirmed())?; - if let Some(e) = result.value.err { - debug_println!("{:#?}", result.value.logs); - return Err(format_err!("simulate_transaction error: {:?}", e)); - } - - send_txn(client, &txn, false)?; - Ok(()) -} - -pub fn init_open_orders( - client: &RpcClient, - program_id: &Pubkey, - owner: &Keypair, - state: &MarketPubkeys, - orders: &mut Option, -) -> Result<()> { - let mut instructions = Vec::new(); - let orders_keypair; - let mut signers = Vec::new(); - let orders_pubkey = match *orders { - Some(pk) => pk, - None => { - let (orders_key, instruction) = create_dex_account( - client, - program_id, - &owner.pubkey(), - size_of::(), - )?; - orders_keypair = orders_key; - signers.push(&orders_keypair); - instructions.push(instruction); - orders_keypair.pubkey() - } - }; - *orders = Some(orders_pubkey); - instructions.push(init_open_orders_ix( - program_id, - &orders_pubkey, - &owner.pubkey(), - &state.market, - None, - )?); - signers.push(owner); - - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&owner.pubkey()), - &signers, - recent_hash, - ); - send_txn(client, &txn, false)?; - Ok(()) -} - -pub fn place_order( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - wallet: &Pubkey, - state: &MarketPubkeys, - orders: &mut Option, - - new_order: NewOrderInstructionV3, -) -> Result<()> { - let mut instructions = Vec::new(); - let orders_keypair; - let mut signers = Vec::new(); - let orders_pubkey = match *orders { - Some(pk) => pk, - None => { - let (orders_key, instruction) = create_dex_account( - client, - program_id, - &payer.pubkey(), - size_of::(), - )?; - orders_keypair = orders_key; - signers.push(&orders_keypair); - instructions.push(instruction); - orders_keypair.pubkey() - } - }; - *orders = Some(orders_pubkey); - let _side = new_order.side; - let data = MarketInstruction::NewOrderV3(new_order).pack(); - let instruction = Instruction { - program_id: *program_id, - data, - accounts: vec![ - AccountMeta::new(*state.market, false), - AccountMeta::new(orders_pubkey, false), - AccountMeta::new(*state.req_q, false), - AccountMeta::new(*state.event_q, false), - AccountMeta::new(*state.bids, false), - AccountMeta::new(*state.asks, false), - AccountMeta::new(*wallet, false), - AccountMeta::new_readonly(payer.pubkey(), true), - AccountMeta::new(*state.coin_vault, false), - AccountMeta::new(*state.pc_vault, false), - AccountMeta::new_readonly(spl_token::ID, false), - AccountMeta::new_readonly(solana_sdk::sysvar::rent::ID, false), - ], - }; - instructions.push(instruction); - signers.push(payer); - - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - send_txn(client, &txn, false)?; - Ok(()) -} - -fn settle_funds( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - state: &MarketPubkeys, - signer: Option<&Keypair>, - orders: &Pubkey, - coin_wallet: &Pubkey, - pc_wallet: &Pubkey, -) -> Result<()> { - let data = MarketInstruction::SettleFunds.pack(); - let instruction = Instruction { - program_id: *program_id, - data, - accounts: vec![ - AccountMeta::new(*state.market, false), - AccountMeta::new(*orders, false), - AccountMeta::new_readonly(signer.unwrap_or(payer).pubkey(), true), - AccountMeta::new(*state.coin_vault, false), - AccountMeta::new(*state.pc_vault, false), - AccountMeta::new(*coin_wallet, false), - AccountMeta::new(*pc_wallet, false), - AccountMeta::new_readonly(*state.vault_signer_key, false), - AccountMeta::new_readonly(spl_token::ID, false), - ], - }; - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let mut signers = vec![payer]; - if let Some(s) = signer { - signers.push(s); - } - let txn = Transaction::new_signed_with_payer( - &[instruction], - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - let mut i = 0; - loop { - i += 1; - assert!(i < 10); - debug_println!("Simulating SettleFunds instruction ..."); - let result = simulate_transaction(client, &txn, true, CommitmentConfig::single())?; - if let Some(e) = result.value.err { - return Err(format_err!("simulate_transaction error: {:?}", e)); - } - debug_println!("{:#?}", result.value); - if result.value.err.is_none() { - break; - } - } - debug_println!("Settling ..."); - send_txn(client, &txn, false)?; - Ok(()) -} - -pub fn list_market( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - coin_mint: &Pubkey, - pc_mint: &Pubkey, - coin_lot_size: u64, - pc_lot_size: u64, -) -> Result { - let (listing_keys, mut instructions) = - gen_listing_params(client, program_id, &payer.pubkey(), coin_mint, pc_mint)?; - let ListingKeys { - market_key, - req_q_key, - event_q_key, - bids_key, - asks_key, - vault_signer_pk, - vault_signer_nonce, - } = listing_keys; - - debug_println!("Creating coin vault..."); - let coin_vault = create_token_account(client, coin_mint, &vault_signer_pk, payer)?; - debug_println!("Created account: {} ...", coin_vault.pubkey()); - - debug_println!("Creating pc vault..."); - let pc_vault = create_token_account(client, pc_mint, &listing_keys.vault_signer_pk, payer)?; - debug_println!("Created account: {} ...", pc_vault.pubkey()); - - let init_market_instruction = serum_dex::instruction::initialize_market( - &market_key.pubkey(), - program_id, - coin_mint, - pc_mint, - &coin_vault.pubkey(), - &pc_vault.pubkey(), - None, - None, - &bids_key.pubkey(), - &asks_key.pubkey(), - &req_q_key.pubkey(), - &event_q_key.pubkey(), - coin_lot_size, - pc_lot_size, - vault_signer_nonce, - 100, - )?; - debug_println!( - "initialize_market_instruction: {:#?}", - &init_market_instruction - ); - - instructions.push(init_market_instruction); - - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let signers = vec![ - payer, - &market_key, - &req_q_key, - &event_q_key, - &bids_key, - &asks_key, - &req_q_key, - &event_q_key, - ]; - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - - debug_println!("txn:\n{:#x?}", txn); - let result = simulate_transaction(client, &txn, true, CommitmentConfig::single())?; - if let Some(e) = result.value.err { - return Err(format_err!("simulate_transaction error: {:?}", e)); - } - debug_println!("{:#?}", result.value); - debug_println!("Listing {} ...", market_key.pubkey()); - send_txn(client, &txn, false)?; - - Ok(MarketPubkeys { - market: Box::new(market_key.pubkey()), - req_q: Box::new(req_q_key.pubkey()), - event_q: Box::new(event_q_key.pubkey()), - bids: Box::new(bids_key.pubkey()), - asks: Box::new(asks_key.pubkey()), - coin_vault: Box::new(coin_vault.pubkey()), - pc_vault: Box::new(pc_vault.pubkey()), - vault_signer_key: Box::new(vault_signer_pk), - }) -} - -struct ListingKeys { - market_key: Keypair, - req_q_key: Keypair, - event_q_key: Keypair, - bids_key: Keypair, - asks_key: Keypair, - vault_signer_pk: Pubkey, - vault_signer_nonce: u64, -} - -fn gen_listing_params( - client: &RpcClient, - program_id: &Pubkey, - payer: &Pubkey, - _coin_mint: &Pubkey, - _pc_mint: &Pubkey, -) -> Result<(ListingKeys, Vec)> { - let (market_key, create_market) = create_dex_account(client, program_id, payer, 376)?; - let (req_q_key, create_req_q) = create_dex_account(client, program_id, payer, 640)?; - let (event_q_key, create_event_q) = create_dex_account(client, program_id, payer, 1 << 20)?; - let (bids_key, create_bids) = create_dex_account(client, program_id, payer, 1 << 16)?; - let (asks_key, create_asks) = create_dex_account(client, program_id, payer, 1 << 16)?; - let (vault_signer_nonce, vault_signer_pk) = { - let mut i = 0; - loop { - assert!(i < 100); - if let Ok(pk) = gen_vault_signer_key(i, &market_key.pubkey(), program_id) { - break (i, pk); - } - i += 1; - } - }; - let info = ListingKeys { - market_key, - req_q_key, - event_q_key, - bids_key, - asks_key, - vault_signer_pk, - vault_signer_nonce, - }; - let instructions = vec![ - create_market, - create_req_q, - create_event_q, - create_bids, - create_asks, - ]; - Ok((info, instructions)) -} - -fn create_dex_account( - client: &RpcClient, - program_id: &Pubkey, - payer: &Pubkey, - unpadded_len: usize, -) -> Result<(Keypair, Instruction)> { - let len = unpadded_len + 12; - let key = Keypair::generate(&mut OsRng); - let create_account_instr = solana_sdk::system_instruction::create_account( - payer, - &key.pubkey(), - client.get_minimum_balance_for_rent_exemption(len)?, - len as u64, - program_id, - ); - Ok((key, create_account_instr)) -} - -pub fn match_orders( - client: &RpcClient, - program_id: &Pubkey, - payer: &Keypair, - state: &MarketPubkeys, - coin_wallet: &Pubkey, - pc_wallet: &Pubkey, -) -> Result<()> { - let instruction_data: Vec = MarketInstruction::MatchOrders(2).pack(); - - let instruction = Instruction { - program_id: *program_id, - accounts: vec![ - AccountMeta::new(*state.market, false), - AccountMeta::new(*state.req_q, false), - AccountMeta::new(*state.event_q, false), - AccountMeta::new(*state.bids, false), - AccountMeta::new(*state.asks, false), - AccountMeta::new(*coin_wallet, false), - AccountMeta::new(*pc_wallet, false), - ], - data: instruction_data, - }; - - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer( - std::slice::from_ref(&instruction), - Some(&payer.pubkey()), - &[payer], - recent_hash, - ); - - debug_println!("Simulating order matching ..."); - let result = simulate_transaction(&client, &txn, true, CommitmentConfig::single())?; - if let Some(e) = result.value.err { - return Err(format_err!("simulate_transaction error: {:?}", e)); - } - debug_println!("{:#?}", result.value); - if result.value.err.is_none() { - debug_println!("Matching orders ..."); - send_txn(client, &txn, false)?; - } - Ok(()) -} - -fn create_account( - client: &RpcClient, - mint_pubkey: &Pubkey, - owner_pubkey: &Pubkey, - payer: &Keypair, -) -> Result { - let spl_account = Keypair::generate(&mut OsRng); - let signers = vec![payer, &spl_account]; - - let lamports = client.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)?; - - let create_account_instr = solana_sdk::system_instruction::create_account( - &payer.pubkey(), - &spl_account.pubkey(), - lamports, - spl_token::state::Account::LEN as u64, - &spl_token::ID, - ); - - let init_account_instr = token_instruction::initialize_account( - &spl_token::ID, - &spl_account.pubkey(), - &mint_pubkey, - &owner_pubkey, - )?; - - let instructions = vec![create_account_instr, init_account_instr]; - - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - - debug_println!("Creating account: {} ...", spl_account.pubkey()); - send_txn(client, &txn, false)?; - Ok(spl_account) -} - -fn mint_to_existing_account( - client: &RpcClient, - payer: &Keypair, - minting_key: &Keypair, - mint: &Pubkey, - recipient: &Pubkey, - quantity: u64, -) -> Result<()> { - let signers = vec![payer, minting_key]; - - let mint_tokens_instr = token_instruction::mint_to( - &spl_token::ID, - mint, - recipient, - &minting_key.pubkey(), - &[], - quantity, - )?; - - let instructions = vec![mint_tokens_instr]; - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&payer.pubkey()), - &signers, - recent_hash, - ); - send_txn(client, &txn, false)?; - Ok(()) -} - -fn initialize_token_account(client: &RpcClient, mint: &Pubkey, owner: &Keypair) -> Result { - let recip_keypair = Keypair::generate(&mut OsRng); - let lamports = client.get_minimum_balance_for_rent_exemption(spl_token::state::Account::LEN)?; - let create_recip_instr = solana_sdk::system_instruction::create_account( - &owner.pubkey(), - &recip_keypair.pubkey(), - lamports, - spl_token::state::Account::LEN as u64, - &spl_token::ID, - ); - let init_recip_instr = token_instruction::initialize_account( - &spl_token::ID, - &recip_keypair.pubkey(), - mint, - &owner.pubkey(), - )?; - let signers = vec![owner, &recip_keypair]; - let instructions = vec![create_recip_instr, init_recip_instr]; - let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; - let txn = Transaction::new_signed_with_payer( - &instructions, - Some(&owner.pubkey()), - &signers, - recent_hash, - ); - send_txn(client, &txn, false)?; - Ok(recip_keypair) -} - -enum MonitorEvent { - NumEvents(usize), - NewConn(std::net::TcpStream), -} - -async fn read_queue_length_loop( - client: RpcClient, - program_id: Pubkey, - market: Pubkey, - port: u16, -) -> Result<()> { - let client = Arc::new(client); - let get_data = warp::path("length").map(move || { - let client = client.clone(); - let market_keys = get_keys_for_market(&client, &program_id, &market).unwrap(); - let event_q_data = client - .get_account_with_commitment(&market_keys.event_q, CommitmentConfig::recent()) - .unwrap() - .value - .expect("Failed to retrieve account") - .data; - let inner: Cow<[u64]> = remove_dex_account_padding(&event_q_data).unwrap(); - let (_header, seg0, seg1) = parse_event_queue(&inner).unwrap(); - let len = seg0.len() + seg1.len(); - format!("{{ \"length\": {} }}", len) - }); - - Ok(warp::serve(get_data).run(([127, 0, 0, 1], port)).await) -} diff --git a/src/main.rs b/src/main.rs new file mode 100644 index 0000000..9cf3f90 --- /dev/null +++ b/src/main.rs @@ -0,0 +1,99 @@ +#[global_allocator] +static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; + +use std::sync::Arc; +use crossbeam::sync::WaitGroup; +use crossbeam_channel; +use anyhow::{anyhow, Result}; +use clap::{App, Arg, SubCommand}; +use log::{error, info, warn}; +use signal_hook::{ + consts::{SIGINT, SIGQUIT, SIGTERM}, + iterator::Signals, +}; +pub mod config; +pub mod crank; + +#[tokio::main] +async fn main() { + let matches = clap::App::new("serum-crank") + .version("0.0.1") + .author("Solfarm") + .about("a performance optimized serum crank service") + .arg( + Arg::with_name("config") + .short("c") + .long("config") + .value_name("FILE") + .help("sets the config file") + .takes_value(true), + ) + .subcommand( + SubCommand::with_name("config") + .about("configuration management commands") + .subcommands(vec![ + SubCommand::with_name("new").about("generates a new configuration file") + ]), + ) + .subcommand(SubCommand::with_name("run").about("runs the serum crank")) + .get_matches(); + let config_file_path = get_config_or_default(&matches); + let res = process_matches(&matches, config_file_path).await; + if res.is_err() { + error!("failed to process command matches {:#?}", res.err()); + } +} +async fn process_matches<'a>( + matches: &clap::ArgMatches<'a>, + config_file_path: String, +) -> Result<()> { + match matches.subcommand() { + ("config", Some(config)) => match config.subcommand() { + ("new", Some(new_config)) => { + config::Configuration::new(config_file_path.as_str(), false)?; + } + _ => return Err(anyhow!("failed to match subcommand")), + }, + ("run", Some(run_crank)) => { + let cfg = Arc::new(config::Configuration::load( + config_file_path.as_str(), + false, + )?); + cfg.init_log(false)?; + let mut signals = + Signals::new(vec![SIGINT, SIGTERM, SIGQUIT]).expect("failed to registers signals"); + let (s, r) = crossbeam_channel::unbounded(); + let wg = WaitGroup::new(); + { + let wg = wg.clone(); + tokio::task::spawn(async move { + let crank_turner = crank::Crank::new(cfg); + let res = crank_turner.start(r); + if res.is_err() { + error!("encountered error while turning crank {:#?}", res.err()); + } + drop(wg); + }); + } + for signal in signals.forever() { + warn!("encountered exit signal {}", signal); + break; + } + let err = s.send(true); + if err.is_err() { + error!("failed to send exit notif {:#?}", err.err()); + return Err(anyhow!("unexpected error during shutdown, failed to send exit notifications").into()); + } + wg.wait() + } + _ => return Err(anyhow!("failed to match subcommand")), + } + Ok(()) +} +// returns the value of the config file argument or the default +fn get_config_or_default(matches: &clap::ArgMatches) -> String { + matches + .value_of("config") + .unwrap_or("config.yaml") + .to_string() +} diff --git a/src/token_instruction.rs b/src/token_instruction.rs deleted file mode 100644 index 9e566ea..0000000 --- a/src/token_instruction.rs +++ /dev/null @@ -1,614 +0,0 @@ -/* -Copyright 2020 Solana Foundation. - -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. -*/ - -#![allow(unused)] -//! Instruction types - -use solana_sdk::{ - instruction::{AccountMeta, Instruction}, - program_error::ProgramError, - pubkey::Pubkey, -}; -use std::mem::size_of; - -/* CUSTOM ERRORS - 0: invalid instruction - 1: owner required if no initial supply -*/ - -/// Minimum number of multisignature signers (min N) -pub const MIN_SIGNERS: usize = 1; -/// Maximum number of multisignature signers (max N) -pub const MAX_SIGNERS: usize = 11; - -/// Instructions supported by the token program. -#[repr(C)] -#[derive(Clone, Debug, PartialEq)] -pub enum TokenInstruction { - /// Initializes a new mint and optionally deposits all the newly minted tokens in an account. - /// - /// The `InitializeMint` instruction requires no signers and MUST be included within - /// the same Transaction as the system program's `CreateInstruction` that creates the account - /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable]` The mint to initialize. - /// 1. - /// * If supply is non-zero: `[writable]` The account to hold all the newly minted tokens. - /// * If supply is zero: `[]` The owner/multisignature of the mint. - /// 2. `[]` (optional) The owner/multisignature of the mint if supply is non-zero, if - /// present then further minting is supported. - /// - InitializeMint { - /// Initial amount of tokens to mint. - amount: u64, - /// Number of base 10 digits to the right of the decimal place. - decimals: u8, - }, - /// Initializes a new account to hold tokens. If this account is associated with the native mint - /// then the token balance of the initialized account will be equal to the amount of SOL in the account. - /// - /// The `InitializeAccount` instruction requires no signers and MUST be included within - /// the same Transaction as the system program's `CreateInstruction` that creates the account - /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable]` The account to initialize. - /// 1. `[]` The mint this account will be associated with. - /// 2. `[]` The new account's owner/multisignature. - InitializeAccount, - /// Initializes a multisignature account with N provided signers. - /// - /// Multisignature accounts can used in place of any single owner/delegate accounts in any - /// token instruction that require an owner/delegate to be present. The variant field represents the - /// number of signers (M) required to validate this multisignature account. - /// - /// The `InitializeMultisig` instruction requires no signers and MUST be included within - /// the same Transaction as the system program's `CreateInstruction` that creates the account - /// being initialized. Otherwise another party can acquire ownership of the uninitialized account. - /// - /// Accounts expected by this instruction: - /// - /// 0. `[writable]` The multisignature account to initialize. - /// 1. ..1+N. `[]` The signer accounts, must equal to N where 1 <= N <= 11. - InitializeMultisig { - /// The number of signers (M) required to validate this multisignature account. - m: u8, - }, - /// Transfers tokens from one account to another either directly or via a delegate. If this - /// account is associated with the native mint then equal amounts of SOL and Tokens will be - /// transferred to the destination account. - /// - /// Accounts expected by this instruction: - /// - /// * Single owner/delegate - /// 0. `[writable]` The source account. - /// 1. `[writable]` The destination account. - /// 2. '[signer]' The source account's owner/delegate. - /// - /// * Multisignature owner/delegate - /// 0. `[writable]` The source account. - /// 1. `[writable]` The destination account. - /// 2. '[]' The source account's multisignature owner/delegate. - /// 3. ..3+M '[signer]' M signer accounts. - Transfer { - /// The amount of tokens to transfer. - amount: u64, - }, - /// Approves a delegate. A delegate is given the authority over - /// tokens on behalf of the source account's owner. - - /// Accounts expected by this instruction: - /// - /// * Single owner - /// 0. `[writable]` The source account. - /// 1. `[]` The delegate. - /// 2. `[signer]` The source account owner. - /// - /// * Multisignature owner - /// 0. `[writable]` The source account. - /// 1. `[]` The delegate. - /// 2. '[]' The source account's multisignature owner. - /// 3. ..3+M '[signer]' M signer accounts - Approve { - /// The amount of tokens the delegate is approved for. - amount: u64, - }, - /// Revokes the delegate's authority. - /// - /// Accounts expected by this instruction: - /// - /// * Single owner - /// 0. `[writable]` The source account. - /// 1. `[signer]` The source account owner. - /// - /// * Multisignature owner - /// 0. `[writable]` The source account. - /// 1. '[]' The source account's multisignature owner. - /// 2. ..2+M '[signer]' M signer accounts - Revoke, - /// Sets a new owner of a mint or account. - /// - /// Accounts expected by this instruction: - /// - /// * Single owner - /// 0. `[writable]` The mint or account to change the owner of. - /// 1. `[]` The new owner/delegate/multisignature. - /// 2. `[signer]` The owner of the mint or account. - /// - /// * Multisignature owner - /// 0. `[writable]` The mint or account to change the owner of. - /// 1. `[]` The new owner/delegate/multisignature. - /// 2. `[]` The mint's or account's multisignature owner. - /// 3. ..3+M '[signer]' M signer accounts - SetOwner, - /// Mints new tokens to an account. The native mint does not support minting. - /// - /// Accounts expected by this instruction: - /// - /// * Single owner - /// 0. `[writable]` The mint. - /// 1. `[writable]` The account to mint tokens to. - /// 2. `[signer]` The mint's owner. - /// - /// * Multisignature owner - /// 0. `[writable]` The mint. - /// 1. `[writable]` The account to mint tokens to. - /// 2. `[]` The mint's multisignature owner. - /// 3. ..3+M '[signer]' M signer accounts. - MintTo { - /// The amount of new tokens to mint. - amount: u64, - }, - /// Burns tokens by removing them from an account. `Burn` does not support accounts - /// associated with the native mint, use `CloseAccount` instead. - /// - /// Accounts expected by this instruction: - /// - /// * Single owner/delegate - /// 0. `[writable]` The account to burn from. - /// 1. `[signer]` The account's owner/delegate. - /// - /// * Multisignature owner/delegate - /// 0. `[writable]` The account to burn from. - /// 1. `[]` The account's multisignature owner/delegate. - /// 2. ..2+M '[signer]' M signer accounts. - Burn { - /// The amount of tokens to burn. - amount: u64, - }, - /// Close an account by transferring all its SOL to the destination account. - /// Non-native accounts may only be closed if its token amount is zero. - /// - /// Accounts expected by this instruction: - /// - /// * Single owner - /// 0. `[writable]` The account to close. - /// 1. '[writable]' The destination account. - /// 2. `[signer]` The account's owner. - /// - /// * Multisignature owner - /// 0. `[writable]` The account to close. - /// 1. '[writable]' The destination account. - /// 2. `[]` The account's multisignature owner. - /// 3. ..3+M '[signer]' M signer accounts. - CloseAccount, -} -impl TokenInstruction { - /// Unpacks a byte buffer into a [TokenInstruction](enum.TokenInstruction.html). - pub fn unpack(input: &[u8]) -> Result { - if input.len() < size_of::() { - return Err(ProgramError::Custom(0)); - } - Ok(match input[0] { - 0 => { - if input.len() < size_of::() + size_of::() + size_of::() { - return Err(ProgramError::Custom(1)); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - let decimals = - unsafe { *(&input[size_of::() + size_of::()] as *const u8) }; - Self::InitializeMint { amount, decimals } - } - 1 => Self::InitializeAccount, - 2 => { - if input.len() < size_of::() + size_of::() { - return Err(ProgramError::Custom(2)); - } - #[allow(clippy::cast_ptr_alignment)] - let m = unsafe { *(&input[1] as *const u8) }; - Self::InitializeMultisig { m } - } - 3 => { - if input.len() < size_of::() + size_of::() { - return Err(ProgramError::Custom(3)); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::Transfer { amount } - } - 4 => { - if input.len() < size_of::() + size_of::() { - return Err(ProgramError::Custom(4)); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::Approve { amount } - } - 5 => Self::Revoke, - 6 => Self::SetOwner, - 7 => { - if input.len() < size_of::() + size_of::() { - return Err(ProgramError::Custom(5)); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::MintTo { amount } - } - 8 => { - if input.len() < size_of::() + size_of::() { - return Err(ProgramError::Custom(6)); - } - #[allow(clippy::cast_ptr_alignment)] - let amount = unsafe { *(&input[size_of::()] as *const u8 as *const u64) }; - Self::Burn { amount } - } - 9 => Self::CloseAccount, - _ => return Err(ProgramError::Custom(7)), - }) - } - - /// Packs a [TokenInstruction](enum.TokenInstruction.html) into a byte buffer. - pub fn pack(self: &Self) -> Result, ProgramError> { - let mut output = vec![0u8; size_of::()]; - match self { - Self::InitializeMint { amount, decimals } => { - output[0] = 0; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut u64) }; - *value = *amount; - let value = - unsafe { &mut *(&mut output[size_of::() + size_of::()] as *mut u8) }; - *value = *decimals; - } - Self::InitializeAccount => output[0] = 1, - Self::InitializeMultisig { m } => { - output[0] = 2; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut u8) }; - *value = *m; - } - Self::Transfer { amount } => { - output[0] = 3; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut u64) }; - *value = *amount; - } - Self::Approve { amount } => { - output[0] = 4; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut u64) }; - *value = *amount; - } - Self::Revoke => output[0] = 5, - Self::SetOwner => output[0] = 6, - Self::MintTo { amount } => { - output[0] = 7; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut u64) }; - *value = *amount; - } - Self::Burn { amount } => { - output[0] = 8; - #[allow(clippy::cast_ptr_alignment)] - let value = unsafe { &mut *(&mut output[size_of::()] as *mut u8 as *mut u64) }; - *value = *amount; - } - Self::CloseAccount => output[0] = 9, - } - Ok(output) - } -} - -/// Creates a 'InitializeMint' instruction. -pub fn initialize_mint( - token_program_id: &Pubkey, - mint_pubkey: &Pubkey, - account_pubkey: Option<&Pubkey>, - owner_pubkey: Option<&Pubkey>, - amount: u64, - decimals: u8, -) -> Result { - let data = TokenInstruction::InitializeMint { amount, decimals }.pack()?; - - let mut accounts = vec![AccountMeta::new(*mint_pubkey, false)]; - if amount != 0 { - match account_pubkey { - Some(pubkey) => accounts.push(AccountMeta::new(*pubkey, false)), - None => { - return Err(ProgramError::NotEnoughAccountKeys); - } - } - } - match owner_pubkey { - Some(pubkey) => accounts.push(AccountMeta::new_readonly(*pubkey, false)), - None => { - if amount == 0 { - return Err(ProgramError::Custom(8)); - } - } - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates a `InitializeAccount` instruction. -pub fn initialize_account( - token_program_id: &Pubkey, - account_pubkey: &Pubkey, - mint_pubkey: &Pubkey, - owner_pubkey: &Pubkey, -) -> Result { - let data = TokenInstruction::InitializeAccount.pack()?; - - let accounts = vec![ - AccountMeta::new(*account_pubkey, false), - AccountMeta::new_readonly(*mint_pubkey, false), - AccountMeta::new_readonly(*owner_pubkey, false), - ]; - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates a `InitializeMultisig` instruction. -pub fn initialize_multisig( - token_program_id: &Pubkey, - multisig_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], - m: u8, -) -> Result { - if !is_valid_signer_index(m as usize) - || !is_valid_signer_index(signer_pubkeys.len()) - || m as usize > signer_pubkeys.len() - { - return Err(ProgramError::MissingRequiredSignature); - } - let data = TokenInstruction::InitializeMultisig { m }.pack()?; - - let mut accounts = Vec::with_capacity(1 + signer_pubkeys.len()); - accounts.push(AccountMeta::new(*multisig_pubkey, false)); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new_readonly(**signer_pubkey, false)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates a `Transfer` instruction. -pub fn transfer( - token_program_id: &Pubkey, - source_pubkey: &Pubkey, - destination_pubkey: &Pubkey, - authority_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], - amount: u64, -) -> Result { - let data = TokenInstruction::Transfer { amount }.pack()?; - - let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); - accounts.push(AccountMeta::new(*source_pubkey, false)); - accounts.push(AccountMeta::new(*destination_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *authority_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates an `Approve` instruction. -pub fn approve( - token_program_id: &Pubkey, - source_pubkey: &Pubkey, - delegate_pubkey: &Pubkey, - owner_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], - amount: u64, -) -> Result { - let data = TokenInstruction::Approve { amount }.pack()?; - - let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); - accounts.push(AccountMeta::new_readonly(*source_pubkey, false)); - accounts.push(AccountMeta::new(*delegate_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *owner_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates an `Approve` instruction. -pub fn revoke( - token_program_id: &Pubkey, - source_pubkey: &Pubkey, - owner_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], -) -> Result { - let data = TokenInstruction::Revoke.pack()?; - - let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len()); - accounts.push(AccountMeta::new_readonly(*source_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *owner_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates an `SetOwner` instruction. -pub fn set_owner( - token_program_id: &Pubkey, - owned_pubkey: &Pubkey, - new_owner_pubkey: &Pubkey, - owner_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], -) -> Result { - let data = TokenInstruction::SetOwner.pack()?; - - let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); - accounts.push(AccountMeta::new(*owned_pubkey, false)); - accounts.push(AccountMeta::new_readonly(*new_owner_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *owner_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates an `MintTo` instruction. -pub fn mint_to( - token_program_id: &Pubkey, - mint_pubkey: &Pubkey, - account_pubkey: &Pubkey, - owner_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], - amount: u64, -) -> Result { - let data = TokenInstruction::MintTo { amount }.pack()?; - - let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); - accounts.push(AccountMeta::new(*mint_pubkey, false)); - accounts.push(AccountMeta::new(*account_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *owner_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates an `Burn` instruction. -pub fn burn( - token_program_id: &Pubkey, - account_pubkey: &Pubkey, - authority_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], - amount: u64, -) -> Result { - let data = TokenInstruction::Burn { amount }.pack()?; - - let mut accounts = Vec::with_capacity(2 + signer_pubkeys.len()); - accounts.push(AccountMeta::new(*account_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *authority_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Creates an `CloseAccount` instruction. -pub fn close_account( - token_program_id: &Pubkey, - account_pubkey: &Pubkey, - dest_pubkey: &Pubkey, - authority_pubkey: &Pubkey, - signer_pubkeys: &[&Pubkey], -) -> Result { - let data = TokenInstruction::CloseAccount.pack()?; - - let mut accounts = Vec::with_capacity(3 + signer_pubkeys.len()); - accounts.push(AccountMeta::new(*account_pubkey, false)); - accounts.push(AccountMeta::new(*dest_pubkey, false)); - accounts.push(AccountMeta::new_readonly( - *authority_pubkey, - signer_pubkeys.is_empty(), - )); - for signer_pubkey in signer_pubkeys.iter() { - accounts.push(AccountMeta::new(**signer_pubkey, true)); - } - - Ok(Instruction { - program_id: *token_program_id, - accounts, - data, - }) -} - -/// Utility function that checks index is between MIN_SIGNERS and MAX_SIGNERS -pub fn is_valid_signer_index(index: usize) -> bool { - !(index < MIN_SIGNERS || index > MAX_SIGNERS) -}