├── .env.example ├── .github └── workflows │ ├── build.yaml │ ├── coverage.yaml │ ├── lint.yaml │ └── test.yaml ├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── README.md ├── doc ├── blockchain_structure.png └── rest_api.postman_collection.json ├── scripts └── coverage_report.sh ├── src ├── api.rs ├── main.rs ├── miner.rs ├── model.rs ├── model │ ├── account_balance_map.rs │ ├── address.rs │ ├── block.rs │ ├── blockchain.rs │ ├── transaction.rs │ └── transaction_pool.rs ├── peer.rs ├── util.rs └── util │ ├── config.rs │ ├── context.rs │ ├── execution.rs │ ├── logger.rs │ └── termination.rs └── tests ├── api_test.rs ├── common ├── api.rs ├── mod.rs └── server.rs └── peer_test.rs /.env.example: -------------------------------------------------------------------------------- 1 | # This is only an example configuration file for this project 2 | # To set you own values, duplicate this file and rename it as ".env" 3 | # All the values will be set as environment variables and read in "src/config.rs" 4 | 5 | # REST API port 6 | PORT = 8000 7 | 8 | # Comma-separated list of peer addresses 9 | # PEERS = http://localhost:8001,http://localhost:8002 10 | 11 | # Period of time to wait between peer block synchronization (milliseconds) 12 | PEER_SYNC_MS = 10000 13 | 14 | # Upper limit of blocks to be mined (0 for unlimited) 15 | MAX_BLOCKS = 0 16 | 17 | # Upper limit of tries for finding a valid block 18 | MAX_NONCE = 1000000 19 | 20 | # Number of zeros needed at the start of the hash of a valid block 21 | DIFFICULTY = 10 22 | 23 | # Amount of milliseconds the miner wil wait before checking new transactions 24 | TRANSACTION_WAITING_MS = 10000 25 | 26 | # Recipient address of the miner, to receive block mining rewards 27 | MINER_ADDRESS = 0000000000000000000000000000000000000000000000000000000000000000 -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - '*' 6 | 7 | name: build 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: build 23 | args: --release -------------------------------------------------------------------------------- /.github/workflows/coverage.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - '*' 6 | 7 | name: coverage 8 | 9 | jobs: 10 | coverage: 11 | name: Coverage 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v1 15 | 16 | - uses: actions-rs/toolchain@v1 17 | with: 18 | toolchain: stable 19 | override: true 20 | 21 | - name: Install llvm-tools-preview 22 | run: rustup component add llvm-tools-preview 23 | 24 | - name: Install grcov 25 | run: cargo install grcov --locked 26 | 27 | - name: Run grcov 28 | env: 29 | RUSTFLAGS: '-C instrument-coverage' 30 | LLVM_PROFILE_FILE: 'rust_blockchain-%p-%m.profraw' 31 | run: | 32 | cargo build 33 | cargo test 34 | grcov . --binary-path ./target/debug/ -s . -t lcov --branch --ignore-not-existing --ignore "/*" --ignore "tests/*" -o lcov.info 35 | 36 | - name: Push grcov results to Coveralls via GitHub Action 37 | uses: coverallsapp/github-action@master 38 | with: 39 | github-token: ${{ secrets.GITHUB_TOKEN }} 40 | path-to-lcov: "lcov.info" -------------------------------------------------------------------------------- /.github/workflows/lint.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - '*' 6 | 7 | name: lint 8 | 9 | jobs: 10 | fmt: 11 | name: Rustfmt 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - run: rustup component add rustfmt 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: fmt 24 | args: --all -- --check 25 | 26 | clippy: 27 | name: Clippy 28 | runs-on: ubuntu-latest 29 | steps: 30 | - uses: actions/checkout@v2 31 | - uses: actions-rs/toolchain@v1 32 | with: 33 | toolchain: stable 34 | components: clippy 35 | override: true 36 | - uses: actions-rs/clippy-check@v1 37 | with: 38 | token: ${{ secrets.GITHUB_TOKEN }} 39 | args: --all-features 40 | name: Clippy Output -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | pull_request: 3 | push: 4 | branches: 5 | - '*' 6 | 7 | name: test 8 | 9 | jobs: 10 | check: 11 | name: Check 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v2 15 | - uses: actions-rs/toolchain@v1 16 | with: 17 | profile: minimal 18 | toolchain: stable 19 | override: true 20 | - uses: actions-rs/cargo@v1 21 | with: 22 | command: test -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # These are backup files generated by rustfmt 6 | **/*.rs.bk 7 | 8 | # Dotenv configuration files 9 | .env 10 | 11 | # Coverage reports 12 | /coverage/ 13 | **/*.profraw -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "actix-codec" 7 | version = "0.5.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "57a7559404a7f3573127aab53c08ce37a6c6a315c374a31070f3c91cd1b4a7fe" 10 | dependencies = [ 11 | "bitflags", 12 | "bytes", 13 | "futures-core", 14 | "futures-sink", 15 | "log", 16 | "memchr", 17 | "pin-project-lite", 18 | "tokio", 19 | "tokio-util", 20 | ] 21 | 22 | [[package]] 23 | name = "actix-http" 24 | version = "3.1.0" 25 | source = "registry+https://github.com/rust-lang/crates.io-index" 26 | checksum = "bd2e9f6794b5826aff6df65e3a0d0127b271d1c03629c774238f3582e903d4e4" 27 | dependencies = [ 28 | "actix-codec", 29 | "actix-rt", 30 | "actix-service", 31 | "actix-utils", 32 | "ahash", 33 | "base64", 34 | "bitflags", 35 | "brotli", 36 | "bytes", 37 | "bytestring", 38 | "derive_more", 39 | "encoding_rs", 40 | "flate2", 41 | "futures-core", 42 | "h2", 43 | "http", 44 | "httparse", 45 | "httpdate", 46 | "itoa 1.0.2", 47 | "language-tags", 48 | "local-channel", 49 | "mime", 50 | "percent-encoding", 51 | "pin-project-lite", 52 | "rand 0.8.5", 53 | "sha1", 54 | "smallvec", 55 | "tracing", 56 | "zstd", 57 | ] 58 | 59 | [[package]] 60 | name = "actix-macros" 61 | version = "0.2.3" 62 | source = "registry+https://github.com/rust-lang/crates.io-index" 63 | checksum = "465a6172cf69b960917811022d8f29bc0b7fa1398bc4f78b3c466673db1213b6" 64 | dependencies = [ 65 | "quote", 66 | "syn", 67 | ] 68 | 69 | [[package]] 70 | name = "actix-router" 71 | version = "0.5.0" 72 | source = "registry+https://github.com/rust-lang/crates.io-index" 73 | checksum = "eb60846b52c118f2f04a56cc90880a274271c489b2498623d58176f8ca21fa80" 74 | dependencies = [ 75 | "bytestring", 76 | "firestorm", 77 | "http", 78 | "log", 79 | "regex", 80 | "serde", 81 | ] 82 | 83 | [[package]] 84 | name = "actix-rt" 85 | version = "2.7.0" 86 | source = "registry+https://github.com/rust-lang/crates.io-index" 87 | checksum = "7ea16c295198e958ef31930a6ef37d0fb64e9ca3b6116e6b93a8bdae96ee1000" 88 | dependencies = [ 89 | "futures-core", 90 | "tokio", 91 | ] 92 | 93 | [[package]] 94 | name = "actix-server" 95 | version = "2.1.1" 96 | source = "registry+https://github.com/rust-lang/crates.io-index" 97 | checksum = "0da34f8e659ea1b077bb4637948b815cd3768ad5a188fdcd74ff4d84240cd824" 98 | dependencies = [ 99 | "actix-rt", 100 | "actix-service", 101 | "actix-utils", 102 | "futures-core", 103 | "futures-util", 104 | "mio 0.8.4", 105 | "num_cpus", 106 | "socket2", 107 | "tokio", 108 | "tracing", 109 | ] 110 | 111 | [[package]] 112 | name = "actix-service" 113 | version = "2.0.2" 114 | source = "registry+https://github.com/rust-lang/crates.io-index" 115 | checksum = "3b894941f818cfdc7ccc4b9e60fa7e53b5042a2e8567270f9147d5591893373a" 116 | dependencies = [ 117 | "futures-core", 118 | "paste", 119 | "pin-project-lite", 120 | ] 121 | 122 | [[package]] 123 | name = "actix-utils" 124 | version = "3.0.0" 125 | source = "registry+https://github.com/rust-lang/crates.io-index" 126 | checksum = "e491cbaac2e7fc788dfff99ff48ef317e23b3cf63dbaf7aaab6418f40f92aa94" 127 | dependencies = [ 128 | "local-waker", 129 | "pin-project-lite", 130 | ] 131 | 132 | [[package]] 133 | name = "actix-web" 134 | version = "4.1.0" 135 | source = "registry+https://github.com/rust-lang/crates.io-index" 136 | checksum = "a27e8fe9ba4ae613c21f677c2cfaf0696c3744030c6f485b34634e502d6bb379" 137 | dependencies = [ 138 | "actix-codec", 139 | "actix-http", 140 | "actix-macros", 141 | "actix-router", 142 | "actix-rt", 143 | "actix-server", 144 | "actix-service", 145 | "actix-utils", 146 | "actix-web-codegen", 147 | "ahash", 148 | "bytes", 149 | "bytestring", 150 | "cfg-if", 151 | "cookie", 152 | "derive_more", 153 | "encoding_rs", 154 | "futures-core", 155 | "futures-util", 156 | "itoa 1.0.2", 157 | "language-tags", 158 | "log", 159 | "mime", 160 | "once_cell", 161 | "pin-project-lite", 162 | "regex", 163 | "serde", 164 | "serde_json", 165 | "serde_urlencoded", 166 | "smallvec", 167 | "socket2", 168 | "time 0.3.11", 169 | "url", 170 | ] 171 | 172 | [[package]] 173 | name = "actix-web-codegen" 174 | version = "4.0.1" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "5f270541caec49c15673b0af0e9a00143421ad4f118d2df7edcb68b627632f56" 177 | dependencies = [ 178 | "actix-router", 179 | "proc-macro2", 180 | "quote", 181 | "syn", 182 | ] 183 | 184 | [[package]] 185 | name = "adler" 186 | version = "1.0.2" 187 | source = "registry+https://github.com/rust-lang/crates.io-index" 188 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 189 | 190 | [[package]] 191 | name = "ahash" 192 | version = "0.7.6" 193 | source = "registry+https://github.com/rust-lang/crates.io-index" 194 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 195 | dependencies = [ 196 | "getrandom", 197 | "once_cell", 198 | "version_check", 199 | ] 200 | 201 | [[package]] 202 | name = "aho-corasick" 203 | version = "0.7.18" 204 | source = "registry+https://github.com/rust-lang/crates.io-index" 205 | checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" 206 | dependencies = [ 207 | "memchr", 208 | ] 209 | 210 | [[package]] 211 | name = "alloc-no-stdlib" 212 | version = "2.0.3" 213 | source = "registry+https://github.com/rust-lang/crates.io-index" 214 | checksum = "35ef4730490ad1c4eae5c4325b2a95f521d023e5c885853ff7aca0a6a1631db3" 215 | 216 | [[package]] 217 | name = "alloc-stdlib" 218 | version = "0.2.1" 219 | source = "registry+https://github.com/rust-lang/crates.io-index" 220 | checksum = "697ed7edc0f1711de49ce108c541623a0af97c6c60b2f6e2b65229847ac843c2" 221 | dependencies = [ 222 | "alloc-no-stdlib", 223 | ] 224 | 225 | [[package]] 226 | name = "anyhow" 227 | version = "1.0.58" 228 | source = "registry+https://github.com/rust-lang/crates.io-index" 229 | checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" 230 | 231 | [[package]] 232 | name = "arrayvec" 233 | version = "0.7.2" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" 236 | 237 | [[package]] 238 | name = "assert_cmd" 239 | version = "2.0.4" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e" 242 | dependencies = [ 243 | "bstr", 244 | "doc-comment", 245 | "predicates", 246 | "predicates-core", 247 | "predicates-tree", 248 | "wait-timeout", 249 | ] 250 | 251 | [[package]] 252 | name = "async-channel" 253 | version = "1.6.1" 254 | source = "registry+https://github.com/rust-lang/crates.io-index" 255 | checksum = "2114d64672151c0c5eaa5e131ec84a74f06e1e559830dabba01ca30605d66319" 256 | dependencies = [ 257 | "concurrent-queue", 258 | "event-listener", 259 | "futures-core", 260 | ] 261 | 262 | [[package]] 263 | name = "atty" 264 | version = "0.2.14" 265 | source = "registry+https://github.com/rust-lang/crates.io-index" 266 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 267 | dependencies = [ 268 | "hermit-abi", 269 | "libc", 270 | "winapi", 271 | ] 272 | 273 | [[package]] 274 | name = "autocfg" 275 | version = "1.1.0" 276 | source = "registry+https://github.com/rust-lang/crates.io-index" 277 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 278 | 279 | [[package]] 280 | name = "base64" 281 | version = "0.13.0" 282 | source = "registry+https://github.com/rust-lang/crates.io-index" 283 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 284 | 285 | [[package]] 286 | name = "bitflags" 287 | version = "1.3.2" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 290 | 291 | [[package]] 292 | name = "bitvec" 293 | version = "1.0.0" 294 | source = "registry+https://github.com/rust-lang/crates.io-index" 295 | checksum = "1489fcb93a5bb47da0462ca93ad252ad6af2145cce58d10d46a83931ba9f016b" 296 | dependencies = [ 297 | "funty", 298 | "radium", 299 | "tap", 300 | "wyz", 301 | ] 302 | 303 | [[package]] 304 | name = "block-buffer" 305 | version = "0.10.2" 306 | source = "registry+https://github.com/rust-lang/crates.io-index" 307 | checksum = "0bf7fe51849ea569fd452f37822f606a5cabb684dc918707a0193fd4664ff324" 308 | dependencies = [ 309 | "generic-array", 310 | ] 311 | 312 | [[package]] 313 | name = "brotli" 314 | version = "3.3.4" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "a1a0b1dbcc8ae29329621f8d4f0d835787c1c38bb1401979b49d13b0b305ff68" 317 | dependencies = [ 318 | "alloc-no-stdlib", 319 | "alloc-stdlib", 320 | "brotli-decompressor", 321 | ] 322 | 323 | [[package]] 324 | name = "brotli-decompressor" 325 | version = "2.3.2" 326 | source = "registry+https://github.com/rust-lang/crates.io-index" 327 | checksum = "59ad2d4653bf5ca36ae797b1f4bb4dbddb60ce49ca4aed8a2ce4829f60425b80" 328 | dependencies = [ 329 | "alloc-no-stdlib", 330 | "alloc-stdlib", 331 | ] 332 | 333 | [[package]] 334 | name = "bstr" 335 | version = "0.2.17" 336 | source = "registry+https://github.com/rust-lang/crates.io-index" 337 | checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" 338 | dependencies = [ 339 | "lazy_static", 340 | "memchr", 341 | "regex-automata", 342 | ] 343 | 344 | [[package]] 345 | name = "byte-slice-cast" 346 | version = "1.2.1" 347 | source = "registry+https://github.com/rust-lang/crates.io-index" 348 | checksum = "87c5fdd0166095e1d463fc6cc01aa8ce547ad77a4e84d42eb6762b084e28067e" 349 | 350 | [[package]] 351 | name = "byteorder" 352 | version = "1.4.3" 353 | source = "registry+https://github.com/rust-lang/crates.io-index" 354 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 355 | 356 | [[package]] 357 | name = "bytes" 358 | version = "1.1.0" 359 | source = "registry+https://github.com/rust-lang/crates.io-index" 360 | checksum = "c4872d67bab6358e59559027aa3b9157c53d9358c51423c17554809a8858e0f8" 361 | 362 | [[package]] 363 | name = "bytestring" 364 | version = "1.0.0" 365 | source = "registry+https://github.com/rust-lang/crates.io-index" 366 | checksum = "90706ba19e97b90786e19dc0d5e2abd80008d99d4c0c5d1ad0b5e72cec7c494d" 367 | dependencies = [ 368 | "bytes", 369 | ] 370 | 371 | [[package]] 372 | name = "cache-padded" 373 | version = "1.1.1" 374 | source = "registry+https://github.com/rust-lang/crates.io-index" 375 | checksum = "631ae5198c9be5e753e5cc215e1bd73c2b466a3565173db433f52bb9d3e66dba" 376 | 377 | [[package]] 378 | name = "cargo-husky" 379 | version = "1.5.0" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "7b02b629252fe8ef6460461409564e2c21d0c8e77e0944f3d189ff06c4e932ad" 382 | 383 | [[package]] 384 | name = "castaway" 385 | version = "0.1.1" 386 | source = "registry+https://github.com/rust-lang/crates.io-index" 387 | checksum = "ed247d1586918e46f2bbe0f13b06498db8dab5a8c1093f156652e9f2e0a73fc3" 388 | 389 | [[package]] 390 | name = "cc" 391 | version = "1.0.71" 392 | source = "registry+https://github.com/rust-lang/crates.io-index" 393 | checksum = "79c2681d6594606957bbb8631c4b90a7fcaaa72cdb714743a437b156d6a7eedd" 394 | dependencies = [ 395 | "jobserver", 396 | ] 397 | 398 | [[package]] 399 | name = "cfg-if" 400 | version = "1.0.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 403 | 404 | [[package]] 405 | name = "chrono" 406 | version = "0.4.19" 407 | source = "registry+https://github.com/rust-lang/crates.io-index" 408 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 409 | dependencies = [ 410 | "libc", 411 | "num-integer", 412 | "num-traits", 413 | "time 0.1.44", 414 | "winapi", 415 | ] 416 | 417 | [[package]] 418 | name = "concurrent-queue" 419 | version = "1.2.2" 420 | source = "registry+https://github.com/rust-lang/crates.io-index" 421 | checksum = "30ed07550be01594c6026cff2a1d7fe9c8f683caa798e12b68694ac9e88286a3" 422 | dependencies = [ 423 | "cache-padded", 424 | ] 425 | 426 | [[package]] 427 | name = "convert_case" 428 | version = "0.4.0" 429 | source = "registry+https://github.com/rust-lang/crates.io-index" 430 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 431 | 432 | [[package]] 433 | name = "cookie" 434 | version = "0.16.0" 435 | source = "registry+https://github.com/rust-lang/crates.io-index" 436 | checksum = "94d4706de1b0fa5b132270cddffa8585166037822e260a944fe161acd137ca05" 437 | dependencies = [ 438 | "percent-encoding", 439 | "time 0.3.11", 440 | "version_check", 441 | ] 442 | 443 | [[package]] 444 | name = "cpufeatures" 445 | version = "0.2.1" 446 | source = "registry+https://github.com/rust-lang/crates.io-index" 447 | checksum = "95059428f66df56b63431fdb4e1947ed2190586af5c5a8a8b71122bdf5a7f469" 448 | dependencies = [ 449 | "libc", 450 | ] 451 | 452 | [[package]] 453 | name = "crc32fast" 454 | version = "1.2.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" 457 | dependencies = [ 458 | "cfg-if", 459 | ] 460 | 461 | [[package]] 462 | name = "crossbeam-utils" 463 | version = "0.8.10" 464 | source = "registry+https://github.com/rust-lang/crates.io-index" 465 | checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" 466 | dependencies = [ 467 | "cfg-if", 468 | "once_cell", 469 | ] 470 | 471 | [[package]] 472 | name = "crunchy" 473 | version = "0.2.2" 474 | source = "registry+https://github.com/rust-lang/crates.io-index" 475 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 476 | 477 | [[package]] 478 | name = "crypto-common" 479 | version = "0.1.3" 480 | source = "registry+https://github.com/rust-lang/crates.io-index" 481 | checksum = "57952ca27b5e3606ff4dd79b0020231aaf9d6aa76dc05fd30137538c50bd3ce8" 482 | dependencies = [ 483 | "generic-array", 484 | "typenum", 485 | ] 486 | 487 | [[package]] 488 | name = "ctrlc" 489 | version = "3.2.2" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "b37feaa84e6861e00a1f5e5aa8da3ee56d605c9992d33e082786754828e20865" 492 | dependencies = [ 493 | "nix", 494 | "winapi", 495 | ] 496 | 497 | [[package]] 498 | name = "curl" 499 | version = "0.4.39" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "aaa3b8db7f3341ddef15786d250106334d4a6c4b0ae4a46cd77082777d9849b9" 502 | dependencies = [ 503 | "curl-sys", 504 | "libc", 505 | "openssl-probe", 506 | "openssl-sys", 507 | "schannel", 508 | "socket2", 509 | "winapi", 510 | ] 511 | 512 | [[package]] 513 | name = "curl-sys" 514 | version = "0.4.55+curl-7.83.1" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "23734ec77368ec583c2e61dd3f0b0e5c98b93abe6d2a004ca06b91dd7e3e2762" 517 | dependencies = [ 518 | "cc", 519 | "libc", 520 | "libnghttp2-sys", 521 | "libz-sys", 522 | "openssl-sys", 523 | "pkg-config", 524 | "vcpkg", 525 | "winapi", 526 | ] 527 | 528 | [[package]] 529 | name = "derive_more" 530 | version = "0.99.16" 531 | source = "registry+https://github.com/rust-lang/crates.io-index" 532 | checksum = "40eebddd2156ce1bb37b20bbe5151340a31828b1f2d22ba4141f3531710e38df" 533 | dependencies = [ 534 | "convert_case", 535 | "proc-macro2", 536 | "quote", 537 | "rustc_version", 538 | "syn", 539 | ] 540 | 541 | [[package]] 542 | name = "difflib" 543 | version = "0.4.0" 544 | source = "registry+https://github.com/rust-lang/crates.io-index" 545 | checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" 546 | 547 | [[package]] 548 | name = "digest" 549 | version = "0.10.3" 550 | source = "registry+https://github.com/rust-lang/crates.io-index" 551 | checksum = "f2fb860ca6fafa5552fb6d0e816a69c8e49f0908bf524e30a90d97c85892d506" 552 | dependencies = [ 553 | "block-buffer", 554 | "crypto-common", 555 | ] 556 | 557 | [[package]] 558 | name = "doc-comment" 559 | version = "0.3.3" 560 | source = "registry+https://github.com/rust-lang/crates.io-index" 561 | checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10" 562 | 563 | [[package]] 564 | name = "dotenv" 565 | version = "0.15.0" 566 | source = "registry+https://github.com/rust-lang/crates.io-index" 567 | checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" 568 | 569 | [[package]] 570 | name = "dotenv_codegen" 571 | version = "0.15.0" 572 | source = "registry+https://github.com/rust-lang/crates.io-index" 573 | checksum = "56966279c10e4f8ee8c22123a15ed74e7c8150b658b26c619c53f4a56eb4a8aa" 574 | dependencies = [ 575 | "dotenv_codegen_implementation", 576 | "proc-macro-hack", 577 | ] 578 | 579 | [[package]] 580 | name = "dotenv_codegen_implementation" 581 | version = "0.15.0" 582 | source = "registry+https://github.com/rust-lang/crates.io-index" 583 | checksum = "53e737a3522cd45f6adc19b644ce43ef53e1e9045f2d2de425c1f468abd4cf33" 584 | dependencies = [ 585 | "dotenv", 586 | "proc-macro-hack", 587 | "proc-macro2", 588 | "quote", 589 | "syn", 590 | ] 591 | 592 | [[package]] 593 | name = "either" 594 | version = "1.6.1" 595 | source = "registry+https://github.com/rust-lang/crates.io-index" 596 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 597 | 598 | [[package]] 599 | name = "encoding_rs" 600 | version = "0.8.28" 601 | source = "registry+https://github.com/rust-lang/crates.io-index" 602 | checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" 603 | dependencies = [ 604 | "cfg-if", 605 | ] 606 | 607 | [[package]] 608 | name = "env_logger" 609 | version = "0.9.0" 610 | source = "registry+https://github.com/rust-lang/crates.io-index" 611 | checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" 612 | dependencies = [ 613 | "atty", 614 | "humantime", 615 | "log", 616 | "regex", 617 | "termcolor", 618 | ] 619 | 620 | [[package]] 621 | name = "ethbloom" 622 | version = "0.12.1" 623 | source = "registry+https://github.com/rust-lang/crates.io-index" 624 | checksum = "11da94e443c60508eb62cf256243a64da87304c2802ac2528847f79d750007ef" 625 | dependencies = [ 626 | "crunchy", 627 | "fixed-hash", 628 | "impl-rlp", 629 | "impl-serde", 630 | "tiny-keccak", 631 | ] 632 | 633 | [[package]] 634 | name = "ethereum-types" 635 | version = "0.13.1" 636 | source = "registry+https://github.com/rust-lang/crates.io-index" 637 | checksum = "b2827b94c556145446fcce834ca86b7abf0c39a805883fe20e72c5bfdb5a0dc6" 638 | dependencies = [ 639 | "ethbloom", 640 | "fixed-hash", 641 | "impl-rlp", 642 | "impl-serde", 643 | "primitive-types", 644 | "uint", 645 | ] 646 | 647 | [[package]] 648 | name = "event-listener" 649 | version = "2.5.1" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "f7531096570974c3a9dcf9e4b8e1cede1ec26cf5046219fb3b9d897503b9be59" 652 | 653 | [[package]] 654 | name = "fastrand" 655 | version = "1.5.0" 656 | source = "registry+https://github.com/rust-lang/crates.io-index" 657 | checksum = "b394ed3d285a429378d3b384b9eb1285267e7df4b166df24b7a6939a04dc392e" 658 | dependencies = [ 659 | "instant", 660 | ] 661 | 662 | [[package]] 663 | name = "firestorm" 664 | version = "0.5.1" 665 | source = "registry+https://github.com/rust-lang/crates.io-index" 666 | checksum = "2c5f6c2c942da57e2aaaa84b8a521489486f14e75e7fa91dab70aba913975f98" 667 | 668 | [[package]] 669 | name = "fixed-hash" 670 | version = "0.7.0" 671 | source = "registry+https://github.com/rust-lang/crates.io-index" 672 | checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" 673 | dependencies = [ 674 | "byteorder", 675 | "rand 0.8.5", 676 | "rustc-hex", 677 | "static_assertions", 678 | ] 679 | 680 | [[package]] 681 | name = "flate2" 682 | version = "1.0.22" 683 | source = "registry+https://github.com/rust-lang/crates.io-index" 684 | checksum = "1e6988e897c1c9c485f43b47a529cef42fde0547f9d8d41a7062518f1d8fc53f" 685 | dependencies = [ 686 | "cfg-if", 687 | "crc32fast", 688 | "libc", 689 | "miniz_oxide", 690 | ] 691 | 692 | [[package]] 693 | name = "fnv" 694 | version = "1.0.7" 695 | source = "registry+https://github.com/rust-lang/crates.io-index" 696 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 697 | 698 | [[package]] 699 | name = "form_urlencoded" 700 | version = "1.0.1" 701 | source = "registry+https://github.com/rust-lang/crates.io-index" 702 | checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" 703 | dependencies = [ 704 | "matches", 705 | "percent-encoding", 706 | ] 707 | 708 | [[package]] 709 | name = "fuchsia-cprng" 710 | version = "0.1.1" 711 | source = "registry+https://github.com/rust-lang/crates.io-index" 712 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 713 | 714 | [[package]] 715 | name = "funty" 716 | version = "2.0.0" 717 | source = "registry+https://github.com/rust-lang/crates.io-index" 718 | checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" 719 | 720 | [[package]] 721 | name = "futures" 722 | version = "0.3.21" 723 | source = "registry+https://github.com/rust-lang/crates.io-index" 724 | checksum = "f73fe65f54d1e12b726f517d3e2135ca3125a437b6d998caf1962961f7172d9e" 725 | dependencies = [ 726 | "futures-channel", 727 | "futures-core", 728 | "futures-executor", 729 | "futures-io", 730 | "futures-sink", 731 | "futures-task", 732 | "futures-util", 733 | ] 734 | 735 | [[package]] 736 | name = "futures-channel" 737 | version = "0.3.21" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "c3083ce4b914124575708913bca19bfe887522d6e2e6d0952943f5eac4a74010" 740 | dependencies = [ 741 | "futures-core", 742 | "futures-sink", 743 | ] 744 | 745 | [[package]] 746 | name = "futures-core" 747 | version = "0.3.21" 748 | source = "registry+https://github.com/rust-lang/crates.io-index" 749 | checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" 750 | 751 | [[package]] 752 | name = "futures-executor" 753 | version = "0.3.21" 754 | source = "registry+https://github.com/rust-lang/crates.io-index" 755 | checksum = "9420b90cfa29e327d0429f19be13e7ddb68fa1cccb09d65e5706b8c7a749b8a6" 756 | dependencies = [ 757 | "futures-core", 758 | "futures-task", 759 | "futures-util", 760 | ] 761 | 762 | [[package]] 763 | name = "futures-io" 764 | version = "0.3.21" 765 | source = "registry+https://github.com/rust-lang/crates.io-index" 766 | checksum = "fc4045962a5a5e935ee2fdedaa4e08284547402885ab326734432bed5d12966b" 767 | 768 | [[package]] 769 | name = "futures-lite" 770 | version = "1.12.0" 771 | source = "registry+https://github.com/rust-lang/crates.io-index" 772 | checksum = "7694489acd39452c77daa48516b894c153f192c3578d5a839b62c58099fcbf48" 773 | dependencies = [ 774 | "fastrand", 775 | "futures-core", 776 | "futures-io", 777 | "memchr", 778 | "parking", 779 | "pin-project-lite", 780 | "waker-fn", 781 | ] 782 | 783 | [[package]] 784 | name = "futures-macro" 785 | version = "0.3.21" 786 | source = "registry+https://github.com/rust-lang/crates.io-index" 787 | checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" 788 | dependencies = [ 789 | "proc-macro2", 790 | "quote", 791 | "syn", 792 | ] 793 | 794 | [[package]] 795 | name = "futures-sink" 796 | version = "0.3.21" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "21163e139fa306126e6eedaf49ecdb4588f939600f0b1e770f4205ee4b7fa868" 799 | 800 | [[package]] 801 | name = "futures-task" 802 | version = "0.3.21" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" 805 | 806 | [[package]] 807 | name = "futures-util" 808 | version = "0.3.21" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" 811 | dependencies = [ 812 | "futures-channel", 813 | "futures-core", 814 | "futures-io", 815 | "futures-macro", 816 | "futures-sink", 817 | "futures-task", 818 | "memchr", 819 | "pin-project-lite", 820 | "pin-utils", 821 | "slab", 822 | ] 823 | 824 | [[package]] 825 | name = "gcc" 826 | version = "0.3.55" 827 | source = "registry+https://github.com/rust-lang/crates.io-index" 828 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 829 | 830 | [[package]] 831 | name = "generic-array" 832 | version = "0.14.4" 833 | source = "registry+https://github.com/rust-lang/crates.io-index" 834 | checksum = "501466ecc8a30d1d3b7fc9229b122b2ce8ed6e9d9223f1138d4babb253e51817" 835 | dependencies = [ 836 | "typenum", 837 | "version_check", 838 | ] 839 | 840 | [[package]] 841 | name = "getrandom" 842 | version = "0.2.7" 843 | source = "registry+https://github.com/rust-lang/crates.io-index" 844 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 845 | dependencies = [ 846 | "cfg-if", 847 | "libc", 848 | "wasi 0.11.0+wasi-snapshot-preview1", 849 | ] 850 | 851 | [[package]] 852 | name = "h2" 853 | version = "0.3.13" 854 | source = "registry+https://github.com/rust-lang/crates.io-index" 855 | checksum = "37a82c6d637fc9515a4694bbf1cb2457b79d81ce52b3108bdeea58b07dd34a57" 856 | dependencies = [ 857 | "bytes", 858 | "fnv", 859 | "futures-core", 860 | "futures-sink", 861 | "futures-util", 862 | "http", 863 | "indexmap", 864 | "slab", 865 | "tokio", 866 | "tokio-util", 867 | "tracing", 868 | ] 869 | 870 | [[package]] 871 | name = "hashbrown" 872 | version = "0.11.2" 873 | source = "registry+https://github.com/rust-lang/crates.io-index" 874 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 875 | 876 | [[package]] 877 | name = "hermit-abi" 878 | version = "0.1.19" 879 | source = "registry+https://github.com/rust-lang/crates.io-index" 880 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 881 | dependencies = [ 882 | "libc", 883 | ] 884 | 885 | [[package]] 886 | name = "hex" 887 | version = "0.4.3" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 890 | 891 | [[package]] 892 | name = "http" 893 | version = "0.2.5" 894 | source = "registry+https://github.com/rust-lang/crates.io-index" 895 | checksum = "1323096b05d41827dadeaee54c9981958c0f94e670bc94ed80037d1a7b8b186b" 896 | dependencies = [ 897 | "bytes", 898 | "fnv", 899 | "itoa 0.4.8", 900 | ] 901 | 902 | [[package]] 903 | name = "httparse" 904 | version = "1.5.1" 905 | source = "registry+https://github.com/rust-lang/crates.io-index" 906 | checksum = "acd94fdbe1d4ff688b67b04eee2e17bd50995534a61539e45adfefb45e5e5503" 907 | 908 | [[package]] 909 | name = "httpdate" 910 | version = "1.0.2" 911 | source = "registry+https://github.com/rust-lang/crates.io-index" 912 | checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421" 913 | 914 | [[package]] 915 | name = "humantime" 916 | version = "2.1.0" 917 | source = "registry+https://github.com/rust-lang/crates.io-index" 918 | checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" 919 | 920 | [[package]] 921 | name = "idna" 922 | version = "0.2.3" 923 | source = "registry+https://github.com/rust-lang/crates.io-index" 924 | checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" 925 | dependencies = [ 926 | "matches", 927 | "unicode-bidi", 928 | "unicode-normalization", 929 | ] 930 | 931 | [[package]] 932 | name = "impl-codec" 933 | version = "0.6.0" 934 | source = "registry+https://github.com/rust-lang/crates.io-index" 935 | checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" 936 | dependencies = [ 937 | "parity-scale-codec", 938 | ] 939 | 940 | [[package]] 941 | name = "impl-rlp" 942 | version = "0.3.0" 943 | source = "registry+https://github.com/rust-lang/crates.io-index" 944 | checksum = "f28220f89297a075ddc7245cd538076ee98b01f2a9c23a53a4f1105d5a322808" 945 | dependencies = [ 946 | "rlp", 947 | ] 948 | 949 | [[package]] 950 | name = "impl-serde" 951 | version = "0.3.2" 952 | source = "registry+https://github.com/rust-lang/crates.io-index" 953 | checksum = "4551f042f3438e64dbd6226b20527fc84a6e1fe65688b58746a2f53623f25f5c" 954 | dependencies = [ 955 | "serde", 956 | ] 957 | 958 | [[package]] 959 | name = "impl-trait-for-tuples" 960 | version = "0.2.2" 961 | source = "registry+https://github.com/rust-lang/crates.io-index" 962 | checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" 963 | dependencies = [ 964 | "proc-macro2", 965 | "quote", 966 | "syn", 967 | ] 968 | 969 | [[package]] 970 | name = "indexmap" 971 | version = "1.7.0" 972 | source = "registry+https://github.com/rust-lang/crates.io-index" 973 | checksum = "bc633605454125dec4b66843673f01c7df2b89479b32e0ed634e43a91cff62a5" 974 | dependencies = [ 975 | "autocfg", 976 | "hashbrown", 977 | ] 978 | 979 | [[package]] 980 | name = "instant" 981 | version = "0.1.11" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "716d3d89f35ac6a34fd0eed635395f4c3b76fa889338a4632e5231a8684216bd" 984 | dependencies = [ 985 | "cfg-if", 986 | ] 987 | 988 | [[package]] 989 | name = "isahc" 990 | version = "1.7.2" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "334e04b4d781f436dc315cb1e7515bd96826426345d498149e4bde36b67f8ee9" 993 | dependencies = [ 994 | "async-channel", 995 | "castaway", 996 | "crossbeam-utils", 997 | "curl", 998 | "curl-sys", 999 | "encoding_rs", 1000 | "event-listener", 1001 | "futures-lite", 1002 | "http", 1003 | "log", 1004 | "mime", 1005 | "once_cell", 1006 | "polling", 1007 | "slab", 1008 | "sluice", 1009 | "tracing", 1010 | "tracing-futures", 1011 | "url", 1012 | "waker-fn", 1013 | ] 1014 | 1015 | [[package]] 1016 | name = "itertools" 1017 | version = "0.10.1" 1018 | source = "registry+https://github.com/rust-lang/crates.io-index" 1019 | checksum = "69ddb889f9d0d08a67338271fa9b62996bc788c7796a5c18cf057420aaed5eaf" 1020 | dependencies = [ 1021 | "either", 1022 | ] 1023 | 1024 | [[package]] 1025 | name = "itoa" 1026 | version = "0.4.8" 1027 | source = "registry+https://github.com/rust-lang/crates.io-index" 1028 | checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" 1029 | 1030 | [[package]] 1031 | name = "itoa" 1032 | version = "1.0.2" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" 1035 | 1036 | [[package]] 1037 | name = "jobserver" 1038 | version = "0.1.24" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" 1041 | dependencies = [ 1042 | "libc", 1043 | ] 1044 | 1045 | [[package]] 1046 | name = "language-tags" 1047 | version = "0.3.2" 1048 | source = "registry+https://github.com/rust-lang/crates.io-index" 1049 | checksum = "d4345964bb142484797b161f473a503a434de77149dd8c7427788c6e13379388" 1050 | 1051 | [[package]] 1052 | name = "lazy_static" 1053 | version = "1.4.0" 1054 | source = "registry+https://github.com/rust-lang/crates.io-index" 1055 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 1056 | 1057 | [[package]] 1058 | name = "libc" 1059 | version = "0.2.126" 1060 | source = "registry+https://github.com/rust-lang/crates.io-index" 1061 | checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" 1062 | 1063 | [[package]] 1064 | name = "libnghttp2-sys" 1065 | version = "0.1.7+1.45.0" 1066 | source = "registry+https://github.com/rust-lang/crates.io-index" 1067 | checksum = "57ed28aba195b38d5ff02b9170cbff627e336a20925e43b4945390401c5dc93f" 1068 | dependencies = [ 1069 | "cc", 1070 | "libc", 1071 | ] 1072 | 1073 | [[package]] 1074 | name = "libz-sys" 1075 | version = "1.1.3" 1076 | source = "registry+https://github.com/rust-lang/crates.io-index" 1077 | checksum = "de5435b8549c16d423ed0c03dbaafe57cf6c3344744f1242520d59c9d8ecec66" 1078 | dependencies = [ 1079 | "cc", 1080 | "libc", 1081 | "pkg-config", 1082 | "vcpkg", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "local-channel" 1087 | version = "0.1.3" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "7f303ec0e94c6c54447f84f3b0ef7af769858a9c4ef56ef2a986d3dcd4c3fc9c" 1090 | dependencies = [ 1091 | "futures-core", 1092 | "futures-sink", 1093 | "futures-util", 1094 | "local-waker", 1095 | ] 1096 | 1097 | [[package]] 1098 | name = "local-waker" 1099 | version = "0.1.3" 1100 | source = "registry+https://github.com/rust-lang/crates.io-index" 1101 | checksum = "e34f76eb3611940e0e7d53a9aaa4e6a3151f69541a282fd0dad5571420c53ff1" 1102 | 1103 | [[package]] 1104 | name = "lock_api" 1105 | version = "0.4.7" 1106 | source = "registry+https://github.com/rust-lang/crates.io-index" 1107 | checksum = "327fa5b6a6940e4699ec49a9beae1ea4845c6bab9314e4f84ac68742139d8c53" 1108 | dependencies = [ 1109 | "autocfg", 1110 | "scopeguard", 1111 | ] 1112 | 1113 | [[package]] 1114 | name = "log" 1115 | version = "0.4.17" 1116 | source = "registry+https://github.com/rust-lang/crates.io-index" 1117 | checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" 1118 | dependencies = [ 1119 | "cfg-if", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "matches" 1124 | version = "0.1.9" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "a3e378b66a060d48947b590737b30a1be76706c8dd7b8ba0f2fe3989c68a853f" 1127 | 1128 | [[package]] 1129 | name = "memchr" 1130 | version = "2.4.1" 1131 | source = "registry+https://github.com/rust-lang/crates.io-index" 1132 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 1133 | 1134 | [[package]] 1135 | name = "memoffset" 1136 | version = "0.6.4" 1137 | source = "registry+https://github.com/rust-lang/crates.io-index" 1138 | checksum = "59accc507f1338036a0477ef61afdae33cde60840f4dfe481319ce3ad116ddf9" 1139 | dependencies = [ 1140 | "autocfg", 1141 | ] 1142 | 1143 | [[package]] 1144 | name = "mime" 1145 | version = "0.3.16" 1146 | source = "registry+https://github.com/rust-lang/crates.io-index" 1147 | checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" 1148 | 1149 | [[package]] 1150 | name = "miniz_oxide" 1151 | version = "0.4.4" 1152 | source = "registry+https://github.com/rust-lang/crates.io-index" 1153 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 1154 | dependencies = [ 1155 | "adler", 1156 | "autocfg", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "mio" 1161 | version = "0.7.14" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "8067b404fe97c70829f082dec8bcf4f71225d7eaea1d8645349cb76fa06205cc" 1164 | dependencies = [ 1165 | "libc", 1166 | "log", 1167 | "miow", 1168 | "ntapi", 1169 | "winapi", 1170 | ] 1171 | 1172 | [[package]] 1173 | name = "mio" 1174 | version = "0.8.4" 1175 | source = "registry+https://github.com/rust-lang/crates.io-index" 1176 | checksum = "57ee1c23c7c63b0c9250c339ffdc69255f110b298b901b9f6c82547b7b87caaf" 1177 | dependencies = [ 1178 | "libc", 1179 | "log", 1180 | "wasi 0.11.0+wasi-snapshot-preview1", 1181 | "windows-sys", 1182 | ] 1183 | 1184 | [[package]] 1185 | name = "miow" 1186 | version = "0.3.7" 1187 | source = "registry+https://github.com/rust-lang/crates.io-index" 1188 | checksum = "b9f1c5b025cda876f66ef43a113f91ebc9f4ccef34843000e0adf6ebbab84e21" 1189 | dependencies = [ 1190 | "winapi", 1191 | ] 1192 | 1193 | [[package]] 1194 | name = "nix" 1195 | version = "0.24.1" 1196 | source = "registry+https://github.com/rust-lang/crates.io-index" 1197 | checksum = "8f17df307904acd05aa8e32e97bb20f2a0df1728bbc2d771ae8f9a90463441e9" 1198 | dependencies = [ 1199 | "bitflags", 1200 | "cfg-if", 1201 | "libc", 1202 | "memoffset", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "ntapi" 1207 | version = "0.3.7" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "c28774a7fd2fbb4f0babd8237ce554b73af68021b5f695a3cebd6c59bac0980f" 1210 | dependencies = [ 1211 | "winapi", 1212 | ] 1213 | 1214 | [[package]] 1215 | name = "num-integer" 1216 | version = "0.1.44" 1217 | source = "registry+https://github.com/rust-lang/crates.io-index" 1218 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 1219 | dependencies = [ 1220 | "autocfg", 1221 | "num-traits", 1222 | ] 1223 | 1224 | [[package]] 1225 | name = "num-traits" 1226 | version = "0.2.14" 1227 | source = "registry+https://github.com/rust-lang/crates.io-index" 1228 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 1229 | dependencies = [ 1230 | "autocfg", 1231 | ] 1232 | 1233 | [[package]] 1234 | name = "num_cpus" 1235 | version = "1.13.0" 1236 | source = "registry+https://github.com/rust-lang/crates.io-index" 1237 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 1238 | dependencies = [ 1239 | "hermit-abi", 1240 | "libc", 1241 | ] 1242 | 1243 | [[package]] 1244 | name = "num_threads" 1245 | version = "0.1.6" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "2819ce041d2ee131036f4fc9d6ae7ae125a3a40e97ba64d04fe799ad9dabbb44" 1248 | dependencies = [ 1249 | "libc", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "once_cell" 1254 | version = "1.12.0" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" 1257 | 1258 | [[package]] 1259 | name = "openssl-probe" 1260 | version = "0.1.4" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "28988d872ab76095a6e6ac88d99b54fd267702734fd7ffe610ca27f533ddb95a" 1263 | 1264 | [[package]] 1265 | name = "openssl-sys" 1266 | version = "0.9.68" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "1c571f25d3f66dd427e417cebf73dbe2361d6125cf6e3a70d143fdf97c9f5150" 1269 | dependencies = [ 1270 | "autocfg", 1271 | "cc", 1272 | "libc", 1273 | "pkg-config", 1274 | "vcpkg", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "parity-scale-codec" 1279 | version = "3.1.5" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "9182e4a71cae089267ab03e67c99368db7cd877baf50f931e5d6d4b71e195ac0" 1282 | dependencies = [ 1283 | "arrayvec", 1284 | "bitvec", 1285 | "byte-slice-cast", 1286 | "impl-trait-for-tuples", 1287 | "parity-scale-codec-derive", 1288 | "serde", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "parity-scale-codec-derive" 1293 | version = "3.1.3" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "9299338969a3d2f491d65f140b00ddec470858402f888af98e8642fb5e8965cd" 1296 | dependencies = [ 1297 | "proc-macro-crate", 1298 | "proc-macro2", 1299 | "quote", 1300 | "syn", 1301 | ] 1302 | 1303 | [[package]] 1304 | name = "parking" 1305 | version = "2.0.0" 1306 | source = "registry+https://github.com/rust-lang/crates.io-index" 1307 | checksum = "427c3892f9e783d91cc128285287e70a59e206ca452770ece88a76f7a3eddd72" 1308 | 1309 | [[package]] 1310 | name = "parking_lot" 1311 | version = "0.11.2" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1314 | dependencies = [ 1315 | "instant", 1316 | "lock_api", 1317 | "parking_lot_core 0.8.5", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "parking_lot" 1322 | version = "0.12.1" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "3742b2c103b9f06bc9fff0a37ff4912935851bee6d36f3c02bcc755bcfec228f" 1325 | dependencies = [ 1326 | "lock_api", 1327 | "parking_lot_core 0.9.3", 1328 | ] 1329 | 1330 | [[package]] 1331 | name = "parking_lot_core" 1332 | version = "0.8.5" 1333 | source = "registry+https://github.com/rust-lang/crates.io-index" 1334 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 1335 | dependencies = [ 1336 | "cfg-if", 1337 | "instant", 1338 | "libc", 1339 | "redox_syscall", 1340 | "smallvec", 1341 | "winapi", 1342 | ] 1343 | 1344 | [[package]] 1345 | name = "parking_lot_core" 1346 | version = "0.9.3" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "09a279cbf25cb0757810394fbc1e359949b59e348145c643a939a525692e6929" 1349 | dependencies = [ 1350 | "cfg-if", 1351 | "libc", 1352 | "redox_syscall", 1353 | "smallvec", 1354 | "windows-sys", 1355 | ] 1356 | 1357 | [[package]] 1358 | name = "paste" 1359 | version = "1.0.7" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" 1362 | 1363 | [[package]] 1364 | name = "percent-encoding" 1365 | version = "2.1.0" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" 1368 | 1369 | [[package]] 1370 | name = "pest" 1371 | version = "2.1.3" 1372 | source = "registry+https://github.com/rust-lang/crates.io-index" 1373 | checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53" 1374 | dependencies = [ 1375 | "ucd-trie", 1376 | ] 1377 | 1378 | [[package]] 1379 | name = "pin-project" 1380 | version = "1.0.8" 1381 | source = "registry+https://github.com/rust-lang/crates.io-index" 1382 | checksum = "576bc800220cc65dac09e99e97b08b358cfab6e17078de8dc5fee223bd2d0c08" 1383 | dependencies = [ 1384 | "pin-project-internal", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "pin-project-internal" 1389 | version = "1.0.8" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "6e8fe8163d14ce7f0cdac2e040116f22eac817edabff0be91e8aff7e9accf389" 1392 | dependencies = [ 1393 | "proc-macro2", 1394 | "quote", 1395 | "syn", 1396 | ] 1397 | 1398 | [[package]] 1399 | name = "pin-project-lite" 1400 | version = "0.2.7" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "8d31d11c69a6b52a174b42bdc0c30e5e11670f90788b2c471c31c1d17d449443" 1403 | 1404 | [[package]] 1405 | name = "pin-utils" 1406 | version = "0.1.0" 1407 | source = "registry+https://github.com/rust-lang/crates.io-index" 1408 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 1409 | 1410 | [[package]] 1411 | name = "pkg-config" 1412 | version = "0.3.22" 1413 | source = "registry+https://github.com/rust-lang/crates.io-index" 1414 | checksum = "12295df4f294471248581bc09bef3c38a5e46f1e36d6a37353621a0c6c357e1f" 1415 | 1416 | [[package]] 1417 | name = "polling" 1418 | version = "2.1.0" 1419 | source = "registry+https://github.com/rust-lang/crates.io-index" 1420 | checksum = "92341d779fa34ea8437ef4d82d440d5e1ce3f3ff7f824aa64424cd481f9a1f25" 1421 | dependencies = [ 1422 | "cfg-if", 1423 | "libc", 1424 | "log", 1425 | "wepoll-ffi", 1426 | "winapi", 1427 | ] 1428 | 1429 | [[package]] 1430 | name = "ppv-lite86" 1431 | version = "0.2.14" 1432 | source = "registry+https://github.com/rust-lang/crates.io-index" 1433 | checksum = "c3ca011bd0129ff4ae15cd04c4eef202cadf6c51c21e47aba319b4e0501db741" 1434 | 1435 | [[package]] 1436 | name = "predicates" 1437 | version = "2.0.3" 1438 | source = "registry+https://github.com/rust-lang/crates.io-index" 1439 | checksum = "5c6ce811d0b2e103743eec01db1c50612221f173084ce2f7941053e94b6bb474" 1440 | dependencies = [ 1441 | "difflib", 1442 | "itertools", 1443 | "predicates-core", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "predicates-core" 1448 | version = "1.0.2" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "57e35a3326b75e49aa85f5dc6ec15b41108cf5aee58eabb1f274dd18b73c2451" 1451 | 1452 | [[package]] 1453 | name = "predicates-tree" 1454 | version = "1.0.4" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "338c7be2905b732ae3984a2f40032b5e94fd8f52505b186c7d4d68d193445df7" 1457 | dependencies = [ 1458 | "predicates-core", 1459 | "termtree", 1460 | ] 1461 | 1462 | [[package]] 1463 | name = "primitive-types" 1464 | version = "0.11.1" 1465 | source = "registry+https://github.com/rust-lang/crates.io-index" 1466 | checksum = "e28720988bff275df1f51b171e1b2a18c30d194c4d2b61defdacecd625a5d94a" 1467 | dependencies = [ 1468 | "fixed-hash", 1469 | "impl-codec", 1470 | "impl-rlp", 1471 | "impl-serde", 1472 | "uint", 1473 | ] 1474 | 1475 | [[package]] 1476 | name = "proc-macro-crate" 1477 | version = "1.1.3" 1478 | source = "registry+https://github.com/rust-lang/crates.io-index" 1479 | checksum = "e17d47ce914bf4de440332250b0edd23ce48c005f59fab39d3335866b114f11a" 1480 | dependencies = [ 1481 | "thiserror", 1482 | "toml", 1483 | ] 1484 | 1485 | [[package]] 1486 | name = "proc-macro-error" 1487 | version = "1.0.4" 1488 | source = "registry+https://github.com/rust-lang/crates.io-index" 1489 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1490 | dependencies = [ 1491 | "proc-macro-error-attr", 1492 | "proc-macro2", 1493 | "quote", 1494 | "syn", 1495 | "version_check", 1496 | ] 1497 | 1498 | [[package]] 1499 | name = "proc-macro-error-attr" 1500 | version = "1.0.4" 1501 | source = "registry+https://github.com/rust-lang/crates.io-index" 1502 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1503 | dependencies = [ 1504 | "proc-macro2", 1505 | "quote", 1506 | "version_check", 1507 | ] 1508 | 1509 | [[package]] 1510 | name = "proc-macro-hack" 1511 | version = "0.5.19" 1512 | source = "registry+https://github.com/rust-lang/crates.io-index" 1513 | checksum = "dbf0c48bc1d91375ae5c3cd81e3722dff1abcf81a30960240640d223f59fe0e5" 1514 | 1515 | [[package]] 1516 | name = "proc-macro2" 1517 | version = "1.0.40" 1518 | source = "registry+https://github.com/rust-lang/crates.io-index" 1519 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 1520 | dependencies = [ 1521 | "unicode-ident", 1522 | ] 1523 | 1524 | [[package]] 1525 | name = "quote" 1526 | version = "1.0.10" 1527 | source = "registry+https://github.com/rust-lang/crates.io-index" 1528 | checksum = "38bc8cc6a5f2e3655e0899c1b848643b2562f853f114bfec7be120678e3ace05" 1529 | dependencies = [ 1530 | "proc-macro2", 1531 | ] 1532 | 1533 | [[package]] 1534 | name = "radium" 1535 | version = "0.7.0" 1536 | source = "registry+https://github.com/rust-lang/crates.io-index" 1537 | checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" 1538 | 1539 | [[package]] 1540 | name = "rand" 1541 | version = "0.3.23" 1542 | source = "registry+https://github.com/rust-lang/crates.io-index" 1543 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 1544 | dependencies = [ 1545 | "libc", 1546 | "rand 0.4.6", 1547 | ] 1548 | 1549 | [[package]] 1550 | name = "rand" 1551 | version = "0.4.6" 1552 | source = "registry+https://github.com/rust-lang/crates.io-index" 1553 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 1554 | dependencies = [ 1555 | "fuchsia-cprng", 1556 | "libc", 1557 | "rand_core 0.3.1", 1558 | "rdrand", 1559 | "winapi", 1560 | ] 1561 | 1562 | [[package]] 1563 | name = "rand" 1564 | version = "0.8.5" 1565 | source = "registry+https://github.com/rust-lang/crates.io-index" 1566 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 1567 | dependencies = [ 1568 | "libc", 1569 | "rand_chacha", 1570 | "rand_core 0.6.3", 1571 | ] 1572 | 1573 | [[package]] 1574 | name = "rand_chacha" 1575 | version = "0.3.1" 1576 | source = "registry+https://github.com/rust-lang/crates.io-index" 1577 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 1578 | dependencies = [ 1579 | "ppv-lite86", 1580 | "rand_core 0.6.3", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "rand_core" 1585 | version = "0.3.1" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 1588 | dependencies = [ 1589 | "rand_core 0.4.2", 1590 | ] 1591 | 1592 | [[package]] 1593 | name = "rand_core" 1594 | version = "0.4.2" 1595 | source = "registry+https://github.com/rust-lang/crates.io-index" 1596 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 1597 | 1598 | [[package]] 1599 | name = "rand_core" 1600 | version = "0.6.3" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" 1603 | dependencies = [ 1604 | "getrandom", 1605 | ] 1606 | 1607 | [[package]] 1608 | name = "rdrand" 1609 | version = "0.4.0" 1610 | source = "registry+https://github.com/rust-lang/crates.io-index" 1611 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1612 | dependencies = [ 1613 | "rand_core 0.3.1", 1614 | ] 1615 | 1616 | [[package]] 1617 | name = "redox_syscall" 1618 | version = "0.2.10" 1619 | source = "registry+https://github.com/rust-lang/crates.io-index" 1620 | checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff" 1621 | dependencies = [ 1622 | "bitflags", 1623 | ] 1624 | 1625 | [[package]] 1626 | name = "regex" 1627 | version = "1.5.6" 1628 | source = "registry+https://github.com/rust-lang/crates.io-index" 1629 | checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" 1630 | dependencies = [ 1631 | "aho-corasick", 1632 | "memchr", 1633 | "regex-syntax", 1634 | ] 1635 | 1636 | [[package]] 1637 | name = "regex-automata" 1638 | version = "0.1.10" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" 1641 | 1642 | [[package]] 1643 | name = "regex-syntax" 1644 | version = "0.6.26" 1645 | source = "registry+https://github.com/rust-lang/crates.io-index" 1646 | checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" 1647 | 1648 | [[package]] 1649 | name = "rlp" 1650 | version = "0.5.1" 1651 | source = "registry+https://github.com/rust-lang/crates.io-index" 1652 | checksum = "999508abb0ae792aabed2460c45b89106d97fe4adac593bdaef433c2605847b5" 1653 | dependencies = [ 1654 | "bytes", 1655 | "rustc-hex", 1656 | ] 1657 | 1658 | [[package]] 1659 | name = "rust-crypto" 1660 | version = "0.2.36" 1661 | source = "registry+https://github.com/rust-lang/crates.io-index" 1662 | checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 1663 | dependencies = [ 1664 | "gcc", 1665 | "libc", 1666 | "rand 0.3.23", 1667 | "rustc-serialize", 1668 | "time 0.1.44", 1669 | ] 1670 | 1671 | [[package]] 1672 | name = "rust_blockchain" 1673 | version = "0.4.0" 1674 | dependencies = [ 1675 | "actix-web", 1676 | "anyhow", 1677 | "assert_cmd", 1678 | "cargo-husky", 1679 | "chrono", 1680 | "crossbeam-utils", 1681 | "ctrlc", 1682 | "dotenv", 1683 | "dotenv_codegen", 1684 | "env_logger", 1685 | "ethereum-types", 1686 | "futures", 1687 | "hex", 1688 | "isahc", 1689 | "log", 1690 | "nix", 1691 | "rust-crypto", 1692 | "serde", 1693 | "serde_json", 1694 | "serial_test", 1695 | "thiserror", 1696 | ] 1697 | 1698 | [[package]] 1699 | name = "rustc-hex" 1700 | version = "2.1.0" 1701 | source = "registry+https://github.com/rust-lang/crates.io-index" 1702 | checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" 1703 | 1704 | [[package]] 1705 | name = "rustc-serialize" 1706 | version = "0.3.24" 1707 | source = "registry+https://github.com/rust-lang/crates.io-index" 1708 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 1709 | 1710 | [[package]] 1711 | name = "rustc_version" 1712 | version = "0.3.3" 1713 | source = "registry+https://github.com/rust-lang/crates.io-index" 1714 | checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" 1715 | dependencies = [ 1716 | "semver", 1717 | ] 1718 | 1719 | [[package]] 1720 | name = "rustversion" 1721 | version = "1.0.7" 1722 | source = "registry+https://github.com/rust-lang/crates.io-index" 1723 | checksum = "a0a5f7c728f5d284929a1cccb5bc19884422bfe6ef4d6c409da2c41838983fcf" 1724 | 1725 | [[package]] 1726 | name = "ryu" 1727 | version = "1.0.5" 1728 | source = "registry+https://github.com/rust-lang/crates.io-index" 1729 | checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" 1730 | 1731 | [[package]] 1732 | name = "schannel" 1733 | version = "0.1.19" 1734 | source = "registry+https://github.com/rust-lang/crates.io-index" 1735 | checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75" 1736 | dependencies = [ 1737 | "lazy_static", 1738 | "winapi", 1739 | ] 1740 | 1741 | [[package]] 1742 | name = "scopeguard" 1743 | version = "1.1.0" 1744 | source = "registry+https://github.com/rust-lang/crates.io-index" 1745 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1746 | 1747 | [[package]] 1748 | name = "semver" 1749 | version = "0.11.0" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" 1752 | dependencies = [ 1753 | "semver-parser", 1754 | ] 1755 | 1756 | [[package]] 1757 | name = "semver-parser" 1758 | version = "0.10.2" 1759 | source = "registry+https://github.com/rust-lang/crates.io-index" 1760 | checksum = "00b0bef5b7f9e0df16536d3961cfb6e84331c065b4066afb39768d0e319411f7" 1761 | dependencies = [ 1762 | "pest", 1763 | ] 1764 | 1765 | [[package]] 1766 | name = "serde" 1767 | version = "1.0.137" 1768 | source = "registry+https://github.com/rust-lang/crates.io-index" 1769 | checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" 1770 | dependencies = [ 1771 | "serde_derive", 1772 | ] 1773 | 1774 | [[package]] 1775 | name = "serde_derive" 1776 | version = "1.0.137" 1777 | source = "registry+https://github.com/rust-lang/crates.io-index" 1778 | checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" 1779 | dependencies = [ 1780 | "proc-macro2", 1781 | "quote", 1782 | "syn", 1783 | ] 1784 | 1785 | [[package]] 1786 | name = "serde_json" 1787 | version = "1.0.81" 1788 | source = "registry+https://github.com/rust-lang/crates.io-index" 1789 | checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" 1790 | dependencies = [ 1791 | "itoa 1.0.2", 1792 | "ryu", 1793 | "serde", 1794 | ] 1795 | 1796 | [[package]] 1797 | name = "serde_urlencoded" 1798 | version = "0.7.0" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "edfa57a7f8d9c1d260a549e7224100f6c43d43f9103e06dd8b4095a9b2b43ce9" 1801 | dependencies = [ 1802 | "form_urlencoded", 1803 | "itoa 0.4.8", 1804 | "ryu", 1805 | "serde", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "serial_test" 1810 | version = "0.7.0" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "d19dbfb999a147cedbfe82f042eb9555f5b0fa4ef95ee4570b74349103d9c9f4" 1813 | dependencies = [ 1814 | "lazy_static", 1815 | "log", 1816 | "parking_lot 0.12.1", 1817 | "serial_test_derive", 1818 | ] 1819 | 1820 | [[package]] 1821 | name = "serial_test_derive" 1822 | version = "0.7.0" 1823 | source = "registry+https://github.com/rust-lang/crates.io-index" 1824 | checksum = "cb9e2050b2be1d681f8f1c1a528bcfe4e00afa2d8995f713974f5333288659f2" 1825 | dependencies = [ 1826 | "proc-macro-error", 1827 | "proc-macro2", 1828 | "quote", 1829 | "rustversion", 1830 | "syn", 1831 | ] 1832 | 1833 | [[package]] 1834 | name = "sha1" 1835 | version = "0.10.1" 1836 | source = "registry+https://github.com/rust-lang/crates.io-index" 1837 | checksum = "c77f4e7f65455545c2153c1253d25056825e77ee2533f0e41deb65a93a34852f" 1838 | dependencies = [ 1839 | "cfg-if", 1840 | "cpufeatures", 1841 | "digest", 1842 | ] 1843 | 1844 | [[package]] 1845 | name = "signal-hook-registry" 1846 | version = "1.4.0" 1847 | source = "registry+https://github.com/rust-lang/crates.io-index" 1848 | checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0" 1849 | dependencies = [ 1850 | "libc", 1851 | ] 1852 | 1853 | [[package]] 1854 | name = "slab" 1855 | version = "0.4.5" 1856 | source = "registry+https://github.com/rust-lang/crates.io-index" 1857 | checksum = "9def91fd1e018fe007022791f865d0ccc9b3a0d5001e01aabb8b40e46000afb5" 1858 | 1859 | [[package]] 1860 | name = "sluice" 1861 | version = "0.5.5" 1862 | source = "registry+https://github.com/rust-lang/crates.io-index" 1863 | checksum = "6d7400c0eff44aa2fcb5e31a5f24ba9716ed90138769e4977a2ba6014ae63eb5" 1864 | dependencies = [ 1865 | "async-channel", 1866 | "futures-core", 1867 | "futures-io", 1868 | ] 1869 | 1870 | [[package]] 1871 | name = "smallvec" 1872 | version = "1.7.0" 1873 | source = "registry+https://github.com/rust-lang/crates.io-index" 1874 | checksum = "1ecab6c735a6bb4139c0caafd0cc3635748bbb3acf4550e8138122099251f309" 1875 | 1876 | [[package]] 1877 | name = "socket2" 1878 | version = "0.4.2" 1879 | source = "registry+https://github.com/rust-lang/crates.io-index" 1880 | checksum = "5dc90fe6c7be1a323296982db1836d1ea9e47b6839496dde9a541bc496df3516" 1881 | dependencies = [ 1882 | "libc", 1883 | "winapi", 1884 | ] 1885 | 1886 | [[package]] 1887 | name = "static_assertions" 1888 | version = "1.1.0" 1889 | source = "registry+https://github.com/rust-lang/crates.io-index" 1890 | checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" 1891 | 1892 | [[package]] 1893 | name = "syn" 1894 | version = "1.0.98" 1895 | source = "registry+https://github.com/rust-lang/crates.io-index" 1896 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 1897 | dependencies = [ 1898 | "proc-macro2", 1899 | "quote", 1900 | "unicode-ident", 1901 | ] 1902 | 1903 | [[package]] 1904 | name = "tap" 1905 | version = "1.0.1" 1906 | source = "registry+https://github.com/rust-lang/crates.io-index" 1907 | checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" 1908 | 1909 | [[package]] 1910 | name = "termcolor" 1911 | version = "1.1.2" 1912 | source = "registry+https://github.com/rust-lang/crates.io-index" 1913 | checksum = "2dfed899f0eb03f32ee8c6a0aabdb8a7949659e3466561fc0adf54e26d88c5f4" 1914 | dependencies = [ 1915 | "winapi-util", 1916 | ] 1917 | 1918 | [[package]] 1919 | name = "termtree" 1920 | version = "0.2.3" 1921 | source = "registry+https://github.com/rust-lang/crates.io-index" 1922 | checksum = "13a4ec180a2de59b57434704ccfad967f789b12737738798fa08798cd5824c16" 1923 | 1924 | [[package]] 1925 | name = "thiserror" 1926 | version = "1.0.31" 1927 | source = "registry+https://github.com/rust-lang/crates.io-index" 1928 | checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" 1929 | dependencies = [ 1930 | "thiserror-impl", 1931 | ] 1932 | 1933 | [[package]] 1934 | name = "thiserror-impl" 1935 | version = "1.0.31" 1936 | source = "registry+https://github.com/rust-lang/crates.io-index" 1937 | checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" 1938 | dependencies = [ 1939 | "proc-macro2", 1940 | "quote", 1941 | "syn", 1942 | ] 1943 | 1944 | [[package]] 1945 | name = "time" 1946 | version = "0.1.44" 1947 | source = "registry+https://github.com/rust-lang/crates.io-index" 1948 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1949 | dependencies = [ 1950 | "libc", 1951 | "wasi 0.10.0+wasi-snapshot-preview1", 1952 | "winapi", 1953 | ] 1954 | 1955 | [[package]] 1956 | name = "time" 1957 | version = "0.3.11" 1958 | source = "registry+https://github.com/rust-lang/crates.io-index" 1959 | checksum = "72c91f41dcb2f096c05f0873d667dceec1087ce5bcf984ec8ffb19acddbb3217" 1960 | dependencies = [ 1961 | "itoa 1.0.2", 1962 | "libc", 1963 | "num_threads", 1964 | "time-macros", 1965 | ] 1966 | 1967 | [[package]] 1968 | name = "time-macros" 1969 | version = "0.2.4" 1970 | source = "registry+https://github.com/rust-lang/crates.io-index" 1971 | checksum = "42657b1a6f4d817cda8e7a0ace261fe0cc946cf3a80314390b22cc61ae080792" 1972 | 1973 | [[package]] 1974 | name = "tiny-keccak" 1975 | version = "2.0.2" 1976 | source = "registry+https://github.com/rust-lang/crates.io-index" 1977 | checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" 1978 | dependencies = [ 1979 | "crunchy", 1980 | ] 1981 | 1982 | [[package]] 1983 | name = "tinyvec" 1984 | version = "1.5.0" 1985 | source = "registry+https://github.com/rust-lang/crates.io-index" 1986 | checksum = "f83b2a3d4d9091d0abd7eba4dc2710b1718583bd4d8992e2190720ea38f391f7" 1987 | dependencies = [ 1988 | "tinyvec_macros", 1989 | ] 1990 | 1991 | [[package]] 1992 | name = "tinyvec_macros" 1993 | version = "0.1.0" 1994 | source = "registry+https://github.com/rust-lang/crates.io-index" 1995 | checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" 1996 | 1997 | [[package]] 1998 | name = "tokio" 1999 | version = "1.16.1" 2000 | source = "registry+https://github.com/rust-lang/crates.io-index" 2001 | checksum = "0c27a64b625de6d309e8c57716ba93021dccf1b3b5c97edd6d3dd2d2135afc0a" 2002 | dependencies = [ 2003 | "bytes", 2004 | "libc", 2005 | "memchr", 2006 | "mio 0.7.14", 2007 | "once_cell", 2008 | "parking_lot 0.11.2", 2009 | "pin-project-lite", 2010 | "signal-hook-registry", 2011 | "winapi", 2012 | ] 2013 | 2014 | [[package]] 2015 | name = "tokio-util" 2016 | version = "0.7.2" 2017 | source = "registry+https://github.com/rust-lang/crates.io-index" 2018 | checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" 2019 | dependencies = [ 2020 | "bytes", 2021 | "futures-core", 2022 | "futures-sink", 2023 | "pin-project-lite", 2024 | "tokio", 2025 | "tracing", 2026 | ] 2027 | 2028 | [[package]] 2029 | name = "toml" 2030 | version = "0.5.9" 2031 | source = "registry+https://github.com/rust-lang/crates.io-index" 2032 | checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" 2033 | dependencies = [ 2034 | "serde", 2035 | ] 2036 | 2037 | [[package]] 2038 | name = "tracing" 2039 | version = "0.1.35" 2040 | source = "registry+https://github.com/rust-lang/crates.io-index" 2041 | checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" 2042 | dependencies = [ 2043 | "cfg-if", 2044 | "log", 2045 | "pin-project-lite", 2046 | "tracing-attributes", 2047 | "tracing-core", 2048 | ] 2049 | 2050 | [[package]] 2051 | name = "tracing-attributes" 2052 | version = "0.1.21" 2053 | source = "registry+https://github.com/rust-lang/crates.io-index" 2054 | checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" 2055 | dependencies = [ 2056 | "proc-macro2", 2057 | "quote", 2058 | "syn", 2059 | ] 2060 | 2061 | [[package]] 2062 | name = "tracing-core" 2063 | version = "0.1.28" 2064 | source = "registry+https://github.com/rust-lang/crates.io-index" 2065 | checksum = "7b7358be39f2f274f322d2aaed611acc57f382e8eb1e5b48cb9ae30933495ce7" 2066 | dependencies = [ 2067 | "once_cell", 2068 | ] 2069 | 2070 | [[package]] 2071 | name = "tracing-futures" 2072 | version = "0.2.5" 2073 | source = "registry+https://github.com/rust-lang/crates.io-index" 2074 | checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" 2075 | dependencies = [ 2076 | "pin-project", 2077 | "tracing", 2078 | ] 2079 | 2080 | [[package]] 2081 | name = "typenum" 2082 | version = "1.14.0" 2083 | source = "registry+https://github.com/rust-lang/crates.io-index" 2084 | checksum = "b63708a265f51345575b27fe43f9500ad611579e764c79edbc2037b1121959ec" 2085 | 2086 | [[package]] 2087 | name = "ucd-trie" 2088 | version = "0.1.3" 2089 | source = "registry+https://github.com/rust-lang/crates.io-index" 2090 | checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c" 2091 | 2092 | [[package]] 2093 | name = "uint" 2094 | version = "0.9.3" 2095 | source = "registry+https://github.com/rust-lang/crates.io-index" 2096 | checksum = "12f03af7ccf01dd611cc450a0d10dbc9b745770d096473e2faf0ca6e2d66d1e0" 2097 | dependencies = [ 2098 | "byteorder", 2099 | "crunchy", 2100 | "hex", 2101 | "static_assertions", 2102 | ] 2103 | 2104 | [[package]] 2105 | name = "unicode-bidi" 2106 | version = "0.3.7" 2107 | source = "registry+https://github.com/rust-lang/crates.io-index" 2108 | checksum = "1a01404663e3db436ed2746d9fefef640d868edae3cceb81c3b8d5732fda678f" 2109 | 2110 | [[package]] 2111 | name = "unicode-ident" 2112 | version = "1.0.1" 2113 | source = "registry+https://github.com/rust-lang/crates.io-index" 2114 | checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" 2115 | 2116 | [[package]] 2117 | name = "unicode-normalization" 2118 | version = "0.1.19" 2119 | source = "registry+https://github.com/rust-lang/crates.io-index" 2120 | checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" 2121 | dependencies = [ 2122 | "tinyvec", 2123 | ] 2124 | 2125 | [[package]] 2126 | name = "url" 2127 | version = "2.2.2" 2128 | source = "registry+https://github.com/rust-lang/crates.io-index" 2129 | checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" 2130 | dependencies = [ 2131 | "form_urlencoded", 2132 | "idna", 2133 | "matches", 2134 | "percent-encoding", 2135 | ] 2136 | 2137 | [[package]] 2138 | name = "vcpkg" 2139 | version = "0.2.15" 2140 | source = "registry+https://github.com/rust-lang/crates.io-index" 2141 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 2142 | 2143 | [[package]] 2144 | name = "version_check" 2145 | version = "0.9.3" 2146 | source = "registry+https://github.com/rust-lang/crates.io-index" 2147 | checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe" 2148 | 2149 | [[package]] 2150 | name = "wait-timeout" 2151 | version = "0.2.0" 2152 | source = "registry+https://github.com/rust-lang/crates.io-index" 2153 | checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" 2154 | dependencies = [ 2155 | "libc", 2156 | ] 2157 | 2158 | [[package]] 2159 | name = "waker-fn" 2160 | version = "1.1.0" 2161 | source = "registry+https://github.com/rust-lang/crates.io-index" 2162 | checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca" 2163 | 2164 | [[package]] 2165 | name = "wasi" 2166 | version = "0.10.0+wasi-snapshot-preview1" 2167 | source = "registry+https://github.com/rust-lang/crates.io-index" 2168 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 2169 | 2170 | [[package]] 2171 | name = "wasi" 2172 | version = "0.11.0+wasi-snapshot-preview1" 2173 | source = "registry+https://github.com/rust-lang/crates.io-index" 2174 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 2175 | 2176 | [[package]] 2177 | name = "wepoll-ffi" 2178 | version = "0.1.2" 2179 | source = "registry+https://github.com/rust-lang/crates.io-index" 2180 | checksum = "d743fdedc5c64377b5fc2bc036b01c7fd642205a0d96356034ae3404d49eb7fb" 2181 | dependencies = [ 2182 | "cc", 2183 | ] 2184 | 2185 | [[package]] 2186 | name = "winapi" 2187 | version = "0.3.9" 2188 | source = "registry+https://github.com/rust-lang/crates.io-index" 2189 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 2190 | dependencies = [ 2191 | "winapi-i686-pc-windows-gnu", 2192 | "winapi-x86_64-pc-windows-gnu", 2193 | ] 2194 | 2195 | [[package]] 2196 | name = "winapi-i686-pc-windows-gnu" 2197 | version = "0.4.0" 2198 | source = "registry+https://github.com/rust-lang/crates.io-index" 2199 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 2200 | 2201 | [[package]] 2202 | name = "winapi-util" 2203 | version = "0.1.5" 2204 | source = "registry+https://github.com/rust-lang/crates.io-index" 2205 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 2206 | dependencies = [ 2207 | "winapi", 2208 | ] 2209 | 2210 | [[package]] 2211 | name = "winapi-x86_64-pc-windows-gnu" 2212 | version = "0.4.0" 2213 | source = "registry+https://github.com/rust-lang/crates.io-index" 2214 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 2215 | 2216 | [[package]] 2217 | name = "windows-sys" 2218 | version = "0.36.1" 2219 | source = "registry+https://github.com/rust-lang/crates.io-index" 2220 | checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" 2221 | dependencies = [ 2222 | "windows_aarch64_msvc", 2223 | "windows_i686_gnu", 2224 | "windows_i686_msvc", 2225 | "windows_x86_64_gnu", 2226 | "windows_x86_64_msvc", 2227 | ] 2228 | 2229 | [[package]] 2230 | name = "windows_aarch64_msvc" 2231 | version = "0.36.1" 2232 | source = "registry+https://github.com/rust-lang/crates.io-index" 2233 | checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" 2234 | 2235 | [[package]] 2236 | name = "windows_i686_gnu" 2237 | version = "0.36.1" 2238 | source = "registry+https://github.com/rust-lang/crates.io-index" 2239 | checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" 2240 | 2241 | [[package]] 2242 | name = "windows_i686_msvc" 2243 | version = "0.36.1" 2244 | source = "registry+https://github.com/rust-lang/crates.io-index" 2245 | checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" 2246 | 2247 | [[package]] 2248 | name = "windows_x86_64_gnu" 2249 | version = "0.36.1" 2250 | source = "registry+https://github.com/rust-lang/crates.io-index" 2251 | checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" 2252 | 2253 | [[package]] 2254 | name = "windows_x86_64_msvc" 2255 | version = "0.36.1" 2256 | source = "registry+https://github.com/rust-lang/crates.io-index" 2257 | checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" 2258 | 2259 | [[package]] 2260 | name = "wyz" 2261 | version = "0.5.0" 2262 | source = "registry+https://github.com/rust-lang/crates.io-index" 2263 | checksum = "30b31594f29d27036c383b53b59ed3476874d518f0efb151b27a4c275141390e" 2264 | dependencies = [ 2265 | "tap", 2266 | ] 2267 | 2268 | [[package]] 2269 | name = "zstd" 2270 | version = "0.11.2+zstd.1.5.2" 2271 | source = "registry+https://github.com/rust-lang/crates.io-index" 2272 | checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" 2273 | dependencies = [ 2274 | "zstd-safe", 2275 | ] 2276 | 2277 | [[package]] 2278 | name = "zstd-safe" 2279 | version = "5.0.2+zstd.1.5.2" 2280 | source = "registry+https://github.com/rust-lang/crates.io-index" 2281 | checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" 2282 | dependencies = [ 2283 | "libc", 2284 | "zstd-sys", 2285 | ] 2286 | 2287 | [[package]] 2288 | name = "zstd-sys" 2289 | version = "2.0.1+zstd.1.5.2" 2290 | source = "registry+https://github.com/rust-lang/crates.io-index" 2291 | checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" 2292 | dependencies = [ 2293 | "cc", 2294 | "libc", 2295 | ] 2296 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rust_blockchain" 3 | version = "0.4.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | actix-web = "4.1.0" 8 | anyhow = "1.0.58" 9 | chrono = "0.4.19" 10 | crossbeam-utils = "0.8.10" 11 | ctrlc = { version = "3.2.2", features = ["termination"] } 12 | dotenv = "0.15.0" 13 | dotenv_codegen = "0.15.0" 14 | env_logger = "0.9.0" 15 | ethereum-types = "0.13.1" 16 | futures = "0.3.21" 17 | hex = "0.4.3" 18 | isahc = "1.7.2" 19 | log = "0.4.17" 20 | rust-crypto = "0.2.36" 21 | serde = { version = "1.0.137", features = ["derive"] } 22 | serde_json = "1.0.81" 23 | thiserror = "1.0.31" 24 | 25 | [dev-dependencies] 26 | assert_cmd = "2.0.4" 27 | nix = "0.24.1" 28 | serial_test = "0.7.0" 29 | 30 | [dev-dependencies.cargo-husky] 31 | version = "1.5" 32 | default-features = false 33 | features = ["precommit-hook", "run-cargo-clippy", "run-cargo-fmt", "run-cargo-check", "run-cargo-test"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-blockchain 2 | 3 | ![example workflow](https://github.com/mrnaveira/rust-blockchain/actions/workflows/build.yaml/badge.svg) ![example workflow](https://github.com/mrnaveira/rust-blockchain/actions/workflows/lint.yaml/badge.svg) ![example workflow](https://github.com/mrnaveira/rust-blockchain/actions/workflows/test.yaml/badge.svg) [![Coverage Status](https://coveralls.io/repos/github/mrnaveira/rust-blockchain/badge.svg?service=github)](https://coveralls.io/github/mrnaveira/rust-blockchain) 4 | 5 | A Proof of Work blockchain written in Rust. For educational purposes only. 6 | 7 | Features: 8 | * Defines data structures to model a minimum blockchain 9 | * Mines new blocks in a separate thread, running a Proof of Work algorithm with a fixed difficulty 10 | * Synchronizes new blocks with peer nodes in a decentralized network 11 | * Provides a REST API to retrieve the blocks and add transactions 12 | 13 | ## Getting Started 14 | You will need Rust and Cargo installed. 15 | 16 | ```bash 17 | # Download the code 18 | $ git clone https://github.com/mrnaveira/rust-blockchain 19 | $ cd rust-blockchain 20 | 21 | # Run all tests 22 | $ cargo test 23 | 24 | # Build the project in release mode 25 | $ cargo build --release 26 | 27 | # Run the application 28 | $ ./target/release/rust_blockchain 29 | ``` 30 | 31 | The application will start mining and listening on port `8000` for incoming client requests via a REST API. To change any environment variable (port, mining parameters, etc.) refer to the `.env.example` file. 32 | 33 | For development setup, check the [development notes section](#development-notes). 34 | 35 | ## Client REST API 36 | The application provides a REST API for clients to operate with the blockchain. 37 | 38 | | Method | URL | Description 39 | | --- | --- | --- | 40 | | GET | /blocks | List all blocks of the blockchain 41 | | POST | /blocks | Append a new block to the blockchain 42 | | POST | /transactions | Add a new transaction to the pool 43 | 44 | The file `doc/rest_api.postman_collection.json` contains a Postman collection with examples of all requests. 45 | 46 | ## Block Structure 47 | 48 | In a blockchain, transactions are grouped into blocks. Aside from transactions, a block contains metadata needed to secure and maintain the sequence in the chain. This sequence of blocks is key to allow transactions to occur in order. 49 | 50 | The `model` module in this project contains the data structures to model the blockchain, as described in the next diagram: 51 | 52 | ![Blockchain structure diagram](./doc/blockchain_structure.png) 53 | 54 | Each block contains the following data: 55 | * **index**: position of the block in the blockchain 56 | * **timestamp**: date and time of block creation 57 | * **nonce**: arbitrary number that makes the block, when hashed, meet the mining difficulty restriction. Is the number that miners are competing to get first 58 | * **previous_hash**: hash of the previous block in the chain. Allows to maintain order of blocks in the blockchain. There is an exception with the first block of the chain (genesis block) which has no previous_hash 59 | * **hash**: hash of the block including all fields 60 | * **transactions**: a list of all transactions included in the block. Each transaction has a **sender**, **recipient** and **amount**. 61 | 62 | ## Proof of Work 63 | 64 | Proof of Work (PoW) is a common consensus algorithm used widely in most cryptocurrencies like Bitcoin. A participant node in the network that wants to add new transactions in the blockchain (and get the rewards for it) must prove that a certain amount of computational work has been done. This work can take a large amount of time to do but at the same time it's very easy to validate by other nodes. 65 | 66 | This prevents the double spending problem by forcing any attacker that wants to remove or modify a transaction to redo all the computational work from the target block to the current one. The attacker must have a larger computational capacity than the rest of the network combined to be able to achieve it (51% attack). 67 | 68 | This project implements a simplified PoW algorithm based on hashes, in the line of what Bitcoin does. The `miner.rs` file implements the steps to create a valid block: 69 | 1. All transactions in the pool are added to the block. If there is no transactions in the pool, do not mine until they arrive. 70 | 2. The block contains the valid index and timestamp, as well as the **hash of the previous block** to maintain order. 71 | 3. Iterate the **nonce** value until the hash of the whole block satisfies the difficulty constraint, which is to be less than a target value. The difficulty target is fixed for the execution of the server, but in a real project we would want dynamic difficulty adjusted in runtime to have constant time intervals between blocks. 72 | 4. When a valid block is found, add it to the blockchain and repeat from step 1 to create the next block. 73 | 74 | ## Development notes 75 | 76 | ### Git hooks 77 | This project uses [cargo-husky](https://github.com/rhysd/cargo-husky) to setup a Git pre-commit hook to check code style (using [clippy](https://github.com/rust-lang/rust-clippy) and [rustfmt](https://github.com/rust-lang/rustfmt)), cargo dependencies and run all tests. If any of those tasks fails, the hook prevents you to commit the changes. 78 | 79 | To automatically create the hooks in your local git repository, simply run all tests the first time: 80 | ```bash 81 | $ cargo test 82 | ``` 83 | 84 | ### GitHub Actions 85 | There are also multiple GitHub Actions (using [actions-rs](https://github.com/actions-rs)) under the `.github/workflows` folder, as a form of CI. On each commit or PR they perform similar checks as the Git hooks (clippy/rustfmt, dependencies/build and test) plus the test coverage (explained in the [coverage section](#test-coverage) ). The results are displayed as badges below the title of this README. 86 | 87 | ### Test organization 88 | The test organization follows the [recommended guidelines for Rust](https://doc.rust-lang.org/book/ch11-03-test-organization.html): 89 | * **Unit tests** are located inside the file with the code they're testing, inside a module annotated with `cfg(test)`. 90 | * **Integration tests** are located inside the `tests` folder. This project is a server application and not a library, so the integration tests run the server in a child OS thread, perform real REST API calls and then terminate the process. This way we test all parts of the application using only the REST API, treating it as a black box. 91 | 92 | ### Test coverage 93 | To generate the test coverage report, at the moment it's required to use the nightly version of Rust. Also you need to install `grconv` and `llvm-tools`. 94 | The detailed instructions are [in the grcov repository](https://github.com/mozilla/grcov#example-how-to-generate-source-based-coverage-for-a-rust-project) as well as in the `scripts/coverage_report.sh` script. 95 | 96 | Then, each time we want to to generate the coverage report, we simply execute the script: 97 | ```bash 98 | $ ./scripts/coverage_report.sh 99 | ``` 100 | 101 | The results will be availabe under the `coverage` folder for inspection. Also, there is a GitHub Action (in `.github/workflows/coverage.yaml`) that will automatically calculate it on every push to `origin` and display the coverage in a badge under the title of this README. 102 | 103 | ### Concurrency implementation 104 | 105 | In this project, the `main` thread spawns three OS threads: 106 | * One for the **miner**. As mining is very computationally-intensive, we want a dedicated OS thread to not slow down other operations in the application. In a real blockchain we would also want parallel mining (by handling a different subrange of nonces in each thread), but for simplicity we will only use one thread. 107 | * Other thread for the **REST API**. The API uses [`actix-web`](https://github.com/actix/actix-web), which internally uses [`tokio`](https://crates.io/crates/tokio), so it's optimized for asynchronous operations. 108 | * A thread for the **peer system**, that periodically sends and receives new blocks from peers over the network. 109 | 110 | Thread spawning and handling is implemented using [`crossbeam-utils`](https://crates.io/crates/crossbeam-utils) to reduce boilerplate code from the standard library. 111 | 112 | Also, all threads share data, specifically the **block list** and the **transaction pool**. Those two data structures are implemented by using `Arc` to allow multiple concurrent writes and reads in a safe way from separate threads. 113 | 114 | ## Roadmap 115 | 116 | - [x] Boilerplate REST API in Rust 117 | - [x] Structs to represent blockchain data 118 | - [x] API methods to add transactions and check blocks 119 | - [x] Transaction pool that holds not realized transactions 120 | - [x] Basic miner that adds transactions every N seconds 121 | - [x] Basic PoW implementation: nonce, miner calculates hashes and fixed difficulty 122 | - [x] Mining peers communicate new blocks over the network 123 | - [x] Block subsidy 124 | - [x] Validate transaction balances 125 | - [ ] Transaction fees 126 | - [ ] Dynamic difficulty (aiming for constant time intervals between blocks) 127 | - [ ] Halving 128 | - [ ] Blockchain disk storage 129 | - [ ] Digital signing of transactions -------------------------------------------------------------------------------- /doc/blockchain_structure.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mrnaveira/rust-blockchain/c529652132a68c7faf8b4869aaebf032e7757d02/doc/blockchain_structure.png -------------------------------------------------------------------------------- /doc/rest_api.postman_collection.json: -------------------------------------------------------------------------------- 1 | { 2 | "variables": [], 3 | "info": { 4 | "name": "rust-blockchain", 5 | "_postman_id": "48bc9439-7670-da27-ee9e-8c3ff3c5cc79", 6 | "description": "", 7 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json" 8 | }, 9 | "item": [ 10 | { 11 | "name": "Get all blocks of the blockchain", 12 | "request": { 13 | "url": "http://localhost:8000/blocks", 14 | "method": "GET", 15 | "header": [], 16 | "body": { 17 | "mode": "raw", 18 | "raw": "" 19 | }, 20 | "description": "" 21 | }, 22 | "response": [] 23 | }, 24 | { 25 | "name": "Add a new block", 26 | "request": { 27 | "url": "http://localhost:8000/blocks", 28 | "method": "POST", 29 | "header": [ 30 | { 31 | "key": "Content-Type", 32 | "value": "application/json" 33 | } 34 | ], 35 | "body": { 36 | "mode": "raw", 37 | "raw": "{\n \"index\": 1,\n \"timestamp\": 0,\n \"nonce\": 0,\n \"previous_hash\": \"0x0\",\n \"hash\": \"0x0\",\n \"transactions\": [\n {\n \"sender\": \"0\",\n \"recipient\": \"1\",\n \"amount\": 1000\n },\n {\n \"sender\": \"0\",\n \"recipient\": \"2\",\n \"amount\": 1000\n }\n ]\n}" 38 | }, 39 | "description": "" 40 | }, 41 | "response": [] 42 | }, 43 | { 44 | "name": "Add a new transaction to the pool", 45 | "request": { 46 | "url": "http://localhost:8000/transactions", 47 | "method": "POST", 48 | "header": [ 49 | { 50 | "key": "Content-Type", 51 | "value": "application/json", 52 | "description": "" 53 | } 54 | ], 55 | "body": { 56 | "mode": "raw", 57 | "raw": "{\n \"sender\": \"f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e\",\n \"recipient\": \"51df097c03c0a6e64e54a6fce90cb6968adebd85955917ed438e3d3c05f2f00f\",\n \"amount\": 1002\n}" 58 | }, 59 | "description": "" 60 | }, 61 | "response": [] 62 | } 63 | ] 64 | } -------------------------------------------------------------------------------- /scripts/coverage_report.sh: -------------------------------------------------------------------------------- 1 | # This script generates a test coverage report under the "/coverage" folder 2 | # To run it you will need Rust version 1.60 or greater and install the following: 3 | # $ cargo install grcov 4 | # $ rustup component add llvm-tools-preview 5 | 6 | # Source of the instructions: 7 | # https://github.com/mozilla/grcov#example-how-to-generate-source-based-coverage-for-a-rust-project 8 | 9 | cargo clean 10 | rm -rf ./coverage ./target *.prof* 11 | 12 | # Export the flags needed to instrument the program to collect code coverage. 13 | export RUSTFLAGS="-C instrument-coverage" 14 | 15 | # Ensure each test runs gets its own profile information by defining the LLVM_PROFILE_FILE environment variable 16 | # (%p will be replaced by the process ID, and %m by the binary signature): 17 | export LLVM_PROFILE_FILE="rust_blockchain-%p-%m.profraw" 18 | 19 | # Build the program 20 | cargo build 21 | 22 | # Run the program 23 | cargo test 24 | 25 | # Generate a HTML report in the coverage/ directory. 26 | grcov . --binary-path ./target/debug/ -s . -t html --branch --ignore-not-existing --ignore "tests/*" -o ./coverage/ 27 | 28 | rm *.prof* -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | model::{Block, Blockchain, Transaction, TransactionPool}, 3 | util::{execution::Runnable, Context}, 4 | }; 5 | use actix_web::{web, App, HttpResponse, HttpServer, Responder}; 6 | use anyhow::Result; 7 | 8 | struct ApiState { 9 | blockchain: Blockchain, 10 | pool: TransactionPool, 11 | } 12 | 13 | pub struct Api { 14 | port: u16, 15 | blockchain: Blockchain, 16 | pool: TransactionPool, 17 | } 18 | 19 | impl Runnable for Api { 20 | fn run(&self) -> Result<()> { 21 | let api_blockchain = self.blockchain.clone(); 22 | let api_pool = self.pool.clone(); 23 | 24 | start_server(self.port, api_blockchain, api_pool) 25 | } 26 | } 27 | 28 | impl Api { 29 | pub fn new(context: &Context) -> Api { 30 | Api { 31 | port: context.config.port, 32 | blockchain: context.blockchain.clone(), 33 | pool: context.pool.clone(), 34 | } 35 | } 36 | } 37 | 38 | #[actix_web::main] 39 | async fn start_server(port: u16, blockchain: Blockchain, pool: TransactionPool) -> Result<()> { 40 | let url = format!("localhost:{}", port); 41 | // These variables are really "Arc" pointers to a shared memory value 42 | // So when we clone them, we are only cloning the pointers and not the actual data 43 | let api_state = web::Data::new(ApiState { blockchain, pool }); 44 | 45 | HttpServer::new(move || { 46 | App::new() 47 | .app_data(api_state.clone()) 48 | .route("/blocks", web::get().to(get_blocks)) 49 | .route("/blocks", web::post().to(add_block)) 50 | .route("/transactions", web::post().to(add_transaction)) 51 | }) 52 | .bind(url) 53 | .unwrap() 54 | .run() 55 | .await?; 56 | 57 | Ok(()) 58 | } 59 | 60 | // Returns a list of all the blocks in the blockchain 61 | async fn get_blocks(state: web::Data) -> impl Responder { 62 | let blockchain = &state.blockchain; 63 | let blocks = blockchain.get_all_blocks(); 64 | 65 | HttpResponse::Ok().json(&blocks) 66 | } 67 | 68 | // Adds a new block to the blockchain 69 | async fn add_block(state: web::Data, block_json: web::Json) -> HttpResponse { 70 | let mut block = block_json.into_inner(); 71 | 72 | // The hash of the block is mandatory and the blockchain checks if it's correct 73 | // That's a bit unconvenient for manual use of the API 74 | // So we ignore the comming hash and recalculate it again before adding to the blockchain 75 | block.hash = block.calculate_hash(); 76 | 77 | let blockchain = &state.blockchain; 78 | let result = blockchain.add_block(block.clone()); 79 | 80 | match result { 81 | Ok(_) => { 82 | info!("Received new block {}", block.index); 83 | HttpResponse::Ok().finish() 84 | } 85 | Err(error) => HttpResponse::BadRequest().body(error.to_string()), 86 | } 87 | } 88 | 89 | // Adds a new transaction to the pool, to be included on the next block 90 | async fn add_transaction( 91 | state: web::Data, 92 | transaction_json: web::Json, 93 | ) -> impl Responder { 94 | let transaction = transaction_json.into_inner(); 95 | let pool = &state.pool; 96 | pool.add_transaction(transaction); 97 | 98 | HttpResponse::Ok() 99 | } 100 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod api; 5 | mod miner; 6 | mod model; 7 | mod peer; 8 | mod util; 9 | 10 | use api::Api; 11 | use miner::Miner; 12 | use model::{Blockchain, TransactionPool}; 13 | use peer::Peer; 14 | use util::{execution, initialize_logger, termination, Config, Context}; 15 | 16 | fn main() { 17 | initialize_logger(); 18 | info!("starting up"); 19 | 20 | // quit the program when the user inputs Ctrl-C 21 | termination::set_ctrlc_handler(); 22 | 23 | // initialize shared data values 24 | let config = Config::read(); 25 | let difficulty = config.difficulty; 26 | let context = Context { 27 | config, 28 | blockchain: Blockchain::new(difficulty), 29 | pool: TransactionPool::new(), 30 | }; 31 | 32 | // initialize the processes 33 | let miner = Miner::new(&context); 34 | let api = Api::new(&context); 35 | let peer = Peer::new(&context); 36 | 37 | // miner, api and peer system run in separate threads 38 | // because mining is very cpu intensive 39 | execution::run_in_parallel(vec![&miner, &api, &peer]); 40 | } 41 | -------------------------------------------------------------------------------- /src/miner.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | model::{ 3 | Address, Block, BlockHash, Blockchain, Transaction, TransactionPool, TransactionVec, 4 | BLOCK_SUBSIDY, 5 | }, 6 | util::{ 7 | execution::{sleep_millis, Runnable}, 8 | Context, 9 | }, 10 | }; 11 | use anyhow::Result; 12 | use thiserror::Error; 13 | 14 | #[derive(Error, Debug)] 15 | pub enum MinerError { 16 | #[error("No valid block was mined at index `{0}`")] 17 | BlockNotMined(u64), 18 | } 19 | 20 | pub struct Miner { 21 | miner_address: Address, 22 | max_blocks: u64, 23 | max_nonce: u64, 24 | tx_waiting_ms: u64, 25 | blockchain: Blockchain, 26 | pool: TransactionPool, 27 | target: BlockHash, 28 | } 29 | 30 | impl Runnable for Miner { 31 | fn run(&self) -> Result<()> { 32 | self.start() 33 | } 34 | } 35 | 36 | impl Miner { 37 | pub fn new(context: &Context) -> Miner { 38 | let target = Self::create_target(context.config.difficulty); 39 | 40 | Miner { 41 | miner_address: context.config.miner_address.clone(), 42 | max_blocks: context.config.max_blocks, 43 | max_nonce: context.config.max_nonce, 44 | tx_waiting_ms: context.config.tx_waiting_ms, 45 | blockchain: context.blockchain.clone(), 46 | pool: context.pool.clone(), 47 | target, 48 | } 49 | } 50 | 51 | // Try to constanly calculate and append new valid blocks to the blockchain, 52 | // including all pending transactions in the transaction pool each time 53 | pub fn start(&self) -> Result<()> { 54 | info!( 55 | "start minining with difficulty {}", 56 | self.blockchain.difficulty 57 | ); 58 | 59 | // In each loop it tries to find the next valid block and append it to the blockchain 60 | let mut block_counter = 0; 61 | loop { 62 | if self.must_stop_mining(block_counter) { 63 | info!("block limit reached, stopping mining"); 64 | return Ok(()); 65 | } 66 | 67 | // Empty all transactions from the pool, they will be included in the new block 68 | let transactions = self.pool.pop(); 69 | 70 | // Do not try to mine a block if there are no transactions in the pool 71 | if transactions.is_empty() { 72 | sleep_millis(self.tx_waiting_ms); 73 | continue; 74 | } 75 | 76 | // try to find a valid next block of the blockchain 77 | let last_block = self.blockchain.get_last_block(); 78 | let mining_result = self.mine_block(&last_block, &transactions.clone()); 79 | match mining_result { 80 | Some(block) => { 81 | info!("valid block found for index {}", block.index); 82 | self.blockchain.add_block(block.clone())?; 83 | block_counter += 1; 84 | } 85 | None => { 86 | let index = last_block.index + 1; 87 | error!("no valid block was foun for index {}", index); 88 | return Err(MinerError::BlockNotMined(index).into()); 89 | } 90 | } 91 | } 92 | } 93 | 94 | // Creates binary data mask with the amount of left padding zeroes indicated by the "difficulty" value 95 | // Used to easily compare if a newly created block has a hash that matches the difficulty 96 | fn create_target(difficulty: u32) -> BlockHash { 97 | BlockHash::MAX >> difficulty 98 | } 99 | 100 | // check if we have hit the limit of mined blocks (if the limit is set) 101 | fn must_stop_mining(&self, block_counter: u64) -> bool { 102 | self.max_blocks > 0 && block_counter >= self.max_blocks 103 | } 104 | 105 | // Tries to find the next valid block of the blockchain 106 | // It will create blocks with different "nonce" values until one has a hash that matches the difficulty 107 | // Returns either a valid block (that satisfies the difficulty) or "None" if no block was found 108 | fn mine_block(&self, last_block: &Block, transactions: &TransactionVec) -> Option { 109 | // Add the coinbase transaction as the first transaction in the block 110 | let coinbase = self.create_coinbase_transaction(); 111 | let mut block_transactions = transactions.clone(); 112 | block_transactions.insert(0, coinbase); 113 | 114 | for nonce in 0..self.max_nonce { 115 | let next_block = self.create_next_block(last_block, block_transactions.clone(), nonce); 116 | 117 | // A valid block must have a hash with enough starting zeroes 118 | // To check that, we simply compare against a binary data mask 119 | if next_block.hash < self.target { 120 | return Some(next_block); 121 | } 122 | } 123 | 124 | None 125 | } 126 | 127 | // Creates a valid next block for a blockchain 128 | // Takes into account the index and the hash of the previous block 129 | fn create_next_block( 130 | &self, 131 | last_block: &Block, 132 | transactions: TransactionVec, 133 | nonce: u64, 134 | ) -> Block { 135 | let index = (last_block.index + 1) as u64; 136 | let previous_hash = last_block.hash; 137 | 138 | // hash of the new block is automatically calculated on creation 139 | Block::new(index, nonce, previous_hash, transactions) 140 | } 141 | 142 | fn create_coinbase_transaction(&self) -> Transaction { 143 | Transaction { 144 | sender: Address::default(), 145 | recipient: self.miner_address.clone(), 146 | amount: BLOCK_SUBSIDY, 147 | } 148 | } 149 | } 150 | 151 | #[cfg(test)] 152 | mod tests { 153 | use super::*; 154 | use crate::model::{ 155 | test_util::{alice, bob}, 156 | Transaction, 157 | }; 158 | 159 | // We use SHA 256 hashes 160 | const MAX_DIFFICULTY: u32 = 256; 161 | 162 | #[test] 163 | fn test_create_next_block() { 164 | let miner = create_default_miner(); 165 | let block = create_empty_block(); 166 | 167 | let next_block = miner.create_next_block(&block, Vec::new(), 0); 168 | 169 | // the next block must follow the previous one 170 | assert_eq!(next_block.index, block.index + 1); 171 | assert_eq!(next_block.previous_hash, block.hash); 172 | } 173 | 174 | #[test] 175 | fn test_create_target_valid_difficulty() { 176 | // try all possibilities of valid difficulties 177 | // the target must have as many leading zeroes 178 | for difficulty in 0..MAX_DIFFICULTY { 179 | let target = Miner::create_target(difficulty); 180 | assert_eq!(target.leading_zeros(), difficulty); 181 | } 182 | } 183 | 184 | #[test] 185 | fn test_create_target_overflowing_difficulty() { 186 | // when passing an overflowing difficulty, 187 | // it must default to the max difficulty 188 | let target = Miner::create_target(MAX_DIFFICULTY + 1); 189 | assert_eq!(target.leading_zeros(), MAX_DIFFICULTY); 190 | } 191 | 192 | #[test] 193 | fn test_mine_block_found() { 194 | // let's use a small difficulty target for fast testing 195 | let difficulty = 1; 196 | 197 | // this should be more than enough nonces to find a block with only 1 zero 198 | let max_nonce = 1_000; 199 | 200 | // check that the block is mined 201 | let miner = create_miner(difficulty, max_nonce); 202 | let last_block = create_empty_block(); 203 | let result = miner.mine_block(&last_block, &Vec::new()); 204 | assert!(result.is_some()); 205 | 206 | // check that the block is valid 207 | let mined_block = result.unwrap(); 208 | assert_mined_block_is_valid(&mined_block, &last_block, difficulty); 209 | } 210 | 211 | #[test] 212 | fn test_mine_block_not_found() { 213 | // let's use a high difficulty target to never find a block 214 | let difficulty = MAX_DIFFICULTY; 215 | 216 | // with a max_nonce so low, we will never find a block 217 | // and also the test will end fast 218 | let max_nonce = 10; 219 | 220 | // check that the block is not mined 221 | let miner = create_miner(difficulty, max_nonce); 222 | let last_block = create_empty_block(); 223 | let result = miner.mine_block(&last_block, &Vec::new()); 224 | assert!(result.is_none()); 225 | } 226 | 227 | #[test] 228 | fn test_run_block_found() { 229 | // with a max_nonce so high and difficulty so low 230 | // we will always find a valid block 231 | let difficulty = 1; 232 | let max_nonce = 1_000_000; 233 | let miner = create_miner(difficulty, max_nonce); 234 | 235 | let blockchain = miner.blockchain.clone(); 236 | let pool = miner.pool.clone(); 237 | 238 | add_mock_transaction(&pool); 239 | let result = miner.run(); 240 | 241 | // mining should be successful 242 | assert!(result.is_ok()); 243 | 244 | // a new block should have been added to the blockchain 245 | let blocks = blockchain.get_all_blocks(); 246 | assert_eq!(blocks.len(), 2); 247 | let genesis_block = &blocks[0]; 248 | let mined_block = &blocks[1]; 249 | 250 | // the mined block must be valid 251 | assert_mined_block_is_valid(mined_block, genesis_block, blockchain.difficulty); 252 | 253 | // the mined block must include the transaction added previously plus the coinbase 254 | let mined_transactions = &mined_block.transactions; 255 | assert_eq!(mined_transactions.len(), 2); 256 | 257 | // the transaction pool must be empty 258 | // because the transaction was added to the block when mining 259 | let transactions = pool.pop(); 260 | assert!(transactions.is_empty()); 261 | } 262 | 263 | #[test] 264 | #[should_panic(expected = "No valid block was mined at index `1`")] 265 | fn test_run_block_not_found() { 266 | // with a max_nonce so low and difficulty so high 267 | // we will never find a valid block 268 | let difficulty = MAX_DIFFICULTY; 269 | let max_nonce = 1; 270 | let miner = create_miner(difficulty, max_nonce); 271 | 272 | let pool = &miner.pool; 273 | add_mock_transaction(pool); 274 | 275 | // mining should return a BlockNotMined error 276 | miner.run().unwrap(); 277 | } 278 | 279 | fn create_default_miner() -> Miner { 280 | let difficulty = 1; 281 | let max_nonce = 1; 282 | create_miner(difficulty, max_nonce) 283 | } 284 | 285 | fn miner_address() -> Address { 286 | alice() 287 | } 288 | 289 | fn create_miner(difficulty: u32, max_nonce: u64) -> Miner { 290 | let miner_address = miner_address(); 291 | let max_blocks = 1; 292 | let tx_waiting_ms = 1; 293 | let target = Miner::create_target(difficulty); 294 | 295 | let blockchain = Blockchain::new(difficulty); 296 | let pool = TransactionPool::new(); 297 | 298 | Miner { 299 | miner_address, 300 | max_blocks, 301 | max_nonce, 302 | tx_waiting_ms, 303 | blockchain, 304 | pool, 305 | target, 306 | } 307 | } 308 | 309 | fn create_empty_block() -> Block { 310 | return Block::new(0, 0, BlockHash::default(), Vec::new()); 311 | } 312 | 313 | fn add_mock_transaction(pool: &TransactionPool) { 314 | // the transaction is valid because the genesis block gives rewards to the miner address 315 | // so that address can be a sender of funds to other addresses 316 | let transaction = Transaction { 317 | sender: miner_address(), 318 | recipient: bob(), 319 | amount: 3, 320 | }; 321 | pool.add_transaction(transaction.clone()); 322 | } 323 | 324 | fn assert_mined_block_is_valid(mined_block: &Block, previous_block: &Block, difficulty: u32) { 325 | assert_eq!(mined_block.index, previous_block.index + 1); 326 | assert_eq!(mined_block.previous_hash, previous_block.hash); 327 | assert!(mined_block.hash.leading_zeros() >= difficulty as u32); 328 | } 329 | } 330 | -------------------------------------------------------------------------------- /src/model.rs: -------------------------------------------------------------------------------- 1 | mod account_balance_map; 2 | mod address; 3 | mod block; 4 | mod blockchain; 5 | mod transaction; 6 | mod transaction_pool; 7 | 8 | // Explicitly controlling which individual identifiers we export 9 | // It also avoids verbose module imports from other files 10 | pub use address::Address; 11 | pub use block::{Block, BlockHash}; 12 | pub use blockchain::{Blockchain, BlockchainError, BLOCK_SUBSIDY}; 13 | pub use transaction::Transaction; 14 | pub use transaction_pool::{TransactionPool, TransactionVec}; 15 | 16 | #[cfg(test)] 17 | pub use address::test_util; 18 | -------------------------------------------------------------------------------- /src/model/account_balance_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | use thiserror::Error; 4 | 5 | use super::Address; 6 | 7 | pub type Amount = u64; 8 | 9 | #[derive(Error, PartialEq, Debug)] 10 | pub enum AccountBalanceMapError { 11 | #[error("Sender account does not exist")] 12 | SenderAccountDoesNotExist, 13 | 14 | #[error("Insufficient funds")] 15 | InsufficientFunds, 16 | } 17 | 18 | #[derive(Debug, Default, Clone)] 19 | pub struct AccountBalanceMap(HashMap); 20 | 21 | impl AccountBalanceMap { 22 | pub fn add_amount(&mut self, recipient: &Address, amount: Amount) { 23 | let balance = self.get_recipient_balance(recipient); 24 | self.update_balance(recipient, balance + amount); 25 | } 26 | 27 | pub fn transfer( 28 | &mut self, 29 | sender: &Address, 30 | recipient: &Address, 31 | amount: Amount, 32 | ) -> Result<(), AccountBalanceMapError> { 33 | let sender_balance = self.get_sender_balance(sender)?; 34 | let recipient_balance = self.get_recipient_balance(recipient); 35 | 36 | if sender_balance < amount { 37 | return Err(AccountBalanceMapError::InsufficientFunds); 38 | } 39 | 40 | self.update_balance(sender, sender_balance - amount); 41 | self.update_balance(recipient, recipient_balance + amount); 42 | 43 | Ok(()) 44 | } 45 | 46 | fn get_recipient_balance(&self, recipient: &Address) -> Amount { 47 | match self.0.get(recipient) { 48 | Some(amount) => *amount, 49 | None => 0, 50 | } 51 | } 52 | 53 | fn get_sender_balance(&self, sender: &Address) -> Result { 54 | match self.0.get(sender) { 55 | Some(balance) => Ok(*balance), 56 | None => Err(AccountBalanceMapError::SenderAccountDoesNotExist), 57 | } 58 | } 59 | 60 | fn update_balance(&mut self, address: &Address, new_balance: Amount) { 61 | let balance = self.0.entry(address.clone()).or_insert(0); 62 | *balance = new_balance; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/model/address.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::{TryFrom, TryInto}, 3 | fmt, 4 | str::FromStr, 5 | }; 6 | 7 | use serde::{Deserialize, Serialize}; 8 | use thiserror::Error; 9 | 10 | // Addresses are 32-bytes long 11 | type Byte = u8; 12 | const LEN: usize = 32; 13 | 14 | #[derive(Error, PartialEq, Debug)] 15 | #[allow(clippy::enum_variant_names)] 16 | pub enum AddressError { 17 | #[error("Invalid format")] 18 | InvalidFormat, 19 | 20 | #[error("Invalid length")] 21 | InvalidLength, 22 | } 23 | 24 | #[derive(Debug, Default, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)] 25 | #[serde(try_from = "String", into = "String")] 26 | pub struct Address([Byte; LEN]); 27 | 28 | impl TryFrom> for Address { 29 | type Error = AddressError; 30 | 31 | fn try_from(vec: Vec) -> Result { 32 | let slice = vec.as_slice(); 33 | match slice.try_into() { 34 | Ok(byte_array) => Ok(Address(byte_array)), 35 | Err(_) => Err(AddressError::InvalidLength), 36 | } 37 | } 38 | } 39 | 40 | impl TryFrom for Address { 41 | type Error = AddressError; 42 | 43 | fn try_from(s: String) -> Result { 44 | match hex::decode(s) { 45 | Ok(decoded_vec) => decoded_vec.try_into(), 46 | Err(_) => Err(AddressError::InvalidFormat), 47 | } 48 | } 49 | } 50 | 51 | impl FromStr for Address { 52 | type Err = AddressError; 53 | 54 | fn from_str(s: &str) -> Result { 55 | Address::try_from(s.to_string()) 56 | } 57 | } 58 | 59 | impl From
for String { 60 | fn from(account: Address) -> Self { 61 | account.to_string() 62 | } 63 | } 64 | 65 | impl fmt::Display for Address { 66 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 67 | write!(f, "{}", hex::encode(self.0)) 68 | } 69 | } 70 | 71 | // Some sample addresses to be used in tests all over the project 72 | // We export functions to workaround constant value restrictions in Rust 73 | #[cfg(test)] 74 | pub mod test_util { 75 | use std::convert::TryFrom; 76 | 77 | use super::Address; 78 | 79 | pub fn alice() -> Address { 80 | Address::try_from( 81 | "f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e".to_string(), 82 | ) 83 | .unwrap() 84 | } 85 | 86 | pub fn bob() -> Address { 87 | Address::try_from( 88 | "51df097c03c0a6e64e54a6fce90cb6968adebd85955917ed438e3d3c05f2f00f".to_string(), 89 | ) 90 | .unwrap() 91 | } 92 | 93 | pub fn carol() -> Address { 94 | Address::try_from( 95 | "b4f8293fb123ef3ff9ad49e923f4afc732774ee2bfdc3b278a359b54473c2277".to_string(), 96 | ) 97 | .unwrap() 98 | } 99 | } 100 | 101 | #[cfg(test)] 102 | mod tests { 103 | use std::{convert::TryFrom, str::FromStr}; 104 | 105 | use crate::model::Address; 106 | 107 | use super::AddressError; 108 | 109 | #[test] 110 | fn parse_valid_address() { 111 | let hex_str = "f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e"; 112 | let address = Address::try_from(hex_str.to_string()).unwrap(); 113 | assert_eq!(address.to_string(), hex_str); 114 | let address = Address::from_str(hex_str).unwrap(); 115 | assert_eq!(address.to_string(), hex_str); 116 | } 117 | 118 | #[test] 119 | fn parse_case_insensitive() { 120 | let hex_str = 121 | "F780B958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e".to_string(); 122 | let address = Address::try_from(hex_str.clone()).unwrap(); 123 | assert_eq!(address.to_string(), hex_str.to_lowercase()); 124 | } 125 | 126 | #[test] 127 | fn parse_json() { 128 | let hex_str = 129 | "f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e".to_string(); 130 | let address: Address = 131 | serde_json::from_value(serde_json::Value::String(hex_str.clone())).unwrap(); 132 | assert_eq!(address.to_string(), hex_str.to_lowercase()); 133 | let address_json = serde_json::to_value(address).unwrap(); 134 | assert_eq!(address_json, serde_json::Value::String(hex_str.clone())); 135 | } 136 | 137 | #[test] 138 | fn reject_too_short() { 139 | // 31-byte string (62 hex chars) 140 | let hex_str = "f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce2".to_string(); 141 | let err = Address::try_from(hex_str).unwrap_err(); 142 | assert_eq!(err, AddressError::InvalidLength); 143 | } 144 | 145 | #[test] 146 | fn reject_too_long() { 147 | // 33-byte string (66 hex chars) 148 | let hex_str = 149 | "f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e10".to_string(); 150 | let err = Address::try_from(hex_str).unwrap_err(); 151 | assert_eq!(err, AddressError::InvalidLength); 152 | } 153 | 154 | #[test] 155 | fn reject_invalid_characters() { 156 | // correct length (32 bytes) but with an invalid hexadecimal char "g" 157 | let hex_str = 158 | "g780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e".to_string(); 159 | let err = Address::try_from(hex_str).unwrap_err(); 160 | assert_eq!(err, AddressError::InvalidFormat); 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/model/block.rs: -------------------------------------------------------------------------------- 1 | use chrono::prelude::*; 2 | use crypto::digest::Digest; 3 | use crypto::sha2::Sha256; 4 | use ethereum_types::U256; 5 | use serde::{Deserialize, Serialize}; 6 | 7 | use super::Transaction; 8 | 9 | // We encapsulate the paricular hash value implementation 10 | // to be able to easily change it in the future 11 | pub type BlockHash = U256; 12 | 13 | // Represents a block in a blockchain 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct Block { 16 | pub index: u64, 17 | pub timestamp: i64, 18 | pub nonce: u64, 19 | pub previous_hash: BlockHash, 20 | pub hash: BlockHash, 21 | pub transactions: Vec, 22 | } 23 | 24 | impl Block { 25 | // Create a brand new block. The hash value will be caclulated and set automatically. 26 | pub fn new( 27 | index: u64, 28 | nonce: u64, 29 | previous_hash: BlockHash, 30 | transactions: Vec, 31 | ) -> Block { 32 | let mut block = Block { 33 | index, 34 | timestamp: Utc::now().timestamp_millis(), 35 | nonce, 36 | previous_hash, 37 | hash: BlockHash::default(), 38 | transactions, 39 | }; 40 | block.hash = block.calculate_hash(); 41 | 42 | block 43 | } 44 | 45 | // Calculate the hash value of the block 46 | pub fn calculate_hash(&self) -> BlockHash { 47 | // We cannot use the hash field to calculate the hash 48 | let mut hashable_data = self.clone(); 49 | hashable_data.hash = BlockHash::default(); 50 | let serialized = serde_json::to_string(&hashable_data).unwrap(); 51 | 52 | // Cacluate and return the SHA-256 hash value for the block 53 | let mut byte_hash = <[u8; 32]>::default(); 54 | let mut hasher = Sha256::new(); 55 | 56 | hasher.input_str(&serialized); 57 | hasher.result(&mut byte_hash); 58 | 59 | U256::from(byte_hash) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/model/blockchain.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use std::{ 3 | slice::Iter, 4 | sync::{Arc, Mutex}, 5 | }; 6 | use thiserror::Error; 7 | 8 | use super::{account_balance_map::AccountBalanceMap, Block, BlockHash, Transaction}; 9 | 10 | pub type BlockVec = Vec; 11 | 12 | // We don't need to export this because concurrency is encapsulated in this file 13 | type SyncedBlockVec = Arc>; 14 | type SyncedAccountBalanceVec = Arc>; 15 | 16 | pub const BLOCK_SUBSIDY: u64 = 100; 17 | 18 | // Error types to return when trying to add blocks with invalid fields 19 | #[derive(Error, PartialEq, Debug)] 20 | #[allow(clippy::enum_variant_names)] 21 | pub enum BlockchainError { 22 | #[error("Invalid index")] 23 | InvalidIndex, 24 | 25 | #[error("Invalid previous_hash")] 26 | InvalidPreviousHash, 27 | 28 | #[error("Invalid hash")] 29 | InvalidHash, 30 | 31 | #[error("Invalid difficulty")] 32 | InvalidDifficulty, 33 | 34 | #[error("Coinbase transaction not found")] 35 | CoinbaseTransactionNotFound, 36 | 37 | #[error("Invalid coinbase amount")] 38 | InvalidCoinbaseAmount, 39 | } 40 | 41 | // Struct that holds all the blocks in the blockhain 42 | // Multiple threads can read/write concurrently to the list of blocks 43 | #[derive(Debug, Clone)] 44 | pub struct Blockchain { 45 | pub difficulty: u32, 46 | blocks: SyncedBlockVec, 47 | account_balances: SyncedAccountBalanceVec, 48 | } 49 | 50 | // Basic operations in the blockchain are encapsulated in the implementation 51 | // Encapsulates concurrency concerns, so external callers do not need to know how it's handled 52 | impl Blockchain { 53 | // Creates a brand new blockchain with a genesis block 54 | pub fn new(difficulty: u32) -> Blockchain { 55 | let genesis_block = Blockchain::create_genesis_block(); 56 | 57 | // add the genesis block to the synced vec of blocks 58 | let blocks = vec![genesis_block]; 59 | let synced_blocks = Arc::new(Mutex::new(blocks)); 60 | let synced_account_balances = SyncedAccountBalanceVec::default(); 61 | 62 | Blockchain { 63 | difficulty, 64 | blocks: synced_blocks, 65 | account_balances: synced_account_balances, 66 | } 67 | } 68 | 69 | fn create_genesis_block() -> Block { 70 | let index = 0; 71 | let nonce = 0; 72 | let previous_hash = BlockHash::default(); 73 | let transactions = Vec::new(); 74 | 75 | let mut block = Block::new(index, nonce, previous_hash, transactions); 76 | 77 | // to easily sync multiple nodes in a network, the genesis blocks must match 78 | // so we clear the timestamp so the hash of the genesis block is predictable 79 | block.timestamp = 0; 80 | block.hash = block.calculate_hash(); 81 | 82 | block 83 | } 84 | 85 | // Returns a copy of the most recent block in the blockchain 86 | pub fn get_last_block(&self) -> Block { 87 | let blocks = self.blocks.lock().unwrap(); 88 | 89 | blocks[blocks.len() - 1].clone() 90 | } 91 | 92 | // Returns a copy of the whole list of blocks 93 | pub fn get_all_blocks(&self) -> BlockVec { 94 | let blocks = self.blocks.lock().unwrap(); 95 | 96 | blocks.clone() 97 | } 98 | 99 | // Tries to append a new block into the blockchain 100 | // It will validate that the values of the new block are consistend with the blockchain state 101 | // This operation is safe to be called concurrently from multiple threads 102 | pub fn add_block(&self, block: Block) -> Result<()> { 103 | // the "blocks" attribute is protected by a Mutex 104 | // so only one thread at a time can access the value when the lock is held 105 | // that prevents adding multiple valid blocks at the same time 106 | // preserving the correct order of indexes and hashes of the blockchain 107 | let mut blocks = self.blocks.lock().unwrap(); 108 | let last = &blocks[blocks.len() - 1]; 109 | 110 | // check that the index is valid 111 | if block.index != last.index + 1 { 112 | return Err(BlockchainError::InvalidIndex.into()); 113 | } 114 | 115 | // check that the previous_hash is valid 116 | if block.previous_hash != last.hash { 117 | return Err(BlockchainError::InvalidPreviousHash.into()); 118 | } 119 | 120 | // check that the hash matches the data 121 | if block.hash != block.calculate_hash() { 122 | return Err(BlockchainError::InvalidHash.into()); 123 | } 124 | 125 | // check that the difficulty is correct 126 | if block.hash.leading_zeros() < self.difficulty { 127 | return Err(BlockchainError::InvalidDifficulty.into()); 128 | } 129 | 130 | // update the account balances by processing the block transactions 131 | self.update_account_balances(&block.transactions)?; 132 | 133 | // append the block to the end 134 | blocks.push(block); 135 | 136 | Ok(()) 137 | } 138 | 139 | fn update_account_balances(&self, transactions: &[Transaction]) -> Result<()> { 140 | let mut account_balances = self.account_balances.lock().unwrap(); 141 | // note that if any transaction (including coinbase) is invalid, an error will be returned before updating the balances 142 | let new_account_balances = 143 | Blockchain::calculate_new_account_balances(&account_balances, transactions)?; 144 | *account_balances = new_account_balances; 145 | 146 | Ok(()) 147 | } 148 | 149 | fn calculate_new_account_balances( 150 | account_balances: &AccountBalanceMap, 151 | transactions: &[Transaction], 152 | ) -> Result { 153 | // we work on a copy of the account balances 154 | let mut new_account_balances = account_balances.clone(); 155 | let mut iter = transactions.iter(); 156 | 157 | // the first transaction is always the coinbase transaction 158 | // in which the miner receives the mining rewards 159 | Blockchain::process_coinbase(&mut new_account_balances, iter.next())?; 160 | 161 | // the rest of the transactions are regular transfers between accounts 162 | Blockchain::process_transfers(&mut new_account_balances, iter)?; 163 | 164 | Ok(new_account_balances) 165 | } 166 | 167 | fn process_coinbase( 168 | account_balances: &mut AccountBalanceMap, 169 | coinbase: Option<&Transaction>, 170 | ) -> Result<()> { 171 | // The coinbase transaction is required in a valid block 172 | let coinbase = match coinbase { 173 | Some(transaction) => transaction, 174 | None => return Err(BlockchainError::CoinbaseTransactionNotFound.into()), 175 | }; 176 | 177 | // In coinbase transactions, we only need to check that the amount is valid, 178 | // because whoever provides a valid proof-of-work block can receive the new coins 179 | let is_valid_amount = coinbase.amount == BLOCK_SUBSIDY; 180 | if !is_valid_amount { 181 | return Err(BlockchainError::InvalidCoinbaseAmount.into()); 182 | } 183 | 184 | // The amount is valid so we add the new coins to the miner's address 185 | account_balances.add_amount(&coinbase.recipient, coinbase.amount); 186 | 187 | Ok(()) 188 | } 189 | 190 | fn process_transfers( 191 | new_account_balances: &mut AccountBalanceMap, 192 | transaction_iter: Iter, 193 | ) -> Result<()> { 194 | // each transaction is validated using the updated account balances from previous transactions 195 | // that means that we allow multiple transacions from the same address in the same block 196 | // as long as they are consistent 197 | for tx in transaction_iter { 198 | new_account_balances.transfer(&tx.sender, &tx.recipient, tx.amount)? 199 | } 200 | 201 | Ok(()) 202 | } 203 | } 204 | 205 | #[cfg(test)] 206 | mod tests { 207 | use crate::model::{ 208 | account_balance_map::AccountBalanceMapError, 209 | test_util::{alice, bob, carol}, 210 | Address, Transaction, 211 | }; 212 | 213 | use super::*; 214 | 215 | const NO_DIFFICULTY: u32 = 0; 216 | 217 | #[test] 218 | fn should_have_valid_genesis_block() { 219 | let blockchain = Blockchain::new(NO_DIFFICULTY); 220 | 221 | // check that a new blockchain has one and only one block 222 | let blocks = blockchain.get_all_blocks(); 223 | assert_eq!(blocks.len(), 1); 224 | 225 | // check that the last block is in the blockchain 226 | let block = blockchain.get_last_block(); 227 | assert_eq!(block.hash, blocks[0].hash); 228 | 229 | // check that the genesis block has valid values 230 | assert_eq!(block.index, 0); 231 | assert_eq!(block.nonce, 0); 232 | assert_eq!(block.previous_hash, BlockHash::default()); 233 | assert!(block.transactions.is_empty()); 234 | } 235 | 236 | #[test] 237 | fn should_let_adding_valid_blocks() { 238 | let blockchain = Blockchain::new(NO_DIFFICULTY); 239 | 240 | // create a valid block 241 | let previous_hash = blockchain.get_last_block().hash; 242 | let coinbase = Transaction { 243 | sender: Address::default(), // sender is ignored in coinbases 244 | recipient: bob(), 245 | amount: BLOCK_SUBSIDY, 246 | }; 247 | let tx1 = Transaction { 248 | sender: bob(), 249 | recipient: alice(), 250 | amount: 5, 251 | }; 252 | let tx2 = Transaction { 253 | sender: alice(), 254 | recipient: bob(), 255 | amount: 5, 256 | }; 257 | let block = Block::new(1, 0, previous_hash, vec![coinbase, tx1, tx2]); 258 | 259 | // add it to the blockchain and check it was really added 260 | let result = blockchain.add_block(block.clone()); 261 | assert!(result.is_ok()); 262 | 263 | let blocks = blockchain.get_all_blocks(); 264 | assert_eq!(blocks.len(), 2); 265 | 266 | let last_block = blockchain.get_last_block(); 267 | assert_eq!(last_block.hash, block.hash); 268 | } 269 | 270 | #[test] 271 | fn should_not_let_adding_block_with_invalid_index() { 272 | let blockchain = Blockchain::new(NO_DIFFICULTY); 273 | 274 | // create a block with invalid index 275 | let invalid_index = 2; 276 | let previous_hash = blockchain.get_last_block().hash; 277 | let block = Block::new(invalid_index, 0, previous_hash, Vec::new()); 278 | 279 | // try adding the invalid block, it should return an error 280 | let result = blockchain.add_block(block.clone()); 281 | assert_err(result, BlockchainError::InvalidIndex); 282 | } 283 | 284 | #[test] 285 | fn should_not_let_adding_block_with_invalid_previous_hash() { 286 | let blockchain = Blockchain::new(NO_DIFFICULTY); 287 | 288 | // create a block with invalid previous hash 289 | let invalid_previous_hash = BlockHash::default(); 290 | let block = Block::new(1, 0, invalid_previous_hash, Vec::new()); 291 | 292 | // try adding the invalid block, it should return an error 293 | let result = blockchain.add_block(block.clone()); 294 | assert_err(result, BlockchainError::InvalidPreviousHash); 295 | } 296 | 297 | #[test] 298 | fn should_not_let_adding_block_with_invalid_hash() { 299 | let blockchain = Blockchain::new(NO_DIFFICULTY); 300 | 301 | // create a block with invalid hash 302 | let previous_hash = blockchain.get_last_block().hash; 303 | let mut block = Block::new(1, 0, previous_hash, Vec::new()); 304 | block.hash = BlockHash::default(); 305 | 306 | // try adding the invalid block, it should return an error 307 | let result = blockchain.add_block(block.clone()); 308 | assert_err(result, BlockchainError::InvalidHash); 309 | } 310 | 311 | #[test] 312 | fn should_not_let_adding_block_with_invalid_difficulty() { 313 | // set up a blockchain with an insane difficulty 314 | let difficulty: u32 = 30; 315 | let blockchain = Blockchain::new(difficulty); 316 | 317 | // create a valid block 318 | let previous_hash = blockchain.get_last_block().hash; 319 | let block = Block::new(1, 0, previous_hash, Vec::new()); 320 | 321 | // ensure that the hash actually does NOT meet the difficulty 322 | assert!(block.hash.leading_zeros() < difficulty); 323 | 324 | // try adding the invalid block, it should return an error 325 | let result = blockchain.add_block(block.clone()); 326 | assert_err(result, BlockchainError::InvalidDifficulty); 327 | } 328 | 329 | #[test] 330 | fn should_not_let_adding_block_with_no_coinbase() { 331 | let blockchain = Blockchain::new(NO_DIFFICULTY); 332 | 333 | // create a block without a coinbase 334 | let previous_hash = blockchain.get_last_block().hash; 335 | let block = Block::new(1, 0, previous_hash, vec![]); 336 | 337 | // try adding the invalid block, it should return an error 338 | let result = blockchain.add_block(block.clone()); 339 | assert_err(result, BlockchainError::CoinbaseTransactionNotFound); 340 | } 341 | 342 | #[test] 343 | fn should_not_let_adding_block_with_invalid_coinbase() { 344 | let blockchain = Blockchain::new(NO_DIFFICULTY); 345 | 346 | // create a block with an invalid coinbase amount 347 | let previous_hash = blockchain.get_last_block().hash; 348 | let coinbase = Transaction { 349 | sender: Address::default(), 350 | recipient: Address::default(), 351 | amount: BLOCK_SUBSIDY + 1, 352 | }; 353 | let block = Block::new(1, 0, previous_hash, vec![coinbase]); 354 | 355 | // try adding the invalid block, it should return an error 356 | let result = blockchain.add_block(block.clone()); 357 | assert_err(result, BlockchainError::InvalidCoinbaseAmount); 358 | } 359 | 360 | #[test] 361 | fn should_not_let_add_transaction_with_insufficient_funds() { 362 | let blockchain = Blockchain::new(NO_DIFFICULTY); 363 | 364 | // create an invalid block 365 | let previous_hash = blockchain.get_last_block().hash; 366 | // the coinbase is valid 367 | let coinbase = Transaction { 368 | sender: Address::default(), // sender is ignored in coinbases 369 | recipient: bob(), 370 | amount: BLOCK_SUBSIDY, 371 | }; 372 | // but the following transaction has an invalid amount 373 | let invalid_transaction = Transaction { 374 | sender: bob(), 375 | recipient: alice(), 376 | // the amount is greated than what bob has 377 | amount: BLOCK_SUBSIDY + 1, 378 | }; 379 | let block = Block::new(1, 0, previous_hash, vec![coinbase, invalid_transaction]); 380 | 381 | // try adding the invalid block, it should return an error 382 | let result = blockchain.add_block(block.clone()); 383 | assert_balance_err(result, AccountBalanceMapError::InsufficientFunds); 384 | } 385 | 386 | #[test] 387 | fn should_not_let_add_transaction_with_non_existent_sender() { 388 | let blockchain = Blockchain::new(NO_DIFFICULTY); 389 | 390 | // create a valid block 391 | let previous_hash = blockchain.get_last_block().hash; 392 | // the coinbase is valid 393 | let coinbase = Transaction { 394 | sender: Address::default(), // sender is ignored in coinbases 395 | recipient: bob(), 396 | amount: BLOCK_SUBSIDY, 397 | }; 398 | // but the sender does not exist 399 | let invalid_transaction = Transaction { 400 | // the sender address do not have any funds from previous transactions 401 | sender: carol(), 402 | recipient: bob(), 403 | amount: 1, 404 | }; 405 | let block = Block::new(1, 0, previous_hash, vec![coinbase, invalid_transaction]); 406 | 407 | // try adding the invalid block, it should return an error 408 | let result = blockchain.add_block(block.clone()); 409 | assert_balance_err(result, AccountBalanceMapError::SenderAccountDoesNotExist); 410 | } 411 | 412 | fn assert_err(result: Result<(), anyhow::Error>, error_type: BlockchainError) { 413 | let err = result.unwrap_err().downcast::().unwrap(); 414 | assert_eq!(err, error_type); 415 | } 416 | 417 | fn assert_balance_err(result: Result<(), anyhow::Error>, error_type: AccountBalanceMapError) { 418 | let err = result 419 | .unwrap_err() 420 | .downcast::() 421 | .unwrap(); 422 | assert_eq!(err, error_type); 423 | } 424 | } 425 | -------------------------------------------------------------------------------- /src/model/transaction.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use super::Address; 4 | 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub struct Transaction { 7 | pub sender: Address, 8 | pub recipient: Address, 9 | pub amount: u64, 10 | } 11 | -------------------------------------------------------------------------------- /src/model/transaction_pool.rs: -------------------------------------------------------------------------------- 1 | use super::Transaction; 2 | use std::sync::{Arc, Mutex}; 3 | 4 | pub type TransactionVec = Vec; 5 | 6 | // We don't need to export this type because concurrency is encapsulated in this file 7 | type SyncedTransactionVec = Arc>; 8 | 9 | // Represents a pool of unrealized transactions 10 | // Multiple threads can read/write concurrently to the pool 11 | #[derive(Debug, Clone)] 12 | pub struct TransactionPool { 13 | transactions: SyncedTransactionVec, 14 | } 15 | 16 | // Basic operations in the transaction pool are encapsulated in the implementation 17 | // Encapsulates concurrency concerns, so external callers do not need to know how it's handled 18 | impl TransactionPool { 19 | // Creates a empty transaction pool 20 | pub fn new() -> TransactionPool { 21 | TransactionPool { 22 | transactions: SyncedTransactionVec::default(), 23 | } 24 | } 25 | 26 | // Adds a new transaction to the pool 27 | pub fn add_transaction(&self, transaction: Transaction) { 28 | // TODO: transactions should be validated before being included in the pool 29 | let mut transactions = self.transactions.lock().unwrap(); 30 | transactions.push(transaction); 31 | info!("transaction added"); 32 | } 33 | 34 | // Returns a copy of all transactions and empties the pool 35 | // This operation is safe to be called concurrently from multiple threads 36 | pub fn pop(&self) -> TransactionVec { 37 | // the "transactions" attribute is protected by a Mutex 38 | // so only one thread at a time can access the value when the lock is held 39 | // preventing inconsitencies when adding new transactions while a pop is in course 40 | let mut transactions = self.transactions.lock().unwrap(); 41 | let transactions_clone = transactions.clone(); 42 | transactions.clear(); 43 | 44 | transactions_clone 45 | } 46 | } 47 | 48 | #[cfg(test)] 49 | mod tests { 50 | use crate::model::test_util::{alice, bob}; 51 | 52 | use super::*; 53 | 54 | #[test] 55 | fn should_be_empty_after_creation() { 56 | let transaction_pool = TransactionPool::new(); 57 | 58 | let transactions = transaction_pool.pop(); 59 | assert!(transactions.is_empty()); 60 | } 61 | 62 | #[test] 63 | fn should_pop_single_value() { 64 | let transaction_pool = TransactionPool::new(); 65 | 66 | // add a new transaction to the pool 67 | let transaction = create_mock_transaction(1); 68 | transaction_pool.add_transaction(transaction.clone()); 69 | 70 | // pop the values and check that the transaction is included 71 | let mut transactions = transaction_pool.pop(); 72 | assert_eq!(transactions.len(), 1); 73 | assert_eq!(transactions[0].amount, transaction.amount); 74 | 75 | // after the previous pop, the pool should still be empty 76 | transactions = transaction_pool.pop(); 77 | assert!(transactions.is_empty()); 78 | } 79 | 80 | #[test] 81 | fn should_pop_multiple_values() { 82 | let transaction_pool = TransactionPool::new(); 83 | 84 | // add a new transaction to the pool 85 | let transaction_a = create_mock_transaction(1); 86 | let transaction_b = create_mock_transaction(2); 87 | transaction_pool.add_transaction(transaction_a.clone()); 88 | transaction_pool.add_transaction(transaction_b.clone()); 89 | 90 | // pop the values and check that the transactions are included 91 | let mut transactions = transaction_pool.pop(); 92 | assert_eq!(transactions.len(), 2); 93 | assert_eq!(transactions[0].amount, transaction_a.amount); 94 | assert_eq!(transactions[1].amount, transaction_b.amount); 95 | 96 | // after the previous pop, the pool should still be empty 97 | transactions = transaction_pool.pop(); 98 | assert!(transactions.is_empty()); 99 | } 100 | 101 | fn create_mock_transaction(amount: u64) -> Transaction { 102 | Transaction { 103 | sender: alice(), 104 | recipient: bob(), 105 | amount: amount, 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/peer.rs: -------------------------------------------------------------------------------- 1 | use std::panic; 2 | 3 | use crate::{ 4 | model::{Block, Blockchain}, 5 | util::{ 6 | execution::{sleep_millis, Runnable}, 7 | Context, 8 | }, 9 | }; 10 | use anyhow::Result; 11 | use isahc::{ReadResponseExt, Request}; 12 | 13 | pub struct Peer { 14 | peer_addresses: Vec, 15 | blockchain: Blockchain, 16 | peer_sync_ms: u64, 17 | } 18 | 19 | impl Runnable for Peer { 20 | fn run(&self) -> Result<()> { 21 | self.start() 22 | } 23 | } 24 | 25 | impl Peer { 26 | pub fn new(context: &Context) -> Peer { 27 | Peer { 28 | peer_addresses: context.config.peers.clone(), 29 | blockchain: context.blockchain.clone(), 30 | peer_sync_ms: context.config.peer_sync_ms, 31 | } 32 | } 33 | 34 | pub fn start(&self) -> Result<()> { 35 | if self.peer_addresses.is_empty() { 36 | info!("No peers configured, exiting peer sync system"); 37 | return Ok(()); 38 | } 39 | 40 | info!( 41 | "start peer system with peers: {}", 42 | self.peer_addresses.join(", ") 43 | ); 44 | 45 | // At regular intervals of time, we try to sync new blocks from our peers 46 | let mut last_sent_block_index = self.get_last_block_index(); 47 | loop { 48 | self.try_receive_new_blocks(); 49 | self.try_send_new_blocks(last_sent_block_index); 50 | last_sent_block_index = self.get_last_block_index(); 51 | sleep_millis(self.peer_sync_ms); 52 | } 53 | } 54 | 55 | fn get_last_block_index(&self) -> usize { 56 | self.blockchain.get_last_block().index as usize 57 | } 58 | 59 | // Retrieve new blocks from all peers and add them to the blockchain 60 | fn try_receive_new_blocks(&self) { 61 | for address in self.peer_addresses.iter() { 62 | // we don't want to panic if one peer is down or not working properly 63 | let result = panic::catch_unwind(|| { 64 | let new_blocks = self.get_new_blocks_from_peer(address); 65 | 66 | if !new_blocks.is_empty() { 67 | self.add_new_blocks(&new_blocks); 68 | } 69 | }); 70 | 71 | // if a peer is not working, we simply log it and ignore the error 72 | if result.is_err() { 73 | error!("Could not sync blocks from peer {}", address); 74 | } 75 | } 76 | } 77 | 78 | // Try to add a bunch of new blocks to our blockchain 79 | fn add_new_blocks(&self, new_blocks: &[Block]) { 80 | for block in new_blocks.iter() { 81 | let result = self.blockchain.add_block(block.clone()); 82 | 83 | // if a block is invalid, no point in trying to add the next ones 84 | if result.is_err() { 85 | error!("Could not add peer block {} to the blockchain", block.index); 86 | return; 87 | } 88 | 89 | info!("Added new peer block {} to the blockchain", block.index); 90 | } 91 | } 92 | 93 | // Retrieve only the new blocks from a peer 94 | fn get_new_blocks_from_peer(&self, address: &str) -> Vec { 95 | // we need to know the last block index in our blockchain 96 | let our_last_index = self.blockchain.get_last_block().index as usize; 97 | 98 | // we retrieve all the blocks from the peer 99 | let peer_blocks = self.get_blocks_from_peer(address); 100 | let peer_last_index = peer_blocks.last().unwrap().index as usize; 101 | 102 | // Check if the peer has new blocks 103 | if peer_last_index <= our_last_index { 104 | return Vec::::new(); 105 | } 106 | 107 | // The peer do have new blocks, and we return ONLY the new ones 108 | let first_new = our_last_index + 1; 109 | let last_new = peer_last_index; 110 | let new_blocks_range = first_new..=last_new; 111 | peer_blocks.get(new_blocks_range).unwrap().to_vec() 112 | } 113 | 114 | // Retrieve ALL blocks from a peer 115 | fn get_blocks_from_peer(&self, address: &str) -> Vec { 116 | let uri = format!("{}/blocks", address); 117 | let mut response = isahc::get(uri).unwrap(); 118 | 119 | // check that the response is sucessful 120 | assert_eq!(response.status().as_u16(), 200); 121 | 122 | // parse and return the list of blocks from the response body 123 | let raw_body = response.text().unwrap(); 124 | serde_json::from_str(&raw_body).unwrap() 125 | } 126 | 127 | // Try to broadcast all new blocks to peers since last time we broadcasted 128 | fn try_send_new_blocks(&self, last_send_block_index: usize) { 129 | let new_blocks = self.get_new_blocks_since(last_send_block_index); 130 | 131 | for block in new_blocks.iter() { 132 | for address in self.peer_addresses.iter() { 133 | // we don't want to panic if one peer is down or not working properly 134 | let result = panic::catch_unwind(|| { 135 | Peer::send_block_to_peer(address, block); 136 | }); 137 | 138 | if result.is_err() { 139 | error!("Could not send block {} to peer {}", block.index, address); 140 | return; 141 | } 142 | 143 | info!("Sended new block {} to peer {}", block.index, address); 144 | } 145 | } 146 | } 147 | 148 | // Return all new blocks added to the blockchain since the one with the indicated index 149 | fn get_new_blocks_since(&self, start_index: usize) -> Vec { 150 | let last_block_index = self.get_last_block_index(); 151 | let new_blocks_range = start_index + 1..=last_block_index; 152 | self.blockchain 153 | .get_all_blocks() 154 | .get(new_blocks_range) 155 | .unwrap() 156 | .to_vec() 157 | } 158 | 159 | // Send a block to a peer using the REST API of the peer 160 | fn send_block_to_peer(address: &str, block: &Block) { 161 | let uri = format!("{}/blocks", address); 162 | let body = serde_json::to_string(&block).unwrap(); 163 | 164 | let request = Request::post(uri) 165 | .header("Content-Type", "application/json") 166 | .body(body) 167 | .unwrap(); 168 | 169 | isahc::send(request).unwrap(); 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod context; 3 | pub mod execution; 4 | mod logger; 5 | pub mod termination; 6 | 7 | // Explicitly controlling which individual identifiers we export 8 | // It also avoids verbose module imports from other files 9 | pub use config::Config; 10 | pub use context::Context; 11 | pub use logger::initialize_logger; 12 | -------------------------------------------------------------------------------- /src/util/config.rs: -------------------------------------------------------------------------------- 1 | extern crate dotenv; 2 | 3 | use dotenv::dotenv; 4 | use std::env; 5 | use std::str::FromStr; 6 | 7 | use crate::model::Address; 8 | 9 | type StringVec = Vec; 10 | 11 | // Encapsulates configuration values to be used across the application 12 | // It ensures correct typing and that at least they will have a default value 13 | pub struct Config { 14 | // Networking settings 15 | pub port: u16, 16 | 17 | // Peer settings 18 | pub peers: StringVec, 19 | pub peer_sync_ms: u64, 20 | 21 | // Miner settings 22 | pub max_blocks: u64, 23 | pub max_nonce: u64, 24 | pub difficulty: u32, 25 | pub tx_waiting_ms: u64, 26 | pub miner_address: Address, 27 | } 28 | 29 | // The implementation reads the values from environment variables 30 | // If a value is missing then it enforces a default value 31 | impl Config { 32 | // Parse and return configuration values from environment variables 33 | pub fn read() -> Config { 34 | dotenv().ok(); 35 | 36 | Config { 37 | // Networking settings 38 | port: Config::read_envvar::("PORT", 8000), 39 | 40 | // Peer settings 41 | peers: Config::read_vec_envvar("PEERS", ",", StringVec::default()), 42 | peer_sync_ms: Config::read_envvar::("PEER_SYNC_MS", 10000), 43 | 44 | // Miner settings 45 | max_blocks: Config::read_envvar::("MAX_BLOCKS", 0), // unlimited blocks 46 | max_nonce: Config::read_envvar::("MAX_NONCE", 1_000_000), 47 | difficulty: Config::read_envvar::("DIFFICULTY", 10), 48 | tx_waiting_ms: Config::read_envvar::("TRANSACTION_WAITING_MS", 10000), 49 | miner_address: Config::read_envvar::
("MINER_ADDRESS", Address::default()), 50 | } 51 | } 52 | 53 | // Parses a singular value from a environment variable, accepting a default value if missing 54 | fn read_envvar(key: &str, default_value: T) -> T { 55 | match env::var(key) { 56 | Ok(val) => val.parse::().unwrap_or(default_value), 57 | Err(_e) => default_value, 58 | } 59 | } 60 | 61 | // Parses a multiple value (Vec) from a environment variable, accepting a default value if missing 62 | fn read_vec_envvar(key: &str, separator: &str, default_value: StringVec) -> StringVec { 63 | match env::var(key) { 64 | Ok(val) => val 65 | .trim() 66 | .split_terminator(separator) 67 | .map(str::to_string) 68 | .collect(), 69 | Err(_e) => default_value, 70 | } 71 | } 72 | } 73 | 74 | #[cfg(test)] 75 | mod tests { 76 | use super::*; 77 | 78 | #[test] 79 | fn read_present_envvar() { 80 | let var_name = "PRESENT_ENVVAR"; 81 | let real_value = 9000; 82 | env::set_var(var_name, real_value.to_string()); 83 | 84 | // read the present var, should NOT return the default value but the real one 85 | let default_value = 8000 as u16; 86 | let value = Config::read_envvar::(var_name, default_value); 87 | 88 | assert_eq!(value, real_value); 89 | 90 | // let's remove the var at the end to not pollute the environment 91 | env::remove_var(var_name); 92 | } 93 | 94 | #[test] 95 | fn read_present_vec_envvar() { 96 | let var_name = "PRESENT_VEC_ENVVAR"; 97 | let value = "FOO,BAR"; 98 | env::set_var(var_name, value.to_string()); 99 | 100 | // read the present var, should NOT return the default value but the real one 101 | let default_value = StringVec::default(); 102 | let actual_value = Config::read_vec_envvar(var_name, ",", default_value.clone()); 103 | let expected_value: Vec = value.split(",").map(str::to_string).collect(); 104 | 105 | assert!(do_vecs_match(&actual_value, &expected_value)); 106 | 107 | // let's remove the var at the end to not pollute the environment 108 | env::remove_var(var_name); 109 | } 110 | 111 | #[test] 112 | fn read_non_present_envvar() { 113 | let var_name = "NON_PRESENT_ENVVAR"; 114 | 115 | // let's remove the var just to make sure it's not setted 116 | env::remove_var(var_name); 117 | 118 | // read the non present var, should return the default value 119 | let default_value = 8000 as u16; 120 | let value = Config::read_envvar::(var_name, default_value); 121 | assert_eq!(value, default_value); 122 | 123 | // same for vec variables 124 | let default_vec_value = StringVec::default(); 125 | let vec_value = Config::read_vec_envvar(var_name, ",", default_vec_value.clone()); 126 | assert_eq!(&vec_value, &default_vec_value); 127 | } 128 | 129 | #[test] 130 | fn read_invalid_envvar() { 131 | // envvars should not have the "=" character in the name 132 | let var_name = "INVALID=VAR=NAME"; 133 | 134 | // read the invalid var, should return the default value 135 | let default_value = 8000 as u16; 136 | let value = Config::read_envvar::(var_name, default_value); 137 | assert_eq!(value, default_value); 138 | 139 | // read the invalid var as a vector, should return the default value as well 140 | let default_vec_value = StringVec::default(); 141 | let vec_value = Config::read_vec_envvar(var_name, ",", default_vec_value.clone()); 142 | assert!(do_vecs_match(&vec_value, &default_vec_value)); 143 | } 144 | 145 | // All credit for this function to https://stackoverflow.com/a/58175659 146 | fn do_vecs_match(a: &Vec, b: &Vec) -> bool { 147 | let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count(); 148 | matching == a.len() && matching == b.len() 149 | } 150 | } 151 | -------------------------------------------------------------------------------- /src/util/context.rs: -------------------------------------------------------------------------------- 1 | use super::Config; 2 | use crate::model::{Blockchain, TransactionPool}; 3 | 4 | pub struct Context { 5 | pub config: Config, 6 | pub blockchain: Blockchain, 7 | pub pool: TransactionPool, 8 | } 9 | -------------------------------------------------------------------------------- /src/util/execution.rs: -------------------------------------------------------------------------------- 1 | use anyhow::Result; 2 | use crossbeam_utils::thread; 3 | use std::time; 4 | 5 | pub trait Runnable: Sync { 6 | fn run(&self) -> Result<()>; 7 | } 8 | 9 | pub fn run_in_parallel(runnables: Vec<&dyn Runnable>) { 10 | thread::scope(|s| { 11 | for runnable in runnables { 12 | s.spawn(move |_| { 13 | runnable.run().unwrap(); 14 | }); 15 | } 16 | }) 17 | .unwrap(); 18 | } 19 | 20 | // Suspend the execution of the thread by a particular amount of milliseconds 21 | pub fn sleep_millis(millis: u64) { 22 | let wait_duration = time::Duration::from_millis(millis); 23 | std::thread::sleep(wait_duration); 24 | } 25 | -------------------------------------------------------------------------------- /src/util/logger.rs: -------------------------------------------------------------------------------- 1 | use env_logger::{Builder, Target}; 2 | use log::LevelFilter; 3 | 4 | pub fn initialize_logger() { 5 | let mut builder = Builder::from_default_env(); 6 | builder.target(Target::Stdout); 7 | builder.filter(None, LevelFilter::Info); 8 | builder.init(); 9 | } 10 | -------------------------------------------------------------------------------- /src/util/termination.rs: -------------------------------------------------------------------------------- 1 | // Quit the program when the user inputs Ctrl-C 2 | pub fn set_ctrlc_handler() { 3 | ctrlc::set_handler(move || { 4 | std::process::exit(0); 5 | }) 6 | .expect("Error setting Ctrl-C handler"); 7 | } 8 | -------------------------------------------------------------------------------- /tests/api_test.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use serial_test::serial; 4 | 5 | use crate::common::{ 6 | Api, Block, BlockHash, ServerBuilder, Transaction, ALICE, BLOCK_SUBSIDY, BOB, MINER_ADDRESS, 7 | }; 8 | 9 | #[test] 10 | #[serial] 11 | #[cfg(unix)] 12 | fn test_should_get_a_valid_genesis_block() { 13 | let node = ServerBuilder::new().start(); 14 | 15 | // list the blocks by querying the REST API 16 | let blocks = node.get_blocks(); 17 | 18 | // check that the blocks only contain the genesis block 19 | assert_eq!(blocks.len(), 1); 20 | let genesis_block = blocks.first().unwrap(); 21 | 22 | // check that the genesis block fields are valid 23 | assert_eq!(genesis_block.index, 0); 24 | assert_eq!(genesis_block.nonce, 0); 25 | assert_eq!(genesis_block.previous_hash, BlockHash::default()); 26 | assert!(genesis_block.transactions.is_empty()); 27 | } 28 | 29 | #[test] 30 | #[serial] 31 | #[cfg(unix)] 32 | fn test_should_let_add_transactions() { 33 | let mut node = ServerBuilder::new().start(); 34 | let genesis_block = node.get_last_block(); 35 | 36 | // create and add a new transaction to the pool 37 | // the sender must the mining address, 38 | // as it should have funds from the coinbase reward of the genesis block 39 | let transaction = Transaction { 40 | sender: MINER_ADDRESS.to_string(), 41 | recipient: BOB.to_string(), 42 | amount: 10 as u64, 43 | }; 44 | let res = node.add_transaction(&transaction); 45 | assert_eq!(res.status().as_u16(), 200); 46 | 47 | // wait for the transaction to be mined 48 | node.wait_for_mining(); 49 | 50 | // check that a new bock was added... 51 | let blocks = node.get_blocks(); 52 | assert_eq!(blocks.len(), 2); 53 | let mined_block = blocks.last().unwrap(); 54 | 55 | // ...and is valid 56 | assert_eq!(mined_block.index, 1); 57 | assert_eq!(mined_block.previous_hash, genesis_block.hash); 58 | 59 | // ...and contains the transaction that we added (plus the coinbase) 60 | assert_eq!(mined_block.transactions.len(), 2); 61 | let mined_transaction = mined_block.transactions.last().unwrap(); 62 | assert_eq!(*mined_transaction, transaction); 63 | } 64 | 65 | #[test] 66 | #[serial] 67 | #[cfg(unix)] 68 | fn test_should_let_add_valid_block() { 69 | let node = ServerBuilder::new().start(); 70 | let genesis_block = node.get_last_block(); 71 | let coinbase = Transaction { 72 | sender: ALICE.to_string(), 73 | recipient: ALICE.to_string(), 74 | amount: BLOCK_SUBSIDY, 75 | }; 76 | 77 | let valid_block = Block { 78 | // there is the genesis block already, so the next index is 1 79 | index: 1, 80 | timestamp: 0, 81 | nonce: 0, 82 | // the previous hash is checked 83 | previous_hash: genesis_block.hash, 84 | // the api automatically recalculates the hash... 85 | // ...so no need to add a valid one here 86 | hash: BlockHash::default(), 87 | // must include the coinbase transaction 88 | transactions: vec![coinbase], 89 | }; 90 | let res = node.add_block(&valid_block); 91 | assert_eq!(res.status().as_u16(), 200); 92 | } 93 | 94 | #[test] 95 | #[serial] 96 | #[cfg(unix)] 97 | fn test_should_not_let_add_invalid_block() { 98 | let node = ServerBuilder::new().start(); 99 | 100 | // let's try to add a new INVALID block, should return an error 101 | let invalid_block = Block { 102 | index: 0, // not valid index, the genesis block already has index 0 103 | timestamp: 0, 104 | nonce: 0, 105 | previous_hash: BlockHash::default(), // also not valid 106 | hash: BlockHash::default(), 107 | transactions: [].to_vec(), 108 | }; 109 | let res = node.add_block(&invalid_block); 110 | assert_eq!(res.status().as_u16(), 400); 111 | } 112 | -------------------------------------------------------------------------------- /tests/common/api.rs: -------------------------------------------------------------------------------- 1 | use ethereum_types::U256; 2 | use isahc::{Body, ReadResponseExt, Request, Response}; 3 | use serde::{Deserialize, Serialize}; 4 | 5 | use super::server::Server; 6 | 7 | pub type BlockHash = U256; 8 | 9 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 10 | pub struct Block { 11 | pub index: u64, 12 | pub timestamp: i64, 13 | pub nonce: u64, 14 | pub previous_hash: BlockHash, 15 | pub hash: BlockHash, 16 | pub transactions: Vec, 17 | } 18 | 19 | #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] 20 | pub struct Transaction { 21 | pub sender: String, 22 | pub recipient: String, 23 | pub amount: u64, 24 | } 25 | 26 | #[allow(dead_code)] 27 | pub const ALICE: &str = "f780b958227ff0bf5795ede8f9f7eaac67e7e06666b043a400026cbd421ce28e"; 28 | 29 | #[allow(dead_code)] 30 | pub const BOB: &str = "51df097c03c0a6e64e54a6fce90cb6968adebd85955917ed438e3d3c05f2f00f"; 31 | 32 | #[allow(dead_code)] 33 | pub const BLOCK_SUBSIDY: u64 = 100; 34 | 35 | pub trait Api { 36 | fn get_blocks(&self) -> Vec; 37 | fn get_last_block(&self) -> Block; 38 | fn add_block(&self, block: &Block) -> Response; 39 | fn add_valid_block(&self) -> Response; 40 | fn add_transaction(&self, transaction: &Transaction) -> Response; 41 | } 42 | 43 | impl Api for Server { 44 | fn get_blocks(&self) -> Vec { 45 | // list the blocks by querying the REST API 46 | let uri = format!("{}/blocks", get_base_url(self)); 47 | let mut response = isahc::get(uri).unwrap(); 48 | 49 | // check that the response is sucessful 50 | assert_eq!(response.status().as_u16(), 200); 51 | 52 | // parse the list of blocks from the response body 53 | let raw_body = response.text().unwrap(); 54 | let blocks: Vec = serde_json::from_str(&raw_body).unwrap(); 55 | 56 | blocks 57 | } 58 | 59 | fn get_last_block(&self) -> Block { 60 | self.get_blocks().last().unwrap().to_owned() 61 | } 62 | 63 | fn add_valid_block(&self) -> Response { 64 | let last_block = self.get_last_block(); 65 | let coinbase = Transaction { 66 | sender: ALICE.to_string(), 67 | recipient: ALICE.to_string(), 68 | amount: BLOCK_SUBSIDY, 69 | }; 70 | let valid_block = Block { 71 | index: last_block.index + 1, 72 | timestamp: 0, 73 | nonce: 0, 74 | // the previous hash is checked 75 | previous_hash: last_block.hash, 76 | // the api automatically recalculates the hash... 77 | // ...so no need to add a valid one here 78 | hash: BlockHash::default(), 79 | transactions: vec![coinbase], 80 | }; 81 | self.add_block(&valid_block) 82 | } 83 | 84 | fn add_block(&self, block: &Block) -> Response { 85 | // send the request to the REST API 86 | let uri = format!("{}/blocks", get_base_url(self)); 87 | let body = serde_json::to_string(&block).unwrap(); 88 | 89 | post_request(uri, body) 90 | } 91 | 92 | fn add_transaction(&self, transaction: &Transaction) -> Response { 93 | // send the request to the REST API 94 | let uri = format!("{}/transactions", get_base_url(self)); 95 | let body = serde_json::to_string(&transaction).unwrap(); 96 | 97 | post_request(uri, body) 98 | } 99 | } 100 | 101 | fn get_base_url(server: &Server) -> String { 102 | format!("http://localhost:{}", server.config.port) 103 | } 104 | 105 | fn post_request(uri: String, body: String) -> Response { 106 | let request = Request::post(uri) 107 | .header("Content-Type", "application/json") 108 | .body(body) 109 | .unwrap(); 110 | 111 | isahc::send(request).unwrap() 112 | } 113 | -------------------------------------------------------------------------------- /tests/common/mod.rs: -------------------------------------------------------------------------------- 1 | mod api; 2 | mod server; 3 | 4 | pub use api::*; 5 | pub use server::*; 6 | -------------------------------------------------------------------------------- /tests/common/server.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | convert::TryInto, 3 | io::{BufRead, BufReader}, 4 | process::{Child, Command, Stdio}, 5 | sync::{Arc, Mutex}, 6 | thread, 7 | time::{Duration, Instant}, 8 | }; 9 | 10 | use assert_cmd::cargo::cargo_bin; 11 | use nix::{ 12 | sys::signal::{kill, Signal::SIGTERM}, 13 | unistd::Pid, 14 | }; 15 | 16 | pub const MINER_ADDRESS: &str = "0000000000000000000000000000000000000000000000000000000000000000"; 17 | 18 | pub struct Config { 19 | pub port: u16, 20 | pub peers: Vec, 21 | pub peer_sync_ms: u64, 22 | pub max_blocks: u64, 23 | pub max_nonce: u64, 24 | pub difficulty: u32, 25 | pub tx_waiting_ms: u64, 26 | pub miner_address: String, 27 | } 28 | 29 | pub struct ServerBuilder { 30 | config: Config, 31 | } 32 | 33 | #[allow(dead_code)] 34 | impl ServerBuilder { 35 | pub fn new() -> ServerBuilder { 36 | // set the default values 37 | let config = Config { 38 | port: 8000, 39 | // not to high to avoid waiting too much, not too shot to spam it 40 | peer_sync_ms: 10, 41 | // no difficulty to minimize the mining time 42 | difficulty: 0, 43 | // not to high to avoid waiting, not too shot to spam it 44 | tx_waiting_ms: 10, 45 | peers: Vec::::new(), 46 | max_blocks: 0, // unlimited blocks 47 | max_nonce: 0, // unlimited nonce 48 | miner_address: MINER_ADDRESS.to_string(), 49 | }; 50 | 51 | ServerBuilder { config } 52 | } 53 | 54 | pub fn difficulty(mut self, difficulty: u32) -> ServerBuilder { 55 | self.config.difficulty = difficulty; 56 | self 57 | } 58 | 59 | pub fn port(mut self, port: u16) -> ServerBuilder { 60 | self.config.port = port; 61 | self 62 | } 63 | 64 | pub fn peer(mut self, port: u64) -> ServerBuilder { 65 | let address = format!("http://localhost:{}", port); 66 | self.config.peers.push(address); 67 | self 68 | } 69 | 70 | pub fn start(self) -> Server { 71 | Server::new(self.config) 72 | } 73 | } 74 | 75 | type SyncedOutput = Arc>>; 76 | 77 | pub struct Server { 78 | pub config: Config, 79 | process: Child, 80 | output: SyncedOutput, 81 | } 82 | 83 | #[allow(dead_code)] 84 | impl Server { 85 | pub fn new(config: Config) -> Server { 86 | let mut process = Server::start_process(&config); 87 | let output = Server::start_stdout_reading(&mut process); 88 | 89 | let mut server = Server { 90 | process, 91 | config, 92 | output, 93 | }; 94 | 95 | // We return the server only after all the processes have started 96 | // The last process to start is the rest api, so we wait until de output indicates it 97 | server.wait_for_log_message("actix-web-service"); 98 | 99 | server 100 | } 101 | 102 | // start the blockchain application in the background 103 | fn start_process(config: &Config) -> Child { 104 | Command::new(cargo_bin("rust_blockchain")) 105 | .env("PORT", config.port.to_string()) 106 | .env("PEERS", config.peers.join(",")) 107 | .env("DIFFICULTY", config.difficulty.to_string()) 108 | .env("TRANSACTION_WAITING_MS", config.tx_waiting_ms.to_string()) 109 | .env("PEER_SYNC_MS", config.peer_sync_ms.to_string()) 110 | .env("MINER_ADDRESS", config.miner_address.clone()) 111 | .stdout(Stdio::piped()) 112 | .stderr(Stdio::piped()) 113 | .spawn() 114 | .unwrap() 115 | } 116 | 117 | // start reading the process output in a separate thread (to not block the execution) 118 | // and continously update a shared value ("output") containing all the log messages in order 119 | fn start_stdout_reading(process: &mut Child) -> SyncedOutput { 120 | let output = Arc::new(Mutex::new(Vec::::new())); 121 | let thread_output = output.clone(); 122 | let stdout = process.stdout.take().unwrap(); 123 | thread::spawn(move || { 124 | let buf = BufReader::new(stdout); 125 | for line in buf.lines() { 126 | match line { 127 | Ok(_) => { 128 | thread_output.lock().unwrap().push(line.unwrap()); 129 | continue; 130 | } 131 | Err(_) => { 132 | break; 133 | } 134 | } 135 | } 136 | }); 137 | 138 | output 139 | } 140 | 141 | // block the execution until we mine a new block 142 | pub fn wait_for_mining(&mut self) { 143 | self.wait_for_log_message("valid block found for index"); 144 | } 145 | 146 | // block the execution until we sync a new block 147 | pub fn wait_for_peer_sync(&mut self) { 148 | self.wait_for_log_message("Added new peer block"); 149 | } 150 | 151 | // block the execution until we receive a new block via api 152 | pub fn wait_to_receive_block_in_api(&mut self) { 153 | self.wait_for_log_message("Received new block"); 154 | } 155 | 156 | // block the execution until a message is contained in the process output 157 | // or until a max time has passed 158 | fn wait_for_log_message(&mut self, message: &str) { 159 | // time interval to check for new output messages 160 | let wait_time = Duration::from_millis(50); 161 | // max time that we are going to wait for the message to appear 162 | let max_wait_time = Duration::from_millis(500); 163 | 164 | let start = Instant::now(); 165 | while Instant::now() < start + max_wait_time { 166 | let message_was_found = self.search_message_in_output(message); 167 | if message_was_found { 168 | return; 169 | } 170 | thread::sleep(wait_time); 171 | } 172 | } 173 | 174 | fn search_message_in_output(&mut self, message: &str) -> bool { 175 | let lines = self.output.lock().unwrap(); 176 | for line in lines.iter() { 177 | if line.contains(message) { 178 | return true; 179 | } 180 | } 181 | 182 | false 183 | } 184 | 185 | fn stop(&mut self) { 186 | println!("Shutting down server on port {}", self.config.port); 187 | 188 | kill(self.get_pid(), SIGTERM).unwrap(); 189 | 190 | // block the thread until the server has finished 191 | self.wait_for_termination(); 192 | } 193 | 194 | fn get_pid(&mut self) -> Pid { 195 | Pid::from_raw(self.process.id().try_into().unwrap()) 196 | } 197 | 198 | fn wait_for_termination(&mut self) { 199 | let max_waiting_in_secs = 5; 200 | 201 | // check every second if the child has finished 202 | for _ in 0..max_waiting_in_secs { 203 | match self.process.try_wait().unwrap() { 204 | // has finished, so we exit 205 | Some(_) => return, 206 | // hasn't finished, we wait another second 207 | None => Server::sleep_millis(1000), 208 | } 209 | } 210 | 211 | // at this point, we waited but the child didn't finish 212 | // so we forcefully kill it 213 | let _ = self.process.kill(); 214 | self.process.wait().unwrap(); 215 | } 216 | 217 | fn sleep_millis(millis: u64) { 218 | let wait_duration = Duration::from_millis(millis); 219 | thread::sleep(wait_duration); 220 | } 221 | } 222 | 223 | // Stopping the server on variable drop allows us to never worry about 224 | // leaving a zombie child process in the background. 225 | // The Rust compiler ensures that this will be always called no matter what (success or panic) 226 | // as soon as the variable is out of scope 227 | impl Drop for Server { 228 | fn drop(&mut self) { 229 | self.stop(); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /tests/peer_test.rs: -------------------------------------------------------------------------------- 1 | mod common; 2 | 3 | use crate::common::{Api, ServerBuilder}; 4 | use serial_test::serial; 5 | 6 | #[test] 7 | #[serial] 8 | #[cfg(unix)] 9 | fn test_should_receive_new_valid_blocks() { 10 | // We will use this node to be the most updated one 11 | let leader_node = ServerBuilder::new().port(8000).start(); 12 | 13 | // This new node will keep asking for new blocks to the leader node 14 | let mut follower_node = ServerBuilder::new().port(8001).peer(8000).start(); 15 | 16 | // At the beggining, both nodes will only have the genesis blocks 17 | assert_eq!(leader_node.get_blocks().len(), 1); 18 | assert_eq!(follower_node.get_blocks().len(), 1); 19 | 20 | // we create a new valid block in the leader node 21 | leader_node.add_valid_block(); 22 | assert_eq!(leader_node.get_blocks().len(), 2); 23 | 24 | // the follower node should eventually ask and add the new block 25 | follower_node.wait_for_peer_sync(); 26 | assert_eq!(follower_node.get_blocks().len(), 2); 27 | 28 | // the last blocks from both the follower and the leader must match 29 | let last_leader_block = leader_node.get_last_block(); 30 | let last_follower_block = leader_node.get_last_block(); 31 | assert_eq!(last_follower_block, last_leader_block); 32 | } 33 | 34 | #[test] 35 | #[serial] 36 | #[cfg(unix)] 37 | fn test_should_not_receive_new_invalid_blocks() { 38 | // We will use this node to be the most updated one 39 | let leader_node = ServerBuilder::new().port(8000).start(); 40 | 41 | // This new node will keep asking for new blocks to the leader node 42 | // But we will require a much higher difficulty, so it should not accept blocks from the leader 43 | let mut follower_node = ServerBuilder::new() 44 | .difficulty(20) 45 | .port(8001) 46 | .peer(8000) 47 | .start(); 48 | 49 | // we create a new valid block in the leader node 50 | leader_node.add_valid_block(); 51 | 52 | // the follower node should eventually ask and receive the new block 53 | follower_node.wait_for_peer_sync(); 54 | 55 | // but the block should not be added as the difficulty will not match 56 | assert_eq!(follower_node.get_blocks().len(), 1); 57 | } 58 | 59 | #[test] 60 | #[serial] 61 | #[cfg(unix)] 62 | fn test_should_ignore_unavailable_peers() { 63 | // We will use this node to be the most updated one 64 | let leader_node = ServerBuilder::new().port(8000).start(); 65 | 66 | // This new node will keep asking for new blocks to the leader node 67 | // but also to a node that does not exist... 68 | let mut follower_node = ServerBuilder::new() 69 | .port(8001) 70 | .peer(9000) 71 | .peer(8000) 72 | .start(); 73 | 74 | // we create a new valid block in the leader node 75 | leader_node.add_valid_block(); 76 | 77 | // the follower node should eventually ask and receive the new block 78 | follower_node.wait_for_peer_sync(); 79 | 80 | // even if one of the peers does not exist, it ignores the error and adds blocks from available peers 81 | assert_eq!(follower_node.get_blocks().len(), 2); 82 | } 83 | 84 | #[test] 85 | #[serial] 86 | #[cfg(unix)] 87 | fn test_should_send_new_blocks() { 88 | // This node will always be behind the leader node 89 | let mut follower_node = ServerBuilder::new().port(8000).start(); 90 | 91 | // We will use this node to be the most updated one 92 | let leader_node = ServerBuilder::new().port(8001).peer(8000).start(); 93 | 94 | // At the beggining, both nodes will only have the genesis blocks 95 | assert_eq!(leader_node.get_blocks().len(), 1); 96 | assert_eq!(follower_node.get_blocks().len(), 1); 97 | 98 | // we create a new valid block in the leader node 99 | leader_node.add_valid_block(); 100 | assert_eq!(leader_node.get_blocks().len(), 2); 101 | 102 | // the follower node should eventually receive and add the new block 103 | follower_node.wait_to_receive_block_in_api(); 104 | assert_eq!(follower_node.get_blocks().len(), 2); 105 | 106 | // the last blocks from both the follower and the leader must match 107 | let last_leader_block = leader_node.get_last_block(); 108 | let last_follower_block = leader_node.get_last_block(); 109 | assert_eq!(last_follower_block, last_leader_block); 110 | } 111 | --------------------------------------------------------------------------------