mirror of
https://github.com/hinto-janai/cuprate.git
synced 2024-11-16 15:58:14 +00:00
Merge branch 'main' into rpc-book
This commit is contained in:
commit
01cfcc2aa0
96 changed files with 2915 additions and 1589 deletions
37
.github/workflows/architecture-book.yml
vendored
Normal file
37
.github/workflows/architecture-book.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
# This action attempts to build the architecture book, if changed.
|
||||
|
||||
name: Architecture mdBook
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'books/architecture/**'
|
||||
|
||||
env:
|
||||
# Version of `mdbook` to install.
|
||||
MDBOOK_VERSION: 0.4.36
|
||||
# Version of `mdbook-last-changed` to install.
|
||||
# <https://github.com/badboy/mdbook-last-changed>.
|
||||
MDBOOK_LAST_CHANGED_VERSION: 0.1.4
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/mdbook
|
||||
~/.cargo/bin/mdbook-last-changed
|
||||
key: architecture-book
|
||||
|
||||
- name: Install mdBook
|
||||
run: |
|
||||
cargo install --version ${MDBOOK_VERSION} mdbook
|
||||
cargo install --version ${MDBOOK_LAST_CHANGED_VERSION} mdbook-last-changed
|
||||
|
||||
- name: Build
|
||||
run: mdbook build books/architecture
|
37
.github/workflows/monero-book.yml
vendored
Normal file
37
.github/workflows/monero-book.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
# This action attempts to build the Monero book, if changed.
|
||||
|
||||
name: Monero mdBook
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'books/protocol/**'
|
||||
|
||||
env:
|
||||
# Version of `mdbook` to install.
|
||||
MDBOOK_VERSION: 0.4.36
|
||||
# Version of `mdbook-svgbob` to install.
|
||||
# <https://github.com/boozook/mdbook-svgbob>.
|
||||
MDBOOK_SVGBOB_VERSION: 0.2.1
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/mdbook
|
||||
~/.cargo/bin/mdbook-svgbob
|
||||
key: monero-book
|
||||
|
||||
- name: Install mdBook
|
||||
run: |
|
||||
cargo install --version ${MDBOOK_VERSION} mdbook
|
||||
cargo install --version ${MDBOOK_SVGBOB_VERSION} mdbook-svgbob
|
||||
|
||||
- name: Build
|
||||
run: mdbook build books/protocol
|
37
.github/workflows/user-book.yml
vendored
Normal file
37
.github/workflows/user-book.yml
vendored
Normal file
|
@ -0,0 +1,37 @@
|
|||
# This action attempts to build the user book, if changed.
|
||||
|
||||
name: User mdBook
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'books/user/**'
|
||||
|
||||
env:
|
||||
# Version of `mdbook` to install.
|
||||
MDBOOK_VERSION: 0.4.36
|
||||
# Version of `mdbook-last-changed` to install.
|
||||
# <https://github.com/badboy/mdbook-last-changed>.
|
||||
MDBOOK_LAST_CHANGED_VERSION: 0.1.4
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Cache
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/mdbook
|
||||
~/.cargo/bin/mdbook-last-changed
|
||||
key: user-book
|
||||
|
||||
- name: Install mdBook
|
||||
run: |
|
||||
cargo install --version ${MDBOOK_VERSION} mdbook
|
||||
cargo install --version ${MDBOOK_LAST_CHANGED_VERSION} mdbook-last-changed
|
||||
|
||||
- name: Build
|
||||
run: mdbook build books/user
|
549
Cargo.lock
generated
549
Cargo.lock
generated
|
@ -29,12 +29,6 @@ dependencies = [
|
|||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
|
@ -56,17 +50,6 @@ version = "1.0.7"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b"
|
||||
|
||||
[[package]]
|
||||
name = "async-lock"
|
||||
version = "3.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff6e472cdea888a4bd64f342f09b3f50e1886d32afe8df3d663c01140b811b18"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"event-listener-strategy",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-stream"
|
||||
version = "0.3.5"
|
||||
|
@ -100,12 +83,73 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
|
||||
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
|
||||
|
||||
[[package]]
|
||||
name = "axum"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3a6c9af12842a67734c9a2e355436e5d03b22383ed60cf13cd0c18fbfe3dcbcf"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"axum-core",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"itoa",
|
||||
"matchit",
|
||||
"memchr",
|
||||
"mime",
|
||||
"percent-encoding",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"serde_urlencoded",
|
||||
"sync_wrapper 1.0.1",
|
||||
"tokio",
|
||||
"tower",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "axum-core"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a15c63fd72d41492dc4f497196f5da1fb04fb7529e631d73630d1b491e47a2e3"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures-util",
|
||||
"http",
|
||||
"http-body",
|
||||
"http-body-util",
|
||||
"mime",
|
||||
"pin-project-lite",
|
||||
"rustversion",
|
||||
"sync_wrapper 0.1.2",
|
||||
"tower-layer",
|
||||
"tower-service",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "backtrace"
|
||||
version = "0.3.73"
|
||||
|
@ -121,28 +165,12 @@ dependencies = [
|
|||
"rustc-demangle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base58-monero"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "978e81a45367d2409ecd33369a45dda2e9a3ca516153ec194de1fbda4b9fb79d"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"tiny-keccak",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "base64"
|
||||
version = "0.22.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "base64ct"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "1.3.3"
|
||||
|
@ -346,15 +374,6 @@ version = "0.7.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70"
|
||||
|
||||
[[package]]
|
||||
name = "concurrent-queue"
|
||||
version = "2.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
|
@ -380,6 +399,15 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc32fast"
|
||||
version = "1.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam"
|
||||
version = "0.8.4"
|
||||
|
@ -436,12 +464,6 @@ version = "0.8.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
|
||||
|
||||
[[package]]
|
||||
name = "crunchy"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-bigint"
|
||||
version = "0.5.5"
|
||||
|
@ -449,6 +471,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76"
|
||||
dependencies = [
|
||||
"subtle",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -471,7 +494,7 @@ dependencies = [
|
|||
"cuprate-test-utils",
|
||||
"cuprate-wire",
|
||||
"futures",
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"rand",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
|
@ -496,14 +519,13 @@ version = "0.0.0"
|
|||
dependencies = [
|
||||
"bitflags 2.5.0",
|
||||
"bytemuck",
|
||||
"crossbeam",
|
||||
"cuprate-database",
|
||||
"cuprate-database-service",
|
||||
"cuprate-helper",
|
||||
"cuprate-pruning",
|
||||
"cuprate-test-utils",
|
||||
"cuprate-types",
|
||||
"curve25519-dalek",
|
||||
"futures",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"monero-serai",
|
||||
|
@ -514,7 +536,6 @@ dependencies = [
|
|||
"tempfile",
|
||||
"thread_local",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower",
|
||||
]
|
||||
|
||||
|
@ -527,12 +548,10 @@ dependencies = [
|
|||
"cuprate-test-utils",
|
||||
"cuprate-types",
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"futures",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"monero-serai",
|
||||
"multiexp",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"rand",
|
||||
|
@ -555,11 +574,9 @@ dependencies = [
|
|||
"cuprate-cryptonight",
|
||||
"cuprate-helper",
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"monero-serai",
|
||||
"multiexp",
|
||||
"proptest",
|
||||
"proptest-derive",
|
||||
"rand",
|
||||
|
@ -609,6 +626,19 @@ dependencies = [
|
|||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuprate-database-service"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam",
|
||||
"cuprate-database",
|
||||
"cuprate-helper",
|
||||
"futures",
|
||||
"rayon",
|
||||
"serde",
|
||||
"tower",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuprate-epee-encoding"
|
||||
version = "0.5.0"
|
||||
|
@ -709,7 +739,7 @@ dependencies = [
|
|||
"dashmap",
|
||||
"futures",
|
||||
"hex",
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"monero-serai",
|
||||
"pin-project",
|
||||
"proptest",
|
||||
|
@ -759,6 +789,20 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "cuprate-rpc-interface"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"axum",
|
||||
"cuprate-epee-encoding",
|
||||
"cuprate-helper",
|
||||
"cuprate-json-rpc",
|
||||
"cuprate-rpc-types",
|
||||
"futures",
|
||||
"paste",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tower",
|
||||
"ureq",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cuprate-rpc-types"
|
||||
|
@ -790,7 +834,9 @@ dependencies = [
|
|||
"futures",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"monero-rpc",
|
||||
"monero-serai",
|
||||
"monero-simple-request-rpc",
|
||||
"paste",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
|
@ -808,6 +854,7 @@ version = "0.0.0"
|
|||
name = "cuprate-types"
|
||||
version = "0.0.0"
|
||||
dependencies = [
|
||||
"borsh",
|
||||
"bytes",
|
||||
"cuprate-epee-encoding",
|
||||
"cuprate-fixed-bytes",
|
||||
|
@ -862,7 +909,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "dalek-ff-group"
|
||||
version = "0.4.1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"crypto-bigint",
|
||||
"curve25519-dalek",
|
||||
|
@ -882,7 +929,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "978747c1d849a7d2ee5e8adc0159961c48fb7e5db2f06af6723b80123bb53856"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"hashbrown 0.14.5",
|
||||
"hashbrown",
|
||||
"lock_api",
|
||||
"once_cell",
|
||||
"parking_lot_core",
|
||||
|
@ -981,27 +1028,6 @@ dependencies = [
|
|||
"windows-sys 0.52.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "5.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba"
|
||||
dependencies = [
|
||||
"concurrent-queue",
|
||||
"parking",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener-strategy"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f214dc438f977e6d4e3500aaa277f5ad94ca83fbbd9b1a15713ce2344ccc5a1"
|
||||
dependencies = [
|
||||
"event-listener",
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.1.0"
|
||||
|
@ -1025,10 +1051,20 @@ version = "0.2.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.30"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flexible-transcript"
|
||||
version = "0.3.2"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"blake2",
|
||||
"digest",
|
||||
|
@ -1175,10 +1211,23 @@ dependencies = [
|
|||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
name = "h2"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
|
||||
checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab"
|
||||
dependencies = [
|
||||
"atomic-waker",
|
||||
"bytes",
|
||||
"fnv",
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"http",
|
||||
"indexmap",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
|
@ -1187,17 +1236,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"allocator-api2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hdrhistogram"
|
||||
version = "7.5.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1262,15 +1300,6 @@ version = "0.4.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46"
|
||||
|
||||
[[package]]
|
||||
name = "hmac"
|
||||
version = "0.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e"
|
||||
dependencies = [
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.1.0"
|
||||
|
@ -1311,6 +1340,12 @@ version = "1.9.3"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0e7a4dd27b9476dc40cb050d3632d3bba3a70ddbff012285f7f8559a1e7e545"
|
||||
|
||||
[[package]]
|
||||
name = "httpdate"
|
||||
version = "1.0.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
|
||||
|
||||
[[package]]
|
||||
name = "hyper"
|
||||
version = "1.3.1"
|
||||
|
@ -1320,9 +1355,11 @@ dependencies = [
|
|||
"bytes",
|
||||
"futures-channel",
|
||||
"futures-util",
|
||||
"h2",
|
||||
"http",
|
||||
"http-body",
|
||||
"httparse",
|
||||
"httpdate",
|
||||
"itoa",
|
||||
"pin-project-lite",
|
||||
"smallvec",
|
||||
|
@ -1521,16 +1558,6 @@ dependencies = [
|
|||
"utf8_iter",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "1.9.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"hashbrown 0.12.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "indexmap"
|
||||
version = "2.2.6"
|
||||
|
@ -1538,7 +1565,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26"
|
||||
dependencies = [
|
||||
"equivalent",
|
||||
"hashbrown 0.14.5",
|
||||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1632,6 +1659,12 @@ version = "0.4.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c"
|
||||
|
||||
[[package]]
|
||||
name = "matchit"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
|
@ -1660,6 +1693,12 @@ dependencies = [
|
|||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mime"
|
||||
version = "0.3.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.3"
|
||||
|
@ -1680,63 +1719,163 @@ dependencies = [
|
|||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-address"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"monero-io",
|
||||
"monero-primitives",
|
||||
"std-shims",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-borromean"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"monero-generators",
|
||||
"monero-io",
|
||||
"monero-primitives",
|
||||
"std-shims",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-bulletproofs"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"monero-generators",
|
||||
"monero-io",
|
||||
"monero-primitives",
|
||||
"rand_core",
|
||||
"std-shims",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-clsag"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"flexible-transcript",
|
||||
"group",
|
||||
"monero-generators",
|
||||
"monero-io",
|
||||
"monero-primitives",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"std-shims",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-generators"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"group",
|
||||
"monero-io",
|
||||
"sha3",
|
||||
"std-shims",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-io"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"std-shims",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-mlsag"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"monero-generators",
|
||||
"monero-io",
|
||||
"monero-primitives",
|
||||
"std-shims",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-primitives"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"curve25519-dalek",
|
||||
"monero-generators",
|
||||
"monero-io",
|
||||
"sha3",
|
||||
"std-shims",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-rpc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"curve25519-dalek",
|
||||
"hex",
|
||||
"monero-address",
|
||||
"monero-serai",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"std-shims",
|
||||
"thiserror",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "monero-serai"
|
||||
version = "0.1.4-alpha"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"async-lock",
|
||||
"async-trait",
|
||||
"base58-monero",
|
||||
"curve25519-dalek",
|
||||
"dalek-ff-group",
|
||||
"digest_auth",
|
||||
"flexible-transcript",
|
||||
"group",
|
||||
"hex",
|
||||
"hex-literal",
|
||||
"monero-borromean",
|
||||
"monero-bulletproofs",
|
||||
"monero-clsag",
|
||||
"monero-generators",
|
||||
"multiexp",
|
||||
"pbkdf2",
|
||||
"rand",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_distr",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha3",
|
||||
"simple-request",
|
||||
"monero-io",
|
||||
"monero-mlsag",
|
||||
"monero-primitives",
|
||||
"std-shims",
|
||||
"subtle",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multiexp"
|
||||
version = "0.4.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
name = "monero-simple-request-rpc"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"ff",
|
||||
"group",
|
||||
"rand_core",
|
||||
"rustversion",
|
||||
"std-shims",
|
||||
"zeroize",
|
||||
"async-trait",
|
||||
"digest_auth",
|
||||
"hex",
|
||||
"monero-rpc",
|
||||
"simple-request",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -1796,12 +1935,6 @@ dependencies = [
|
|||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.3"
|
||||
|
@ -1825,35 +1958,12 @@ dependencies = [
|
|||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "password-hash"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "346f04948ba92c43e8469c1ee6736c7563d71012b17d40745260fe106aac2166"
|
||||
dependencies = [
|
||||
"base64ct",
|
||||
"rand_core",
|
||||
"subtle",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a"
|
||||
|
||||
[[package]]
|
||||
name = "pbkdf2"
|
||||
version = "0.12.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2"
|
||||
dependencies = [
|
||||
"digest",
|
||||
"hmac",
|
||||
"password-hash",
|
||||
"sha2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "percent-encoding"
|
||||
version = "2.3.1"
|
||||
|
@ -2226,6 +2336,7 @@ version = "0.23.10"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05cff451f60db80f490f3c182b77c35260baace73209e9cdbbe526bfe3a4d402"
|
||||
dependencies = [
|
||||
"log",
|
||||
"once_cell",
|
||||
"ring",
|
||||
"rustls-pki-types",
|
||||
|
@ -2373,6 +2484,28 @@ dependencies = [
|
|||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_path_to_error"
|
||||
version = "0.1.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af99884400da37c88f5e9146b7f1fd0fbcae8f6eec4e9da38b67d05486f814a6"
|
||||
dependencies = [
|
||||
"itoa",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_urlencoded"
|
||||
version = "0.7.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd"
|
||||
dependencies = [
|
||||
"form_urlencoded",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
|
@ -2406,7 +2539,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "simple-request"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"http-body-util",
|
||||
"hyper",
|
||||
|
@ -2462,9 +2595,9 @@ checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
|||
[[package]]
|
||||
name = "std-shims"
|
||||
version = "0.1.1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d27d934#d27d93480aa8a849d84214ad4c71d83ce6fea0c1"
|
||||
source = "git+https://github.com/Cuprate/serai.git?rev=d5205ce#d5205ce2319e09414eb91d12cf38e83a08165f79"
|
||||
dependencies = [
|
||||
"hashbrown 0.14.5",
|
||||
"hashbrown",
|
||||
"spin",
|
||||
]
|
||||
|
||||
|
@ -2508,6 +2641,18 @@ dependencies = [
|
|||
"syn 2.0.66",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sync_wrapper"
|
||||
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 = "synchronoise"
|
||||
version = "1.0.1"
|
||||
|
@ -2576,15 +2721,6 @@ dependencies = [
|
|||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tiny-keccak"
|
||||
version = "2.0.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
|
||||
dependencies = [
|
||||
"crunchy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.7.6"
|
||||
|
@ -2669,10 +2805,7 @@ checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1"
|
|||
dependencies = [
|
||||
"bytes",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-util",
|
||||
"hashbrown 0.14.5",
|
||||
"pin-project-lite",
|
||||
"slab",
|
||||
"tokio",
|
||||
|
@ -2691,7 +2824,7 @@ version = "0.21.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1"
|
||||
dependencies = [
|
||||
"indexmap 2.2.6",
|
||||
"indexmap",
|
||||
"toml_datetime",
|
||||
"winnow",
|
||||
]
|
||||
|
@ -2704,12 +2837,8 @@ checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
|
|||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hdrhistogram",
|
||||
"indexmap 1.9.3",
|
||||
"pin-project",
|
||||
"pin-project-lite",
|
||||
"rand",
|
||||
"slab",
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tower-layer",
|
||||
|
@ -2735,6 +2864,7 @@ version = "0.1.40"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
|
@ -2799,6 +2929,24 @@ version = "0.9.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1"
|
||||
|
||||
[[package]]
|
||||
name = "ureq"
|
||||
version = "2.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72139d247e5f97a3eff96229a7ae85ead5328a39efe76f8bf5a06313d505b6ea"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"flate2",
|
||||
"log",
|
||||
"once_cell",
|
||||
"rustls",
|
||||
"rustls-pki-types",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"url",
|
||||
"webpki-roots",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "url"
|
||||
version = "2.5.1"
|
||||
|
@ -2906,6 +3054,15 @@ version = "0.2.92"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
|
||||
|
||||
[[package]]
|
||||
name = "webpki-roots"
|
||||
version = "0.26.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bd7c23921eeb1713a4e851530e9b9756e4fb0e89978582942612524cf09f01cd"
|
||||
dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
|
|
18
Cargo.toml
18
Cargo.toml
|
@ -17,6 +17,7 @@ members = [
|
|||
"p2p/async-buffer",
|
||||
"p2p/address-book",
|
||||
"storage/blockchain",
|
||||
"storage/service",
|
||||
"storage/txpool",
|
||||
"storage/database",
|
||||
"pruning",
|
||||
|
@ -57,15 +58,13 @@ chrono = { version = "0.4.31", default-features = false }
|
|||
crypto-bigint = { version = "0.5.5", default-features = false }
|
||||
crossbeam = { version = "0.8.4", default-features = false }
|
||||
curve25519-dalek = { version = "4.1.3", default-features = false }
|
||||
dalek-ff-group = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false }
|
||||
dashmap = { version = "5.5.3", default-features = false }
|
||||
dirs = { version = "5.0.1", default-features = false }
|
||||
futures = { version = "0.3.29", default-features = false }
|
||||
hex = { version = "0.4.3", default-features = false }
|
||||
hex-literal = { version = "0.4", default-features = false }
|
||||
indexmap = { version = "2.2.5", default-features = false }
|
||||
monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false }
|
||||
multiexp = { git = "https://github.com/Cuprate/serai.git", rev = "d27d934", default-features = false }
|
||||
monero-serai = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce", default-features = false }
|
||||
paste = { version = "1.0.14", default-features = false }
|
||||
pin-project = { version = "1.1.3", default-features = false }
|
||||
randomx-rs = { git = "https://github.com/Cuprate/randomx-rs.git", rev = "0028464", default-features = false }
|
||||
|
@ -85,11 +84,13 @@ tracing-subscriber = { version = "0.3.17", default-features = false }
|
|||
tracing = { version = "0.1.40", default-features = false }
|
||||
|
||||
## workspace.dev-dependencies
|
||||
tempfile = { version = "3" }
|
||||
pretty_assertions = { version = "1.4.0" }
|
||||
proptest = { version = "1" }
|
||||
proptest-derive = { version = "0.4.0" }
|
||||
tokio-test = { version = "0.4.4" }
|
||||
monero-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
|
||||
monero-simple-request-rpc = { git = "https://github.com/Cuprate/serai.git", rev = "d5205ce" }
|
||||
tempfile = { version = "3" }
|
||||
pretty_assertions = { version = "1.4.0" }
|
||||
proptest = { version = "1" }
|
||||
proptest-derive = { version = "0.4.0" }
|
||||
tokio-test = { version = "0.4.4" }
|
||||
|
||||
## TODO:
|
||||
## Potential dependencies.
|
||||
|
@ -243,7 +244,6 @@ try_err = "deny"
|
|||
lossy_float_literal = "deny"
|
||||
let_underscore_must_use = "deny"
|
||||
iter_over_hash_type = "deny"
|
||||
impl_trait_in_params = "deny"
|
||||
get_unwrap = "deny"
|
||||
error_impl_error = "deny"
|
||||
empty_structs_with_brackets = "deny"
|
||||
|
|
|
@ -19,8 +19,6 @@ futures = { workspace = true, features = ["std", "async-await"] }
|
|||
|
||||
randomx-rs = { workspace = true }
|
||||
monero-serai = { workspace = true, features = ["std"] }
|
||||
multiexp = { workspace = true }
|
||||
dalek-ff-group = { workspace = true }
|
||||
curve25519-dalek = { workspace = true }
|
||||
|
||||
rayon = { workspace = true }
|
||||
|
|
|
@ -4,30 +4,30 @@ use clap::Parser;
|
|||
use tower::{Service, ServiceExt};
|
||||
|
||||
use cuprate_blockchain::{
|
||||
config::ConfigBuilder, cuprate_database::RuntimeError, service::DatabaseReadHandle,
|
||||
config::ConfigBuilder, cuprate_database::RuntimeError, service::BlockchainReadHandle,
|
||||
};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain,
|
||||
};
|
||||
|
||||
use cuprate_fast_sync::{hash_of_hashes, BlockId, HashOfHashes};
|
||||
|
||||
const BATCH_SIZE: u64 = 512;
|
||||
const BATCH_SIZE: usize = 512;
|
||||
|
||||
async fn read_batch(
|
||||
handle: &mut DatabaseReadHandle,
|
||||
height_from: u64,
|
||||
handle: &mut BlockchainReadHandle,
|
||||
height_from: usize,
|
||||
) -> Result<Vec<BlockId>, RuntimeError> {
|
||||
let mut block_ids = Vec::<BlockId>::with_capacity(BATCH_SIZE as usize);
|
||||
let mut block_ids = Vec::<BlockId>::with_capacity(BATCH_SIZE);
|
||||
|
||||
for height in height_from..(height_from + BATCH_SIZE) {
|
||||
let request = BCReadRequest::BlockHash(height, Chain::Main);
|
||||
let request = BlockchainReadRequest::BlockHash(height, Chain::Main);
|
||||
let response_channel = handle.ready().await?.call(request);
|
||||
let response = response_channel.await?;
|
||||
|
||||
match response {
|
||||
BCResponse::BlockHash(block_id) => block_ids.push(block_id),
|
||||
BlockchainResponse::BlockHash(block_id) => block_ids.push(block_id),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ fn generate_hex(hashes: &[HashOfHashes]) -> String {
|
|||
#[command(version, about, long_about = None)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
height: u64,
|
||||
height: usize,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
|
@ -63,11 +63,11 @@ async fn main() {
|
|||
|
||||
let config = ConfigBuilder::new().build();
|
||||
|
||||
let (mut read_handle, _) = cuprate_blockchain::service::init(config).unwrap();
|
||||
let (mut read_handle, _, _) = cuprate_blockchain::service::init(config).unwrap();
|
||||
|
||||
let mut hashes_of_hashes = Vec::new();
|
||||
|
||||
let mut height = 0u64;
|
||||
let mut height = 0_usize;
|
||||
|
||||
while height < height_target {
|
||||
match read_batch(&mut read_handle, height).await {
|
||||
|
|
|
@ -244,7 +244,7 @@ where
|
|||
|
||||
let block_blob = block.serialize();
|
||||
|
||||
let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else {
|
||||
let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else {
|
||||
return Err(FastSyncError::MinerTx(MinerTxError::InputNotOfTypeGen));
|
||||
};
|
||||
if *height != block_chain_ctx.chain_height {
|
||||
|
@ -252,7 +252,7 @@ where
|
|||
}
|
||||
|
||||
let mut verified_txs = Vec::with_capacity(txs.len());
|
||||
for tx in &block.txs {
|
||||
for tx in &block.transactions {
|
||||
let tx = txs
|
||||
.remove(tx)
|
||||
.ok_or(FastSyncError::TxsIncludedWithBlockIncorrect)?;
|
||||
|
@ -269,8 +269,8 @@ where
|
|||
|
||||
let total_fees = verified_txs.iter().map(|tx| tx.fee).sum::<u64>();
|
||||
let total_outputs = block
|
||||
.miner_tx
|
||||
.prefix
|
||||
.miner_transaction
|
||||
.prefix()
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output| output.amount.unwrap_or(0))
|
||||
|
@ -278,8 +278,8 @@ where
|
|||
|
||||
let generated_coins = total_outputs - total_fees;
|
||||
|
||||
let weight =
|
||||
block.miner_tx.weight() + verified_txs.iter().map(|tx| tx.tx_weight).sum::<usize>();
|
||||
let weight = block.miner_transaction.weight()
|
||||
+ verified_txs.iter().map(|tx| tx.tx_weight).sum::<usize>();
|
||||
|
||||
Ok(FastSyncResponse::ValidateBlock(VerifiedBlockInformation {
|
||||
block_blob,
|
||||
|
|
|
@ -15,8 +15,6 @@ cuprate-helper = { path = "../../helper", default-features = false, features = [
|
|||
cuprate-cryptonight = {path = "../../cryptonight"}
|
||||
|
||||
monero-serai = { workspace = true, features = ["std"] }
|
||||
multiexp = { workspace = true, features = ["std", "batch"] }
|
||||
dalek-ff-group = { workspace = true, features = ["std"] }
|
||||
curve25519-dalek = { workspace = true, features = ["alloc", "zeroize", "precomputed-tables"] }
|
||||
|
||||
rand = { workspace = true, features = ["std", "std_rng"] }
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use multiexp::BatchVerifier as InternalBatchVerifier;
|
||||
use monero_serai::ringct::bulletproofs::BatchVerifier as InternalBatchVerifier;
|
||||
|
||||
/// This trait represents a batch verifier.
|
||||
///
|
||||
|
@ -12,18 +12,12 @@ pub trait BatchVerifier {
|
|||
/// # Panics
|
||||
/// This function may panic if `stmt` contains calls to `rayon`'s parallel iterators, e.g. `par_iter()`.
|
||||
// TODO: remove the panics by adding a generic API upstream.
|
||||
fn queue_statement<R>(
|
||||
&mut self,
|
||||
stmt: impl FnOnce(&mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>) -> R,
|
||||
) -> R;
|
||||
fn queue_statement<R>(&mut self, stmt: impl FnOnce(&mut InternalBatchVerifier) -> R) -> R;
|
||||
}
|
||||
|
||||
// impl this for a single threaded batch verifier.
|
||||
impl BatchVerifier for &'_ mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint> {
|
||||
fn queue_statement<R>(
|
||||
&mut self,
|
||||
stmt: impl FnOnce(&mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>) -> R,
|
||||
) -> R {
|
||||
impl BatchVerifier for &'_ mut InternalBatchVerifier {
|
||||
fn queue_statement<R>(&mut self, stmt: impl FnOnce(&mut InternalBatchVerifier) -> R) -> R {
|
||||
stmt(self)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,8 +21,8 @@ pub const PENALTY_FREE_ZONE_1: usize = 20000;
|
|||
pub const PENALTY_FREE_ZONE_2: usize = 60000;
|
||||
pub const PENALTY_FREE_ZONE_5: usize = 300000;
|
||||
|
||||
pub const RX_SEEDHASH_EPOCH_BLOCKS: u64 = 2048;
|
||||
pub const RX_SEEDHASH_EPOCH_LAG: u64 = 64;
|
||||
pub const RX_SEEDHASH_EPOCH_BLOCKS: usize = 2048;
|
||||
pub const RX_SEEDHASH_EPOCH_LAG: usize = 64;
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
|
||||
pub enum BlockError {
|
||||
|
@ -52,14 +52,14 @@ pub trait RandomX {
|
|||
}
|
||||
|
||||
/// Returns if this height is a RandomX seed height.
|
||||
pub fn is_randomx_seed_height(height: u64) -> bool {
|
||||
pub fn is_randomx_seed_height(height: usize) -> bool {
|
||||
height % RX_SEEDHASH_EPOCH_BLOCKS == 0
|
||||
}
|
||||
|
||||
/// Returns the RandomX seed height for this block.
|
||||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks.html#randomx-seed>
|
||||
pub fn randomx_seed_height(height: u64) -> u64 {
|
||||
pub fn randomx_seed_height(height: usize) -> usize {
|
||||
if height <= RX_SEEDHASH_EPOCH_BLOCKS + RX_SEEDHASH_EPOCH_LAG {
|
||||
0
|
||||
} else {
|
||||
|
@ -75,7 +75,7 @@ pub fn randomx_seed_height(height: u64) -> u64 {
|
|||
pub fn calculate_pow_hash<R: RandomX>(
|
||||
randomx_vm: Option<&R>,
|
||||
buf: &[u8],
|
||||
height: u64,
|
||||
height: usize,
|
||||
hf: &HardFork,
|
||||
) -> Result<[u8; 32], BlockError> {
|
||||
if height == 202612 {
|
||||
|
@ -89,7 +89,8 @@ pub fn calculate_pow_hash<R: RandomX>(
|
|||
} else if hf < &HardFork::V10 {
|
||||
cryptonight_hash_v2(buf)
|
||||
} else if hf < &HardFork::V12 {
|
||||
cryptonight_hash_r(buf, height)
|
||||
// FIXME: https://github.com/Cuprate/cuprate/issues/167.
|
||||
cryptonight_hash_r(buf, height as u64)
|
||||
} else {
|
||||
randomx_vm
|
||||
.expect("RandomX VM needed from hf 12")
|
||||
|
@ -220,7 +221,7 @@ pub struct ContextToVerifyBlock {
|
|||
/// Contains the median timestamp over the last 60 blocks, if there is less than 60 blocks this should be [`None`]
|
||||
pub median_block_timestamp: Option<u64>,
|
||||
/// The current chain height.
|
||||
pub chain_height: u64,
|
||||
pub chain_height: usize,
|
||||
/// The current hard-fork.
|
||||
pub current_hf: HardFork,
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/difficulty.html#calculating-difficulty>
|
||||
|
@ -263,11 +264,11 @@ pub fn check_block(
|
|||
check_block_weight(block_weight, block_chain_ctx.median_weight_for_block_reward)?;
|
||||
block_size_sanity_check(block_blob_len, block_chain_ctx.effective_median_weight)?;
|
||||
|
||||
check_amount_txs(block.txs.len())?;
|
||||
check_txs_unique(&block.txs)?;
|
||||
check_amount_txs(block.transactions.len())?;
|
||||
check_txs_unique(&block.transactions)?;
|
||||
|
||||
let generated_coins = check_miner_tx(
|
||||
&block.miner_tx,
|
||||
&block.miner_transaction,
|
||||
total_fees,
|
||||
block_chain_ctx.chain_height,
|
||||
block_weight,
|
||||
|
|
|
@ -29,14 +29,14 @@ fn genesis_miner_tx(network: &Network) -> Transaction {
|
|||
pub fn generate_genesis_block(network: &Network) -> Block {
|
||||
Block {
|
||||
header: BlockHeader {
|
||||
major_version: 1,
|
||||
minor_version: 0,
|
||||
hardfork_version: 1,
|
||||
hardfork_signal: 0,
|
||||
timestamp: 0,
|
||||
previous: [0; 32],
|
||||
nonce: genesis_nonce(network),
|
||||
},
|
||||
miner_tx: genesis_miner_tx(network),
|
||||
txs: vec![],
|
||||
miner_transaction: genesis_miner_tx(network),
|
||||
transactions: vec![],
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -40,11 +40,11 @@ pub enum HardForkError {
|
|||
/// Information about a given hard-fork.
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct HFInfo {
|
||||
height: u64,
|
||||
threshold: u64,
|
||||
height: usize,
|
||||
threshold: usize,
|
||||
}
|
||||
impl HFInfo {
|
||||
pub const fn new(height: u64, threshold: u64) -> HFInfo {
|
||||
pub const fn new(height: usize, threshold: usize) -> HFInfo {
|
||||
HFInfo { height, threshold }
|
||||
}
|
||||
}
|
||||
|
@ -202,8 +202,8 @@ impl HardFork {
|
|||
#[inline]
|
||||
pub fn from_block_header(header: &BlockHeader) -> Result<(HardFork, HardFork), HardForkError> {
|
||||
Ok((
|
||||
HardFork::from_version(header.major_version)?,
|
||||
HardFork::from_vote(header.minor_version),
|
||||
HardFork::from_version(header.hardfork_version)?,
|
||||
HardFork::from_vote(header.hardfork_signal),
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -245,7 +245,7 @@ impl HardFork {
|
|||
/// A struct holding the current voting state of the blockchain.
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
pub struct HFVotes {
|
||||
votes: [u64; NUMB_OF_HARD_FORKS],
|
||||
votes: [usize; NUMB_OF_HARD_FORKS],
|
||||
vote_list: VecDeque<HardFork>,
|
||||
window_size: usize,
|
||||
}
|
||||
|
@ -318,13 +318,13 @@ impl HFVotes {
|
|||
/// Returns the total votes for a hard-fork.
|
||||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#accepting-a-fork>
|
||||
pub fn votes_for_hf(&self, hf: &HardFork) -> u64 {
|
||||
pub fn votes_for_hf(&self, hf: &HardFork) -> usize {
|
||||
self.votes[*hf as usize - 1..].iter().sum()
|
||||
}
|
||||
|
||||
/// Returns the total amount of votes being tracked
|
||||
pub fn total_votes(&self) -> u64 {
|
||||
self.votes.iter().sum()
|
||||
pub fn total_votes(&self) -> usize {
|
||||
self.vote_list.len()
|
||||
}
|
||||
|
||||
/// Checks if a future hard fork should be activated, returning the next hard-fork that should be
|
||||
|
@ -334,8 +334,8 @@ impl HFVotes {
|
|||
pub fn current_fork(
|
||||
&self,
|
||||
current_hf: &HardFork,
|
||||
current_height: u64,
|
||||
window: u64,
|
||||
current_height: usize,
|
||||
window: usize,
|
||||
hfs_info: &HFsInfo,
|
||||
) -> HardFork {
|
||||
let mut current_hf = *current_hf;
|
||||
|
@ -361,6 +361,6 @@ impl HFVotes {
|
|||
/// Returns the votes needed for a hard-fork.
|
||||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/hardforks.html#accepting-a-fork>
|
||||
pub fn votes_needed(threshold: u64, window: u64) -> u64 {
|
||||
pub fn votes_needed(threshold: usize, window: usize) -> usize {
|
||||
(threshold * window).div_ceil(100)
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use proptest::{arbitrary::any, prop_assert_eq, prop_compose, proptest};
|
|||
|
||||
use crate::hard_forks::{HFVotes, HardFork, NUMB_OF_HARD_FORKS};
|
||||
|
||||
const TEST_WINDOW_SIZE: u64 = 25;
|
||||
const TEST_WINDOW_SIZE: usize = 25;
|
||||
|
||||
#[test]
|
||||
fn target_block_time() {
|
||||
|
@ -35,9 +35,9 @@ prop_compose! {
|
|||
fn arb_full_hf_votes()
|
||||
(
|
||||
// we can't use HardFork as for some reason it overflows the stack, so we use u8.
|
||||
votes in any::<[u8; TEST_WINDOW_SIZE as usize]>()
|
||||
votes in any::<[u8; TEST_WINDOW_SIZE]>()
|
||||
) -> HFVotes {
|
||||
let mut vote_count = HFVotes::new(TEST_WINDOW_SIZE as usize);
|
||||
let mut vote_count = HFVotes::new(TEST_WINDOW_SIZE);
|
||||
for vote in votes {
|
||||
vote_count.add_vote_for_hf(&HardFork::from_vote(vote % 17));
|
||||
}
|
||||
|
@ -48,9 +48,9 @@ prop_compose! {
|
|||
proptest! {
|
||||
#[test]
|
||||
fn hf_vote_counter_total_correct(hf_votes in arb_full_hf_votes()) {
|
||||
prop_assert_eq!(hf_votes.total_votes(), u64::try_from(hf_votes.vote_list.len()).unwrap());
|
||||
prop_assert_eq!(hf_votes.total_votes(), hf_votes.vote_list.len());
|
||||
|
||||
let mut votes = [0_u64; NUMB_OF_HARD_FORKS];
|
||||
let mut votes = [0_usize; NUMB_OF_HARD_FORKS];
|
||||
for vote in hf_votes.vote_list.iter() {
|
||||
// manually go through the list of votes tallying
|
||||
votes[*vote as usize - 1] += 1;
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
use monero_serai::{
|
||||
ringct::RctType,
|
||||
transaction::{Input, Output, Timelock, Transaction},
|
||||
};
|
||||
use monero_serai::transaction::{Input, Output, Timelock, Transaction};
|
||||
|
||||
use crate::{is_decomposed_amount, transactions::check_output_types, HardFork, TxVersion};
|
||||
|
||||
|
@ -35,7 +32,7 @@ const MONEY_SUPPLY: u64 = u64::MAX;
|
|||
/// The minimum block reward per minute, "tail-emission"
|
||||
const MINIMUM_REWARD_PER_MIN: u64 = 3 * 10_u64.pow(11);
|
||||
/// The value which `lock_time` should be for a coinbase output.
|
||||
const MINER_TX_TIME_LOCKED_BLOCKS: u64 = 60;
|
||||
const MINER_TX_TIME_LOCKED_BLOCKS: usize = 60;
|
||||
|
||||
/// Calculates the base block reward without taking away the penalty for expanding
|
||||
/// the block.
|
||||
|
@ -88,7 +85,7 @@ fn check_miner_tx_version(tx_version: &TxVersion, hf: &HardFork) -> Result<(), M
|
|||
/// Checks the miner transactions inputs.
|
||||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#input>
|
||||
fn check_inputs(inputs: &[Input], chain_height: u64) -> Result<(), MinerTxError> {
|
||||
fn check_inputs(inputs: &[Input], chain_height: usize) -> Result<(), MinerTxError> {
|
||||
if inputs.len() != 1 {
|
||||
return Err(MinerTxError::IncorrectNumbOfInputs);
|
||||
}
|
||||
|
@ -108,15 +105,15 @@ fn check_inputs(inputs: &[Input], chain_height: u64) -> Result<(), MinerTxError>
|
|||
/// Checks the miner transaction has a correct time lock.
|
||||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#unlock-time>
|
||||
fn check_time_lock(time_lock: &Timelock, chain_height: u64) -> Result<(), MinerTxError> {
|
||||
fn check_time_lock(time_lock: &Timelock, chain_height: usize) -> Result<(), MinerTxError> {
|
||||
match time_lock {
|
||||
Timelock::Block(till_height) => {
|
||||
&Timelock::Block(till_height) => {
|
||||
// Lock times above this amount are timestamps not blocks.
|
||||
// This is just for safety though and shouldn't actually be hit.
|
||||
if till_height > &500_000_000 {
|
||||
if till_height > 500_000_000 {
|
||||
Err(MinerTxError::InvalidLockTime)?;
|
||||
}
|
||||
if u64::try_from(*till_height).unwrap() != chain_height + MINER_TX_TIME_LOCKED_BLOCKS {
|
||||
if till_height != chain_height + MINER_TX_TIME_LOCKED_BLOCKS {
|
||||
Err(MinerTxError::InvalidLockTime)
|
||||
} else {
|
||||
Ok(())
|
||||
|
@ -182,28 +179,33 @@ fn check_total_output_amt(
|
|||
pub fn check_miner_tx(
|
||||
tx: &Transaction,
|
||||
total_fees: u64,
|
||||
chain_height: u64,
|
||||
chain_height: usize,
|
||||
block_weight: usize,
|
||||
median_bw: usize,
|
||||
already_generated_coins: u64,
|
||||
hf: &HardFork,
|
||||
) -> Result<u64, MinerTxError> {
|
||||
let tx_version = TxVersion::from_raw(tx.prefix.version).ok_or(MinerTxError::VersionInvalid)?;
|
||||
let tx_version = TxVersion::from_raw(tx.version()).ok_or(MinerTxError::VersionInvalid)?;
|
||||
check_miner_tx_version(&tx_version, hf)?;
|
||||
|
||||
// ref: <https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#ringct-type>
|
||||
if hf >= &HardFork::V12 && tx.rct_signatures.rct_type() != RctType::Null {
|
||||
return Err(MinerTxError::RCTTypeNotNULL);
|
||||
match tx {
|
||||
Transaction::V1 { .. } => (),
|
||||
Transaction::V2 { proofs, .. } => {
|
||||
if hf >= &HardFork::V12 && proofs.is_some() {
|
||||
return Err(MinerTxError::RCTTypeNotNULL);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
check_time_lock(&tx.prefix.timelock, chain_height)?;
|
||||
check_time_lock(&tx.prefix().additional_timelock, chain_height)?;
|
||||
|
||||
check_inputs(&tx.prefix.inputs, chain_height)?;
|
||||
check_inputs(&tx.prefix().inputs, chain_height)?;
|
||||
|
||||
check_output_types(&tx.prefix.outputs, hf).map_err(|_| MinerTxError::InvalidOutputType)?;
|
||||
check_output_types(&tx.prefix().outputs, hf).map_err(|_| MinerTxError::InvalidOutputType)?;
|
||||
|
||||
let reward = calculate_block_reward(block_weight, median_bw, already_generated_coins, hf);
|
||||
let total_outs = sum_outputs(&tx.prefix.outputs, hf, &tx_version)?;
|
||||
let total_outs = sum_outputs(&tx.prefix().outputs, hf, &tx_version)?;
|
||||
|
||||
check_total_output_amt(total_outs, reward, total_fees, hf)
|
||||
}
|
||||
|
|
|
@ -91,7 +91,7 @@ impl TxVersion {
|
|||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions.html#version>
|
||||
/// && <https://monero-book.cuprate.org/consensus_rules/blocks/miner_tx.html#version>
|
||||
pub fn from_raw(version: u64) -> Option<TxVersion> {
|
||||
pub fn from_raw(version: u8) -> Option<TxVersion> {
|
||||
Some(match version {
|
||||
1 => TxVersion::RingSignatures,
|
||||
2 => TxVersion::RingCT,
|
||||
|
@ -205,7 +205,7 @@ fn check_number_of_outputs(
|
|||
outputs: usize,
|
||||
hf: &HardFork,
|
||||
tx_version: &TxVersion,
|
||||
rct_type: &RctType,
|
||||
bp_or_bpp: bool,
|
||||
) -> Result<(), TransactionError> {
|
||||
if tx_version == &TxVersion::RingSignatures {
|
||||
return Ok(());
|
||||
|
@ -215,18 +215,10 @@ fn check_number_of_outputs(
|
|||
return Err(TransactionError::InvalidNumberOfOutputs);
|
||||
}
|
||||
|
||||
match rct_type {
|
||||
RctType::Bulletproofs
|
||||
| RctType::BulletproofsCompactAmount
|
||||
| RctType::Clsag
|
||||
| RctType::BulletproofsPlus => {
|
||||
if outputs <= MAX_BULLETPROOFS_OUTPUTS {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(TransactionError::InvalidNumberOfOutputs)
|
||||
}
|
||||
}
|
||||
_ => Ok(()),
|
||||
if bp_or_bpp && outputs > MAX_BULLETPROOFS_OUTPUTS {
|
||||
Err(TransactionError::InvalidNumberOfOutputs)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -239,11 +231,11 @@ fn check_outputs_semantics(
|
|||
outputs: &[Output],
|
||||
hf: &HardFork,
|
||||
tx_version: &TxVersion,
|
||||
rct_type: &RctType,
|
||||
bp_or_bpp: bool,
|
||||
) -> Result<u64, TransactionError> {
|
||||
check_output_types(outputs, hf)?;
|
||||
check_output_keys(outputs)?;
|
||||
check_number_of_outputs(outputs.len(), hf, tx_version, rct_type)?;
|
||||
check_number_of_outputs(outputs.len(), hf, tx_version, bp_or_bpp)?;
|
||||
|
||||
sum_outputs(outputs, hf, tx_version)
|
||||
}
|
||||
|
@ -255,14 +247,14 @@ fn check_outputs_semantics(
|
|||
/// <https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html>
|
||||
pub fn output_unlocked(
|
||||
time_lock: &Timelock,
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
current_time_lock_timestamp: u64,
|
||||
hf: &HardFork,
|
||||
) -> bool {
|
||||
match *time_lock {
|
||||
Timelock::None => true,
|
||||
Timelock::Block(unlock_height) => {
|
||||
check_block_time_lock(unlock_height.try_into().unwrap(), current_chain_height)
|
||||
check_block_time_lock(unlock_height, current_chain_height)
|
||||
}
|
||||
Timelock::Time(unlock_time) => {
|
||||
check_timestamp_time_lock(unlock_time, current_time_lock_timestamp, hf)
|
||||
|
@ -273,7 +265,7 @@ pub fn output_unlocked(
|
|||
/// Returns if a locked output, which uses a block height, can be spent.
|
||||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions/unlock_time.html#block-height>
|
||||
fn check_block_time_lock(unlock_height: u64, current_chain_height: u64) -> bool {
|
||||
fn check_block_time_lock(unlock_height: usize, current_chain_height: usize) -> bool {
|
||||
// current_chain_height = 1 + top height
|
||||
unlock_height <= current_chain_height
|
||||
}
|
||||
|
@ -297,7 +289,7 @@ fn check_timestamp_time_lock(
|
|||
/// <https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#the-output-must-not-be-locked>
|
||||
fn check_all_time_locks(
|
||||
time_locks: &[Timelock],
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
current_time_lock_timestamp: u64,
|
||||
hf: &HardFork,
|
||||
) -> Result<(), TransactionError> {
|
||||
|
@ -442,8 +434,8 @@ fn check_inputs_sorted(inputs: &[Input], hf: &HardFork) -> Result<(), Transactio
|
|||
///
|
||||
/// ref: <https://monero-book.cuprate.org/consensus_rules/transactions/inputs.html#10-block-lock>
|
||||
fn check_10_block_lock(
|
||||
youngest_used_out_height: u64,
|
||||
current_chain_height: u64,
|
||||
youngest_used_out_height: usize,
|
||||
current_chain_height: usize,
|
||||
hf: &HardFork,
|
||||
) -> Result<(), TransactionError> {
|
||||
if hf >= &HardFork::V12 {
|
||||
|
@ -510,7 +502,7 @@ fn check_inputs_semantics(inputs: &[Input], hf: &HardFork) -> Result<u64, Transa
|
|||
fn check_inputs_contextual(
|
||||
inputs: &[Input],
|
||||
tx_ring_members_info: &TxRingMembersInfo,
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
hf: &HardFork,
|
||||
) -> Result<(), TransactionError> {
|
||||
// This rule is not contained in monero-core explicitly, but it is enforced by how Monero picks ring members.
|
||||
|
@ -615,28 +607,41 @@ pub fn check_transaction_semantic(
|
|||
Err(TransactionError::TooBig)?;
|
||||
}
|
||||
|
||||
let tx_version = TxVersion::from_raw(tx.prefix.version)
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?;
|
||||
let tx_version =
|
||||
TxVersion::from_raw(tx.version()).ok_or(TransactionError::TransactionVersionInvalid)?;
|
||||
|
||||
let outputs_sum = check_outputs_semantics(
|
||||
&tx.prefix.outputs,
|
||||
hf,
|
||||
&tx_version,
|
||||
&tx.rct_signatures.rct_type(),
|
||||
)?;
|
||||
let inputs_sum = check_inputs_semantics(&tx.prefix.inputs, hf)?;
|
||||
let bp_or_bpp = match tx {
|
||||
Transaction::V2 {
|
||||
proofs: Some(proofs),
|
||||
..
|
||||
} => match proofs.rct_type() {
|
||||
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => false,
|
||||
RctType::MlsagBulletproofs
|
||||
| RctType::MlsagBulletproofsCompactAmount
|
||||
| RctType::ClsagBulletproof
|
||||
| RctType::ClsagBulletproofPlus => true,
|
||||
},
|
||||
Transaction::V2 { proofs: None, .. } | Transaction::V1 { .. } => false,
|
||||
};
|
||||
|
||||
let fee = match tx_version {
|
||||
TxVersion::RingSignatures => {
|
||||
let outputs_sum = check_outputs_semantics(&tx.prefix().outputs, hf, &tx_version, bp_or_bpp)?;
|
||||
let inputs_sum = check_inputs_semantics(&tx.prefix().inputs, hf)?;
|
||||
|
||||
let fee = match tx {
|
||||
Transaction::V1 { .. } => {
|
||||
if outputs_sum >= inputs_sum {
|
||||
Err(TransactionError::OutputsTooHigh)?;
|
||||
}
|
||||
inputs_sum - outputs_sum
|
||||
}
|
||||
TxVersion::RingCT => {
|
||||
ring_ct::ring_ct_semantic_checks(tx, tx_hash, verifier, hf)?;
|
||||
Transaction::V2 { proofs, .. } => {
|
||||
let proofs = proofs
|
||||
.as_ref()
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?;
|
||||
|
||||
tx.rct_signatures.base.fee
|
||||
ring_ct::ring_ct_semantic_checks(proofs, tx_hash, verifier, hf)?;
|
||||
|
||||
proofs.base.fee
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -654,15 +659,15 @@ pub fn check_transaction_semantic(
|
|||
pub fn check_transaction_contextual(
|
||||
tx: &Transaction,
|
||||
tx_ring_members_info: &TxRingMembersInfo,
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
current_time_lock_timestamp: u64,
|
||||
hf: &HardFork,
|
||||
) -> Result<(), TransactionError> {
|
||||
let tx_version = TxVersion::from_raw(tx.prefix.version)
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?;
|
||||
let tx_version =
|
||||
TxVersion::from_raw(tx.version()).ok_or(TransactionError::TransactionVersionInvalid)?;
|
||||
|
||||
check_inputs_contextual(
|
||||
&tx.prefix.inputs,
|
||||
&tx.prefix().inputs,
|
||||
tx_ring_members_info,
|
||||
current_chain_height,
|
||||
hf,
|
||||
|
@ -676,17 +681,22 @@ pub fn check_transaction_contextual(
|
|||
hf,
|
||||
)?;
|
||||
|
||||
match tx_version {
|
||||
TxVersion::RingSignatures => ring_signatures::check_input_signatures(
|
||||
&tx.prefix.inputs,
|
||||
&tx.signatures,
|
||||
match &tx {
|
||||
Transaction::V1 { prefix, signatures } => ring_signatures::check_input_signatures(
|
||||
&prefix.inputs,
|
||||
signatures,
|
||||
&tx_ring_members_info.rings,
|
||||
&tx.signature_hash(),
|
||||
// This will only return None on v2 miner txs.
|
||||
&tx.signature_hash()
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?,
|
||||
),
|
||||
TxVersion::RingCT => Ok(ring_ct::check_input_signatures(
|
||||
&tx.signature_hash(),
|
||||
&tx.prefix.inputs,
|
||||
&tx.rct_signatures,
|
||||
Transaction::V2 { prefix, proofs } => Ok(ring_ct::check_input_signatures(
|
||||
&tx.signature_hash()
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?,
|
||||
&prefix.inputs,
|
||||
proofs
|
||||
.as_ref()
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?,
|
||||
&tx_ring_members_info.rings,
|
||||
)?),
|
||||
}
|
||||
|
|
|
@ -70,7 +70,7 @@ pub struct TxRingMembersInfo {
|
|||
pub rings: Rings,
|
||||
/// Information on the structure of the decoys, must be [`None`] for txs before [`HardFork::V1`]
|
||||
pub decoy_info: Option<DecoyInfo>,
|
||||
pub youngest_used_out_height: u64,
|
||||
pub youngest_used_out_height: usize,
|
||||
pub time_locked_outs: Vec<Timelock>,
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
use curve25519_dalek::{EdwardsPoint, Scalar};
|
||||
use hex_literal::hex;
|
||||
use monero_serai::{
|
||||
generators::H,
|
||||
ringct::{
|
||||
clsag::ClsagError,
|
||||
mlsag::{AggregateRingMatrixBuilder, MlsagError, RingMatrix},
|
||||
RctPrunable, RctSignatures, RctType,
|
||||
RctProofs, RctPrunable, RctType,
|
||||
},
|
||||
transaction::{Input, Transaction},
|
||||
H,
|
||||
transaction::Input,
|
||||
};
|
||||
use rand::thread_rng;
|
||||
#[cfg(feature = "rayon")]
|
||||
|
@ -48,12 +48,12 @@ fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(),
|
|||
use RctType as T;
|
||||
|
||||
match ty {
|
||||
T::MlsagAggregate | T::MlsagIndividual if hf >= F::V4 && hf < F::V9 => Ok(()),
|
||||
T::Bulletproofs if hf >= F::V8 && hf < F::V11 => Ok(()),
|
||||
T::BulletproofsCompactAmount if hf >= F::V10 && hf < F::V14 => Ok(()),
|
||||
T::BulletproofsCompactAmount if GRANDFATHERED_TRANSACTIONS.contains(tx_hash) => Ok(()),
|
||||
T::Clsag if hf >= F::V13 && hf < F::V16 => Ok(()),
|
||||
T::BulletproofsPlus if hf >= F::V15 => Ok(()),
|
||||
T::AggregateMlsagBorromean | T::MlsagBorromean if hf >= F::V4 && hf < F::V9 => Ok(()),
|
||||
T::MlsagBulletproofs if hf >= F::V8 && hf < F::V11 => Ok(()),
|
||||
T::MlsagBulletproofsCompactAmount if hf >= F::V10 && hf < F::V14 => Ok(()),
|
||||
T::MlsagBulletproofsCompactAmount if GRANDFATHERED_TRANSACTIONS.contains(tx_hash) => Ok(()),
|
||||
T::ClsagBulletproof if hf >= F::V13 && hf < F::V16 => Ok(()),
|
||||
T::ClsagBulletproofPlus if hf >= F::V15 => Ok(()),
|
||||
_ => Err(RingCTError::TypeNotAllowed),
|
||||
}
|
||||
}
|
||||
|
@ -61,20 +61,22 @@ fn check_rct_type(ty: &RctType, hf: HardFork, tx_hash: &[u8; 32]) -> Result<(),
|
|||
/// Checks that the pseudo-outs sum to the same point as the output commitments.
|
||||
///
|
||||
/// <https://monero-book.cuprate.org/consensus_rules/ring_ct.html#pseudo-outs-outpks-balance>
|
||||
fn simple_type_balances(rct_sig: &RctSignatures) -> Result<(), RingCTError> {
|
||||
let pseudo_outs = if rct_sig.rct_type() == RctType::MlsagIndividual {
|
||||
fn simple_type_balances(rct_sig: &RctProofs) -> Result<(), RingCTError> {
|
||||
let pseudo_outs = if rct_sig.rct_type() == RctType::MlsagBorromean {
|
||||
&rct_sig.base.pseudo_outs
|
||||
} else {
|
||||
match &rct_sig.prunable {
|
||||
RctPrunable::Clsag { pseudo_outs, .. }
|
||||
| RctPrunable::MlsagBulletproofsCompactAmount { pseudo_outs, .. }
|
||||
| RctPrunable::MlsagBulletproofs { pseudo_outs, .. } => pseudo_outs,
|
||||
_ => panic!("RingCT type is not simple!"),
|
||||
RctPrunable::MlsagBorromean { .. } => &rct_sig.base.pseudo_outs,
|
||||
RctPrunable::AggregateMlsagBorromean { .. } => panic!("RingCT type is not simple!"),
|
||||
}
|
||||
};
|
||||
|
||||
let sum_inputs = pseudo_outs.iter().sum::<EdwardsPoint>();
|
||||
let sum_outputs = rct_sig.base.commitments.iter().sum::<EdwardsPoint>()
|
||||
+ Scalar::from(rct_sig.base.fee) * H();
|
||||
let sum_outputs =
|
||||
rct_sig.base.commitments.iter().sum::<EdwardsPoint>() + Scalar::from(rct_sig.base.fee) * *H;
|
||||
|
||||
if sum_inputs == sum_outputs {
|
||||
Ok(())
|
||||
|
@ -89,13 +91,12 @@ fn simple_type_balances(rct_sig: &RctSignatures) -> Result<(), RingCTError> {
|
|||
/// <https://monero-book.cuprate.org/consensus_rules/ring_ct/bulletproofs.html>
|
||||
/// <https://monero-book.cuprate.org/consensus_rules/ring_ct/bulletproofs+.html>
|
||||
fn check_output_range_proofs(
|
||||
rct_sig: &RctSignatures,
|
||||
proofs: &RctProofs,
|
||||
mut verifier: impl BatchVerifier,
|
||||
) -> Result<(), RingCTError> {
|
||||
let commitments = &rct_sig.base.commitments;
|
||||
let commitments = &proofs.base.commitments;
|
||||
|
||||
match &rct_sig.prunable {
|
||||
RctPrunable::Null => Err(RingCTError::TypeNotAllowed)?,
|
||||
match &proofs.prunable {
|
||||
RctPrunable::MlsagBorromean { borromean, .. }
|
||||
| RctPrunable::AggregateMlsagBorromean { borromean, .. } => try_par_iter(borromean)
|
||||
.zip(commitments)
|
||||
|
@ -106,10 +107,11 @@ fn check_output_range_proofs(
|
|||
Err(RingCTError::BorromeanRangeInvalid)
|
||||
}
|
||||
}),
|
||||
RctPrunable::MlsagBulletproofs { bulletproofs, .. }
|
||||
| RctPrunable::Clsag { bulletproofs, .. } => {
|
||||
RctPrunable::MlsagBulletproofs { bulletproof, .. }
|
||||
| RctPrunable::MlsagBulletproofsCompactAmount { bulletproof, .. }
|
||||
| RctPrunable::Clsag { bulletproof, .. } => {
|
||||
if verifier.queue_statement(|verifier| {
|
||||
bulletproofs.batch_verify(&mut thread_rng(), verifier, (), commitments)
|
||||
bulletproof.batch_verify(&mut thread_rng(), verifier, commitments)
|
||||
}) {
|
||||
Ok(())
|
||||
} else {
|
||||
|
@ -120,18 +122,18 @@ fn check_output_range_proofs(
|
|||
}
|
||||
|
||||
pub(crate) fn ring_ct_semantic_checks(
|
||||
tx: &Transaction,
|
||||
proofs: &RctProofs,
|
||||
tx_hash: &[u8; 32],
|
||||
verifier: impl BatchVerifier,
|
||||
hf: &HardFork,
|
||||
) -> Result<(), RingCTError> {
|
||||
let rct_type = tx.rct_signatures.rct_type();
|
||||
let rct_type = proofs.rct_type();
|
||||
|
||||
check_rct_type(&rct_type, *hf, tx_hash)?;
|
||||
check_output_range_proofs(&tx.rct_signatures, verifier)?;
|
||||
check_output_range_proofs(proofs, verifier)?;
|
||||
|
||||
if rct_type != RctType::MlsagAggregate {
|
||||
simple_type_balances(&tx.rct_signatures)?;
|
||||
if rct_type != RctType::AggregateMlsagBorromean {
|
||||
simple_type_balances(proofs)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -144,7 +146,7 @@ pub(crate) fn ring_ct_semantic_checks(
|
|||
pub(crate) fn check_input_signatures(
|
||||
msg: &[u8; 32],
|
||||
inputs: &[Input],
|
||||
rct_sig: &RctSignatures,
|
||||
proofs: &RctProofs,
|
||||
rings: &Rings,
|
||||
) -> Result<(), RingCTError> {
|
||||
let Rings::RingCT(rings) = rings else {
|
||||
|
@ -155,15 +157,15 @@ pub(crate) fn check_input_signatures(
|
|||
Err(RingCTError::RingInvalid)?;
|
||||
}
|
||||
|
||||
let pseudo_outs = match &rct_sig.prunable {
|
||||
let pseudo_outs = match &proofs.prunable {
|
||||
RctPrunable::MlsagBulletproofs { pseudo_outs, .. }
|
||||
| RctPrunable::MlsagBulletproofsCompactAmount { pseudo_outs, .. }
|
||||
| RctPrunable::Clsag { pseudo_outs, .. } => pseudo_outs.as_slice(),
|
||||
RctPrunable::MlsagBorromean { .. } => rct_sig.base.pseudo_outs.as_slice(),
|
||||
RctPrunable::AggregateMlsagBorromean { .. } | RctPrunable::Null => &[],
|
||||
RctPrunable::MlsagBorromean { .. } => proofs.base.pseudo_outs.as_slice(),
|
||||
RctPrunable::AggregateMlsagBorromean { .. } => &[],
|
||||
};
|
||||
|
||||
match &rct_sig.prunable {
|
||||
RctPrunable::Null => Err(RingCTError::TypeNotAllowed)?,
|
||||
match &proofs.prunable {
|
||||
RctPrunable::AggregateMlsagBorromean { mlsag, .. } => {
|
||||
let key_images = inputs
|
||||
.iter()
|
||||
|
@ -176,11 +178,14 @@ pub(crate) fn check_input_signatures(
|
|||
.collect::<Vec<_>>();
|
||||
|
||||
let mut matrix =
|
||||
AggregateRingMatrixBuilder::new(&rct_sig.base.commitments, rct_sig.base.fee);
|
||||
AggregateRingMatrixBuilder::new(&proofs.base.commitments, proofs.base.fee);
|
||||
|
||||
rings.iter().try_for_each(|ring| matrix.push_ring(ring))?;
|
||||
|
||||
Ok(mlsag.verify(msg, &matrix.build()?, &key_images)?)
|
||||
}
|
||||
RctPrunable::MlsagBorromean { mlsags, .. }
|
||||
| RctPrunable::MlsagBulletproofsCompactAmount { mlsags, .. }
|
||||
| RctPrunable::MlsagBulletproofs { mlsags, .. } => try_par_iter(mlsags)
|
||||
.zip(pseudo_outs)
|
||||
.zip(inputs)
|
||||
|
@ -216,18 +221,21 @@ mod tests {
|
|||
|
||||
#[test]
|
||||
fn grandfathered_bulletproofs2() {
|
||||
assert!(
|
||||
check_rct_type(&RctType::BulletproofsCompactAmount, HardFork::V14, &[0; 32]).is_err()
|
||||
);
|
||||
assert!(check_rct_type(
|
||||
&RctType::MlsagBulletproofsCompactAmount,
|
||||
HardFork::V14,
|
||||
&[0; 32]
|
||||
)
|
||||
.is_err());
|
||||
|
||||
assert!(check_rct_type(
|
||||
&RctType::BulletproofsCompactAmount,
|
||||
&RctType::MlsagBulletproofsCompactAmount,
|
||||
HardFork::V14,
|
||||
&GRANDFATHERED_TRANSACTIONS[0]
|
||||
)
|
||||
.is_ok());
|
||||
assert!(check_rct_type(
|
||||
&RctType::BulletproofsCompactAmount,
|
||||
&RctType::MlsagBulletproofsCompactAmount,
|
||||
HardFork::V14,
|
||||
&GRANDFATHERED_TRANSACTIONS[1]
|
||||
)
|
||||
|
|
|
@ -97,31 +97,6 @@ fn test_torsion_ki() {
|
|||
}
|
||||
}
|
||||
|
||||
/// Returns a strategy that resolves to a [`RctType`] that uses
|
||||
/// BPs(+).
|
||||
#[allow(unreachable_code)]
|
||||
#[allow(clippy::diverging_sub_expression)]
|
||||
fn bulletproof_rct_type() -> BoxedStrategy<RctType> {
|
||||
return prop_oneof![
|
||||
Just(RctType::Bulletproofs),
|
||||
Just(RctType::BulletproofsCompactAmount),
|
||||
Just(RctType::Clsag),
|
||||
Just(RctType::BulletproofsPlus),
|
||||
]
|
||||
.boxed();
|
||||
|
||||
// Here to make sure this is updated when needed.
|
||||
match unreachable!() {
|
||||
RctType::Null => {}
|
||||
RctType::MlsagAggregate => {}
|
||||
RctType::MlsagIndividual => {}
|
||||
RctType::Bulletproofs => {}
|
||||
RctType::BulletproofsCompactAmount => {}
|
||||
RctType::Clsag => {}
|
||||
RctType::BulletproofsPlus => {}
|
||||
};
|
||||
}
|
||||
|
||||
prop_compose! {
|
||||
/// Returns a valid prime-order point.
|
||||
fn random_point()(bytes in any::<[u8; 32]>()) -> EdwardsPoint {
|
||||
|
@ -240,13 +215,13 @@ proptest! {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_valid_number_of_outputs(valid_numb_outs in 2..17_usize, rct_type in bulletproof_rct_type()) {
|
||||
prop_assert!(check_number_of_outputs(valid_numb_outs, &HardFork::V16, &TxVersion::RingCT, &rct_type).is_ok());
|
||||
fn test_valid_number_of_outputs(valid_numb_outs in 2..17_usize) {
|
||||
prop_assert!(check_number_of_outputs(valid_numb_outs, &HardFork::V16, &TxVersion::RingCT, true).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_invalid_number_of_outputs(numb_outs in 17..usize::MAX, rct_type in bulletproof_rct_type()) {
|
||||
prop_assert!(check_number_of_outputs(numb_outs, &HardFork::V16, &TxVersion::RingCT, &rct_type).is_err());
|
||||
fn test_invalid_number_of_outputs(numb_outs in 17..usize::MAX) {
|
||||
prop_assert!(check_number_of_outputs(numb_outs, &HardFork::V16, &TxVersion::RingCT, true).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -256,7 +231,7 @@ proptest! {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_unlock_time(height in 1..u64::MAX) {
|
||||
fn test_block_unlock_time(height in 1..usize::MAX) {
|
||||
prop_assert!(check_block_time_lock(height, height));
|
||||
prop_assert!(!check_block_time_lock(height, height - 1));
|
||||
prop_assert!(check_block_time_lock(height, height+1));
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
use std::{cell::RefCell, ops::DerefMut};
|
||||
|
||||
use multiexp::BatchVerifier as InternalBatchVerifier;
|
||||
use monero_serai::ringct::bulletproofs::BatchVerifier as InternalBatchVerifier;
|
||||
use rayon::prelude::*;
|
||||
use thread_local::ThreadLocal;
|
||||
|
||||
use cuprate_consensus_rules::batch_verifier::BatchVerifier;
|
||||
|
||||
/// A multithreaded batch verifier.
|
||||
pub struct MultiThreadedBatchVerifier {
|
||||
internal: ThreadLocal<RefCell<InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>>>,
|
||||
internal: ThreadLocal<RefCell<InternalBatchVerifier>>,
|
||||
}
|
||||
|
||||
impl MultiThreadedBatchVerifier {
|
||||
|
@ -22,19 +24,22 @@ impl MultiThreadedBatchVerifier {
|
|||
.into_iter()
|
||||
.map(RefCell::into_inner)
|
||||
.par_bridge()
|
||||
.find_any(|batch_verifier| !batch_verifier.verify_vartime())
|
||||
.is_none()
|
||||
.try_for_each(|batch_verifier| {
|
||||
if batch_verifier.verify() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(())
|
||||
}
|
||||
})
|
||||
.is_ok()
|
||||
}
|
||||
}
|
||||
|
||||
impl cuprate_consensus_rules::batch_verifier::BatchVerifier for &'_ MultiThreadedBatchVerifier {
|
||||
fn queue_statement<R>(
|
||||
&mut self,
|
||||
stmt: impl FnOnce(&mut InternalBatchVerifier<(), dalek_ff_group::EdwardsPoint>) -> R,
|
||||
) -> R {
|
||||
impl BatchVerifier for &'_ MultiThreadedBatchVerifier {
|
||||
fn queue_statement<R>(&mut self, stmt: impl FnOnce(&mut InternalBatchVerifier) -> R) -> R {
|
||||
let mut verifier = self
|
||||
.internal
|
||||
.get_or(|| RefCell::new(InternalBatchVerifier::new(32)))
|
||||
.get_or(|| RefCell::new(InternalBatchVerifier::new()))
|
||||
.borrow_mut();
|
||||
|
||||
stmt(verifier.deref_mut())
|
||||
|
|
|
@ -57,7 +57,7 @@ pub struct PreparedBlockExPow {
|
|||
/// The block's hash.
|
||||
pub block_hash: [u8; 32],
|
||||
/// The height of the block.
|
||||
pub height: u64,
|
||||
pub height: usize,
|
||||
|
||||
/// The weight of the block's miner transaction.
|
||||
pub miner_tx_weight: usize,
|
||||
|
@ -74,7 +74,7 @@ impl PreparedBlockExPow {
|
|||
let (hf_version, hf_vote) =
|
||||
HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?;
|
||||
|
||||
let Some(Input::Gen(height)) = block.miner_tx.prefix.inputs.first() else {
|
||||
let Some(Input::Gen(height)) = block.miner_transaction.prefix().inputs.first() else {
|
||||
Err(ConsensusError::Block(BlockError::MinerTxError(
|
||||
MinerTxError::InputNotOfTypeGen,
|
||||
)))?
|
||||
|
@ -88,7 +88,7 @@ impl PreparedBlockExPow {
|
|||
block_hash: block.hash(),
|
||||
height: *height,
|
||||
|
||||
miner_tx_weight: block.miner_tx.weight(),
|
||||
miner_tx_weight: block.miner_transaction.weight(),
|
||||
block,
|
||||
})
|
||||
}
|
||||
|
@ -128,7 +128,7 @@ impl PreparedBlock {
|
|||
let (hf_version, hf_vote) =
|
||||
HardFork::from_block_header(&block.header).map_err(BlockError::HardForkError)?;
|
||||
|
||||
let [Input::Gen(height)] = &block.miner_tx.prefix.inputs[..] else {
|
||||
let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else {
|
||||
Err(ConsensusError::Block(BlockError::MinerTxError(
|
||||
MinerTxError::InputNotOfTypeGen,
|
||||
)))?
|
||||
|
@ -142,12 +142,12 @@ impl PreparedBlock {
|
|||
block_hash: block.hash(),
|
||||
pow_hash: calculate_pow_hash(
|
||||
randomx_vm,
|
||||
&block.serialize_hashable(),
|
||||
&block.serialize_pow_hash(),
|
||||
*height,
|
||||
&hf_version,
|
||||
)?,
|
||||
|
||||
miner_tx_weight: block.miner_tx.weight(),
|
||||
miner_tx_weight: block.miner_transaction.weight(),
|
||||
block,
|
||||
})
|
||||
}
|
||||
|
@ -172,12 +172,12 @@ impl PreparedBlock {
|
|||
block_hash: block.block_hash,
|
||||
pow_hash: calculate_pow_hash(
|
||||
randomx_vm,
|
||||
&block.block.serialize_hashable(),
|
||||
&block.block.serialize_pow_hash(),
|
||||
block.height,
|
||||
&block.hf_version,
|
||||
)?,
|
||||
|
||||
miner_tx_weight: block.block.miner_tx.weight(),
|
||||
miner_tx_weight: block.block.miner_transaction.weight(),
|
||||
block: block.block,
|
||||
})
|
||||
}
|
||||
|
@ -359,8 +359,8 @@ where
|
|||
|
||||
// Set up the block and just pass it to [`verify_prepped_main_chain_block`]
|
||||
|
||||
// We just use the raw `major_version` here, no need to turn it into a `HardFork`.
|
||||
let rx_vms = if block.header.major_version < 12 {
|
||||
// We just use the raw `hardfork_version` here, no need to turn it into a `HardFork`.
|
||||
let rx_vms = if block.header.hardfork_version < 12 {
|
||||
HashMap::new()
|
||||
} else {
|
||||
let BlockChainContextResponse::RxVms(rx_vms) = context_svc
|
||||
|
@ -443,12 +443,12 @@ where
|
|||
check_block_pow(&prepped_block.pow_hash, context.next_difficulty)
|
||||
.map_err(ConsensusError::Block)?;
|
||||
|
||||
if prepped_block.block.txs.len() != txs.len() {
|
||||
if prepped_block.block.transactions.len() != txs.len() {
|
||||
return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect);
|
||||
}
|
||||
|
||||
if !prepped_block.block.txs.is_empty() {
|
||||
for (expected_tx_hash, tx) in prepped_block.block.txs.iter().zip(txs.iter()) {
|
||||
if !prepped_block.block.transactions.is_empty() {
|
||||
for (expected_tx_hash, tx) in prepped_block.block.transactions.iter().zip(txs.iter()) {
|
||||
if expected_tx_hash != &tx.tx_hash {
|
||||
return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect);
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ where
|
|||
};
|
||||
|
||||
// Check if the block's miner input is formed correctly.
|
||||
let [Input::Gen(height)] = &block.miner_tx.prefix.inputs[..] else {
|
||||
let [Input::Gen(height)] = &block.miner_transaction.prefix().inputs[..] else {
|
||||
Err(ConsensusError::Block(BlockError::MinerTxError(
|
||||
MinerTxError::InputNotOfTypeGen,
|
||||
)))?
|
||||
|
@ -79,7 +79,7 @@ where
|
|||
let prepped_block = {
|
||||
let rx_vm = alt_rx_vm(
|
||||
alt_context_cache.chain_height,
|
||||
block.header.major_version,
|
||||
block.header.hardfork_version,
|
||||
alt_context_cache.parent_chain,
|
||||
&mut alt_context_cache,
|
||||
&mut context_svc,
|
||||
|
@ -188,7 +188,7 @@ where
|
|||
///
|
||||
/// If the `hf` is less than 12 (the height RX activates), then [`None`] is returned.
|
||||
async fn alt_rx_vm<C>(
|
||||
block_height: u64,
|
||||
block_height: usize,
|
||||
hf: u8,
|
||||
parent_chain: Chain,
|
||||
alt_chain_context: &mut AltChainContextCache,
|
||||
|
|
|
@ -12,14 +12,14 @@ pub(crate) fn pull_ordered_transactions(
|
|||
block: &Block,
|
||||
mut txs: HashMap<[u8; 32], TransactionVerificationData>,
|
||||
) -> Result<Vec<TransactionVerificationData>, ExtendedConsensusError> {
|
||||
if block.txs.len() != txs.len() {
|
||||
if block.transactions.len() != txs.len() {
|
||||
return Err(ExtendedConsensusError::TxsIncludedWithBlockIncorrect);
|
||||
}
|
||||
|
||||
let mut ordered_txs = Vec::with_capacity(txs.len());
|
||||
|
||||
if !block.txs.is_empty() {
|
||||
for tx_hash in &block.txs {
|
||||
if !block.transactions.is_empty() {
|
||||
for tx_hash in &block.transactions {
|
||||
let tx = txs
|
||||
.remove(tx_hash)
|
||||
.ok_or(ExtendedConsensusError::TxsIncludedWithBlockIncorrect)?;
|
||||
|
|
|
@ -202,7 +202,7 @@ pub struct NewBlockData {
|
|||
/// The blocks hash.
|
||||
pub block_hash: [u8; 32],
|
||||
/// The blocks height.
|
||||
pub height: u64,
|
||||
pub height: usize,
|
||||
/// The blocks timestamp.
|
||||
pub timestamp: u64,
|
||||
/// The blocks weight.
|
||||
|
@ -246,7 +246,7 @@ pub enum BlockChainContextRequest {
|
|||
/// # Panics
|
||||
///
|
||||
/// This will panic if the number of blocks will pop the genesis block.
|
||||
numb_blocks: u64,
|
||||
numb_blocks: usize,
|
||||
},
|
||||
/// Clear the alt chain context caches.
|
||||
ClearAltCache,
|
||||
|
@ -289,7 +289,7 @@ pub enum BlockChainContextRequest {
|
|||
/// handle getting the randomX VM of an alt chain.
|
||||
AltChainRxVM {
|
||||
/// The height the RandomX VM is needed for.
|
||||
height: u64,
|
||||
height: usize,
|
||||
/// The chain to look in for the seed.
|
||||
chain: Chain,
|
||||
/// An internal token to prevent external crates calling this request.
|
||||
|
@ -313,7 +313,7 @@ pub enum BlockChainContextResponse {
|
|||
/// Blockchain context response.
|
||||
Context(BlockChainContext),
|
||||
/// A map of seed height to RandomX VMs.
|
||||
RxVms(HashMap<u64, Arc<RandomXVM>>),
|
||||
RxVms(HashMap<usize, Arc<RandomXVM>>),
|
||||
/// A list of difficulties.
|
||||
BatchDifficulties(Vec<u128>),
|
||||
/// An alt chain context cache.
|
||||
|
|
|
@ -4,7 +4,7 @@ use tower::ServiceExt;
|
|||
|
||||
use cuprate_consensus_rules::{blocks::BlockError, ConsensusError};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain, ChainId,
|
||||
};
|
||||
|
||||
|
@ -32,10 +32,10 @@ pub struct AltChainContextCache {
|
|||
pub difficulty_cache: Option<DifficultyCache>,
|
||||
|
||||
/// A cached RX VM.
|
||||
pub cached_rx_vm: Option<(u64, Arc<RandomXVM>)>,
|
||||
pub cached_rx_vm: Option<(usize, Arc<RandomXVM>)>,
|
||||
|
||||
/// The chain height of the alt chain.
|
||||
pub chain_height: u64,
|
||||
pub chain_height: usize,
|
||||
/// The top hash of the alt chain.
|
||||
pub top_hash: [u8; 32],
|
||||
/// The [`ChainID`] of the alt chain.
|
||||
|
@ -48,7 +48,7 @@ impl AltChainContextCache {
|
|||
/// Add a new block to the cache.
|
||||
pub fn add_new_block(
|
||||
&mut self,
|
||||
height: u64,
|
||||
height: usize,
|
||||
block_hash: [u8; 32],
|
||||
block_weight: usize,
|
||||
long_term_block_weight: usize,
|
||||
|
@ -100,8 +100,9 @@ impl AltChainMap {
|
|||
}
|
||||
|
||||
// find the block with hash == prev_id.
|
||||
let BCResponse::FindBlock(res) =
|
||||
database.oneshot(BCReadRequest::FindBlock(prev_id)).await?
|
||||
let BlockchainResponse::FindBlock(res) = database
|
||||
.oneshot(BlockchainReadRequest::FindBlock(prev_id))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database returned wrong response");
|
||||
};
|
||||
|
@ -130,10 +131,10 @@ pub async fn get_alt_chain_difficulty_cache<D: Database + Clone>(
|
|||
mut database: D,
|
||||
) -> Result<DifficultyCache, ExtendedConsensusError> {
|
||||
// find the block with hash == prev_id.
|
||||
let BCResponse::FindBlock(res) = database
|
||||
let BlockchainResponse::FindBlock(res) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::FindBlock(prev_id))
|
||||
.call(BlockchainReadRequest::FindBlock(prev_id))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database returned wrong response");
|
||||
|
@ -177,10 +178,10 @@ pub async fn get_alt_chain_weight_cache<D: Database + Clone>(
|
|||
mut database: D,
|
||||
) -> Result<BlockWeightsCache, ExtendedConsensusError> {
|
||||
// find the block with hash == prev_id.
|
||||
let BCResponse::FindBlock(res) = database
|
||||
let BlockchainResponse::FindBlock(res) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::FindBlock(prev_id))
|
||||
.call(BlockchainReadRequest::FindBlock(prev_id))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database returned wrong response");
|
||||
|
|
|
@ -13,7 +13,7 @@ use tracing::instrument;
|
|||
|
||||
use cuprate_helper::num::median;
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain,
|
||||
};
|
||||
|
||||
|
@ -48,8 +48,8 @@ impl DifficultyCacheConfig {
|
|||
}
|
||||
|
||||
/// Returns the total amount of blocks we need to track to calculate difficulty
|
||||
pub fn total_block_count(&self) -> u64 {
|
||||
(self.window + self.lag).try_into().unwrap()
|
||||
pub fn total_block_count(&self) -> usize {
|
||||
self.window + self.lag
|
||||
}
|
||||
|
||||
/// The amount of blocks we account for after removing the outliers.
|
||||
|
@ -78,7 +78,7 @@ pub struct DifficultyCache {
|
|||
/// The current cumulative difficulty of the chain.
|
||||
pub(crate) cumulative_difficulties: VecDeque<u128>,
|
||||
/// The last height we accounted for.
|
||||
pub(crate) last_accounted_height: u64,
|
||||
pub(crate) last_accounted_height: usize,
|
||||
/// The config
|
||||
pub(crate) config: DifficultyCacheConfig,
|
||||
}
|
||||
|
@ -87,7 +87,7 @@ impl DifficultyCache {
|
|||
/// Initialize the difficulty cache from the specified chain height.
|
||||
#[instrument(name = "init_difficulty_cache", level = "info", skip(database, config))]
|
||||
pub async fn init_from_chain_height<D: Database + Clone>(
|
||||
chain_height: u64,
|
||||
chain_height: usize,
|
||||
config: DifficultyCacheConfig,
|
||||
database: D,
|
||||
chain: Chain,
|
||||
|
@ -104,7 +104,7 @@ impl DifficultyCache {
|
|||
let (timestamps, cumulative_difficulties) =
|
||||
get_blocks_in_pow_info(database.clone(), block_start..chain_height, chain).await?;
|
||||
|
||||
debug_assert_eq!(timestamps.len() as u64, chain_height - block_start);
|
||||
debug_assert_eq!(timestamps.len(), chain_height - block_start);
|
||||
|
||||
tracing::info!(
|
||||
"Current chain height: {}, accounting for {} blocks timestamps",
|
||||
|
@ -132,14 +132,10 @@ impl DifficultyCache {
|
|||
#[instrument(name = "pop_blocks_diff_cache", skip_all, fields(numb_blocks = numb_blocks))]
|
||||
pub async fn pop_blocks_main_chain<D: Database + Clone>(
|
||||
&mut self,
|
||||
numb_blocks: u64,
|
||||
numb_blocks: usize,
|
||||
database: D,
|
||||
) -> Result<(), ExtendedConsensusError> {
|
||||
let Some(retained_blocks) = self
|
||||
.timestamps
|
||||
.len()
|
||||
.checked_sub(usize::try_from(numb_blocks).unwrap())
|
||||
else {
|
||||
let Some(retained_blocks) = self.timestamps.len().checked_sub(numb_blocks) else {
|
||||
// More blocks to pop than we have in the cache, so just restart a new cache.
|
||||
*self = Self::init_from_chain_height(
|
||||
self.last_accounted_height - numb_blocks + 1,
|
||||
|
@ -167,7 +163,7 @@ impl DifficultyCache {
|
|||
database,
|
||||
new_start_height
|
||||
// current_chain_height - self.timestamps.len() blocks are already in the cache.
|
||||
..(current_chain_height - u64::try_from(self.timestamps.len()).unwrap()),
|
||||
..(current_chain_height - self.timestamps.len()),
|
||||
Chain::Main,
|
||||
)
|
||||
.await?;
|
||||
|
@ -187,7 +183,7 @@ impl DifficultyCache {
|
|||
}
|
||||
|
||||
/// Add a new block to the difficulty cache.
|
||||
pub fn new_block(&mut self, height: u64, timestamp: u64, cumulative_difficulty: u128) {
|
||||
pub fn new_block(&mut self, height: usize, timestamp: u64, cumulative_difficulty: u128) {
|
||||
assert_eq!(self.last_accounted_height + 1, height);
|
||||
self.last_accounted_height += 1;
|
||||
|
||||
|
@ -199,7 +195,7 @@ impl DifficultyCache {
|
|||
self.cumulative_difficulties
|
||||
.push_back(cumulative_difficulty);
|
||||
|
||||
if u64::try_from(self.timestamps.len()).unwrap() > self.config.total_block_count() {
|
||||
if self.timestamps.len() > self.config.total_block_count() {
|
||||
self.timestamps.pop_front();
|
||||
self.cumulative_difficulties.pop_front();
|
||||
}
|
||||
|
@ -244,7 +240,7 @@ impl DifficultyCache {
|
|||
let last_cum_diff = cumulative_difficulties.back().copied().unwrap_or(1);
|
||||
cumulative_difficulties.push_back(last_cum_diff + *difficulties.last().unwrap());
|
||||
|
||||
if u64::try_from(timestamps.len()).unwrap() > self.config.total_block_count() {
|
||||
if timestamps.len() > self.config.total_block_count() {
|
||||
diff_info_popped.push((
|
||||
timestamps.pop_front().unwrap(),
|
||||
cumulative_difficulties.pop_front().unwrap(),
|
||||
|
@ -266,22 +262,21 @@ impl DifficultyCache {
|
|||
///
|
||||
/// Will return [`None`] if there aren't enough blocks.
|
||||
pub fn median_timestamp(&self, numb_blocks: usize) -> Option<u64> {
|
||||
let mut timestamps =
|
||||
if self.last_accounted_height + 1 == u64::try_from(numb_blocks).unwrap() {
|
||||
// if the chain height is equal to `numb_blocks` add the genesis block.
|
||||
// otherwise if the chain height is less than `numb_blocks` None is returned
|
||||
// and if it's more it would be excluded from calculations.
|
||||
let mut timestamps = self.timestamps.clone();
|
||||
// all genesis blocks have a timestamp of 0.
|
||||
// https://cuprate.github.io/monero-book/consensus_rules/genesis_block.html
|
||||
timestamps.push_front(0);
|
||||
timestamps.into()
|
||||
} else {
|
||||
self.timestamps
|
||||
.range(self.timestamps.len().checked_sub(numb_blocks)?..)
|
||||
.copied()
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
let mut timestamps = if self.last_accounted_height + 1 == numb_blocks {
|
||||
// if the chain height is equal to `numb_blocks` add the genesis block.
|
||||
// otherwise if the chain height is less than `numb_blocks` None is returned
|
||||
// and if it's more it would be excluded from calculations.
|
||||
let mut timestamps = self.timestamps.clone();
|
||||
// all genesis blocks have a timestamp of 0.
|
||||
// https://cuprate.github.io/monero-book/consensus_rules/genesis_block.html
|
||||
timestamps.push_front(0);
|
||||
timestamps.into()
|
||||
} else {
|
||||
self.timestamps
|
||||
.range(self.timestamps.len().checked_sub(numb_blocks)?..)
|
||||
.copied()
|
||||
.collect::<Vec<_>>()
|
||||
};
|
||||
timestamps.sort_unstable();
|
||||
debug_assert_eq!(timestamps.len(), numb_blocks);
|
||||
|
||||
|
@ -368,13 +363,13 @@ fn get_window_start_and_end(
|
|||
#[instrument(name = "get_blocks_timestamps", skip(database), level = "info")]
|
||||
async fn get_blocks_in_pow_info<D: Database + Clone>(
|
||||
database: D,
|
||||
block_heights: Range<u64>,
|
||||
block_heights: Range<usize>,
|
||||
chain: Chain,
|
||||
) -> Result<(VecDeque<u64>, VecDeque<u128>), ExtendedConsensusError> {
|
||||
tracing::info!("Getting blocks timestamps");
|
||||
|
||||
let BCResponse::BlockExtendedHeaderInRange(ext_header) = database
|
||||
.oneshot(BCReadRequest::BlockExtendedHeaderInRange(
|
||||
let BlockchainResponse::BlockExtendedHeaderInRange(ext_header) = database
|
||||
.oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
|
||||
block_heights,
|
||||
chain,
|
||||
))
|
||||
|
|
|
@ -5,7 +5,7 @@ use tracing::instrument;
|
|||
|
||||
use cuprate_consensus_rules::{HFVotes, HFsInfo, HardFork};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain,
|
||||
};
|
||||
|
||||
|
@ -14,7 +14,7 @@ use crate::{Database, ExtendedConsensusError};
|
|||
/// The default amount of hard-fork votes to track to decide on activation of a hard-fork.
|
||||
///
|
||||
/// ref: <https://cuprate.github.io/monero-docs/consensus_rules/hardforks.html#accepting-a-fork>
|
||||
const DEFAULT_WINDOW_SIZE: u64 = 10080; // supermajority window check length - a week
|
||||
const DEFAULT_WINDOW_SIZE: usize = 10080; // supermajority window check length - a week
|
||||
|
||||
/// Configuration for hard-forks.
|
||||
///
|
||||
|
@ -23,7 +23,7 @@ pub struct HardForkConfig {
|
|||
/// The network we are on.
|
||||
pub(crate) info: HFsInfo,
|
||||
/// The amount of votes we are taking into account to decide on a fork activation.
|
||||
pub(crate) window: u64,
|
||||
pub(crate) window: usize,
|
||||
}
|
||||
|
||||
impl HardForkConfig {
|
||||
|
@ -64,14 +64,14 @@ pub struct HardForkState {
|
|||
pub(crate) votes: HFVotes,
|
||||
|
||||
/// The last block height accounted for.
|
||||
pub(crate) last_height: u64,
|
||||
pub(crate) last_height: usize,
|
||||
}
|
||||
|
||||
impl HardForkState {
|
||||
/// Initialize the [`HardForkState`] from the specified chain height.
|
||||
#[instrument(name = "init_hardfork_state", skip(config, database), level = "info")]
|
||||
pub async fn init_from_chain_height<D: Database + Clone>(
|
||||
chain_height: u64,
|
||||
chain_height: usize,
|
||||
config: HardForkConfig,
|
||||
mut database: D,
|
||||
) -> Result<Self, ExtendedConsensusError> {
|
||||
|
@ -79,21 +79,17 @@ impl HardForkState {
|
|||
|
||||
let block_start = chain_height.saturating_sub(config.window);
|
||||
|
||||
let votes = get_votes_in_range(
|
||||
database.clone(),
|
||||
block_start..chain_height,
|
||||
usize::try_from(config.window).unwrap(),
|
||||
)
|
||||
.await?;
|
||||
let votes =
|
||||
get_votes_in_range(database.clone(), block_start..chain_height, config.window).await?;
|
||||
|
||||
if chain_height > config.window {
|
||||
debug_assert_eq!(votes.total_votes(), config.window)
|
||||
}
|
||||
|
||||
let BCResponse::BlockExtendedHeader(ext_header) = database
|
||||
let BlockchainResponse::BlockExtendedHeader(ext_header) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::BlockExtendedHeader(chain_height - 1))
|
||||
.call(BlockchainReadRequest::BlockExtendedHeader(chain_height - 1))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!");
|
||||
|
@ -129,7 +125,7 @@ impl HardForkState {
|
|||
/// This _must_ only be used on a main-chain cache.
|
||||
pub async fn pop_blocks_main_chain<D: Database + Clone>(
|
||||
&mut self,
|
||||
numb_blocks: u64,
|
||||
numb_blocks: usize,
|
||||
database: D,
|
||||
) -> Result<(), ExtendedConsensusError> {
|
||||
let Some(retained_blocks) = self.votes.total_votes().checked_sub(self.config.window) else {
|
||||
|
@ -153,19 +149,18 @@ impl HardForkState {
|
|||
..current_chain_height
|
||||
.saturating_sub(numb_blocks)
|
||||
.saturating_sub(retained_blocks),
|
||||
usize::try_from(numb_blocks).unwrap(),
|
||||
numb_blocks,
|
||||
)
|
||||
.await?;
|
||||
|
||||
self.votes
|
||||
.reverse_blocks(usize::try_from(numb_blocks).unwrap(), oldest_votes);
|
||||
self.votes.reverse_blocks(numb_blocks, oldest_votes);
|
||||
self.last_height -= numb_blocks;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Add a new block to the cache.
|
||||
pub fn new_block(&mut self, vote: HardFork, height: u64) {
|
||||
pub fn new_block(&mut self, vote: HardFork, height: usize) {
|
||||
// We don't _need_ to take in `height` but it's for safety, so we don't silently loose track
|
||||
// of blocks.
|
||||
assert_eq!(self.last_height + 1, height);
|
||||
|
@ -209,13 +204,13 @@ impl HardForkState {
|
|||
#[instrument(name = "get_votes", skip(database))]
|
||||
async fn get_votes_in_range<D: Database>(
|
||||
database: D,
|
||||
block_heights: Range<u64>,
|
||||
block_heights: Range<usize>,
|
||||
window_size: usize,
|
||||
) -> Result<HFVotes, ExtendedConsensusError> {
|
||||
let mut votes = HFVotes::new(window_size);
|
||||
|
||||
let BCResponse::BlockExtendedHeaderInRange(vote_list) = database
|
||||
.oneshot(BCReadRequest::BlockExtendedHeaderInRange(
|
||||
let BlockchainResponse::BlockExtendedHeaderInRange(vote_list) = database
|
||||
.oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
|
||||
block_heights,
|
||||
Chain::Main,
|
||||
))
|
||||
|
|
|
@ -22,7 +22,7 @@ use cuprate_consensus_rules::{
|
|||
};
|
||||
use cuprate_helper::asynch::rayon_spawn_async;
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain,
|
||||
};
|
||||
|
||||
|
@ -74,9 +74,9 @@ impl RandomX for RandomXVM {
|
|||
#[derive(Clone, Debug)]
|
||||
pub struct RandomXVMCache {
|
||||
/// The top [`RX_SEEDS_CACHED`] RX seeds.
|
||||
pub(crate) seeds: VecDeque<(u64, [u8; 32])>,
|
||||
pub(crate) seeds: VecDeque<(usize, [u8; 32])>,
|
||||
/// The VMs for `seeds` (if after hf 12, otherwise this will be empty).
|
||||
pub(crate) vms: HashMap<u64, Arc<RandomXVM>>,
|
||||
pub(crate) vms: HashMap<usize, Arc<RandomXVM>>,
|
||||
|
||||
/// A single cached VM that was given to us from a part of Cuprate.
|
||||
pub(crate) cached_vm: Option<([u8; 32], Arc<RandomXVM>)>,
|
||||
|
@ -85,7 +85,7 @@ pub struct RandomXVMCache {
|
|||
impl RandomXVMCache {
|
||||
#[instrument(name = "init_rx_vm_cache", level = "info", skip(database))]
|
||||
pub async fn init_from_chain_height<D: Database + Clone>(
|
||||
chain_height: u64,
|
||||
chain_height: usize,
|
||||
hf: &HardFork,
|
||||
database: D,
|
||||
) -> Result<Self, ExtendedConsensusError> {
|
||||
|
@ -94,7 +94,8 @@ impl RandomXVMCache {
|
|||
|
||||
tracing::debug!("last {RX_SEEDS_CACHED} randomX seed heights: {seed_heights:?}",);
|
||||
|
||||
let seeds: VecDeque<(u64, [u8; 32])> = seed_heights.into_iter().zip(seed_hashes).collect();
|
||||
let seeds: VecDeque<(usize, [u8; 32])> =
|
||||
seed_heights.into_iter().zip(seed_hashes).collect();
|
||||
|
||||
let vms = if hf >= &HardFork::V12 {
|
||||
tracing::debug!("Creating RandomX VMs");
|
||||
|
@ -132,14 +133,14 @@ impl RandomXVMCache {
|
|||
/// of them first.
|
||||
pub async fn get_alt_vm<D: Database>(
|
||||
&mut self,
|
||||
height: u64,
|
||||
height: usize,
|
||||
chain: Chain,
|
||||
database: D,
|
||||
) -> Result<Arc<RandomXVM>, ExtendedConsensusError> {
|
||||
let seed_height = randomx_seed_height(height);
|
||||
|
||||
let BCResponse::BlockHash(seed_hash) = database
|
||||
.oneshot(BCReadRequest::BlockHash(seed_height, chain))
|
||||
let BlockchainResponse::BlockHash(seed_hash) = database
|
||||
.oneshot(BlockchainReadRequest::BlockHash(seed_height, chain))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database returned wrong response!");
|
||||
|
@ -161,7 +162,7 @@ impl RandomXVMCache {
|
|||
}
|
||||
|
||||
/// Get the main-chain RandomX VMs.
|
||||
pub async fn get_vms(&mut self) -> HashMap<u64, Arc<RandomXVM>> {
|
||||
pub async fn get_vms(&mut self) -> HashMap<usize, Arc<RandomXVM>> {
|
||||
match self.seeds.len().checked_sub(self.vms.len()) {
|
||||
// No difference in the amount of seeds to VMs.
|
||||
Some(0) => (),
|
||||
|
@ -213,7 +214,7 @@ impl RandomXVMCache {
|
|||
}
|
||||
|
||||
/// Removes all the RandomX VMs above the `new_height`.
|
||||
pub fn pop_blocks_main_chain(&mut self, new_height: u64) {
|
||||
pub fn pop_blocks_main_chain(&mut self, new_height: usize) {
|
||||
self.seeds.retain(|(height, _)| *height < new_height);
|
||||
self.vms.retain(|height, _| *height < new_height);
|
||||
}
|
||||
|
@ -221,7 +222,7 @@ impl RandomXVMCache {
|
|||
/// Add a new block to the VM cache.
|
||||
///
|
||||
/// hash is the block hash not the blocks PoW hash.
|
||||
pub fn new_block(&mut self, height: u64, hash: &[u8; 32]) {
|
||||
pub fn new_block(&mut self, height: usize, hash: &[u8; 32]) {
|
||||
if is_randomx_seed_height(height) {
|
||||
tracing::debug!("Block {height} is a randomX seed height, adding it to the cache.",);
|
||||
|
||||
|
@ -242,7 +243,7 @@ impl RandomXVMCache {
|
|||
|
||||
/// Get the last `amount` of RX seeds, the top height returned here will not necessarily be the RX VM for the top block
|
||||
/// in the chain as VMs include some lag before a seed activates.
|
||||
pub(crate) fn get_last_rx_seed_heights(mut last_height: u64, mut amount: usize) -> Vec<u64> {
|
||||
pub(crate) fn get_last_rx_seed_heights(mut last_height: usize, mut amount: usize) -> Vec<usize> {
|
||||
let mut seeds = Vec::with_capacity(amount);
|
||||
if is_randomx_seed_height(last_height) {
|
||||
seeds.push(last_height);
|
||||
|
@ -265,7 +266,7 @@ pub(crate) fn get_last_rx_seed_heights(mut last_height: u64, mut amount: usize)
|
|||
|
||||
/// Gets the block hashes for the heights specified.
|
||||
async fn get_block_hashes<D: Database + Clone>(
|
||||
heights: Vec<u64>,
|
||||
heights: Vec<usize>,
|
||||
database: D,
|
||||
) -> Result<Vec<[u8; 32]>, ExtendedConsensusError> {
|
||||
let mut fut = FuturesOrdered::new();
|
||||
|
@ -273,9 +274,9 @@ async fn get_block_hashes<D: Database + Clone>(
|
|||
for height in heights {
|
||||
let db = database.clone();
|
||||
fut.push_back(async move {
|
||||
let BCResponse::BlockHash(hash) = db
|
||||
let BlockchainResponse::BlockHash(hash) = db
|
||||
.clone()
|
||||
.oneshot(BCReadRequest::BlockHash(height, Chain::Main))
|
||||
.oneshot(BlockchainReadRequest::BlockHash(height, Chain::Main))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!");
|
||||
|
|
|
@ -10,7 +10,7 @@ use tracing::Instrument;
|
|||
|
||||
use cuprate_consensus_rules::blocks::ContextToVerifyBlock;
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain,
|
||||
};
|
||||
|
||||
|
@ -52,7 +52,7 @@ pub struct ContextTask<D: Database> {
|
|||
alt_chain_cache_map: AltChainMap,
|
||||
|
||||
/// The current chain height.
|
||||
chain_height: u64,
|
||||
chain_height: usize,
|
||||
/// The top block hash.
|
||||
top_block_hash: [u8; 32],
|
||||
/// The total amount of coins generated.
|
||||
|
@ -76,19 +76,19 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
|
|||
|
||||
tracing::debug!("Initialising blockchain context");
|
||||
|
||||
let BCResponse::ChainHeight(chain_height, top_block_hash) = database
|
||||
let BlockchainResponse::ChainHeight(chain_height, top_block_hash) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::ChainHeight)
|
||||
.call(BlockchainReadRequest::ChainHeight)
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!");
|
||||
};
|
||||
|
||||
let BCResponse::GeneratedCoins(already_generated_coins) = database
|
||||
let BlockchainResponse::GeneratedCoins(already_generated_coins) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::GeneratedCoins(chain_height - 1))
|
||||
.call(BlockchainReadRequest::GeneratedCoins(chain_height - 1))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!");
|
||||
|
@ -248,21 +248,24 @@ impl<D: Database + Clone + Send + 'static> ContextTask<D> {
|
|||
|
||||
self.chain_height -= numb_blocks;
|
||||
|
||||
let BCResponse::GeneratedCoins(already_generated_coins) = self
|
||||
let BlockchainResponse::GeneratedCoins(already_generated_coins) = self
|
||||
.database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::GeneratedCoins(self.chain_height - 1))
|
||||
.call(BlockchainReadRequest::GeneratedCoins(self.chain_height - 1))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!");
|
||||
};
|
||||
|
||||
let BCResponse::BlockHash(top_block_hash) = self
|
||||
let BlockchainResponse::BlockHash(top_block_hash) = self
|
||||
.database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::BlockHash(self.chain_height - 1, Chain::Main))
|
||||
.call(BlockchainReadRequest::BlockHash(
|
||||
self.chain_height - 1,
|
||||
Chain::Main,
|
||||
))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database returned incorrect response!");
|
||||
|
|
|
@ -17,28 +17,28 @@ use tracing::instrument;
|
|||
use cuprate_consensus_rules::blocks::{penalty_free_zone, PENALTY_FREE_ZONE_5};
|
||||
use cuprate_helper::{asynch::rayon_spawn_async, num::RollingMedian};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain,
|
||||
};
|
||||
|
||||
use crate::{Database, ExtendedConsensusError, HardFork};
|
||||
|
||||
/// The short term block weight window.
|
||||
const SHORT_TERM_WINDOW: u64 = 100;
|
||||
const SHORT_TERM_WINDOW: usize = 100;
|
||||
/// The long term block weight window.
|
||||
const LONG_TERM_WINDOW: u64 = 100000;
|
||||
const LONG_TERM_WINDOW: usize = 100000;
|
||||
|
||||
/// Configuration for the block weight cache.
|
||||
///
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub struct BlockWeightsCacheConfig {
|
||||
short_term_window: u64,
|
||||
long_term_window: u64,
|
||||
short_term_window: usize,
|
||||
long_term_window: usize,
|
||||
}
|
||||
|
||||
impl BlockWeightsCacheConfig {
|
||||
/// Creates a new [`BlockWeightsCacheConfig`]
|
||||
pub const fn new(short_term_window: u64, long_term_window: u64) -> BlockWeightsCacheConfig {
|
||||
pub const fn new(short_term_window: usize, long_term_window: usize) -> BlockWeightsCacheConfig {
|
||||
BlockWeightsCacheConfig {
|
||||
short_term_window,
|
||||
long_term_window,
|
||||
|
@ -67,7 +67,7 @@ pub struct BlockWeightsCache {
|
|||
long_term_weights: RollingMedian<usize>,
|
||||
|
||||
/// The height of the top block.
|
||||
pub(crate) tip_height: u64,
|
||||
pub(crate) tip_height: usize,
|
||||
|
||||
pub(crate) config: BlockWeightsCacheConfig,
|
||||
}
|
||||
|
@ -76,7 +76,7 @@ impl BlockWeightsCache {
|
|||
/// Initialize the [`BlockWeightsCache`] at the the given chain height.
|
||||
#[instrument(name = "init_weight_cache", level = "info", skip(database, config))]
|
||||
pub async fn init_from_chain_height<D: Database + Clone>(
|
||||
chain_height: u64,
|
||||
chain_height: usize,
|
||||
config: BlockWeightsCacheConfig,
|
||||
database: D,
|
||||
chain: Chain,
|
||||
|
@ -101,17 +101,11 @@ impl BlockWeightsCache {
|
|||
|
||||
Ok(BlockWeightsCache {
|
||||
short_term_block_weights: rayon_spawn_async(move || {
|
||||
RollingMedian::from_vec(
|
||||
short_term_block_weights,
|
||||
usize::try_from(config.short_term_window).unwrap(),
|
||||
)
|
||||
RollingMedian::from_vec(short_term_block_weights, config.short_term_window)
|
||||
})
|
||||
.await,
|
||||
long_term_weights: rayon_spawn_async(move || {
|
||||
RollingMedian::from_vec(
|
||||
long_term_weights,
|
||||
usize::try_from(config.long_term_window).unwrap(),
|
||||
)
|
||||
RollingMedian::from_vec(long_term_weights, config.long_term_window)
|
||||
})
|
||||
.await,
|
||||
tip_height: chain_height - 1,
|
||||
|
@ -125,10 +119,10 @@ impl BlockWeightsCache {
|
|||
#[instrument(name = "pop_blocks_weight_cache", skip_all, fields(numb_blocks = numb_blocks))]
|
||||
pub async fn pop_blocks_main_chain<D: Database + Clone>(
|
||||
&mut self,
|
||||
numb_blocks: u64,
|
||||
numb_blocks: usize,
|
||||
database: D,
|
||||
) -> Result<(), ExtendedConsensusError> {
|
||||
if self.long_term_weights.window_len() <= usize::try_from(numb_blocks).unwrap() {
|
||||
if self.long_term_weights.window_len() <= numb_blocks {
|
||||
// More blocks to pop than we have in the cache, so just restart a new cache.
|
||||
*self = Self::init_from_chain_height(
|
||||
self.tip_height - numb_blocks + 1,
|
||||
|
@ -150,7 +144,7 @@ impl BlockWeightsCache {
|
|||
let old_long_term_weights = get_long_term_weight_in_range(
|
||||
new_long_term_start_height
|
||||
// current_chain_height - self.long_term_weights.len() blocks are already in the cache.
|
||||
..(chain_height - u64::try_from(self.long_term_weights.window_len()).unwrap()),
|
||||
..(chain_height - self.long_term_weights.window_len()),
|
||||
database.clone(),
|
||||
Chain::Main,
|
||||
)
|
||||
|
@ -163,11 +157,11 @@ impl BlockWeightsCache {
|
|||
let old_short_term_weights = get_blocks_weight_in_range(
|
||||
new_short_term_start_height
|
||||
// current_chain_height - self.long_term_weights.len() blocks are already in the cache.
|
||||
..(chain_height - u64::try_from(self.short_term_block_weights.window_len()).unwrap()),
|
||||
..(chain_height - self.short_term_block_weights.window_len()),
|
||||
database,
|
||||
Chain::Main
|
||||
Chain::Main,
|
||||
)
|
||||
.await?;
|
||||
.await?;
|
||||
|
||||
for _ in 0..numb_blocks {
|
||||
self.short_term_block_weights.pop_back();
|
||||
|
@ -186,7 +180,7 @@ impl BlockWeightsCache {
|
|||
///
|
||||
/// The block_height **MUST** be one more than the last height the cache has
|
||||
/// seen.
|
||||
pub fn new_block(&mut self, block_height: u64, block_weight: usize, long_term_weight: usize) {
|
||||
pub fn new_block(&mut self, block_height: usize, block_weight: usize, long_term_weight: usize) {
|
||||
assert_eq!(self.tip_height + 1, block_height);
|
||||
self.tip_height += 1;
|
||||
tracing::debug!(
|
||||
|
@ -290,14 +284,16 @@ pub fn calculate_block_long_term_weight(
|
|||
/// Gets the block weights from the blocks with heights in the range provided.
|
||||
#[instrument(name = "get_block_weights", skip(database))]
|
||||
async fn get_blocks_weight_in_range<D: Database + Clone>(
|
||||
range: Range<u64>,
|
||||
range: Range<usize>,
|
||||
database: D,
|
||||
chain: Chain,
|
||||
) -> Result<Vec<usize>, ExtendedConsensusError> {
|
||||
tracing::info!("getting block weights.");
|
||||
|
||||
let BCResponse::BlockExtendedHeaderInRange(ext_headers) = database
|
||||
.oneshot(BCReadRequest::BlockExtendedHeaderInRange(range, chain))
|
||||
let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
|
||||
.oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
|
||||
range, chain,
|
||||
))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!")
|
||||
|
@ -312,14 +308,16 @@ async fn get_blocks_weight_in_range<D: Database + Clone>(
|
|||
/// Gets the block long term weights from the blocks with heights in the range provided.
|
||||
#[instrument(name = "get_long_term_weights", skip(database), level = "info")]
|
||||
async fn get_long_term_weight_in_range<D: Database + Clone>(
|
||||
range: Range<u64>,
|
||||
range: Range<usize>,
|
||||
database: D,
|
||||
chain: Chain,
|
||||
) -> Result<Vec<usize>, ExtendedConsensusError> {
|
||||
tracing::info!("getting block long term weights.");
|
||||
|
||||
let BCResponse::BlockExtendedHeaderInRange(ext_headers) = database
|
||||
.oneshot(BCReadRequest::BlockExtendedHeaderInRange(range, chain))
|
||||
let BlockchainResponse::BlockExtendedHeaderInRange(ext_headers) = database
|
||||
.oneshot(BlockchainReadRequest::BlockExtendedHeaderInRange(
|
||||
range, chain,
|
||||
))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!")
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
//! - [`TxVerifierService`] Which handles transaction verification.
|
||||
//!
|
||||
//! This crate is generic over the database which is implemented as a [`tower::Service`]. To
|
||||
//! implement a database you need to have a service which accepts [`BCReadRequest`] and responds
|
||||
//! with [`BCResponse`].
|
||||
//! implement a database you need to have a service which accepts [`BlockchainReadRequest`] and responds
|
||||
//! with [`BlockchainResponse`].
|
||||
//!
|
||||
use cuprate_consensus_rules::{ConsensusError, HardFork};
|
||||
|
||||
|
@ -27,7 +27,7 @@ pub use context::{
|
|||
pub use transactions::{TxVerifierService, VerifyTxRequest, VerifyTxResponse};
|
||||
|
||||
// re-export.
|
||||
pub use cuprate_types::blockchain::{BCReadRequest, BCResponse};
|
||||
pub use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
|
||||
|
||||
/// An Error returned from one of the consensus services.
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
|
@ -83,7 +83,7 @@ use __private::Database;
|
|||
pub mod __private {
|
||||
use std::future::Future;
|
||||
|
||||
use cuprate_types::blockchain::{BCReadRequest, BCResponse};
|
||||
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
|
||||
|
||||
/// A type alias trait used to represent a database, so we don't have to write [`tower::Service`] bounds
|
||||
/// everywhere.
|
||||
|
@ -94,8 +94,8 @@ pub mod __private {
|
|||
/// ```
|
||||
pub trait Database:
|
||||
tower::Service<
|
||||
BCReadRequest,
|
||||
Response = BCResponse,
|
||||
BlockchainReadRequest,
|
||||
Response = BlockchainResponse,
|
||||
Error = tower::BoxError,
|
||||
Future = Self::Future2,
|
||||
>
|
||||
|
@ -103,8 +103,13 @@ pub mod __private {
|
|||
type Future2: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static;
|
||||
}
|
||||
|
||||
impl<T: tower::Service<BCReadRequest, Response = BCResponse, Error = tower::BoxError>>
|
||||
crate::Database for T
|
||||
impl<
|
||||
T: tower::Service<
|
||||
BlockchainReadRequest,
|
||||
Response = BlockchainResponse,
|
||||
Error = tower::BoxError,
|
||||
>,
|
||||
> crate::Database for T
|
||||
where
|
||||
T::Future: Future<Output = Result<Self::Response, Self::Error>> + Send + 'static,
|
||||
{
|
||||
|
|
|
@ -29,10 +29,10 @@ const TEST_CONTEXT_CONFIG: ContextConfig = ContextConfig {
|
|||
|
||||
#[tokio::test]
|
||||
async fn context_invalidated_on_new_block() -> Result<(), tower::BoxError> {
|
||||
const BLOCKCHAIN_HEIGHT: u64 = 6000;
|
||||
const BLOCKCHAIN_HEIGHT: usize = 6000;
|
||||
|
||||
let mut runner = TestRunner::default();
|
||||
let db = arb_dummy_database(BLOCKCHAIN_HEIGHT.try_into().unwrap())
|
||||
let db = arb_dummy_database(BLOCKCHAIN_HEIGHT)
|
||||
.new_tree(&mut runner)
|
||||
.unwrap()
|
||||
.current();
|
||||
|
@ -71,10 +71,10 @@ async fn context_invalidated_on_new_block() -> Result<(), tower::BoxError> {
|
|||
|
||||
#[tokio::test]
|
||||
async fn context_height_correct() -> Result<(), tower::BoxError> {
|
||||
const BLOCKCHAIN_HEIGHT: u64 = 6000;
|
||||
const BLOCKCHAIN_HEIGHT: usize = 6000;
|
||||
|
||||
let mut runner = TestRunner::default();
|
||||
let db = arb_dummy_database(BLOCKCHAIN_HEIGHT.try_into().unwrap())
|
||||
let db = arb_dummy_database(BLOCKCHAIN_HEIGHT)
|
||||
.new_tree(&mut runner)
|
||||
.unwrap()
|
||||
.current();
|
||||
|
|
|
@ -63,10 +63,7 @@ async fn calculate_diff_3000000_3002000() -> Result<(), tower::BoxError> {
|
|||
let cfg = DifficultyCacheConfig::main_net();
|
||||
|
||||
let mut db_builder = DummyDatabaseBuilder::default();
|
||||
for (cum_dif, timestamp) in DIF_3000000_3002000
|
||||
.iter()
|
||||
.take(cfg.total_block_count() as usize)
|
||||
{
|
||||
for (cum_dif, timestamp) in DIF_3000000_3002000.iter().take(cfg.total_block_count()) {
|
||||
db_builder.add_block(
|
||||
DummyBlockExtendedHeader::default().with_difficulty_info(*timestamp, *cum_dif),
|
||||
)
|
||||
|
@ -82,14 +79,14 @@ async fn calculate_diff_3000000_3002000() -> Result<(), tower::BoxError> {
|
|||
|
||||
for (i, diff_info) in DIF_3000000_3002000
|
||||
.windows(2)
|
||||
.skip(cfg.total_block_count() as usize - 1)
|
||||
.skip(cfg.total_block_count() - 1)
|
||||
.enumerate()
|
||||
{
|
||||
let diff = diff_info[1].0 - diff_info[0].0;
|
||||
|
||||
assert_eq!(diff_cache.next_difficulty(&HardFork::V16), diff);
|
||||
|
||||
diff_cache.new_block(3_000_720 + i as u64, diff_info[1].1, diff_info[1].0);
|
||||
diff_cache.new_block(3_000_720 + i, diff_info[1].1, diff_info[1].0);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
@ -104,7 +101,7 @@ prop_compose! {
|
|||
let (timestamps, mut cumulative_difficulties): (Vec<_>, Vec<_>) = blocks.into_iter().unzip();
|
||||
cumulative_difficulties.sort_unstable();
|
||||
DifficultyCache {
|
||||
last_accounted_height: timestamps.len().try_into().unwrap(),
|
||||
last_accounted_height: timestamps.len(),
|
||||
config: TEST_DIFFICULTY_CONFIG,
|
||||
timestamps: timestamps.into(),
|
||||
// we generate cumulative_difficulties in range 0..u64::MAX as if the generated values are close to u128::MAX
|
||||
|
@ -165,7 +162,7 @@ proptest! {
|
|||
let mut timestamps: VecDeque<u64> = timestamps.into();
|
||||
|
||||
let diff_cache = DifficultyCache {
|
||||
last_accounted_height: (TEST_WINDOW -1).try_into().unwrap(),
|
||||
last_accounted_height: TEST_WINDOW -1,
|
||||
config: TEST_DIFFICULTY_CONFIG,
|
||||
timestamps: timestamps.clone(),
|
||||
// we dont need cumulative_difficulties
|
||||
|
@ -234,7 +231,7 @@ proptest! {
|
|||
new_cache.new_block(new_cache.last_accounted_height+1, timestamp, cumulative_difficulty);
|
||||
}
|
||||
|
||||
new_cache.pop_blocks_main_chain(blocks_to_pop as u64, database).await?;
|
||||
new_cache.pop_blocks_main_chain(blocks_to_pop, database).await?;
|
||||
|
||||
prop_assert_eq!(new_cache, old_cache);
|
||||
|
||||
|
@ -258,7 +255,7 @@ proptest! {
|
|||
new_cache.new_block(new_cache.last_accounted_height+1, timestamp, cumulative_difficulty);
|
||||
}
|
||||
|
||||
new_cache.pop_blocks_main_chain(blocks_to_pop as u64, database).await?;
|
||||
new_cache.pop_blocks_main_chain(blocks_to_pop, database).await?;
|
||||
|
||||
prop_assert_eq!(new_cache, old_cache);
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ use crate::{
|
|||
},
|
||||
};
|
||||
|
||||
const TEST_WINDOW_SIZE: u64 = 25;
|
||||
const TEST_WINDOW_SIZE: usize = 25;
|
||||
|
||||
const TEST_HFS: [HFInfo; NUMB_OF_HARD_FORKS] = [
|
||||
HFInfo::new(0, 0),
|
||||
|
@ -79,7 +79,7 @@ async fn hf_v15_v16_correct() {
|
|||
|
||||
for (i, (_, vote)) in HFS_2688888_2689608.into_iter().enumerate() {
|
||||
assert_eq!(state.current_hardfork, HardFork::V15);
|
||||
state.new_block(vote, (2688888 + i) as u64);
|
||||
state.new_block(vote, 2688888 + i);
|
||||
}
|
||||
|
||||
assert_eq!(state.current_hardfork, HardFork::V16);
|
||||
|
@ -91,8 +91,8 @@ proptest! {
|
|||
extra_hfs in vec(any::<HardFork>(), 0..100)
|
||||
) {
|
||||
tokio_test::block_on(async move {
|
||||
let numb_hfs = hfs.len() as u64;
|
||||
let numb_pop_blocks = extra_hfs.len() as u64;
|
||||
let numb_hfs = hfs.len();
|
||||
let numb_pop_blocks = extra_hfs.len();
|
||||
|
||||
let mut db_builder = DummyDatabaseBuilder::default();
|
||||
|
||||
|
@ -102,7 +102,7 @@ proptest! {
|
|||
);
|
||||
}
|
||||
|
||||
let db = db_builder.finish(Some(numb_hfs as usize));
|
||||
let db = db_builder.finish(Some(numb_hfs ));
|
||||
|
||||
let mut state = HardForkState::init_from_chain_height(
|
||||
numb_hfs,
|
||||
|
@ -114,7 +114,7 @@ proptest! {
|
|||
let state_clone = state.clone();
|
||||
|
||||
for (i, hf) in extra_hfs.into_iter().enumerate() {
|
||||
state.new_block(hf, state.last_height + u64::try_from(i).unwrap() + 1);
|
||||
state.new_block(hf, state.last_height + i + 1);
|
||||
}
|
||||
|
||||
state.pop_blocks_main_chain(numb_pop_blocks, db).await?;
|
||||
|
|
|
@ -123,14 +123,14 @@ async fn weight_cache_calculates_correct_median() -> Result<(), tower::BoxError>
|
|||
.await?;
|
||||
|
||||
for height in 1..=100 {
|
||||
weight_cache.new_block(height as u64, height, height);
|
||||
weight_cache.new_block(height, height, height);
|
||||
|
||||
assert_eq!(weight_cache.median_short_term_weight(), height / 2);
|
||||
assert_eq!(weight_cache.median_long_term_weight(), height / 2);
|
||||
}
|
||||
|
||||
for height in 101..=5000 {
|
||||
weight_cache.new_block(height as u64, height, height);
|
||||
weight_cache.new_block(height, height, height);
|
||||
|
||||
assert_eq!(weight_cache.median_long_term_weight(), height / 2);
|
||||
}
|
||||
|
@ -162,7 +162,7 @@ async fn calc_bw_ltw_2850000_3050000() {
|
|||
weight_cache.median_long_term_weight(),
|
||||
);
|
||||
assert_eq!(calc_ltw, *ltw);
|
||||
weight_cache.new_block((2950000 + i) as u64, *weight, *ltw);
|
||||
weight_cache.new_block(2950000 + i, *weight, *ltw);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ use proptest_derive::Arbitrary;
|
|||
use tower::{BoxError, Service};
|
||||
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
ExtendedBlockHeader,
|
||||
};
|
||||
|
||||
|
@ -133,8 +133,8 @@ impl DummyDatabase {
|
|||
}
|
||||
}
|
||||
|
||||
impl Service<BCReadRequest> for DummyDatabase {
|
||||
type Response = BCResponse;
|
||||
impl Service<BlockchainReadRequest> for DummyDatabase {
|
||||
type Response = BlockchainResponse;
|
||||
type Error = BoxError;
|
||||
type Future =
|
||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||
|
@ -143,21 +143,21 @@ impl Service<BCReadRequest> for DummyDatabase {
|
|||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: BCReadRequest) -> Self::Future {
|
||||
fn call(&mut self, req: BlockchainReadRequest) -> Self::Future {
|
||||
let blocks = self.blocks.clone();
|
||||
let dummy_height = self.dummy_height;
|
||||
|
||||
async move {
|
||||
Ok(match req {
|
||||
BCReadRequest::BlockExtendedHeader(id) => {
|
||||
let mut id = usize::try_from(id).unwrap();
|
||||
BlockchainReadRequest::BlockExtendedHeader(id) => {
|
||||
let mut id = id;
|
||||
if let Some(dummy_height) = dummy_height {
|
||||
let block_len = blocks.read().unwrap().len();
|
||||
|
||||
id -= dummy_height - block_len;
|
||||
}
|
||||
|
||||
BCResponse::BlockExtendedHeader(
|
||||
BlockchainResponse::BlockExtendedHeader(
|
||||
blocks
|
||||
.read()
|
||||
.unwrap()
|
||||
|
@ -167,14 +167,14 @@ impl Service<BCReadRequest> for DummyDatabase {
|
|||
.ok_or("block not in database!")?,
|
||||
)
|
||||
}
|
||||
BCReadRequest::BlockHash(id, _) => {
|
||||
BlockchainReadRequest::BlockHash(id, _) => {
|
||||
let mut hash = [0; 32];
|
||||
hash[0..8].copy_from_slice(&id.to_le_bytes());
|
||||
BCResponse::BlockHash(hash)
|
||||
BlockchainResponse::BlockHash(hash)
|
||||
}
|
||||
BCReadRequest::BlockExtendedHeaderInRange(range, _) => {
|
||||
let mut end = usize::try_from(range.end).unwrap();
|
||||
let mut start = usize::try_from(range.start).unwrap();
|
||||
BlockchainReadRequest::BlockExtendedHeaderInRange(range, _) => {
|
||||
let mut end = range.end;
|
||||
let mut start = range.start;
|
||||
|
||||
if let Some(dummy_height) = dummy_height {
|
||||
let block_len = blocks.read().unwrap().len();
|
||||
|
@ -183,7 +183,7 @@ impl Service<BCReadRequest> for DummyDatabase {
|
|||
start -= dummy_height - block_len;
|
||||
}
|
||||
|
||||
BCResponse::BlockExtendedHeaderInRange(
|
||||
BlockchainResponse::BlockExtendedHeaderInRange(
|
||||
blocks
|
||||
.read()
|
||||
.unwrap()
|
||||
|
@ -195,18 +195,15 @@ impl Service<BCReadRequest> for DummyDatabase {
|
|||
.collect(),
|
||||
)
|
||||
}
|
||||
BCReadRequest::ChainHeight => {
|
||||
let height: u64 = dummy_height
|
||||
.unwrap_or(blocks.read().unwrap().len())
|
||||
.try_into()
|
||||
.unwrap();
|
||||
BlockchainReadRequest::ChainHeight => {
|
||||
let height = dummy_height.unwrap_or(blocks.read().unwrap().len());
|
||||
|
||||
let mut top_hash = [0; 32];
|
||||
top_hash[0..8].copy_from_slice(&height.to_le_bytes());
|
||||
|
||||
BCResponse::ChainHeight(height, top_hash)
|
||||
BlockchainResponse::ChainHeight(height, top_hash)
|
||||
}
|
||||
BCReadRequest::GeneratedCoins(_) => BCResponse::GeneratedCoins(0),
|
||||
BlockchainReadRequest::GeneratedCoins(_) => BlockchainResponse::GeneratedCoins(0),
|
||||
_ => unimplemented!("the context svc should not need these requests!"),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -12,10 +12,7 @@ use std::{
|
|||
};
|
||||
|
||||
use futures::FutureExt;
|
||||
use monero_serai::{
|
||||
ringct::RctType,
|
||||
transaction::{Input, Timelock, Transaction},
|
||||
};
|
||||
use monero_serai::transaction::{Input, Timelock, Transaction};
|
||||
use rayon::prelude::*;
|
||||
use tower::{Service, ServiceExt};
|
||||
use tracing::instrument;
|
||||
|
@ -28,7 +25,7 @@ use cuprate_consensus_rules::{
|
|||
ConsensusError, HardFork, TxVersion,
|
||||
};
|
||||
use cuprate_helper::asynch::rayon_spawn_async;
|
||||
use cuprate_types::blockchain::{BCReadRequest, BCResponse};
|
||||
use cuprate_types::blockchain::{BlockchainReadRequest, BlockchainResponse};
|
||||
|
||||
use crate::{
|
||||
batch_verifier::MultiThreadedBatchVerifier,
|
||||
|
@ -37,6 +34,7 @@ use crate::{
|
|||
};
|
||||
|
||||
pub mod contextual_data;
|
||||
mod free;
|
||||
|
||||
/// A struct representing the type of validation that needs to be completed for this transaction.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
|
||||
|
@ -103,22 +101,17 @@ impl TransactionVerificationData {
|
|||
let tx_hash = tx.hash();
|
||||
let tx_blob = tx.serialize();
|
||||
|
||||
// the tx weight is only different from the blobs length for bp(+) txs.
|
||||
let tx_weight = match tx.rct_signatures.rct_type() {
|
||||
RctType::Bulletproofs
|
||||
| RctType::BulletproofsCompactAmount
|
||||
| RctType::Clsag
|
||||
| RctType::BulletproofsPlus => tx.weight(),
|
||||
_ => tx_blob.len(),
|
||||
};
|
||||
let tx_weight = free::tx_weight(&tx, &tx_blob);
|
||||
|
||||
let fee = free::tx_fee(&tx)?;
|
||||
|
||||
Ok(TransactionVerificationData {
|
||||
tx_hash,
|
||||
tx_blob,
|
||||
tx_weight,
|
||||
fee: tx.rct_signatures.base.fee,
|
||||
fee,
|
||||
cached_verification_state: StdMutex::new(CachedVerificationState::NotVerified),
|
||||
version: TxVersion::from_raw(tx.prefix.version)
|
||||
version: TxVersion::from_raw(tx.version())
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?,
|
||||
tx,
|
||||
})
|
||||
|
@ -133,7 +126,7 @@ pub enum VerifyTxRequest {
|
|||
// TODO: Can we use references to remove the Vec? wont play nicely with Service though
|
||||
txs: Vec<Arc<TransactionVerificationData>>,
|
||||
/// The current chain height.
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
/// The top block hash.
|
||||
top_hash: [u8; 32],
|
||||
/// The value for time to use to check time locked outputs.
|
||||
|
@ -147,7 +140,7 @@ pub enum VerifyTxRequest {
|
|||
/// The transactions to verify.
|
||||
txs: Vec<Transaction>,
|
||||
/// The current chain height.
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
/// The top block hash.
|
||||
top_hash: [u8; 32],
|
||||
/// The value for time to use to check time locked outputs.
|
||||
|
@ -246,7 +239,7 @@ where
|
|||
async fn prep_and_verify_transactions<D>(
|
||||
database: D,
|
||||
txs: Vec<Transaction>,
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
top_hash: [u8; 32],
|
||||
time_for_time_lock: u64,
|
||||
hf: HardFork,
|
||||
|
@ -281,7 +274,7 @@ where
|
|||
async fn verify_prepped_transactions<D>(
|
||||
mut database: D,
|
||||
txs: &[Arc<TransactionVerificationData>],
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
top_hash: [u8; 32],
|
||||
time_for_time_lock: u64,
|
||||
hf: HardFork,
|
||||
|
@ -296,7 +289,7 @@ where
|
|||
let mut spent_kis = HashSet::with_capacity(txs.len());
|
||||
|
||||
txs.iter().try_for_each(|tx| {
|
||||
tx.tx.prefix.inputs.iter().try_for_each(|input| {
|
||||
tx.tx.prefix().inputs.iter().try_for_each(|input| {
|
||||
if let Input::ToKey { key_image, .. } = input {
|
||||
if !spent_kis.insert(key_image.compress().0) {
|
||||
tracing::debug!("Duplicate key image found in batch.");
|
||||
|
@ -308,10 +301,10 @@ where
|
|||
})
|
||||
})?;
|
||||
|
||||
let BCResponse::KeyImagesSpent(kis_spent) = database
|
||||
let BlockchainResponse::KeyImagesSpent(kis_spent) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::KeyImagesSpent(spent_kis))
|
||||
.call(BlockchainReadRequest::KeyImagesSpent(spent_kis))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!");
|
||||
|
@ -340,10 +333,12 @@ where
|
|||
if !verified_at_block_hashes.is_empty() {
|
||||
tracing::trace!("Filtering block hashes not in the main chain.");
|
||||
|
||||
let BCResponse::FilterUnknownHashes(known_hashes) = database
|
||||
let BlockchainResponse::FilterUnknownHashes(known_hashes) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::FilterUnknownHashes(verified_at_block_hashes))
|
||||
.call(BlockchainReadRequest::FilterUnknownHashes(
|
||||
verified_at_block_hashes,
|
||||
))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database returned wrong response!");
|
||||
|
@ -380,7 +375,7 @@ fn transactions_needing_verification(
|
|||
txs: &[Arc<TransactionVerificationData>],
|
||||
hashes_in_main_chain: HashSet<[u8; 32]>,
|
||||
current_hf: &HardFork,
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
time_for_time_lock: u64,
|
||||
) -> Result<
|
||||
(
|
||||
|
@ -471,7 +466,7 @@ where
|
|||
|
||||
async fn verify_transactions<D>(
|
||||
txs: Vec<(Arc<TransactionVerificationData>, VerificationNeeded)>,
|
||||
current_chain_height: u64,
|
||||
current_chain_height: usize,
|
||||
top_hash: [u8; 32],
|
||||
current_time_lock_timestamp: u64,
|
||||
hf: HardFork,
|
||||
|
@ -499,7 +494,7 @@ where
|
|||
&hf,
|
||||
&batch_verifier,
|
||||
)?;
|
||||
// make sure monero-serai calculated the same fee.
|
||||
// make sure we calculated the right fee.
|
||||
assert_eq!(fee, tx.fee);
|
||||
}
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ use cuprate_consensus_rules::{
|
|||
ConsensusError, HardFork, TxVersion,
|
||||
};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
OutputOnChain,
|
||||
};
|
||||
|
||||
|
@ -149,23 +149,23 @@ pub async fn batch_get_ring_member_info<D: Database>(
|
|||
let mut output_ids = HashMap::new();
|
||||
|
||||
for tx_v_data in txs_verification_data.clone() {
|
||||
insert_ring_member_ids(&tx_v_data.tx.prefix.inputs, &mut output_ids)
|
||||
insert_ring_member_ids(&tx_v_data.tx.prefix().inputs, &mut output_ids)
|
||||
.map_err(ConsensusError::Transaction)?;
|
||||
}
|
||||
|
||||
let BCResponse::Outputs(outputs) = database
|
||||
let BlockchainResponse::Outputs(outputs) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::Outputs(output_ids))
|
||||
.call(BlockchainReadRequest::Outputs(output_ids))
|
||||
.await?
|
||||
else {
|
||||
panic!("Database sent incorrect response!")
|
||||
};
|
||||
|
||||
let BCResponse::NumberOutputsWithAmount(outputs_with_amount) = database
|
||||
let BlockchainResponse::NumberOutputsWithAmount(outputs_with_amount) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::NumberOutputsWithAmount(
|
||||
.call(BlockchainReadRequest::NumberOutputsWithAmount(
|
||||
outputs.keys().copied().collect(),
|
||||
))
|
||||
.await?
|
||||
|
@ -179,14 +179,14 @@ pub async fn batch_get_ring_member_info<D: Database>(
|
|||
|
||||
let ring_members_for_tx = get_ring_members_for_inputs(
|
||||
|amt, idx| outputs.get(&amt)?.get(&idx).copied(),
|
||||
&tx_v_data.tx.prefix.inputs,
|
||||
&tx_v_data.tx.prefix().inputs,
|
||||
)
|
||||
.map_err(ConsensusError::Transaction)?;
|
||||
|
||||
let decoy_info = if hf != &HardFork::V1 {
|
||||
// this data is only needed after hard-fork 1.
|
||||
Some(
|
||||
DecoyInfo::new(&tx_v_data.tx.prefix.inputs, numb_outputs, hf)
|
||||
DecoyInfo::new(&tx_v_data.tx.prefix().inputs, numb_outputs, hf)
|
||||
.map_err(ConsensusError::Transaction)?,
|
||||
)
|
||||
} else {
|
||||
|
@ -222,7 +222,7 @@ pub async fn batch_get_decoy_info<'a, D: Database + Clone + Send + 'static>(
|
|||
let unique_input_amounts = txs_verification_data
|
||||
.iter()
|
||||
.flat_map(|tx_info| {
|
||||
tx_info.tx.prefix.inputs.iter().map(|input| match input {
|
||||
tx_info.tx.prefix().inputs.iter().map(|input| match input {
|
||||
Input::ToKey { amount, .. } => amount.unwrap_or(0),
|
||||
_ => 0,
|
||||
})
|
||||
|
@ -234,10 +234,10 @@ pub async fn batch_get_decoy_info<'a, D: Database + Clone + Send + 'static>(
|
|||
unique_input_amounts.len()
|
||||
);
|
||||
|
||||
let BCResponse::NumberOutputsWithAmount(outputs_with_amount) = database
|
||||
let BlockchainResponse::NumberOutputsWithAmount(outputs_with_amount) = database
|
||||
.ready()
|
||||
.await?
|
||||
.call(BCReadRequest::NumberOutputsWithAmount(
|
||||
.call(BlockchainReadRequest::NumberOutputsWithAmount(
|
||||
unique_input_amounts.into_iter().collect(),
|
||||
))
|
||||
.await?
|
||||
|
@ -247,7 +247,7 @@ pub async fn batch_get_decoy_info<'a, D: Database + Clone + Send + 'static>(
|
|||
|
||||
Ok(txs_verification_data.iter().map(move |tx_v_data| {
|
||||
DecoyInfo::new(
|
||||
&tx_v_data.tx.prefix.inputs,
|
||||
&tx_v_data.tx.prefix().inputs,
|
||||
|amt| outputs_with_amount.get(&amt).copied().unwrap_or(0),
|
||||
&hf,
|
||||
)
|
||||
|
|
64
consensus/src/transactions/free.rs
Normal file
64
consensus/src/transactions/free.rs
Normal file
|
@ -0,0 +1,64 @@
|
|||
use monero_serai::{
|
||||
ringct::{bulletproofs::Bulletproof, RctType},
|
||||
transaction::{Input, Transaction},
|
||||
};
|
||||
|
||||
use cuprate_consensus_rules::transactions::TransactionError;
|
||||
|
||||
/// Calculates the weight of a [`Transaction`].
|
||||
///
|
||||
/// This is more efficient that [`Transaction::weight`] if you already have the transaction blob.
|
||||
pub fn tx_weight(tx: &Transaction, tx_blob: &[u8]) -> usize {
|
||||
// the tx weight is only different from the blobs length for bp(+) txs.
|
||||
|
||||
match &tx {
|
||||
Transaction::V1 { .. } | Transaction::V2 { proofs: None, .. } => tx_blob.len(),
|
||||
Transaction::V2 {
|
||||
proofs: Some(proofs),
|
||||
..
|
||||
} => match proofs.rct_type() {
|
||||
RctType::AggregateMlsagBorromean | RctType::MlsagBorromean => tx_blob.len(),
|
||||
RctType::MlsagBulletproofs
|
||||
| RctType::MlsagBulletproofsCompactAmount
|
||||
| RctType::ClsagBulletproof => {
|
||||
tx_blob.len()
|
||||
+ Bulletproof::calculate_bp_clawback(false, tx.prefix().outputs.len()).0
|
||||
}
|
||||
RctType::ClsagBulletproofPlus => {
|
||||
tx_blob.len()
|
||||
+ Bulletproof::calculate_bp_clawback(true, tx.prefix().outputs.len()).0
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the fee of the [`Transaction`].
|
||||
pub fn tx_fee(tx: &Transaction) -> Result<u64, TransactionError> {
|
||||
let mut fee = 0_u64;
|
||||
|
||||
match &tx {
|
||||
Transaction::V1 { prefix, .. } => {
|
||||
for input in &prefix.inputs {
|
||||
if let Input::ToKey { amount, .. } = input {
|
||||
fee = fee
|
||||
.checked_add(amount.unwrap_or(0))
|
||||
.ok_or(TransactionError::InputsOverflow)?;
|
||||
}
|
||||
}
|
||||
|
||||
for output in &prefix.outputs {
|
||||
fee.checked_sub(output.amount.unwrap_or(0))
|
||||
.ok_or(TransactionError::OutputsTooHigh)?;
|
||||
}
|
||||
}
|
||||
Transaction::V2 { proofs, .. } => {
|
||||
fee = proofs
|
||||
.as_ref()
|
||||
.ok_or(TransactionError::TransactionVersionInvalid)?
|
||||
.base
|
||||
.fee;
|
||||
}
|
||||
};
|
||||
|
||||
Ok(fee)
|
||||
}
|
|
@ -12,7 +12,7 @@ use cuprate_consensus::{
|
|||
TxVerifierService, VerifyTxRequest, VerifyTxResponse, __private::Database,
|
||||
};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
OutputOnChain,
|
||||
};
|
||||
|
||||
|
@ -23,12 +23,12 @@ use cuprate_test_utils::data::TX_E2D393;
|
|||
fn dummy_database(outputs: BTreeMap<u64, OutputOnChain>) -> impl Database + Clone {
|
||||
let outputs = Arc::new(outputs);
|
||||
|
||||
service_fn(move |req: BCReadRequest| {
|
||||
service_fn(move |req: BlockchainReadRequest| {
|
||||
ready(Ok(match req {
|
||||
BCReadRequest::NumberOutputsWithAmount(_) => {
|
||||
BCResponse::NumberOutputsWithAmount(HashMap::new())
|
||||
BlockchainReadRequest::NumberOutputsWithAmount(_) => {
|
||||
BlockchainResponse::NumberOutputsWithAmount(HashMap::new())
|
||||
}
|
||||
BCReadRequest::Outputs(outs) => {
|
||||
BlockchainReadRequest::Outputs(outs) => {
|
||||
let idxs = outs.get(&0).unwrap();
|
||||
|
||||
let mut ret = HashMap::new();
|
||||
|
@ -40,9 +40,9 @@ fn dummy_database(outputs: BTreeMap<u64, OutputOnChain>) -> impl Database + Clon
|
|||
.collect::<HashMap<_, _>>(),
|
||||
);
|
||||
|
||||
BCResponse::Outputs(ret)
|
||||
BlockchainResponse::Outputs(ret)
|
||||
}
|
||||
BCReadRequest::KeyImagesSpent(_) => BCResponse::KeyImagesSpent(false),
|
||||
BlockchainReadRequest::KeyImagesSpent(_) => BlockchainResponse::KeyImagesSpent(false),
|
||||
_ => panic!("Database request not needed for this test"),
|
||||
}))
|
||||
})
|
||||
|
|
|
@ -260,7 +260,7 @@ impl<Z: BorshNetworkZone> AddressBook<Z> {
|
|||
|
||||
fn take_random_white_peer(
|
||||
&mut self,
|
||||
block_needed: Option<u64>,
|
||||
block_needed: Option<usize>,
|
||||
) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
|
||||
tracing::debug!("Retrieving random white peer");
|
||||
self.white_list
|
||||
|
@ -269,7 +269,7 @@ impl<Z: BorshNetworkZone> AddressBook<Z> {
|
|||
|
||||
fn take_random_gray_peer(
|
||||
&mut self,
|
||||
block_needed: Option<u64>,
|
||||
block_needed: Option<usize>,
|
||||
) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
|
||||
tracing::debug!("Retrieving random gray peer");
|
||||
self.gray_list
|
||||
|
|
|
@ -88,7 +88,7 @@ impl<Z: NetworkZone> PeerList<Z> {
|
|||
pub fn take_random_peer<R: Rng>(
|
||||
&mut self,
|
||||
r: &mut R,
|
||||
block_needed: Option<u64>,
|
||||
block_needed: Option<usize>,
|
||||
must_keep_peers: &HashSet<Z::Addr>,
|
||||
) -> Option<ZoneSpecificPeerListEntryBase<Z::Addr>> {
|
||||
// Take a random peer and see if it's in the list of must_keep_peers, if it is try again.
|
||||
|
|
|
@ -14,7 +14,7 @@ pub enum PeerSyncRequest<N: NetworkZone> {
|
|||
/// claim to have a higher cumulative difficulty.
|
||||
PeersToSyncFrom {
|
||||
current_cumulative_difficulty: u128,
|
||||
block_needed: Option<u64>,
|
||||
block_needed: Option<usize>,
|
||||
},
|
||||
/// Add/update a peer's core sync data.
|
||||
IncomingCoreSyncData(InternalPeerID<N::Addr>, ConnectionHandle, CoreSyncData),
|
||||
|
@ -115,18 +115,18 @@ pub enum AddressBookRequest<Z: NetworkZone> {
|
|||
/// Takes a random white peer from the peer list. If height is specified
|
||||
/// then the peer list should retrieve a peer that should have a full
|
||||
/// block at that height according to it's pruning seed
|
||||
TakeRandomWhitePeer { height: Option<u64> },
|
||||
TakeRandomWhitePeer { height: Option<usize> },
|
||||
/// Takes a random gray peer from the peer list. If height is specified
|
||||
/// then the peer list should retrieve a peer that should have a full
|
||||
/// block at that height according to it's pruning seed
|
||||
TakeRandomGrayPeer { height: Option<u64> },
|
||||
TakeRandomGrayPeer { height: Option<usize> },
|
||||
/// Takes a random peer from the peer list. If height is specified
|
||||
/// then the peer list should retrieve a peer that should have a full
|
||||
/// block at that height according to it's pruning seed.
|
||||
///
|
||||
/// The address book will look in the white peer list first, then the gray
|
||||
/// one if no peer is found.
|
||||
TakeRandomPeer { height: Option<u64> },
|
||||
TakeRandomPeer { height: Option<usize> },
|
||||
/// Gets the specified number of white peers, or less if we don't have enough.
|
||||
GetWhitePeers(usize),
|
||||
/// Checks if the given peer is banned.
|
||||
|
|
|
@ -121,7 +121,7 @@ pub enum ChainSvcResponse {
|
|||
/// The response for [`ChainSvcRequest::FindFirstUnknown`].
|
||||
///
|
||||
/// Contains the index of the first unknown block and its expected height.
|
||||
FindFirstUnknown(Option<(usize, u64)>),
|
||||
FindFirstUnknown(Option<(usize, usize)>),
|
||||
/// The response for [`ChainSvcRequest::CumulativeDifficulty`].
|
||||
///
|
||||
/// The current cumulative difficulty of our chain.
|
||||
|
@ -207,7 +207,7 @@ struct BlockDownloader<N: NetworkZone, S, C> {
|
|||
/// The amount of blocks to request in the next batch.
|
||||
amount_of_blocks_to_request: usize,
|
||||
/// The height at which [`Self::amount_of_blocks_to_request`] was updated.
|
||||
amount_of_blocks_to_request_updated_at: u64,
|
||||
amount_of_blocks_to_request_updated_at: usize,
|
||||
|
||||
/// The amount of consecutive empty chain entries we received.
|
||||
///
|
||||
|
@ -225,12 +225,12 @@ struct BlockDownloader<N: NetworkZone, S, C> {
|
|||
/// The current inflight requests.
|
||||
///
|
||||
/// This is a map of batch start heights to block IDs and related information of the batch.
|
||||
inflight_requests: BTreeMap<u64, BlocksToRetrieve<N>>,
|
||||
inflight_requests: BTreeMap<usize, BlocksToRetrieve<N>>,
|
||||
|
||||
/// A queue of start heights from failed batches that should be retried.
|
||||
///
|
||||
/// Wrapped in [`Reverse`] so we prioritize early batches.
|
||||
failed_batches: BinaryHeap<Reverse<u64>>,
|
||||
failed_batches: BinaryHeap<Reverse<usize>>,
|
||||
|
||||
block_queue: BlockQueue,
|
||||
|
||||
|
@ -524,7 +524,7 @@ where
|
|||
/// Handles a response to a request to get blocks from a peer.
|
||||
async fn handle_download_batch_res(
|
||||
&mut self,
|
||||
start_height: u64,
|
||||
start_height: usize,
|
||||
res: Result<(ClientPoolDropGuard<N>, BlockBatch), BlockDownloadError>,
|
||||
chain_tracker: &mut ChainTracker<N>,
|
||||
pending_peers: &mut BTreeMap<PruningSeed, Vec<ClientPoolDropGuard<N>>>,
|
||||
|
@ -692,18 +692,19 @@ where
|
|||
/// The return value from the block download tasks.
|
||||
struct BlockDownloadTaskResponse<N: NetworkZone> {
|
||||
/// The start height of the batch.
|
||||
start_height: u64,
|
||||
start_height: usize,
|
||||
/// A result containing the batch or an error.
|
||||
result: Result<(ClientPoolDropGuard<N>, BlockBatch), BlockDownloadError>,
|
||||
}
|
||||
|
||||
/// Returns if a peer has all the blocks in a range, according to its [`PruningSeed`].
|
||||
fn client_has_block_in_range(pruning_seed: &PruningSeed, start_height: u64, length: usize) -> bool {
|
||||
fn client_has_block_in_range(
|
||||
pruning_seed: &PruningSeed,
|
||||
start_height: usize,
|
||||
length: usize,
|
||||
) -> bool {
|
||||
pruning_seed.has_full_block(start_height, CRYPTONOTE_MAX_BLOCK_HEIGHT)
|
||||
&& pruning_seed.has_full_block(
|
||||
start_height + u64::try_from(length).unwrap(),
|
||||
CRYPTONOTE_MAX_BLOCK_HEIGHT,
|
||||
)
|
||||
&& pruning_seed.has_full_block(start_height + length, CRYPTONOTE_MAX_BLOCK_HEIGHT)
|
||||
}
|
||||
|
||||
/// Calculates the next amount of blocks to request in a batch.
|
||||
|
|
|
@ -15,7 +15,7 @@ use super::{BlockBatch, BlockDownloadError};
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct ReadyQueueBatch {
|
||||
/// The start height of the batch.
|
||||
pub start_height: u64,
|
||||
pub start_height: usize,
|
||||
/// The batch of blocks.
|
||||
pub block_batch: BlockBatch,
|
||||
}
|
||||
|
@ -64,7 +64,7 @@ impl BlockQueue {
|
|||
}
|
||||
|
||||
/// Returns the oldest batch that has not been put in the [`async_buffer`] yet.
|
||||
pub fn oldest_ready_batch(&self) -> Option<u64> {
|
||||
pub fn oldest_ready_batch(&self) -> Option<usize> {
|
||||
self.ready_batches.peek().map(|batch| batch.start_height)
|
||||
}
|
||||
|
||||
|
@ -80,13 +80,13 @@ impl BlockQueue {
|
|||
pub async fn add_incoming_batch(
|
||||
&mut self,
|
||||
new_batch: ReadyQueueBatch,
|
||||
oldest_in_flight_start_height: Option<u64>,
|
||||
oldest_in_flight_start_height: Option<usize>,
|
||||
) -> Result<(), BlockDownloadError> {
|
||||
self.ready_batches_size += new_batch.block_batch.size;
|
||||
self.ready_batches.push(new_batch);
|
||||
|
||||
// The height to stop pushing batches into the buffer.
|
||||
let height_to_stop_at = oldest_in_flight_start_height.unwrap_or(u64::MAX);
|
||||
let height_to_stop_at = oldest_in_flight_start_height.unwrap_or(usize::MAX);
|
||||
|
||||
while self
|
||||
.ready_batches
|
||||
|
@ -124,14 +124,14 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
prop_compose! {
|
||||
fn ready_batch_strategy()(start_height in 0_u64..500_000_000) -> ReadyQueueBatch {
|
||||
fn ready_batch_strategy()(start_height in 0_usize..500_000_000) -> ReadyQueueBatch {
|
||||
let (_, peer_handle) = HandleBuilder::new().build();
|
||||
|
||||
ReadyQueueBatch {
|
||||
start_height,
|
||||
block_batch: BlockBatch {
|
||||
blocks: vec![],
|
||||
size: start_height as usize,
|
||||
size: start_height,
|
||||
peer_handle,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ pub struct BlocksToRetrieve<N: NetworkZone> {
|
|||
/// The hash of the last block before this batch.
|
||||
pub prev_id: [u8; 32],
|
||||
/// The expected height of the first block in [`BlocksToRetrieve::ids`].
|
||||
pub start_height: u64,
|
||||
pub start_height: usize,
|
||||
/// The peer who told us about this batch.
|
||||
pub peer_who_told_us: InternalPeerID<N::Addr>,
|
||||
/// The peer who told us about this batch's handle.
|
||||
|
@ -54,7 +54,7 @@ pub struct ChainTracker<N: NetworkZone> {
|
|||
/// A list of [`ChainEntry`]s, in order.
|
||||
entries: VecDeque<ChainEntry<N>>,
|
||||
/// The height of the first block, in the first entry in [`Self::entries`].
|
||||
first_height: u64,
|
||||
first_height: usize,
|
||||
/// The hash of the last block in the last entry.
|
||||
top_seen_hash: [u8; 32],
|
||||
/// The hash of the block one below [`Self::first_height`].
|
||||
|
@ -67,7 +67,7 @@ impl<N: NetworkZone> ChainTracker<N> {
|
|||
/// Creates a new chain tracker.
|
||||
pub fn new(
|
||||
new_entry: ChainEntry<N>,
|
||||
first_height: u64,
|
||||
first_height: usize,
|
||||
our_genesis: [u8; 32],
|
||||
previous_hash: [u8; 32],
|
||||
) -> Self {
|
||||
|
@ -96,14 +96,14 @@ impl<N: NetworkZone> ChainTracker<N> {
|
|||
}
|
||||
|
||||
/// Returns the height of the highest block we are tracking.
|
||||
pub fn top_height(&self) -> u64 {
|
||||
pub fn top_height(&self) -> usize {
|
||||
let top_block_idx = self
|
||||
.entries
|
||||
.iter()
|
||||
.map(|entry| entry.ids.len())
|
||||
.sum::<usize>();
|
||||
|
||||
self.first_height + u64::try_from(top_block_idx).unwrap()
|
||||
self.first_height + top_block_idx
|
||||
}
|
||||
|
||||
/// Returns the total number of queued batches for a certain `batch_size`.
|
||||
|
@ -171,15 +171,12 @@ impl<N: NetworkZone> ChainTracker<N> {
|
|||
// - index of the next pruned block for this seed
|
||||
let end_idx = min(
|
||||
min(entry.ids.len(), max_blocks),
|
||||
usize::try_from(
|
||||
pruning_seed
|
||||
.get_next_pruned_block(self.first_height, CRYPTONOTE_MAX_BLOCK_HEIGHT)
|
||||
.expect("We use local values to calculate height which should be below the sanity limit")
|
||||
// Use a big value as a fallback if the seed does no pruning.
|
||||
.unwrap_or(CRYPTONOTE_MAX_BLOCK_HEIGHT)
|
||||
- self.first_height,
|
||||
)
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
if end_idx == 0 {
|
||||
|
@ -198,7 +195,7 @@ impl<N: NetworkZone> ChainTracker<N> {
|
|||
failures: 0,
|
||||
};
|
||||
|
||||
self.first_height += u64::try_from(end_idx).unwrap();
|
||||
self.first_height += end_idx;
|
||||
// TODO: improve ByteArrayVec API.
|
||||
self.previous_hash = blocks.ids[blocks.ids.len() - 1];
|
||||
|
||||
|
|
|
@ -34,7 +34,7 @@ pub async fn download_batch_task<N: NetworkZone>(
|
|||
client: ClientPoolDropGuard<N>,
|
||||
ids: ByteArrayVec<32>,
|
||||
previous_id: [u8; 32],
|
||||
expected_start_height: u64,
|
||||
expected_start_height: usize,
|
||||
_attempt: usize,
|
||||
) -> BlockDownloadTaskResponse<N> {
|
||||
BlockDownloadTaskResponse {
|
||||
|
@ -51,7 +51,7 @@ async fn request_batch_from_peer<N: NetworkZone>(
|
|||
mut client: ClientPoolDropGuard<N>,
|
||||
ids: ByteArrayVec<32>,
|
||||
previous_id: [u8; 32],
|
||||
expected_start_height: u64,
|
||||
expected_start_height: usize,
|
||||
) -> Result<(ClientPoolDropGuard<N>, BlockBatch), BlockDownloadError> {
|
||||
let request = PeerRequest::Protocol(ProtocolRequest::GetObjects(GetObjectsRequest {
|
||||
blocks: ids.clone(),
|
||||
|
@ -105,7 +105,7 @@ async fn request_batch_from_peer<N: NetworkZone>(
|
|||
|
||||
fn deserialize_batch(
|
||||
blocks_response: GetObjectsResponse,
|
||||
expected_start_height: u64,
|
||||
expected_start_height: usize,
|
||||
requested_ids: ByteArrayVec<32>,
|
||||
previous_id: [u8; 32],
|
||||
peer_handle: ConnectionHandle,
|
||||
|
@ -115,7 +115,7 @@ fn deserialize_batch(
|
|||
.into_par_iter()
|
||||
.enumerate()
|
||||
.map(|(i, block_entry)| {
|
||||
let expected_height = u64::try_from(i).unwrap() + expected_start_height;
|
||||
let expected_height = i + expected_start_height;
|
||||
|
||||
let mut size = block_entry.block.len();
|
||||
|
||||
|
@ -125,7 +125,7 @@ fn deserialize_batch(
|
|||
let block_hash = block.hash();
|
||||
|
||||
// Check the block matches the one requested and the peer sent enough transactions.
|
||||
if requested_ids[i] != block_hash || block.txs.len() != block_entry.txs.len() {
|
||||
if requested_ids[i] != block_hash || block.transactions.len() != block_entry.txs.len() {
|
||||
return Err(BlockDownloadError::PeersResponseWasInvalid);
|
||||
}
|
||||
|
||||
|
@ -177,7 +177,7 @@ fn deserialize_batch(
|
|||
.collect::<Result<Vec<_>, _>>()?;
|
||||
|
||||
// Make sure the transactions in the block were the ones the peer sent.
|
||||
let mut expected_txs = block.txs.iter().collect::<HashSet<_>>();
|
||||
let mut expected_txs = block.transactions.iter().collect::<HashSet<_>>();
|
||||
|
||||
for tx in &txs {
|
||||
if !expected_txs.remove(&tx.hash()) {
|
||||
|
|
|
@ -11,7 +11,6 @@ use futures::{FutureExt, StreamExt};
|
|||
use indexmap::IndexMap;
|
||||
use monero_serai::{
|
||||
block::{Block, BlockHeader},
|
||||
ringct::{RctBase, RctPrunable, RctSignatures},
|
||||
transaction::{Input, Timelock, Transaction, TransactionPrefix},
|
||||
};
|
||||
use proptest::{collection::vec, prelude::*};
|
||||
|
@ -90,30 +89,20 @@ proptest! {
|
|||
|
||||
prop_compose! {
|
||||
/// Returns a strategy to generate a [`Transaction`] that is valid for the block downloader.
|
||||
fn dummy_transaction_stragtegy(height: u64)
|
||||
fn dummy_transaction_stragtegy(height: usize)
|
||||
(
|
||||
extra in vec(any::<u8>(), 0..1_000),
|
||||
timelock in 1_usize..50_000_000,
|
||||
)
|
||||
-> Transaction {
|
||||
Transaction {
|
||||
Transaction::V1 {
|
||||
prefix: TransactionPrefix {
|
||||
version: 1,
|
||||
timelock: Timelock::Block(timelock),
|
||||
additional_timelock: Timelock::Block(timelock),
|
||||
inputs: vec![Input::Gen(height)],
|
||||
outputs: vec![],
|
||||
extra,
|
||||
},
|
||||
signatures: vec![],
|
||||
rct_signatures: RctSignatures {
|
||||
base: RctBase {
|
||||
fee: 0,
|
||||
pseudo_outs: vec![],
|
||||
encrypted_amounts: vec![],
|
||||
commitments: vec![],
|
||||
},
|
||||
prunable: RctPrunable::Null
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -121,25 +110,25 @@ prop_compose! {
|
|||
prop_compose! {
|
||||
/// Returns a strategy to generate a [`Block`] that is valid for the block downloader.
|
||||
fn dummy_block_stragtegy(
|
||||
height: u64,
|
||||
height: usize,
|
||||
previous: [u8; 32],
|
||||
)
|
||||
(
|
||||
miner_tx in dummy_transaction_stragtegy(height),
|
||||
miner_transaction in dummy_transaction_stragtegy(height),
|
||||
txs in vec(dummy_transaction_stragtegy(height), 0..25)
|
||||
)
|
||||
-> (Block, Vec<Transaction>) {
|
||||
(
|
||||
Block {
|
||||
header: BlockHeader {
|
||||
major_version: 0,
|
||||
minor_version: 0,
|
||||
hardfork_version: 0,
|
||||
hardfork_signal: 0,
|
||||
timestamp: 0,
|
||||
previous,
|
||||
nonce: 0,
|
||||
},
|
||||
miner_tx,
|
||||
txs: txs.iter().map(Transaction::hash).collect(),
|
||||
miner_transaction,
|
||||
transactions: txs.iter().map(Transaction::hash).collect(),
|
||||
},
|
||||
txs
|
||||
)
|
||||
|
@ -167,7 +156,7 @@ prop_compose! {
|
|||
for (height, mut block) in blocks.into_iter().enumerate() {
|
||||
if let Some(last) = blockchain.last() {
|
||||
block.0.header.previous = *last.0;
|
||||
block.0.miner_tx.prefix.inputs = vec![Input::Gen(height as u64)]
|
||||
block.0.miner_transaction.prefix_mut().inputs = vec![Input::Gen(height)]
|
||||
}
|
||||
|
||||
blockchain.insert(block.0.hash(), block);
|
||||
|
|
|
@ -38,7 +38,7 @@ enum OutboundConnectorError {
|
|||
/// set needs specific data that none of the currently connected peers have.
|
||||
pub struct MakeConnectionRequest {
|
||||
/// The block needed that no connected peers have due to pruning.
|
||||
block_needed: Option<u64>,
|
||||
block_needed: Option<usize>,
|
||||
}
|
||||
|
||||
/// The outbound connection count keeper.
|
||||
|
|
|
@ -99,7 +99,7 @@ impl<N: NetworkZone> PeerSyncSvc<N> {
|
|||
fn peers_to_sync_from(
|
||||
&self,
|
||||
current_cum_diff: u128,
|
||||
block_needed: Option<u64>,
|
||||
block_needed: Option<usize>,
|
||||
) -> Vec<InternalPeerID<N::Addr>> {
|
||||
self.cumulative_difficulties
|
||||
.range((current_cum_diff + 1)..)
|
||||
|
|
|
@ -22,13 +22,13 @@ use std::cmp::Ordering;
|
|||
|
||||
use thiserror::Error;
|
||||
|
||||
pub const CRYPTONOTE_MAX_BLOCK_HEIGHT: u64 = 500000000;
|
||||
pub const CRYPTONOTE_MAX_BLOCK_HEIGHT: usize = 500000000;
|
||||
/// The default log stripes for Monero pruning.
|
||||
pub const CRYPTONOTE_PRUNING_LOG_STRIPES: u32 = 3;
|
||||
/// The amount of blocks that peers keep before another stripe starts storing blocks.
|
||||
pub const CRYPTONOTE_PRUNING_STRIPE_SIZE: u64 = 4096;
|
||||
pub const CRYPTONOTE_PRUNING_STRIPE_SIZE: usize = 4096;
|
||||
/// The amount of blocks from the top of the chain that should not be pruned.
|
||||
pub const CRYPTONOTE_PRUNING_TIP_BLOCKS: u64 = 5500;
|
||||
pub const CRYPTONOTE_PRUNING_TIP_BLOCKS: usize = 5500;
|
||||
|
||||
const PRUNING_SEED_LOG_STRIPES_SHIFT: u32 = 7;
|
||||
const PRUNING_SEED_STRIPE_SHIFT: u32 = 0;
|
||||
|
@ -127,7 +127,7 @@ impl PruningSeed {
|
|||
}
|
||||
|
||||
/// Returns `true` if a peer with this pruning seed should have a non-pruned version of a block.
|
||||
pub fn has_full_block(&self, height: u64, blockchain_height: u64) -> bool {
|
||||
pub fn has_full_block(&self, height: usize, blockchain_height: usize) -> bool {
|
||||
match self {
|
||||
PruningSeed::NotPruned => true,
|
||||
PruningSeed::Pruned(seed) => seed.has_full_block(height, blockchain_height),
|
||||
|
@ -151,9 +151,9 @@ impl PruningSeed {
|
|||
/// This function will also error if `block_height` > `blockchain_height`
|
||||
pub fn get_next_pruned_block(
|
||||
&self,
|
||||
block_height: u64,
|
||||
blockchain_height: u64,
|
||||
) -> Result<Option<u64>, PruningError> {
|
||||
block_height: usize,
|
||||
blockchain_height: usize,
|
||||
) -> Result<Option<usize>, PruningError> {
|
||||
Ok(match self {
|
||||
PruningSeed::NotPruned => None,
|
||||
PruningSeed::Pruned(seed) => {
|
||||
|
@ -177,9 +177,9 @@ impl PruningSeed {
|
|||
///
|
||||
pub fn get_next_unpruned_block(
|
||||
&self,
|
||||
block_height: u64,
|
||||
blockchain_height: u64,
|
||||
) -> Result<u64, PruningError> {
|
||||
block_height: usize,
|
||||
blockchain_height: usize,
|
||||
) -> Result<usize, PruningError> {
|
||||
Ok(match self {
|
||||
PruningSeed::NotPruned => block_height,
|
||||
PruningSeed::Pruned(seed) => {
|
||||
|
@ -312,7 +312,7 @@ impl DecompressedPruningSeed {
|
|||
}
|
||||
|
||||
/// Returns `true` if a peer with this pruning seed should have a non-pruned version of a block.
|
||||
pub fn has_full_block(&self, height: u64, blockchain_height: u64) -> bool {
|
||||
pub fn has_full_block(&self, height: usize, blockchain_height: usize) -> bool {
|
||||
match get_block_pruning_stripe(height, blockchain_height, self.log_stripes) {
|
||||
Some(block_stripe) => self.stripe == block_stripe,
|
||||
None => true,
|
||||
|
@ -334,9 +334,9 @@ impl DecompressedPruningSeed {
|
|||
///
|
||||
pub fn get_next_unpruned_block(
|
||||
&self,
|
||||
block_height: u64,
|
||||
blockchain_height: u64,
|
||||
) -> Result<u64, PruningError> {
|
||||
block_height: usize,
|
||||
blockchain_height: usize,
|
||||
) -> Result<usize, PruningError> {
|
||||
if block_height > CRYPTONOTE_MAX_BLOCK_HEIGHT || block_height > blockchain_height {
|
||||
return Err(PruningError::BlockHeightTooLarge);
|
||||
}
|
||||
|
@ -373,7 +373,7 @@ impl DecompressedPruningSeed {
|
|||
|
||||
// amt_of_cycles * blocks in a cycle + how many blocks through a cycles until the seed starts storing blocks
|
||||
let calculated_height = cycles_start * (CRYPTONOTE_PRUNING_STRIPE_SIZE << self.log_stripes)
|
||||
+ (self.stripe as u64 - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE;
|
||||
+ (self.stripe as usize - 1) * CRYPTONOTE_PRUNING_STRIPE_SIZE;
|
||||
|
||||
if calculated_height + CRYPTONOTE_PRUNING_TIP_BLOCKS > blockchain_height {
|
||||
// if our calculated height is greater than the amount of tip blocks then the start of the tip blocks will be the next un-pruned
|
||||
|
@ -400,9 +400,9 @@ impl DecompressedPruningSeed {
|
|||
///
|
||||
pub fn get_next_pruned_block(
|
||||
&self,
|
||||
block_height: u64,
|
||||
blockchain_height: u64,
|
||||
) -> Result<Option<u64>, PruningError> {
|
||||
block_height: usize,
|
||||
blockchain_height: usize,
|
||||
) -> Result<Option<usize>, PruningError> {
|
||||
if block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height {
|
||||
// If we are within `CRYPTONOTE_PRUNING_TIP_BLOCKS` of the chain we should
|
||||
// not prune blocks.
|
||||
|
@ -434,16 +434,16 @@ impl DecompressedPruningSeed {
|
|||
}
|
||||
|
||||
fn get_block_pruning_stripe(
|
||||
block_height: u64,
|
||||
blockchain_height: u64,
|
||||
block_height: usize,
|
||||
blockchain_height: usize,
|
||||
log_stripe: u32,
|
||||
) -> Option<u32> {
|
||||
if block_height + CRYPTONOTE_PRUNING_TIP_BLOCKS >= blockchain_height {
|
||||
None
|
||||
} else {
|
||||
Some(
|
||||
(((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & ((1 << log_stripe) as u64 - 1)) + 1)
|
||||
as u32, // it's trivial to prove it's ok to us `as` here
|
||||
(((block_height / CRYPTONOTE_PRUNING_STRIPE_SIZE) & ((1 << log_stripe) as usize - 1))
|
||||
+ 1) as u32, // it's trivial to prove it's ok to us `as` here
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -503,7 +503,7 @@ mod tests {
|
|||
for i in 0_u32..8 {
|
||||
assert_eq!(
|
||||
get_block_pruning_stripe(
|
||||
(i * 4096) as u64,
|
||||
(i * 4096) as usize,
|
||||
blockchain_height,
|
||||
CRYPTONOTE_PRUNING_LOG_STRIPES
|
||||
)
|
||||
|
@ -515,7 +515,7 @@ mod tests {
|
|||
for i in 0_u32..8 {
|
||||
assert_eq!(
|
||||
get_block_pruning_stripe(
|
||||
32768 + (i * 4096) as u64,
|
||||
32768 + (i * 4096) as usize,
|
||||
blockchain_height,
|
||||
CRYPTONOTE_PRUNING_LOG_STRIPES
|
||||
)
|
||||
|
@ -527,7 +527,7 @@ mod tests {
|
|||
for i in 1_u32..8 {
|
||||
assert_eq!(
|
||||
get_block_pruning_stripe(
|
||||
32767 + (i * 4096) as u64,
|
||||
32767 + (i * 4096) as usize,
|
||||
blockchain_height,
|
||||
CRYPTONOTE_PRUNING_LOG_STRIPES
|
||||
)
|
||||
|
@ -553,23 +553,23 @@ mod tests {
|
|||
for (i, seed) in all_valid_seeds.iter().enumerate() {
|
||||
assert_eq!(
|
||||
seed.get_next_unpruned_block(0, blockchain_height).unwrap(),
|
||||
i as u64 * 4096
|
||||
i * 4096
|
||||
)
|
||||
}
|
||||
|
||||
for (i, seed) in all_valid_seeds.iter().enumerate() {
|
||||
assert_eq!(
|
||||
seed.get_next_unpruned_block((i as u64 + 1) * 4096, blockchain_height)
|
||||
seed.get_next_unpruned_block((i + 1) * 4096, blockchain_height)
|
||||
.unwrap(),
|
||||
i as u64 * 4096 + 32768
|
||||
i * 4096 + 32768
|
||||
)
|
||||
}
|
||||
|
||||
for (i, seed) in all_valid_seeds.iter().enumerate() {
|
||||
assert_eq!(
|
||||
seed.get_next_unpruned_block((i as u64 + 8) * 4096, blockchain_height)
|
||||
seed.get_next_unpruned_block((i + 8) * 4096, blockchain_height)
|
||||
.unwrap(),
|
||||
i as u64 * 4096 + 32768
|
||||
i * 4096 + 32768
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -610,19 +610,19 @@ mod tests {
|
|||
|
||||
for (i, seed) in all_valid_seeds.iter().enumerate() {
|
||||
assert_eq!(
|
||||
seed.get_next_pruned_block((i as u64 + 1) * 4096, blockchain_height)
|
||||
seed.get_next_pruned_block((i + 1) * 4096, blockchain_height)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
(i as u64 + 1) * 4096
|
||||
(i + 1) * 4096
|
||||
)
|
||||
}
|
||||
|
||||
for (i, seed) in all_valid_seeds.iter().enumerate() {
|
||||
assert_eq!(
|
||||
seed.get_next_pruned_block((i as u64 + 8) * 4096, blockchain_height)
|
||||
seed.get_next_pruned_block((i + 8) * 4096, blockchain_height)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
(i as u64 + 9) * 4096
|
||||
(i + 9) * 4096
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -9,7 +9,24 @@ repository = "https://github.com/Cuprate/cuprate/tree/main/rpc/cuprate-rpc-inte
|
|||
keywords = ["cuprate", "rpc", "interface"]
|
||||
|
||||
[features]
|
||||
default = ["dummy", "serde"]
|
||||
dummy = []
|
||||
|
||||
[dependencies]
|
||||
cuprate-epee-encoding = { path = "../../net/epee-encoding", default-features = false }
|
||||
cuprate-json-rpc = { path = "../json-rpc", default-features = false }
|
||||
cuprate-rpc-types = { path = "../types", features = ["serde", "epee"], default-features = false }
|
||||
cuprate-helper = { path = "../../helper", features = ["asynch"], default-features = false }
|
||||
|
||||
axum = { version = "0.7.5", features = ["json"], default-features = false }
|
||||
serde = { workspace = true, optional = true }
|
||||
serde_json = { workspace = true, features = ["std"] }
|
||||
tower = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
axum = { version = "0.7.5", features = ["json", "tokio", "http2"] }
|
||||
serde_json = { workspace = true, features = ["std"] }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
ureq = { version = "2.10.0", features = ["json"] }
|
161
rpc/interface/README.md
Normal file
161
rpc/interface/README.md
Normal file
|
@ -0,0 +1,161 @@
|
|||
# `cuprate-rpc-interface`
|
||||
This crate provides Cuprate's RPC _interface_.
|
||||
|
||||
This crate is _not_ a standalone RPC server, it is just the interface.
|
||||
|
||||
```text
|
||||
cuprate-rpc-interface provides these parts
|
||||
│ │
|
||||
┌───────────────────────────┤ ├───────────────────┐
|
||||
▼ ▼ ▼ ▼
|
||||
CLIENT ─► ROUTE ─► REQUEST ─► HANDLER ─► RESPONSE ─► CLIENT
|
||||
▲ ▲
|
||||
└───┬───┘
|
||||
│
|
||||
You provide this part
|
||||
```
|
||||
|
||||
Everything coming _in_ from a client is handled by this crate.
|
||||
|
||||
This is where your [`RpcHandler`] turns this [`RpcRequest`] into a [`RpcResponse`].
|
||||
|
||||
You hand this `Response` back to `cuprate-rpc-interface` and it will take care of sending it back to the client.
|
||||
|
||||
The main handler used by Cuprate is implemented in the `cuprate-rpc-handler` crate;
|
||||
it implements the standard RPC handlers modeled after `monerod`.
|
||||
|
||||
# Purpose
|
||||
`cuprate-rpc-interface` is built on-top of [`axum`],
|
||||
which is the crate _actually_ handling everything.
|
||||
|
||||
This crate simply handles:
|
||||
- Registering endpoint routes (e.g. `/get_block.bin`)
|
||||
- Defining handler function signatures
|
||||
- (De)serialization of requests/responses (JSON-RPC, binary, JSON)
|
||||
|
||||
The actual server details are all handled by the [`axum`] and [`tower`] ecosystem.
|
||||
|
||||
The proper usage of this crate is to:
|
||||
1. Implement a [`RpcHandler`]
|
||||
2. Use it with [`RouterBuilder`] to generate an
|
||||
[`axum::Router`] with all Monero RPC routes set
|
||||
3. Do whatever with it
|
||||
|
||||
# The [`RpcHandler`]
|
||||
This is your [`tower::Service`] that converts [`RpcRequest`]s into [`RpcResponse`]s,
|
||||
i.e. the "inner handler".
|
||||
|
||||
Said concretely, `RpcHandler` is a `tower::Service` where the associated types are from this crate:
|
||||
- [`RpcRequest`]
|
||||
- [`RpcResponse`]
|
||||
- [`RpcError`]
|
||||
|
||||
`RpcHandler`'s [`Future`](std::future::Future) is generic, _although_,
|
||||
it must output `Result<RpcResponse, RpcError>`.
|
||||
|
||||
The `RpcHandler` must also hold some state that is required
|
||||
for RPC server operation.
|
||||
|
||||
The only state currently needed is [`RpcHandler::restricted`], which determines if an RPC
|
||||
server is restricted or not, and thus, if some endpoints/methods are allowed or not.
|
||||
|
||||
# Unknown endpoint behavior
|
||||
TODO: decide what this crate should return (per different endpoint)
|
||||
when a request is received to an unknown endpoint, including HTTP stuff, e.g. status code.
|
||||
|
||||
# Unknown JSON-RPC method behavior
|
||||
TODO: decide what this crate returns when a `/json_rpc`
|
||||
request is received with an unknown method, including HTTP stuff, e.g. status code.
|
||||
|
||||
# Example
|
||||
Example usage of this crate + starting an RPC server.
|
||||
|
||||
This uses `RpcHandlerDummy` as the handler; it always responds with the
|
||||
correct response type, but set to a default value regardless of the request.
|
||||
|
||||
```rust
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::{net::TcpListener, sync::Barrier};
|
||||
|
||||
use cuprate_json_rpc::{Request, Response, Id};
|
||||
use cuprate_rpc_types::{
|
||||
json::{JsonRpcRequest, JsonRpcResponse, GetBlockCountResponse},
|
||||
other::{OtherRequest, OtherResponse},
|
||||
};
|
||||
use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy, RpcRequest};
|
||||
|
||||
// Send a `/get_height` request. This endpoint has no inputs.
|
||||
async fn get_height(port: u16) -> OtherResponse {
|
||||
let url = format!("http://127.0.0.1:{port}/get_height");
|
||||
ureq::get(&url)
|
||||
.set("Content-Type", "application/json")
|
||||
.call()
|
||||
.unwrap()
|
||||
.into_json()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
// Send a JSON-RPC request with the `get_block_count` method.
|
||||
//
|
||||
// The returned [`String`] is JSON.
|
||||
async fn get_block_count(port: u16) -> String {
|
||||
let url = format!("http://127.0.0.1:{port}/json_rpc");
|
||||
let method = JsonRpcRequest::GetBlockCount(Default::default());
|
||||
let request = Request::new(method);
|
||||
ureq::get(&url)
|
||||
.set("Content-Type", "application/json")
|
||||
.send_json(request)
|
||||
.unwrap()
|
||||
.into_string()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
// Start a local RPC server.
|
||||
let port = {
|
||||
// Create the router.
|
||||
let state = RpcHandlerDummy { restricted: false };
|
||||
let router = RouterBuilder::new().all().build().with_state(state);
|
||||
|
||||
// Start a server.
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.await
|
||||
.unwrap();
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
|
||||
// Run the server with `axum`.
|
||||
tokio::task::spawn(async move {
|
||||
axum::serve(listener, router).await.unwrap();
|
||||
});
|
||||
|
||||
port
|
||||
};
|
||||
|
||||
// Assert the response is the default.
|
||||
let response = get_height(port).await;
|
||||
let expected = OtherResponse::GetHeight(Default::default());
|
||||
assert_eq!(response, expected);
|
||||
|
||||
// Assert the response JSON is correct.
|
||||
let response = get_block_count(port).await;
|
||||
let expected = r#"{"jsonrpc":"2.0","id":null,"result":{"status":"OK","untrusted":false,"count":0}}"#;
|
||||
assert_eq!(response, expected);
|
||||
|
||||
// Assert that (de)serialization works.
|
||||
let expected = Response::ok(Id::Null, Default::default());
|
||||
let response: Response<GetBlockCountResponse> = serde_json::from_str(&response).unwrap();
|
||||
assert_eq!(response, expected);
|
||||
}
|
||||
```
|
||||
|
||||
# Feature flags
|
||||
List of feature flags for `cuprate-rpc-interface`.
|
||||
|
||||
All are enabled by default.
|
||||
|
||||
| Feature flag | Does what |
|
||||
|--------------|-----------|
|
||||
| `serde` | Enables serde on applicable types
|
||||
| `dummy` | Enables the `RpcHandlerDummy` type
|
|
@ -1 +1,123 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
//---------------------------------------------------------------------------------------------------- Lints
|
||||
// Forbid lints.
|
||||
// Our code, and code generated (e.g macros) cannot overrule these.
|
||||
#![forbid(
|
||||
// `unsafe` is allowed but it _must_ be
|
||||
// commented with `SAFETY: reason`.
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
|
||||
// Never.
|
||||
unused_unsafe,
|
||||
redundant_semicolons,
|
||||
unused_allocation,
|
||||
coherence_leak_check,
|
||||
while_true,
|
||||
|
||||
// Maybe can be put into `#[deny]`.
|
||||
unconditional_recursion,
|
||||
for_loops_over_fallibles,
|
||||
unused_braces,
|
||||
unused_labels,
|
||||
keyword_idents,
|
||||
non_ascii_idents,
|
||||
variant_size_differences,
|
||||
single_use_lifetimes,
|
||||
|
||||
// Probably can be put into `#[deny]`.
|
||||
future_incompatible,
|
||||
let_underscore,
|
||||
break_with_label_and_loop,
|
||||
duplicate_macro_attributes,
|
||||
exported_private_dependencies,
|
||||
large_assignments,
|
||||
overlapping_range_endpoints,
|
||||
semicolon_in_expressions_from_macros,
|
||||
noop_method_call,
|
||||
)]
|
||||
// Deny lints.
|
||||
// Some of these are `#[allow]`'ed on a per-case basis.
|
||||
#![deny(
|
||||
clippy::all,
|
||||
clippy::correctness,
|
||||
clippy::suspicious,
|
||||
clippy::style,
|
||||
clippy::complexity,
|
||||
clippy::perf,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
clippy::cargo,
|
||||
unused_doc_comments,
|
||||
unused_mut,
|
||||
missing_docs,
|
||||
deprecated,
|
||||
unused_comparisons,
|
||||
nonstandard_style,
|
||||
unreachable_pub
|
||||
)]
|
||||
#![allow(
|
||||
// FIXME: this lint affects crates outside of
|
||||
// `database/` for some reason, allow for now.
|
||||
clippy::cargo_common_metadata,
|
||||
|
||||
// FIXME: adding `#[must_use]` onto everything
|
||||
// might just be more annoying than useful...
|
||||
// although it is sometimes nice.
|
||||
clippy::must_use_candidate,
|
||||
|
||||
// FIXME: good lint but too many false positives
|
||||
// with our `Env` + `RwLock` setup.
|
||||
clippy::significant_drop_tightening,
|
||||
|
||||
// FIXME: good lint but is less clear in most cases.
|
||||
clippy::items_after_statements,
|
||||
|
||||
// TODO
|
||||
rustdoc::bare_urls,
|
||||
|
||||
clippy::module_name_repetitions,
|
||||
clippy::module_inception,
|
||||
clippy::redundant_pub_crate,
|
||||
clippy::option_if_let_else,
|
||||
)]
|
||||
// Allow some lints when running in debug mode.
|
||||
#![cfg_attr(
|
||||
debug_assertions,
|
||||
allow(
|
||||
clippy::todo,
|
||||
clippy::multiple_crate_versions,
|
||||
unused_imports,
|
||||
unused_variables
|
||||
)
|
||||
)]
|
||||
// Allow some lints in tests.
|
||||
#![cfg_attr(
|
||||
test,
|
||||
allow(
|
||||
clippy::cognitive_complexity,
|
||||
clippy::needless_pass_by_value,
|
||||
clippy::cast_possible_truncation,
|
||||
clippy::too_many_lines
|
||||
)
|
||||
)]
|
||||
// TODO: remove me after finishing impl
|
||||
#![allow(dead_code, unreachable_code, clippy::diverging_sub_expression)]
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Mod
|
||||
mod route;
|
||||
mod router_builder;
|
||||
mod rpc_error;
|
||||
mod rpc_handler;
|
||||
#[cfg(feature = "dummy")]
|
||||
mod rpc_handler_dummy;
|
||||
mod rpc_request;
|
||||
mod rpc_response;
|
||||
|
||||
pub use router_builder::RouterBuilder;
|
||||
pub use rpc_error::RpcError;
|
||||
pub use rpc_handler::RpcHandler;
|
||||
#[cfg(feature = "dummy")]
|
||||
pub use rpc_handler_dummy::RpcHandlerDummy;
|
||||
pub use rpc_request::RpcRequest;
|
||||
pub use rpc_response::RpcResponse;
|
||||
|
|
108
rpc/interface/src/route/bin.rs
Normal file
108
rpc/interface/src/route/bin.rs
Normal file
|
@ -0,0 +1,108 @@
|
|||
//! Binary route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::{body::Bytes, extract::State, http::StatusCode};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_epee_encoding::from_bytes;
|
||||
use cuprate_rpc_types::bin::{BinRequest, BinResponse, GetTransactionPoolHashesRequest};
|
||||
|
||||
use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// This macro generates route functions that expect input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
/// TODO
|
||||
#[allow(unused_mut)]
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
mut request: Bytes,
|
||||
) -> Result<Bytes, StatusCode> {
|
||||
// Serialize into the request type.
|
||||
let request = BinRequest::$variant(
|
||||
from_bytes(&mut request).map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?
|
||||
);
|
||||
|
||||
generate_endpoints_inner!($variant, handler, request)
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// This macro generates route functions that expect _no_ input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_no_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type (that is empty)
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
/// TODO
|
||||
#[allow(unused_mut)]
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
) -> Result<Bytes, StatusCode> {
|
||||
const REQUEST: BinRequest = BinRequest::$variant([<$variant Request>] {});
|
||||
generate_endpoints_inner!($variant, handler, REQUEST)
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// De-duplicated inner function body for:
|
||||
/// - [`generate_endpoints_with_input`]
|
||||
/// - [`generate_endpoints_with_no_input`]
|
||||
macro_rules! generate_endpoints_inner {
|
||||
($variant:ident, $handler:ident, $request:expr) => {
|
||||
paste::paste! {
|
||||
{
|
||||
// Send request.
|
||||
let request = RpcRequest::Binary($request);
|
||||
let channel = $handler.oneshot(request).await?;
|
||||
|
||||
// Assert the response from the inner handler is correct.
|
||||
let RpcResponse::Binary(response) = channel else {
|
||||
panic!("RPC handler did not return a binary response");
|
||||
};
|
||||
let BinResponse::$variant(response) = response else {
|
||||
panic!("RPC handler returned incorrect response");
|
||||
};
|
||||
|
||||
// Serialize to bytes and respond.
|
||||
match cuprate_epee_encoding::to_bytes(response) {
|
||||
Ok(bytes) => Ok(bytes.freeze()),
|
||||
Err(e) => Err(StatusCode::INTERNAL_SERVER_ERROR),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_endpoints_with_input! {
|
||||
get_blocks => GetBlocks,
|
||||
get_blocks_by_height => GetBlocksByHeight,
|
||||
get_hashes => GetHashes,
|
||||
get_o_indexes => GetOutputIndexes,
|
||||
get_outs => GetOuts,
|
||||
get_output_distribution => GetOutputDistribution
|
||||
}
|
||||
|
||||
generate_endpoints_with_no_input! {
|
||||
get_transaction_pool_hashes => GetTransactionPoolHashes
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
18
rpc/interface/src/route/fallback.rs
Normal file
18
rpc/interface/src/route/fallback.rs
Normal file
|
@ -0,0 +1,18 @@
|
|||
//! Fallback route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::http::StatusCode;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// Fallback route function.
|
||||
///
|
||||
/// This is used as the fallback endpoint in [`crate::RouterBuilder`].
|
||||
pub(crate) async fn fallback() -> StatusCode {
|
||||
StatusCode::NOT_FOUND
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
68
rpc/interface/src/route/json_rpc.rs
Normal file
68
rpc/interface/src/route/json_rpc.rs
Normal file
|
@ -0,0 +1,68 @@
|
|||
//! JSON-RPC 2.0 endpoint route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::borrow::Cow;
|
||||
|
||||
use axum::{extract::State, http::StatusCode, Json};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_json_rpc::{
|
||||
error::{ErrorCode, ErrorObject},
|
||||
Id,
|
||||
};
|
||||
use cuprate_rpc_types::{
|
||||
json::{JsonRpcRequest, JsonRpcResponse},
|
||||
RpcCallValue,
|
||||
};
|
||||
|
||||
use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// The `/json_rpc` route function used in [`crate::RouterBuilder`].
|
||||
pub(crate) async fn json_rpc<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
Json(request): Json<cuprate_json_rpc::Request<JsonRpcRequest>>,
|
||||
) -> Result<Json<cuprate_json_rpc::Response<JsonRpcResponse>>, StatusCode> {
|
||||
// TODO: <https://www.jsonrpc.org/specification#notification>
|
||||
//
|
||||
// JSON-RPC notifications (requests without `id`)
|
||||
// must not be responded too, although, the request's side-effects
|
||||
// must remain. How to do this considering this function will
|
||||
// always return and cause `axum` to respond?
|
||||
|
||||
// Return early if this RPC server is restricted and
|
||||
// the requested method is only for non-restricted RPC.
|
||||
if request.body.is_restricted() && handler.restricted() {
|
||||
let error_object = ErrorObject {
|
||||
code: ErrorCode::ServerError(-1 /* TODO */),
|
||||
message: Cow::Borrowed("Restricted. TODO: mimic monerod message"),
|
||||
data: None,
|
||||
};
|
||||
|
||||
// JSON-RPC 2.0 rule:
|
||||
// If there was an error in detecting the `Request`'s ID,
|
||||
// the `Response` must contain an `Id::Null`
|
||||
let id = request.id.unwrap_or(Id::Null);
|
||||
|
||||
let response = cuprate_json_rpc::Response::err(id, error_object);
|
||||
|
||||
return Ok(Json(response));
|
||||
}
|
||||
|
||||
// Send request.
|
||||
let request = RpcRequest::JsonRpc(request);
|
||||
let channel = handler.oneshot(request).await?;
|
||||
|
||||
// Assert the response from the inner handler is correct.
|
||||
let RpcResponse::JsonRpc(response) = channel else {
|
||||
panic!("RPC handler returned incorrect response");
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
9
rpc/interface/src/route/mod.rs
Normal file
9
rpc/interface/src/route/mod.rs
Normal file
|
@ -0,0 +1,9 @@
|
|||
//! Routing functions.
|
||||
//!
|
||||
//! These are the function signatures passed to
|
||||
//! [`crate::RouterBuilder`] when registering routes.
|
||||
|
||||
pub(crate) mod bin;
|
||||
pub(crate) mod fallback;
|
||||
pub(crate) mod json_rpc;
|
||||
pub(crate) mod other;
|
138
rpc/interface/src/route/other.rs
Normal file
138
rpc/interface/src/route/other.rs
Normal file
|
@ -0,0 +1,138 @@
|
|||
//! Other JSON endpoint route functions.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::{extract::State, http::StatusCode, Json};
|
||||
use tower::ServiceExt;
|
||||
|
||||
use cuprate_rpc_types::{
|
||||
other::{
|
||||
GetAltBlocksHashesRequest, GetAltBlocksHashesResponse, GetHeightRequest, GetHeightResponse,
|
||||
GetLimitRequest, GetLimitResponse, GetNetStatsRequest, GetNetStatsResponse, GetOutsRequest,
|
||||
GetOutsResponse, GetPeerListRequest, GetPeerListResponse, GetPublicNodesRequest,
|
||||
GetPublicNodesResponse, GetTransactionPoolHashesRequest, GetTransactionPoolHashesResponse,
|
||||
GetTransactionPoolRequest, GetTransactionPoolResponse, GetTransactionPoolStatsRequest,
|
||||
GetTransactionPoolStatsResponse, GetTransactionsRequest, GetTransactionsResponse,
|
||||
InPeersRequest, InPeersResponse, IsKeyImageSpentRequest, IsKeyImageSpentResponse,
|
||||
MiningStatusRequest, MiningStatusResponse, OtherRequest, OtherResponse, OutPeersRequest,
|
||||
OutPeersResponse, PopBlocksRequest, PopBlocksResponse, SaveBcRequest, SaveBcResponse,
|
||||
SendRawTransactionRequest, SendRawTransactionResponse, SetBootstrapDaemonRequest,
|
||||
SetBootstrapDaemonResponse, SetLimitRequest, SetLimitResponse, SetLogCategoriesRequest,
|
||||
SetLogCategoriesResponse, SetLogHashRateRequest, SetLogHashRateResponse,
|
||||
SetLogLevelRequest, SetLogLevelResponse, StartMiningRequest, StartMiningResponse,
|
||||
StopDaemonRequest, StopDaemonResponse, StopMiningRequest, StopMiningResponse,
|
||||
UpdateRequest, UpdateResponse,
|
||||
},
|
||||
RpcCall,
|
||||
};
|
||||
|
||||
use crate::{rpc_handler::RpcHandler, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Routes
|
||||
/// This macro generates route functions that expect input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
Json(request): Json<[<$variant Request>]>,
|
||||
) -> Result<Json<[<$variant Response>]>, StatusCode> {
|
||||
generate_endpoints_inner!($variant, handler, request)
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// This macro generates route functions that expect _no_ input.
|
||||
///
|
||||
/// See below for usage.
|
||||
macro_rules! generate_endpoints_with_no_input {
|
||||
($(
|
||||
// Syntax:
|
||||
// Function name => Expected input type (that is empty)
|
||||
$endpoint:ident => $variant:ident
|
||||
),*) => { paste::paste! {
|
||||
$(
|
||||
pub(crate) async fn $endpoint<H: RpcHandler>(
|
||||
State(handler): State<H>,
|
||||
) -> Result<Json<[<$variant Response>]>, StatusCode> {
|
||||
generate_endpoints_inner!($variant, handler, [<$variant Request>] {})
|
||||
}
|
||||
)*
|
||||
}};
|
||||
}
|
||||
|
||||
/// De-duplicated inner function body for:
|
||||
/// - [`generate_endpoints_with_input`]
|
||||
/// - [`generate_endpoints_with_no_input`]
|
||||
macro_rules! generate_endpoints_inner {
|
||||
($variant:ident, $handler:ident, $request:expr) => {
|
||||
paste::paste! {
|
||||
{
|
||||
// Check if restricted.
|
||||
if [<$variant Request>]::IS_RESTRICTED && $handler.restricted() {
|
||||
// TODO: mimic `monerod` behavior.
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
// Send request.
|
||||
let request = RpcRequest::Other(OtherRequest::$variant($request));
|
||||
let channel = $handler.oneshot(request).await?;
|
||||
|
||||
// Assert the response from the inner handler is correct.
|
||||
let RpcResponse::Other(response) = channel else {
|
||||
panic!("RPC handler did not return a binary response");
|
||||
};
|
||||
let OtherResponse::$variant(response) = response else {
|
||||
panic!("RPC handler returned incorrect response")
|
||||
};
|
||||
|
||||
Ok(Json(response))
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_endpoints_with_input! {
|
||||
get_transactions => GetTransactions,
|
||||
is_key_image_spent => IsKeyImageSpent,
|
||||
send_raw_transaction => SendRawTransaction,
|
||||
start_mining => StartMining,
|
||||
get_peer_list => GetPeerList,
|
||||
set_log_hash_rate => SetLogHashRate,
|
||||
set_log_level => SetLogLevel,
|
||||
set_log_categories => SetLogCategories,
|
||||
set_bootstrap_daemon => SetBootstrapDaemon,
|
||||
set_limit => SetLimit,
|
||||
out_peers => OutPeers,
|
||||
in_peers => InPeers,
|
||||
get_outs => GetOuts,
|
||||
update => Update,
|
||||
pop_blocks => PopBlocks,
|
||||
get_public_nodes => GetPublicNodes
|
||||
}
|
||||
|
||||
generate_endpoints_with_no_input! {
|
||||
get_height => GetHeight,
|
||||
get_alt_blocks_hashes => GetAltBlocksHashes,
|
||||
stop_mining => StopMining,
|
||||
mining_status => MiningStatus,
|
||||
save_bc => SaveBc,
|
||||
get_transaction_pool => GetTransactionPool,
|
||||
get_transaction_pool_stats => GetTransactionPoolStats,
|
||||
stop_daemon => StopDaemon,
|
||||
get_limit => GetLimit,
|
||||
get_net_stats => GetNetStats,
|
||||
get_transaction_pool_hashes => GetTransactionPoolHashes
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
198
rpc/interface/src/router_builder.rs
Normal file
198
rpc/interface/src/router_builder.rs
Normal file
|
@ -0,0 +1,198 @@
|
|||
//! Free functions.
|
||||
|
||||
use std::marker::PhantomData;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use axum::{
|
||||
routing::{method_routing::get, post},
|
||||
Router,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
route::{bin, fallback, json_rpc, other},
|
||||
rpc_handler::RpcHandler,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RouterBuilder
|
||||
/// Generate the `RouterBuilder` struct.
|
||||
macro_rules! generate_router_builder {
|
||||
($(
|
||||
// Syntax:
|
||||
// $BUILDER_FUNCTION_NAME =>
|
||||
// $ACTUAL_ENDPOINT_STRING =>
|
||||
// $ENDPOINT_FUNCTION_MODULE::$ENDPOINT_FUNCTION =>
|
||||
// ($HTTP_METHOD(s))
|
||||
$endpoint_ident:ident =>
|
||||
$endpoint_string:literal =>
|
||||
$endpoint_module:ident::$endpoint_fn:ident =>
|
||||
($($http_method:ident),*)
|
||||
),* $(,)?) => {
|
||||
/// Builder for creating the RPC router.
|
||||
///
|
||||
/// This builder allows you to selectively enable endpoints for the router,
|
||||
/// and a [`fallback`](RouterBuilder::fallback) route.
|
||||
///
|
||||
/// The [`default`](RouterBuilder::default) is to enable [`all`](RouterBuilder::all) routes.
|
||||
///
|
||||
/// # Routes
|
||||
/// Functions that enable routes are separated into 3 groups:
|
||||
/// - `json_rpc` (enables all of JSON RPC 2.0)
|
||||
/// - `other_` (e.g. [`other_get_height`](RouterBuilder::other_get_height))
|
||||
/// - `bin_` (e.g. [`bin_get_blocks`](RouterBuilder::bin_get_blocks))
|
||||
///
|
||||
/// For a list of all `monerod` routes, see
|
||||
/// [here](https://github.com/monero-project/monero/blob/cc73fe71162d564ffda8e549b79a350bca53c454/src/rpc/core_rpc_server.h#L97-L189),
|
||||
/// or the source file of this type.
|
||||
///
|
||||
/// # Aliases
|
||||
/// Some routes have aliases, such as [`/get_height`](RouterBuilder::other_get_height)
|
||||
/// and [`/getheight`](RouterBuilder::other_getheight).
|
||||
///
|
||||
/// These both route to the same handler function, but they do not enable each other.
|
||||
///
|
||||
/// If desired, you can enable `/get_height` but not `/getheight`.
|
||||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use cuprate_rpc_interface::{RouterBuilder, RpcHandlerDummy};
|
||||
///
|
||||
/// // Create a router with _only_ `/json_rpc` enabled.
|
||||
/// let only_json_rpc = RouterBuilder::<RpcHandlerDummy>::new()
|
||||
/// .json_rpc()
|
||||
/// .build();
|
||||
///
|
||||
/// // Create a router with:
|
||||
/// // - `/get_outs.bin` enabled
|
||||
/// // - A fallback enabled
|
||||
/// let get_outs_bin_and_fallback = RouterBuilder::<RpcHandlerDummy>::new()
|
||||
/// .bin_get_outs()
|
||||
/// .fallback()
|
||||
/// .build();
|
||||
///
|
||||
/// // Create a router with all endpoints enabled.
|
||||
/// let all = RouterBuilder::<RpcHandlerDummy>::new()
|
||||
/// .all()
|
||||
/// .build();
|
||||
/// ```
|
||||
#[allow(clippy::struct_excessive_bools)]
|
||||
#[derive(Clone)]
|
||||
pub struct RouterBuilder<H: RpcHandler> {
|
||||
router: Router<H>,
|
||||
}
|
||||
|
||||
impl<H: RpcHandler> RouterBuilder<H> {
|
||||
/// Create a new [`Self`].
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
router: Router::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Build [`Self`] into a [`Router`].
|
||||
///
|
||||
/// All endpoints enabled in [`RouterBuilder`]
|
||||
/// will be enabled in this [`Router`].
|
||||
pub fn build(self) -> Router<H> {
|
||||
self.router
|
||||
}
|
||||
|
||||
/// Enable all endpoints, including [`Self::fallback`].
|
||||
#[must_use]
|
||||
pub fn all(mut self) -> Self {
|
||||
$(
|
||||
self = self.$endpoint_ident();
|
||||
)*
|
||||
|
||||
self.fallback()
|
||||
}
|
||||
|
||||
/// Enable the catch-all fallback route.
|
||||
///
|
||||
/// Any unknown or disabled route will route here, e.g.:
|
||||
/// - `get_info`
|
||||
/// - `getinfo`
|
||||
/// - `asdf`
|
||||
#[must_use]
|
||||
pub fn fallback(self) -> Self {
|
||||
Self {
|
||||
router: self.router.fallback(fallback::fallback),
|
||||
}
|
||||
}
|
||||
|
||||
$(
|
||||
#[doc = concat!(
|
||||
"Enable the `",
|
||||
$endpoint_string,
|
||||
"` endpoint.",
|
||||
)]
|
||||
#[must_use]
|
||||
pub fn $endpoint_ident(self) -> Self {
|
||||
Self {
|
||||
router: self.router.route(
|
||||
$endpoint_string,
|
||||
::axum::routing::method_routing::MethodRouter::new()
|
||||
$(.$http_method($endpoint_module::$endpoint_fn::<H>))*
|
||||
),
|
||||
}
|
||||
}
|
||||
)*
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate_router_builder! {
|
||||
// JSON-RPC 2.0 route.
|
||||
json_rpc => "/json_rpc" => json_rpc::json_rpc => (get, post),
|
||||
|
||||
// Other JSON routes.
|
||||
other_get_height => "/get_height" => other::get_height => (get, post),
|
||||
other_getheight => "/getheight" => other::get_height => (get, post),
|
||||
other_get_transactions => "/get_transactions" => other::get_transactions => (get, post),
|
||||
other_gettransactions => "/gettransactions" => other::get_transactions => (get, post),
|
||||
other_get_alt_blocks_hashes => "/get_alt_blocks_hashes" => other::get_alt_blocks_hashes => (get, post),
|
||||
other_is_key_image_spent => "/is_key_image_spent" => other::is_key_image_spent => (get, post),
|
||||
other_send_raw_transaction => "/send_raw_transaction" => other::send_raw_transaction => (get, post),
|
||||
other_sendrawtransaction => "/sendrawtransaction" => other::send_raw_transaction => (get, post),
|
||||
other_start_mining => "/start_mining" => other::start_mining => (get, post),
|
||||
other_stop_mining => "/stop_mining" => other::stop_mining => (get, post),
|
||||
other_mining_status => "/mining_status" => other::mining_status => (get, post),
|
||||
other_save_bc => "/save_bc" => other::save_bc => (get, post),
|
||||
other_get_peer_list => "/get_peer_list" => other::get_peer_list => (get, post),
|
||||
other_get_public_nodes => "/get_public_nodes" => other::get_public_nodes => (get, post),
|
||||
other_set_log_hash_rate => "/set_log_hash_rate" => other::set_log_hash_rate => (get, post),
|
||||
other_set_log_level => "/set_log_level" => other::set_log_level => (get, post),
|
||||
other_set_log_categories => "/set_log_categories" => other::set_log_categories => (get, post),
|
||||
other_get_transaction_pool => "/get_transaction_pool" => other::get_transaction_pool => (get, post),
|
||||
other_get_transaction_pool_hashes => "/get_transaction_pool_hashes" => other::get_transaction_pool_hashes => (get, post),
|
||||
other_get_transaction_pool_stats => "/get_transaction_pool_stats" => other::get_transaction_pool_stats => (get, post),
|
||||
other_set_bootstrap_daemon => "/set_bootstrap_daemon" => other::set_bootstrap_daemon => (get, post),
|
||||
other_stop_daemon => "/stop_daemon" => other::stop_daemon => (get, post),
|
||||
other_get_net_stats => "/get_net_stats" => other::get_net_stats => (get, post),
|
||||
other_get_limit => "/get_limit" => other::get_limit => (get, post),
|
||||
other_set_limit => "/set_limit" => other::set_limit => (get, post),
|
||||
other_out_peers => "/out_peers" => other::out_peers => (get, post),
|
||||
other_in_peers => "/in_peers" => other::in_peers => (get, post),
|
||||
other_get_outs => "/get_outs" => other::get_outs => (get, post),
|
||||
other_update => "/update" => other::update => (get, post),
|
||||
other_pop_blocks => "/pop_blocks" => other::pop_blocks => (get, post),
|
||||
|
||||
// Binary routes.
|
||||
bin_get_blocks => "/get_blocks.bin" => bin::get_blocks => (get, post),
|
||||
bin_getblocks => "/getblocks.bin" => bin::get_blocks => (get, post),
|
||||
bin_get_blocks_by_height => "/get_blocks_by_height.bin" => bin::get_blocks_by_height => (get, post),
|
||||
bin_getblocks_by_height => "/getblocks_by_height.bin" => bin::get_blocks_by_height => (get, post),
|
||||
bin_get_hashes => "/get_hashes.bin" => bin::get_hashes => (get, post),
|
||||
bin_gethashes => "/gethashes.bin" => bin::get_hashes => (get, post),
|
||||
bin_get_o_indexes => "/get_o_indexes.bin" => bin::get_o_indexes => (get, post),
|
||||
bin_get_outs => "/get_outs.bin" => bin::get_outs => (get, post),
|
||||
bin_get_transaction_pool_hashes => "/get_transaction_pool_hashes.bin" => bin::get_transaction_pool_hashes => (get, post),
|
||||
bin_get_output_distribution => "/get_output_distribution.bin" => bin::get_output_distribution => (get, post),
|
||||
}
|
||||
|
||||
impl<H: RpcHandler> Default for RouterBuilder<H> {
|
||||
/// Uses [`Self::all`].
|
||||
fn default() -> Self {
|
||||
Self::new().all()
|
||||
}
|
||||
}
|
34
rpc/interface/src/rpc_error.rs
Normal file
34
rpc/interface/src/rpc_error.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
//! RPC errors.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use axum::http::StatusCode;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcError
|
||||
/// Possible errors during RPC operation.
|
||||
///
|
||||
/// These are any errors that can happen _during_ a handler function.
|
||||
/// I.e. if this error surfaces, it happened _after_ the request was
|
||||
/// deserialized.
|
||||
///
|
||||
/// This is the `Error` type required to be used in an [`RpcHandler`](crate::RpcHandler).
|
||||
///
|
||||
/// TODO: This is empty as possible errors will be
|
||||
/// enumerated when the handler functions are created.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum RpcError {}
|
||||
|
||||
impl From<RpcError> for StatusCode {
|
||||
fn from(value: RpcError) -> Self {
|
||||
// TODO
|
||||
Self::INTERNAL_SERVER_ERROR
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
57
rpc/interface/src/rpc_handler.rs
Normal file
57
rpc/interface/src/rpc_handler.rs
Normal file
|
@ -0,0 +1,57 @@
|
|||
//! RPC handler trait.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use std::{future::Future, task::Poll};
|
||||
|
||||
use axum::{http::StatusCode, response::IntoResponse};
|
||||
use futures::{channel::oneshot::channel, FutureExt};
|
||||
use tower::Service;
|
||||
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_json_rpc::Id;
|
||||
use cuprate_rpc_types::json::JsonRpcRequest;
|
||||
|
||||
use crate::{rpc_error::RpcError, rpc_request::RpcRequest, rpc_response::RpcResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcHandler
|
||||
/// An RPC handler.
|
||||
///
|
||||
/// This trait represents a type that can turn [`RpcRequest`]s into [`RpcResponse`]s.
|
||||
///
|
||||
/// Implementors of this trait must be [`tower::Service`]s that use:
|
||||
/// - [`RpcRequest`] as the generic `Request` type
|
||||
/// - [`RpcResponse`] as the associated `Response` type
|
||||
/// - [`RpcError`] as the associated `Error` type
|
||||
/// - A generic [`Future`] that outputs `Result<RpcResponse, RpcError>`
|
||||
///
|
||||
/// See this crate's `RpcHandlerDummy` for an implementation example of this trait.
|
||||
///
|
||||
/// # Panics
|
||||
/// Your [`RpcHandler`] must reply to [`RpcRequest`]s with the correct
|
||||
/// [`RpcResponse`] or else this crate will panic during routing functions.
|
||||
///
|
||||
/// For example, upon a [`RpcRequest::Binary`] must be replied with
|
||||
/// [`RpcRequest::Binary`]. If an [`RpcRequest::Other`] were returned instead,
|
||||
/// this crate would panic.
|
||||
pub trait RpcHandler:
|
||||
Clone
|
||||
+ Send
|
||||
+ Sync
|
||||
+ 'static
|
||||
+ Service<
|
||||
RpcRequest,
|
||||
Response = RpcResponse,
|
||||
Error = RpcError,
|
||||
Future: Future<Output = Result<RpcResponse, RpcError>> + Send + Sync + 'static,
|
||||
>
|
||||
{
|
||||
/// Is this [`RpcHandler`] restricted?
|
||||
///
|
||||
/// If this returns `true`, restricted methods and endpoints such as:
|
||||
/// - `/json_rpc`'s `relay_tx` method
|
||||
/// - The `/pop_blocks` endpoint
|
||||
///
|
||||
/// will automatically be denied access when using the
|
||||
/// [`axum::Router`] provided by [`RouterBuilder`](crate::RouterBuilder).
|
||||
fn restricted(&self) -> bool;
|
||||
}
|
142
rpc/interface/src/rpc_handler_dummy.rs
Normal file
142
rpc/interface/src/rpc_handler_dummy.rs
Normal file
|
@ -0,0 +1,142 @@
|
|||
//! Dummy implementation of [`RpcHandler`].
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use std::task::Poll;
|
||||
|
||||
use futures::{channel::oneshot::channel, FutureExt};
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower::Service;
|
||||
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_json_rpc::Id;
|
||||
use cuprate_rpc_types::json::JsonRpcRequest;
|
||||
|
||||
use crate::{
|
||||
rpc_error::RpcError, rpc_handler::RpcHandler, rpc_request::RpcRequest,
|
||||
rpc_response::RpcResponse,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcHandlerDummy
|
||||
/// An [`RpcHandler`] that always returns [`Default::default`].
|
||||
///
|
||||
/// This `struct` implements [`RpcHandler`], and always responds
|
||||
/// with the response `struct` set to [`Default::default`].
|
||||
///
|
||||
/// See the [`crate`] documentation for example usage.
|
||||
///
|
||||
/// This is mostly used for testing purposes and can
|
||||
/// be disabled by disable the `dummy` feature flag.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub struct RpcHandlerDummy {
|
||||
/// Should this RPC server be [restricted](RpcHandler::restricted)?
|
||||
///
|
||||
/// The dummy will honor this [`bool`]
|
||||
/// on restricted methods/endpoints.
|
||||
pub restricted: bool,
|
||||
}
|
||||
|
||||
impl RpcHandler for RpcHandlerDummy {
|
||||
fn restricted(&self) -> bool {
|
||||
self.restricted
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<RpcRequest> for RpcHandlerDummy {
|
||||
type Response = RpcResponse;
|
||||
type Error = RpcError;
|
||||
type Future = InfallibleOneshotReceiver<Result<RpcResponse, RpcError>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut std::task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: RpcRequest) -> Self::Future {
|
||||
use cuprate_rpc_types::bin::BinRequest as BReq;
|
||||
use cuprate_rpc_types::bin::BinResponse as BResp;
|
||||
use cuprate_rpc_types::json::JsonRpcRequest as JReq;
|
||||
use cuprate_rpc_types::json::JsonRpcResponse as JResp;
|
||||
use cuprate_rpc_types::other::OtherRequest as OReq;
|
||||
use cuprate_rpc_types::other::OtherResponse as OResp;
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[allow(clippy::default_trait_access)]
|
||||
let resp = match req {
|
||||
RpcRequest::JsonRpc(j) => RpcResponse::JsonRpc(cuprate_json_rpc::Response::ok(Id::Null, match j.body {
|
||||
JReq::GetBlockCount(_) => JResp::GetBlockCount(Default::default()),
|
||||
JReq::OnGetBlockHash(_) => JResp::OnGetBlockHash(Default::default()),
|
||||
JReq::SubmitBlock(_) => JResp::SubmitBlock(Default::default()),
|
||||
JReq::GenerateBlocks(_) => JResp::GenerateBlocks(Default::default()),
|
||||
JReq::GetLastBlockHeader(_) => JResp::GetLastBlockHeader(Default::default()),
|
||||
JReq::GetBlockHeaderByHash(_) => JResp::GetBlockHeaderByHash(Default::default()),
|
||||
JReq::GetBlockHeaderByHeight(_) => JResp::GetBlockHeaderByHeight(Default::default()),
|
||||
JReq::GetBlockHeadersRange(_) => JResp::GetBlockHeadersRange(Default::default()),
|
||||
JReq::GetBlock(_) => JResp::GetBlock(Default::default()),
|
||||
JReq::GetConnections(_) => JResp::GetConnections(Default::default()),
|
||||
JReq::GetInfo(_) => JResp::GetInfo(Default::default()),
|
||||
JReq::HardForkInfo(_) => JResp::HardForkInfo(Default::default()),
|
||||
JReq::SetBans(_) => JResp::SetBans(Default::default()),
|
||||
JReq::GetBans(_) => JResp::GetBans(Default::default()),
|
||||
JReq::Banned(_) => JResp::Banned(Default::default()),
|
||||
JReq::FlushTransactionPool(_) => JResp::FlushTransactionPool(Default::default()),
|
||||
JReq::GetOutputHistogram(_) => JResp::GetOutputHistogram(Default::default()),
|
||||
JReq::GetCoinbaseTxSum(_) => JResp::GetCoinbaseTxSum(Default::default()),
|
||||
JReq::GetVersion(_) => JResp::GetVersion(Default::default()),
|
||||
JReq::GetFeeEstimate(_) => JResp::GetFeeEstimate(Default::default()),
|
||||
JReq::GetAlternateChains(_) => JResp::GetAlternateChains(Default::default()),
|
||||
JReq::RelayTx(_) => JResp::RelayTx(Default::default()),
|
||||
JReq::SyncInfo(_) => JResp::SyncInfo(Default::default()),
|
||||
JReq::GetTransactionPoolBacklog(_) => JResp::GetTransactionPoolBacklog(Default::default()),
|
||||
JReq::GetMinerData(_) => JResp::GetMinerData(Default::default()),
|
||||
JReq::PruneBlockchain(_) => JResp::PruneBlockchain(Default::default()),
|
||||
JReq::CalcPow(_) => JResp::CalcPow(Default::default()),
|
||||
JReq::FlushCache(_) => JResp::FlushCache(Default::default()),
|
||||
JReq::AddAuxPow(_) => JResp::AddAuxPow(Default::default()),
|
||||
JReq::GetTxIdsLoose(_) => JResp::GetTxIdsLoose(Default::default()),
|
||||
})),
|
||||
RpcRequest::Binary(b) => RpcResponse::Binary(match b {
|
||||
BReq::GetBlocks(_) => BResp::GetBlocks(Default::default()),
|
||||
BReq::GetBlocksByHeight(_) => BResp::GetBlocksByHeight(Default::default()),
|
||||
BReq::GetHashes(_) => BResp::GetHashes(Default::default()),
|
||||
BReq::GetOutputIndexes(_) => BResp::GetOutputIndexes(Default::default()),
|
||||
BReq::GetOuts(_) => BResp::GetOuts(Default::default()),
|
||||
BReq::GetTransactionPoolHashes(_) => BResp::GetTransactionPoolHashes(Default::default()),
|
||||
BReq::GetOutputDistribution(_) => BResp::GetOutputDistribution(Default::default()),
|
||||
}),
|
||||
RpcRequest::Other(o) => RpcResponse::Other(match o {
|
||||
OReq::GetHeight(_) => OResp::GetHeight(Default::default()),
|
||||
OReq::GetTransactions(_) => OResp::GetTransactions(Default::default()),
|
||||
OReq::GetAltBlocksHashes(_) => OResp::GetAltBlocksHashes(Default::default()),
|
||||
OReq::IsKeyImageSpent(_) => OResp::IsKeyImageSpent(Default::default()),
|
||||
OReq::SendRawTransaction(_) => OResp::SendRawTransaction(Default::default()),
|
||||
OReq::StartMining(_) => OResp::StartMining(Default::default()),
|
||||
OReq::StopMining(_) => OResp::StopMining(Default::default()),
|
||||
OReq::MiningStatus(_) => OResp::MiningStatus(Default::default()),
|
||||
OReq::SaveBc(_) => OResp::SaveBc(Default::default()),
|
||||
OReq::GetPeerList(_) => OResp::GetPeerList(Default::default()),
|
||||
OReq::SetLogHashRate(_) => OResp::SetLogHashRate(Default::default()),
|
||||
OReq::SetLogLevel(_) => OResp::SetLogLevel(Default::default()),
|
||||
OReq::SetLogCategories(_) => OResp::SetLogCategories(Default::default()),
|
||||
OReq::SetBootstrapDaemon(_) => OResp::SetBootstrapDaemon(Default::default()),
|
||||
OReq::GetTransactionPool(_) => OResp::GetTransactionPool(Default::default()),
|
||||
OReq::GetTransactionPoolStats(_) => OResp::GetTransactionPoolStats(Default::default()),
|
||||
OReq::StopDaemon(_) => OResp::StopDaemon(Default::default()),
|
||||
OReq::GetLimit(_) => OResp::GetLimit(Default::default()),
|
||||
OReq::SetLimit(_) => OResp::SetLimit(Default::default()),
|
||||
OReq::OutPeers(_) => OResp::OutPeers(Default::default()),
|
||||
OReq::InPeers(_) => OResp::InPeers(Default::default()),
|
||||
OReq::GetNetStats(_) => OResp::GetNetStats(Default::default()),
|
||||
OReq::GetOuts(_) => OResp::GetOuts(Default::default()),
|
||||
OReq::Update(_) => OResp::Update(Default::default()),
|
||||
OReq::PopBlocks(_) => OResp::PopBlocks(Default::default()),
|
||||
OReq::GetTransactionPoolHashes(_) => OResp::GetTransactionPoolHashes(Default::default()),
|
||||
OReq::GetPublicNodes(_) => OResp::GetPublicNodes(Default::default()),
|
||||
})
|
||||
};
|
||||
|
||||
let (tx, rx) = channel();
|
||||
drop(tx.send(Ok(resp)));
|
||||
InfallibleOneshotReceiver::from(rx)
|
||||
}
|
||||
}
|
33
rpc/interface/src/rpc_request.rs
Normal file
33
rpc/interface/src/rpc_request.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
//! RPC requests.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cuprate_rpc_types::{bin::BinRequest, json::JsonRpcRequest, other::OtherRequest};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcRequest
|
||||
/// All possible RPC requests.
|
||||
///
|
||||
/// This enum encapsulates all possible RPC requests:
|
||||
/// - JSON RPC 2.0 requests
|
||||
/// - Binary requests
|
||||
/// - Other JSON requests
|
||||
///
|
||||
/// It is the `Request` type required to be used in an [`RpcHandler`](crate::RpcHandler).
|
||||
#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum RpcRequest {
|
||||
/// JSON-RPC 2.0 requests.
|
||||
JsonRpc(cuprate_json_rpc::Request<JsonRpcRequest>),
|
||||
/// Binary requests.
|
||||
Binary(BinRequest),
|
||||
/// Other JSON requests.
|
||||
Other(OtherRequest),
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
33
rpc/interface/src/rpc_response.rs
Normal file
33
rpc/interface/src/rpc_response.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
//! RPC responses.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cuprate_rpc_types::{bin::BinResponse, json::JsonRpcResponse, other::OtherResponse};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- RpcResponse
|
||||
/// All possible RPC responses.
|
||||
///
|
||||
/// This enum encapsulates all possible RPC responses:
|
||||
/// - JSON RPC 2.0 responses
|
||||
/// - Binary responses
|
||||
/// - Other JSON responses
|
||||
///
|
||||
/// It is the `Response` type required to be used in an [`RpcHandler`](crate::RpcHandler).
|
||||
#[derive(Clone, PartialEq, Eq)]
|
||||
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
|
||||
pub enum RpcResponse {
|
||||
/// JSON RPC 2.0 responses.
|
||||
JsonRpc(cuprate_json_rpc::Response<JsonRpcResponse>),
|
||||
/// Binary responses.
|
||||
Binary(BinResponse),
|
||||
/// Other JSON responses.
|
||||
Other(OtherResponse),
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
// use super::*;
|
||||
}
|
|
@ -15,15 +15,16 @@ default = ["heed", "service"]
|
|||
heed = ["cuprate-database/heed"]
|
||||
redb = ["cuprate-database/redb"]
|
||||
redb-memory = ["cuprate-database/redb-memory"]
|
||||
service = ["dep:crossbeam", "dep:futures", "dep:tokio", "dep:tokio-util", "dep:tower", "dep:rayon"]
|
||||
service = ["dep:thread_local", "dep:rayon"]
|
||||
|
||||
[dependencies]
|
||||
# FIXME:
|
||||
# We only need the `thread` feature if `service` is enabled.
|
||||
# Figure out how to enable features of an already pulled in dependency conditionally.
|
||||
cuprate-database = { path = "../database" }
|
||||
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] }
|
||||
cuprate-types = { path = "../../types", features = ["blockchain"] }
|
||||
cuprate-database = { path = "../database" }
|
||||
cuprate-database-service = { path = "../service" }
|
||||
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] }
|
||||
cuprate-types = { path = "../../types", features = ["blockchain"] }
|
||||
|
||||
bitflags = { workspace = true, features = ["serde", "bytemuck"] }
|
||||
bytemuck = { version = "1.14.3", features = ["must_cast", "derive", "min_const_generics", "extern_crate_alloc"] }
|
||||
|
@ -33,19 +34,16 @@ monero-serai = { workspace = true, features = ["std"] }
|
|||
serde = { workspace = true, optional = true }
|
||||
|
||||
# `service` feature.
|
||||
crossbeam = { workspace = true, features = ["std"], optional = true }
|
||||
futures = { workspace = true, optional = true }
|
||||
tokio = { workspace = true, features = ["full"], optional = true }
|
||||
tokio-util = { workspace = true, features = ["full"], optional = true }
|
||||
tower = { workspace = true, features = ["full"], optional = true }
|
||||
thread_local = { workspace = true }
|
||||
tower = { workspace = true }
|
||||
thread_local = { workspace = true, optional = true }
|
||||
rayon = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
cuprate-helper = { path = "../../helper", features = ["thread"] }
|
||||
cuprate-test-utils = { path = "../../test-utils" }
|
||||
|
||||
tempfile = { version = "3.10.0" }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tempfile = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
proptest = { workspace = true }
|
||||
hex = { workspace = true }
|
||||
|
|
|
@ -1,31 +0,0 @@
|
|||
//! SOMEDAY
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
num::NonZeroUsize,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use cuprate_helper::fs::cuprate_blockchain_dir;
|
||||
|
||||
use crate::{
|
||||
config::{ReaderThreads, SyncMode},
|
||||
constants::DATABASE_DATA_FILENAME,
|
||||
resize::ResizeAlgorithm,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Backend
|
||||
/// SOMEDAY: allow runtime hot-swappable backends.
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum Backend {
|
||||
#[default]
|
||||
/// SOMEDAY
|
||||
Heed,
|
||||
/// SOMEDAY
|
||||
Redb,
|
||||
}
|
|
@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize};
|
|||
use cuprate_database::{config::SyncMode, resize::ResizeAlgorithm};
|
||||
use cuprate_helper::fs::cuprate_blockchain_dir;
|
||||
|
||||
use crate::config::ReaderThreads;
|
||||
// re-exports
|
||||
pub use cuprate_database_service::ReaderThreads;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- ConfigBuilder
|
||||
/// Builder for [`Config`].
|
||||
|
|
|
@ -34,14 +34,11 @@
|
|||
//! .build();
|
||||
//!
|
||||
//! // Start a database `service` using this configuration.
|
||||
//! let (reader_handle, _) = cuprate_blockchain::service::init(config.clone())?;
|
||||
//! let (_, _, env) = cuprate_blockchain::service::init(config.clone())?;
|
||||
//! // It's using the config we provided.
|
||||
//! assert_eq!(reader_handle.env().config(), &config.db_config);
|
||||
//! assert_eq!(env.config(), &config.db_config);
|
||||
//! # Ok(()) }
|
||||
//! ```
|
||||
|
||||
mod config;
|
||||
pub use config::{Config, ConfigBuilder};
|
||||
|
||||
mod reader_threads;
|
||||
pub use reader_threads::ReaderThreads;
|
||||
pub use config::{Config, ConfigBuilder, ReaderThreads};
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
//! Database [`Env`](crate::Env) configuration.
|
||||
//!
|
||||
//! This module contains the main [`Config`]uration struct
|
||||
//! for the database [`Env`](crate::Env)ironment, and data
|
||||
//! structures related to any configuration setting.
|
||||
//!
|
||||
//! These configurations are processed at runtime, meaning
|
||||
//! the `Env` can/will dynamically adjust its behavior
|
||||
//! based on these values.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- SyncMode
|
||||
/// Disk synchronization mode.
|
||||
///
|
||||
/// This controls how/when the database syncs its data to disk.
|
||||
///
|
||||
/// Regardless of the variant chosen, dropping [`Env`](crate::Env)
|
||||
/// will always cause it to fully sync to disk.
|
||||
///
|
||||
/// # Sync vs Async
|
||||
/// All invariants except [`SyncMode::Async`] & [`SyncMode::Fast`]
|
||||
/// are `synchronous`, as in the database will wait until the OS has
|
||||
/// finished syncing all the data to disk before continuing.
|
||||
///
|
||||
/// `SyncMode::Async` & `SyncMode::Fast` are `asynchronous`, meaning
|
||||
/// the database will _NOT_ wait until the data is fully synced to disk
|
||||
/// before continuing. Note that this doesn't mean the database itself
|
||||
/// won't be synchronized between readers/writers, but rather that the
|
||||
/// data _on disk_ may not be immediately synchronized after a write.
|
||||
///
|
||||
/// Something like:
|
||||
/// ```rust,ignore
|
||||
/// db.put("key", value);
|
||||
/// db.get("key");
|
||||
/// ```
|
||||
/// will be fine, most likely pulling from memory instead of disk.
|
||||
///
|
||||
/// # SOMEDAY
|
||||
/// Dynamic sync's are not yet supported.
|
||||
///
|
||||
/// Only:
|
||||
///
|
||||
/// - [`SyncMode::Safe`]
|
||||
/// - [`SyncMode::Async`]
|
||||
/// - [`SyncMode::Fast`]
|
||||
///
|
||||
/// are supported, all other variants will panic on [`crate::Env::open`].
|
||||
#[derive(Copy, Clone, Debug, Default, PartialEq, PartialOrd, Eq, Ord, Hash)]
|
||||
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||
pub enum SyncMode {
|
||||
/// Use [`SyncMode::Fast`] until fully synced,
|
||||
/// then use [`SyncMode::Safe`].
|
||||
///
|
||||
// # SOMEDAY: how to implement this?
|
||||
// ref: <https://github.com/monero-project/monero/issues/1463>
|
||||
// monerod-solution: <https://github.com/monero-project/monero/pull/1506>
|
||||
// cuprate-issue: <https://github.com/Cuprate/cuprate/issues/78>
|
||||
//
|
||||
// We could:
|
||||
// ```rust,ignore
|
||||
// if current_db_block <= top_block.saturating_sub(N) {
|
||||
// // don't sync()
|
||||
// } else {
|
||||
// // sync()
|
||||
// }
|
||||
// ```
|
||||
// where N is some threshold we pick that is _close_ enough
|
||||
// to being synced where we want to start being safer.
|
||||
//
|
||||
// Essentially, when we are in a certain % range of being finished,
|
||||
// switch to safe mode, until then, go fast.
|
||||
FastThenSafe,
|
||||
|
||||
#[default]
|
||||
/// Fully sync to disk per transaction.
|
||||
///
|
||||
/// Every database transaction commit will
|
||||
/// fully sync all data to disk, _synchronously_,
|
||||
/// so the database (writer) halts until synced.
|
||||
///
|
||||
/// This is expected to be very slow.
|
||||
///
|
||||
/// This matches:
|
||||
/// - LMDB without any special sync flags
|
||||
/// - [`redb::Durability::Immediate`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Immediate)
|
||||
Safe,
|
||||
|
||||
/// Asynchrously sync to disk per transaction.
|
||||
///
|
||||
/// This is the same as [`SyncMode::Safe`],
|
||||
/// but the syncs will be asynchronous, i.e.
|
||||
/// each transaction commit will sync to disk,
|
||||
/// but only eventually, not necessarily immediately.
|
||||
///
|
||||
/// This matches:
|
||||
/// - [`MDB_MAPASYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#gab034ed0d8e5938090aef5ee0997f7e94)
|
||||
/// - [`redb::Durability::Eventual`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.Eventual)
|
||||
Async,
|
||||
|
||||
/// Fully sync to disk after we cross this transaction threshold.
|
||||
///
|
||||
/// After committing [`usize`] amount of database
|
||||
/// transactions, it will be sync to disk.
|
||||
///
|
||||
/// `0` behaves the same as [`SyncMode::Safe`], and a ridiculously large
|
||||
/// number like `usize::MAX` is practically the same as [`SyncMode::Fast`].
|
||||
Threshold(usize),
|
||||
|
||||
/// Only flush at database shutdown.
|
||||
///
|
||||
/// This is the fastest, yet unsafest option.
|
||||
///
|
||||
/// It will cause the database to never _actively_ sync,
|
||||
/// letting the OS decide when to flush data to disk.
|
||||
///
|
||||
/// This matches:
|
||||
/// - [`MDB_NOSYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#ga5791dd1adb09123f82dd1f331209e12e) + [`MDB_MAPASYNC`](http://www.lmdb.tech/doc/group__mdb__env.html#gab034ed0d8e5938090aef5ee0997f7e94)
|
||||
/// - [`redb::Durability::None`](https://docs.rs/redb/1.5.0/redb/enum.Durability.html#variant.None)
|
||||
///
|
||||
/// `monerod` reference: <https://github.com/monero-project/monero/blob/7b7958bbd9d76375c47dc418b4adabba0f0b1785/src/blockchain_db/lmdb/db_lmdb.cpp#L1380-L1381>
|
||||
///
|
||||
/// # Corruption
|
||||
/// In the case of a system crash, the database
|
||||
/// may become corrupted when using this option.
|
||||
//
|
||||
// FIXME: we could call this `unsafe`
|
||||
// and use that terminology in the config file
|
||||
// so users know exactly what they are getting
|
||||
// themselves into.
|
||||
Fast,
|
||||
}
|
|
@ -65,17 +65,17 @@ pub fn add_block(
|
|||
#[cfg(debug_assertions)]
|
||||
{
|
||||
assert_eq!(block.block.serialize(), block.block_blob);
|
||||
assert_eq!(block.block.txs.len(), block.txs.len());
|
||||
assert_eq!(block.block.transactions.len(), block.txs.len());
|
||||
for (i, tx) in block.txs.iter().enumerate() {
|
||||
assert_eq!(tx.tx_blob, tx.tx.serialize());
|
||||
assert_eq!(tx.tx_hash, block.block.txs[i]);
|
||||
assert_eq!(tx.tx_hash, block.block.transactions[i]);
|
||||
}
|
||||
}
|
||||
|
||||
//------------------------------------------------------ Transaction / Outputs / Key Images
|
||||
// Add the miner transaction first.
|
||||
{
|
||||
let tx = &block.block.miner_tx;
|
||||
let tx = &block.block.miner_transaction;
|
||||
add_tx(tx, &tx.serialize(), &tx.hash(), &chain_height, tables)?;
|
||||
}
|
||||
|
||||
|
@ -154,8 +154,8 @@ pub fn pop_block(
|
|||
let block = Block::read(&mut block_blob.as_slice())?;
|
||||
|
||||
//------------------------------------------------------ Transaction / Outputs / Key Images
|
||||
remove_tx(&block.miner_tx.hash(), tables)?;
|
||||
for tx_hash in &block.txs {
|
||||
remove_tx(&block.miner_transaction.hash(), tables)?;
|
||||
for tx_hash in &block.transactions {
|
||||
remove_tx(tx_hash, tables)?;
|
||||
}
|
||||
|
||||
|
@ -200,8 +200,8 @@ pub fn get_block_extended_header_from_height(
|
|||
#[allow(clippy::cast_possible_truncation)]
|
||||
Ok(ExtendedBlockHeader {
|
||||
cumulative_difficulty,
|
||||
version: block.header.major_version,
|
||||
vote: block.header.minor_version,
|
||||
version: block.header.hardfork_version,
|
||||
vote: block.header.hardfork_signal,
|
||||
timestamp: block.header.timestamp,
|
||||
block_weight: block_info.weight as usize,
|
||||
long_term_weight: block_info.long_term_weight as usize,
|
||||
|
@ -297,7 +297,7 @@ mod test {
|
|||
// HACK: `add_block()` asserts blocks with non-sequential heights
|
||||
// cannot be added, to get around this, manually edit the block height.
|
||||
for (height, block) in blocks.iter_mut().enumerate() {
|
||||
block.height = height as u64;
|
||||
block.height = height;
|
||||
assert_eq!(block.block.serialize(), block.block_blob);
|
||||
}
|
||||
let generated_coins_sum = blocks
|
||||
|
@ -369,8 +369,8 @@ mod test {
|
|||
let b1 = block_header_from_hash;
|
||||
let b2 = block;
|
||||
assert_eq!(b1, block_header_from_height);
|
||||
assert_eq!(b1.version, b2.block.header.major_version);
|
||||
assert_eq!(b1.vote, b2.block.header.minor_version);
|
||||
assert_eq!(b1.version, b2.block.header.hardfork_version);
|
||||
assert_eq!(b1.vote, b2.block.header.hardfork_signal);
|
||||
assert_eq!(b1.timestamp, b2.block.header.timestamp);
|
||||
assert_eq!(b1.cumulative_difficulty, b2.cumulative_difficulty);
|
||||
assert_eq!(b1.block_weight, b2.weight);
|
||||
|
@ -388,7 +388,7 @@ mod test {
|
|||
|
||||
assert_eq!(tx.tx_blob, tx2.serialize());
|
||||
assert_eq!(tx.tx_weight, tx2.weight());
|
||||
assert_eq!(tx.tx_hash, block.block.txs[i]);
|
||||
assert_eq!(tx.tx_hash, block.block.transactions[i]);
|
||||
assert_eq!(tx.tx_hash, tx2.hash());
|
||||
}
|
||||
}
|
||||
|
@ -440,7 +440,7 @@ mod test {
|
|||
|
||||
let mut block = block_v9_tx3().clone();
|
||||
|
||||
block.height = u64::from(u32::MAX) + 1;
|
||||
block.height = usize::try_from(u32::MAX).unwrap() + 1;
|
||||
add_block(&block, &mut tables).unwrap();
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,8 @@ use crate::{
|
|||
pub fn chain_height(
|
||||
table_block_heights: &impl DatabaseRo<BlockHeights>,
|
||||
) -> Result<BlockHeight, RuntimeError> {
|
||||
table_block_heights.len()
|
||||
#[allow(clippy::cast_possible_truncation)] // we enforce 64-bit
|
||||
table_block_heights.len().map(|height| height as usize)
|
||||
}
|
||||
|
||||
/// Retrieve the height of the top block.
|
||||
|
@ -47,7 +48,8 @@ pub fn top_block_height(
|
|||
) -> Result<BlockHeight, RuntimeError> {
|
||||
match table_block_heights.len()? {
|
||||
0 => Err(RuntimeError::KeyNotFound),
|
||||
height => Ok(height - 1),
|
||||
#[allow(clippy::cast_possible_truncation)] // we enforce 64-bit
|
||||
height => Ok(height as usize - 1),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -110,7 +112,7 @@ mod test {
|
|||
block_v9_tx3().clone(),
|
||||
block_v16_tx0().clone(),
|
||||
];
|
||||
let blocks_len = u64::try_from(blocks.len()).unwrap();
|
||||
let blocks_len = blocks.len();
|
||||
|
||||
// Add blocks.
|
||||
{
|
||||
|
@ -127,7 +129,6 @@ mod test {
|
|||
);
|
||||
|
||||
for (i, block) in blocks.iter_mut().enumerate() {
|
||||
let i = u64::try_from(i).unwrap();
|
||||
// HACK: `add_block()` asserts blocks with non-sequential heights
|
||||
// cannot be added, to get around this, manually edit the block height.
|
||||
block.height = i;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use curve25519_dalek::{constants::ED25519_BASEPOINT_POINT, edwards::CompressedEdwardsY, Scalar};
|
||||
use monero_serai::{transaction::Timelock, H};
|
||||
use monero_serai::{generators::H, transaction::Timelock};
|
||||
|
||||
use cuprate_database::{
|
||||
RuntimeError, {DatabaseRo, DatabaseRw},
|
||||
|
@ -157,7 +157,7 @@ pub fn output_to_output_on_chain(
|
|||
) -> Result<OutputOnChain, RuntimeError> {
|
||||
// FIXME: implement lookup table for common values:
|
||||
// <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
|
||||
let commitment = ED25519_BASEPOINT_POINT + H() * Scalar::from(amount);
|
||||
let commitment = ED25519_BASEPOINT_POINT + *H * Scalar::from(amount);
|
||||
|
||||
let time_lock = if output
|
||||
.output_flags
|
||||
|
@ -173,7 +173,7 @@ pub fn output_to_output_on_chain(
|
|||
.unwrap_or(None);
|
||||
|
||||
Ok(OutputOnChain {
|
||||
height: u64::from(output.height),
|
||||
height: output.height as usize,
|
||||
time_lock,
|
||||
key,
|
||||
commitment,
|
||||
|
@ -213,7 +213,7 @@ pub fn rct_output_to_output_on_chain(
|
|||
.unwrap_or(None);
|
||||
|
||||
Ok(OutputOnChain {
|
||||
height: u64::from(rct_output.height),
|
||||
height: rct_output.height as usize,
|
||||
time_lock,
|
||||
key,
|
||||
commitment,
|
||||
|
|
|
@ -68,7 +68,7 @@ pub fn add_tx(
|
|||
// so the `u64/usize` is stored without any tag.
|
||||
//
|
||||
// <https://github.com/Cuprate/cuprate/pull/102#discussion_r1558504285>
|
||||
match tx.prefix.timelock {
|
||||
match tx.prefix().additional_timelock {
|
||||
Timelock::None => (),
|
||||
Timelock::Block(height) => tables.tx_unlock_time_mut().put(&tx_id, &(height as u64))?,
|
||||
Timelock::Time(time) => tables.tx_unlock_time_mut().put(&tx_id, &time)?,
|
||||
|
@ -92,7 +92,7 @@ pub fn add_tx(
|
|||
let mut miner_tx = false;
|
||||
|
||||
// Key images.
|
||||
for inputs in &tx.prefix.inputs {
|
||||
for inputs in &tx.prefix().inputs {
|
||||
match inputs {
|
||||
// Key images.
|
||||
Input::ToKey { key_image, .. } => {
|
||||
|
@ -106,70 +106,64 @@ pub fn add_tx(
|
|||
//------------------------------------------------------ Outputs
|
||||
// Output bit flags.
|
||||
// Set to a non-zero bit value if the unlock time is non-zero.
|
||||
let output_flags = match tx.prefix.timelock {
|
||||
let output_flags = match tx.prefix().additional_timelock {
|
||||
Timelock::None => OutputFlags::empty(),
|
||||
Timelock::Block(_) | Timelock::Time(_) => OutputFlags::NON_ZERO_UNLOCK_TIME,
|
||||
};
|
||||
|
||||
let mut amount_indices = Vec::with_capacity(tx.prefix.outputs.len());
|
||||
|
||||
for (i, output) in tx.prefix.outputs.iter().enumerate() {
|
||||
let key = *output.key.as_bytes();
|
||||
|
||||
// Outputs with clear amounts.
|
||||
let amount_index = if let Some(amount) = output.amount {
|
||||
// RingCT (v2 transaction) miner outputs.
|
||||
if miner_tx && tx.prefix.version == 2 {
|
||||
// Create commitment.
|
||||
// <https://github.com/Cuprate/cuprate/pull/102#discussion_r1559489302>
|
||||
// FIXME: implement lookup table for common values:
|
||||
// <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
|
||||
let commitment = (ED25519_BASEPOINT_POINT
|
||||
+ monero_serai::H() * Scalar::from(amount))
|
||||
.compress()
|
||||
.to_bytes();
|
||||
|
||||
add_rct_output(
|
||||
&RctOutput {
|
||||
key,
|
||||
height,
|
||||
output_flags,
|
||||
tx_idx: tx_id,
|
||||
commitment,
|
||||
},
|
||||
tables.rct_outputs_mut(),
|
||||
)?
|
||||
// Pre-RingCT outputs.
|
||||
} else {
|
||||
add_output(
|
||||
amount,
|
||||
let amount_indices = match &tx {
|
||||
Transaction::V1 { prefix, .. } => prefix
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output| {
|
||||
// Pre-RingCT outputs.
|
||||
Ok(add_output(
|
||||
output.amount.unwrap_or(0),
|
||||
&Output {
|
||||
key,
|
||||
key: output.key.0,
|
||||
height,
|
||||
output_flags,
|
||||
tx_idx: tx_id,
|
||||
},
|
||||
tables,
|
||||
)?
|
||||
.amount_index
|
||||
}
|
||||
// RingCT outputs.
|
||||
} else {
|
||||
let commitment = tx.rct_signatures.base.commitments[i].compress().to_bytes();
|
||||
add_rct_output(
|
||||
&RctOutput {
|
||||
key,
|
||||
height,
|
||||
output_flags,
|
||||
tx_idx: tx_id,
|
||||
commitment,
|
||||
},
|
||||
tables.rct_outputs_mut(),
|
||||
)?
|
||||
};
|
||||
.amount_index)
|
||||
})
|
||||
.collect::<Result<Vec<_>, RuntimeError>>()?,
|
||||
Transaction::V2 { prefix, proofs } => prefix
|
||||
.outputs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(i, output)| {
|
||||
// Create commitment.
|
||||
// <https://github.com/Cuprate/cuprate/pull/102#discussion_r1559489302>
|
||||
// FIXME: implement lookup table for common values:
|
||||
// <https://github.com/monero-project/monero/blob/c8214782fb2a769c57382a999eaf099691c836e7/src/ringct/rctOps.cpp#L322>
|
||||
let commitment = if miner_tx {
|
||||
ED25519_BASEPOINT_POINT
|
||||
+ *monero_serai::generators::H * Scalar::from(output.amount.unwrap_or(0))
|
||||
} else {
|
||||
proofs
|
||||
.as_ref()
|
||||
.expect("A V2 transaction with no RCT proofs is a miner tx")
|
||||
.base
|
||||
.commitments[i]
|
||||
};
|
||||
|
||||
amount_indices.push(amount_index);
|
||||
} // for each output
|
||||
// Add the RCT output.
|
||||
add_rct_output(
|
||||
&RctOutput {
|
||||
key: output.key.0,
|
||||
height,
|
||||
output_flags,
|
||||
tx_idx: tx_id,
|
||||
commitment: commitment.compress().0,
|
||||
},
|
||||
tables.rct_outputs_mut(),
|
||||
)
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
};
|
||||
|
||||
tables
|
||||
.tx_outputs_mut()
|
||||
|
@ -227,7 +221,7 @@ pub fn remove_tx(
|
|||
//------------------------------------------------------ Key Images
|
||||
// Is this a miner transaction?
|
||||
let mut miner_tx = false;
|
||||
for inputs in &tx.prefix.inputs {
|
||||
for inputs in &tx.prefix().inputs {
|
||||
match inputs {
|
||||
// Key images.
|
||||
Input::ToKey { key_image, .. } => {
|
||||
|
@ -240,11 +234,11 @@ pub fn remove_tx(
|
|||
|
||||
//------------------------------------------------------ Outputs
|
||||
// Remove each output in the transaction.
|
||||
for output in &tx.prefix.outputs {
|
||||
for output in &tx.prefix().outputs {
|
||||
// Outputs with clear amounts.
|
||||
if let Some(amount) = output.amount {
|
||||
// RingCT miner outputs.
|
||||
if miner_tx && tx.prefix.version == 2 {
|
||||
if miner_tx && tx.version() == 2 {
|
||||
let amount_index = get_rct_num_outputs(tables.rct_outputs())? - 1;
|
||||
remove_rct_output(&amount_index, tables.rct_outputs_mut())?;
|
||||
// Pre-RingCT outputs.
|
||||
|
|
|
@ -3,11 +3,12 @@
|
|||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::sync::Arc;
|
||||
|
||||
use cuprate_database::InitError;
|
||||
use cuprate_database::{ConcreteEnv, InitError};
|
||||
|
||||
use crate::service::{init_read_service, init_write_service};
|
||||
use crate::{
|
||||
config::Config,
|
||||
service::{DatabaseReadHandle, DatabaseWriteHandle},
|
||||
service::types::{BlockchainReadHandle, BlockchainWriteHandle},
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Init
|
||||
|
@ -20,17 +21,26 @@ use crate::{
|
|||
///
|
||||
/// # Errors
|
||||
/// This will forward the error if [`crate::open`] failed.
|
||||
pub fn init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle), InitError> {
|
||||
pub fn init(
|
||||
config: Config,
|
||||
) -> Result<
|
||||
(
|
||||
BlockchainReadHandle,
|
||||
BlockchainWriteHandle,
|
||||
Arc<ConcreteEnv>,
|
||||
),
|
||||
InitError,
|
||||
> {
|
||||
let reader_threads = config.reader_threads;
|
||||
|
||||
// Initialize the database itself.
|
||||
let db = Arc::new(crate::open(config)?);
|
||||
|
||||
// Spawn the Reader thread pool and Writer.
|
||||
let readers = DatabaseReadHandle::init(&db, reader_threads);
|
||||
let writer = DatabaseWriteHandle::init(db);
|
||||
let readers = init_read_service(db.clone(), reader_threads);
|
||||
let writer = init_write_service(db.clone());
|
||||
|
||||
Ok((readers, writer))
|
||||
Ok((readers, writer, db))
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Compact history
|
||||
|
@ -38,9 +48,9 @@ pub fn init(config: Config) -> Result<(DatabaseReadHandle, DatabaseWriteHandle),
|
|||
///
|
||||
/// The height offset is the difference between the top block's height and the block height that should be in that position.
|
||||
#[inline]
|
||||
pub(super) const fn compact_history_index_to_height_offset<const INITIAL_BLOCKS: u64>(
|
||||
i: u64,
|
||||
) -> u64 {
|
||||
pub(super) const fn compact_history_index_to_height_offset<const INITIAL_BLOCKS: usize>(
|
||||
i: usize,
|
||||
) -> usize {
|
||||
// If the position is below the initial blocks just return the position back
|
||||
if i <= INITIAL_BLOCKS {
|
||||
i
|
||||
|
@ -56,8 +66,8 @@ pub(super) const fn compact_history_index_to_height_offset<const INITIAL_BLOCKS:
|
|||
///
|
||||
/// The genesis must always be included in the compact history.
|
||||
#[inline]
|
||||
pub(super) const fn compact_history_genesis_not_included<const INITIAL_BLOCKS: u64>(
|
||||
top_block_height: u64,
|
||||
pub(super) const fn compact_history_genesis_not_included<const INITIAL_BLOCKS: usize>(
|
||||
top_block_height: usize,
|
||||
) -> bool {
|
||||
// If the top block height is less than the initial blocks then it will always be included.
|
||||
// Otherwise, we use the fact that to reach the genesis block this statement must be true (for a
|
||||
|
@ -81,7 +91,7 @@ mod tests {
|
|||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn compact_history(top_height in 0_u64..500_000_000) {
|
||||
fn compact_history(top_height in 0_usize..500_000_000) {
|
||||
let mut heights = (0..)
|
||||
.map(compact_history_index_to_height_offset::<11>)
|
||||
.map_while(|i| top_height.checked_sub(i))
|
||||
|
|
|
@ -14,8 +14,8 @@
|
|||
//!
|
||||
//! ## Handles
|
||||
//! The 2 handles to the database are:
|
||||
//! - [`DatabaseReadHandle`]
|
||||
//! - [`DatabaseWriteHandle`]
|
||||
//! - [`BlockchainReadHandle`]
|
||||
//! - [`BlockchainWriteHandle`]
|
||||
//!
|
||||
//! The 1st allows any caller to send [`ReadRequest`][req_r]s.
|
||||
//!
|
||||
|
@ -33,8 +33,10 @@
|
|||
//!
|
||||
//! ## Shutdown
|
||||
//! Upon the above handles being dropped, the corresponding thread(s) will automatically exit, i.e:
|
||||
//! - The last [`DatabaseReadHandle`] is dropped => reader thread-pool exits
|
||||
//! - The last [`DatabaseWriteHandle`] is dropped => writer thread exits
|
||||
//! - The last [`BlockchainReadHandle`] is dropped => reader thread-pool exits
|
||||
//! - The last [`BlockchainWriteHandle`] is dropped => writer thread exits
|
||||
//!
|
||||
//! TODO: update this when `ConcreteEnv` is removed
|
||||
//!
|
||||
//! Upon dropping the [`cuprate_database::ConcreteEnv`]:
|
||||
//! - All un-processed database transactions are completed
|
||||
|
@ -50,11 +52,11 @@
|
|||
//! This channel can be `.await`ed upon to (eventually) receive
|
||||
//! the corresponding `Response` to your `Request`.
|
||||
//!
|
||||
//! [req_r]: cuprate_types::blockchain::BCReadRequest
|
||||
//! [req_r]: cuprate_types::blockchain::BlockchainReadRequest
|
||||
//!
|
||||
//! [req_w]: cuprate_types::blockchain::BCWriteRequest
|
||||
//! [req_w]: cuprate_types::blockchain::BlockchainWriteRequest
|
||||
//!
|
||||
//! [resp]: cuprate_types::blockchain::BCResponse
|
||||
//! [resp]: cuprate_types::blockchain::BlockchainResponse
|
||||
//!
|
||||
//! # Example
|
||||
//! Simple usage of `service`.
|
||||
|
@ -63,7 +65,7 @@
|
|||
//! use hex_literal::hex;
|
||||
//! use tower::{Service, ServiceExt};
|
||||
//!
|
||||
//! use cuprate_types::{blockchain::{BCReadRequest, BCWriteRequest, BCResponse}, Chain};
|
||||
//! use cuprate_types::{blockchain::{BlockchainReadRequest, BlockchainWriteRequest, BlockchainResponse}, Chain};
|
||||
//! use cuprate_test_utils::data::block_v16_tx0;
|
||||
//!
|
||||
//! use cuprate_blockchain::{
|
||||
|
@ -81,12 +83,12 @@
|
|||
//! .build();
|
||||
//!
|
||||
//! // Initialize the database thread-pool.
|
||||
//! let (mut read_handle, mut write_handle) = cuprate_blockchain::service::init(config)?;
|
||||
//! let (mut read_handle, mut write_handle, _) = cuprate_blockchain::service::init(config)?;
|
||||
//!
|
||||
//! // Prepare a request to write block.
|
||||
//! let mut block = block_v16_tx0().clone();
|
||||
//! # block.height = 0_u64; // must be 0th height or panic in `add_block()`
|
||||
//! let request = BCWriteRequest::WriteBlock(block);
|
||||
//! # block.height = 0_usize; // must be 0th height or panic in `add_block()`
|
||||
//! let request = BlockchainWriteRequest::WriteBlock(block);
|
||||
//!
|
||||
//! // Send the request.
|
||||
//! // We receive back an `async` channel that will
|
||||
|
@ -96,16 +98,16 @@
|
|||
//!
|
||||
//! // Block write was OK.
|
||||
//! let response = response_channel.await?;
|
||||
//! assert_eq!(response, BCResponse::WriteBlockOk);
|
||||
//! assert_eq!(response, BlockchainResponse::WriteBlockOk);
|
||||
//!
|
||||
//! // Now, let's try getting the block hash
|
||||
//! // of the block we just wrote.
|
||||
//! let request = BCReadRequest::BlockHash(0, Chain::Main);
|
||||
//! let request = BlockchainReadRequest::BlockHash(0, Chain::Main);
|
||||
//! let response_channel = read_handle.ready().await?.call(request);
|
||||
//! let response = response_channel.await?;
|
||||
//! assert_eq!(
|
||||
//! response,
|
||||
//! BCResponse::BlockHash(
|
||||
//! BlockchainResponse::BlockHash(
|
||||
//! hex!("43bd1f2b6556dcafa413d8372974af59e4e8f37dbf74dc6b2a9b7212d0577428")
|
||||
//! )
|
||||
//! );
|
||||
|
@ -118,17 +120,19 @@
|
|||
//! # Ok(()) }
|
||||
//! ```
|
||||
|
||||
// needed for docs
|
||||
use tower as _;
|
||||
|
||||
mod read;
|
||||
pub use read::DatabaseReadHandle;
|
||||
pub use read::{init_read_service, init_read_service_with_pool};
|
||||
|
||||
mod write;
|
||||
pub use write::DatabaseWriteHandle;
|
||||
pub use write::init_write_service;
|
||||
|
||||
mod free;
|
||||
pub use free::init;
|
||||
|
||||
// Internal type aliases for `service`.
|
||||
mod types;
|
||||
pub use types::{BlockchainReadHandle, BlockchainWriteHandle};
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
|
|
@ -4,24 +4,23 @@
|
|||
use std::{
|
||||
collections::{HashMap, HashSet},
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::{channel::oneshot, ready};
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use rayon::{
|
||||
iter::{IntoParallelIterator, ParallelIterator},
|
||||
ThreadPool,
|
||||
};
|
||||
use thread_local::ThreadLocal;
|
||||
use tokio::sync::{OwnedSemaphorePermit, Semaphore};
|
||||
use tokio_util::sync::PollSemaphore;
|
||||
|
||||
use cuprate_database::{ConcreteEnv, DatabaseRo, Env, EnvInner, RuntimeError};
|
||||
use cuprate_helper::{asynch::InfallibleOneshotReceiver, map::combine_low_high_bits_to_u128};
|
||||
use cuprate_database_service::{init_thread_pool, DatabaseReadService, ReaderThreads};
|
||||
use cuprate_helper::map::combine_low_high_bits_to_u128;
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse},
|
||||
Chain, ExtendedBlockHeader, OutputOnChain,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
config::ReaderThreads,
|
||||
ops::{
|
||||
block::{
|
||||
block_exists, get_block_extended_header_from_height, get_block_height, get_block_info,
|
||||
|
@ -32,156 +31,38 @@ use crate::{
|
|||
},
|
||||
service::{
|
||||
free::{compact_history_genesis_not_included, compact_history_index_to_height_offset},
|
||||
types::{ResponseReceiver, ResponseResult, ResponseSender},
|
||||
types::{BlockchainReadHandle, ResponseResult},
|
||||
},
|
||||
tables::OpenTables,
|
||||
tables::{BlockHeights, BlockInfos, Tables},
|
||||
tables::{BlockHeights, BlockInfos, OpenTables, Tables},
|
||||
types::{Amount, AmountIndex, BlockHash, BlockHeight, KeyImage, PreRctOutputId},
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- DatabaseReadHandle
|
||||
/// Read handle to the database.
|
||||
//---------------------------------------------------------------------------------------------------- init_read_service
|
||||
/// Initialize the [`BlockchainReadHandle`] thread-pool backed by [`rayon`].
|
||||
///
|
||||
/// This is cheaply [`Clone`]able handle that
|
||||
/// allows `async`hronously reading from the database.
|
||||
/// This spawns `threads` amount of reader threads
|
||||
/// attached to `env` and returns a handle to the pool.
|
||||
///
|
||||
/// Calling [`tower::Service::call`] with a [`DatabaseReadHandle`] & [`BCReadRequest`]
|
||||
/// will return an `async`hronous channel that can be `.await`ed upon
|
||||
/// to receive the corresponding [`BCResponse`].
|
||||
pub struct DatabaseReadHandle {
|
||||
/// Handle to the custom `rayon` DB reader thread-pool.
|
||||
///
|
||||
/// Requests are [`rayon::ThreadPool::spawn`]ed in this thread-pool,
|
||||
/// and responses are returned via a channel we (the caller) provide.
|
||||
pool: Arc<rayon::ThreadPool>,
|
||||
/// Should be called _once_ per actual database. Calling this function more than once will create
|
||||
/// multiple unnecessary rayon thread-pools.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
pub fn init_read_service(env: Arc<ConcreteEnv>, threads: ReaderThreads) -> BlockchainReadHandle {
|
||||
init_read_service_with_pool(env, init_thread_pool(threads))
|
||||
}
|
||||
|
||||
/// Counting semaphore asynchronous permit for database access.
|
||||
/// Each [`tower::Service::poll_ready`] will acquire a permit
|
||||
/// before actually sending a request to the `rayon` DB threadpool.
|
||||
semaphore: PollSemaphore,
|
||||
|
||||
/// An owned permit.
|
||||
/// This will be set to [`Some`] in `poll_ready()` when we successfully acquire
|
||||
/// the permit, and will be [`Option::take()`]n after `tower::Service::call()` is called.
|
||||
///
|
||||
/// The actual permit will be dropped _after_ the rayon DB thread has finished
|
||||
/// the request, i.e., after [`map_request()`] finishes.
|
||||
permit: Option<OwnedSemaphorePermit>,
|
||||
|
||||
/// Access to the database.
|
||||
/// Initialize the blockchain database read service, with a specific rayon thread-pool instead of
|
||||
/// creating a new one.
|
||||
///
|
||||
/// Should be called _once_ per actual database, although nothing bad will happen, cloning the [`BlockchainReadHandle`]
|
||||
/// is the correct way to get multiple handles to the database.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
pub fn init_read_service_with_pool(
|
||||
env: Arc<ConcreteEnv>,
|
||||
}
|
||||
|
||||
// `OwnedSemaphorePermit` does not implement `Clone`,
|
||||
// so manually clone all elements, while keeping `permit`
|
||||
// `None` across clones.
|
||||
impl Clone for DatabaseReadHandle {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pool: Arc::clone(&self.pool),
|
||||
semaphore: self.semaphore.clone(),
|
||||
permit: None,
|
||||
env: Arc::clone(&self.env),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseReadHandle {
|
||||
/// Initialize the `DatabaseReader` thread-pool backed by `rayon`.
|
||||
///
|
||||
/// This spawns `N` amount of `DatabaseReader`'s
|
||||
/// attached to `env` and returns a handle to the pool.
|
||||
///
|
||||
/// Should be called _once_ per actual database.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
pub(super) fn init(env: &Arc<ConcreteEnv>, reader_threads: ReaderThreads) -> Self {
|
||||
// How many reader threads to spawn?
|
||||
let reader_count = reader_threads.as_threads().get();
|
||||
|
||||
// Spawn `rayon` reader threadpool.
|
||||
let pool = rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(reader_count)
|
||||
.thread_name(|i| format!("cuprate_helper::service::read::DatabaseReader{i}"))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
// Create a semaphore with the same amount of
|
||||
// permits as the amount of reader threads.
|
||||
let semaphore = PollSemaphore::new(Arc::new(Semaphore::new(reader_count)));
|
||||
|
||||
// Return a handle to the pool.
|
||||
Self {
|
||||
pool: Arc::new(pool),
|
||||
semaphore,
|
||||
permit: None,
|
||||
env: Arc::clone(env),
|
||||
}
|
||||
}
|
||||
|
||||
/// Access to the actual database environment.
|
||||
///
|
||||
/// # ⚠️ Warning
|
||||
/// This function gives you access to the actual
|
||||
/// underlying database connected to by `self`.
|
||||
///
|
||||
/// I.e. it allows you to read/write data _directly_
|
||||
/// instead of going through a request.
|
||||
///
|
||||
/// Be warned that using the database directly
|
||||
/// in this manner has not been tested.
|
||||
#[inline]
|
||||
pub const fn env(&self) -> &Arc<ConcreteEnv> {
|
||||
&self.env
|
||||
}
|
||||
}
|
||||
|
||||
impl tower::Service<BCReadRequest> for DatabaseReadHandle {
|
||||
type Response = BCResponse;
|
||||
type Error = RuntimeError;
|
||||
type Future = ResponseReceiver;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
// Check if we already have a permit.
|
||||
if self.permit.is_some() {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
|
||||
// Acquire a permit before returning `Ready`.
|
||||
let permit =
|
||||
ready!(self.semaphore.poll_acquire(cx)).expect("this semaphore is never closed");
|
||||
|
||||
self.permit = Some(permit);
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, request: BCReadRequest) -> Self::Future {
|
||||
let permit = self
|
||||
.permit
|
||||
.take()
|
||||
.expect("poll_ready() should have acquire a permit before calling call()");
|
||||
|
||||
// Response channel we `.await` on.
|
||||
let (response_sender, receiver) = oneshot::channel();
|
||||
|
||||
// Spawn the request in the rayon DB thread-pool.
|
||||
//
|
||||
// Note that this uses `self.pool` instead of `rayon::spawn`
|
||||
// such that any `rayon` parallel code that runs within
|
||||
// the passed closure uses the same `rayon` threadpool.
|
||||
//
|
||||
// INVARIANT:
|
||||
// The below `DatabaseReader` function impl block relies on this behavior.
|
||||
let env = Arc::clone(&self.env);
|
||||
self.pool.spawn(move || {
|
||||
let _permit: OwnedSemaphorePermit = permit;
|
||||
map_request(&env, request, response_sender);
|
||||
}); // drop(permit/env);
|
||||
|
||||
InfallibleOneshotReceiver::from(receiver)
|
||||
}
|
||||
pool: Arc<ThreadPool>,
|
||||
) -> BlockchainReadHandle {
|
||||
DatabaseReadService::new(env, pool, map_request)
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Request Mapping
|
||||
|
@ -194,17 +75,16 @@ impl tower::Service<BCReadRequest> for DatabaseReadHandle {
|
|||
/// The basic structure is:
|
||||
/// 1. `Request` is mapped to a handler function
|
||||
/// 2. Handler function is called
|
||||
/// 3. [`BCResponse`] is sent
|
||||
/// 3. [`BlockchainResponse`] is returned
|
||||
fn map_request(
|
||||
env: &ConcreteEnv, // Access to the database
|
||||
request: BCReadRequest, // The request we must fulfill
|
||||
response_sender: ResponseSender, // The channel we must send the response back to
|
||||
) {
|
||||
use BCReadRequest as R;
|
||||
env: &ConcreteEnv, // Access to the database
|
||||
request: BlockchainReadRequest, // The request we must fulfill
|
||||
) -> ResponseResult {
|
||||
use BlockchainReadRequest as R;
|
||||
|
||||
/* SOMEDAY: pre-request handling, run some code for each request? */
|
||||
|
||||
let response = match request {
|
||||
match request {
|
||||
R::BlockExtendedHeader(block) => block_extended_header(env, block),
|
||||
R::BlockHash(block, chain) => block_hash(env, block, chain),
|
||||
R::FindBlock(_) => todo!("Add alt blocks to DB"),
|
||||
|
@ -219,11 +99,6 @@ fn map_request(
|
|||
R::KeyImagesSpent(set) => key_images_spent(env, set),
|
||||
R::CompactChainHistory => compact_chain_history(env),
|
||||
R::FindFirstUnknown(block_ids) => find_first_unknown(env, &block_ids),
|
||||
};
|
||||
|
||||
if let Err(e) = response_sender.send(response) {
|
||||
// TODO: use tracing.
|
||||
println!("database reader failed to send response: {e:?}");
|
||||
}
|
||||
|
||||
/* SOMEDAY: post-request handling, run some code for each request? */
|
||||
|
@ -300,7 +175,7 @@ macro_rules! get_tables {
|
|||
// TODO: The overhead of parallelism may be too much for every request, perfomace test to find optimal
|
||||
// amount of parallelism.
|
||||
|
||||
/// [`BCReadRequest::BlockExtendedHeader`].
|
||||
/// [`BlockchainReadRequest::BlockExtendedHeader`].
|
||||
#[inline]
|
||||
fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> ResponseResult {
|
||||
// Single-threaded, no `ThreadLocal` required.
|
||||
|
@ -308,12 +183,12 @@ fn block_extended_header(env: &ConcreteEnv, block_height: BlockHeight) -> Respon
|
|||
let tx_ro = env_inner.tx_ro()?;
|
||||
let tables = env_inner.open_tables(&tx_ro)?;
|
||||
|
||||
Ok(BCResponse::BlockExtendedHeader(
|
||||
Ok(BlockchainResponse::BlockExtendedHeader(
|
||||
get_block_extended_header_from_height(&block_height, &tables)?,
|
||||
))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::BlockHash`].
|
||||
/// [`BlockchainReadRequest::BlockHash`].
|
||||
#[inline]
|
||||
fn block_hash(env: &ConcreteEnv, block_height: BlockHeight, chain: Chain) -> ResponseResult {
|
||||
// Single-threaded, no `ThreadLocal` required.
|
||||
|
@ -326,10 +201,10 @@ fn block_hash(env: &ConcreteEnv, block_height: BlockHeight, chain: Chain) -> Res
|
|||
Chain::Alt(_) => todo!("Add alt blocks to DB"),
|
||||
};
|
||||
|
||||
Ok(BCResponse::BlockHash(block_hash))
|
||||
Ok(BlockchainResponse::BlockHash(block_hash))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::FilterUnknownHashes`].
|
||||
/// [`BlockchainReadRequest::FilterUnknownHashes`].
|
||||
#[inline]
|
||||
fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet<BlockHash>) -> ResponseResult {
|
||||
// Single-threaded, no `ThreadLocal` required.
|
||||
|
@ -353,11 +228,11 @@ fn filter_unknown_hashes(env: &ConcreteEnv, mut hashes: HashSet<BlockHash>) -> R
|
|||
if let Some(e) = err {
|
||||
Err(e)
|
||||
} else {
|
||||
Ok(BCResponse::FilterUnknownHashes(hashes))
|
||||
Ok(BlockchainResponse::FilterUnknownHashes(hashes))
|
||||
}
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::BlockExtendedHeaderInRange`].
|
||||
/// [`BlockchainReadRequest::BlockExtendedHeaderInRange`].
|
||||
#[inline]
|
||||
fn block_extended_header_in_range(
|
||||
env: &ConcreteEnv,
|
||||
|
@ -382,10 +257,10 @@ fn block_extended_header_in_range(
|
|||
Chain::Alt(_) => todo!("Add alt blocks to DB"),
|
||||
};
|
||||
|
||||
Ok(BCResponse::BlockExtendedHeaderInRange(vec))
|
||||
Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::ChainHeight`].
|
||||
/// [`BlockchainReadRequest::ChainHeight`].
|
||||
#[inline]
|
||||
fn chain_height(env: &ConcreteEnv) -> ResponseResult {
|
||||
// Single-threaded, no `ThreadLocal` required.
|
||||
|
@ -398,24 +273,23 @@ fn chain_height(env: &ConcreteEnv) -> ResponseResult {
|
|||
let block_hash =
|
||||
get_block_info(&chain_height.saturating_sub(1), &table_block_infos)?.block_hash;
|
||||
|
||||
Ok(BCResponse::ChainHeight(chain_height, block_hash))
|
||||
Ok(BlockchainResponse::ChainHeight(chain_height, block_hash))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::GeneratedCoins`].
|
||||
/// [`BlockchainReadRequest::GeneratedCoins`].
|
||||
#[inline]
|
||||
fn generated_coins(env: &ConcreteEnv, height: u64) -> ResponseResult {
|
||||
fn generated_coins(env: &ConcreteEnv, height: usize) -> ResponseResult {
|
||||
// Single-threaded, no `ThreadLocal` required.
|
||||
let env_inner = env.env_inner();
|
||||
let tx_ro = env_inner.tx_ro()?;
|
||||
let table_block_infos = env_inner.open_db_ro::<BlockInfos>(&tx_ro)?;
|
||||
|
||||
Ok(BCResponse::GeneratedCoins(cumulative_generated_coins(
|
||||
&height,
|
||||
&table_block_infos,
|
||||
)?))
|
||||
Ok(BlockchainResponse::GeneratedCoins(
|
||||
cumulative_generated_coins(&height, &table_block_infos)?,
|
||||
))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::Outputs`].
|
||||
/// [`BlockchainReadRequest::Outputs`].
|
||||
#[inline]
|
||||
fn outputs(env: &ConcreteEnv, outputs: HashMap<Amount, HashSet<AmountIndex>>) -> ResponseResult {
|
||||
// Prepare tx/tables in `ThreadLocal`.
|
||||
|
@ -453,10 +327,10 @@ fn outputs(env: &ConcreteEnv, outputs: HashMap<Amount, HashSet<AmountIndex>>) ->
|
|||
})
|
||||
.collect::<Result<HashMap<Amount, HashMap<AmountIndex, OutputOnChain>>, RuntimeError>>()?;
|
||||
|
||||
Ok(BCResponse::Outputs(map))
|
||||
Ok(BlockchainResponse::Outputs(map))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::NumberOutputsWithAmount`].
|
||||
/// [`BlockchainReadRequest::NumberOutputsWithAmount`].
|
||||
#[inline]
|
||||
fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec<Amount>) -> ResponseResult {
|
||||
// Prepare tx/tables in `ThreadLocal`.
|
||||
|
@ -498,10 +372,10 @@ fn number_outputs_with_amount(env: &ConcreteEnv, amounts: Vec<Amount>) -> Respon
|
|||
})
|
||||
.collect::<Result<HashMap<Amount, usize>, RuntimeError>>()?;
|
||||
|
||||
Ok(BCResponse::NumberOutputsWithAmount(map))
|
||||
Ok(BlockchainResponse::NumberOutputsWithAmount(map))
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::KeyImagesSpent`].
|
||||
/// [`BlockchainReadRequest::KeyImagesSpent`].
|
||||
#[inline]
|
||||
fn key_images_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> ResponseResult {
|
||||
// Prepare tx/tables in `ThreadLocal`.
|
||||
|
@ -532,13 +406,13 @@ fn key_images_spent(env: &ConcreteEnv, key_images: HashSet<KeyImage>) -> Respons
|
|||
// Else, `Ok(false)` will continue the iterator.
|
||||
.find_any(|result| !matches!(result, Ok(false)))
|
||||
{
|
||||
None | Some(Ok(false)) => Ok(BCResponse::KeyImagesSpent(false)), // Key image was NOT found.
|
||||
Some(Ok(true)) => Ok(BCResponse::KeyImagesSpent(true)), // Key image was found.
|
||||
None | Some(Ok(false)) => Ok(BlockchainResponse::KeyImagesSpent(false)), // Key image was NOT found.
|
||||
Some(Ok(true)) => Ok(BlockchainResponse::KeyImagesSpent(true)), // Key image was found.
|
||||
Some(Err(e)) => Err(e), // A database error occurred.
|
||||
}
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::CompactChainHistory`]
|
||||
/// [`BlockchainReadRequest::CompactChainHistory`]
|
||||
fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult {
|
||||
let env_inner = env.env_inner();
|
||||
let tx_ro = env_inner.tx_ro()?;
|
||||
|
@ -555,7 +429,7 @@ fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult {
|
|||
);
|
||||
|
||||
/// The amount of top block IDs in the compact chain.
|
||||
const INITIAL_BLOCKS: u64 = 11;
|
||||
const INITIAL_BLOCKS: usize = 11;
|
||||
|
||||
// rayon is not used here because the amount of block IDs is expected to be small.
|
||||
let mut block_ids = (0..)
|
||||
|
@ -568,13 +442,13 @@ fn compact_chain_history(env: &ConcreteEnv) -> ResponseResult {
|
|||
block_ids.push(get_block_info(&0, &table_block_infos)?.block_hash);
|
||||
}
|
||||
|
||||
Ok(BCResponse::CompactChainHistory {
|
||||
Ok(BlockchainResponse::CompactChainHistory {
|
||||
cumulative_difficulty,
|
||||
block_ids,
|
||||
})
|
||||
}
|
||||
|
||||
/// [`BCReadRequest::FindFirstUnknown`]
|
||||
/// [`BlockchainReadRequest::FindFirstUnknown`]
|
||||
///
|
||||
/// # Invariant
|
||||
/// `block_ids` must be sorted in chronological block order, or else
|
||||
|
@ -606,12 +480,12 @@ fn find_first_unknown(env: &ConcreteEnv, block_ids: &[BlockHash]) -> ResponseRes
|
|||
}
|
||||
|
||||
Ok(if idx == block_ids.len() {
|
||||
BCResponse::FindFirstUnknown(None)
|
||||
BlockchainResponse::FindFirstUnknown(None)
|
||||
} else if idx == 0 {
|
||||
BCResponse::FindFirstUnknown(Some((0, 0)))
|
||||
BlockchainResponse::FindFirstUnknown(Some((0, 0)))
|
||||
} else {
|
||||
let last_known_height = get_block_height(&block_ids[idx - 1], &table_block_heights)?;
|
||||
|
||||
BCResponse::FindFirstUnknown(Some((idx, last_known_height + 1)))
|
||||
BlockchainResponse::FindFirstUnknown(Some((idx, last_known_height + 1)))
|
||||
})
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ use tower::{Service, ServiceExt};
|
|||
use cuprate_database::{ConcreteEnv, DatabaseIter, DatabaseRo, Env, EnvInner, RuntimeError};
|
||||
use cuprate_test_utils::data::{block_v16_tx0, block_v1_tx2, block_v9_tx3};
|
||||
use cuprate_types::{
|
||||
blockchain::{BCReadRequest, BCResponse, BCWriteRequest},
|
||||
blockchain::{BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest},
|
||||
Chain, OutputOnChain, VerifiedBlockInformation,
|
||||
};
|
||||
|
||||
|
@ -29,7 +29,7 @@ use crate::{
|
|||
blockchain::chain_height,
|
||||
output::id_to_output_on_chain,
|
||||
},
|
||||
service::{init, DatabaseReadHandle, DatabaseWriteHandle},
|
||||
service::{init, BlockchainReadHandle, BlockchainWriteHandle},
|
||||
tables::{OpenTables, Tables, TablesIter},
|
||||
tests::AssertTableLen,
|
||||
types::{Amount, AmountIndex, PreRctOutputId},
|
||||
|
@ -38,8 +38,8 @@ use crate::{
|
|||
//---------------------------------------------------------------------------------------------------- Helper functions
|
||||
/// Initialize the `service`.
|
||||
fn init_service() -> (
|
||||
DatabaseReadHandle,
|
||||
DatabaseWriteHandle,
|
||||
BlockchainReadHandle,
|
||||
BlockchainWriteHandle,
|
||||
Arc<ConcreteEnv>,
|
||||
tempfile::TempDir,
|
||||
) {
|
||||
|
@ -48,8 +48,7 @@ fn init_service() -> (
|
|||
.db_directory(Cow::Owned(tempdir.path().into()))
|
||||
.low_power()
|
||||
.build();
|
||||
let (reader, writer) = init(config).unwrap();
|
||||
let env = reader.env().clone();
|
||||
let (reader, writer, env) = init(config).unwrap();
|
||||
(reader, writer, env, tempdir)
|
||||
}
|
||||
|
||||
|
@ -79,13 +78,13 @@ async fn test_template(
|
|||
// cannot be added, to get around this, manually edit the block height.
|
||||
for (i, block_fn) in block_fns.iter().enumerate() {
|
||||
let mut block = block_fn().clone();
|
||||
block.height = i as u64;
|
||||
block.height = i;
|
||||
|
||||
// Request a block to be written, assert it was written.
|
||||
let request = BCWriteRequest::WriteBlock(block);
|
||||
let request = BlockchainWriteRequest::WriteBlock(block);
|
||||
let response_channel = writer.call(request);
|
||||
let response = response_channel.await.unwrap();
|
||||
assert_eq!(response, BCResponse::WriteBlockOk);
|
||||
assert_eq!(response, BlockchainResponse::WriteBlockOk);
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------- Reset the transaction
|
||||
|
@ -101,36 +100,36 @@ async fn test_template(
|
|||
// Next few lines are just for preparing the expected responses,
|
||||
// see further below for usage.
|
||||
|
||||
let extended_block_header_0 = Ok(BCResponse::BlockExtendedHeader(
|
||||
let extended_block_header_0 = Ok(BlockchainResponse::BlockExtendedHeader(
|
||||
get_block_extended_header_from_height(&0, &tables).unwrap(),
|
||||
));
|
||||
|
||||
let extended_block_header_1 = if block_fns.len() > 1 {
|
||||
Ok(BCResponse::BlockExtendedHeader(
|
||||
Ok(BlockchainResponse::BlockExtendedHeader(
|
||||
get_block_extended_header_from_height(&1, &tables).unwrap(),
|
||||
))
|
||||
} else {
|
||||
Err(RuntimeError::KeyNotFound)
|
||||
};
|
||||
|
||||
let block_hash_0 = Ok(BCResponse::BlockHash(
|
||||
let block_hash_0 = Ok(BlockchainResponse::BlockHash(
|
||||
get_block_info(&0, tables.block_infos()).unwrap().block_hash,
|
||||
));
|
||||
|
||||
let block_hash_1 = if block_fns.len() > 1 {
|
||||
Ok(BCResponse::BlockHash(
|
||||
Ok(BlockchainResponse::BlockHash(
|
||||
get_block_info(&1, tables.block_infos()).unwrap().block_hash,
|
||||
))
|
||||
} else {
|
||||
Err(RuntimeError::KeyNotFound)
|
||||
};
|
||||
|
||||
let range_0_1 = Ok(BCResponse::BlockExtendedHeaderInRange(vec![
|
||||
let range_0_1 = Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec![
|
||||
get_block_extended_header_from_height(&0, &tables).unwrap(),
|
||||
]));
|
||||
|
||||
let range_0_2 = if block_fns.len() >= 2 {
|
||||
Ok(BCResponse::BlockExtendedHeaderInRange(vec![
|
||||
Ok(BlockchainResponse::BlockExtendedHeaderInRange(vec![
|
||||
get_block_extended_header_from_height(&0, &tables).unwrap(),
|
||||
get_block_extended_header_from_height(&1, &tables).unwrap(),
|
||||
]))
|
||||
|
@ -143,13 +142,15 @@ async fn test_template(
|
|||
let chain_height = {
|
||||
let block_info =
|
||||
get_block_info(&test_chain_height.saturating_sub(1), tables.block_infos()).unwrap();
|
||||
Ok(BCResponse::ChainHeight(
|
||||
Ok(BlockchainResponse::ChainHeight(
|
||||
test_chain_height,
|
||||
block_info.block_hash,
|
||||
))
|
||||
};
|
||||
|
||||
let cumulative_generated_coins = Ok(BCResponse::GeneratedCoins(cumulative_generated_coins));
|
||||
let cumulative_generated_coins = Ok(BlockchainResponse::GeneratedCoins(
|
||||
cumulative_generated_coins,
|
||||
));
|
||||
|
||||
let num_req = tables
|
||||
.outputs_iter()
|
||||
|
@ -159,7 +160,7 @@ async fn test_template(
|
|||
.map(|key| key.amount)
|
||||
.collect::<Vec<Amount>>();
|
||||
|
||||
let num_resp = Ok(BCResponse::NumberOutputsWithAmount(
|
||||
let num_resp = Ok(BlockchainResponse::NumberOutputsWithAmount(
|
||||
num_req
|
||||
.iter()
|
||||
.map(|amount| match tables.num_outputs().get(amount) {
|
||||
|
@ -174,36 +175,45 @@ async fn test_template(
|
|||
|
||||
// Contains a fake non-spent key-image.
|
||||
let ki_req = HashSet::from([[0; 32]]);
|
||||
let ki_resp = Ok(BCResponse::KeyImagesSpent(false));
|
||||
let ki_resp = Ok(BlockchainResponse::KeyImagesSpent(false));
|
||||
|
||||
//----------------------------------------------------------------------- Assert expected response
|
||||
// Assert read requests lead to the expected responses.
|
||||
for (request, expected_response) in [
|
||||
(
|
||||
BCReadRequest::BlockExtendedHeader(0),
|
||||
BlockchainReadRequest::BlockExtendedHeader(0),
|
||||
extended_block_header_0,
|
||||
),
|
||||
(
|
||||
BCReadRequest::BlockExtendedHeader(1),
|
||||
BlockchainReadRequest::BlockExtendedHeader(1),
|
||||
extended_block_header_1,
|
||||
),
|
||||
(BCReadRequest::BlockHash(0, Chain::Main), block_hash_0),
|
||||
(BCReadRequest::BlockHash(1, Chain::Main), block_hash_1),
|
||||
(
|
||||
BCReadRequest::BlockExtendedHeaderInRange(0..1, Chain::Main),
|
||||
BlockchainReadRequest::BlockHash(0, Chain::Main),
|
||||
block_hash_0,
|
||||
),
|
||||
(
|
||||
BlockchainReadRequest::BlockHash(1, Chain::Main),
|
||||
block_hash_1,
|
||||
),
|
||||
(
|
||||
BlockchainReadRequest::BlockExtendedHeaderInRange(0..1, Chain::Main),
|
||||
range_0_1,
|
||||
),
|
||||
(
|
||||
BCReadRequest::BlockExtendedHeaderInRange(0..2, Chain::Main),
|
||||
BlockchainReadRequest::BlockExtendedHeaderInRange(0..2, Chain::Main),
|
||||
range_0_2,
|
||||
),
|
||||
(BCReadRequest::ChainHeight, chain_height),
|
||||
(BlockchainReadRequest::ChainHeight, chain_height),
|
||||
(
|
||||
BCReadRequest::GeneratedCoins(test_chain_height),
|
||||
BlockchainReadRequest::GeneratedCoins(test_chain_height),
|
||||
cumulative_generated_coins,
|
||||
),
|
||||
(BCReadRequest::NumberOutputsWithAmount(num_req), num_resp),
|
||||
(BCReadRequest::KeyImagesSpent(ki_req), ki_resp),
|
||||
(
|
||||
BlockchainReadRequest::NumberOutputsWithAmount(num_req),
|
||||
num_resp,
|
||||
),
|
||||
(BlockchainReadRequest::KeyImagesSpent(ki_req), ki_resp),
|
||||
] {
|
||||
let response = reader.clone().oneshot(request).await;
|
||||
println!("response: {response:#?}, expected_response: {expected_response:#?}");
|
||||
|
@ -217,10 +227,10 @@ async fn test_template(
|
|||
// Assert each key image we inserted comes back as "spent".
|
||||
for key_image in tables.key_images_iter().keys().unwrap() {
|
||||
let key_image = key_image.unwrap();
|
||||
let request = BCReadRequest::KeyImagesSpent(HashSet::from([key_image]));
|
||||
let request = BlockchainReadRequest::KeyImagesSpent(HashSet::from([key_image]));
|
||||
let response = reader.clone().oneshot(request).await;
|
||||
println!("response: {response:#?}, key_image: {key_image:#?}");
|
||||
assert_eq!(response.unwrap(), BCResponse::KeyImagesSpent(true));
|
||||
assert_eq!(response.unwrap(), BlockchainResponse::KeyImagesSpent(true));
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------- Output checks
|
||||
|
@ -281,10 +291,10 @@ async fn test_template(
|
|||
.collect::<Vec<OutputOnChain>>();
|
||||
|
||||
// Send a request for every output we inserted before.
|
||||
let request = BCReadRequest::Outputs(map.clone());
|
||||
let request = BlockchainReadRequest::Outputs(map.clone());
|
||||
let response = reader.clone().oneshot(request).await;
|
||||
println!("Response::Outputs response: {response:#?}");
|
||||
let Ok(BCResponse::Outputs(response)) = response else {
|
||||
let Ok(BlockchainResponse::Outputs(response)) = response else {
|
||||
panic!("{response:#?}")
|
||||
};
|
||||
|
||||
|
|
|
@ -1,30 +1,20 @@
|
|||
//! Database service type aliases.
|
||||
//!
|
||||
//! Only used internally for our `tower::Service` impls.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Use
|
||||
use futures::channel::oneshot::Sender;
|
||||
|
||||
use cuprate_database::RuntimeError;
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_types::blockchain::BCResponse;
|
||||
use cuprate_database_service::{DatabaseReadService, DatabaseWriteHandle};
|
||||
use cuprate_types::blockchain::{
|
||||
BlockchainReadRequest, BlockchainResponse, BlockchainWriteRequest,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Types
|
||||
/// The actual type of the response.
|
||||
///
|
||||
/// Either our [`BCResponse`], or a database error occurred.
|
||||
pub(super) type ResponseResult = Result<BCResponse, RuntimeError>;
|
||||
/// Either our [`BlockchainResponse`], or a database error occurred.
|
||||
pub(super) type ResponseResult = Result<BlockchainResponse, RuntimeError>;
|
||||
|
||||
/// The `Receiver` channel that receives the read response.
|
||||
///
|
||||
/// This is owned by the caller (the reader/writer thread)
|
||||
/// who `.await`'s for the response.
|
||||
///
|
||||
/// The channel itself should never fail,
|
||||
/// but the actual database operation might.
|
||||
pub(super) type ResponseReceiver = InfallibleOneshotReceiver<ResponseResult>;
|
||||
/// The blockchain database write service.
|
||||
pub type BlockchainWriteHandle = DatabaseWriteHandle<BlockchainWriteRequest, BlockchainResponse>;
|
||||
|
||||
/// The `Sender` channel for the response.
|
||||
///
|
||||
/// The database reader/writer thread uses this to send the database result to the caller.
|
||||
pub(super) type ResponseSender = Sender<ResponseResult>;
|
||||
/// The blockchain database read service.
|
||||
pub type BlockchainReadHandle = DatabaseReadService<BlockchainReadRequest, BlockchainResponse>;
|
||||
|
|
|
@ -1,209 +1,34 @@
|
|||
//! Database writer thread definitions and logic.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::{
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use std::sync::Arc;
|
||||
|
||||
use cuprate_database::{ConcreteEnv, Env, EnvInner, RuntimeError, TxRw};
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
use cuprate_database_service::DatabaseWriteHandle;
|
||||
use cuprate_types::{
|
||||
blockchain::{BCResponse, BCWriteRequest},
|
||||
blockchain::{BlockchainResponse, BlockchainWriteRequest},
|
||||
VerifiedBlockInformation,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
service::types::{ResponseReceiver, ResponseResult, ResponseSender},
|
||||
service::types::{BlockchainWriteHandle, ResponseResult},
|
||||
tables::OpenTables,
|
||||
};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Constants
|
||||
/// Name of the writer thread.
|
||||
const WRITER_THREAD_NAME: &str = concat!(module_path!(), "::DatabaseWriter");
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- DatabaseWriteHandle
|
||||
/// Write handle to the database.
|
||||
///
|
||||
/// This is handle that allows `async`hronously writing to the database,
|
||||
/// it is not [`Clone`]able as there is only ever 1 place within Cuprate
|
||||
/// that writes.
|
||||
///
|
||||
/// Calling [`tower::Service::call`] with a [`DatabaseWriteHandle`] & [`BCWriteRequest`]
|
||||
/// will return an `async`hronous channel that can be `.await`ed upon
|
||||
/// to receive the corresponding [`BCResponse`].
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseWriteHandle {
|
||||
/// Sender channel to the database write thread-pool.
|
||||
///
|
||||
/// We provide the response channel for the thread-pool.
|
||||
pub(super) sender: crossbeam::channel::Sender<(BCWriteRequest, ResponseSender)>,
|
||||
//---------------------------------------------------------------------------------------------------- init_write_service
|
||||
/// Initialize the blockchain write service from a [`ConcreteEnv`].
|
||||
pub fn init_write_service(env: Arc<ConcreteEnv>) -> BlockchainWriteHandle {
|
||||
DatabaseWriteHandle::init(env, handle_blockchain_request)
|
||||
}
|
||||
|
||||
impl DatabaseWriteHandle {
|
||||
/// Initialize the single `DatabaseWriter` thread.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
pub(super) fn init(env: Arc<ConcreteEnv>) -> Self {
|
||||
// Initialize `Request/Response` channels.
|
||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||
|
||||
// Spawn the writer.
|
||||
std::thread::Builder::new()
|
||||
.name(WRITER_THREAD_NAME.into())
|
||||
.spawn(move || {
|
||||
let this = DatabaseWriter { receiver, env };
|
||||
DatabaseWriter::main(this);
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Self { sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl tower::Service<BCWriteRequest> for DatabaseWriteHandle {
|
||||
type Response = BCResponse;
|
||||
type Error = RuntimeError;
|
||||
type Future = ResponseReceiver;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, request: BCWriteRequest) -> Self::Future {
|
||||
// Response channel we `.await` on.
|
||||
let (response_sender, receiver) = oneshot::channel();
|
||||
|
||||
// Send the write request.
|
||||
self.sender.send((request, response_sender)).unwrap();
|
||||
|
||||
InfallibleOneshotReceiver::from(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- DatabaseWriter
|
||||
/// The single database writer thread.
|
||||
pub(super) struct DatabaseWriter {
|
||||
/// Receiver side of the database request channel.
|
||||
///
|
||||
/// Any caller can send some requests to this channel.
|
||||
/// They send them alongside another `Response` channel,
|
||||
/// which we will eventually send to.
|
||||
receiver: crossbeam::channel::Receiver<(BCWriteRequest, ResponseSender)>,
|
||||
|
||||
/// Access to the database.
|
||||
env: Arc<ConcreteEnv>,
|
||||
}
|
||||
|
||||
impl Drop for DatabaseWriter {
|
||||
fn drop(&mut self) {
|
||||
// TODO: log the writer thread has exited?
|
||||
}
|
||||
}
|
||||
|
||||
impl DatabaseWriter {
|
||||
/// The `DatabaseWriter`'s main function.
|
||||
///
|
||||
/// The writer just loops in this function, handling requests forever
|
||||
/// until the request channel is dropped or a panic occurs.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
fn main(self) {
|
||||
// 1. Hang on request channel
|
||||
// 2. Map request to some database function
|
||||
// 3. Execute that function, get the result
|
||||
// 4. Return the result via channel
|
||||
'main: loop {
|
||||
let Ok((request, response_sender)) = self.receiver.recv() else {
|
||||
// If this receive errors, it means that the channel is empty
|
||||
// and disconnected, meaning the other side (all senders) have
|
||||
// been dropped. This means "shutdown", and we return here to
|
||||
// exit the thread.
|
||||
//
|
||||
// Since the channel is empty, it means we've also processed
|
||||
// all requests. Since it is disconnected, it means future
|
||||
// ones cannot come in.
|
||||
return;
|
||||
};
|
||||
|
||||
/// How many times should we retry handling the request on resize errors?
|
||||
///
|
||||
/// This is 1 on automatically resizing databases, meaning there is only 1 iteration.
|
||||
const REQUEST_RETRY_LIMIT: usize = if ConcreteEnv::MANUAL_RESIZE { 3 } else { 1 };
|
||||
|
||||
// Map [`Request`]'s to specific database functions.
|
||||
//
|
||||
// Both will:
|
||||
// 1. Map the request to a function
|
||||
// 2. Call the function
|
||||
// 3. (manual resize only) If resize is needed, resize and retry
|
||||
// 4. (manual resize only) Redo step {1, 2}
|
||||
// 5. Send the function's `Result` back to the requester
|
||||
//
|
||||
// FIXME: there's probably a more elegant way
|
||||
// to represent this retry logic with recursive
|
||||
// functions instead of a loop.
|
||||
'retry: for retry in 0..REQUEST_RETRY_LIMIT {
|
||||
// FIXME: will there be more than 1 write request?
|
||||
// this won't have to be an enum.
|
||||
let response = match &request {
|
||||
BCWriteRequest::WriteBlock(block) => write_block(&self.env, block),
|
||||
};
|
||||
|
||||
// If the database needs to resize, do so.
|
||||
if ConcreteEnv::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded))
|
||||
{
|
||||
// If this is the last iteration of the outer `for` loop and we
|
||||
// encounter a resize error _again_, it means something is wrong.
|
||||
assert_ne!(
|
||||
retry, REQUEST_RETRY_LIMIT,
|
||||
"database resize failed maximum of {REQUEST_RETRY_LIMIT} times"
|
||||
);
|
||||
|
||||
// Resize the map, and retry the request handling loop.
|
||||
//
|
||||
// FIXME:
|
||||
// We could pass in custom resizes to account for
|
||||
// batches, i.e., we're about to add ~5GB of data,
|
||||
// add that much instead of the default 1GB.
|
||||
// <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L665-L695>
|
||||
let old = self.env.current_map_size();
|
||||
let new = self.env.resize_map(None);
|
||||
|
||||
// TODO: use tracing.
|
||||
println!("resizing database memory map, old: {old}B, new: {new}B");
|
||||
|
||||
// Try handling the request again.
|
||||
continue 'retry;
|
||||
}
|
||||
|
||||
// Automatically resizing databases should not be returning a resize error.
|
||||
#[cfg(debug_assertions)]
|
||||
if !ConcreteEnv::MANUAL_RESIZE {
|
||||
assert!(
|
||||
!matches!(response, Err(RuntimeError::ResizeNeeded)),
|
||||
"auto-resizing database returned a ResizeNeeded error"
|
||||
);
|
||||
}
|
||||
|
||||
// Send the response back, whether if it's an `Ok` or `Err`.
|
||||
if let Err(e) = response_sender.send(response) {
|
||||
// TODO: use tracing.
|
||||
println!("database writer failed to send response: {e:?}");
|
||||
}
|
||||
|
||||
continue 'main;
|
||||
}
|
||||
|
||||
// Above retry loop should either:
|
||||
// - continue to the next ['main] loop or...
|
||||
// - ...retry until panic
|
||||
unreachable!();
|
||||
}
|
||||
//---------------------------------------------------------------------------------------------------- handle_bc_request
|
||||
/// Handle an incoming [`BlockchainWriteRequest`], returning a [`BlockchainResponse`].
|
||||
fn handle_blockchain_request(
|
||||
env: &ConcreteEnv,
|
||||
req: &BlockchainWriteRequest,
|
||||
) -> Result<BlockchainResponse, RuntimeError> {
|
||||
match req {
|
||||
BlockchainWriteRequest::WriteBlock(block) => write_block(env, block),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -216,7 +41,7 @@ impl DatabaseWriter {
|
|||
// Each function will return the [`Response`] that we
|
||||
// should send back to the caller in [`map_request()`].
|
||||
|
||||
/// [`BCWriteRequest::WriteBlock`].
|
||||
/// [`BlockchainWriteRequest::WriteBlock`].
|
||||
#[inline]
|
||||
fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseResult {
|
||||
let env_inner = env.env_inner();
|
||||
|
@ -230,7 +55,7 @@ fn write_block(env: &ConcreteEnv, block: &VerifiedBlockInformation) -> ResponseR
|
|||
match result {
|
||||
Ok(()) => {
|
||||
TxRw::commit(tx_rw)?;
|
||||
Ok(BCResponse::WriteBlockOk)
|
||||
Ok(BlockchainResponse::WriteBlockOk)
|
||||
}
|
||||
Err(e) => {
|
||||
// INVARIANT: ensure database atomicity by aborting
|
||||
|
|
|
@ -68,7 +68,7 @@ pub type BlockBlob = StorableVec<u8>;
|
|||
pub type BlockHash = [u8; 32];
|
||||
|
||||
/// A block's height.
|
||||
pub type BlockHeight = u64;
|
||||
pub type BlockHeight = usize;
|
||||
|
||||
/// A key image.
|
||||
pub type KeyImage = [u8; 32];
|
||||
|
|
22
storage/service/Cargo.toml
Normal file
22
storage/service/Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
|||
[package]
|
||||
name = "cuprate-database-service"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
description = "Cuprate's database service abstraction"
|
||||
license = "MIT"
|
||||
authors = ["Boog900"]
|
||||
repository = "https://github.com/Cuprate/cuprate/tree/main/storage/service"
|
||||
keywords = ["cuprate", "service", "database"]
|
||||
|
||||
[dependencies]
|
||||
cuprate-database = { path = "../database" }
|
||||
cuprate-helper = { path = "../../helper", features = ["fs", "thread", "map"] }
|
||||
|
||||
serde = { workspace = true, optional = true }
|
||||
rayon = { workspace = true }
|
||||
tower = { workspace = true }
|
||||
futures = { workspace = true }
|
||||
crossbeam = { workspace = true, features = ["std"] }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
7
storage/service/README.md
Normal file
7
storage/service/README.md
Normal file
|
@ -0,0 +1,7 @@
|
|||
# Cuprate's `tower::Service` database abstraction.
|
||||
|
||||
This crate contains the building blocks for creating a [`tower::Service`] interface to [`cuprate_blockchain`](https://doc.cuprate.org/cuprate_blockchain).
|
||||
|
||||
It is split into 2 `tower::Service`s:
|
||||
1. A [read service](crate::DatabaseReadService) which is backed by a [`rayon::ThreadPool`]
|
||||
1. A [write service](crate::DatabaseWriteHandle) which spawns a single thread to handle write requests
|
8
storage/service/src/lib.rs
Normal file
8
storage/service/src/lib.rs
Normal file
|
@ -0,0 +1,8 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
mod reader_threads;
|
||||
mod service;
|
||||
|
||||
pub use reader_threads::{init_thread_pool, ReaderThreads};
|
||||
|
||||
pub use service::{DatabaseReadService, DatabaseWriteHandle};
|
|
@ -1,23 +1,36 @@
|
|||
//! Database [`Env`](crate::Env) configuration.
|
||||
//! Reader thread-pool configuration and initiation.
|
||||
//!
|
||||
//! This module contains the main [`Config`]uration struct
|
||||
//! for the database [`Env`](crate::Env)ironment, and data
|
||||
//! structures related to any configuration setting.
|
||||
//! This module contains [`ReaderThreads`] which allow specifying the amount of
|
||||
//! reader threads for the [`rayon::ThreadPool`].
|
||||
//!
|
||||
//! These configurations are processed at runtime, meaning
|
||||
//! the `Env` can/will dynamically adjust its behavior
|
||||
//! based on these values.
|
||||
//! It also contains [`init_thread_pool`] which initiates the thread-pool.
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::num::NonZeroUsize;
|
||||
use std::{num::NonZeroUsize, sync::Arc};
|
||||
|
||||
use rayon::ThreadPool;
|
||||
#[cfg(feature = "serde")]
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- init_thread_pool
|
||||
/// Initialize the reader thread-pool backed by `rayon`.
|
||||
pub fn init_thread_pool(reader_threads: ReaderThreads) -> Arc<ThreadPool> {
|
||||
// How many reader threads to spawn?
|
||||
let reader_count = reader_threads.as_threads().get();
|
||||
|
||||
Arc::new(
|
||||
rayon::ThreadPoolBuilder::new()
|
||||
.num_threads(reader_count)
|
||||
.thread_name(|i| format!("{}::DatabaseReader({i})", module_path!()))
|
||||
.build()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- ReaderThreads
|
||||
/// Amount of database reader threads to spawn when using [`service`](crate::service).
|
||||
/// Amount of database reader threads to spawn.
|
||||
///
|
||||
/// This controls how many reader thread `service`'s
|
||||
/// This controls how many reader threads the [`DatabaseReadService`](crate::DatabaseReadService)
|
||||
/// thread-pool will spawn to receive and send requests/responses.
|
||||
///
|
||||
/// # Invariant
|
||||
|
@ -48,7 +61,7 @@ pub enum ReaderThreads {
|
|||
/// as such, it is equal to [`ReaderThreads::OnePerThread`].
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cuprate_blockchain::config::*;
|
||||
/// # use cuprate_database_service::*;
|
||||
/// let reader_threads = ReaderThreads::from(0_usize);
|
||||
/// assert!(matches!(reader_threads, ReaderThreads::OnePerThread));
|
||||
/// ```
|
||||
|
@ -80,7 +93,7 @@ pub enum ReaderThreads {
|
|||
/// non-zero, but not 1 thread, the minimum value 1 will be returned.
|
||||
///
|
||||
/// ```rust
|
||||
/// # use cuprate_blockchain::config::*;
|
||||
/// # use cuprate_database_service::ReaderThreads;
|
||||
/// assert_eq!(ReaderThreads::Percent(0.000000001).as_threads().get(), 1);
|
||||
/// ```
|
||||
Percent(f32),
|
||||
|
@ -96,7 +109,7 @@ impl ReaderThreads {
|
|||
///
|
||||
/// # Example
|
||||
/// ```rust
|
||||
/// use cuprate_blockchain::config::ReaderThreads as R;
|
||||
/// use cuprate_database_service::ReaderThreads as R;
|
||||
///
|
||||
/// let total_threads: std::num::NonZeroUsize =
|
||||
/// cuprate_helper::thread::threads();
|
5
storage/service/src/service.rs
Normal file
5
storage/service/src/service.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
mod read;
|
||||
mod write;
|
||||
|
||||
pub use read::DatabaseReadService;
|
||||
pub use write::DatabaseWriteHandle;
|
95
storage/service/src/service/read.rs
Normal file
95
storage/service/src/service/read.rs
Normal file
|
@ -0,0 +1,95 @@
|
|||
use std::{
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::channel::oneshot;
|
||||
use rayon::ThreadPool;
|
||||
use tower::Service;
|
||||
|
||||
use cuprate_database::{ConcreteEnv, RuntimeError};
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
|
||||
/// The [`rayon::ThreadPool`] service.
|
||||
///
|
||||
/// Uses an inner request handler and a rayon thread-pool to asynchronously handle requests.
|
||||
///
|
||||
/// - `Req` is the request type
|
||||
/// - `Res` is the response type
|
||||
pub struct DatabaseReadService<Req, Res> {
|
||||
/// Handle to the custom `rayon` DB reader thread-pool.
|
||||
///
|
||||
/// Requests are [`rayon::ThreadPool::spawn`]ed in this thread-pool,
|
||||
/// and responses are returned via a channel we (the caller) provide.
|
||||
pool: Arc<ThreadPool>,
|
||||
|
||||
/// The function used to handle request.
|
||||
inner_handler: Arc<dyn Fn(Req) -> Result<Res, RuntimeError> + Send + Sync + 'static>,
|
||||
}
|
||||
|
||||
// Deriving [`Clone`] means `Req` & `Res` need to be `Clone`, even if they aren't.
|
||||
impl<Req, Res> Clone for DatabaseReadService<Req, Res> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
pool: Arc::clone(&self.pool),
|
||||
inner_handler: Arc::clone(&self.inner_handler),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Res> DatabaseReadService<Req, Res>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Res: Send + 'static,
|
||||
{
|
||||
/// Creates the [`DatabaseReadService`] with the provided backing thread-pool.
|
||||
///
|
||||
/// Should be called _once_ per actual database, although nothing bad will happen, cloning the [`DatabaseReadService`]
|
||||
/// is the correct way to get multiple handles to the database.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
pub fn new(
|
||||
env: Arc<ConcreteEnv>,
|
||||
pool: Arc<ThreadPool>,
|
||||
req_handler: impl Fn(&ConcreteEnv, Req) -> Result<Res, RuntimeError> + Send + Sync + 'static,
|
||||
) -> Self {
|
||||
let inner_handler = Arc::new(move |req| req_handler(&env, req));
|
||||
|
||||
Self {
|
||||
pool,
|
||||
inner_handler,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Res> Service<Req> for DatabaseReadService<Req, Res>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Res: Send + 'static,
|
||||
{
|
||||
type Response = Res;
|
||||
type Error = RuntimeError;
|
||||
type Future = InfallibleOneshotReceiver<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: Req) -> Self::Future {
|
||||
// Response channel we `.await` on.
|
||||
let (response_sender, receiver) = oneshot::channel();
|
||||
|
||||
let handler = Arc::clone(&self.inner_handler);
|
||||
|
||||
// Spawn the request in the rayon DB thread-pool.
|
||||
//
|
||||
// Note that this uses `self.pool` instead of `rayon::spawn`
|
||||
// such that any `rayon` parallel code that runs within
|
||||
// the passed closure uses the same `rayon` threadpool.
|
||||
self.pool.spawn(move || {
|
||||
drop(response_sender.send(handler(req)));
|
||||
});
|
||||
|
||||
InfallibleOneshotReceiver::from(receiver)
|
||||
}
|
||||
}
|
178
storage/service/src/service/write.rs
Normal file
178
storage/service/src/service/write.rs
Normal file
|
@ -0,0 +1,178 @@
|
|||
use std::{
|
||||
fmt::Debug,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use futures::channel::oneshot;
|
||||
|
||||
use cuprate_database::{ConcreteEnv, Env, RuntimeError};
|
||||
use cuprate_helper::asynch::InfallibleOneshotReceiver;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Constants
|
||||
/// Name of the writer thread.
|
||||
const WRITER_THREAD_NAME: &str = concat!(module_path!(), "::DatabaseWriter");
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- DatabaseWriteHandle
|
||||
/// Write handle to the database.
|
||||
///
|
||||
/// This is handle that allows `async`hronously writing to the database.
|
||||
///
|
||||
/// Calling [`tower::Service::call`] with a [`DatabaseWriteHandle`]
|
||||
/// will return an `async`hronous channel that can be `.await`ed upon
|
||||
/// to receive the corresponding response.
|
||||
#[derive(Debug)]
|
||||
pub struct DatabaseWriteHandle<Req, Res> {
|
||||
/// Sender channel to the database write thread-pool.
|
||||
///
|
||||
/// We provide the response channel for the thread-pool.
|
||||
pub(super) sender:
|
||||
crossbeam::channel::Sender<(Req, oneshot::Sender<Result<Res, RuntimeError>>)>,
|
||||
}
|
||||
|
||||
impl<Req, Res> DatabaseWriteHandle<Req, Res>
|
||||
where
|
||||
Req: Send + 'static,
|
||||
Res: Debug + Send + 'static,
|
||||
{
|
||||
/// Initialize the single `DatabaseWriter` thread.
|
||||
#[cold]
|
||||
#[inline(never)] // Only called once.
|
||||
pub fn init(
|
||||
env: Arc<ConcreteEnv>,
|
||||
inner_handler: impl Fn(&ConcreteEnv, &Req) -> Result<Res, RuntimeError> + Send + 'static,
|
||||
) -> Self {
|
||||
// Initialize `Request/Response` channels.
|
||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||
|
||||
// Spawn the writer.
|
||||
std::thread::Builder::new()
|
||||
.name(WRITER_THREAD_NAME.into())
|
||||
.spawn(move || database_writer(&env, &receiver, inner_handler))
|
||||
.unwrap();
|
||||
|
||||
Self { sender }
|
||||
}
|
||||
}
|
||||
|
||||
impl<Req, Res> tower::Service<Req> for DatabaseWriteHandle<Req, Res> {
|
||||
type Response = Res;
|
||||
type Error = RuntimeError;
|
||||
type Future = InfallibleOneshotReceiver<Result<Res, RuntimeError>>;
|
||||
|
||||
#[inline]
|
||||
fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn call(&mut self, request: Req) -> Self::Future {
|
||||
// Response channel we `.await` on.
|
||||
let (response_sender, receiver) = oneshot::channel();
|
||||
|
||||
// Send the write request.
|
||||
self.sender.send((request, response_sender)).unwrap();
|
||||
|
||||
InfallibleOneshotReceiver::from(receiver)
|
||||
}
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- database_writer
|
||||
/// The main function of the writer thread.
|
||||
fn database_writer<Req, Res>(
|
||||
env: &ConcreteEnv,
|
||||
receiver: &crossbeam::channel::Receiver<(Req, oneshot::Sender<Result<Res, RuntimeError>>)>,
|
||||
inner_handler: impl Fn(&ConcreteEnv, &Req) -> Result<Res, RuntimeError>,
|
||||
) where
|
||||
Req: Send + 'static,
|
||||
Res: Debug + Send + 'static,
|
||||
{
|
||||
// 1. Hang on request channel
|
||||
// 2. Map request to some database function
|
||||
// 3. Execute that function, get the result
|
||||
// 4. Return the result via channel
|
||||
'main: loop {
|
||||
let Ok((request, response_sender)) = receiver.recv() else {
|
||||
// If this receive errors, it means that the channel is empty
|
||||
// and disconnected, meaning the other side (all senders) have
|
||||
// been dropped. This means "shutdown", and we return here to
|
||||
// exit the thread.
|
||||
//
|
||||
// Since the channel is empty, it means we've also processed
|
||||
// all requests. Since it is disconnected, it means future
|
||||
// ones cannot come in.
|
||||
return;
|
||||
};
|
||||
|
||||
/// How many times should we retry handling the request on resize errors?
|
||||
///
|
||||
/// This is 1 on automatically resizing databases, meaning there is only 1 iteration.
|
||||
const REQUEST_RETRY_LIMIT: usize = if ConcreteEnv::MANUAL_RESIZE { 3 } else { 1 };
|
||||
|
||||
// Map [`Request`]'s to specific database functions.
|
||||
//
|
||||
// Both will:
|
||||
// 1. Map the request to a function
|
||||
// 2. Call the function
|
||||
// 3. (manual resize only) If resize is needed, resize and retry
|
||||
// 4. (manual resize only) Redo step {1, 2}
|
||||
// 5. Send the function's `Result` back to the requester
|
||||
//
|
||||
// FIXME: there's probably a more elegant way
|
||||
// to represent this retry logic with recursive
|
||||
// functions instead of a loop.
|
||||
'retry: for retry in 0..REQUEST_RETRY_LIMIT {
|
||||
// FIXME: will there be more than 1 write request?
|
||||
// this won't have to be an enum.
|
||||
let response = inner_handler(env, &request);
|
||||
|
||||
// If the database needs to resize, do so.
|
||||
if ConcreteEnv::MANUAL_RESIZE && matches!(response, Err(RuntimeError::ResizeNeeded)) {
|
||||
// If this is the last iteration of the outer `for` loop and we
|
||||
// encounter a resize error _again_, it means something is wrong.
|
||||
assert_ne!(
|
||||
retry, REQUEST_RETRY_LIMIT,
|
||||
"database resize failed maximum of {REQUEST_RETRY_LIMIT} times"
|
||||
);
|
||||
|
||||
// Resize the map, and retry the request handling loop.
|
||||
//
|
||||
// FIXME:
|
||||
// We could pass in custom resizes to account for
|
||||
// batches, i.e., we're about to add ~5GB of data,
|
||||
// add that much instead of the default 1GB.
|
||||
// <https://github.com/monero-project/monero/blob/059028a30a8ae9752338a7897329fe8012a310d5/src/blockchain_db/lmdb/db_lmdb.cpp#L665-L695>
|
||||
let old = env.current_map_size();
|
||||
let new = env.resize_map(None);
|
||||
|
||||
// TODO: use tracing.
|
||||
println!("resizing database memory map, old: {old}B, new: {new}B");
|
||||
|
||||
// Try handling the request again.
|
||||
continue 'retry;
|
||||
}
|
||||
|
||||
// Automatically resizing databases should not be returning a resize error.
|
||||
#[cfg(debug_assertions)]
|
||||
if !ConcreteEnv::MANUAL_RESIZE {
|
||||
assert!(
|
||||
!matches!(response, Err(RuntimeError::ResizeNeeded)),
|
||||
"auto-resizing database returned a ResizeNeeded error"
|
||||
);
|
||||
}
|
||||
|
||||
// Send the response back, whether if it's an `Ok` or `Err`.
|
||||
if let Err(e) = response_sender.send(response) {
|
||||
// TODO: use tracing.
|
||||
println!("database writer failed to send response: {e:?}");
|
||||
}
|
||||
|
||||
continue 'main;
|
||||
}
|
||||
|
||||
// Above retry loop should either:
|
||||
// - continue to the next ['main] loop or...
|
||||
// - ...retry until panic
|
||||
unreachable!();
|
||||
}
|
||||
}
|
|
@ -6,24 +6,26 @@ license = "MIT"
|
|||
authors = ["Boog900", "hinto-janai"]
|
||||
|
||||
[dependencies]
|
||||
cuprate-types = { path = "../types" }
|
||||
cuprate-helper = { path = "../helper", features = ["map"] }
|
||||
cuprate-wire = { path = "../net/wire" }
|
||||
cuprate-p2p-core = { path = "../p2p/p2p-core", features = ["borsh"] }
|
||||
cuprate-types = { path = "../types" }
|
||||
cuprate-helper = { path = "../helper", features = ["map"] }
|
||||
cuprate-wire = { path = "../net/wire" }
|
||||
cuprate-p2p-core = { path = "../p2p/p2p-core", features = ["borsh"] }
|
||||
|
||||
hex = { workspace = true }
|
||||
hex-literal = { workspace = true }
|
||||
monero-serai = { workspace = true, features = ["std", "http-rpc"] }
|
||||
futures = { workspace = true, features = ["std"] }
|
||||
async-trait = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-util = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
bytes = { workspace = true, features = ["std"] }
|
||||
tempfile = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
borsh = { workspace = true, features = ["derive"]}
|
||||
hex = { workspace = true }
|
||||
hex-literal = { workspace = true }
|
||||
monero-serai = { workspace = true, features = ["std"] }
|
||||
monero-simple-request-rpc = { workspace = true }
|
||||
monero-rpc = { workspace = true }
|
||||
futures = { workspace = true, features = ["std"] }
|
||||
async-trait = { workspace = true }
|
||||
tokio = { workspace = true, features = ["full"] }
|
||||
tokio-util = { workspace = true }
|
||||
serde = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
bytes = { workspace = true, features = ["std"] }
|
||||
tempfile = { workspace = true }
|
||||
paste = { workspace = true }
|
||||
borsh = { workspace = true, features = ["derive"]}
|
||||
|
||||
[dev-dependencies]
|
||||
hex = { workspace = true }
|
||||
|
|
|
@ -34,12 +34,12 @@ macro_rules! const_block_blob {
|
|||
#[doc = ""]
|
||||
#[doc = concat!("let block = Block::read(&mut ", stringify!($name), ").unwrap();")]
|
||||
#[doc = ""]
|
||||
#[doc = concat!("assert_eq!(block.header.major_version, ", $major_version, ");")]
|
||||
#[doc = concat!("assert_eq!(block.header.minor_version, ", $minor_version, ");")]
|
||||
#[doc = concat!("assert_eq!(block.header.hardfork_version, ", $major_version, ");")]
|
||||
#[doc = concat!("assert_eq!(block.header.hardfork_signal, ", $minor_version, ");")]
|
||||
#[doc = concat!("assert_eq!(block.header.timestamp, ", $timestamp, ");")]
|
||||
#[doc = concat!("assert_eq!(block.header.nonce, ", $nonce, ");")]
|
||||
#[doc = concat!("assert!(matches!(block.miner_tx.prefix.inputs[0], Input::Gen(", $height, ")));")]
|
||||
#[doc = concat!("assert_eq!(block.txs.len(), ", $tx_len, ");")]
|
||||
#[doc = concat!("assert!(matches!(block.miner_transaction.prefix().inputs[0], Input::Gen(", $height, ")));")]
|
||||
#[doc = concat!("assert_eq!(block.transactions.len(), ", $tx_len, ");")]
|
||||
#[doc = concat!("assert_eq!(hex::encode(block.hash()), \"", $hash, "\")")]
|
||||
/// ```
|
||||
pub const $name: &[u8] = include_bytes!($data_path);
|
||||
|
@ -107,7 +107,6 @@ macro_rules! const_tx_blob {
|
|||
timelock: $timelock:expr, // Transaction's timelock (use the real type `Timelock`)
|
||||
input_len: $input_len:literal, // Amount of inputs
|
||||
output_len: $output_len:literal, // Amount of outputs
|
||||
signatures_len: $signatures_len:literal, // Amount of signatures
|
||||
) => {
|
||||
#[doc = concat!("Transaction with hash `", $hash, "`.")]
|
||||
///
|
||||
|
@ -117,11 +116,10 @@ macro_rules! const_tx_blob {
|
|||
#[doc = ""]
|
||||
#[doc = concat!("let tx = Transaction::read(&mut ", stringify!($name), ").unwrap();")]
|
||||
#[doc = ""]
|
||||
#[doc = concat!("assert_eq!(tx.prefix.version, ", $version, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.prefix.timelock, ", stringify!($timelock), ");")]
|
||||
#[doc = concat!("assert_eq!(tx.prefix.inputs.len(), ", $input_len, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.prefix.outputs.len(), ", $output_len, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.signatures.len(), ", $signatures_len, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.version(), ", $version, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.prefix().additional_timelock, ", stringify!($timelock), ");")]
|
||||
#[doc = concat!("assert_eq!(tx.prefix().inputs.len(), ", $input_len, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.prefix().outputs.len(), ", $output_len, ");")]
|
||||
#[doc = concat!("assert_eq!(hex::encode(tx.hash()), \"", $hash, "\")")]
|
||||
/// ```
|
||||
pub const $name: &[u8] = include_bytes!($data_path);
|
||||
|
@ -136,7 +134,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::Block(100_081),
|
||||
input_len: 1,
|
||||
output_len: 5,
|
||||
signatures_len: 0,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -147,7 +144,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 19,
|
||||
output_len: 61,
|
||||
signatures_len: 19,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -158,7 +154,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 46,
|
||||
output_len: 46,
|
||||
signatures_len: 46,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -169,7 +164,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 1,
|
||||
output_len: 2,
|
||||
signatures_len: 0,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -180,7 +174,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 1,
|
||||
output_len: 2,
|
||||
signatures_len: 0,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -191,7 +184,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 2,
|
||||
output_len: 2,
|
||||
signatures_len: 0,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -202,7 +194,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 2,
|
||||
output_len: 5,
|
||||
signatures_len: 2,
|
||||
}
|
||||
|
||||
const_tx_blob! {
|
||||
|
@ -213,7 +204,6 @@ const_tx_blob! {
|
|||
timelock: Timelock::None,
|
||||
input_len: 2,
|
||||
output_len: 2,
|
||||
signatures_len: 0,
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Tests
|
||||
|
|
|
@ -8,11 +8,11 @@
|
|||
//---------------------------------------------------------------------------------------------------- Import
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use hex_literal::hex;
|
||||
use monero_serai::{block::Block, transaction::Transaction};
|
||||
|
||||
use cuprate_helper::map::combine_low_high_bits_to_u128;
|
||||
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
|
||||
use hex_literal::hex;
|
||||
use monero_serai::transaction::Input;
|
||||
use monero_serai::{block::Block, transaction::Transaction};
|
||||
|
||||
use crate::data::constants::{
|
||||
BLOCK_43BD1F, BLOCK_5ECB7E, BLOCK_F91043, TX_2180A8, TX_3BC7FF, TX_84D48D, TX_9E3F73,
|
||||
|
@ -31,7 +31,7 @@ use crate::data::constants::{
|
|||
struct VerifiedBlockMap {
|
||||
block_blob: &'static [u8],
|
||||
pow_hash: [u8; 32],
|
||||
height: u64,
|
||||
height: usize,
|
||||
generated_coins: u64,
|
||||
weight: usize,
|
||||
long_term_weight: usize,
|
||||
|
@ -68,11 +68,11 @@ impl VerifiedBlockMap {
|
|||
|
||||
assert_eq!(
|
||||
txs.len(),
|
||||
block.txs.len(),
|
||||
block.transactions.len(),
|
||||
"(deserialized txs).len() != (txs hashes in block).len()"
|
||||
);
|
||||
|
||||
for (tx, tx_hash_in_block) in txs.iter().zip(&block.txs) {
|
||||
for (tx, tx_hash_in_block) in txs.iter().zip(&block.transactions) {
|
||||
assert_eq!(
|
||||
&tx.tx_hash, tx_hash_in_block,
|
||||
"deserialized tx hash is not the same as the one in the parent block"
|
||||
|
@ -103,13 +103,43 @@ fn to_tx_verification_data(tx_blob: impl AsRef<[u8]>) -> VerifiedTransactionInfo
|
|||
let tx = Transaction::read(&mut tx_blob.as_slice()).unwrap();
|
||||
VerifiedTransactionInformation {
|
||||
tx_weight: tx.weight(),
|
||||
fee: tx.rct_signatures.base.fee,
|
||||
fee: tx_fee(&tx),
|
||||
tx_hash: tx.hash(),
|
||||
tx_blob,
|
||||
tx,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculates the fee of the [`Transaction`].
|
||||
///
|
||||
/// # Panics
|
||||
/// This will panic if the inputs overflow or the transaction outputs too much.
|
||||
pub fn tx_fee(tx: &Transaction) -> u64 {
|
||||
let mut fee = 0_u64;
|
||||
|
||||
match &tx {
|
||||
Transaction::V1 { prefix, .. } => {
|
||||
for input in &prefix.inputs {
|
||||
match input {
|
||||
Input::Gen(_) => return 0,
|
||||
Input::ToKey { amount, .. } => {
|
||||
fee = fee.checked_add(amount.unwrap_or(0)).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for output in &prefix.outputs {
|
||||
fee.checked_sub(output.amount.unwrap_or(0)).unwrap();
|
||||
}
|
||||
}
|
||||
Transaction::V2 { proofs, .. } => {
|
||||
fee = proofs.as_ref().unwrap().base.fee;
|
||||
}
|
||||
};
|
||||
|
||||
fee
|
||||
}
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Blocks
|
||||
/// Generate a block accessor function with this signature:
|
||||
/// `fn() -> &'static VerifiedBlockInformation`
|
||||
|
@ -255,7 +285,6 @@ macro_rules! transaction_verification_data_fn {
|
|||
#[doc = concat!("assert_eq!(tx.tx_blob, ", stringify!($tx_blob), ");")]
|
||||
#[doc = concat!("assert_eq!(tx.tx_weight, ", $weight, ");")]
|
||||
#[doc = concat!("assert_eq!(tx.tx_hash, hex!(\"", $hash, "\"));")]
|
||||
#[doc = "assert_eq!(tx.fee, tx.tx.rct_signatures.base.fee);"]
|
||||
/// ```
|
||||
pub fn $fn_name() -> &'static VerifiedTransactionInformation {
|
||||
static TX: OnceLock<VerifiedTransactionInformation> = OnceLock::new();
|
||||
|
|
|
@ -32,4 +32,6 @@ pub use constants::{
|
|||
};
|
||||
|
||||
mod free;
|
||||
pub use free::{block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3};
|
||||
pub use free::{
|
||||
block_v16_tx0, block_v1_tx2, block_v9_tx3, tx_fee, tx_v1_sig0, tx_v1_sig2, tx_v2_rct3,
|
||||
};
|
||||
|
|
|
@ -5,13 +5,14 @@ use serde::Deserialize;
|
|||
use serde_json::json;
|
||||
use tokio::task::spawn_blocking;
|
||||
|
||||
use monero_serai::{
|
||||
block::Block,
|
||||
rpc::{HttpRpc, Rpc},
|
||||
};
|
||||
use monero_rpc::Rpc;
|
||||
use monero_serai::block::Block;
|
||||
use monero_simple_request_rpc::SimpleRequestRpc;
|
||||
|
||||
use cuprate_types::{VerifiedBlockInformation, VerifiedTransactionInformation};
|
||||
|
||||
use crate::data::tx_fee;
|
||||
|
||||
//---------------------------------------------------------------------------------------------------- Constants
|
||||
/// The default URL used for Monero RPC connections.
|
||||
pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081";
|
||||
|
@ -20,7 +21,7 @@ pub const LOCALHOST_RPC_URL: &str = "http://127.0.0.1:18081";
|
|||
/// An HTTP RPC client for Monero.
|
||||
pub struct HttpRpcClient {
|
||||
address: String,
|
||||
rpc: Rpc<HttpRpc>,
|
||||
rpc: SimpleRequestRpc,
|
||||
}
|
||||
|
||||
impl HttpRpcClient {
|
||||
|
@ -40,7 +41,7 @@ impl HttpRpcClient {
|
|||
let address = address.unwrap_or_else(|| LOCALHOST_RPC_URL.to_string());
|
||||
|
||||
Self {
|
||||
rpc: HttpRpc::new(address.clone()).await.unwrap(),
|
||||
rpc: SimpleRequestRpc::new(address.clone()).await.unwrap(),
|
||||
address,
|
||||
}
|
||||
}
|
||||
|
@ -53,7 +54,7 @@ impl HttpRpcClient {
|
|||
|
||||
/// Access to the inner RPC client for other usage.
|
||||
#[allow(dead_code)]
|
||||
const fn rpc(&self) -> &Rpc<HttpRpc> {
|
||||
const fn rpc(&self) -> &SimpleRequestRpc {
|
||||
&self.rpc
|
||||
}
|
||||
|
||||
|
@ -62,7 +63,7 @@ impl HttpRpcClient {
|
|||
/// # Panics
|
||||
/// This function will panic at any error point, e.g.,
|
||||
/// if the node cannot be connected to, if deserialization fails, etc.
|
||||
pub async fn get_verified_block_information(&self, height: u64) -> VerifiedBlockInformation {
|
||||
pub async fn get_verified_block_information(&self, height: usize) -> VerifiedBlockInformation {
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Result {
|
||||
blob: String,
|
||||
|
@ -75,7 +76,7 @@ impl HttpRpcClient {
|
|||
long_term_weight: usize,
|
||||
cumulative_difficulty: u128,
|
||||
hash: String,
|
||||
height: u64,
|
||||
height: usize,
|
||||
pow_hash: String,
|
||||
reward: u64, // generated_coins + total_tx_fees
|
||||
}
|
||||
|
@ -111,7 +112,7 @@ impl HttpRpcClient {
|
|||
.unwrap();
|
||||
|
||||
let txs: Vec<VerifiedTransactionInformation> = self
|
||||
.get_transaction_verification_data(&block.txs)
|
||||
.get_transaction_verification_data(&block.transactions)
|
||||
.await
|
||||
.collect();
|
||||
|
||||
|
@ -124,8 +125,8 @@ impl HttpRpcClient {
|
|||
|
||||
let total_tx_fees = txs.iter().map(|tx| tx.fee).sum::<u64>();
|
||||
let generated_coins = block
|
||||
.miner_tx
|
||||
.prefix
|
||||
.miner_transaction
|
||||
.prefix()
|
||||
.outputs
|
||||
.iter()
|
||||
.map(|output| output.amount.expect("miner_tx amount was None"))
|
||||
|
@ -173,7 +174,7 @@ impl HttpRpcClient {
|
|||
tx_blob: tx.serialize(),
|
||||
tx_weight: tx.weight(),
|
||||
tx_hash,
|
||||
fee: tx.rct_signatures.base.fee,
|
||||
fee: tx_fee(&tx),
|
||||
tx,
|
||||
}
|
||||
})
|
||||
|
@ -199,7 +200,7 @@ mod tests {
|
|||
#[allow(clippy::too_many_arguments)]
|
||||
async fn assert_eq(
|
||||
rpc: &HttpRpcClient,
|
||||
height: u64,
|
||||
height: usize,
|
||||
block_hash: [u8; 32],
|
||||
pow_hash: [u8; 32],
|
||||
generated_coins: u64,
|
||||
|
|
|
@ -22,5 +22,6 @@ bytes = { workspace = true }
|
|||
curve25519-dalek = { workspace = true }
|
||||
monero-serai = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"], optional = true }
|
||||
borsh = { workspace = true, optional = true }
|
||||
|
||||
[dev-dependencies]
|
|
@ -1,4 +1,4 @@
|
|||
//! Database [`BCReadRequest`]s, [`BCWriteRequest`]s, and [`BCResponse`]s.
|
||||
//! Database [`BlockchainReadRequest`]s, [`BlockchainWriteRequest`]s, and [`BlockchainResponse`]s.
|
||||
//!
|
||||
//! Tests that assert particular requests lead to particular
|
||||
//! responses are also tested in Cuprate's blockchain database crate.
|
||||
|
@ -14,23 +14,23 @@ use crate::types::{Chain, ExtendedBlockHeader, OutputOnChain, VerifiedBlockInfor
|
|||
//---------------------------------------------------------------------------------------------------- ReadRequest
|
||||
/// A read request to the blockchain database.
|
||||
///
|
||||
/// This pairs with [`BCResponse`], where each variant here
|
||||
/// matches in name with a [`BCResponse`] variant. For example,
|
||||
/// the proper response for a [`BCReadRequest::BlockHash`]
|
||||
/// would be a [`BCResponse::BlockHash`].
|
||||
/// This pairs with [`BlockchainResponse`], where each variant here
|
||||
/// matches in name with a [`BlockchainResponse`] variant. For example,
|
||||
/// the proper response for a [`BlockchainReadRequest::BlockHash`]
|
||||
/// would be a [`BlockchainResponse::BlockHash`].
|
||||
///
|
||||
/// See `Response` for the expected responses per `Request`.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BCReadRequest {
|
||||
pub enum BlockchainReadRequest {
|
||||
/// Request a block's extended header.
|
||||
///
|
||||
/// The input is the block's height.
|
||||
BlockExtendedHeader(u64),
|
||||
BlockExtendedHeader(usize),
|
||||
|
||||
/// Request a block's hash.
|
||||
///
|
||||
/// The input is the block's height and the chain it is on.
|
||||
BlockHash(u64, Chain),
|
||||
BlockHash(usize, Chain),
|
||||
|
||||
/// Request to check if we have a block and which [`Chain`] it is on.
|
||||
///
|
||||
|
@ -45,7 +45,7 @@ pub enum BCReadRequest {
|
|||
/// Request a range of block extended headers.
|
||||
///
|
||||
/// The input is a range of block heights.
|
||||
BlockExtendedHeaderInRange(Range<u64>, Chain),
|
||||
BlockExtendedHeaderInRange(Range<usize>, Chain),
|
||||
|
||||
/// Request the current chain height.
|
||||
///
|
||||
|
@ -53,7 +53,7 @@ pub enum BCReadRequest {
|
|||
ChainHeight,
|
||||
|
||||
/// Request the total amount of generated coins (atomic units) at this height.
|
||||
GeneratedCoins(u64),
|
||||
GeneratedCoins(usize),
|
||||
|
||||
/// Request data for multiple outputs.
|
||||
///
|
||||
|
@ -104,10 +104,10 @@ pub enum BCReadRequest {
|
|||
/// A write request to the blockchain database.
|
||||
///
|
||||
/// There is currently only 1 write request to the database,
|
||||
/// as such, the only valid [`BCResponse`] to this request is
|
||||
/// the proper response for a [`BCResponse::WriteBlockOk`].
|
||||
/// as such, the only valid [`BlockchainResponse`] to this request is
|
||||
/// the proper response for a [`BlockchainResponse::WriteBlockOk`].
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BCWriteRequest {
|
||||
pub enum BlockchainWriteRequest {
|
||||
/// Request that a block be written to the database.
|
||||
///
|
||||
/// Input is an already verified block.
|
||||
|
@ -119,60 +119,60 @@ pub enum BCWriteRequest {
|
|||
///
|
||||
/// These are the data types returned when using sending a `Request`.
|
||||
///
|
||||
/// This pairs with [`BCReadRequest`] and [`BCWriteRequest`],
|
||||
/// This pairs with [`BlockchainReadRequest`] and [`BlockchainWriteRequest`],
|
||||
/// see those two for more info.
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum BCResponse {
|
||||
pub enum BlockchainResponse {
|
||||
//------------------------------------------------------ Reads
|
||||
/// Response to [`BCReadRequest::BlockExtendedHeader`].
|
||||
/// Response to [`BlockchainReadRequest::BlockExtendedHeader`].
|
||||
///
|
||||
/// Inner value is the extended headed of the requested block.
|
||||
BlockExtendedHeader(ExtendedBlockHeader),
|
||||
|
||||
/// Response to [`BCReadRequest::BlockHash`].
|
||||
/// Response to [`BlockchainReadRequest::BlockHash`].
|
||||
///
|
||||
/// Inner value is the hash of the requested block.
|
||||
BlockHash([u8; 32]),
|
||||
|
||||
/// Response to [`BCReadRequest::FindBlock`].
|
||||
/// Response to [`BlockchainReadRequest::FindBlock`].
|
||||
///
|
||||
/// Inner value is the chain and height of the block if found.
|
||||
FindBlock(Option<(Chain, u64)>),
|
||||
FindBlock(Option<(Chain, usize)>),
|
||||
|
||||
/// Response to [`BCReadRequest::FilterUnknownHashes`].
|
||||
/// Response to [`BlockchainReadRequest::FilterUnknownHashes`].
|
||||
///
|
||||
/// Inner value is the list of hashes that were in the main chain.
|
||||
FilterUnknownHashes(HashSet<[u8; 32]>),
|
||||
|
||||
/// Response to [`BCReadRequest::BlockExtendedHeaderInRange`].
|
||||
/// Response to [`BlockchainReadRequest::BlockExtendedHeaderInRange`].
|
||||
///
|
||||
/// Inner value is the list of extended header(s) of the requested block(s).
|
||||
BlockExtendedHeaderInRange(Vec<ExtendedBlockHeader>),
|
||||
|
||||
/// Response to [`BCReadRequest::ChainHeight`].
|
||||
/// Response to [`BlockchainReadRequest::ChainHeight`].
|
||||
///
|
||||
/// Inner value is the chain height, and the top block's hash.
|
||||
ChainHeight(u64, [u8; 32]),
|
||||
ChainHeight(usize, [u8; 32]),
|
||||
|
||||
/// Response to [`BCReadRequest::GeneratedCoins`].
|
||||
/// Response to [`BlockchainReadRequest::GeneratedCoins`].
|
||||
///
|
||||
/// Inner value is the total amount of generated coins up to and including the chosen height, in atomic units.
|
||||
GeneratedCoins(u64),
|
||||
|
||||
/// Response to [`BCReadRequest::Outputs`].
|
||||
/// Response to [`BlockchainReadRequest::Outputs`].
|
||||
///
|
||||
/// Inner value is all the outputs requested,
|
||||
/// associated with their amount and amount index.
|
||||
Outputs(HashMap<u64, HashMap<u64, OutputOnChain>>),
|
||||
|
||||
/// Response to [`BCReadRequest::NumberOutputsWithAmount`].
|
||||
/// Response to [`BlockchainReadRequest::NumberOutputsWithAmount`].
|
||||
///
|
||||
/// Inner value is a `HashMap` of all the outputs requested where:
|
||||
/// - Key = output amount
|
||||
/// - Value = count of outputs with the same amount
|
||||
NumberOutputsWithAmount(HashMap<u64, usize>),
|
||||
|
||||
/// Response to [`BCReadRequest::KeyImagesSpent`].
|
||||
/// Response to [`BlockchainReadRequest::KeyImagesSpent`].
|
||||
///
|
||||
/// The inner value is `true` if _any_ of the key images
|
||||
/// were spent (existed in the database already).
|
||||
|
@ -180,7 +180,7 @@ pub enum BCResponse {
|
|||
/// The inner value is `false` if _none_ of the key images were spent.
|
||||
KeyImagesSpent(bool),
|
||||
|
||||
/// Response to [`BCReadRequest::CompactChainHistory`].
|
||||
/// Response to [`BlockchainReadRequest::CompactChainHistory`].
|
||||
CompactChainHistory {
|
||||
/// A list of blocks IDs in our chain, starting with the most recent block, all the way to the genesis block.
|
||||
///
|
||||
|
@ -190,15 +190,15 @@ pub enum BCResponse {
|
|||
cumulative_difficulty: u128,
|
||||
},
|
||||
|
||||
/// The response for [`BCReadRequest::FindFirstUnknown`].
|
||||
/// The response for [`BlockchainReadRequest::FindFirstUnknown`].
|
||||
///
|
||||
/// Contains the index of the first unknown block and its expected height.
|
||||
///
|
||||
/// This will be [`None`] if all blocks were known.
|
||||
FindFirstUnknown(Option<(usize, u64)>),
|
||||
FindFirstUnknown(Option<(usize, usize)>),
|
||||
|
||||
//------------------------------------------------------ Writes
|
||||
/// Response to [`BCWriteRequest::WriteBlock`].
|
||||
/// Response to [`BlockchainWriteRequest::WriteBlock`].
|
||||
///
|
||||
/// This response indicates that the requested block has
|
||||
/// successfully been written to the database without error.
|
||||
|
|
|
@ -17,13 +17,13 @@ pub struct ExtendedBlockHeader {
|
|||
///
|
||||
/// This can also be represented with `cuprate_consensus::HardFork`.
|
||||
///
|
||||
/// This is the same value as [`monero_serai::block::BlockHeader::major_version`].
|
||||
/// This is the same value as [`monero_serai::block::BlockHeader::hardfork_version`].
|
||||
pub version: u8,
|
||||
/// The block's hard-fork vote.
|
||||
///
|
||||
/// This can also be represented with `cuprate_consensus::HardFork`.
|
||||
///
|
||||
/// This is the same value as [`monero_serai::block::BlockHeader::minor_version`].
|
||||
/// This is the same value as [`monero_serai::block::BlockHeader::hardfork_signal`].
|
||||
pub vote: u8,
|
||||
/// The UNIX time at which the block was mined.
|
||||
pub timestamp: u64,
|
||||
|
@ -72,7 +72,7 @@ pub struct VerifiedBlockInformation {
|
|||
///
|
||||
/// [`Block::serialize`].
|
||||
pub block_blob: Vec<u8>,
|
||||
/// All the transactions in the block, excluding the [`Block::miner_tx`].
|
||||
/// All the transactions in the block, excluding the [`Block::miner_transaction`].
|
||||
pub txs: Vec<VerifiedTransactionInformation>,
|
||||
/// The block's hash.
|
||||
///
|
||||
|
@ -81,7 +81,7 @@ pub struct VerifiedBlockInformation {
|
|||
/// The block's proof-of-work hash.
|
||||
pub pow_hash: [u8; 32],
|
||||
/// The block's height.
|
||||
pub height: u64,
|
||||
pub height: usize,
|
||||
/// The amount of generated coins (atomic units) in this block.
|
||||
pub generated_coins: u64,
|
||||
/// The adjusted block size, in bytes.
|
||||
|
@ -119,7 +119,7 @@ pub struct AltBlockInformation {
|
|||
///
|
||||
/// [`Block::serialize`].
|
||||
pub block_blob: Vec<u8>,
|
||||
/// All the transactions in the block, excluding the [`Block::miner_tx`].
|
||||
/// All the transactions in the block, excluding the [`Block::miner_transaction`].
|
||||
pub txs: Vec<VerifiedTransactionInformation>,
|
||||
/// The block's hash.
|
||||
///
|
||||
|
@ -128,7 +128,7 @@ pub struct AltBlockInformation {
|
|||
/// The block's proof-of-work hash.
|
||||
pub pow_hash: [u8; 32],
|
||||
/// The block's height.
|
||||
pub height: u64,
|
||||
pub height: usize,
|
||||
/// The adjusted block size, in bytes.
|
||||
pub weight: usize,
|
||||
/// The long term block weight, which is the weight factored in with previous block weights.
|
||||
|
@ -144,7 +144,7 @@ pub struct AltBlockInformation {
|
|||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct OutputOnChain {
|
||||
/// The block height this output belongs to.
|
||||
pub height: u64,
|
||||
pub height: usize,
|
||||
/// The timelock of this output, if any.
|
||||
pub time_lock: Timelock,
|
||||
/// The public key of this output, if any.
|
||||
|
|
Loading…
Reference in a new issue