├── CONTRIBUTING.md ├── Cargo.lock ├── Cargo.toml ├── DEMO.md ├── PLAN.md ├── README.md ├── build.rs ├── docs ├── INTRO.md └── SPEC.md ├── examples ├── standalone-channels │ └── main.rs └── standalone-http │ └── main.rs ├── protos └── protos.proto ├── rustfmt.toml └── src ├── algos.rs ├── cli ├── cmd │ ├── accounts.rs │ ├── mod.rs │ ├── network.rs │ └── node.rs ├── tendermint.rs └── utils.rs ├── config.rs ├── crypto.rs ├── events.rs ├── lib.rs ├── main.rs ├── messages.rs ├── params.rs ├── process.rs ├── rpc_client.rs └── rpc_server.rs /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing 2 | ============ 3 | 4 | ## Project maintenance. 5 | 6 | **Lint:** 7 | 8 | ```sh 9 | cargo clippy --fix --allow-dirty 10 | ``` 11 | 12 | **Format:** 13 | 14 | ```sh 15 | rustup component add rustfmt --toolchain nightly 16 | cargo +nightly fmt 17 | ``` 18 | 19 | -------------------------------------------------------------------------------- /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 = "addr2line" 7 | version = "0.24.1" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "f5fb1d8e4442bd405fdfd1dacb42792696b0cf9cb15882e5d097b742a676d375" 10 | dependencies = [ 11 | "gimli", 12 | ] 13 | 14 | [[package]] 15 | name = "adler2" 16 | version = "2.0.0" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" 19 | 20 | [[package]] 21 | name = "aho-corasick" 22 | version = "1.1.3" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 25 | dependencies = [ 26 | "memchr", 27 | ] 28 | 29 | [[package]] 30 | name = "atomic-waker" 31 | version = "1.1.2" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" 34 | 35 | [[package]] 36 | name = "atty" 37 | version = "0.2.14" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 40 | dependencies = [ 41 | "hermit-abi 0.1.19", 42 | "libc", 43 | "winapi", 44 | ] 45 | 46 | [[package]] 47 | name = "autocfg" 48 | version = "1.2.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" 51 | 52 | [[package]] 53 | name = "backtrace" 54 | version = "0.3.74" 55 | source = "registry+https://github.com/rust-lang/crates.io-index" 56 | checksum = "8d82cb332cdfaed17ae235a638438ac4d4839913cc2af585c3c6746e8f8bee1a" 57 | dependencies = [ 58 | "addr2line", 59 | "cfg-if", 60 | "libc", 61 | "miniz_oxide", 62 | "object", 63 | "rustc-demangle", 64 | "windows-targets", 65 | ] 66 | 67 | [[package]] 68 | name = "base64" 69 | version = "0.21.7" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" 72 | 73 | [[package]] 74 | name = "base64" 75 | version = "0.22.1" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" 78 | 79 | [[package]] 80 | name = "bitflags" 81 | version = "1.3.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 84 | 85 | [[package]] 86 | name = "bitflags" 87 | version = "2.6.0" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" 90 | 91 | [[package]] 92 | name = "block-buffer" 93 | version = "0.10.4" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" 96 | dependencies = [ 97 | "generic-array", 98 | ] 99 | 100 | [[package]] 101 | name = "bumpalo" 102 | version = "3.16.0" 103 | source = "registry+https://github.com/rust-lang/crates.io-index" 104 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 105 | 106 | [[package]] 107 | name = "byteorder" 108 | version = "1.5.0" 109 | source = "registry+https://github.com/rust-lang/crates.io-index" 110 | checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" 111 | 112 | [[package]] 113 | name = "bytes" 114 | version = "1.7.1" 115 | source = "registry+https://github.com/rust-lang/crates.io-index" 116 | checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" 117 | 118 | [[package]] 119 | name = "cc" 120 | version = "1.1.19" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "2d74707dde2ba56f86ae90effb3b43ddd369504387e718014de010cec7959800" 123 | dependencies = [ 124 | "shlex", 125 | ] 126 | 127 | [[package]] 128 | name = "cfg-if" 129 | version = "1.0.0" 130 | source = "registry+https://github.com/rust-lang/crates.io-index" 131 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 132 | 133 | [[package]] 134 | name = "clap" 135 | version = "3.2.25" 136 | source = "registry+https://github.com/rust-lang/crates.io-index" 137 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 138 | dependencies = [ 139 | "atty", 140 | "bitflags 1.3.2", 141 | "clap_derive", 142 | "clap_lex", 143 | "indexmap 1.9.3", 144 | "once_cell", 145 | "strsim", 146 | "termcolor", 147 | "textwrap", 148 | ] 149 | 150 | [[package]] 151 | name = "clap_derive" 152 | version = "3.2.25" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "ae6371b8bdc8b7d3959e9cf7b22d4435ef3e79e138688421ec654acf8c81b008" 155 | dependencies = [ 156 | "heck", 157 | "proc-macro-error", 158 | "proc-macro2", 159 | "quote", 160 | "syn 1.0.109", 161 | ] 162 | 163 | [[package]] 164 | name = "clap_lex" 165 | version = "0.2.4" 166 | source = "registry+https://github.com/rust-lang/crates.io-index" 167 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 168 | dependencies = [ 169 | "os_str_bytes", 170 | ] 171 | 172 | [[package]] 173 | name = "core-foundation" 174 | version = "0.9.4" 175 | source = "registry+https://github.com/rust-lang/crates.io-index" 176 | checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" 177 | dependencies = [ 178 | "core-foundation-sys", 179 | "libc", 180 | ] 181 | 182 | [[package]] 183 | name = "core-foundation-sys" 184 | version = "0.8.7" 185 | source = "registry+https://github.com/rust-lang/crates.io-index" 186 | checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" 187 | 188 | [[package]] 189 | name = "cpufeatures" 190 | version = "0.2.14" 191 | source = "registry+https://github.com/rust-lang/crates.io-index" 192 | checksum = "608697df725056feaccfa42cffdaeeec3fccc4ffc38358ecd19b243e716a78e0" 193 | dependencies = [ 194 | "libc", 195 | ] 196 | 197 | [[package]] 198 | name = "crypto-common" 199 | version = "0.1.6" 200 | source = "registry+https://github.com/rust-lang/crates.io-index" 201 | checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" 202 | dependencies = [ 203 | "generic-array", 204 | "typenum", 205 | ] 206 | 207 | [[package]] 208 | name = "data-encoding" 209 | version = "2.6.0" 210 | source = "registry+https://github.com/rust-lang/crates.io-index" 211 | checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" 212 | 213 | [[package]] 214 | name = "digest" 215 | version = "0.10.7" 216 | source = "registry+https://github.com/rust-lang/crates.io-index" 217 | checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" 218 | dependencies = [ 219 | "block-buffer", 220 | "crypto-common", 221 | ] 222 | 223 | [[package]] 224 | name = "encoding_rs" 225 | version = "0.8.34" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" 228 | dependencies = [ 229 | "cfg-if", 230 | ] 231 | 232 | [[package]] 233 | name = "equivalent" 234 | version = "1.0.1" 235 | source = "registry+https://github.com/rust-lang/crates.io-index" 236 | checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" 237 | 238 | [[package]] 239 | name = "errno" 240 | version = "0.3.9" 241 | source = "registry+https://github.com/rust-lang/crates.io-index" 242 | checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" 243 | dependencies = [ 244 | "libc", 245 | "windows-sys 0.52.0", 246 | ] 247 | 248 | [[package]] 249 | name = "fastrand" 250 | version = "2.1.1" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" 253 | 254 | [[package]] 255 | name = "fnv" 256 | version = "1.0.7" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 259 | 260 | [[package]] 261 | name = "foreign-types" 262 | version = "0.3.2" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" 265 | dependencies = [ 266 | "foreign-types-shared", 267 | ] 268 | 269 | [[package]] 270 | name = "foreign-types-shared" 271 | version = "0.1.1" 272 | source = "registry+https://github.com/rust-lang/crates.io-index" 273 | checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" 274 | 275 | [[package]] 276 | name = "form_urlencoded" 277 | version = "1.2.1" 278 | source = "registry+https://github.com/rust-lang/crates.io-index" 279 | checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" 280 | dependencies = [ 281 | "percent-encoding", 282 | ] 283 | 284 | [[package]] 285 | name = "futures-channel" 286 | version = "0.3.30" 287 | source = "registry+https://github.com/rust-lang/crates.io-index" 288 | checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" 289 | dependencies = [ 290 | "futures-core", 291 | "futures-sink", 292 | ] 293 | 294 | [[package]] 295 | name = "futures-core" 296 | version = "0.3.30" 297 | source = "registry+https://github.com/rust-lang/crates.io-index" 298 | checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" 299 | 300 | [[package]] 301 | name = "futures-sink" 302 | version = "0.3.30" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" 305 | 306 | [[package]] 307 | name = "futures-task" 308 | version = "0.3.30" 309 | source = "registry+https://github.com/rust-lang/crates.io-index" 310 | checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" 311 | 312 | [[package]] 313 | name = "futures-util" 314 | version = "0.3.30" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" 317 | dependencies = [ 318 | "futures-core", 319 | "futures-sink", 320 | "futures-task", 321 | "pin-project-lite", 322 | "pin-utils", 323 | "slab", 324 | ] 325 | 326 | [[package]] 327 | name = "generic-array" 328 | version = "0.14.7" 329 | source = "registry+https://github.com/rust-lang/crates.io-index" 330 | checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" 331 | dependencies = [ 332 | "typenum", 333 | "version_check", 334 | ] 335 | 336 | [[package]] 337 | name = "getrandom" 338 | version = "0.2.15" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" 341 | dependencies = [ 342 | "cfg-if", 343 | "libc", 344 | "wasi", 345 | ] 346 | 347 | [[package]] 348 | name = "gimli" 349 | version = "0.31.0" 350 | source = "registry+https://github.com/rust-lang/crates.io-index" 351 | checksum = "32085ea23f3234fc7846555e85283ba4de91e21016dc0455a16286d87a292d64" 352 | 353 | [[package]] 354 | name = "h2" 355 | version = "0.3.26" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" 358 | dependencies = [ 359 | "bytes", 360 | "fnv", 361 | "futures-core", 362 | "futures-sink", 363 | "futures-util", 364 | "http 0.2.12", 365 | "indexmap 2.5.0", 366 | "slab", 367 | "tokio", 368 | "tokio-util", 369 | "tracing", 370 | ] 371 | 372 | [[package]] 373 | name = "h2" 374 | version = "0.4.6" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "524e8ac6999421f49a846c2d4411f337e53497d8ec55d67753beffa43c5d9205" 377 | dependencies = [ 378 | "atomic-waker", 379 | "bytes", 380 | "fnv", 381 | "futures-core", 382 | "futures-sink", 383 | "http 1.1.0", 384 | "indexmap 2.5.0", 385 | "slab", 386 | "tokio", 387 | "tokio-util", 388 | "tracing", 389 | ] 390 | 391 | [[package]] 392 | name = "hashbrown" 393 | version = "0.12.3" 394 | source = "registry+https://github.com/rust-lang/crates.io-index" 395 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 396 | 397 | [[package]] 398 | name = "hashbrown" 399 | version = "0.14.5" 400 | source = "registry+https://github.com/rust-lang/crates.io-index" 401 | checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" 402 | 403 | [[package]] 404 | name = "headers" 405 | version = "0.3.9" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "06683b93020a07e3dbcf5f8c0f6d40080d725bea7936fc01ad345c01b97dc270" 408 | dependencies = [ 409 | "base64 0.21.7", 410 | "bytes", 411 | "headers-core", 412 | "http 0.2.12", 413 | "httpdate", 414 | "mime", 415 | "sha1", 416 | ] 417 | 418 | [[package]] 419 | name = "headers-core" 420 | version = "0.2.0" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" 423 | dependencies = [ 424 | "http 0.2.12", 425 | ] 426 | 427 | [[package]] 428 | name = "heck" 429 | version = "0.4.1" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" 432 | 433 | [[package]] 434 | name = "hermit-abi" 435 | version = "0.1.19" 436 | source = "registry+https://github.com/rust-lang/crates.io-index" 437 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 438 | dependencies = [ 439 | "libc", 440 | ] 441 | 442 | [[package]] 443 | name = "hermit-abi" 444 | version = "0.3.9" 445 | source = "registry+https://github.com/rust-lang/crates.io-index" 446 | checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" 447 | 448 | [[package]] 449 | name = "hex" 450 | version = "0.4.3" 451 | source = "registry+https://github.com/rust-lang/crates.io-index" 452 | checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" 453 | 454 | [[package]] 455 | name = "http" 456 | version = "0.2.12" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" 459 | dependencies = [ 460 | "bytes", 461 | "fnv", 462 | "itoa", 463 | ] 464 | 465 | [[package]] 466 | name = "http" 467 | version = "1.1.0" 468 | source = "registry+https://github.com/rust-lang/crates.io-index" 469 | checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" 470 | dependencies = [ 471 | "bytes", 472 | "fnv", 473 | "itoa", 474 | ] 475 | 476 | [[package]] 477 | name = "http-body" 478 | version = "0.4.6" 479 | source = "registry+https://github.com/rust-lang/crates.io-index" 480 | checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" 481 | dependencies = [ 482 | "bytes", 483 | "http 0.2.12", 484 | "pin-project-lite", 485 | ] 486 | 487 | [[package]] 488 | name = "http-body" 489 | version = "1.0.1" 490 | source = "registry+https://github.com/rust-lang/crates.io-index" 491 | checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" 492 | dependencies = [ 493 | "bytes", 494 | "http 1.1.0", 495 | ] 496 | 497 | [[package]] 498 | name = "http-body-util" 499 | version = "0.1.2" 500 | source = "registry+https://github.com/rust-lang/crates.io-index" 501 | checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" 502 | dependencies = [ 503 | "bytes", 504 | "futures-util", 505 | "http 1.1.0", 506 | "http-body 1.0.1", 507 | "pin-project-lite", 508 | ] 509 | 510 | [[package]] 511 | name = "httparse" 512 | version = "1.9.4" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" 515 | 516 | [[package]] 517 | name = "httpdate" 518 | version = "1.0.3" 519 | source = "registry+https://github.com/rust-lang/crates.io-index" 520 | checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" 521 | 522 | [[package]] 523 | name = "hyper" 524 | version = "0.14.30" 525 | source = "registry+https://github.com/rust-lang/crates.io-index" 526 | checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" 527 | dependencies = [ 528 | "bytes", 529 | "futures-channel", 530 | "futures-core", 531 | "futures-util", 532 | "h2 0.3.26", 533 | "http 0.2.12", 534 | "http-body 0.4.6", 535 | "httparse", 536 | "httpdate", 537 | "itoa", 538 | "pin-project-lite", 539 | "socket2", 540 | "tokio", 541 | "tower-service", 542 | "tracing", 543 | "want", 544 | ] 545 | 546 | [[package]] 547 | name = "hyper" 548 | version = "1.4.1" 549 | source = "registry+https://github.com/rust-lang/crates.io-index" 550 | checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" 551 | dependencies = [ 552 | "bytes", 553 | "futures-channel", 554 | "futures-util", 555 | "h2 0.4.6", 556 | "http 1.1.0", 557 | "http-body 1.0.1", 558 | "httparse", 559 | "itoa", 560 | "pin-project-lite", 561 | "smallvec", 562 | "tokio", 563 | "want", 564 | ] 565 | 566 | [[package]] 567 | name = "hyper-rustls" 568 | version = "0.27.3" 569 | source = "registry+https://github.com/rust-lang/crates.io-index" 570 | checksum = "08afdbb5c31130e3034af566421053ab03787c640246a446327f550d11bcb333" 571 | dependencies = [ 572 | "futures-util", 573 | "http 1.1.0", 574 | "hyper 1.4.1", 575 | "hyper-util", 576 | "rustls", 577 | "rustls-pki-types", 578 | "tokio", 579 | "tokio-rustls", 580 | "tower-service", 581 | ] 582 | 583 | [[package]] 584 | name = "hyper-tls" 585 | version = "0.6.0" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" 588 | dependencies = [ 589 | "bytes", 590 | "http-body-util", 591 | "hyper 1.4.1", 592 | "hyper-util", 593 | "native-tls", 594 | "tokio", 595 | "tokio-native-tls", 596 | "tower-service", 597 | ] 598 | 599 | [[package]] 600 | name = "hyper-util" 601 | version = "0.1.8" 602 | source = "registry+https://github.com/rust-lang/crates.io-index" 603 | checksum = "da62f120a8a37763efb0cf8fdf264b884c7b8b9ac8660b900c8661030c00e6ba" 604 | dependencies = [ 605 | "bytes", 606 | "futures-channel", 607 | "futures-util", 608 | "http 1.1.0", 609 | "http-body 1.0.1", 610 | "hyper 1.4.1", 611 | "pin-project-lite", 612 | "socket2", 613 | "tokio", 614 | "tower", 615 | "tower-service", 616 | "tracing", 617 | ] 618 | 619 | [[package]] 620 | name = "idna" 621 | version = "0.5.0" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" 624 | dependencies = [ 625 | "unicode-bidi", 626 | "unicode-normalization", 627 | ] 628 | 629 | [[package]] 630 | name = "indexmap" 631 | version = "1.9.3" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 634 | dependencies = [ 635 | "autocfg", 636 | "hashbrown 0.12.3", 637 | ] 638 | 639 | [[package]] 640 | name = "indexmap" 641 | version = "2.5.0" 642 | source = "registry+https://github.com/rust-lang/crates.io-index" 643 | checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" 644 | dependencies = [ 645 | "equivalent", 646 | "hashbrown 0.14.5", 647 | ] 648 | 649 | [[package]] 650 | name = "ipnet" 651 | version = "2.10.0" 652 | source = "registry+https://github.com/rust-lang/crates.io-index" 653 | checksum = "187674a687eed5fe42285b40c6291f9a01517d415fad1c3cbc6a9f778af7fcd4" 654 | 655 | [[package]] 656 | name = "itoa" 657 | version = "1.0.11" 658 | source = "registry+https://github.com/rust-lang/crates.io-index" 659 | checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" 660 | 661 | [[package]] 662 | name = "js-sys" 663 | version = "0.3.70" 664 | source = "registry+https://github.com/rust-lang/crates.io-index" 665 | checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" 666 | dependencies = [ 667 | "wasm-bindgen", 668 | ] 669 | 670 | [[package]] 671 | name = "keccak" 672 | version = "0.1.5" 673 | source = "registry+https://github.com/rust-lang/crates.io-index" 674 | checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" 675 | dependencies = [ 676 | "cpufeatures", 677 | ] 678 | 679 | [[package]] 680 | name = "lazy_static" 681 | version = "1.5.0" 682 | source = "registry+https://github.com/rust-lang/crates.io-index" 683 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 684 | 685 | [[package]] 686 | name = "libc" 687 | version = "0.2.158" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" 690 | 691 | [[package]] 692 | name = "linux-raw-sys" 693 | version = "0.4.14" 694 | source = "registry+https://github.com/rust-lang/crates.io-index" 695 | checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" 696 | 697 | [[package]] 698 | name = "lock_api" 699 | version = "0.4.12" 700 | source = "registry+https://github.com/rust-lang/crates.io-index" 701 | checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" 702 | dependencies = [ 703 | "autocfg", 704 | "scopeguard", 705 | ] 706 | 707 | [[package]] 708 | name = "log" 709 | version = "0.4.22" 710 | source = "registry+https://github.com/rust-lang/crates.io-index" 711 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 712 | 713 | [[package]] 714 | name = "memchr" 715 | version = "2.7.4" 716 | source = "registry+https://github.com/rust-lang/crates.io-index" 717 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 718 | 719 | [[package]] 720 | name = "mime" 721 | version = "0.3.17" 722 | source = "registry+https://github.com/rust-lang/crates.io-index" 723 | checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" 724 | 725 | [[package]] 726 | name = "mime_guess" 727 | version = "2.0.5" 728 | source = "registry+https://github.com/rust-lang/crates.io-index" 729 | checksum = "f7c44f8e672c00fe5308fa235f821cb4198414e1c77935c1ab6948d3fd78550e" 730 | dependencies = [ 731 | "mime", 732 | "unicase", 733 | ] 734 | 735 | [[package]] 736 | name = "miniz_oxide" 737 | version = "0.8.0" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1" 740 | dependencies = [ 741 | "adler2", 742 | ] 743 | 744 | [[package]] 745 | name = "mio" 746 | version = "1.0.2" 747 | source = "registry+https://github.com/rust-lang/crates.io-index" 748 | checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" 749 | dependencies = [ 750 | "hermit-abi 0.3.9", 751 | "libc", 752 | "wasi", 753 | "windows-sys 0.52.0", 754 | ] 755 | 756 | [[package]] 757 | name = "multer" 758 | version = "2.1.0" 759 | source = "registry+https://github.com/rust-lang/crates.io-index" 760 | checksum = "01acbdc23469fd8fe07ab135923371d5f5a422fbf9c522158677c8eb15bc51c2" 761 | dependencies = [ 762 | "bytes", 763 | "encoding_rs", 764 | "futures-util", 765 | "http 0.2.12", 766 | "httparse", 767 | "log", 768 | "memchr", 769 | "mime", 770 | "spin", 771 | "version_check", 772 | ] 773 | 774 | [[package]] 775 | name = "native-tls" 776 | version = "0.2.12" 777 | source = "registry+https://github.com/rust-lang/crates.io-index" 778 | checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" 779 | dependencies = [ 780 | "libc", 781 | "log", 782 | "openssl", 783 | "openssl-probe", 784 | "openssl-sys", 785 | "schannel", 786 | "security-framework", 787 | "security-framework-sys", 788 | "tempfile", 789 | ] 790 | 791 | [[package]] 792 | name = "object" 793 | version = "0.36.4" 794 | source = "registry+https://github.com/rust-lang/crates.io-index" 795 | checksum = "084f1a5821ac4c651660a94a7153d27ac9d8a53736203f58b31945ded098070a" 796 | dependencies = [ 797 | "memchr", 798 | ] 799 | 800 | [[package]] 801 | name = "once_cell" 802 | version = "1.19.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" 805 | 806 | [[package]] 807 | name = "openssl" 808 | version = "0.10.66" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" 811 | dependencies = [ 812 | "bitflags 2.6.0", 813 | "cfg-if", 814 | "foreign-types", 815 | "libc", 816 | "once_cell", 817 | "openssl-macros", 818 | "openssl-sys", 819 | ] 820 | 821 | [[package]] 822 | name = "openssl-macros" 823 | version = "0.1.1" 824 | source = "registry+https://github.com/rust-lang/crates.io-index" 825 | checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" 826 | dependencies = [ 827 | "proc-macro2", 828 | "quote", 829 | "syn 2.0.58", 830 | ] 831 | 832 | [[package]] 833 | name = "openssl-probe" 834 | version = "0.1.5" 835 | source = "registry+https://github.com/rust-lang/crates.io-index" 836 | checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" 837 | 838 | [[package]] 839 | name = "openssl-sys" 840 | version = "0.9.103" 841 | source = "registry+https://github.com/rust-lang/crates.io-index" 842 | checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" 843 | dependencies = [ 844 | "cc", 845 | "libc", 846 | "pkg-config", 847 | "vcpkg", 848 | ] 849 | 850 | [[package]] 851 | name = "os_str_bytes" 852 | version = "6.6.1" 853 | source = "registry+https://github.com/rust-lang/crates.io-index" 854 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 855 | 856 | [[package]] 857 | name = "parking_lot" 858 | version = "0.12.3" 859 | source = "registry+https://github.com/rust-lang/crates.io-index" 860 | checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" 861 | dependencies = [ 862 | "lock_api", 863 | "parking_lot_core", 864 | ] 865 | 866 | [[package]] 867 | name = "parking_lot_core" 868 | version = "0.9.10" 869 | source = "registry+https://github.com/rust-lang/crates.io-index" 870 | checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" 871 | dependencies = [ 872 | "cfg-if", 873 | "libc", 874 | "redox_syscall", 875 | "smallvec", 876 | "windows-targets", 877 | ] 878 | 879 | [[package]] 880 | name = "percent-encoding" 881 | version = "2.3.1" 882 | source = "registry+https://github.com/rust-lang/crates.io-index" 883 | checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" 884 | 885 | [[package]] 886 | name = "pin-project" 887 | version = "1.1.5" 888 | source = "registry+https://github.com/rust-lang/crates.io-index" 889 | checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" 890 | dependencies = [ 891 | "pin-project-internal", 892 | ] 893 | 894 | [[package]] 895 | name = "pin-project-internal" 896 | version = "1.1.5" 897 | source = "registry+https://github.com/rust-lang/crates.io-index" 898 | checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" 899 | dependencies = [ 900 | "proc-macro2", 901 | "quote", 902 | "syn 2.0.58", 903 | ] 904 | 905 | [[package]] 906 | name = "pin-project-lite" 907 | version = "0.2.14" 908 | source = "registry+https://github.com/rust-lang/crates.io-index" 909 | checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" 910 | 911 | [[package]] 912 | name = "pin-utils" 913 | version = "0.1.0" 914 | source = "registry+https://github.com/rust-lang/crates.io-index" 915 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 916 | 917 | [[package]] 918 | name = "pkg-config" 919 | version = "0.3.30" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" 922 | 923 | [[package]] 924 | name = "ppv-lite86" 925 | version = "0.2.20" 926 | source = "registry+https://github.com/rust-lang/crates.io-index" 927 | checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" 928 | dependencies = [ 929 | "zerocopy", 930 | ] 931 | 932 | [[package]] 933 | name = "proc-macro-error" 934 | version = "1.0.4" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 937 | dependencies = [ 938 | "proc-macro-error-attr", 939 | "proc-macro2", 940 | "quote", 941 | "syn 1.0.109", 942 | "version_check", 943 | ] 944 | 945 | [[package]] 946 | name = "proc-macro-error-attr" 947 | version = "1.0.4" 948 | source = "registry+https://github.com/rust-lang/crates.io-index" 949 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 950 | dependencies = [ 951 | "proc-macro2", 952 | "quote", 953 | "version_check", 954 | ] 955 | 956 | [[package]] 957 | name = "proc-macro2" 958 | version = "1.0.79" 959 | source = "registry+https://github.com/rust-lang/crates.io-index" 960 | checksum = "e835ff2298f5721608eb1a980ecaee1aef2c132bf95ecc026a11b7bf3c01c02e" 961 | dependencies = [ 962 | "unicode-ident", 963 | ] 964 | 965 | [[package]] 966 | name = "quote" 967 | version = "1.0.35" 968 | source = "registry+https://github.com/rust-lang/crates.io-index" 969 | checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" 970 | dependencies = [ 971 | "proc-macro2", 972 | ] 973 | 974 | [[package]] 975 | name = "rand" 976 | version = "0.8.5" 977 | source = "registry+https://github.com/rust-lang/crates.io-index" 978 | checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" 979 | dependencies = [ 980 | "libc", 981 | "rand_chacha", 982 | "rand_core", 983 | ] 984 | 985 | [[package]] 986 | name = "rand_chacha" 987 | version = "0.3.1" 988 | source = "registry+https://github.com/rust-lang/crates.io-index" 989 | checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" 990 | dependencies = [ 991 | "ppv-lite86", 992 | "rand_core", 993 | ] 994 | 995 | [[package]] 996 | name = "rand_core" 997 | version = "0.6.4" 998 | source = "registry+https://github.com/rust-lang/crates.io-index" 999 | checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" 1000 | dependencies = [ 1001 | "getrandom", 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "redox_syscall" 1006 | version = "0.5.4" 1007 | source = "registry+https://github.com/rust-lang/crates.io-index" 1008 | checksum = "0884ad60e090bf1345b93da0a5de8923c93884cd03f40dfcfddd3b4bee661853" 1009 | dependencies = [ 1010 | "bitflags 2.6.0", 1011 | ] 1012 | 1013 | [[package]] 1014 | name = "regex" 1015 | version = "1.10.6" 1016 | source = "registry+https://github.com/rust-lang/crates.io-index" 1017 | checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" 1018 | dependencies = [ 1019 | "aho-corasick", 1020 | "memchr", 1021 | "regex-automata", 1022 | "regex-syntax", 1023 | ] 1024 | 1025 | [[package]] 1026 | name = "regex-automata" 1027 | version = "0.4.7" 1028 | source = "registry+https://github.com/rust-lang/crates.io-index" 1029 | checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" 1030 | dependencies = [ 1031 | "aho-corasick", 1032 | "memchr", 1033 | "regex-syntax", 1034 | ] 1035 | 1036 | [[package]] 1037 | name = "regex-syntax" 1038 | version = "0.8.4" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" 1041 | 1042 | [[package]] 1043 | name = "reqwest" 1044 | version = "0.12.7" 1045 | source = "registry+https://github.com/rust-lang/crates.io-index" 1046 | checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" 1047 | dependencies = [ 1048 | "base64 0.22.1", 1049 | "bytes", 1050 | "encoding_rs", 1051 | "futures-core", 1052 | "futures-util", 1053 | "h2 0.4.6", 1054 | "http 1.1.0", 1055 | "http-body 1.0.1", 1056 | "http-body-util", 1057 | "hyper 1.4.1", 1058 | "hyper-rustls", 1059 | "hyper-tls", 1060 | "hyper-util", 1061 | "ipnet", 1062 | "js-sys", 1063 | "log", 1064 | "mime", 1065 | "native-tls", 1066 | "once_cell", 1067 | "percent-encoding", 1068 | "pin-project-lite", 1069 | "rustls-pemfile", 1070 | "serde", 1071 | "serde_json", 1072 | "serde_urlencoded", 1073 | "sync_wrapper", 1074 | "system-configuration", 1075 | "tokio", 1076 | "tokio-native-tls", 1077 | "tower-service", 1078 | "url", 1079 | "wasm-bindgen", 1080 | "wasm-bindgen-futures", 1081 | "web-sys", 1082 | "windows-registry", 1083 | ] 1084 | 1085 | [[package]] 1086 | name = "ring" 1087 | version = "0.17.8" 1088 | source = "registry+https://github.com/rust-lang/crates.io-index" 1089 | checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" 1090 | dependencies = [ 1091 | "cc", 1092 | "cfg-if", 1093 | "getrandom", 1094 | "libc", 1095 | "spin", 1096 | "untrusted", 1097 | "windows-sys 0.52.0", 1098 | ] 1099 | 1100 | [[package]] 1101 | name = "rustc-demangle" 1102 | version = "0.1.24" 1103 | source = "registry+https://github.com/rust-lang/crates.io-index" 1104 | checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" 1105 | 1106 | [[package]] 1107 | name = "rustix" 1108 | version = "0.38.37" 1109 | source = "registry+https://github.com/rust-lang/crates.io-index" 1110 | checksum = "8acb788b847c24f28525660c4d7758620a7210875711f79e7f663cc152726811" 1111 | dependencies = [ 1112 | "bitflags 2.6.0", 1113 | "errno", 1114 | "libc", 1115 | "linux-raw-sys", 1116 | "windows-sys 0.52.0", 1117 | ] 1118 | 1119 | [[package]] 1120 | name = "rustls" 1121 | version = "0.23.13" 1122 | source = "registry+https://github.com/rust-lang/crates.io-index" 1123 | checksum = "f2dabaac7466917e566adb06783a81ca48944c6898a1b08b9374106dd671f4c8" 1124 | dependencies = [ 1125 | "once_cell", 1126 | "rustls-pki-types", 1127 | "rustls-webpki", 1128 | "subtle", 1129 | "zeroize", 1130 | ] 1131 | 1132 | [[package]] 1133 | name = "rustls-pemfile" 1134 | version = "2.1.3" 1135 | source = "registry+https://github.com/rust-lang/crates.io-index" 1136 | checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" 1137 | dependencies = [ 1138 | "base64 0.22.1", 1139 | "rustls-pki-types", 1140 | ] 1141 | 1142 | [[package]] 1143 | name = "rustls-pki-types" 1144 | version = "1.8.0" 1145 | source = "registry+https://github.com/rust-lang/crates.io-index" 1146 | checksum = "fc0a2ce646f8655401bb81e7927b812614bd5d91dbc968696be50603510fcaf0" 1147 | 1148 | [[package]] 1149 | name = "rustls-webpki" 1150 | version = "0.102.8" 1151 | source = "registry+https://github.com/rust-lang/crates.io-index" 1152 | checksum = "64ca1bc8749bd4cf37b5ce386cc146580777b4e8572c7b97baf22c83f444bee9" 1153 | dependencies = [ 1154 | "ring", 1155 | "rustls-pki-types", 1156 | "untrusted", 1157 | ] 1158 | 1159 | [[package]] 1160 | name = "ryu" 1161 | version = "1.0.18" 1162 | source = "registry+https://github.com/rust-lang/crates.io-index" 1163 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 1164 | 1165 | [[package]] 1166 | name = "schannel" 1167 | version = "0.1.24" 1168 | source = "registry+https://github.com/rust-lang/crates.io-index" 1169 | checksum = "e9aaafd5a2b6e3d657ff009d82fbd630b6bd54dd4eb06f21693925cdf80f9b8b" 1170 | dependencies = [ 1171 | "windows-sys 0.59.0", 1172 | ] 1173 | 1174 | [[package]] 1175 | name = "scoped-tls" 1176 | version = "1.0.1" 1177 | source = "registry+https://github.com/rust-lang/crates.io-index" 1178 | checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" 1179 | 1180 | [[package]] 1181 | name = "scopeguard" 1182 | version = "1.2.0" 1183 | source = "registry+https://github.com/rust-lang/crates.io-index" 1184 | checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" 1185 | 1186 | [[package]] 1187 | name = "secp256k1" 1188 | version = "0.24.3" 1189 | source = "registry+https://github.com/rust-lang/crates.io-index" 1190 | checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" 1191 | dependencies = [ 1192 | "rand", 1193 | "secp256k1-sys", 1194 | ] 1195 | 1196 | [[package]] 1197 | name = "secp256k1-sys" 1198 | version = "0.6.1" 1199 | source = "registry+https://github.com/rust-lang/crates.io-index" 1200 | checksum = "83080e2c2fc1006e625be82e5d1eb6a43b7fd9578b617fcc55814daf286bba4b" 1201 | dependencies = [ 1202 | "cc", 1203 | ] 1204 | 1205 | [[package]] 1206 | name = "security-framework" 1207 | version = "2.11.1" 1208 | source = "registry+https://github.com/rust-lang/crates.io-index" 1209 | checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" 1210 | dependencies = [ 1211 | "bitflags 2.6.0", 1212 | "core-foundation", 1213 | "core-foundation-sys", 1214 | "libc", 1215 | "security-framework-sys", 1216 | ] 1217 | 1218 | [[package]] 1219 | name = "security-framework-sys" 1220 | version = "2.11.1" 1221 | source = "registry+https://github.com/rust-lang/crates.io-index" 1222 | checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" 1223 | dependencies = [ 1224 | "core-foundation-sys", 1225 | "libc", 1226 | ] 1227 | 1228 | [[package]] 1229 | name = "serde" 1230 | version = "1.0.210" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" 1233 | dependencies = [ 1234 | "serde_derive", 1235 | ] 1236 | 1237 | [[package]] 1238 | name = "serde_canonical_json" 1239 | version = "1.0.0" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "3ef94ee2661f3ce924fa936258393d02155fa22c5a81125016a24069e23a0465" 1242 | dependencies = [ 1243 | "itoa", 1244 | "lazy_static", 1245 | "regex", 1246 | "serde_json", 1247 | ] 1248 | 1249 | [[package]] 1250 | name = "serde_derive" 1251 | version = "1.0.210" 1252 | source = "registry+https://github.com/rust-lang/crates.io-index" 1253 | checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" 1254 | dependencies = [ 1255 | "proc-macro2", 1256 | "quote", 1257 | "syn 2.0.58", 1258 | ] 1259 | 1260 | [[package]] 1261 | name = "serde_json" 1262 | version = "1.0.128" 1263 | source = "registry+https://github.com/rust-lang/crates.io-index" 1264 | checksum = "6ff5456707a1de34e7e37f2a6fd3d3f808c318259cbd01ab6377795054b483d8" 1265 | dependencies = [ 1266 | "itoa", 1267 | "memchr", 1268 | "ryu", 1269 | "serde", 1270 | ] 1271 | 1272 | [[package]] 1273 | name = "serde_urlencoded" 1274 | version = "0.7.1" 1275 | source = "registry+https://github.com/rust-lang/crates.io-index" 1276 | checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" 1277 | dependencies = [ 1278 | "form_urlencoded", 1279 | "itoa", 1280 | "ryu", 1281 | "serde", 1282 | ] 1283 | 1284 | [[package]] 1285 | name = "sha1" 1286 | version = "0.10.6" 1287 | source = "registry+https://github.com/rust-lang/crates.io-index" 1288 | checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" 1289 | dependencies = [ 1290 | "cfg-if", 1291 | "cpufeatures", 1292 | "digest", 1293 | ] 1294 | 1295 | [[package]] 1296 | name = "sha3" 1297 | version = "0.10.8" 1298 | source = "registry+https://github.com/rust-lang/crates.io-index" 1299 | checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" 1300 | dependencies = [ 1301 | "digest", 1302 | "keccak", 1303 | ] 1304 | 1305 | [[package]] 1306 | name = "shlex" 1307 | version = "1.3.0" 1308 | source = "registry+https://github.com/rust-lang/crates.io-index" 1309 | checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" 1310 | 1311 | [[package]] 1312 | name = "signal-hook-registry" 1313 | version = "1.4.2" 1314 | source = "registry+https://github.com/rust-lang/crates.io-index" 1315 | checksum = "a9e9e0b4211b72e7b8b6e85c807d36c212bdb33ea8587f7569562a84df5465b1" 1316 | dependencies = [ 1317 | "libc", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "slab" 1322 | version = "0.4.9" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" 1325 | dependencies = [ 1326 | "autocfg", 1327 | ] 1328 | 1329 | [[package]] 1330 | name = "smallvec" 1331 | version = "1.13.2" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" 1334 | 1335 | [[package]] 1336 | name = "socket2" 1337 | version = "0.5.7" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" 1340 | dependencies = [ 1341 | "libc", 1342 | "windows-sys 0.52.0", 1343 | ] 1344 | 1345 | [[package]] 1346 | name = "spin" 1347 | version = "0.9.8" 1348 | source = "registry+https://github.com/rust-lang/crates.io-index" 1349 | checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" 1350 | 1351 | [[package]] 1352 | name = "strsim" 1353 | version = "0.10.0" 1354 | source = "registry+https://github.com/rust-lang/crates.io-index" 1355 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1356 | 1357 | [[package]] 1358 | name = "subtle" 1359 | version = "2.6.1" 1360 | source = "registry+https://github.com/rust-lang/crates.io-index" 1361 | checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" 1362 | 1363 | [[package]] 1364 | name = "syn" 1365 | version = "1.0.109" 1366 | source = "registry+https://github.com/rust-lang/crates.io-index" 1367 | checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" 1368 | dependencies = [ 1369 | "proc-macro2", 1370 | "quote", 1371 | "unicode-ident", 1372 | ] 1373 | 1374 | [[package]] 1375 | name = "syn" 1376 | version = "2.0.58" 1377 | source = "registry+https://github.com/rust-lang/crates.io-index" 1378 | checksum = "44cfb93f38070beee36b3fef7d4f5a16f27751d94b187b666a5cc5e9b0d30687" 1379 | dependencies = [ 1380 | "proc-macro2", 1381 | "quote", 1382 | "unicode-ident", 1383 | ] 1384 | 1385 | [[package]] 1386 | name = "sync_wrapper" 1387 | version = "1.0.1" 1388 | source = "registry+https://github.com/rust-lang/crates.io-index" 1389 | checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" 1390 | dependencies = [ 1391 | "futures-core", 1392 | ] 1393 | 1394 | [[package]] 1395 | name = "system-configuration" 1396 | version = "0.6.1" 1397 | source = "registry+https://github.com/rust-lang/crates.io-index" 1398 | checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" 1399 | dependencies = [ 1400 | "bitflags 2.6.0", 1401 | "core-foundation", 1402 | "system-configuration-sys", 1403 | ] 1404 | 1405 | [[package]] 1406 | name = "system-configuration-sys" 1407 | version = "0.6.0" 1408 | source = "registry+https://github.com/rust-lang/crates.io-index" 1409 | checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" 1410 | dependencies = [ 1411 | "core-foundation-sys", 1412 | "libc", 1413 | ] 1414 | 1415 | [[package]] 1416 | name = "tempfile" 1417 | version = "3.12.0" 1418 | source = "registry+https://github.com/rust-lang/crates.io-index" 1419 | checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" 1420 | dependencies = [ 1421 | "cfg-if", 1422 | "fastrand", 1423 | "once_cell", 1424 | "rustix", 1425 | "windows-sys 0.59.0", 1426 | ] 1427 | 1428 | [[package]] 1429 | name = "tendermint" 1430 | version = "0.1.0" 1431 | dependencies = [ 1432 | "clap", 1433 | "hex", 1434 | "rand", 1435 | "reqwest", 1436 | "secp256k1", 1437 | "serde", 1438 | "serde_canonical_json", 1439 | "serde_json", 1440 | "sha3", 1441 | "tokio", 1442 | "tokio-stream", 1443 | "warp", 1444 | ] 1445 | 1446 | [[package]] 1447 | name = "termcolor" 1448 | version = "1.4.1" 1449 | source = "registry+https://github.com/rust-lang/crates.io-index" 1450 | checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" 1451 | dependencies = [ 1452 | "winapi-util", 1453 | ] 1454 | 1455 | [[package]] 1456 | name = "textwrap" 1457 | version = "0.16.1" 1458 | source = "registry+https://github.com/rust-lang/crates.io-index" 1459 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 1460 | 1461 | [[package]] 1462 | name = "thiserror" 1463 | version = "1.0.63" 1464 | source = "registry+https://github.com/rust-lang/crates.io-index" 1465 | checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" 1466 | dependencies = [ 1467 | "thiserror-impl", 1468 | ] 1469 | 1470 | [[package]] 1471 | name = "thiserror-impl" 1472 | version = "1.0.63" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" 1475 | dependencies = [ 1476 | "proc-macro2", 1477 | "quote", 1478 | "syn 2.0.58", 1479 | ] 1480 | 1481 | [[package]] 1482 | name = "tinyvec" 1483 | version = "1.8.0" 1484 | source = "registry+https://github.com/rust-lang/crates.io-index" 1485 | checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 1486 | dependencies = [ 1487 | "tinyvec_macros", 1488 | ] 1489 | 1490 | [[package]] 1491 | name = "tinyvec_macros" 1492 | version = "0.1.1" 1493 | source = "registry+https://github.com/rust-lang/crates.io-index" 1494 | checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" 1495 | 1496 | [[package]] 1497 | name = "tokio" 1498 | version = "1.40.0" 1499 | source = "registry+https://github.com/rust-lang/crates.io-index" 1500 | checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" 1501 | dependencies = [ 1502 | "backtrace", 1503 | "bytes", 1504 | "libc", 1505 | "mio", 1506 | "parking_lot", 1507 | "pin-project-lite", 1508 | "signal-hook-registry", 1509 | "socket2", 1510 | "tokio-macros", 1511 | "windows-sys 0.52.0", 1512 | ] 1513 | 1514 | [[package]] 1515 | name = "tokio-macros" 1516 | version = "2.4.0" 1517 | source = "registry+https://github.com/rust-lang/crates.io-index" 1518 | checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" 1519 | dependencies = [ 1520 | "proc-macro2", 1521 | "quote", 1522 | "syn 2.0.58", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "tokio-native-tls" 1527 | version = "0.3.1" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" 1530 | dependencies = [ 1531 | "native-tls", 1532 | "tokio", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "tokio-rustls" 1537 | version = "0.26.0" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" 1540 | dependencies = [ 1541 | "rustls", 1542 | "rustls-pki-types", 1543 | "tokio", 1544 | ] 1545 | 1546 | [[package]] 1547 | name = "tokio-stream" 1548 | version = "0.1.16" 1549 | source = "registry+https://github.com/rust-lang/crates.io-index" 1550 | checksum = "4f4e6ce100d0eb49a2734f8c0812bcd324cf357d21810932c5df6b96ef2b86f1" 1551 | dependencies = [ 1552 | "futures-core", 1553 | "pin-project-lite", 1554 | "tokio", 1555 | "tokio-util", 1556 | ] 1557 | 1558 | [[package]] 1559 | name = "tokio-tungstenite" 1560 | version = "0.21.0" 1561 | source = "registry+https://github.com/rust-lang/crates.io-index" 1562 | checksum = "c83b561d025642014097b66e6c1bb422783339e0909e4429cde4749d1990bc38" 1563 | dependencies = [ 1564 | "futures-util", 1565 | "log", 1566 | "tokio", 1567 | "tungstenite", 1568 | ] 1569 | 1570 | [[package]] 1571 | name = "tokio-util" 1572 | version = "0.7.12" 1573 | source = "registry+https://github.com/rust-lang/crates.io-index" 1574 | checksum = "61e7c3654c13bcd040d4a03abee2c75b1d14a37b423cf5a813ceae1cc903ec6a" 1575 | dependencies = [ 1576 | "bytes", 1577 | "futures-core", 1578 | "futures-sink", 1579 | "pin-project-lite", 1580 | "tokio", 1581 | ] 1582 | 1583 | [[package]] 1584 | name = "tower" 1585 | version = "0.4.13" 1586 | source = "registry+https://github.com/rust-lang/crates.io-index" 1587 | checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" 1588 | dependencies = [ 1589 | "futures-core", 1590 | "futures-util", 1591 | "pin-project", 1592 | "pin-project-lite", 1593 | "tokio", 1594 | "tower-layer", 1595 | "tower-service", 1596 | ] 1597 | 1598 | [[package]] 1599 | name = "tower-layer" 1600 | version = "0.3.3" 1601 | source = "registry+https://github.com/rust-lang/crates.io-index" 1602 | checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" 1603 | 1604 | [[package]] 1605 | name = "tower-service" 1606 | version = "0.3.3" 1607 | source = "registry+https://github.com/rust-lang/crates.io-index" 1608 | checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" 1609 | 1610 | [[package]] 1611 | name = "tracing" 1612 | version = "0.1.40" 1613 | source = "registry+https://github.com/rust-lang/crates.io-index" 1614 | checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" 1615 | dependencies = [ 1616 | "log", 1617 | "pin-project-lite", 1618 | "tracing-core", 1619 | ] 1620 | 1621 | [[package]] 1622 | name = "tracing-core" 1623 | version = "0.1.32" 1624 | source = "registry+https://github.com/rust-lang/crates.io-index" 1625 | checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" 1626 | dependencies = [ 1627 | "once_cell", 1628 | ] 1629 | 1630 | [[package]] 1631 | name = "try-lock" 1632 | version = "0.2.5" 1633 | source = "registry+https://github.com/rust-lang/crates.io-index" 1634 | checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" 1635 | 1636 | [[package]] 1637 | name = "tungstenite" 1638 | version = "0.21.0" 1639 | source = "registry+https://github.com/rust-lang/crates.io-index" 1640 | checksum = "9ef1a641ea34f399a848dea702823bbecfb4c486f911735368f1f137cb8257e1" 1641 | dependencies = [ 1642 | "byteorder", 1643 | "bytes", 1644 | "data-encoding", 1645 | "http 1.1.0", 1646 | "httparse", 1647 | "log", 1648 | "rand", 1649 | "sha1", 1650 | "thiserror", 1651 | "url", 1652 | "utf-8", 1653 | ] 1654 | 1655 | [[package]] 1656 | name = "typenum" 1657 | version = "1.17.0" 1658 | source = "registry+https://github.com/rust-lang/crates.io-index" 1659 | checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" 1660 | 1661 | [[package]] 1662 | name = "unicase" 1663 | version = "2.7.0" 1664 | source = "registry+https://github.com/rust-lang/crates.io-index" 1665 | checksum = "f7d2d4dafb69621809a81864c9c1b864479e1235c0dd4e199924b9742439ed89" 1666 | dependencies = [ 1667 | "version_check", 1668 | ] 1669 | 1670 | [[package]] 1671 | name = "unicode-bidi" 1672 | version = "0.3.15" 1673 | source = "registry+https://github.com/rust-lang/crates.io-index" 1674 | checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" 1675 | 1676 | [[package]] 1677 | name = "unicode-ident" 1678 | version = "1.0.12" 1679 | source = "registry+https://github.com/rust-lang/crates.io-index" 1680 | checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" 1681 | 1682 | [[package]] 1683 | name = "unicode-normalization" 1684 | version = "0.1.23" 1685 | source = "registry+https://github.com/rust-lang/crates.io-index" 1686 | checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" 1687 | dependencies = [ 1688 | "tinyvec", 1689 | ] 1690 | 1691 | [[package]] 1692 | name = "untrusted" 1693 | version = "0.9.0" 1694 | source = "registry+https://github.com/rust-lang/crates.io-index" 1695 | checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" 1696 | 1697 | [[package]] 1698 | name = "url" 1699 | version = "2.5.2" 1700 | source = "registry+https://github.com/rust-lang/crates.io-index" 1701 | checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" 1702 | dependencies = [ 1703 | "form_urlencoded", 1704 | "idna", 1705 | "percent-encoding", 1706 | ] 1707 | 1708 | [[package]] 1709 | name = "utf-8" 1710 | version = "0.7.6" 1711 | source = "registry+https://github.com/rust-lang/crates.io-index" 1712 | checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" 1713 | 1714 | [[package]] 1715 | name = "vcpkg" 1716 | version = "0.2.15" 1717 | source = "registry+https://github.com/rust-lang/crates.io-index" 1718 | checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" 1719 | 1720 | [[package]] 1721 | name = "version_check" 1722 | version = "0.9.5" 1723 | source = "registry+https://github.com/rust-lang/crates.io-index" 1724 | checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" 1725 | 1726 | [[package]] 1727 | name = "want" 1728 | version = "0.3.1" 1729 | source = "registry+https://github.com/rust-lang/crates.io-index" 1730 | checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" 1731 | dependencies = [ 1732 | "try-lock", 1733 | ] 1734 | 1735 | [[package]] 1736 | name = "warp" 1737 | version = "0.3.7" 1738 | source = "registry+https://github.com/rust-lang/crates.io-index" 1739 | checksum = "4378d202ff965b011c64817db11d5829506d3404edeadb61f190d111da3f231c" 1740 | dependencies = [ 1741 | "bytes", 1742 | "futures-channel", 1743 | "futures-util", 1744 | "headers", 1745 | "http 0.2.12", 1746 | "hyper 0.14.30", 1747 | "log", 1748 | "mime", 1749 | "mime_guess", 1750 | "multer", 1751 | "percent-encoding", 1752 | "pin-project", 1753 | "scoped-tls", 1754 | "serde", 1755 | "serde_json", 1756 | "serde_urlencoded", 1757 | "tokio", 1758 | "tokio-tungstenite", 1759 | "tokio-util", 1760 | "tower-service", 1761 | "tracing", 1762 | ] 1763 | 1764 | [[package]] 1765 | name = "wasi" 1766 | version = "0.11.0+wasi-snapshot-preview1" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1769 | 1770 | [[package]] 1771 | name = "wasm-bindgen" 1772 | version = "0.2.93" 1773 | source = "registry+https://github.com/rust-lang/crates.io-index" 1774 | checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" 1775 | dependencies = [ 1776 | "cfg-if", 1777 | "once_cell", 1778 | "wasm-bindgen-macro", 1779 | ] 1780 | 1781 | [[package]] 1782 | name = "wasm-bindgen-backend" 1783 | version = "0.2.93" 1784 | source = "registry+https://github.com/rust-lang/crates.io-index" 1785 | checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" 1786 | dependencies = [ 1787 | "bumpalo", 1788 | "log", 1789 | "once_cell", 1790 | "proc-macro2", 1791 | "quote", 1792 | "syn 2.0.58", 1793 | "wasm-bindgen-shared", 1794 | ] 1795 | 1796 | [[package]] 1797 | name = "wasm-bindgen-futures" 1798 | version = "0.4.43" 1799 | source = "registry+https://github.com/rust-lang/crates.io-index" 1800 | checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" 1801 | dependencies = [ 1802 | "cfg-if", 1803 | "js-sys", 1804 | "wasm-bindgen", 1805 | "web-sys", 1806 | ] 1807 | 1808 | [[package]] 1809 | name = "wasm-bindgen-macro" 1810 | version = "0.2.93" 1811 | source = "registry+https://github.com/rust-lang/crates.io-index" 1812 | checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" 1813 | dependencies = [ 1814 | "quote", 1815 | "wasm-bindgen-macro-support", 1816 | ] 1817 | 1818 | [[package]] 1819 | name = "wasm-bindgen-macro-support" 1820 | version = "0.2.93" 1821 | source = "registry+https://github.com/rust-lang/crates.io-index" 1822 | checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" 1823 | dependencies = [ 1824 | "proc-macro2", 1825 | "quote", 1826 | "syn 2.0.58", 1827 | "wasm-bindgen-backend", 1828 | "wasm-bindgen-shared", 1829 | ] 1830 | 1831 | [[package]] 1832 | name = "wasm-bindgen-shared" 1833 | version = "0.2.93" 1834 | source = "registry+https://github.com/rust-lang/crates.io-index" 1835 | checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" 1836 | 1837 | [[package]] 1838 | name = "web-sys" 1839 | version = "0.3.70" 1840 | source = "registry+https://github.com/rust-lang/crates.io-index" 1841 | checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" 1842 | dependencies = [ 1843 | "js-sys", 1844 | "wasm-bindgen", 1845 | ] 1846 | 1847 | [[package]] 1848 | name = "winapi" 1849 | version = "0.3.9" 1850 | source = "registry+https://github.com/rust-lang/crates.io-index" 1851 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1852 | dependencies = [ 1853 | "winapi-i686-pc-windows-gnu", 1854 | "winapi-x86_64-pc-windows-gnu", 1855 | ] 1856 | 1857 | [[package]] 1858 | name = "winapi-i686-pc-windows-gnu" 1859 | version = "0.4.0" 1860 | source = "registry+https://github.com/rust-lang/crates.io-index" 1861 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1862 | 1863 | [[package]] 1864 | name = "winapi-util" 1865 | version = "0.1.9" 1866 | source = "registry+https://github.com/rust-lang/crates.io-index" 1867 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 1868 | dependencies = [ 1869 | "windows-sys 0.59.0", 1870 | ] 1871 | 1872 | [[package]] 1873 | name = "winapi-x86_64-pc-windows-gnu" 1874 | version = "0.4.0" 1875 | source = "registry+https://github.com/rust-lang/crates.io-index" 1876 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1877 | 1878 | [[package]] 1879 | name = "windows-registry" 1880 | version = "0.2.0" 1881 | source = "registry+https://github.com/rust-lang/crates.io-index" 1882 | checksum = "e400001bb720a623c1c69032f8e3e4cf09984deec740f007dd2b03ec864804b0" 1883 | dependencies = [ 1884 | "windows-result", 1885 | "windows-strings", 1886 | "windows-targets", 1887 | ] 1888 | 1889 | [[package]] 1890 | name = "windows-result" 1891 | version = "0.2.0" 1892 | source = "registry+https://github.com/rust-lang/crates.io-index" 1893 | checksum = "1d1043d8214f791817bab27572aaa8af63732e11bf84aa21a45a78d6c317ae0e" 1894 | dependencies = [ 1895 | "windows-targets", 1896 | ] 1897 | 1898 | [[package]] 1899 | name = "windows-strings" 1900 | version = "0.1.0" 1901 | source = "registry+https://github.com/rust-lang/crates.io-index" 1902 | checksum = "4cd9b125c486025df0eabcb585e62173c6c9eddcec5d117d3b6e8c30e2ee4d10" 1903 | dependencies = [ 1904 | "windows-result", 1905 | "windows-targets", 1906 | ] 1907 | 1908 | [[package]] 1909 | name = "windows-sys" 1910 | version = "0.52.0" 1911 | source = "registry+https://github.com/rust-lang/crates.io-index" 1912 | checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" 1913 | dependencies = [ 1914 | "windows-targets", 1915 | ] 1916 | 1917 | [[package]] 1918 | name = "windows-sys" 1919 | version = "0.59.0" 1920 | source = "registry+https://github.com/rust-lang/crates.io-index" 1921 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 1922 | dependencies = [ 1923 | "windows-targets", 1924 | ] 1925 | 1926 | [[package]] 1927 | name = "windows-targets" 1928 | version = "0.52.6" 1929 | source = "registry+https://github.com/rust-lang/crates.io-index" 1930 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 1931 | dependencies = [ 1932 | "windows_aarch64_gnullvm", 1933 | "windows_aarch64_msvc", 1934 | "windows_i686_gnu", 1935 | "windows_i686_gnullvm", 1936 | "windows_i686_msvc", 1937 | "windows_x86_64_gnu", 1938 | "windows_x86_64_gnullvm", 1939 | "windows_x86_64_msvc", 1940 | ] 1941 | 1942 | [[package]] 1943 | name = "windows_aarch64_gnullvm" 1944 | version = "0.52.6" 1945 | source = "registry+https://github.com/rust-lang/crates.io-index" 1946 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 1947 | 1948 | [[package]] 1949 | name = "windows_aarch64_msvc" 1950 | version = "0.52.6" 1951 | source = "registry+https://github.com/rust-lang/crates.io-index" 1952 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 1953 | 1954 | [[package]] 1955 | name = "windows_i686_gnu" 1956 | version = "0.52.6" 1957 | source = "registry+https://github.com/rust-lang/crates.io-index" 1958 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 1959 | 1960 | [[package]] 1961 | name = "windows_i686_gnullvm" 1962 | version = "0.52.6" 1963 | source = "registry+https://github.com/rust-lang/crates.io-index" 1964 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 1965 | 1966 | [[package]] 1967 | name = "windows_i686_msvc" 1968 | version = "0.52.6" 1969 | source = "registry+https://github.com/rust-lang/crates.io-index" 1970 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 1971 | 1972 | [[package]] 1973 | name = "windows_x86_64_gnu" 1974 | version = "0.52.6" 1975 | source = "registry+https://github.com/rust-lang/crates.io-index" 1976 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 1977 | 1978 | [[package]] 1979 | name = "windows_x86_64_gnullvm" 1980 | version = "0.52.6" 1981 | source = "registry+https://github.com/rust-lang/crates.io-index" 1982 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 1983 | 1984 | [[package]] 1985 | name = "windows_x86_64_msvc" 1986 | version = "0.52.6" 1987 | source = "registry+https://github.com/rust-lang/crates.io-index" 1988 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 1989 | 1990 | [[package]] 1991 | name = "zerocopy" 1992 | version = "0.7.35" 1993 | source = "registry+https://github.com/rust-lang/crates.io-index" 1994 | checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" 1995 | dependencies = [ 1996 | "byteorder", 1997 | "zerocopy-derive", 1998 | ] 1999 | 2000 | [[package]] 2001 | name = "zerocopy-derive" 2002 | version = "0.7.35" 2003 | source = "registry+https://github.com/rust-lang/crates.io-index" 2004 | checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" 2005 | dependencies = [ 2006 | "proc-macro2", 2007 | "quote", 2008 | "syn 2.0.58", 2009 | ] 2010 | 2011 | [[package]] 2012 | name = "zeroize" 2013 | version = "1.8.1" 2014 | source = "registry+https://github.com/rust-lang/crates.io-index" 2015 | checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" 2016 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "tendermint" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | name = "tendermint" 10 | path = "src/lib.rs" 11 | 12 | [[bin]] 13 | name = "tendermint" 14 | path = "src/cli/tendermint.rs" 15 | doc = false 16 | 17 | [dependencies] 18 | tokio = { version = "1", features = ["full", "sync"] } 19 | 20 | secp256k1 = { version = "0.24.0", features = ["rand"] } 21 | rand = "0.8.5" 22 | sha3 = "0.10.0" 23 | tokio-stream = { version = "0.1", features = ["sync"] } 24 | 25 | # The core APIs, including the Serialize and Deserialize traits. Always 26 | # required when using Serde. The "derive" feature is only required when 27 | # using #[derive(Serialize, Deserialize)] to make Serde work with structs 28 | # and enums defined in your crate. 29 | serde = { version = "1.0", features = ["derive"] } 30 | 31 | # Each data format lives in its own crate; the sample code below uses JSON 32 | # but you may be using a different one. 33 | serde_json = "1.0" 34 | serde_canonical_json = "1.0.0" 35 | warp = "0.3.7" 36 | reqwest = { version = "0.12.7", features = ["json"] } 37 | hex = "0.4.3" 38 | clap = { version = "3.1.18", features = ["derive"] } 39 | 40 | -------------------------------------------------------------------------------- /DEMO.md: -------------------------------------------------------------------------------- 1 | Demo 2 | ==== 3 | 4 | The point of the demo is to demonstrate how Tendermint consensus works in the face of failures. 5 | 6 | Ideally we will have a network of ~5 validators. So long as we receive 2f+1 votes then we are good. 7 | 8 | A consensus system agrees on a single value ([single shot consensus](https://decentralizedthoughts.github.io/2022-11-19-from-single-shot-to-smr/)). In order to make this demo simple, we're going to agree to the current system time. This is a value that nodes can get from their system clock, and doesn't require any gossipping unlike transactions in a blockchain (replicated state machine) model. 9 | 10 | A network begins at a particular genesis timestamp, and progresses at an irregular interval depending on the connectivity of the nodes. When a node disconnects (say it shuts down) and then reconnects to other nodes, it needs to figure out the current proposer rotation. Why? The current proposer is determined by the current round. Nodes can get in sync with each other node by simply listening for messages. 11 | 12 | ## Build. 13 | 14 | ```sh 15 | cargo build --release 16 | ``` 17 | 18 | ## Setup network. 19 | 20 | ```sh 21 | cargo run network > genesis-config.json 22 | cargo run account --new > account.json 23 | cargo run node --config genesis-config.json --account account.json 24 | ``` 25 | 26 | ## Install. 27 | 28 | ```sh 29 | cargo run accounts --new 30 | cargo run node --prvkey XXX --peers 31 | ``` 32 | 33 | 34 | -------------------------------------------------------------------------------- /PLAN.md: -------------------------------------------------------------------------------- 1 | # Implementation plan. 2 | 3 | Features: 4 | 5 | - [x] `run_round` runs a single round. 6 | - [x] encapsulate round state, so we can pass it into `run_round` and repeat. 7 | - [ ] test `run_round` independently. 8 | - [x] add http node api's 9 | - [x] add loop which runs rounds. 10 | - [x] hooks/extensions/events api: 11 | - [x] get value 12 | - [x] on new decision 13 | - [x] add pubkey identities for nodes. add signatures to node messages. 14 | - [ ] fix consensus height + stuff. commit data to log on disk. 15 | - [ ] implement dynamic timeouts to allow network to resolve with backoff. 16 | - [x] change node to start up on a network interface and listen to messages. 17 | - [ ] add node sync so it restarts and gets history from other nodes for height before it. 18 | - [ ] check precommits/prevotes are unique. 19 | - sync: rewrite algo so that time is abstracted away, and we can simulate old consensus rounds. 20 | 21 | Demo network: 22 | 23 | - [x] startup node. 24 | - [x] try listen for other nodes. 25 | - [x] peer configs, network configs. 26 | - [ ] peer handshake - dial and allow peers to add each other to the routing table. connect with "senders" channel. 27 | - [ ] connect to server listing all validators and their ip's. 28 | - [ ] choose get value function - idk probably most recently 29 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tendermint-rs 2 | 3 | A minimal reimplementation of the Tendermint BFT consensus protocol in Rust. 4 | 5 | Dependencies: 6 | 7 | * tokio - for async runtime. 8 | * secp256k1 - for cryptographic identities. 9 | * serde - for message serialisation. 10 | * warp/reqwest - for HTTP server/clients (for node RPC). 11 | * hex. 12 | 13 | ## Conceptual overview. 14 | 15 | A consensus protocol consists of a set of processes, which communicate by sending messages to each other in order to agree on a value. Processes may crash, run at arbitrary speeds, and display byzantine failures. The challenge of consensus is building a protocol which can finalise and does so safely and consistently given these assumptions. 16 | 17 | The basic Tendermint algorithm is implemented as `Process`. Each `Process` communicates via abstract channels - there is an implementation using just local communication (`examples/standalone-channels`), and an implementation using RPC over HTTP servers (`examples/standalone-http`). Processes emit consensus events via tokio async streams - consumers can subscribe to the process and receive callbacks for new values agreed on by the network (called "decisions"). Each node has an ECDSA keypair it uses to sign messages. 18 | 19 | 20 | ## Status. 21 | 22 | Protocol runs and achieves consensus, with rounds, epochs. 23 | 24 | See [PLAN](./PLAN.md) for the backlog. 25 | 26 | 27 | ## Usage. 28 | 29 | ### Running tests. 30 | 31 | ```sh 32 | cargo build 33 | 34 | cargo run --example standalone-channels 35 | cargo run --example standalone-http 36 | ``` 37 | 38 | ### Using it. 39 | 40 | Rust Tendermint can be used to build a consistent and partition-tolerant network, with a custom value that is agreed per epoch, and event streams which allow you to consume different events (such agreement - referred to as a decision, and intermediate stages). 41 | 42 | ```rs 43 | use tendermint::Process; 44 | 45 | fn example() { 46 | // Setup networking substrate between nodes. 47 | let node = Process::new(); 48 | 49 | // Pass a get_value callback to get the next candidate value for proposal. 50 | // Subscribe and listen to Decision events to process them. 51 | } 52 | ``` 53 | 54 | See `examples/`. 55 | 56 | 57 | ## Readings. 58 | 59 | - [The latest gossip on BFT consensus.](https://arxiv.org/abs/1807.04938) 60 | - [Tendermint: Byzantine Fault Tolerance in the Age of Blockchains.](https://atrium.lib.uoguelph.ca/server/api/core/bitstreams/0816af2c-5fd4-4d99-86d6-ced4eef2fb52/content) 61 | 62 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | tonic_build::configure() 3 | .build_server(true) 4 | .build_client(true) 5 | .out_dir("src/") 6 | .compile(&["proto/protos.proto"], &["proto"])?; 7 | Ok(()) 8 | } 9 | -------------------------------------------------------------------------------- /docs/INTRO.md: -------------------------------------------------------------------------------- 1 | 2 | In this article I'm going to explain my recent efforts in reimplementing Tendermint from scratch. 3 | 4 | ## Overview. 5 | 6 | Tendermint is a byzantine fault tolerant consensus protocol. 7 | 8 | The network is 3F+1 nodes, with the allowance for F byzantine failures. In the base case of F=1, the network must have 4 nodes. 9 | 10 | The network decides on a single value via the consensus algorithm, at an interval bounded by a timeout. 11 | 12 | The network communication cost is $O(N^2)$, as each node in the network must gossip to every other node. 13 | 14 | The core of Tendermint's algorithm: 15 | 16 | * Tendermint is used to build blockchains. 17 | * The consensus protocol produces decisions - each successful agreement on a value among the nodes is called a decision. 18 | * Each decision happens within an epoch. 19 | * During an epoch, the network attempts to decide on a value. 20 | * An epoch consists of an unlimited number of rounds. 21 | * A round consists of 3 phases: propose, prevote, precommit. 22 | * **Propose**. A single proposer is chosen deterministically from the validator set. The proposer sends a proposed value to decide on to the rest of the network. The rest of nodes wait a predefined timeout for this proposal. 23 | * **Prevote**. In the prevote phase, nodes try to agree on which value was proposed, and in the next phase they agree on whether to commit it. Every node sends votes to every other node, stating the value they believe is proposed for this round. Upon 2f+1 prevotes for a value, a node sets the decision value. 24 | * **Precommit**. In the precommit phase, nodes try to commit to the decision value they agreed on in the previous phase. We broadcast a precommit for the decision value we've seen (or nil). Upon 2f+1 precommits for a decision value, we decide on it. 25 | * **Decision**. If we have received 2f+1 precommits for a non-nil value, we have achieved a decision and finish this epoch. Else, we begin the next round, which selects the next proposer. 26 | * The protocol exhibits safety, finality, liveness. 27 | * **Timeouts**. 28 | >**FLP impossibility result** - it is impossible in a fully asynchronous message-passing distributed system, in which at least one process may have a crash failure, for a deterministic algorithm to achieve consensus 29 | * Tendermint eventually finalises due to timeouts. At each phase of a round - propose, prevote, precommit - timeouts are used to make sure the protocol completes. 30 | * Tendermint guarantees the safety and liveness of the blockchain so long as less than 1/3 of the total weight of the Validator set is malicious or faulty. 31 | 32 | In terms of Tendermint's implementation: 33 | 34 | * Sync. 35 | * To participate in consensus rounds, we need to be fully synced in order to determine the current proposer. 36 | * Unlike Nakamoto consensus, Tendermint consensus history is linear. There are no divergent branches to explore, as the protocol finalises on only one block at each consensus epoch. So this makes sync a lot easier. 37 | * **Sync algorithm**. A node connects to all available peers and requests their heights. Starting from our latest pool.height, we request blocks in sequence from peers that reported higher heights than ours. Requests are continuously made for blocks of higher heights until the limit is reached. The node is considered caught up if it has at least one peer and its height is at least as high as the max reported peer height. 38 | * Once we have completed synchronisation, the node swaps to live consensus mode. 39 | * Synchronisation leaves us up-to-date at an epoch level, but does not allow us to get up-to-date to a specific round or round phase in consensus. 40 | * Timeouts 41 | * Timeout in Tendermint increases exponentially with round number, in order to give more time for nodes to reach consensus in the presence of delays. 42 | * $timeoutX(r) = initT timeoutX + r * timeoutDelta;$ 43 | * The timeouts are reset for every new height (consensus instance). 44 | * Proposer selection. 45 | * The proposer is selected each round according to the algorithm [here](https://github.com/tendermint/tendermint/blob/v0.34.x/spec/consensus/proposer-selection.md). 46 | * You can imagine proposer selection as a round-robin rotation, weighted according to the validator's stake. 47 | * The validator set can change at each epoch. 48 | 49 | 50 | ## Diagrams. 51 | 52 | A round and its phases: 53 | 54 | ![consensus_logic](./tendermintround.png) 55 | 56 | ## Analogues between Nakamoto and Tendermint consensus. 57 | 58 | * Weak subjectivity. 59 | * In Bitcoin, there are manually embedded block "checkpoints" in the source code, preventing reorgs beyond these points. This is weak subjectivity. 60 | * In Tendermint, weak subjectivity applies to syncing of the entire chain. 61 | * Syncing algorithm. 62 | * In both Nakamoto and Tendermint, the node has two consensus modes - one for live consensus and one for offline sync. 63 | * When we are not in live consensus, we must skip verifying the timestamps in blocks. This applies in Bitcoin and Tendermint. 64 | * In Tendermint, sync is simply requesting the linear sequence of blocks up to the current height. In Bitcoin, sync is a greedy iterative search where we continually ask for more blocks from all peers. Although game theoretically, the Bitcoin longest chain will always be the heaviest chain in the limit, we cannot rely on this assumption during sync as an attacker can disconnect us from the nodes with this information. 65 | * In Tendermint, we are caught up when we "have at least one peer and its height is at least as high as the max reported peer height". In Bitcoin, we are caught up when we have a block with the highest accumulated work and each of our peers has no deeper blocks with more accumulated work (we are at the tip). 66 | 67 | ## Additional readings. 68 | 69 | - https://timroughgarden.github.io/fob21/l/l7.pdf 70 | - https://arxiv.org/pdf/1807.04938 71 | - https://jzhao.xyz/thoughts/Tendermint 72 | - https://groups.csail.mit.edu/tds/papers/Lynch/jacm88.pdf 73 | - https://tendermint.com/static/docs/tendermint.pdf 74 | 75 | ## FAQ's. 76 | 77 | **How do nodes stay in sync on the value?** 78 | 79 | The proposal contains the full value (e.g. block) being proposed. 80 | 81 | **What do nodes need to resync?** 82 | 83 | In the barebones case, just an online persistent peer to download blocks from. 84 | 85 | **How do nodes orient themselves in the current round and timeouts when they first reconnect?** 86 | 87 | They download the most recent block, thus **rooting** themselves in the latest block timestamp. (props to [Adi Serendischi](https://x.com/AdiSeredinschi) for this). 88 | 89 | **Assuming that validators perform sync by downloading the history from other validators, how do they know when they are "up-to-speed"?** 90 | 91 | `IsCaughtUp` method - the node is considered caught up if it has at least one peer and its height is at least as high as the max reported peer height. 92 | 93 | **What happens when a node disconnects from and then needs to reconnect to other validators?** 94 | 95 | If a node loses network access during live consensus and missed >1 block, it needs to resync first before switching back into live consensus mode. 96 | 97 | In the current Tendermint node implementation, this necessitates a manual restart. 98 | 99 | **Does the protocol continue when one node is offline? Or there are less than the required number of connections?** 100 | 101 | In Paxos consensus, we don't propose unless we have enough acceptors online to constitute a quorum. This is the same in Tendermint. 102 | 103 | **How do you prevent an eclipse attack? ie. validators fool you into alternative view of consensus** 104 | 105 | Tendermint's trust model involves weak subjectivity. 106 | 107 | > See here for the original intro to the concept of "weak subjectivity". Essentially, the first time a node comes online, and any subsequent time a node comes online after being offline for a very long duration (ie. multiple months), that node must find some third-party source to determine the correct head of the chain. This could be their friend, it could be exchanges and block explorer sites, the client developers themselves, or many other actors. PoW does not have this requirement. 108 | > 109 | > Source: https://vitalik.eth.limo/general/2020/11/06/pos2020.html 110 | 111 | **Why do we have to prevote and then precommit? Why not just precommit?** 112 | 113 | * In non-Byzantine environments, consensus requires two communication steps. A node either accepts the proposed value or rejects it. Usually it rejects silently, namely, there is no PREVOTE nil. Once a majority accept a value, the value is decided. This is what the PRECOMMIT messages are for in Tendermint. 114 | * When we introduce Byzantine behavior, it is possible for the proposer to equivocate. Namely, it can propose different values to different processes. The PREVOTE phase is therefore introduced to reach an agreement on what value the proposer has proposed. If it has equivocated, we don't reach an agreement here. So the PREVOTE phase is intented to agree on the correctness of the proposer. 115 | 116 | **Explain why the network needs 3F+1 nodes, what F is, what is the Byzantine fault model?** 117 | 118 | In a crash fault, a process simply halts. In a Byzantine fault, it can behave arbitrarily. Crash faults are easier to handle, as no process can lie to another process. Systems which only tolerate crash faults can operate via simple majority rule, and therefore typically tolerate simultaneous failure of up to half of the system. If the number of failures the system can tolerate is f, such systems must have at least 2f + 1 processes. 119 | 120 | Byzantine failures are more complicated. In a system of 2f + 1 processes, if f are Byzantine, they can co-ordinate to say arbitrary things to the other f + 1 processes. For instance, suppose we are trying to agree on the value of a single bit, and f = 1, so we have N = 3 processes, A, B, and C, where C is Byzantine. C can tell A that the value is 0 and tell B that it’s 1. If A agrees that its 0, and B agrees that its 1, then they will both think they have a majority and commit, thereby violating the safety condition. Hence, the upper bound on faults tolerated by a Byzantine system is strictly lower than a non-Byzantine one. 121 | 122 | -------------------------------------------------------------------------------- /docs/SPEC.md: -------------------------------------------------------------------------------- 1 | config 2 | validators 3 | ip 4 | pubkey 5 | 6 | getvalue 7 | get current value at round 8 | 9 | message wrappers 10 | for each protocol buffers message 11 | we want to wrap it in our own datatype 12 | 13 | transaction 14 | .hash function which computes the sha256 hash 15 | .verify_sig function which validates the signature is correct 16 | 17 | vote 18 | .verify_sig function which validates the signature is correct for the sender 19 | 20 | block 21 | .hash function which computes canonical hash for list of txs 22 | stores block in store by hash for later pickup 23 | 24 | AbstractMonotonicClock 25 | used for local testing of timeouts etc. 26 | simulates a monotonic clock 27 | has a timeout method used in tendermint algo 28 | 29 | methods 30 | crank(time: ms) 31 | get_time(time: ms) 32 | async timeout(time: ms) 33 | fires after time 34 | 35 | validator 36 | backing store 37 | trait 38 | uses leveldb on backend 39 | methods: 40 | get_prevotes() 41 | get_precommits() 42 | insert_prevote() 43 | insert_precommit() 44 | 45 | startup 46 | ask all peers 47 | download latest block 48 | "root" in this block timestamp 49 | now node is oriented 50 | 51 | consensus step 52 | propose 53 | prevote 54 | precommit 55 | decision -> invoke decision handler 56 | 57 | test 58 | factory 59 | generates 5 validators 60 | 0.0.0.0:port % i 61 | pubkey 62 | leveldb database name -> use purely in-memory version for tests 63 | -------------------------------------------------------------------------------- /examples/standalone-channels/main.rs: -------------------------------------------------------------------------------- 1 | use std::collections::VecDeque; 2 | 3 | use std::{ 4 | sync::Arc, 5 | time::{SystemTime, UNIX_EPOCH}, 6 | }; 7 | use tendermint::{crypto::ECDSAKeypair, params::*, process::*}; 8 | use tokio::sync::{mpsc, Mutex}; 9 | use tokio_stream::StreamExt; 10 | 11 | async fn setup_pure_sendreceive() { 12 | // Create channels for each node 13 | let mut senders = Vec::new(); 14 | let mut receivers = VecDeque::new(); 15 | 16 | let get_value = || SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(); 17 | 18 | // Separate the creation of senders and receivers 19 | for _ in 0..NODES { 20 | let (tx, rx) = mpsc::channel(100); 21 | senders.push(tx); 22 | receivers.push_back(rx); 23 | } 24 | 25 | // Define proposer sequence (round-robin) 26 | let proposer_sequence: Vec = (0..NODES).collect(); 27 | 28 | // Initialize nodes 29 | let mut nodes = Vec::new(); 30 | for i in 0..NODES { 31 | let mut node_senders = Vec::new(); 32 | for j in 0..NODES { 33 | if i != j { 34 | node_senders.push(senders[j].clone()); 35 | } 36 | } 37 | let keypair = ECDSAKeypair::new(); 38 | let receiver = receivers.pop_front().unwrap(); 39 | let node = Process::new( 40 | i, 41 | keypair, 42 | Arc::new(Mutex::new(receiver)), 43 | node_senders, 44 | proposer_sequence.clone(), 45 | get_value, 46 | ); 47 | nodes.push(node); 48 | } 49 | 50 | // Listen to events from node0. 51 | let mut subscriber1 = nodes[0].subscribe(); 52 | tokio::spawn(async move { 53 | while let Some(event) = subscriber1.next().await { 54 | println!("Subscriber 1 received: {:?}", event); 55 | } 56 | }); 57 | 58 | // Run all nodes 59 | let handles: Vec<_> = nodes 60 | .into_iter() 61 | .map(|mut node| { 62 | tokio::spawn(async move { 63 | node.run_epoch(None).await; 64 | }) 65 | }) 66 | .collect(); 67 | 68 | // Wait for all nodes to finish 69 | for handle in handles { 70 | let _ = handle.await; 71 | } 72 | 73 | println!("Consensus reached."); 74 | } 75 | 76 | #[tokio::main] 77 | async fn main() { 78 | setup_pure_sendreceive().await; 79 | } 80 | -------------------------------------------------------------------------------- /examples/standalone-http/main.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | collections::VecDeque, 3 | time::{SystemTime, UNIX_EPOCH}, 4 | }; 5 | use tendermint::{ 6 | crypto::ECDSAKeypair, messages::SignedMessage, params::*, process::*, rpc_client::RpcClient, 7 | rpc_server::Server, 8 | }; 9 | use tokio_stream::StreamExt; 10 | 11 | async fn setup_api_servers() { 12 | // Create channels for each node 13 | let mut senders = Vec::new(); 14 | let mut receivers = VecDeque::new(); 15 | 16 | let get_value = || SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(); 17 | 18 | // Setup node API servers. 19 | for i in 0..NODES { 20 | let server = Server::::new("127.0.0.1".parse().unwrap(), 3030 + i as u16); 21 | receivers.push_back(server.get_receiver()); 22 | let client = RpcClient::::new( 23 | 100, 24 | format!("http://localhost:{}/inbox/", server.port), 25 | ); 26 | senders.push(client.get_sender()); 27 | 28 | tokio::spawn(async move { 29 | server.run().await; 30 | }); 31 | tokio::spawn(async move { 32 | client.start().await; 33 | }); 34 | } 35 | 36 | // Define proposer sequence (round-robin) 37 | let proposer_sequence: Vec = (0..NODES).collect(); 38 | 39 | // Initialize nodes 40 | let mut nodes = Vec::new(); 41 | for i in 0..NODES { 42 | let mut node_senders = Vec::new(); 43 | for j in 0..NODES { 44 | if i != j { 45 | node_senders.push(senders[j].clone()); 46 | } 47 | } 48 | 49 | let keypair = ECDSAKeypair::new(); 50 | let receiver = receivers.pop_front().unwrap(); 51 | let node = 52 | Process::new(i, keypair, receiver, node_senders, proposer_sequence.clone(), get_value); 53 | nodes.push(node); 54 | } 55 | 56 | // Listen to events from node0. 57 | let mut subscriber1 = nodes[0].subscribe(); 58 | tokio::spawn(async move { 59 | while let Some(event) = subscriber1.next().await { 60 | println!("Subscriber 1 received: {:?}", event); 61 | } 62 | }); 63 | 64 | // Run all nodes 65 | let handles: Vec<_> = nodes 66 | .into_iter() 67 | .map(|mut node| { 68 | tokio::spawn(async move { 69 | node.run_epoch(None).await; 70 | }) 71 | }) 72 | .collect(); 73 | 74 | // Wait for all nodes to finish 75 | for handle in handles { 76 | handle.await.unwrap(); 77 | } 78 | 79 | println!("Consensus reached."); 80 | } 81 | 82 | #[tokio::main] 83 | async fn main() { 84 | println!("Main"); 85 | setup_api_servers().await; 86 | 87 | // just wait for sigkill 88 | tokio::signal::ctrl_c().await.unwrap(); 89 | } 90 | -------------------------------------------------------------------------------- /protos/protos.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package protos; 3 | 4 | service ValidatorService { 5 | // Sync: Get more than one block for resyncing fully. 6 | rpc GetHistory(GetHistoryQuery) returns (GetHistoryResponse); 7 | // Sync: Get the latest block for reconnecting to network. 8 | rpc GetLatest(GetLatestQuery) returns (GetLatestResponse); 9 | 10 | // Gossip transactions to other nodes. 11 | rpc GossipTxs(stream TxGossip) returns (stream TxGossip); 12 | 13 | // Consensus: Propose. 14 | rpc Propose(ProposeMessage) returns (ProposeResponse); 15 | // Consensus: Prevote. 16 | rpc Prevote(VoteMessage) returns (PrevoteResponse); 17 | // // Consensus: Precommit. 18 | rpc Precommit(VoteMessage) returns (PrecommitResponse); 19 | } 20 | 21 | // 22 | // SYNC. 23 | // 24 | 25 | message GetHistoryQuery { 26 | int64 start_height = 1; 27 | int64 end_height = 2; 28 | } 29 | 30 | message GetHistoryResponse { 31 | repeated Block blocks = 1; 32 | repeated VoteMessage votes = 2; 33 | } 34 | 35 | message GetLatestQuery {} 36 | 37 | message GetLatestResponse { 38 | repeated VoteMessage votes = 1; 39 | int64 localTime = 2; 40 | } 41 | 42 | message Block { 43 | repeated Transaction txs = 1; 44 | bytes proposer = 2; 45 | bytes previous_block_hash = 3; 46 | } 47 | 48 | message TxGossip { 49 | repeated Transaction txs = 1; 50 | } 51 | 52 | message Transaction { 53 | bytes data = 1; 54 | bytes sig = 2; 55 | bytes sender = 3; 56 | int64 timestamp = 4; 57 | } 58 | 59 | enum MessageType { 60 | PROPOSE = 1; 61 | PREVOTE = 2; 62 | PRECOMMIT = 3; 63 | } 64 | 65 | message ProposeMessage { 66 | MessageType msgType = 5; 67 | int64 round = 1; 68 | int64 height = 2; 69 | bytes value = 3; 70 | bytes sig = 4; 71 | bytes sender = 5; 72 | int64 timestamp = 6; 73 | } 74 | 75 | message VoteMessage { 76 | MessageType msgType = 5; 77 | int64 round = 1; 78 | int64 height = 2; 79 | bytes value = 3; 80 | bytes sig = 4; 81 | bytes sender = 5; 82 | int64 timestamp = 6; 83 | } 84 | 85 | message PrevoteResponse {} 86 | message PrecommitResponse {} -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | imports_granularity = "Crate" 3 | use_small_heuristics = "Max" 4 | comment_width = 100 5 | wrap_comments = true 6 | binop_separator = "Back" 7 | trailing_comma = "Vertical" 8 | trailing_semicolon = false 9 | use_field_init_shorthand = true 10 | format_code_in_doc_comments = true 11 | doc_comment_code_block_width = 100 -------------------------------------------------------------------------------- /src/algos.rs: -------------------------------------------------------------------------------- 1 | use tokio::time::Duration; 2 | 3 | /// Gets the proposer for a round. 4 | pub fn get_proposer_for_round(round: u8, proposer_sequence: &[usize]) -> usize { 5 | proposer_sequence[(round - 1) as usize % proposer_sequence.len()] 6 | /* 7 | 8 | // Tendermint/CometBFT consensus WIP. 9 | // https://github.com/tendermint/tendermint/blob/main/spec/consensus/consensus.md#common-exit-conditions 10 | // https://docs.tendermint.com/v0.34/introduction/what-is-tendermint.html# 11 | 12 | /* 13 | # Tendermint is a byzantine fault-tolerant consensus algorithm. 14 | # It consists of a validator set, where each validator is a node with a public key and some voting power. 15 | 16 | 17 | # vset - the validator set 18 | # n - the number of validators 19 | # VP(i) - voting power of validator i 20 | # A(i) - accumulated priority for validator i 21 | # P - total voting power of set 22 | # avg - average of all validator priorities 23 | # prop - proposer 24 | def voting_power(i): 25 | return 0 26 | 27 | # Select the proposer for the next epoch, from a dynamic validator set and 28 | # the history of past proposers (priority). 29 | # [1]: https://github.com/tendermint/tendermint/blob/v0.34.x/spec/consensus/proposer-selection.md 30 | def ProposerSelection(vset, priority): 31 | A = priority 32 | A2 = priority.copy() 33 | 34 | # P - total voting power of set 35 | P = sum(voting_power(i) for i in vset) 36 | 37 | # scale the priority values 38 | diff = max(A) - min(A) 39 | threshold = 2 * P 40 | if diff > threshold: 41 | scale = diff/threshold 42 | for validator in vset: 43 | i = validator 44 | A2[i] = A[i]/scale 45 | 46 | # center priorities around zero 47 | avg = sum(A(i) for i in vset)/len(vset) 48 | for validator in vset: 49 | i = validator 50 | A2[i] -= avg 51 | 52 | # compute priorities and elect proposer 53 | for validator in vset: 54 | i = validator 55 | A2[i] += voting_power(i) 56 | 57 | prop = max(A) 58 | A2[prop] -= P 59 | */ 60 | 61 | package tendermint 62 | 63 | import ( 64 | "testing" 65 | ) 66 | 67 | func TestTendermint(t *testing.T) { 68 | tendermint_run() 69 | } 70 | 71 | */ 72 | } 73 | 74 | /// Gets the timeout for a round. 75 | /// Timeout in Tendermint increases exponentially with round number, in order to give more time for 76 | /// nodes to reach consensus in the presence of delays. 77 | pub fn get_timeout_for_round(_round: u64) -> Duration { 78 | // timeout constnats. 79 | // The timeouts prevent the algorithm from blocking and waiting forever for some condition to be true, ensure that processes continuously transition between rounds, and guarantee that eventually (after GST) communication between correct processes is timely and reliable so they can decide 80 | // The last role is achieved by increasing the timeouts with every new round r, i.e, timeoutX(r) = initT timeoutX + r * timeoutDelta; they are reset for every new height (consensus instance). 81 | Duration::from_millis(1000) 82 | } 83 | -------------------------------------------------------------------------------- /src/cli/cmd/accounts.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::CmdSync; 2 | use clap::Parser; 3 | use serde_json::Result; 4 | use tendermint::{config::AccountConfig, crypto::ECDSAKeypair}; 5 | 6 | pub struct AccountsOutput {} 7 | 8 | #[derive(Parser, Debug)] 9 | #[clap(author, version, about, long_about = None)] 10 | pub struct AccountsArgs { 11 | // --list 12 | #[clap(long, help = "List all accounts")] 13 | pub list: bool, 14 | // --new 15 | #[clap(long, help = "Create a new account")] 16 | pub new: bool, 17 | } 18 | 19 | impl CmdSync for AccountsArgs { 20 | type Output = Result; 21 | 22 | fn run(self) -> Self::Output { 23 | if self.list { 24 | // TODO. 25 | } else if self.new { 26 | new_account() 27 | } 28 | Ok(AccountsOutput {}) 29 | } 30 | } 31 | 32 | fn new_account() { 33 | let keypair = ECDSAKeypair::new(); 34 | let datum = AccountConfig { 35 | pubkey: keypair.get_public_key().to_string(), 36 | privkey: keypair.get_secret_key().display_secret().to_string(), 37 | }; 38 | println!("{}", serde_json::to_string_pretty(&datum).unwrap()); 39 | } 40 | -------------------------------------------------------------------------------- /src/cli/cmd/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod accounts; 2 | pub mod network; 3 | pub mod node; 4 | -------------------------------------------------------------------------------- /src/cli/cmd/network.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::CmdSync; 2 | use clap::Parser; 3 | use serde_json::Result; 4 | use tendermint::{ 5 | config::{TendermintConfig, ValidatorInfo}, 6 | crypto::ECDSAKeypair, 7 | }; 8 | 9 | pub struct NetworkOutput {} 10 | 11 | #[derive(Parser, Debug)] 12 | #[clap(author, version, about, long_about = None)] 13 | pub struct NetworkArgs {} 14 | 15 | impl CmdSync for NetworkArgs { 16 | type Output = Result; 17 | 18 | fn run(self) -> Self::Output { 19 | let keypair = ECDSAKeypair::new(); 20 | let config = TendermintConfig { 21 | validators: vec![ValidatorInfo { 22 | pubkey: keypair.get_public_key().to_string(), 23 | address: "0.0.0.0".parse().unwrap(), 24 | port: 3030, 25 | }], 26 | }; 27 | // Print to JSON format (pretty) 28 | let config = serde_json::to_string_pretty(&config).unwrap(); 29 | println!("{}", config); 30 | Ok(NetworkOutput {}) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/cli/cmd/node.rs: -------------------------------------------------------------------------------- 1 | use crate::utils::CmdAsync; 2 | use clap::Parser; 3 | use serde_json::Result; 4 | use std::{net::IpAddr, path::PathBuf}; 5 | use tendermint::config::{parse_config, AccountConfig, ValidatorInfo}; 6 | 7 | pub struct NodeOutput {} 8 | 9 | #[derive(Parser, Debug)] 10 | #[clap(author, version, about, long_about = None)] 11 | pub struct NodeArgs { 12 | // port 13 | #[clap(long, default_value = "3030")] 14 | port: u16, 15 | 16 | // host 17 | #[clap(long, default_value = "0.0.0.0")] 18 | host: IpAddr, 19 | 20 | // privatekey 21 | #[clap(long)] 22 | account: PathBuf, 23 | 24 | // genesis config. 25 | #[clap(long)] 26 | config: PathBuf, 27 | } 28 | 29 | impl CmdAsync for NodeArgs { 30 | type Output = Result; 31 | 32 | async fn run(self) -> Self::Output { 33 | let config = parse_config(self.config); 34 | // Load the account config. 35 | let accountData = std::fs::read_to_string(self.account).unwrap(); 36 | let account: AccountConfig = serde_json::from_str(&accountData).unwrap(); 37 | println!("Account: {:?}", account); 38 | run_node(config.validators, self.host, self.port).await; 39 | Ok(NodeOutput {}) 40 | } 41 | } 42 | 43 | use std::time::{SystemTime, UNIX_EPOCH}; 44 | 45 | use tendermint::{ 46 | crypto::ECDSAKeypair, messages::SignedMessage, process::Process, rpc_server::Server, 47 | }; 48 | use tokio_stream::StreamExt; 49 | 50 | async fn run_node(_validators: Vec, host: IpAddr, port: u16) { 51 | // Network configuration: 52 | // - peers: (pubkey,address)[] 53 | // Parse the configuration file. 54 | 55 | // Setup RPC server. 56 | // Setup RPC client for each peer. 57 | // Setup process. 58 | // Run process. 59 | 60 | let keypair = ECDSAKeypair::new(); 61 | 62 | let peer_senders = Vec::new(); 63 | 64 | let api_server = Server::::new(host, port); 65 | let receiver = api_server.get_receiver(); 66 | tokio::spawn(async move { 67 | api_server.run().await; 68 | }); 69 | // tokio::spawn(async move { 70 | // client.start().await; 71 | // }); 72 | 73 | // The function to get the current value for the chain. 74 | let get_value = || SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs().to_string(); 75 | 76 | // Define proposer sequence (round-robin) 77 | let proposer_sequence: Vec = (0..4).collect(); 78 | let mut process = 79 | Process::new(0, keypair, receiver, peer_senders, proposer_sequence.clone(), get_value); 80 | 81 | // Listen to events from node0. 82 | let mut subscriber1 = process.subscribe(); 83 | tokio::spawn(async move { 84 | while let Some(event) = subscriber1.next().await { 85 | println!("Subscriber 1 received: {:?}", event); 86 | } 87 | }); 88 | 89 | tokio::spawn(async move { 90 | process.run_epoch(None).await; 91 | }) 92 | .await 93 | .unwrap(); 94 | 95 | println!("Consensus reached."); 96 | } 97 | -------------------------------------------------------------------------------- /src/cli/tendermint.rs: -------------------------------------------------------------------------------- 1 | pub mod cmd; 2 | mod utils; 3 | 4 | use crate::utils::{CmdAsync, CmdSync}; 5 | use clap::{Parser, Subcommand}; 6 | use cmd::{accounts::AccountsArgs, network::NetworkArgs, node::NodeArgs}; 7 | 8 | #[derive(Debug, Parser)] 9 | #[clap(name = "tendermint")] 10 | pub struct Opts { 11 | #[clap(subcommand)] 12 | pub sub: Subcommands, 13 | } 14 | 15 | #[derive(Debug, Subcommand)] 16 | #[allow(clippy::large_enum_variant)] 17 | pub enum Subcommands { 18 | Node(NodeArgs), 19 | Accounts(AccountsArgs), 20 | Network(NetworkArgs), 21 | } 22 | 23 | #[tokio::main] 24 | async fn main() { 25 | let opts = Opts::parse(); 26 | match opts.sub { 27 | Subcommands::Node(cmd) => { 28 | cmd.run().await.unwrap(); 29 | } 30 | Subcommands::Accounts(cmd) => { 31 | cmd.run().unwrap(); 32 | } 33 | Subcommands::Network(cmd) => { 34 | cmd.run().unwrap(); 35 | } 36 | } 37 | 38 | // TODO: consider returning Result for error codes. 39 | // Ok(()) 40 | } 41 | -------------------------------------------------------------------------------- /src/cli/utils.rs: -------------------------------------------------------------------------------- 1 | /// Common trait for all cli commands 2 | pub trait CmdSync: clap::Parser + Sized { 3 | type Output; 4 | 5 | fn run(self) -> Self::Output; 6 | } 7 | 8 | pub trait CmdAsync: clap::Parser + Sized { 9 | type Output; 10 | 11 | async fn run(self) -> Self::Output; 12 | } 13 | -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | use std::{net::IpAddr, path::PathBuf}; 3 | 4 | #[derive(Debug, Clone, Serialize, Deserialize)] 5 | pub struct ValidatorInfo { 6 | /// The public key of the validator. 7 | pub pubkey: String, 8 | /// The IP address of the validator. 9 | pub address: IpAddr, 10 | /// The IP port of the validator. 11 | pub port: u16, 12 | } 13 | 14 | #[derive(Debug, Clone, Serialize, Deserialize)] 15 | pub struct TendermintConfig { 16 | /// The set of validators at genesis. 17 | pub validators: Vec, 18 | } 19 | 20 | #[derive(Debug, Clone, Serialize, Deserialize)] 21 | pub struct AccountConfig { 22 | pub pubkey: String, 23 | pub privkey: String, 24 | } 25 | 26 | pub fn parse_config(config: PathBuf) -> TendermintConfig { 27 | // Parse the configuration file. 28 | let config = std::fs::read_to_string(config).unwrap(); 29 | let config: TendermintConfig = serde_json::from_str(&config).unwrap(); 30 | println!("Config: {:?}", config); 31 | config 32 | } 33 | -------------------------------------------------------------------------------- /src/crypto.rs: -------------------------------------------------------------------------------- 1 | use rand::rngs::OsRng; 2 | use secp256k1::{ecdsa::SerializedSignature, Message, Secp256k1, SecretKey}; 3 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 4 | use sha3::{Digest, Keccak256}; 5 | use std::{ 6 | fmt, 7 | fmt::{Display, Formatter}, 8 | str::FromStr, 9 | }; 10 | 11 | #[derive(Clone, Debug, Copy)] 12 | pub struct Signature(secp256k1::ecdsa::SerializedSignature); 13 | 14 | #[derive(Clone, Debug, Copy)] 15 | pub struct PublicKey(secp256k1::PublicKey); 16 | 17 | pub type Keypair = ECDSAKeypair; 18 | 19 | #[derive(Debug)] 20 | pub struct ECDSAKeypair { 21 | secret_key: SecretKey, 22 | public_key: PublicKey, 23 | } 24 | 25 | impl Default for ECDSAKeypair { 26 | fn default() -> Self { 27 | Self::new() 28 | } 29 | } 30 | 31 | impl ECDSAKeypair { 32 | pub fn new() -> Self { 33 | let secp = Secp256k1::new(); 34 | let (secret_key, public_key) = secp.generate_keypair(&mut OsRng); 35 | ECDSAKeypair { secret_key, public_key: PublicKey(public_key) } 36 | } 37 | 38 | pub fn new_from_privatekey(private_key: &str) -> Self { 39 | let secp = Secp256k1::new(); 40 | let secret_key = SecretKey::from_str(private_key).unwrap(); 41 | 42 | let public_key = secp256k1::PublicKey::from_secret_key(&secp, &secret_key); 43 | ECDSAKeypair { secret_key, public_key: PublicKey(public_key) } 44 | } 45 | 46 | pub fn get_public_key(&self) -> PublicKey { 47 | self.public_key 48 | } 49 | 50 | pub fn get_secret_key(&self) -> SecretKey { 51 | self.secret_key 52 | } 53 | 54 | pub fn sign(&self, data: &[u8]) -> Signature { 55 | let secp = Secp256k1::new(); 56 | let mut hasher = Keccak256::new(); 57 | hasher.update(data); 58 | let hash = hasher.finalize(); 59 | let message = Message::from_slice(&hash).expect("32 bytes"); 60 | let signature = secp.sign_ecdsa(&message, &self.secret_key); 61 | Signature(signature.serialize_der()) 62 | } 63 | } 64 | 65 | pub fn verify_signature( 66 | data: &[u8], 67 | signature: &SerializedSignature, 68 | public_key: PublicKey, 69 | ) -> bool { 70 | let secp: Secp256k1 = Secp256k1::new(); 71 | let mut hasher = Keccak256::new(); 72 | hasher.update(data); 73 | let hash = hasher.finalize(); 74 | let message = Message::from_slice(&hash).expect("32 bytes"); 75 | secp.verify_ecdsa(&message, &signature.to_signature().unwrap(), &public_key.0).is_ok() 76 | } 77 | 78 | // PublicKey. 79 | // Deserialize. 80 | impl<'de> Deserialize<'de> for PublicKey { 81 | fn deserialize(deserializer: D) -> Result 82 | where 83 | D: Deserializer<'de>, 84 | { 85 | let s = String::deserialize(deserializer)?; 86 | let public_key = PublicKey::from_str(&s).map_err(serde::de::Error::custom)?; 87 | Ok(PublicKey(public_key.0)) 88 | } 89 | } 90 | 91 | // FromStr. 92 | impl FromStr for PublicKey { 93 | type Err = secp256k1::Error; 94 | 95 | fn from_str(s: &str) -> Result { 96 | let public_key = secp256k1::PublicKey::from_str(s)?; 97 | Ok(PublicKey(public_key)) 98 | } 99 | } 100 | 101 | // Serialize. 102 | impl Serialize for PublicKey { 103 | fn serialize(&self, serializer: S) -> Result 104 | where 105 | S: Serializer, 106 | { 107 | let public_key_str = self.0.to_string(); 108 | serializer.serialize_str(&public_key_str) 109 | } 110 | } 111 | 112 | // Signature. 113 | // Deserialize. 114 | impl<'de> Deserialize<'de> for Signature { 115 | fn deserialize(deserializer: D) -> Result 116 | where 117 | D: Deserializer<'de>, 118 | { 119 | let s = String::deserialize(deserializer)?; 120 | let b = hex::decode(s).map_err(serde::de::Error::custom)?; 121 | let signature = 122 | secp256k1::ecdsa::Signature::from_der(&b).map_err(serde::de::Error::custom)?; 123 | Ok(Signature(signature.serialize_der())) 124 | } 125 | } 126 | 127 | // FromStr. 128 | impl FromStr for Signature { 129 | type Err = secp256k1::Error; 130 | 131 | fn from_str(s: &str) -> Result { 132 | let signature = secp256k1::ecdsa::Signature::from_der(s.as_bytes())?; 133 | Ok(Signature(signature.serialize_der())) 134 | } 135 | } 136 | 137 | // Serialize. 138 | impl Serialize for Signature { 139 | fn serialize(&self, serializer: S) -> Result 140 | where 141 | S: Serializer, 142 | { 143 | // get the signature bytes no length 144 | let b = self.0.as_ref(); 145 | let mut signature_str = String::new(); 146 | for i in b { 147 | signature_str.push_str(&format!("{:02x}", i)); 148 | } 149 | serializer.serialize_str(&signature_str) 150 | } 151 | } 152 | 153 | // to_inner 154 | impl Signature { 155 | pub fn to_inner(&self) -> secp256k1::ecdsa::SerializedSignature { 156 | self.0 157 | } 158 | } 159 | 160 | impl Display for PublicKey { 161 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 162 | write!(f, "{}", self.0) 163 | } 164 | } 165 | 166 | impl Display for Signature { 167 | fn fmt(&self, f: &mut Formatter) -> fmt::Result { 168 | write!(f, "{}", self.0) 169 | } 170 | } 171 | 172 | 173 | #[cfg(test)] 174 | mod tests { 175 | use super::*; 176 | // use crypto::verify_signature; 177 | 178 | #[test] 179 | fn test_parse_keypair() { 180 | let keypair = ECDSAKeypair::new(); 181 | let keypair2 = ECDSAKeypair::new_from_privatekey(&keypair.get_secret_key().display_secret().to_string(),); 182 | // Verify generated keypair. 183 | assert!(keypair2.get_secret_key().display_secret().to_string() == keypair.get_secret_key().display_secret().to_string()); 184 | assert!(keypair2.get_public_key().to_string() == keypair.get_public_key().to_string()); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /src/events.rs: -------------------------------------------------------------------------------- 1 | use tokio::sync::broadcast; 2 | use tokio_stream::{wrappers::BroadcastStream, StreamExt}; 3 | 4 | pub struct EventSystem 5 | where 6 | T: Clone + Send + 'static, 7 | { 8 | sender: broadcast::Sender, 9 | } 10 | 11 | impl Default for EventSystem 12 | where 13 | T: Clone + Send + 'static, 14 | { 15 | fn default() -> Self { 16 | Self::new() 17 | } 18 | } 19 | 20 | impl EventSystem 21 | where 22 | T: Clone + Send + 'static, 23 | { 24 | // Create a new EventSystem 25 | pub fn new() -> Self { 26 | let (sender, _) = broadcast::channel(100); // Create a broadcast channel with 100 capacity 27 | EventSystem { sender } 28 | } 29 | 30 | // Function to subscribe to the event stream 31 | pub fn subscribe(&self) -> impl tokio_stream::Stream { 32 | let receiver = self.sender.subscribe(); 33 | // Wrap receiver into a BroadcastStream which implements Stream trait 34 | BroadcastStream::new(receiver).filter_map(|res| res.ok()) 35 | } 36 | 37 | // Function to publish an event 38 | pub fn publish(&self, event: T) { 39 | let _ = self.sender.send(event); // Ignore the error for simplicity 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod algos; 2 | pub mod config; 3 | pub mod crypto; 4 | pub mod events; 5 | pub mod messages; 6 | pub mod params; 7 | pub mod process; 8 | pub mod rpc_client; 9 | pub mod rpc_server; 10 | 11 | #[cfg(test)] 12 | mod tests { 13 | use super::*; 14 | use crypto::verify_signature; 15 | 16 | #[test] 17 | fn test_create_sign() { 18 | let keypair = crypto::ECDSAKeypair::new(); 19 | let data = b"gm tendermint"; 20 | 21 | let signature = keypair.sign(data); 22 | assert!(verify_signature(data, &signature.to_inner(), keypair.get_public_key())); 23 | 24 | println!("Signature verified successfully!"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | mod algos; 2 | mod crypto; 3 | mod events; 4 | mod messages; 5 | mod params; 6 | mod process; 7 | mod rpc_client; 8 | mod rpc_server; 9 | 10 | use crypto::ECDSAKeypair; 11 | use params::*; 12 | 13 | #[tokio::main] 14 | async fn main() { 15 | generate_node_keys(); 16 | } 17 | -------------------------------------------------------------------------------- /src/messages.rs: -------------------------------------------------------------------------------- 1 | use crate::crypto::{verify_signature, Keypair, PublicKey, Signature}; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | // Define message types 5 | #[derive(Debug, Clone, Serialize, Deserialize)] 6 | pub enum Message { 7 | Propose { round: u64, value: String }, 8 | Prevote { round: u64, value: Option }, 9 | Precommit { round: u64, value: Option }, 10 | } 11 | 12 | pub enum MessageType { 13 | Propose, 14 | Prevote, 15 | Precommit, 16 | } 17 | 18 | impl MessageType { 19 | pub fn matches(&self, msg: &Message) -> bool { 20 | match self { 21 | MessageType::Propose => matches!(msg, Message::Propose { .. }), 22 | MessageType::Prevote => matches!(msg, Message::Prevote { .. }), 23 | MessageType::Precommit => matches!(msg, Message::Precommit { .. }), 24 | } 25 | } 26 | } 27 | 28 | #[derive(Debug, Clone, Serialize, Deserialize)] 29 | pub struct SignedMessage { 30 | pub body: Message, 31 | pub signature: Signature, 32 | pub sender: PublicKey, 33 | } 34 | 35 | impl SignedMessage { 36 | pub fn new(message: Message, keypair: &Keypair) -> Self { 37 | let sender = keypair.get_public_key(); 38 | let sz: String = serde_json::to_string(&message).unwrap(); 39 | let signature = keypair.sign(sz.as_bytes()); 40 | 41 | SignedMessage { body: message, sender, signature } 42 | } 43 | 44 | pub fn verify(&self) -> bool { 45 | let sz: String = serde_json::to_string(&self.body).unwrap(); 46 | verify_signature(sz.as_bytes(), &self.signature.to_inner(), self.sender) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/params.rs: -------------------------------------------------------------------------------- 1 | // Define the number of nodes and the maximum number of faulty nodes (f) 2 | // NOTE: NODES must be at least 4 if you're building a real BFT system. N >= 3F+1. 3 | pub const NODES: usize = 5; 4 | pub const F: usize = 1; 5 | pub const QUORUM: usize = 2 * F + 1; 6 | -------------------------------------------------------------------------------- /src/process.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::Arc}; 2 | use tokio::{ 3 | sync::{mpsc, Mutex}, 4 | time::{timeout, Duration}, 5 | }; 6 | 7 | use crate::{algos::*, crypto::*, events::*, messages::*, params::*}; 8 | 9 | #[derive(Debug, Clone)] 10 | pub enum Event { 11 | Decision { height: u64, round: u64, value: String, from: usize }, 12 | } 13 | 14 | /// A process running the Tendermint consensus algorithm. 15 | pub struct Process { 16 | pub id: usize, 17 | 18 | pub keypair: Keypair, 19 | 20 | /// Channel to receive messages from other processes. 21 | receiver: Arc>>, 22 | 23 | /// Channels to send messages to other processes. 24 | processes: Vec>, 25 | 26 | /// The array of all proposers, used for proposer selection. 27 | proposer_sequence: Vec, 28 | 29 | /// Event source. 30 | events: EventSystem, 31 | 32 | /// State. 33 | decisions: Vec, 34 | 35 | /// Callback to get the value to be proposed for agreement. 36 | get_value: fn() -> String, 37 | } 38 | 39 | 40 | 41 | /// Consensus operates in terms of epochs, which contain an unlimited number of rounds. 42 | #[derive(Debug, Clone)] 43 | pub struct EpochState { 44 | /// The current height of the consensus instance. 45 | height: u64, 46 | /// The current round number. 47 | round: u64, 48 | /// Stores a proposal received for each round. 49 | proposals: HashMap, 50 | /// Stores prevote messages received for each round. 51 | prevotes: HashMap>>, 52 | /// Stores precommit messages received for each round. 53 | precommits: HashMap>>, 54 | /// The decision reached by the consensus algorithm, if any. 55 | decision: Option, 56 | } 57 | 58 | impl Process { 59 | pub fn new( 60 | id: usize, 61 | keypair: Keypair, 62 | receiver: Arc>>, 63 | processes: Vec>, 64 | proposer_sequence: Vec, 65 | get_value: fn() -> String, 66 | ) -> Self { 67 | Process { 68 | id, 69 | keypair, 70 | receiver, 71 | processes, 72 | proposer_sequence, 73 | decisions: Vec::new(), 74 | events: EventSystem::new(), 75 | get_value, 76 | } 77 | } 78 | 79 | /// Subscribes to the consensus event stream. 80 | pub fn subscribe(&self) -> impl tokio_stream::Stream { 81 | self.events.subscribe() 82 | } 83 | 84 | /// Runs 85 | // pub async fn run(&self) { 86 | // loop { 87 | // epoch_state = self.run_round(epoch_state).await; 88 | // if epoch_state.decision.is_some() { 89 | // break; 90 | // } 91 | // } 92 | // epoch_state 93 | // } 94 | 95 | /// Runs a single epoch of Tendermint consensus, taking in optionally the current epoch state. 96 | /// Each epoch consists of at least one round. If the round fails to reach consensus, the epoch 97 | /// will continue to the next round. This function returns upon the consensus deciding a new 98 | /// value. 99 | pub async fn run_epoch(&mut self, epoch_state: Option) -> EpochState { 100 | let mut epoch_state = epoch_state.unwrap_or(EpochState { 101 | height: 0, 102 | round: 0, 103 | proposals: HashMap::new(), 104 | prevotes: HashMap::new(), 105 | precommits: HashMap::new(), 106 | decision: None, 107 | }); 108 | 109 | loop { 110 | epoch_state = self.run_round(epoch_state).await; 111 | 112 | if epoch_state.decision.is_some() { 113 | epoch_state.height += 1; 114 | self.decisions.push(epoch_state.decision.clone().unwrap()); 115 | 116 | // Publish decision event 117 | self.events.publish(Event::Decision { 118 | height: epoch_state.height, 119 | round: epoch_state.round, 120 | value: epoch_state.decision.clone().unwrap(), 121 | from: self.id, 122 | }); 123 | break; 124 | } 125 | } 126 | println!("Node {} decided on {:?}", self.id, epoch_state.decision); 127 | epoch_state 128 | } 129 | 130 | /// Runs a single round of Tendermint consensus, taking in the current epoch state. 131 | /// Returns the updated epoch state. 132 | pub async fn run_round(&self, epoch_state0: EpochState) -> EpochState { 133 | let mut epoch = epoch_state0.clone(); 134 | epoch.round += 1; 135 | 136 | let round = epoch.round; 137 | println!("Node {} starting round {}", self.id, round); 138 | 139 | // Determine proposer 140 | let proposer = get_proposer_for_round(round as u8, &self.proposer_sequence); 141 | if self.id == proposer { 142 | // Propose a value. 143 | let value = (self.get_value)(); 144 | println!("Node {} proposing value {}", self.id, value); 145 | self.broadcast(Message::Propose { round, value: value.clone() }).await; 146 | // Save own proposal 147 | epoch.proposals.insert(round, value); 148 | } 149 | 150 | // Await proposals 151 | if self.id != proposer { 152 | let propose_timeout = get_timeout_for_round(round); 153 | 154 | self.receive_messages_until_timeout( 155 | MessageType::Propose, 156 | propose_timeout, 157 | |msg| { 158 | if let Message::Propose { round: r, value } = msg.body { 159 | if r == round { 160 | println!( 161 | "Node {} received proposal from Node {}: {}", 162 | self.id, msg.sender, value 163 | ); 164 | epoch.proposals.insert(r, value); 165 | return true 166 | } 167 | } 168 | false 169 | }, 170 | || { 171 | // Timeout reached 172 | println!("Node {} timed out waiting for proposals in round {}", self.id, round); 173 | }, 174 | ) 175 | .await; 176 | } 177 | 178 | // Prevote phase 179 | let proposal = epoch.proposals.get(&round).cloned(); 180 | self.broadcast(Message::Prevote { round, value: proposal.clone() }).await; 181 | 182 | // Collect prevotes 183 | let prevote_timeout = get_timeout_for_round(round); 184 | let _start = tokio::time::Instant::now(); 185 | let mut prevotes = Vec::new(); 186 | 187 | self.receive_messages_until_timeout( 188 | MessageType::Prevote, 189 | prevote_timeout, 190 | |msg| { 191 | if let Message::Prevote { round: r, value } = msg.body { 192 | if r == round { 193 | prevotes.push(value.clone()); 194 | println!( 195 | "Node {} received prevote from Node {}: {:?}", 196 | self.id, msg.sender, value 197 | ); 198 | if prevotes.len() >= QUORUM { 199 | return true; 200 | } 201 | } 202 | } 203 | false 204 | }, 205 | || { 206 | // Timeout reached 207 | println!("Node {} timed out waiting for prevotes in round {}", self.id, round); 208 | }, 209 | ) 210 | .await; 211 | epoch.prevotes.insert(round, prevotes.clone()); 212 | 213 | // Determine decision based on prevotes 214 | let decision = Self::majority_decision(&prevotes); 215 | // println!("Node {} decided on {:?}", self.id, decision); 216 | self.broadcast(Message::Precommit { round, value: decision.clone() }).await; 217 | 218 | // Collect precommits 219 | let _precommit_timeout = get_timeout_for_round(round); 220 | let mut precommits = Vec::new(); 221 | 222 | self.receive_messages_until_timeout( 223 | MessageType::Precommit, 224 | prevote_timeout, 225 | |msg| { 226 | if let Message::Precommit { round: r, value } = msg.body { 227 | if r == round { 228 | precommits.push(value.clone()); 229 | println!( 230 | "Node {} received precommit from Node {}: {:?}", 231 | self.id, msg.sender, value 232 | ); 233 | if precommits.len() >= QUORUM { 234 | return true; 235 | } 236 | } 237 | } 238 | false 239 | }, 240 | || { 241 | // Timeout reached 242 | println!("Node {} timed out waiting for precommits in round {}", self.id, round); 243 | }, 244 | ) 245 | .await; 246 | epoch.precommits.insert(round, precommits.clone()); 247 | 248 | // Final decision 249 | if Self::count_occurrences(&precommits, &decision) >= QUORUM { 250 | println!("Node {} has committed value {:?} in round {}", self.id, decision, round); 251 | // Consensus reached 252 | epoch.decision = decision; 253 | } else { 254 | println!("Node {} failed to decide in round {}. Moving to next round.", self.id, round); 255 | } 256 | 257 | epoch 258 | } 259 | 260 | async fn broadcast(&self, msg: Message) { 261 | let signed_msg = SignedMessage::new(msg, &self.keypair); 262 | for sender in &self.processes { 263 | let _ = sender.send(signed_msg.clone()).await; 264 | } 265 | } 266 | 267 | async fn receive_messages_until_timeout( 268 | &self, 269 | msg_type: MessageType, 270 | timeout_duration: Duration, 271 | mut handler: impl FnMut(SignedMessage) -> bool, 272 | on_timeout: impl Fn(), 273 | ) { 274 | let start = tokio::time::Instant::now(); 275 | let mut receiver = self.receiver.lock().await; 276 | 277 | while start.elapsed() < timeout_duration { 278 | match timeout(timeout_duration - start.elapsed(), receiver.recv()).await { 279 | Ok(Some(msg)) => { 280 | if !msg.verify() { 281 | // Ignore messages with invalid signatures. 282 | continue; 283 | } 284 | 285 | if msg_type.matches(&msg.body) && handler(msg) { 286 | break; 287 | } 288 | } 289 | _ => { 290 | // Timeout reached or channel closed 291 | on_timeout(); 292 | break; 293 | } 294 | } 295 | } 296 | } 297 | 298 | fn majority_decision(prevotes: &Vec>) -> Option { 299 | let mut counts = HashMap::new(); 300 | for vote in prevotes { 301 | *counts.entry(vote.clone()).or_insert(0) += 1; 302 | } 303 | counts 304 | .into_iter() 305 | .max_by_key(|&(_, count)| count) 306 | .filter(|&(_, count)| count >= QUORUM) 307 | .map(|(value, _)| value) 308 | .unwrap_or(None) 309 | } 310 | 311 | fn count_occurrences(precommits: &Vec>, decision: &Option) -> usize { 312 | precommits.iter().filter(|&v| v == decision).count() 313 | } 314 | } 315 | -------------------------------------------------------------------------------- /src/rpc_client.rs: -------------------------------------------------------------------------------- 1 | use reqwest::Client; 2 | use serde::Serialize; 3 | use std::{fmt::Debug, sync::Arc}; 4 | use tokio::sync::{mpsc, Mutex}; 5 | 6 | #[derive(Debug, Clone)] 7 | pub struct RpcClient { 8 | inbox_url: String, 9 | sender: mpsc::Sender, // Exposes the mpsc::Sender to send messages 10 | client: Arc, // Reqwest HTTP client wrapped in Arc for shared ownership 11 | receiver: Arc>>, // Internal receiver used by the background task 12 | } 13 | 14 | impl RpcClient 15 | where 16 | T: Send + 'static + Serialize + Debug, // T must implement Serialize to be posted via HTTP 17 | { 18 | // Initialize the RpcClient with a given channel capacity and reqwest client 19 | pub fn new(capacity: usize, inbox_url: String) -> Self { 20 | let (sender, receiver) = mpsc::channel(capacity); 21 | RpcClient { 22 | inbox_url, 23 | sender, 24 | client: Arc::new(Client::new()), 25 | receiver: Arc::new(Mutex::new(receiver)), 26 | } 27 | } 28 | 29 | // Start the task that listens to the mpsc::Receiver and sends HTTP POST requests 30 | pub async fn start(self) { 31 | let receiver = self.receiver.clone(); 32 | let client = self.client.clone(); 33 | 34 | // Spawn a background task that listens for messages and sends them to /inbox 35 | tokio::spawn(async move { 36 | let mut receiver = receiver.lock().await; 37 | 38 | // Loop over the messages in the receiver 39 | while let Some(message) = receiver.recv().await { 40 | let client = client.clone(); 41 | let url = self.inbox_url.clone(); 42 | 43 | // Send the HTTP POST request with the message 44 | tokio::spawn(async move { 45 | match client.post(&url).json(&message).send().await { 46 | Ok(response) => { 47 | if response.status().is_success() { 48 | } else { 49 | eprintln!("Failed to send message: {:?}", response); 50 | // print response body 51 | eprintln!("Response body: {:?}", response.text().await); 52 | eprintln!("Message: {:?}", message); 53 | } 54 | } 55 | Err(err) => { 56 | eprintln!("HTTP error: {:?}", err); 57 | } 58 | } 59 | }); 60 | } 61 | }); 62 | } 63 | 64 | // Expose the mpsc::Sender so that other parts of the code can send messages 65 | pub fn get_sender(&self) -> mpsc::Sender { 66 | self.sender.clone() 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/rpc_server.rs: -------------------------------------------------------------------------------- 1 | use serde::{de::DeserializeOwned, Serialize}; 2 | use std::{net::IpAddr, sync::Arc}; 3 | use tokio::sync::{mpsc, Mutex}; 4 | use warp::Filter; 5 | 6 | pub struct Server { 7 | sender: mpsc::Sender, 8 | // receiver: mpsc::Receiver, 9 | receiver: Arc>>, 10 | pub addr: IpAddr, 11 | pub port: u16, 12 | } 13 | 14 | impl Server 15 | where 16 | T: Send + Sync + 'static + Serialize + DeserializeOwned + std::fmt::Debug + Clone, 17 | { 18 | pub fn new(addr: IpAddr, port: u16) -> Self { 19 | let (sender, receiver) = mpsc::channel(100); 20 | Server { sender, receiver: Arc::new(Mutex::new(receiver)), addr, port } 21 | } 22 | 23 | pub fn get_receiver(&self) -> Arc>> { 24 | self.receiver.clone() 25 | } 26 | 27 | pub async fn run(&self) { 28 | let sender = self.sender.clone(); 29 | let port = self.port; 30 | println!("Server running on port {}", port); 31 | 32 | // Define the warp route that listens on /inbox and sends incoming messages to the sender 33 | let inbox_route = warp::path("inbox").and(warp::post()).and(warp::body::json()).and_then( 34 | move |message: T| { 35 | // Print message JSON, prefixed with port [:3030] Received message: Message { data: 36 | // "Hello, world!" } 37 | println!("[:{}] Received message: {:?}", port, message); 38 | 39 | let sender = sender.clone(); 40 | async move { 41 | sender.send(message).await.unwrap(); // Send the message to the channel 42 | Ok::<_, warp::Rejection>(warp::reply::json(&"Message received")) 43 | } 44 | }, 45 | ); 46 | 47 | // Start the server 48 | warp::serve(inbox_route).run((self.addr, self.port)).await; 49 | } 50 | } 51 | --------------------------------------------------------------------------------