diff --git a/.env.example b/.env.example index 91d49fa90..b5dd9ed3f 100644 --- a/.env.example +++ b/.env.example @@ -1,10 +1,5 @@ -# [REQUIRED] - the connection URL for the application database. -DB_USER=granola -DB_PASS=systems -DB_HOST=127.0.0.1 -DB_PORT=5432 -DB_NAME=db -DATABASE_URL=postgresql://granola:systems@127.0.0.1:5432/db +# The path from which to fetch the proposals manifest. Empty defaults to the MinaFoundation/mina-on-chain-voting GitHub. +PROPOSALS_URL="" BUCKET_NAME="673156464838-mina-staking-ledgers" @@ -43,4 +38,4 @@ NEXT_PUBLIC_API_BASE_URL=http://127.0.0.1:8080 # SKIP_ENV_VALIDATION=1 # Sets the log level for the server. Must be on 'debug' for testing. -RUST_LOG=debug +RUST_LOG=debug,error,info diff --git a/docker-compose.yaml b/docker-compose.yaml index 0e169032b..a182cbf3b 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,42 +1,22 @@ services: web: container_name: web - profiles: ["all", "web"] build: context: ./web dockerfile: Dockerfile env_file: .env environment: - - API_BASE_URL=http://server:8080 - NEXT_PUBLIC_API_BASE_URL=http://server:8080 - RELEASE_STAGE=production ports: - 3000:3000 server: container_name: server - profiles: ["all", "server-db", "server"] image: ocv-server build: context: ./server dockerfile: Dockerfile # dockerfile: Dockerfile.dev env_file: .env - environment: - - DB_HOST=db - - DATABASE_URL=postgresql://granola:systems@db:5432/db ports: - 8080:8080 - depends_on: - - db - db: - container_name: postgres - profiles: ["all", "server-db", "db"] - image: postgres:15.2 - ports: - - 5432:5432 - environment: - POSTGRES_USER: granola - POSTGRES_PASSWORD: systems - POSTGRES_DB: db - tmpfs: - - /var/lib/postgresql/data diff --git a/rustfmt.toml b/rustfmt.toml index cdb0b7b69..fab799385 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -6,6 +6,7 @@ overflow_delimited_expr = true spaces_around_ranges = true tab_spaces = 2 use_field_init_shorthand = true +use_small_heuristics = "Max" use_try_shorthand = true version = "Two" wrap_comments = true diff --git a/server/Cargo.lock b/server/Cargo.lock index 949b185b2..18307c368 100644 --- a/server/Cargo.lock +++ b/server/Cargo.lock @@ -4,19 +4,13 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" dependencies = [ "gimli", ] -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "adler2" version = "2.0.0" @@ -94,9 +88,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.86" +version = "1.0.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" +checksum = "86fdf8605db99b54d3cd748a44c6d04df638eb5dafb219b135d0149bd0db01f6" [[package]] name = "arrayvec" @@ -117,13 +111,13 @@ dependencies = [ [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.82" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "a27b8a3a6e1a44fa4c8baf1f653e4172e81486d4941f2237e20dc2d0cf4ddff1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -134,18 +128,20 @@ checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "axum" -version = "0.6.20" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b829e4e32b91e643de6eafe82b1d90675f5874230191a4ffbc1b336dec4d6bf" +checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf" dependencies = [ "async-trait", "axum-core", - "bitflags 1.3.2", + "axum-macros", "bytes", "futures-util", - "http", - "http-body", - "hyper", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", + "hyper 1.4.1", + "hyper-util", "itoa", "matchit", "memchr", @@ -157,43 +153,60 @@ dependencies = [ "serde_json", "serde_path_to_error", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 1.0.1", "tokio", "tower", "tower-layer", "tower-service", + "tracing", ] [[package]] name = "axum-core" -version = "0.3.4" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759fa577a247914fd3f7f76d62972792636412fbfd634cd452f6a385a74d2d2c" +checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3" dependencies = [ "async-trait", "bytes", "futures-util", - "http", - "http-body", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "mime", + "pin-project-lite", "rustversion", + "sync_wrapper 0.1.2", "tower-layer", "tower-service", + "tracing", +] + +[[package]] +name = "axum-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00c055ee2d014ae5981ce1016374e8213682aa14d9bf40e48ab48b5f3ef20eaa" +dependencies = [ + "heck 0.4.1", + "proc-macro2", + "quote", + "syn 2.0.77", ] [[package]] name = "backtrace" -version = "0.3.73" +version = "0.3.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" +checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", - "miniz_oxide 0.7.4", + "miniz_oxide", "object", "rustc-demangle", + "windows-targets 0.52.6", ] [[package]] @@ -268,7 +281,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "syn_derive", ] @@ -323,9 +336,9 @@ checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" -version = "1.1.15" +version = "1.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" +checksum = "45bcde016d64c21da4be18b655631e5ab6d3107607e71a73a9f53eb48aae23fb" dependencies = [ "shlex", ] @@ -344,9 +357,9 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" [[package]] name = "clap" -version = "4.5.16" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed6719fffa43d0d87e5fd8caeab59be1554fb028cd30edc88fc4369b17971019" +checksum = "3e5a21b8495e732f1b3c364c9949b201ca7bae518c502c80256c96ad79eaf6ac" dependencies = [ "clap_builder", "clap_derive", @@ -354,9 +367,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "8cf2dd12af7a047ad9d6da2b6b249759a22a7abc0f474c1dae1777afa4b21a73" dependencies = [ "anstream", "anstyle", @@ -373,7 +386,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -415,9 +428,9 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" dependencies = [ "libc", ] @@ -476,7 +489,7 @@ dependencies = [ "proc-macro2", "quote", "strsim", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -487,14 +500,14 @@ checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" dependencies = [ "darling_core", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "diesel" -version = "2.2.3" +version = "2.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65e13bab2796f412722112327f3e575601a3e9cdcbe426f0d30dbf43f3f5dc71" +checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e" dependencies = [ "bigdecimal", "bitflags 2.6.0", @@ -517,7 +530,7 @@ dependencies = [ "heck 0.4.1", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -530,7 +543,7 @@ dependencies = [ "dsl_auto_type", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -539,7 +552,7 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" dependencies = [ - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -562,7 +575,7 @@ dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -642,7 +655,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "324a1be68054ef05ad64b861cc9eaf1d623d2d8cb25b4bf2cb9cdd902b4bf253" dependencies = [ "crc32fast", - "miniz_oxide 0.8.0", + "miniz_oxide", ] [[package]] @@ -710,7 +723,7 @@ checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -764,9 +777,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.31.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" [[package]] name = "h2" @@ -779,7 +792,7 @@ dependencies = [ "futures-core", "futures-sink", "futures-util", - "http", + "http 0.2.12", "indexmap", "slab", "tokio", @@ -831,6 +844,17 @@ dependencies = [ "itoa", ] +[[package]] +name = "http" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + [[package]] name = "http-body" version = "0.4.6" @@ -838,15 +862,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" dependencies = [ "bytes", - "http", + "http 0.2.12", "pin-project-lite", ] [[package]] -name = "http-range-header" -version = "0.3.1" +name = "http-body" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "add0ab9360ddbd88cfeb3bd9574a1d85cfdfa14db10b3e21d3700dbc4328758f" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http 1.1.0", +] + +[[package]] +name = "http-body-util" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "pin-project-lite", +] [[package]] name = "httparse" @@ -871,8 +912,8 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", + "http 0.2.12", + "http-body 0.4.6", "httparse", "httpdate", "itoa", @@ -884,6 +925,25 @@ dependencies = [ "want", ] +[[package]] +name = "hyper" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "httparse", + "httpdate", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", +] + [[package]] name = "hyper-tls" version = "0.5.0" @@ -891,12 +951,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" dependencies = [ "bytes", - "hyper", + "hyper 0.14.30", "native-tls", "tokio", "tokio-native-tls", ] +[[package]] +name = "hyper-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" +dependencies = [ + "bytes", + "futures-util", + "http 1.1.0", + "http-body 1.0.1", + "hyper 1.4.1", + "pin-project-lite", + "tokio", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -915,9 +990,9 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.4.0" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" dependencies = [ "equivalent", "hashbrown 0.14.5", @@ -925,9 +1000,9 @@ dependencies = [ [[package]] name = "ipnet" -version = "2.9.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" [[package]] name = "is_terminal_polyfill" @@ -1029,7 +1104,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] -name = "mina-ocv-server" +name = "mina-ocv" version = "0.8.0" dependencies = [ "anyhow", @@ -1051,21 +1126,11 @@ dependencies = [ "tar", "thiserror", "tokio", - "tower", "tower-http", "tracing", "tracing-subscriber", ] -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "miniz_oxide" version = "0.8.0" @@ -1168,9 +1233,9 @@ dependencies = [ [[package]] name = "object" -version = "0.36.3" +version = "0.36.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b64972346851a39438c60b341ebc01bba47464ae329e55cf343eb93964efd9" +checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" dependencies = [ "memchr", ] @@ -1210,7 +1275,7 @@ checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1239,9 +1304,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" @@ -1289,7 +1354,7 @@ checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1321,9 +1386,9 @@ dependencies = [ [[package]] name = "pq-sys" -version = "0.6.1" +version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a24ff9e4cf6945c988f0db7005d87747bf72864965c3529d259ad155ac41d584" +checksum = "a92c30dd81695321846d4dfe348da67b1752ebb61cd1549d203a7b57e323c435" dependencies = [ "vcpkg", ] @@ -1471,9 +1536,9 @@ dependencies = [ [[package]] name = "redox_syscall" -version = "0.5.3" +version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" +checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" dependencies = [ "bitflags 2.6.0", ] @@ -1543,9 +1608,9 @@ dependencies = [ "futures-core", "futures-util", "h2", - "http", - "http-body", - "hyper", + "http 0.2.12", + "http-body 0.4.6", + "hyper 0.14.30", "hyper-tls", "ipnet", "js-sys", @@ -1559,7 +1624,7 @@ dependencies = [ "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", + "sync_wrapper 0.1.2", "system-configuration", "tokio", "tokio-native-tls", @@ -1633,9 +1698,9 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.35" +version = "0.38.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a85d50532239da68e9addb745ba38ff4612a242c1c7ceea689c4bc7c2f43c36f" +checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" dependencies = [ "bitflags 2.6.0", "errno", @@ -1667,11 +1732,11 @@ checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "schannel" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" +checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.59.0", ] [[package]] @@ -1726,29 +1791,29 @@ checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.128" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" dependencies = [ "itoa", "memchr", @@ -1865,9 +1930,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ "proc-macro2", "quote", @@ -1883,7 +1948,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1892,6 +1957,12 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +[[package]] +name = "sync_wrapper" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" + [[package]] name = "system-configuration" version = "0.5.1" @@ -1966,7 +2037,7 @@ checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -1996,9 +2067,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.3" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", @@ -2020,7 +2091,7 @@ checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2035,9 +2106,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" dependencies = [ "bytes", "futures-core", @@ -2054,9 +2125,9 @@ checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" [[package]] name = "toml_edit" -version = "0.22.20" +version = "0.22.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "583c44c02ad26b0c3f3066fe629275e50627026c51ac2e595cca4c230ce1ce1d" +checksum = "3b072cee73c449a636ffd6f32bd8de3a9f7119139aff882f44943ce2986dc5cf" dependencies = [ "indexmap", "toml_datetime", @@ -2081,21 +2152,18 @@ dependencies = [ [[package]] name = "tower-http" -version = "0.4.4" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61c5bb1d698276a2443e5ecfabc1008bf15a36c12e6a7176e7bf089ea9131140" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" dependencies = [ "bitflags 2.6.0", "bytes", - "futures-core", - "futures-util", - "http", - "http-body", - "http-range-header", + "http 1.1.0", + "http-body 1.0.1", + "http-body-util", "pin-project-lite", "tower-layer", "tower-service", - "tracing", ] [[package]] @@ -2130,7 +2198,7 @@ checksum = "34704c8d6ebcbc939824180af020566b01a7c01f80641264eba0999f6c2b6be7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] [[package]] @@ -2198,9 +2266,9 @@ checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] name = "unicode-normalization" @@ -2292,7 +2360,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "wasm-bindgen-shared", ] @@ -2326,7 +2394,7 @@ checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -2574,5 +2642,5 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.77", ] diff --git a/server/Cargo.toml b/server/Cargo.toml index 6c767062f..bad295e6e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,32 +1,28 @@ [package] -name = "mina-ocv-server" +name = "mina-ocv" version = "0.8.0" edition = "2021" [dependencies] -# Core dependencies -axum = { version = "0.6.4", features = ["tower-log"] } +anyhow = "1.0.69" +axum = { version = "0.7.5", features = ["macros"] } +bigdecimal = "0.4.5" bs58 = { version = "0.4.0", features = ["check"] } +bytes = "1.4.0" clap = { version = "4.1.4", features = ["derive", "env"] } diesel = { version = "2.0.3", features = ["postgres", "r2d2", "numeric"] } diesel-derive-enum = { version = "2.0.0", features = ["postgres"] } +flate2 = "1.0.33" +futures-util = "0.3" moka = { version = "0.12.0", features = ["future"] } +r2d2 = "0.8.10" reqwest = { version = "0.11.14", features = ["json", "blocking"] } +rust_decimal = "1.28.0" serde = { version = "1.0.152", features = ["derive"] } serde_json = "1.0.93" +tar = "0.4.41" thiserror = "1.0.38" tokio = { version = "1.25.0", features = ["full"] } -tower = "0.4.13" -tower-http = { version = "0.4.0", features = ["trace", "cors"] } +tower-http = { version = "0.5.0", features = ["cors"] } tracing = "0.1" tracing-subscriber = { version = "0.3", features = ["env-filter"] } - -# Utility dependencies -anyhow = "1.0.69" -bigdecimal = "0.4.5" -bytes = "1.4.0" -flate2 = "1.0.33" -futures-util = "0.3" -r2d2 = "0.8.10" -rust_decimal = "1.28.0" -tar = "0.4.41" diff --git a/server/Dockerfile b/server/Dockerfile index f770590b9..d0324ee2c 100644 --- a/server/Dockerfile +++ b/server/Dockerfile @@ -40,4 +40,4 @@ COPY --chown=mina:mina . . RUN cargo build --release # start the server -CMD ["./start"] +CMD ["./target/release/mina_ocv"] diff --git a/server/diesel.toml b/server/diesel.toml deleted file mode 100644 index 55e832d54..000000000 --- a/server/diesel.toml +++ /dev/null @@ -1,8 +0,0 @@ -# For documentation on how to configure this file, -# see https://diesel.rs/guides/configuring-diesel-cli - -[print_schema] -file = "./src/schema.rs" - -[migrations_directory] -dir = "./migrations" diff --git a/server/migrations/.keep b/server/migrations/.keep deleted file mode 100644 index e69de29bb..000000000 diff --git a/server/migrations/00000000000000_diesel_initial_setup/down.sql b/server/migrations/00000000000000_diesel_initial_setup/down.sql deleted file mode 100644 index a9f526091..000000000 --- a/server/migrations/00000000000000_diesel_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - -DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); -DROP FUNCTION IF EXISTS diesel_set_updated_at(); diff --git a/server/migrations/00000000000000_diesel_initial_setup/up.sql b/server/migrations/00000000000000_diesel_initial_setup/up.sql deleted file mode 100644 index d68895b1a..000000000 --- a/server/migrations/00000000000000_diesel_initial_setup/up.sql +++ /dev/null @@ -1,36 +0,0 @@ --- This file was automatically created by Diesel to setup helper functions --- and other internal bookkeeping. This file is safe to edit, any future --- changes will be added to existing projects as new migrations. - - - - --- Sets up a trigger for the given table to automatically set a column called --- `updated_at` whenever the row is modified (unless `updated_at` was included --- in the modified columns) --- --- # Example --- --- ```sql --- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW()); --- --- SELECT diesel_manage_updated_at('users'); --- ``` -CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$ -BEGIN - EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s - FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl); -END; -$$ LANGUAGE plpgsql; - -CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$ -BEGIN - IF ( - NEW IS DISTINCT FROM OLD AND - NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at - ) THEN - NEW.updated_at := current_timestamp; - END IF; - RETURN NEW; -END; -$$ LANGUAGE plpgsql; diff --git a/server/migrations/2023-08-20-211633_add_initial_schema/down.sql b/server/migrations/2023-08-20-211633_add_initial_schema/down.sql deleted file mode 100644 index 177fbc590..000000000 --- a/server/migrations/2023-08-20-211633_add_initial_schema/down.sql +++ /dev/null @@ -1,7 +0,0 @@ --- This file should undo anything in `up.sql` - -DROP INDEX IF EXISTS mina_proposals_key_idx; -DROP TABLE IF EXISTS mina_proposals; - -DROP TYPE IF EXISTS proposal_version; -DROP TYPE IF EXISTS proposal_category; \ No newline at end of file diff --git a/server/migrations/2023-08-20-211633_add_initial_schema/up.sql b/server/migrations/2023-08-20-211633_add_initial_schema/up.sql deleted file mode 100644 index 83cebfa7a..000000000 --- a/server/migrations/2023-08-20-211633_add_initial_schema/up.sql +++ /dev/null @@ -1,108 +0,0 @@ --- Your SQL goes here -CREATE TYPE proposal_version AS ENUM ('V1', 'V2'); -CREATE TYPE proposal_category AS ENUM ( - 'Core', - 'Networking', - 'Interface', - 'ERC', - 'Cryptography' -); -CREATE TABLE mina_proposals ( - id SERIAL PRIMARY KEY, - key TEXT NOT NULL UNIQUE, - start_time BIGINT NOT NULL, - end_time BIGINT NOT NULL, - epoch BIGINT NOT NULL, - ledger_hash TEXT, - category proposal_category NOT NULL, - version proposal_version NOT NULL DEFAULT 'V2', - title TEXT NOT NULL, - description TEXT NOT NULL, - url TEXT NOT NULL -); -CREATE INDEX mina_proposals_key_idx ON mina_proposals (key); -INSERT INTO mina_proposals -VALUES ( - 1, - 'MIP1', - 1672848000000, - 1673685000000, - 46, - 'jxQXzUkst2L9Ma9g9YQ3kfpgB5v5Znr1vrYb1mupakc5y7T89H8', - 'Core', - 'V1', - 'Remove supercharged rewards', - 'Removing the short-term incentive of supercharged rewards.', - 'https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-remove-supercharged-rewards.md' - ); -INSERT INTO mina_proposals -VALUES ( - 3, - 'MIP3', - 1684562400000, - 1685253600000, - 55, - 'jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS', - 'Cryptography', - 'V2', - 'Kimchi, a new proof system', - 'Kimchi is an update to the proof system currently used by Mina.', - 'https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-kimchi.md' - ); -INSERT INTO mina_proposals -VALUES ( - 4, - 'MIP4', - 1684562400000, - 1685253600000, - 55, - 'jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS', - 'Core', - 'V2', - 'Easier zkApp programmability on mainnet', - 'Adding programmable smart contracts (zkApps) to the Mina protocol.', - 'https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-zkapps.md' - ); -INSERT INTO mina_proposals -VALUES ( - 5, - 'MIP50', - 1722981771000, - 1728252171000, - 73, - 'jwUfRQindewPk7Uep8z3YUjMvQKaq6drpKvJRTq47wGU6Dxkhqx', - 'Core', - 'V2', - 'Devnet Testing Title', - 'Devnet Testing Description', - 'https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-zkapps.md' - ); -INSERT INTO mina_proposals -VALUES ( - 6, - 'MIP51', - 1726170486000, - 1726789686000, - 73, - 'jwUfRQindewPk7Uep8z3YUjMvQKaq6drpKvJRTq47wGU6Dxkhqx', - 'Core', - 'V2', - 'Devnet Testing Title', - 'Devnet Testing Description', - 'https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-zkapps.md' - ); -INSERT INTO mina_proposals -VALUES ( - 7, - 'MIP52', - 1726170486000, - 1726789686000, - 73, - 'jwUfRQindewPk7Uep8z3YUjMvQKaq6drpKvJRTq47wGU6Dxkhqx', - 'Core', - 'V2', - 'Devnet Testing Title', - 'Devnet Testing Description', - 'https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-zkapps.md' - ); - diff --git a/server/proposals/proposals.json b/server/proposals/proposals.json new file mode 100644 index 000000000..c0ab25a6d --- /dev/null +++ b/server/proposals/proposals.json @@ -0,0 +1,89 @@ +{ + "$schema": "./proposals_schema.json", + "proposals": [ + { + "id": 0, + "key": "MIP1", + "category": "Core", + "title": "Remove supercharged rewards", + "description": "Removing the short-term incentive of supercharged rewards.", + "start_time": 1672848000000, + "end_time": 1673685000000, + "epoch": 46, + "ledger_hash": "jxQXzUkst2L9Ma9g9YQ3kfpgB5v5Znr1vrYb1mupakc5y7T89H8", + "url": "https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-remove-supercharged-rewards.md", + "version": "V1", + "network": "mainnet" + }, + { + "id": 1, + "key": "MIP3", + "category": "Cryptography", + "title": "Kimchi, a new proof system", + "description": "Kimchi is an update to the proof system currently used by Mina.", + "start_time": 1684562400000, + "end_time": 1685253600000, + "epoch": 55, + "ledger_hash": "jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS", + "url": "https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-kimchi.md", + "version": "V2", + "network": "mainnet" + }, + { + "id": 2, + "key": "MIP4", + "category": "Core", + "title": "Easier zkApp programmability on mainnet", + "description": "Adding programmable smart contracts (zkApps) to the Mina protocol.", + "start_time": 1684562400000, + "end_time": 1685253600000, + "epoch": 55, + "ledger_hash": "jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS", + "url": "https://github.com/MinaProtocol/MIPs/blob/main/MIPS/mip-zkapps.md", + "version": "V2", + "network": "mainnet" + }, + { + "id": 3, + "key": "MIP50", + "category": "Core", + "title": "", + "description": "", + "start_time": 1726170486000, + "end_time": 1726789686000, + "epoch": 73, + "ledger_hash": "jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS", + "url": "", + "version": "V2", + "network": "devnet" + }, + { + "id": 4, + "key": "MIP51", + "category": "Core", + "title": "", + "description": "", + "start_time": 1726170486000, + "end_time": 1726789686000, + "epoch": 73, + "ledger_hash": "jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS", + "url": "", + "version": "V2", + "network": "devnet" + }, + { + "id": 5, + "key": "MIP52", + "category": "Core", + "title": "", + "description": "", + "start_time": 1726170486000, + "end_time": 1726789686000, + "epoch": 73, + "ledger_hash": "jw8dXuUqXVgd6NvmpryGmFLnRv1176oozHAro8gMFwj8yuvhBeS", + "url": "", + "version": "V2", + "network": "devnet" + } + ] +} diff --git a/server/proposals/proposals_schema.json b/server/proposals/proposals_schema.json new file mode 100644 index 000000000..bc10ff7eb --- /dev/null +++ b/server/proposals/proposals_schema.json @@ -0,0 +1,80 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "MinaProposals", + "type": "object", + "properties": { + "proposals": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "number", + "description": "The ID of the proposal" + }, + "key": { + "type": "string", + "description": "Key string of the proposal" + }, + "start_time": { + "type": "integer", + "description": "Start time of the proposal (Unix timestamp)" + }, + "end_time": { + "type": "integer", + "description": "End time of the proposal (Unix timestamp)" + }, + "epoch": { + "type": "integer", + "description": "Epoch in which the proposal is valid" + }, + "ledger_hash": { + "type": ["string", "null"], + "description": "Optional ledger hash" + }, + "category": { + "type": "string", + "enum": ["Core", "Networking", "Interface", "ERC", "Cryptography"], + "description": "Category of the proposal" + }, + "version": { + "type": "string", + "enum": ["V1", "V2"], + "description": "Version of the proposal" + }, + "title": { + "type": "string", + "description": "Title of the proposal" + }, + "description": { + "type": "string", + "description": "Detailed description of the proposal" + }, + "url": { + "type": "string", + "description": "URL for more information on the proposal" + }, + "network": { + "type": "string", + "enum": ["devnet", "mainnet"], + "description": "Network on which the proposal is valid" + } + }, + "required": [ + "id", + "key", + "start_time", + "end_time", + "epoch", + "category", + "version", + "title", + "description", + "url", + "network" + ] + } + } + }, + "required": ["proposals"] +} diff --git a/server/rust-toolchain b/server/rust-toolchain deleted file mode 100644 index 870bbe4e5..000000000 --- a/server/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -stable \ No newline at end of file diff --git a/server/src/archive.rs b/server/src/archive.rs new file mode 100644 index 000000000..c6923b281 --- /dev/null +++ b/server/src/archive.rs @@ -0,0 +1,86 @@ +use crate::{BlockStatus, ChainStatusType}; +use anyhow::{Context, Result}; +use diesel::{ + r2d2::ConnectionManager, + sql_query, + sql_types::{BigInt, Text}, + PgConnection, QueryableByName, RunQueryDsl, +}; +use r2d2::Pool; + +#[derive(Clone)] +pub struct Archive(Pool>); + +impl Archive { + pub fn new(archive_database_url: &String) -> Self { + let archive_manager = ConnectionManager::::new(archive_database_url); + let pool = Pool::builder() + .test_on_check_out(true) + .build(archive_manager) + .unwrap_or_else(|_| panic!("Error: failed to build `archive` connection pool")); + Self(pool) + } + + pub fn fetch_chain_tip(&self) -> Result { + let connection = &mut self.0.get().context("failed to get archive db connection")?; + let result = sql_query("SELECT MAX(height) FROM blocks").get_result::(connection)?; + Ok(result.max) + } + + pub fn fetch_latest_slot(&self) -> Result { + let connection = &mut self.0.get().context("failed to get archive db connection")?; + let result = sql_query("SELECT MAX(global_slot) FROM blocks").get_result::(connection)?; + Ok(result.max) + } + + pub fn fetch_transactions(&self, start_time: i64, end_time: i64) -> Result> { + let connection = &mut self.0.get().context("failed to get archive db connection")?; + let results = sql_query( + "SELECT DISTINCT pk.value as account, uc.memo as memo, uc.nonce as nonce, uc.hash as hash, b.height as height, b.chain_status as status, b.timestamp::bigint as timestamp + FROM user_commands AS uc + JOIN blocks_user_commands AS buc + ON uc.id = buc.user_command_id + JOIN blocks AS b + ON buc.block_id = b.id + JOIN public_keys AS pk + ON uc.source_id = pk.id + WHERE uc.command_type = 'payment' + AND uc.source_id = uc.receiver_id + AND NOT b.chain_status = 'orphaned' + AND buc.status = 'applied' + AND b.timestamp::bigint BETWEEN $1 AND $2" + ); + let results = results.bind::(start_time).bind::(end_time).get_results(connection)?; + Ok(results) + } +} + +#[derive(QueryableByName)] +pub struct FetchChainTipResult { + #[diesel(sql_type = BigInt)] + pub max: i64, +} + +#[derive(QueryableByName)] +pub struct FetchLatestSlotResult { + #[diesel(sql_type = BigInt)] + pub max: i64, +} + +#[derive(QueryableByName)] +pub struct FetchTransactionResult { + #[diesel(sql_type = Text)] + pub account: String, + #[diesel(sql_type = Text)] + pub hash: String, + #[diesel(sql_type = Text)] + pub memo: String, + #[diesel(sql_type = BigInt)] + pub height: i64, + #[diesel(sql_type = ChainStatusType)] + pub status: BlockStatus, + #[diesel(sql_type = BigInt)] + pub timestamp: i64, + #[diesel(sql_type = BigInt)] + pub nonce: i64, +} diff --git a/server/src/bin/mina_ocv.rs b/server/src/bin/mina_ocv.rs new file mode 100644 index 000000000..41c79a41f --- /dev/null +++ b/server/src/bin/mina_ocv.rs @@ -0,0 +1,70 @@ +use anyhow::Result; +use axum::{ + debug_handler, + extract::{Path, State}, + response::{IntoResponse, Response}, + routing::get, + serve as axum_serve, Json, Router, +}; +use clap::Parser; +use mina_ocv::{shutdown_signal, Ocv, OcvConfig, Wrapper}; +use std::sync::Arc; +use tokio::net::TcpListener; +use tower_http::cors::CorsLayer; + +#[derive(Clone, Parser)] +struct ServeArgs { + /// API Host. + #[clap(long, env, default_value = "0.0.0.0")] + pub host: String, + /// API Port. + #[clap(long, env, default_value = "8080")] + pub port: u16, + /// OCV Args. + #[command(flatten)] + pub config: OcvConfig, +} + +#[tokio::main] +async fn main() -> Result<()> { + let ServeArgs { host, port, config } = ServeArgs::parse(); + tracing_subscriber::fmt::init(); + + let listener = TcpListener::bind(format!("{}:{}", host, port)).await?; + tracing::info!("Starting server at http://{}.", listener.local_addr()?); + + let ocv = config.to_ocv().await?; + let router = Router::new() + .route("/api/info", get(get_info)) + .route("/api/proposals", get(get_proposals)) + .route("/api/proposal/:id", get(get_proposal)) + .route("/api/proposal/:id/results", get(get_proposal_result)) + .layer(CorsLayer::permissive()) + .with_state(Arc::new(ocv)); + axum_serve(listener, router).with_graceful_shutdown(shutdown_signal()).await?; + Ok(()) +} + +#[debug_handler] +async fn get_info(ctx: State>) -> Response { + tracing::info!("get_info"); + Wrapper(ctx.info().await).into_response() +} + +#[debug_handler] +async fn get_proposals(ctx: State>) -> Response { + tracing::info!("get_proposals"); + Json(ctx.proposals_manifest.proposals.clone()).into_response() +} + +#[debug_handler] +async fn get_proposal(ctx: State>, Path(id): Path) -> Response { + tracing::info!("get_proposal {}", id); + Wrapper(ctx.proposal(id).await).into_response() +} + +#[debug_handler] +async fn get_proposal_result(ctx: State>, Path(id): Path) -> Response { + tracing::info!("get_proposal_result {}", id); + Wrapper(ctx.proposal_result(id).await).into_response() +} diff --git a/server/src/config.rs b/server/src/config.rs index 0aec908cc..ac012a9a2 100644 --- a/server/src/config.rs +++ b/server/src/config.rs @@ -1,108 +1,53 @@ -use crate::{ - database::{cache::CacheManager, DBConnectionManager}, - prelude::*, -}; -use axum::http::{HeaderValue, Method}; -use clap::{Parser, ValueEnum}; -use std::{collections::HashSet, fmt, sync::Arc}; -use tower_http::cors::{Any, CorsLayer}; -use tracing_subscriber::EnvFilter; +use crate::{Archive, Caches, Ocv, ProposalsManifest}; +use anyhow::Result; +use clap::{Args, Parser, ValueEnum}; +use std::fmt; -#[derive(Clone)] -pub(crate) struct Context { - pub(crate) cache: Arc, - pub(crate) conn_manager: Arc, - pub(crate) network: NetworkConfig, - pub(crate) ledger_storage_path: String, - pub(crate) bucket_name: String, -} - -#[derive(Clone, Copy, Parser, ValueEnum, Debug)] -pub(crate) enum NetworkConfig { - Mainnet, - Devnet, - Berkeley, -} - -impl fmt::Display for NetworkConfig { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - NetworkConfig::Mainnet => write!(f, "mainnet"), - NetworkConfig::Devnet => write!(f, "devnet"), - NetworkConfig::Berkeley => write!(f, "berkeley"), - } - } -} - -#[derive(Clone, Parser)] -pub(crate) struct Config { +#[derive(Clone, Args)] +pub struct OcvConfig { /// The mina network to connect to. #[clap(long, env)] - pub(crate) mina_network: NetworkConfig, - /// The connection URL for the application database. - #[clap(long, env)] - pub(crate) database_url: String, + pub mina_network: Network, + /// The URL from which the `proposals.json` should be fetched. + #[clap(long, env = "PROPOSALS_URL")] + pub maybe_proposals_url: Option, /// The connection URL for the archive database. #[clap(long, env)] - pub(crate) archive_database_url: String, - /// API Port. - #[clap(long, env, default_value_t = 8080)] - pub(crate) port: u16, - /// Origins allowed to make cross-site requests. - #[clap(long, env = "SERVER_ALLOWED_ORIGINS", value_parser = parse_allowed_origins )] - pub(crate) allowed_origins: HashSet, + pub archive_database_url: String, /// Set the name of the bucket containing the ledgers #[clap(long, env)] - pub(crate) bucket_name: String, + pub bucket_name: String, /// Path to store the ledgers #[clap(long, env, default_value = "/tmp/ledgers")] - pub(crate) ledger_storage_path: String, + pub ledger_storage_path: String, } -#[allow(clippy::unnecessary_wraps)] -fn parse_allowed_origins(arg: &str) -> Result> { - let allowed_origins = HashSet::from_iter( - arg - .split_whitespace() - .map(std::borrow::ToOwned::to_owned) - .collect::>(), - ); - - assert!( - !allowed_origins.is_empty(), - "failed to parse allowed_origins: {allowed_origins:?}" - ); - - Ok(allowed_origins) -} - -pub(crate) fn init_cors(cfg: &Config) -> CorsLayer { - let origins = cfg - .allowed_origins - .clone() - .into_iter() - .map(|origin| { - origin - .parse() - .unwrap_or_else(|_| panic!("Error: failed parsing allowed-origin {origin}")) +impl OcvConfig { + pub async fn to_ocv(&self) -> Result { + Ok(Ocv { + caches: Caches::build(), + archive: Archive::new(&self.archive_database_url), + network: self.mina_network, + ledger_storage_path: self.ledger_storage_path.clone(), + bucket_name: self.bucket_name.clone(), + proposals_manifest: ProposalsManifest::load(&self.maybe_proposals_url).await?, }) - .collect::>(); - - let layer = if cfg.allowed_origins.contains("*") { - CorsLayer::new().allow_origin(Any) - } else { - CorsLayer::new().allow_origin(origins) - }; + } +} - layer - .allow_methods([Method::GET, Method::POST, Method::OPTIONS]) - .allow_headers(Any) +#[derive(Clone, Copy, Parser, ValueEnum, Debug)] +pub enum Network { + Mainnet, + Devnet, + Berkeley, } -pub(crate) fn init_tracing() { - tracing_subscriber::fmt::Subscriber::builder() - .with_env_filter(EnvFilter::from_default_env()) - .with_writer(std::io::stderr) - .compact() - .init(); +impl fmt::Display for Network { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Network::Mainnet => write!(f, "mainnet"), + Network::Devnet => write!(f, "devnet"), + Network::Berkeley => write!(f, "berkeley"), + } + } } diff --git a/server/src/database/archive.rs b/server/src/database/archive.rs deleted file mode 100644 index 9af676659..000000000 --- a/server/src/database/archive.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::{ - database::DBConnectionManager, - models::vote::{ChainStatusType, MinaBlockStatus}, - prelude::*, -}; -use anyhow::Context; -use diesel::{ - sql_query, - sql_types::{BigInt, Text}, - QueryableByName, RunQueryDsl, -}; - -#[derive(QueryableByName)] -pub(crate) struct FetchChainTipResult { - #[diesel(sql_type = BigInt)] - pub(crate) max: i64, -} - -pub(crate) fn fetch_chain_tip(conn_manager: &DBConnectionManager) -> Result { - let connection = &mut conn_manager - .archive - .get() - .context("failed to get archive db connection")?; - - let result = sql_query("SELECT MAX(height) FROM blocks").get_result::(connection)?; - Ok(result.max) -} - -#[derive(QueryableByName)] -pub(crate) struct FetchLatestSlotResult { - #[diesel(sql_type = BigInt)] - pub(crate) max: i64, -} - -pub(crate) fn fetch_latest_slot(conn_manager: &DBConnectionManager) -> Result { - let connection = &mut conn_manager - .archive - .get() - .context("failed to get archive db connection")?; - - let result = sql_query("SELECT MAX(global_slot) FROM blocks").get_result::(connection)?; - Ok(result.max) -} - -#[derive(QueryableByName)] -pub(crate) struct FetchTransactionResult { - #[diesel(sql_type = Text)] - pub(crate) account: String, - #[diesel(sql_type = Text)] - pub(crate) hash: String, - #[diesel(sql_type = Text)] - pub(crate) memo: String, - #[diesel(sql_type = BigInt)] - pub(crate) height: i64, - #[diesel(sql_type = ChainStatusType)] - pub(crate) status: MinaBlockStatus, - #[diesel(sql_type = BigInt)] - pub(crate) timestamp: i64, - #[diesel(sql_type = BigInt)] - pub(crate) nonce: i64, -} - -pub(crate) fn fetch_transactions( - conn_manager: &DBConnectionManager, - start_time: i64, - end_time: i64, -) -> Result> { - let connection = &mut conn_manager - .archive - .get() - .context("failed to get archive db connection")?; - - let results = sql_query( - "SELECT DISTINCT pk.value as account, uc.memo as memo, uc.nonce as nonce, uc.hash as hash, b.height as height, b.chain_status as status, b.timestamp::bigint as timestamp - FROM user_commands AS uc - JOIN blocks_user_commands AS buc - ON uc.id = buc.user_command_id - JOIN blocks AS b - ON buc.block_id = b.id - JOIN public_keys AS pk - ON uc.source_id = pk.id - WHERE uc.command_type = 'payment' - AND uc.source_id = uc.receiver_id - AND NOT b.chain_status = 'orphaned' - AND buc.status = 'applied' - AND b.timestamp::bigint BETWEEN $1 AND $2" - ); - let results = results - .bind::(start_time) - .bind::(end_time) - .get_results(connection)?; - Ok(results) -} diff --git a/server/src/database/cache.rs b/server/src/database/cache.rs deleted file mode 100644 index 31313ee46..000000000 --- a/server/src/database/cache.rs +++ /dev/null @@ -1,36 +0,0 @@ -use crate::models::{ - ledger::LedgerAccount, - vote::{MinaVote, MinaVoteWithWeight}, -}; -use moka::future::Cache as MokaCache; -use std::sync::Arc; - -type ArcVotes = Arc>; -type ArcVotesWeighted = Arc>; -type ArcLedger = Arc>; - -pub(crate) type VotesCache = MokaCache; -pub(crate) type VotesWeightedCache = MokaCache; -pub(crate) type LedgerCache = MokaCache; - -pub(crate) struct CacheManager { - pub(crate) votes: VotesCache, - pub(crate) votes_weighted: VotesWeightedCache, - pub(crate) ledger: LedgerCache, -} - -impl CacheManager { - pub(crate) fn build() -> CacheManager { - CacheManager { - votes: VotesCache::builder() - .time_to_live(std::time::Duration::from_secs(60 * 5)) - .build(), - votes_weighted: VotesWeightedCache::builder() - .time_to_live(std::time::Duration::from_secs(60 * 5)) - .build(), - ledger: LedgerCache::builder() - .time_to_live(std::time::Duration::from_secs(60 * 60 * 12)) - .build(), - } - } -} diff --git a/server/src/database/mod.rs b/server/src/database/mod.rs deleted file mode 100644 index b1d60a8e2..000000000 --- a/server/src/database/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -pub(crate) mod archive; -pub(crate) mod cache; - -use crate::config::Config; -use diesel::{ - prelude::*, - r2d2::{ConnectionManager, Pool}, -}; - -pub(crate) type PgConnectionPool = Pool>; - -pub(crate) struct DBConnectionManager { - pub(crate) main: PgConnectionPool, - pub(crate) archive: PgConnectionPool, -} - -impl DBConnectionManager { - pub(crate) fn get_connections(cfg: &Config) -> DBConnectionManager { - let main_manager = ConnectionManager::::new(&cfg.database_url); - let archive_manager = ConnectionManager::::new(&cfg.archive_database_url); - - DBConnectionManager { - main: Pool::builder() - .test_on_check_out(true) - .build(main_manager) - .unwrap_or_else(|_| panic!("Error: failed to build `main` connection pool")), - - archive: Pool::builder() - .test_on_check_out(true) - .build(archive_manager) - .unwrap_or_else(|_| panic!("Error: failed to build `archive` connection pool")), - } - } -} diff --git a/server/src/error.rs b/server/src/error.rs deleted file mode 100644 index bfb5ab00e..000000000 --- a/server/src/error.rs +++ /dev/null @@ -1,42 +0,0 @@ -use axum::http::StatusCode; -use axum::response::{IntoResponse, Response}; - -use crate::MINA_GOVERNANCE_SERVER; - -#[derive(thiserror::Error, Debug)] -pub enum Error { - #[error(transparent)] - Diesel(#[from] diesel::result::Error), - - #[error(transparent)] - Anyhow(#[from] anyhow::Error), -} - -impl Error { - fn status_code(&self) -> StatusCode { - match self { - Self::Diesel(ref error) => match error { - diesel::result::Error::NotFound => StatusCode::NOT_FOUND, - _ => StatusCode::INTERNAL_SERVER_ERROR, - }, - - Self::Anyhow(_) => StatusCode::INTERNAL_SERVER_ERROR, - } - } -} - -impl IntoResponse for Error { - fn into_response(self) -> Response { - match self { - Self::Diesel(ref error) => { - tracing::error!(target: MINA_GOVERNANCE_SERVER, "Error: {error}"); - } - - Self::Anyhow(ref error) => { - tracing::error!(target: MINA_GOVERNANCE_SERVER, "Error: {error}"); - } - } - - (self.status_code(), self.to_string()).into_response() - } -} diff --git a/server/src/models/ledger.rs b/server/src/ledger.rs similarity index 70% rename from server/src/models/ledger.rs rename to server/src/ledger.rs index a3e608f93..e700bbf10 100644 --- a/server/src/models/ledger.rs +++ b/server/src/ledger.rs @@ -1,41 +1,28 @@ -use crate::{ - config::NetworkConfig, - models::{diesel::ProposalVersion, vote::MinaVote}, - prelude::*, -}; -use anyhow::anyhow; +use crate::{Network, ProposalVersion, Vote, Wrapper}; +use anyhow::{anyhow, Result}; use rust_decimal::Decimal; use serde::{Deserialize, Serialize}; use std::{collections::HashMap, io::Read, path::Path}; -const LEDGER_BALANCE_SCALE: u32 = 9; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -pub(crate) struct Ledger(pub(crate) Vec); - #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] -#[serde(rename_all = "camelCase")] -pub(crate) struct LedgerAccount { - pub(crate) pk: String, - pub(crate) balance: String, - pub(crate) delegate: Option, -} +pub struct Ledger(pub Vec); impl Ledger { - pub(crate) async fn fetch( + pub async fn fetch( hash: impl Into, ledger_storage_path: String, - network: NetworkConfig, + network: Network, bucket_name: String, epoch: i64, ) -> Result { let hash: String = hash.into(); - let ledger_file_path = f!("{ledger_storage_path}/{network}-{epoch}-{hash}.json"); + let ledger_file_path = format!("{ledger_storage_path}/{network}-{epoch}-{hash}.json"); if !Path::new(&ledger_file_path).exists() { if !Path::new(&ledger_storage_path).exists() { let _ = std::fs::create_dir_all(ledger_storage_path.clone()); } - let url = f!("https://{bucket_name}.s3.us-west-2.amazonaws.com/{network}/{network}-{epoch}-{hash}.json.tar.gz"); + let url = + format!("https://{bucket_name}.s3.us-west-2.amazonaws.com/{network}/{network}-{epoch}-{hash}.json.tar.gz"); tracing::info!("Ledger path not found, downloading {} to {}", url, ledger_file_path); let response = reqwest::get(url).await.unwrap(); if response.status().is_success() { @@ -48,31 +35,22 @@ impl Ledger { } let mut bytes = Vec::new(); println!("Trying to access: {}", ledger_file_path); - std::fs::File::open(ledger_file_path) - .unwrap() - .read_to_end(&mut bytes) - .unwrap(); + std::fs::File::open(ledger_file_path).unwrap().read_to_end(&mut bytes).unwrap(); Ok(Ledger(serde_json::from_slice(&bytes).unwrap())) } - pub(crate) fn get_stake_weight( + pub fn get_stake_weight( &self, - map: &Wrapper>, + map: &Wrapper>, version: &ProposalVersion, public_key: impl Into, ) -> Result { let public_key: String = public_key.into(); - let account = self - .0 - .iter() - .find(|d| d.pk == public_key) - .ok_or_else(|| anyhow!("account {public_key} not found in ledger"))?; + let account = + self.0.iter().find(|d| d.pk == public_key).ok_or_else(|| anyhow!("account {public_key} not found in ledger"))?; - let balance = account - .balance - .parse() - .unwrap_or_else(|_| Decimal::new(0, LEDGER_BALANCE_SCALE)); + let balance = account.balance.parse().unwrap_or_else(|_| Decimal::new(0, LEDGER_BALANCE_SCALE)); match version { ProposalVersion::V1 => { @@ -91,10 +69,7 @@ impl Ledger { } let stake_weight = delegators.iter().fold(Decimal::new(0, LEDGER_BALANCE_SCALE), |acc, x| { - x.balance - .parse() - .unwrap_or_else(|_| Decimal::new(0, LEDGER_BALANCE_SCALE)) - + acc + x.balance.parse().unwrap_or_else(|_| Decimal::new(0, LEDGER_BALANCE_SCALE)) + acc }); Ok(stake_weight + balance) @@ -113,10 +88,7 @@ impl Ledger { } let stake_weight = delegators.iter().fold(Decimal::new(0, LEDGER_BALANCE_SCALE), |acc, x| { - x.balance - .parse() - .unwrap_or_else(|_| Decimal::new(0, LEDGER_BALANCE_SCALE)) - + acc + x.balance.parse().unwrap_or_else(|_| Decimal::new(0, LEDGER_BALANCE_SCALE)) + acc }); Ok(stake_weight + balance) @@ -125,62 +97,26 @@ impl Ledger { } } -#[cfg(test)] -mod tests { - use super::*; - - impl LedgerAccount { - pub fn new(pk: String, balance: String, delegate: Option) -> LedgerAccount { - LedgerAccount { pk, balance, delegate } - } - } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] +#[serde(rename_all = "camelCase")] +pub struct LedgerAccount { + pub pk: String, + pub balance: String, + pub delegate: Option, +} - fn get_accounts() -> ( - LedgerAccount, - LedgerAccount, - LedgerAccount, - LedgerAccount, - LedgerAccount, - ) { - ( - LedgerAccount::new("A".to_string(), "1".to_string(), None), - LedgerAccount::new("B".to_string(), "1".to_string(), None), - LedgerAccount::new("C".to_string(), "1".to_string(), Some("A".to_string())), - LedgerAccount::new("D".to_string(), "1".to_string(), Some("A".to_string())), - LedgerAccount::new("E".to_string(), "1".to_string(), Some("B".to_string())), - ) +impl LedgerAccount { + pub fn new(pk: String, balance: String, delegate: Option) -> LedgerAccount { + LedgerAccount { pk, balance, delegate } } +} - fn get_votes() -> HashMap { - let mut map = HashMap::new(); - map.insert( - "B".to_string(), - MinaVote::new( - "B".to_string(), - "", - "", - 1, - crate::models::vote::MinaBlockStatus::Canonical, - 1, - 0, - ), - ); - - map.insert( - "C".to_string(), - MinaVote::new( - "C".to_string(), - "", - "", - 1, - crate::models::vote::MinaBlockStatus::Canonical, - 1, - 0, - ), - ); +pub const LEDGER_BALANCE_SCALE: u32 = 9; - map - } +#[cfg(test)] +mod tests { + use super::*; + use crate::BlockStatus; #[test] fn test_stake_weight_v1() { @@ -194,7 +130,7 @@ mod tests { &ProposalVersion::V1, "E", ); - assert_eq!(error.is_err(), true); + assert!(error.is_err()); // Delegated stake away - returns 0.000000000. let d_weight = Ledger::get_stake_weight( @@ -237,7 +173,7 @@ mod tests { &ProposalVersion::V2, "F", ); - assert_eq!(error.is_err(), true); + assert!(error.is_err()); let a_weight = Ledger::get_stake_weight( &Ledger(vec![a.clone(), b.clone(), c.clone(), d.clone(), e.clone()]), @@ -256,4 +192,21 @@ mod tests { assert_eq!(b_weight.unwrap(), Decimal::new(2000000000, LEDGER_BALANCE_SCALE)); } + + fn get_accounts() -> (LedgerAccount, LedgerAccount, LedgerAccount, LedgerAccount, LedgerAccount) { + ( + LedgerAccount::new("A".to_string(), "1".to_string(), None), + LedgerAccount::new("B".to_string(), "1".to_string(), None), + LedgerAccount::new("C".to_string(), "1".to_string(), Some("A".to_string())), + LedgerAccount::new("D".to_string(), "1".to_string(), Some("A".to_string())), + LedgerAccount::new("E".to_string(), "1".to_string(), Some("B".to_string())), + ) + } + + fn get_votes() -> HashMap { + let mut map = HashMap::new(); + map.insert("B".to_string(), Vote::new("B".to_string(), "", "", 1, BlockStatus::Canonical, 1, 0)); + map.insert("C".to_string(), Vote::new("C".to_string(), "", "", 1, BlockStatus::Canonical, 1, 0)); + map + } } diff --git a/server/src/lib.rs b/server/src/lib.rs new file mode 100644 index 000000000..b8187657b --- /dev/null +++ b/server/src/lib.rs @@ -0,0 +1,15 @@ +mod archive; +mod config; +mod ledger; +mod ocv; +mod proposals; +mod util; +mod vote; + +pub use archive::*; +pub use config::*; +pub use ledger::*; +pub use ocv::*; +pub use proposals::*; +pub use util::*; +pub use vote::*; diff --git a/server/src/main.rs b/server/src/main.rs deleted file mode 100644 index df912b7aa..000000000 --- a/server/src/main.rs +++ /dev/null @@ -1,97 +0,0 @@ -mod config; -mod database; -mod error; -mod models; -mod prelude; -mod routes; -mod schema; - -use crate::{ - config::{Config, Context}, - database::{cache::CacheManager, DBConnectionManager}, - prelude::*, - routes::Build, -}; -use axum::Extension; -use clap::Parser; -use std::{net::SocketAddr, sync::Arc}; -use tokio::signal; -use tower::ServiceBuilder; -use tower_http::trace::TraceLayer; - -extern crate tracing; - -pub(crate) const MINA_GOVERNANCE_SERVER: &str = "mina_governance_server"; - -#[tokio::main] -async fn main() -> Result<()> { - config::init_tracing(); - - let config = Config::parse(); - let cache = CacheManager::build(); - let cors = config::init_cors(&config); - - tracing::info!( - target: MINA_GOVERNANCE_SERVER, - "Initializing database connection pools..." - ); - - let conn_manager = DBConnectionManager::get_connections(&config); - - let router = axum::Router::build().layer( - ServiceBuilder::new() - .layer(TraceLayer::new_for_http()) - .layer(cors) - .layer(Extension(Context { - cache: Arc::new(cache), - conn_manager: Arc::new(conn_manager), - network: config.mina_network, - ledger_storage_path: config.ledger_storage_path, - bucket_name: config.bucket_name, - })), - ); - - serve(router.clone(), config.port).await; - Ok(()) -} - -async fn serve(router: axum::Router, port: u16) { - let addr = SocketAddr::from(([0, 0, 0, 0], port)); - - tracing::info!( - target: MINA_GOVERNANCE_SERVER, - "Started server on {addr} - http://{addr}" - ); - - axum::Server::bind(&addr) - .serve(router.into_make_service()) - .with_graceful_shutdown(shutdown()) - .await - .expect("Error: failed to start axum runtime"); -} - -async fn shutdown() { - let windows = async { - signal::ctrl_c() - .await - .unwrap_or_else(|_| panic!("Error: failed to install windows shutdown handler")); - }; - - #[cfg(unix)] - let unix = async { - signal::unix::signal(signal::unix::SignalKind::terminate()) - .unwrap_or_else(|_| panic!("Error: failed to install unix shutdown handler")) - .recv() - .await; - }; - - #[cfg(not(unix))] - let terminate = std::future::pending::<()>(); - - tokio::select! { - () = windows => {}, - () = unix => {}, - } - - println!("Signal received - starting graceful shutdown..."); -} diff --git a/server/src/models/diesel.rs b/server/src/models/diesel.rs deleted file mode 100644 index 5a47bacf2..000000000 --- a/server/src/models/diesel.rs +++ /dev/null @@ -1,38 +0,0 @@ -use diesel::Queryable; -use diesel_derive_enum::DbEnum; -use serde::{Deserialize, Serialize}; - -#[derive(Serialize, Deserialize, Debug, DbEnum)] -#[ExistingTypePath = "crate::schema::sql_types::ProposalVersion"] -#[DbValueStyle = "SCREAMING_SNAKE_CASE"] -pub(crate) enum ProposalVersion { - V1, - V2, -} - -#[allow(clippy::upper_case_acronyms)] -#[derive(Serialize, Deserialize, Debug, DbEnum)] -#[ExistingTypePath = "crate::schema::sql_types::ProposalCategory"] -#[DbValueStyle = "verbatim"] -pub(crate) enum ProposalCategory { - Core, - Networking, - Interface, - ERC, - Cryptography, -} - -#[derive(Queryable, Serialize, Deserialize, Debug)] -pub(crate) struct MinaProposal { - pub(crate) id: i32, - pub(crate) key: String, - pub(crate) start_time: i64, - pub(crate) end_time: i64, - pub(crate) epoch: i64, - pub(crate) ledger_hash: Option, - pub(crate) category: ProposalCategory, - pub(crate) version: ProposalVersion, - pub(crate) title: String, - pub(crate) description: String, - pub(crate) url: String, -} diff --git a/server/src/models/mod.rs b/server/src/models/mod.rs deleted file mode 100644 index 436f1214f..000000000 --- a/server/src/models/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub(crate) mod diesel; -pub(crate) mod ledger; -pub(crate) mod vote; diff --git a/server/src/ocv.rs b/server/src/ocv.rs new file mode 100644 index 000000000..ccac4505a --- /dev/null +++ b/server/src/ocv.rs @@ -0,0 +1,135 @@ +use crate::{util::Caches, Archive, Ledger, Network, Proposal, ProposalsManifest, Vote, VoteWithWeight, Wrapper}; +use anyhow::Result; +use rust_decimal::Decimal; +use serde::Serialize; +use std::sync::Arc; + +#[derive(Clone)] +pub struct Ocv { + pub caches: Caches, + pub archive: Archive, + pub network: Network, + pub ledger_storage_path: String, + pub bucket_name: String, + pub proposals_manifest: ProposalsManifest, +} + +impl Ocv { + pub async fn info(&self) -> Result { + let chain_tip = self.archive.fetch_chain_tip()?; + let current_slot = self.archive.fetch_latest_slot()?; + Ok(GetCoreApiInfoResponse { chain_tip, current_slot }) + } + + pub async fn proposal(&self, id: usize) -> Result { + let proposal = self.proposals_manifest.proposal(id)?; + + if let Some(cached) = self.caches.votes.get(&proposal.key).await { + return Ok(ProposalResponse { proposal, votes: cached.to_vec() }); + } + + let transactions = self.archive.fetch_transactions(proposal.start_time, proposal.end_time)?; + + let chain_tip = self.archive.fetch_chain_tip()?; + + let votes = Wrapper(transactions.into_iter().map(std::convert::Into::into).collect()) + .process(&proposal.key, chain_tip) + .sort_by_timestamp() + .to_vec() + .0; + + self.caches.votes.insert(proposal.key.clone(), Arc::new(votes.clone())).await; + + Ok(ProposalResponse { proposal, votes }) + } + + pub async fn proposal_result(&self, id: usize) -> Result { + let proposal = self.proposals_manifest.proposal(id)?; + if proposal.ledger_hash.is_none() { + return Ok(GetMinaProposalResultResponse { + proposal, + total_stake_weight: Decimal::ZERO, + positive_stake_weight: Decimal::ZERO, + negative_stake_weight: Decimal::ZERO, + votes: Vec::new(), + }); + } + let hash = proposal.ledger_hash.clone().expect("hash should always be present"); + + let votes = if let Some(cached_votes) = self.caches.votes_weighted.get(&proposal.key).await { + cached_votes.to_vec() + } else { + let transactions = self.archive.fetch_transactions(proposal.start_time, proposal.end_time)?; + + let chain_tip = self.archive.fetch_chain_tip()?; + + let ledger = if let Some(cached_ledger) = self.caches.ledger.get(&hash).await { + Ledger(cached_ledger.to_vec()) + } else { + let ledger = Ledger::fetch( + &hash, + self.ledger_storage_path.clone(), + self.network, + self.bucket_name.clone(), + proposal.epoch, + ) + .await?; + + self.caches.ledger.insert(hash, Arc::new(ledger.0.clone())).await; + + ledger + }; + + let votes = Wrapper(transactions.into_iter().map(std::convert::Into::into).collect()) + .into_weighted(&proposal, &ledger, chain_tip) + .sort_by_timestamp() + .0; + + self.caches.votes_weighted.insert(proposal.key.clone(), Arc::new(votes.clone())).await; + + votes + }; + + let mut positive_stake_weight = Decimal::from(0); + let mut negative_stake_weight = Decimal::from(0); + + for vote in &votes { + if vote.memo.split_whitespace().next().eq(&Some("no")) { + negative_stake_weight += vote.weight; + } else { + positive_stake_weight += vote.weight; + } + } + + Ok(GetMinaProposalResultResponse { + proposal, + total_stake_weight: positive_stake_weight + negative_stake_weight, + positive_stake_weight, + negative_stake_weight, + votes, + }) + } +} + +#[derive(Serialize)] +pub struct GetCoreApiInfoResponse { + chain_tip: i64, + current_slot: i64, +} + +#[derive(Serialize)] +pub struct ProposalResponse { + #[serde(flatten)] + proposal: Proposal, + votes: Vec, +} + +#[derive(Serialize)] +pub struct GetMinaProposalResultResponse { + #[serde(flatten)] + proposal: Proposal, + total_stake_weight: Decimal, + positive_stake_weight: Decimal, + negative_stake_weight: Decimal, + votes: Vec, +} diff --git a/server/src/prelude.rs b/server/src/prelude.rs deleted file mode 100644 index 0c0a83a2c..000000000 --- a/server/src/prelude.rs +++ /dev/null @@ -1,25 +0,0 @@ -pub(crate) use crate::error::Error; -pub(crate) type Result = std::result::Result; - -pub(crate) use std::format as f; - -/// Wrapper around a type `T` that can be used to implement external traits for `T`. -/// -/// # Examples -/// -/// ``` -/// use std::convert::From; -/// -/// struct Wrapper(pub T); -/// -/// impl From for Wrapper { -/// fn from(t: T) -> Self { -/// Wrapper(t) -/// } -/// } -/// -/// -/// let value = Wrapper::from(42); -/// assert_eq!(value.0, 42); -/// ``` -pub(crate) struct Wrapper(pub T); diff --git a/server/src/proposals.rs b/server/src/proposals.rs new file mode 100644 index 000000000..92561eeff --- /dev/null +++ b/server/src/proposals.rs @@ -0,0 +1,58 @@ +use anyhow::{anyhow, Result}; +use bytes::Bytes; +use serde::{Deserialize, Serialize}; + +#[derive(Deserialize, Debug, Clone)] +pub struct ProposalsManifest { + pub proposals: Vec, +} + +static PROPOSALS_MANIFEST_GITHUB_URL: &str = + "https://raw.githubusercontent.com/MinaFoundation/mina-on-chain-voting/main/server/proposals/proposals.json"; + +impl ProposalsManifest { + pub async fn load(maybe_proposals_url: &Option) -> Result { + let manifest_bytes = match maybe_proposals_url { + Some(url) => { + let url = if url.is_empty() { &PROPOSALS_MANIFEST_GITHUB_URL.to_string() } else { url }; + reqwest::Client::new().get(url).send().await?.bytes().await? + } + None => Bytes::from_static(include_bytes!("../proposals/proposals.json")), + }; + Ok(serde_json::from_slice(manifest_bytes.as_ref())?) + } + + pub fn proposal(&self, id: usize) -> Result { + self.proposals.get(id).cloned().ok_or(anyhow!("Could not retrieve proposal with ID {}", id)) + } +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct Proposal { + pub id: usize, + pub key: String, + pub start_time: i64, + pub end_time: i64, + pub epoch: i64, + pub ledger_hash: Option, + pub category: ProposalCategory, + pub version: ProposalVersion, + pub title: String, + pub description: String, + pub url: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ProposalCategory { + Core, + Networking, + Interface, + ERC, + Cryptography, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub enum ProposalVersion { + V1, + V2, +} diff --git a/server/src/routes/info.rs b/server/src/routes/info.rs deleted file mode 100644 index 16df43a7f..000000000 --- a/server/src/routes/info.rs +++ /dev/null @@ -1,29 +0,0 @@ -use crate::{ - database::archive::{fetch_chain_tip, fetch_latest_slot}, - prelude::*, -}; -use axum::{http::StatusCode, response::IntoResponse, routing::get, Extension, Json, Router}; -use serde::{Deserialize, Serialize}; - -pub(crate) fn router() -> Router { - Router::new().route("/api/info", get(get_core_api_info)) -} - -#[derive(Serialize, Deserialize)] -struct GetCoreApiInfoResponse { - chain_tip: i64, - current_slot: i64, -} - -#[allow(clippy::unused_async)] -async fn get_core_api_info(ctx: Extension) -> Result { - let chain_tip = fetch_chain_tip(&ctx.conn_manager)?; - let current_slot = fetch_latest_slot(&ctx.conn_manager)?; - - let response = GetCoreApiInfoResponse { - chain_tip, - current_slot, - }; - - Ok((StatusCode::OK, Json(response)).into_response()) -} diff --git a/server/src/routes/mod.rs b/server/src/routes/mod.rs deleted file mode 100644 index eb97ec3d6..000000000 --- a/server/src/routes/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod info; -mod proposal; - -use axum::Router; - -pub(crate) trait Build { - fn build() -> Router; -} - -impl Build for Router { - fn build() -> Router { - proposal::router().merge(info::router()) - } -} diff --git a/server/src/routes/proposal.rs b/server/src/routes/proposal.rs deleted file mode 100644 index 5bc524b50..000000000 --- a/server/src/routes/proposal.rs +++ /dev/null @@ -1,180 +0,0 @@ -use crate::{ - database::archive::{fetch_chain_tip, fetch_transactions}, - models::{ - diesel::MinaProposal, - ledger::Ledger, - vote::{MinaVote, MinaVoteWithWeight}, - }, - prelude::*, -}; -use anyhow::Context; -use axum::{extract::Path, http::StatusCode, response::IntoResponse, routing::get, Extension, Json, Router}; -use diesel::prelude::*; -use rust_decimal::Decimal; -use serde::{Deserialize, Serialize}; -use std::sync::Arc; - -pub(crate) fn router() -> Router { - Router::new() - .route("/api/proposals", get(get_mina_proposals)) - .route("/api/proposal/:id", get(get_mina_proposal)) - .route("/api/proposal/:id/results", get(get_mina_proposal_result)) -} - -#[allow(clippy::unused_async)] -async fn get_mina_proposals(ctx: Extension) -> Result { - use crate::schema::mina_proposals::dsl as mina_proposal_dsl; - - let conn = &mut ctx - .conn_manager - .main - .get() - .context("failed to get primary db connection")?; - - let proposals: Vec = mina_proposal_dsl::mina_proposals - .order(mina_proposal_dsl::id.desc()) - .load(conn)?; - - Ok((StatusCode::OK, Json(proposals)).into_response()) -} - -#[derive(Serialize, Deserialize)] -struct GetMinaProposalResponse { - #[serde(flatten)] - proposal: MinaProposal, - votes: Vec, -} - -async fn get_mina_proposal(ctx: Extension, Path(id): Path) -> Result { - use crate::schema::mina_proposals::dsl as mina_proposal_dsl; - - let conn = &mut ctx - .conn_manager - .main - .get() - .context("failed to get primary db connection")?; - - let proposal: MinaProposal = mina_proposal_dsl::mina_proposals.find(id).first(conn)?; - - if let Some(cached) = ctx.cache.votes.get(&proposal.key).await { - let response = GetMinaProposalResponse { - proposal, - votes: cached.to_vec(), - }; - - return Ok((StatusCode::OK, Json(response)).into_response()); - } - - let transactions = fetch_transactions(&ctx.conn_manager, proposal.start_time, proposal.end_time)?; - - let chain_tip = fetch_chain_tip(&ctx.conn_manager)?; - - let votes = Wrapper(transactions.into_iter().map(std::convert::Into::into).collect()) - .process(&proposal.key, chain_tip) - .sort_by_timestamp() - .to_vec() - .0; - - ctx - .cache - .votes - .insert(proposal.key.clone(), Arc::new(votes.clone())) - .await; - - let response = GetMinaProposalResponse { proposal, votes }; - - Ok((StatusCode::OK, Json(response)).into_response()) -} - -#[derive(Serialize, Deserialize)] -struct GetMinaProposalResultResponse { - #[serde(flatten)] - proposal: MinaProposal, - total_stake_weight: Decimal, - positive_stake_weight: Decimal, - negative_stake_weight: Decimal, - votes: Vec, -} - -async fn get_mina_proposal_result(ctx: Extension, Path(id): Path) -> Result { - use crate::schema::mina_proposals::dsl as mina_proposal_dsl; - - let conn = &mut ctx - .conn_manager - .main - .get() - .context("failed to get primary db connection")?; - - let proposal: MinaProposal = mina_proposal_dsl::mina_proposals.find(id).first(conn)?; - - if proposal.ledger_hash.is_none() { - let response = GetMinaProposalResultResponse { - proposal, - total_stake_weight: Decimal::ZERO, - positive_stake_weight: Decimal::ZERO, - negative_stake_weight: Decimal::ZERO, - votes: Vec::new(), - }; - return Ok((StatusCode::OK, Json(response)).into_response()); - } - let hash = proposal.ledger_hash.clone().expect("hash should always be present"); - - let votes = if let Some(cached_votes) = ctx.cache.votes_weighted.get(&proposal.key).await { - cached_votes.to_vec() - } else { - let transactions = fetch_transactions(&ctx.conn_manager, proposal.start_time, proposal.end_time)?; - - let chain_tip = fetch_chain_tip(&ctx.conn_manager)?; - - let ledger = if let Some(cached_ledger) = ctx.cache.ledger.get(&hash).await { - Ledger(cached_ledger.to_vec()) - } else { - let ledger = Ledger::fetch( - &hash, - ctx.ledger_storage_path.clone(), - ctx.network, - ctx.bucket_name.clone(), - proposal.epoch, - ) - .await?; - - ctx.cache.ledger.insert(hash, Arc::new(ledger.0.clone())).await; - - ledger - }; - - let votes = Wrapper(transactions.into_iter().map(std::convert::Into::into).collect()) - .into_weighted(&proposal, &ledger, chain_tip) - .sort_by_timestamp() - .0; - - ctx - .cache - .votes_weighted - .insert(proposal.key.clone(), Arc::new(votes.clone())) - .await; - - votes - }; - - let mut positive_stake_weight = Decimal::from(0); - let mut negative_stake_weight = Decimal::from(0); - - for vote in &votes { - if vote.memo.split_whitespace().next().eq(&Some("no")) { - negative_stake_weight += vote.weight; - } else { - positive_stake_weight += vote.weight; - } - } - - let response = GetMinaProposalResultResponse { - proposal, - total_stake_weight: positive_stake_weight + negative_stake_weight, - positive_stake_weight, - negative_stake_weight, - votes, - }; - - Ok((StatusCode::OK, Json(response)).into_response()) -} diff --git a/server/src/schema.rs b/server/src/schema.rs deleted file mode 100644 index fb172bd7b..000000000 --- a/server/src/schema.rs +++ /dev/null @@ -1,31 +0,0 @@ -// @generated automatically by Diesel CLI. - -pub mod sql_types { - #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "proposal_category"))] - pub struct ProposalCategory; - - #[derive(diesel::sql_types::SqlType)] - #[diesel(postgres_type(name = "proposal_version"))] - pub struct ProposalVersion; -} - -diesel::table! { - use diesel::sql_types::{Int4, Int8, Nullable, Text}; - use super::sql_types::ProposalCategory; - use super::sql_types::ProposalVersion; - - mina_proposals (id) { - id -> Int4, - key -> Text, - start_time -> Int8, - end_time -> Int8, - epoch -> Int8, - ledger_hash -> Nullable, - category -> ProposalCategory, - version -> ProposalVersion, - title -> Text, - description -> Text, - url -> Text, - } -} diff --git a/server/src/util.rs b/server/src/util.rs new file mode 100644 index 000000000..10db0e4d5 --- /dev/null +++ b/server/src/util.rs @@ -0,0 +1,7 @@ +mod caches; +mod shutdown_signal; +mod wrapper; + +pub use caches::Caches; +pub use shutdown_signal::shutdown_signal; +pub use wrapper::Wrapper; diff --git a/server/src/util/caches.rs b/server/src/util/caches.rs new file mode 100644 index 000000000..96eae7ec9 --- /dev/null +++ b/server/src/util/caches.rs @@ -0,0 +1,20 @@ +use crate::{ledger::LedgerAccount, Vote, VoteWithWeight}; +use moka::future::Cache as MokaCache; +use std::sync::Arc; + +#[derive(Clone)] +pub struct Caches { + pub votes: MokaCache>>, + pub votes_weighted: MokaCache>>, + pub ledger: MokaCache>>, +} + +impl Caches { + pub fn build() -> Self { + Self { + votes: MokaCache::builder().time_to_live(std::time::Duration::from_secs(60 * 5)).build(), + votes_weighted: MokaCache::builder().time_to_live(std::time::Duration::from_secs(60 * 5)).build(), + ledger: MokaCache::builder().time_to_live(std::time::Duration::from_secs(60 * 60 * 12)).build(), + } + } +} diff --git a/server/src/util/shutdown_signal.rs b/server/src/util/shutdown_signal.rs new file mode 100644 index 000000000..5eba7134f --- /dev/null +++ b/server/src/util/shutdown_signal.rs @@ -0,0 +1,25 @@ +use tokio::{select, signal}; + +pub async fn shutdown_signal() { + let windows = async { + signal::ctrl_c().await.unwrap_or_else(|_| panic!("Error: failed to install windows shutdown handler")); + }; + + #[cfg(unix)] + let unix = async { + signal::unix::signal(signal::unix::SignalKind::terminate()) + .unwrap_or_else(|_| panic!("Error: failed to install unix shutdown handler")) + .recv() + .await; + }; + + #[cfg(not(unix))] + let terminate = std::future::pending::<()>(); + + select! { + () = windows => {}, + () = unix => {}, + } + + println!("Signal received - starting graceful shutdown..."); +} diff --git a/server/src/util/wrapper.rs b/server/src/util/wrapper.rs new file mode 100644 index 000000000..9514ef713 --- /dev/null +++ b/server/src/util/wrapper.rs @@ -0,0 +1,18 @@ +use anyhow::Result; +use axum::{ + extract::Json, + http::StatusCode, + response::{IntoResponse, Response}, +}; +use serde::Serialize; + +pub struct Wrapper(pub T); + +impl IntoResponse for Wrapper> { + fn into_response(self) -> Response { + match self.0 { + Ok(v) => Json(v).into_response(), + Err(e) => (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()).into_response(), + } + } +} diff --git a/server/src/models/vote.rs b/server/src/vote.rs similarity index 54% rename from server/src/models/vote.rs rename to server/src/vote.rs index cffb338ad..0c4b72b10 100644 --- a/server/src/models/vote.rs +++ b/server/src/vote.rs @@ -1,9 +1,5 @@ -use crate::{ - database::archive::FetchTransactionResult, - models::{diesel::MinaProposal, ledger::Ledger}, - prelude::*, -}; -use anyhow::Context; +use crate::{archive::FetchTransactionResult, ledger::Ledger, Proposal, Wrapper}; +use anyhow::{Context, Result}; use diesel::SqlType; use diesel_derive_enum::DbEnum; use rust_decimal::Decimal; @@ -12,62 +8,54 @@ use std::collections::{hash_map::Entry, HashMap}; #[derive(SqlType)] #[diesel(postgres_type(name = "chain_status_type"))] -pub(crate) struct ChainStatusType; +pub struct ChainStatusType; #[derive(Debug, PartialEq, Eq, Clone, Copy, Serialize, Deserialize, DbEnum)] #[ExistingTypePath = "ChainStatusType"] -pub(crate) enum MinaBlockStatus { +pub enum BlockStatus { Pending, Canonical, Orphaned, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub(crate) struct MinaVoteWithWeight { - pub(crate) account: String, - pub(crate) hash: String, - pub(crate) memo: String, - pub(crate) height: i64, - pub(crate) status: MinaBlockStatus, - pub(crate) timestamp: i64, - pub(crate) nonce: i64, - pub(crate) weight: Decimal, +pub struct VoteWithWeight { + pub account: String, + pub hash: String, + pub memo: String, + pub height: i64, + pub status: BlockStatus, + pub timestamp: i64, + pub nonce: i64, + pub weight: Decimal, } #[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)] -pub(crate) struct MinaVote { - pub(crate) account: String, - pub(crate) hash: String, - pub(crate) memo: String, - pub(crate) height: i64, - pub(crate) status: MinaBlockStatus, - pub(crate) timestamp: i64, - pub(crate) nonce: i64, +pub struct Vote { + pub account: String, + pub hash: String, + pub memo: String, + pub height: i64, + pub status: BlockStatus, + pub timestamp: i64, + pub nonce: i64, } -impl MinaVote { - pub(crate) fn new( +impl Vote { + pub fn new( account: impl Into, hash: impl Into, memo: impl Into, height: i64, - status: MinaBlockStatus, + status: BlockStatus, timestamp: i64, nonce: i64, - ) -> MinaVote { - MinaVote { - account: account.into(), - hash: hash.into(), - memo: memo.into(), - height, - status, - timestamp, - nonce, - } + ) -> Self { + Self { account: account.into(), hash: hash.into(), memo: memo.into(), height, status, timestamp, nonce } } - pub(crate) fn to_weighted(&self, weight: Decimal) -> MinaVoteWithWeight { - MinaVoteWithWeight { + pub fn to_weighted(&self, weight: Decimal) -> VoteWithWeight { + VoteWithWeight { account: self.account.clone(), hash: self.hash.clone(), memo: self.memo.clone(), @@ -79,55 +67,49 @@ impl MinaVote { } } - pub(crate) fn update_memo(&mut self, memo: impl Into) { + pub fn update_memo(&mut self, memo: impl Into) { let memo = memo.into(); self.memo = memo; } - pub(crate) fn update_status(&mut self, status: MinaBlockStatus) { + pub fn update_status(&mut self, status: BlockStatus) { self.status = status; } - pub(crate) fn is_newer_than(&self, other: &MinaVote) -> bool { + pub fn is_newer_than(&self, other: &Vote) -> bool { self.height > other.height || (self.height == other.height && self.nonce > other.nonce) } - pub(crate) fn match_decoded_memo(&mut self, key: &str) -> Option { + pub fn match_decoded_memo(&mut self, key: &str) -> Option { if let Ok(decoded) = self.decode_memo() { - if decoded.to_lowercase() == key.to_lowercase() || decoded.to_lowercase() == f!("no {}", key.to_lowercase()) { + if decoded.to_lowercase() == key.to_lowercase() || decoded.to_lowercase() == format!("no {}", key.to_lowercase()) + { return Some(decoded); } } None } - fn decode_memo(&self) -> Result { - let decoded = bs58::decode(&self.memo) - .into_vec() - .with_context(|| f!("failed to decode memo {} - bs58", &self.memo))?; + pub(crate) fn decode_memo(&self) -> Result { + let decoded = + bs58::decode(&self.memo).into_vec().with_context(|| format!("failed to decode memo {} - bs58", &self.memo))?; let value = &decoded[3 .. decoded[2] as usize + 3]; - Ok(String::from_utf8(value.to_vec()).with_context(|| f!("failed to decode memo {} - from_utf8", &self.memo))?) + let result = + String::from_utf8(value.to_vec()).with_context(|| format!("failed to decode memo {} - from_utf8", &self.memo))?; + Ok(result) } } -impl From for MinaVote { +impl From for Vote { fn from(res: FetchTransactionResult) -> Self { - MinaVote::new( - res.account, - res.hash, - res.memo, - res.height, - res.status, - res.timestamp, - res.nonce, - ) + Vote::new(res.account, res.hash, res.memo, res.height, res.status, res.timestamp, res.nonce) } } -impl Wrapper> { - pub(crate) fn process(self, key: impl Into, tip: i64) -> Wrapper> { +impl Wrapper> { + pub fn process(self, key: impl Into, tip: i64) -> Wrapper> { let mut map = HashMap::new(); let key = key.into(); @@ -136,7 +118,7 @@ impl Wrapper> { vote.update_memo(memo); if tip - vote.height >= 10 { - vote.update_status(MinaBlockStatus::Canonical); + vote.update_status(BlockStatus::Canonical); } match map.entry(vote.account.clone()) { @@ -156,15 +138,10 @@ impl Wrapper> { Wrapper(map) } - pub(crate) fn into_weighted( - self, - proposal: &MinaProposal, - ledger: &Ledger, - tip: i64, - ) -> Wrapper> { + pub fn into_weighted(self, proposal: &Proposal, ledger: &Ledger, tip: i64) -> Wrapper> { let votes = self.process(&proposal.key, tip); - let votes_with_stake: Vec = votes + let votes_with_stake: Vec = votes .0 .iter() .filter_map(|(account, vote)| { @@ -177,20 +154,20 @@ impl Wrapper> { } } -impl Wrapper> { - pub(crate) fn to_vec(&self) -> Wrapper> { +impl Wrapper> { + pub fn to_vec(&self) -> Wrapper> { Wrapper(self.0.values().cloned().collect()) } } -impl Wrapper> { - pub(crate) fn sort_by_timestamp(&mut self) -> &Self { +impl Wrapper> { + pub fn sort_by_timestamp(&mut self) -> &Self { self.to_vec().0.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); self } } -impl Wrapper> { - pub(crate) fn sort_by_timestamp(mut self) -> Self { +impl Wrapper> { + pub fn sort_by_timestamp(mut self) -> Self { self.0.sort_by(|a, b| b.timestamp.cmp(&a.timestamp)); self } @@ -200,59 +177,9 @@ impl Wrapper> { mod tests { use super::*; - fn get_test_votes() -> Vec { - vec![ - MinaVote::new( - "1", - "1", - "E4YjFkHVUXbEAkQcUrAEcS1fqvbncnn9Tuz2Jtb1Uu79zY9UAJRpd", - 100, - MinaBlockStatus::Pending, - 100, - 1, - ), - MinaVote::new( - "1", - "2", - "E4YjFkHVUXbEAkQcUrAEcS1fqvbncnn9Tuz2Jtb1Uu79zY9UAJRpd", - 110, - MinaBlockStatus::Pending, - 110, - 1, - ), - MinaVote::new( - "2", - "3", - "E4YjFkHVUXbEAkQcUrAEcS1fqvbncnn9Tuz2Jtb1Uu79zY9UAJRpd", - 110, - MinaBlockStatus::Pending, - 110, - 1, - ), - MinaVote::new( - "2", - "4", - "E4YdLeukpqzqyBAxujeELx9SZWoUW9MhcUfnGHF9PhQmxTJcpmj7j", - 120, - MinaBlockStatus::Pending, - 120, - 2, - ), - MinaVote::new( - "2", - "4", - "E4YiC7vB4DC9JoQvaj83nBWwHC3gJh4G9EBef7xh4ti4idBAgZai7", - 120, - MinaBlockStatus::Pending, - 120, - 2, - ), - ] - } - #[test] fn test_decode_memo() { - let mut vote = MinaVote::new("1", "1", "", 100, MinaBlockStatus::Pending, 100, 1); + let mut vote = Vote::new("1", "1", "", 100, BlockStatus::Pending, 100, 1); vote.update_memo("E4Yf92G48v8FApR4EWQq3iKb2vZkHHxZHPaZ73NQNBXmHeXNzHHSp"); assert_eq!(vote.decode_memo().unwrap(), "Payment#0"); @@ -300,14 +227,24 @@ mod tests { assert_eq!(a1.hash, "2"); assert_eq!(a1.memo, "no cftest-2"); assert_eq!(a1.height, 110); - assert_eq!(a1.status, MinaBlockStatus::Canonical); + assert_eq!(a1.status, BlockStatus::Canonical); assert_eq!(a1.nonce, 1); assert_eq!(a2.account, "2"); assert_eq!(a2.hash, "4"); assert_eq!(a2.memo, "cftest-2"); assert_eq!(a2.height, 120); - assert_eq!(a2.status, MinaBlockStatus::Pending); + assert_eq!(a2.status, BlockStatus::Pending); assert_eq!(a2.nonce, 2); } + + fn get_test_votes() -> Vec { + vec![ + Vote::new("1", "1", "E4YjFkHVUXbEAkQcUrAEcS1fqvbncnn9Tuz2Jtb1Uu79zY9UAJRpd", 100, BlockStatus::Pending, 100, 1), + Vote::new("1", "2", "E4YjFkHVUXbEAkQcUrAEcS1fqvbncnn9Tuz2Jtb1Uu79zY9UAJRpd", 110, BlockStatus::Pending, 110, 1), + Vote::new("2", "3", "E4YjFkHVUXbEAkQcUrAEcS1fqvbncnn9Tuz2Jtb1Uu79zY9UAJRpd", 110, BlockStatus::Pending, 110, 1), + Vote::new("2", "4", "E4YdLeukpqzqyBAxujeELx9SZWoUW9MhcUfnGHF9PhQmxTJcpmj7j", 120, BlockStatus::Pending, 120, 2), + Vote::new("2", "4", "E4YiC7vB4DC9JoQvaj83nBWwHC3gJh4G9EBef7xh4ti4idBAgZai7", 120, BlockStatus::Pending, 120, 2), + ] + } } diff --git a/server/start b/server/start deleted file mode 100755 index c6fac0a50..000000000 --- a/server/start +++ /dev/null @@ -1,27 +0,0 @@ -#!/bin/sh - -set -ex - -# Debug info -echo DB_HOST="$DB_HOST" -echo DB_PORT="$DB_PORT" -echo DB_NAME="$DB_NAME" -echo DB_USER="$DB_USER" -echo DATABASE_URL="$DATABASE_URL" - -# Wait for the other container to start, and have its networking configured. -sleep 5 - -# Wait for up to 1 minute for the database instance to be ready. -pg_isready \ - -h "$DB_HOST" \ - -p "$DB_PORT" \ - -d "$DB_NAME" \ - -U "$DB_USER" \ - -t 60 - -# Run the migrations -diesel migration run - -# Start the server -exec ./target/release/mina-ocv-server diff --git a/words.txt b/words.txt index be0e43ce4..047087cb8 100644 --- a/words.txt +++ b/words.txt @@ -1,6 +1,10 @@ codebases +delegators devnet +lightnet mina moka +reqwest runbook rustsec +thiserror