├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── LICENSE ├── NOTICE ├── README.md ├── build-lib.sh ├── examples └── wallet │ ├── README.md │ ├── main.rs │ └── ui.rs └── src ├── api.rs ├── blockdownload.rs ├── config.rs ├── db.rs ├── error.rs ├── jni.rs ├── lib.rs ├── p2p_bitcoin.rs ├── sendtx.rs ├── store.rs ├── trunk.rs └── wallet.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /regtest/ 3 | /testnet/ 4 | /.idea 5 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | [[package]] 4 | name = "addr2line" 5 | version = "0.12.2" 6 | source = "registry+https://github.com/rust-lang/crates.io-index" 7 | checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c" 8 | dependencies = [ 9 | "gimli", 10 | ] 11 | 12 | [[package]] 13 | name = "adler32" 14 | version = "1.1.0" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d" 17 | 18 | [[package]] 19 | name = "aho-corasick" 20 | version = "0.7.13" 21 | source = "registry+https://github.com/rust-lang/crates.io-index" 22 | checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86" 23 | dependencies = [ 24 | "memchr", 25 | ] 26 | 27 | [[package]] 28 | name = "android_liblog-sys" 29 | version = "0.1.4" 30 | source = "registry+https://github.com/rust-lang/crates.io-index" 31 | checksum = "aaf82c031178ca72b38595a54d16df8a257df9deea7d97a8992870e5c6a738e7" 32 | dependencies = [ 33 | "libc", 34 | ] 35 | 36 | [[package]] 37 | name = "android_log" 38 | version = "0.1.3" 39 | source = "registry+https://github.com/rust-lang/crates.io-index" 40 | checksum = "cc00e0d3a060cce3fa338f9644ce9a93901c79f5405330891aeca69c9957009a" 41 | dependencies = [ 42 | "android_liblog-sys", 43 | "log 0.3.9", 44 | ] 45 | 46 | [[package]] 47 | name = "ansi_term" 48 | version = "0.11.0" 49 | source = "registry+https://github.com/rust-lang/crates.io-index" 50 | checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" 51 | dependencies = [ 52 | "winapi 0.3.9", 53 | ] 54 | 55 | [[package]] 56 | name = "arrayref" 57 | version = "0.3.6" 58 | source = "registry+https://github.com/rust-lang/crates.io-index" 59 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 60 | 61 | [[package]] 62 | name = "arrayvec" 63 | version = "0.5.1" 64 | source = "registry+https://github.com/rust-lang/crates.io-index" 65 | checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" 66 | 67 | [[package]] 68 | name = "ascii" 69 | version = "0.9.3" 70 | source = "registry+https://github.com/rust-lang/crates.io-index" 71 | checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e" 72 | 73 | [[package]] 74 | name = "atty" 75 | version = "0.2.14" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 78 | dependencies = [ 79 | "hermit-abi", 80 | "libc", 81 | "winapi 0.3.9", 82 | ] 83 | 84 | [[package]] 85 | name = "autocfg" 86 | version = "0.1.7" 87 | source = "registry+https://github.com/rust-lang/crates.io-index" 88 | checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" 89 | 90 | [[package]] 91 | name = "autocfg" 92 | version = "1.0.0" 93 | source = "registry+https://github.com/rust-lang/crates.io-index" 94 | checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" 95 | 96 | [[package]] 97 | name = "backtrace" 98 | version = "0.3.49" 99 | source = "registry+https://github.com/rust-lang/crates.io-index" 100 | checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c" 101 | dependencies = [ 102 | "addr2line", 103 | "cfg-if", 104 | "libc", 105 | "miniz_oxide", 106 | "object", 107 | "rustc-demangle", 108 | ] 109 | 110 | [[package]] 111 | name = "base64" 112 | version = "0.11.0" 113 | source = "registry+https://github.com/rust-lang/crates.io-index" 114 | checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" 115 | 116 | [[package]] 117 | name = "bdk" 118 | version = "0.1.0" 119 | dependencies = [ 120 | "android_log", 121 | "bitcoin", 122 | "bitcoin-wallet", 123 | "bitcoin_hashes", 124 | "byteorder", 125 | "chrono", 126 | "clap", 127 | "dirs 2.0.2", 128 | "env_logger", 129 | "fern", 130 | "futures-preview", 131 | "futures-timer", 132 | "hex", 133 | "jni", 134 | "log 0.4.8", 135 | "lru-cache", 136 | "murmel", 137 | "once_cell", 138 | "rand 0.7.3", 139 | "rand_distr", 140 | "rusqlite", 141 | "rustyline", 142 | "serde", 143 | "serde_cbor", 144 | "serde_derive", 145 | "simplelog", 146 | "siphasher", 147 | "toml", 148 | ] 149 | 150 | [[package]] 151 | name = "bech32" 152 | version = "0.7.2" 153 | source = "registry+https://github.com/rust-lang/crates.io-index" 154 | checksum = "cdcf67bb7ba7797a081cd19009948ab533af7c355d5caf1d08c777582d351e9c" 155 | 156 | [[package]] 157 | name = "bitcoin" 158 | version = "0.21.0" 159 | source = "registry+https://github.com/rust-lang/crates.io-index" 160 | checksum = "dc34f963060a2091b4e285d8082e1946be35caf467e73b3155262c8357fb4595" 161 | dependencies = [ 162 | "bech32", 163 | "bitcoin_hashes", 164 | "byteorder", 165 | "hex", 166 | "secp256k1", 167 | "serde", 168 | ] 169 | 170 | [[package]] 171 | name = "bitcoin-wallet" 172 | version = "1.1.0" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | checksum = "b99191e69b23808ee29120a67cc4f6f1628c59ef3fdb48a779f4002c780f39c8" 175 | dependencies = [ 176 | "bitcoin", 177 | "bitcoin_hashes", 178 | "rand 0.7.3", 179 | "rust-crypto", 180 | "secp256k1", 181 | "serde", 182 | "serde_derive", 183 | ] 184 | 185 | [[package]] 186 | name = "bitcoin_hashes" 187 | version = "0.7.6" 188 | source = "registry+https://github.com/rust-lang/crates.io-index" 189 | checksum = "b375d62f341cef9cd9e77793ec8f1db3fc9ce2e4d57e982c8fe697a2c16af3b6" 190 | dependencies = [ 191 | "serde", 192 | ] 193 | 194 | [[package]] 195 | name = "bitflags" 196 | version = "1.2.1" 197 | source = "registry+https://github.com/rust-lang/crates.io-index" 198 | checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" 199 | 200 | [[package]] 201 | name = "blake2b_simd" 202 | version = "0.5.10" 203 | source = "registry+https://github.com/rust-lang/crates.io-index" 204 | checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" 205 | dependencies = [ 206 | "arrayref", 207 | "arrayvec", 208 | "constant_time_eq", 209 | ] 210 | 211 | [[package]] 212 | name = "byteorder" 213 | version = "1.3.4" 214 | source = "registry+https://github.com/rust-lang/crates.io-index" 215 | checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de" 216 | 217 | [[package]] 218 | name = "cc" 219 | version = "1.0.41" 220 | source = "registry+https://github.com/rust-lang/crates.io-index" 221 | checksum = "8dae9c4b8fedcae85592ba623c4fd08cfdab3e3b72d6df780c6ead964a69bfff" 222 | 223 | [[package]] 224 | name = "cesu8" 225 | version = "1.1.0" 226 | source = "registry+https://github.com/rust-lang/crates.io-index" 227 | checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" 228 | 229 | [[package]] 230 | name = "cfg-if" 231 | version = "0.1.10" 232 | source = "registry+https://github.com/rust-lang/crates.io-index" 233 | checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" 234 | 235 | [[package]] 236 | name = "chrono" 237 | version = "0.4.11" 238 | source = "registry+https://github.com/rust-lang/crates.io-index" 239 | checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2" 240 | dependencies = [ 241 | "num-integer", 242 | "num-traits", 243 | "time", 244 | ] 245 | 246 | [[package]] 247 | name = "clap" 248 | version = "2.33.1" 249 | source = "registry+https://github.com/rust-lang/crates.io-index" 250 | checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129" 251 | dependencies = [ 252 | "ansi_term", 253 | "atty", 254 | "bitflags", 255 | "strsim", 256 | "textwrap", 257 | "unicode-width", 258 | "vec_map", 259 | ] 260 | 261 | [[package]] 262 | name = "cloudabi" 263 | version = "0.0.3" 264 | source = "registry+https://github.com/rust-lang/crates.io-index" 265 | checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" 266 | dependencies = [ 267 | "bitflags", 268 | ] 269 | 270 | [[package]] 271 | name = "combine" 272 | version = "3.8.1" 273 | source = "registry+https://github.com/rust-lang/crates.io-index" 274 | checksum = "da3da6baa321ec19e1cc41d31bf599f00c783d0517095cdaf0332e3fe8d20680" 275 | dependencies = [ 276 | "ascii", 277 | "byteorder", 278 | "either", 279 | "memchr", 280 | "unreachable", 281 | ] 282 | 283 | [[package]] 284 | name = "constant_time_eq" 285 | version = "0.1.5" 286 | source = "registry+https://github.com/rust-lang/crates.io-index" 287 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 288 | 289 | [[package]] 290 | name = "crossbeam-utils" 291 | version = "0.7.2" 292 | source = "registry+https://github.com/rust-lang/crates.io-index" 293 | checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" 294 | dependencies = [ 295 | "autocfg 1.0.0", 296 | "cfg-if", 297 | "lazy_static", 298 | ] 299 | 300 | [[package]] 301 | name = "dirs" 302 | version = "1.0.5" 303 | source = "registry+https://github.com/rust-lang/crates.io-index" 304 | checksum = "3fd78930633bd1c6e35c4b42b1df7b0cbc6bc191146e512bb3bedf243fcc3901" 305 | dependencies = [ 306 | "libc", 307 | "redox_users", 308 | "winapi 0.3.9", 309 | ] 310 | 311 | [[package]] 312 | name = "dirs" 313 | version = "2.0.2" 314 | source = "registry+https://github.com/rust-lang/crates.io-index" 315 | checksum = "13aea89a5c93364a98e9b37b2fa237effbb694d5cfe01c5b70941f7eb087d5e3" 316 | dependencies = [ 317 | "cfg-if", 318 | "dirs-sys", 319 | ] 320 | 321 | [[package]] 322 | name = "dirs-next" 323 | version = "1.0.1" 324 | source = "registry+https://github.com/rust-lang/crates.io-index" 325 | checksum = "1cbcf9241d9e8d106295bd496bbe2e9cffd5fa098f2a8c9e2bbcbf09773c11a8" 326 | dependencies = [ 327 | "cfg-if", 328 | "dirs-sys-next", 329 | ] 330 | 331 | [[package]] 332 | name = "dirs-sys" 333 | version = "0.3.5" 334 | source = "registry+https://github.com/rust-lang/crates.io-index" 335 | checksum = "8e93d7f5705de3e49895a2b5e0b8855a1c27f080192ae9c32a6432d50741a57a" 336 | dependencies = [ 337 | "libc", 338 | "redox_users", 339 | "winapi 0.3.9", 340 | ] 341 | 342 | [[package]] 343 | name = "dirs-sys-next" 344 | version = "0.1.0" 345 | source = "registry+https://github.com/rust-lang/crates.io-index" 346 | checksum = "9c60f7b8a8953926148223260454befb50c751d3c50e1c178c4fd1ace4083c9a" 347 | dependencies = [ 348 | "libc", 349 | "redox_users", 350 | "winapi 0.3.9", 351 | ] 352 | 353 | [[package]] 354 | name = "either" 355 | version = "1.5.3" 356 | source = "registry+https://github.com/rust-lang/crates.io-index" 357 | checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3" 358 | 359 | [[package]] 360 | name = "env_logger" 361 | version = "0.7.1" 362 | source = "registry+https://github.com/rust-lang/crates.io-index" 363 | checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" 364 | dependencies = [ 365 | "atty", 366 | "humantime", 367 | "log 0.4.8", 368 | "regex", 369 | "termcolor", 370 | ] 371 | 372 | [[package]] 373 | name = "error-chain" 374 | version = "0.12.2" 375 | source = "registry+https://github.com/rust-lang/crates.io-index" 376 | checksum = "d371106cc88ffdfb1eabd7111e432da544f16f3e2d7bf1dfe8bf575f1df045cd" 377 | dependencies = [ 378 | "backtrace", 379 | "version_check", 380 | ] 381 | 382 | [[package]] 383 | name = "fallible-iterator" 384 | version = "0.2.0" 385 | source = "registry+https://github.com/rust-lang/crates.io-index" 386 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 387 | 388 | [[package]] 389 | name = "fallible-streaming-iterator" 390 | version = "0.1.9" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" 393 | 394 | [[package]] 395 | name = "fern" 396 | version = "0.6.0" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "8c9a4820f0ccc8a7afd67c39a0f1a0f4b07ca1725164271a64939d7aeb9af065" 399 | dependencies = [ 400 | "log 0.4.8", 401 | ] 402 | 403 | [[package]] 404 | name = "fuchsia-cprng" 405 | version = "0.1.1" 406 | source = "registry+https://github.com/rust-lang/crates.io-index" 407 | checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" 408 | 409 | [[package]] 410 | name = "fuchsia-zircon" 411 | version = "0.3.3" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" 414 | dependencies = [ 415 | "bitflags", 416 | "fuchsia-zircon-sys", 417 | ] 418 | 419 | [[package]] 420 | name = "fuchsia-zircon-sys" 421 | version = "0.3.3" 422 | source = "registry+https://github.com/rust-lang/crates.io-index" 423 | checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" 424 | 425 | [[package]] 426 | name = "futures-channel-preview" 427 | version = "0.3.0-alpha.18" 428 | source = "registry+https://github.com/rust-lang/crates.io-index" 429 | checksum = "f477fd0292c4a4ae77044454e7f2b413207942ad405f759bb0b4698b7ace5b12" 430 | dependencies = [ 431 | "futures-core-preview", 432 | "futures-sink-preview", 433 | ] 434 | 435 | [[package]] 436 | name = "futures-core-preview" 437 | version = "0.3.0-alpha.18" 438 | source = "registry+https://github.com/rust-lang/crates.io-index" 439 | checksum = "4a2f26f774b81b3847dcda0c81bd4b6313acfb4f69e5a0390c7cb12c058953e9" 440 | 441 | [[package]] 442 | name = "futures-executor-preview" 443 | version = "0.3.0-alpha.18" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "80705612926df8a1bc05f0057e77460e29318801f988bf7d803a734cf54e7528" 446 | dependencies = [ 447 | "futures-core-preview", 448 | "futures-util-preview", 449 | "num_cpus", 450 | ] 451 | 452 | [[package]] 453 | name = "futures-io-preview" 454 | version = "0.3.0-alpha.18" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "ee7de0c1c9ed23f9457b0437fec7663ce64d9cc3c906597e714e529377b5ddd1" 457 | 458 | [[package]] 459 | name = "futures-preview" 460 | version = "0.3.0-alpha.18" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "efa8f90c4fb2328e381f8adfd4255b4a2b696f77d1c63a3dee6700b564c4e4b5" 463 | dependencies = [ 464 | "futures-channel-preview", 465 | "futures-core-preview", 466 | "futures-executor-preview", 467 | "futures-io-preview", 468 | "futures-sink-preview", 469 | "futures-util-preview", 470 | ] 471 | 472 | [[package]] 473 | name = "futures-sink-preview" 474 | version = "0.3.0-alpha.18" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "e9b65a2481863d1b78e094a07e9c0eed458cc7dc6e72b22b7138b8a67d924859" 477 | dependencies = [ 478 | "futures-core-preview", 479 | ] 480 | 481 | [[package]] 482 | name = "futures-timer" 483 | version = "0.3.0" 484 | source = "registry+https://github.com/rust-lang/crates.io-index" 485 | checksum = "8f9eb554aa23143abc64ec4d0016f038caf53bb7cbc3d91490835c54edc96550" 486 | dependencies = [ 487 | "futures-preview", 488 | "pin-utils", 489 | ] 490 | 491 | [[package]] 492 | name = "futures-util-preview" 493 | version = "0.3.0-alpha.18" 494 | source = "registry+https://github.com/rust-lang/crates.io-index" 495 | checksum = "7df53daff1e98cc024bf2720f3ceb0414d96fbb0a94f3cad3a5c3bf3be1d261c" 496 | dependencies = [ 497 | "futures-channel-preview", 498 | "futures-core-preview", 499 | "futures-io-preview", 500 | "futures-sink-preview", 501 | "memchr", 502 | "pin-utils", 503 | "slab", 504 | ] 505 | 506 | [[package]] 507 | name = "gcc" 508 | version = "0.3.55" 509 | source = "registry+https://github.com/rust-lang/crates.io-index" 510 | checksum = "8f5f3913fa0bfe7ee1fd8248b6b9f42a5af4b9d65ec2dd2c3c26132b950ecfc2" 511 | 512 | [[package]] 513 | name = "getrandom" 514 | version = "0.1.14" 515 | source = "registry+https://github.com/rust-lang/crates.io-index" 516 | checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" 517 | dependencies = [ 518 | "cfg-if", 519 | "libc", 520 | "wasi", 521 | ] 522 | 523 | [[package]] 524 | name = "gimli" 525 | version = "0.21.0" 526 | source = "registry+https://github.com/rust-lang/crates.io-index" 527 | checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c" 528 | 529 | [[package]] 530 | name = "half" 531 | version = "1.6.0" 532 | source = "registry+https://github.com/rust-lang/crates.io-index" 533 | checksum = "d36fab90f82edc3c747f9d438e06cf0a491055896f2a279638bb5beed6c40177" 534 | 535 | [[package]] 536 | name = "hammersbald" 537 | version = "2.4.0" 538 | source = "registry+https://github.com/rust-lang/crates.io-index" 539 | checksum = "ba56eabb107827e7365630fbbf38a3928d2422cb305a2ca0acd4bdc34f5fd98e" 540 | dependencies = [ 541 | "bitcoin", 542 | "bitcoin_hashes", 543 | "byteorder", 544 | "lru-cache", 545 | "rand 0.7.3", 546 | "serde", 547 | "serde_cbor", 548 | ] 549 | 550 | [[package]] 551 | name = "hermit-abi" 552 | version = "0.1.14" 553 | source = "registry+https://github.com/rust-lang/crates.io-index" 554 | checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909" 555 | dependencies = [ 556 | "libc", 557 | ] 558 | 559 | [[package]] 560 | name = "hex" 561 | version = "0.3.2" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" 564 | 565 | [[package]] 566 | name = "humantime" 567 | version = "1.3.0" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" 570 | dependencies = [ 571 | "quick-error", 572 | ] 573 | 574 | [[package]] 575 | name = "iovec" 576 | version = "0.1.4" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e" 579 | dependencies = [ 580 | "libc", 581 | ] 582 | 583 | [[package]] 584 | name = "jni" 585 | version = "0.13.1" 586 | source = "registry+https://github.com/rust-lang/crates.io-index" 587 | checksum = "7e00f1fd30a82a801f8bf38bcb0895088a0013cde111acb713c0824edc372aa4" 588 | dependencies = [ 589 | "cesu8", 590 | "combine", 591 | "error-chain", 592 | "jni-sys", 593 | "log 0.4.8", 594 | "walkdir", 595 | ] 596 | 597 | [[package]] 598 | name = "jni-sys" 599 | version = "0.3.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" 602 | 603 | [[package]] 604 | name = "kernel32-sys" 605 | version = "0.2.2" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" 608 | dependencies = [ 609 | "winapi 0.2.8", 610 | "winapi-build", 611 | ] 612 | 613 | [[package]] 614 | name = "lazy_static" 615 | version = "1.4.0" 616 | source = "registry+https://github.com/rust-lang/crates.io-index" 617 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 618 | 619 | [[package]] 620 | name = "libc" 621 | version = "0.2.71" 622 | source = "registry+https://github.com/rust-lang/crates.io-index" 623 | checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49" 624 | 625 | [[package]] 626 | name = "libsqlite3-sys" 627 | version = "0.16.0" 628 | source = "registry+https://github.com/rust-lang/crates.io-index" 629 | checksum = "5e5b95e89c330291768dc840238db7f9e204fd208511ab6319b56193a7f2ae25" 630 | dependencies = [ 631 | "cc", 632 | "pkg-config", 633 | "vcpkg", 634 | ] 635 | 636 | [[package]] 637 | name = "linked-hash-map" 638 | version = "0.5.3" 639 | source = "registry+https://github.com/rust-lang/crates.io-index" 640 | checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a" 641 | 642 | [[package]] 643 | name = "log" 644 | version = "0.3.9" 645 | source = "registry+https://github.com/rust-lang/crates.io-index" 646 | checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" 647 | dependencies = [ 648 | "log 0.4.8", 649 | ] 650 | 651 | [[package]] 652 | name = "log" 653 | version = "0.4.8" 654 | source = "registry+https://github.com/rust-lang/crates.io-index" 655 | checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" 656 | dependencies = [ 657 | "cfg-if", 658 | ] 659 | 660 | [[package]] 661 | name = "lru-cache" 662 | version = "0.1.2" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c" 665 | dependencies = [ 666 | "linked-hash-map", 667 | ] 668 | 669 | [[package]] 670 | name = "memchr" 671 | version = "2.3.3" 672 | source = "registry+https://github.com/rust-lang/crates.io-index" 673 | checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" 674 | 675 | [[package]] 676 | name = "miniz_oxide" 677 | version = "0.3.7" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" 680 | dependencies = [ 681 | "adler32", 682 | ] 683 | 684 | [[package]] 685 | name = "mio" 686 | version = "0.6.22" 687 | source = "registry+https://github.com/rust-lang/crates.io-index" 688 | checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430" 689 | dependencies = [ 690 | "cfg-if", 691 | "fuchsia-zircon", 692 | "fuchsia-zircon-sys", 693 | "iovec", 694 | "kernel32-sys", 695 | "libc", 696 | "log 0.4.8", 697 | "miow", 698 | "net2", 699 | "slab", 700 | "winapi 0.2.8", 701 | ] 702 | 703 | [[package]] 704 | name = "miow" 705 | version = "0.2.1" 706 | source = "registry+https://github.com/rust-lang/crates.io-index" 707 | checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919" 708 | dependencies = [ 709 | "kernel32-sys", 710 | "net2", 711 | "winapi 0.2.8", 712 | "ws2_32-sys", 713 | ] 714 | 715 | [[package]] 716 | name = "murmel" 717 | version = "0.2.1" 718 | source = "git+https://github.com/rust-bitcoin/murmel#ac2f469b3c2c86f12650e8564cf468c93df86226" 719 | dependencies = [ 720 | "bitcoin", 721 | "bitcoin_hashes", 722 | "byteorder", 723 | "futures-preview", 724 | "futures-timer", 725 | "hammersbald", 726 | "log 0.4.8", 727 | "lru-cache", 728 | "mio", 729 | "rand 0.7.3", 730 | "serde", 731 | "serde_derive", 732 | "simple_logger", 733 | ] 734 | 735 | [[package]] 736 | name = "net2" 737 | version = "0.2.34" 738 | source = "registry+https://github.com/rust-lang/crates.io-index" 739 | checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7" 740 | dependencies = [ 741 | "cfg-if", 742 | "libc", 743 | "winapi 0.3.9", 744 | ] 745 | 746 | [[package]] 747 | name = "nix" 748 | version = "0.17.0" 749 | source = "registry+https://github.com/rust-lang/crates.io-index" 750 | checksum = "50e4785f2c3b7589a0d0c1dd60285e1188adac4006e8abd6dd578e1567027363" 751 | dependencies = [ 752 | "bitflags", 753 | "cc", 754 | "cfg-if", 755 | "libc", 756 | "void", 757 | ] 758 | 759 | [[package]] 760 | name = "num-integer" 761 | version = "0.1.43" 762 | source = "registry+https://github.com/rust-lang/crates.io-index" 763 | checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" 764 | dependencies = [ 765 | "autocfg 1.0.0", 766 | "num-traits", 767 | ] 768 | 769 | [[package]] 770 | name = "num-traits" 771 | version = "0.2.12" 772 | source = "registry+https://github.com/rust-lang/crates.io-index" 773 | checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" 774 | dependencies = [ 775 | "autocfg 1.0.0", 776 | ] 777 | 778 | [[package]] 779 | name = "num_cpus" 780 | version = "1.13.0" 781 | source = "registry+https://github.com/rust-lang/crates.io-index" 782 | checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" 783 | dependencies = [ 784 | "hermit-abi", 785 | "libc", 786 | ] 787 | 788 | [[package]] 789 | name = "object" 790 | version = "0.20.0" 791 | source = "registry+https://github.com/rust-lang/crates.io-index" 792 | checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5" 793 | 794 | [[package]] 795 | name = "once_cell" 796 | version = "1.4.0" 797 | source = "registry+https://github.com/rust-lang/crates.io-index" 798 | checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d" 799 | 800 | [[package]] 801 | name = "pin-utils" 802 | version = "0.1.0" 803 | source = "registry+https://github.com/rust-lang/crates.io-index" 804 | checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" 805 | 806 | [[package]] 807 | name = "pkg-config" 808 | version = "0.3.17" 809 | source = "registry+https://github.com/rust-lang/crates.io-index" 810 | checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677" 811 | 812 | [[package]] 813 | name = "ppv-lite86" 814 | version = "0.2.8" 815 | source = "registry+https://github.com/rust-lang/crates.io-index" 816 | checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" 817 | 818 | [[package]] 819 | name = "proc-macro2" 820 | version = "1.0.18" 821 | source = "registry+https://github.com/rust-lang/crates.io-index" 822 | checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa" 823 | dependencies = [ 824 | "unicode-xid", 825 | ] 826 | 827 | [[package]] 828 | name = "quick-error" 829 | version = "1.2.3" 830 | source = "registry+https://github.com/rust-lang/crates.io-index" 831 | checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" 832 | 833 | [[package]] 834 | name = "quote" 835 | version = "1.0.7" 836 | source = "registry+https://github.com/rust-lang/crates.io-index" 837 | checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37" 838 | dependencies = [ 839 | "proc-macro2", 840 | ] 841 | 842 | [[package]] 843 | name = "rand" 844 | version = "0.3.23" 845 | source = "registry+https://github.com/rust-lang/crates.io-index" 846 | checksum = "64ac302d8f83c0c1974bf758f6b041c6c8ada916fbb44a609158ca8b064cc76c" 847 | dependencies = [ 848 | "libc", 849 | "rand 0.4.6", 850 | ] 851 | 852 | [[package]] 853 | name = "rand" 854 | version = "0.4.6" 855 | source = "registry+https://github.com/rust-lang/crates.io-index" 856 | checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" 857 | dependencies = [ 858 | "fuchsia-cprng", 859 | "libc", 860 | "rand_core 0.3.1", 861 | "rdrand", 862 | "winapi 0.3.9", 863 | ] 864 | 865 | [[package]] 866 | name = "rand" 867 | version = "0.6.5" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca" 870 | dependencies = [ 871 | "autocfg 0.1.7", 872 | "libc", 873 | "rand_chacha 0.1.1", 874 | "rand_core 0.4.2", 875 | "rand_hc 0.1.0", 876 | "rand_isaac", 877 | "rand_jitter", 878 | "rand_os", 879 | "rand_pcg", 880 | "rand_xorshift", 881 | "winapi 0.3.9", 882 | ] 883 | 884 | [[package]] 885 | name = "rand" 886 | version = "0.7.3" 887 | source = "registry+https://github.com/rust-lang/crates.io-index" 888 | checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" 889 | dependencies = [ 890 | "getrandom", 891 | "libc", 892 | "rand_chacha 0.2.2", 893 | "rand_core 0.5.1", 894 | "rand_hc 0.2.0", 895 | ] 896 | 897 | [[package]] 898 | name = "rand_chacha" 899 | version = "0.1.1" 900 | source = "registry+https://github.com/rust-lang/crates.io-index" 901 | checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef" 902 | dependencies = [ 903 | "autocfg 0.1.7", 904 | "rand_core 0.3.1", 905 | ] 906 | 907 | [[package]] 908 | name = "rand_chacha" 909 | version = "0.2.2" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" 912 | dependencies = [ 913 | "ppv-lite86", 914 | "rand_core 0.5.1", 915 | ] 916 | 917 | [[package]] 918 | name = "rand_core" 919 | version = "0.3.1" 920 | source = "registry+https://github.com/rust-lang/crates.io-index" 921 | checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" 922 | dependencies = [ 923 | "rand_core 0.4.2", 924 | ] 925 | 926 | [[package]] 927 | name = "rand_core" 928 | version = "0.4.2" 929 | source = "registry+https://github.com/rust-lang/crates.io-index" 930 | checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" 931 | 932 | [[package]] 933 | name = "rand_core" 934 | version = "0.5.1" 935 | source = "registry+https://github.com/rust-lang/crates.io-index" 936 | checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" 937 | dependencies = [ 938 | "getrandom", 939 | ] 940 | 941 | [[package]] 942 | name = "rand_distr" 943 | version = "0.2.2" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "96977acbdd3a6576fb1d27391900035bf3863d4a16422973a409b488cf29ffb2" 946 | dependencies = [ 947 | "rand 0.7.3", 948 | ] 949 | 950 | [[package]] 951 | name = "rand_hc" 952 | version = "0.1.0" 953 | source = "registry+https://github.com/rust-lang/crates.io-index" 954 | checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4" 955 | dependencies = [ 956 | "rand_core 0.3.1", 957 | ] 958 | 959 | [[package]] 960 | name = "rand_hc" 961 | version = "0.2.0" 962 | source = "registry+https://github.com/rust-lang/crates.io-index" 963 | checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" 964 | dependencies = [ 965 | "rand_core 0.5.1", 966 | ] 967 | 968 | [[package]] 969 | name = "rand_isaac" 970 | version = "0.1.1" 971 | source = "registry+https://github.com/rust-lang/crates.io-index" 972 | checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08" 973 | dependencies = [ 974 | "rand_core 0.3.1", 975 | ] 976 | 977 | [[package]] 978 | name = "rand_jitter" 979 | version = "0.1.4" 980 | source = "registry+https://github.com/rust-lang/crates.io-index" 981 | checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b" 982 | dependencies = [ 983 | "libc", 984 | "rand_core 0.4.2", 985 | "winapi 0.3.9", 986 | ] 987 | 988 | [[package]] 989 | name = "rand_os" 990 | version = "0.1.3" 991 | source = "registry+https://github.com/rust-lang/crates.io-index" 992 | checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071" 993 | dependencies = [ 994 | "cloudabi", 995 | "fuchsia-cprng", 996 | "libc", 997 | "rand_core 0.4.2", 998 | "rdrand", 999 | "winapi 0.3.9", 1000 | ] 1001 | 1002 | [[package]] 1003 | name = "rand_pcg" 1004 | version = "0.1.2" 1005 | source = "registry+https://github.com/rust-lang/crates.io-index" 1006 | checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44" 1007 | dependencies = [ 1008 | "autocfg 0.1.7", 1009 | "rand_core 0.4.2", 1010 | ] 1011 | 1012 | [[package]] 1013 | name = "rand_xorshift" 1014 | version = "0.1.1" 1015 | source = "registry+https://github.com/rust-lang/crates.io-index" 1016 | checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c" 1017 | dependencies = [ 1018 | "rand_core 0.3.1", 1019 | ] 1020 | 1021 | [[package]] 1022 | name = "rdrand" 1023 | version = "0.4.0" 1024 | source = "registry+https://github.com/rust-lang/crates.io-index" 1025 | checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" 1026 | dependencies = [ 1027 | "rand_core 0.3.1", 1028 | ] 1029 | 1030 | [[package]] 1031 | name = "redox_syscall" 1032 | version = "0.1.56" 1033 | source = "registry+https://github.com/rust-lang/crates.io-index" 1034 | checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84" 1035 | 1036 | [[package]] 1037 | name = "redox_users" 1038 | version = "0.3.4" 1039 | source = "registry+https://github.com/rust-lang/crates.io-index" 1040 | checksum = "09b23093265f8d200fa7b4c2c76297f47e681c655f6f1285a8780d6a022f7431" 1041 | dependencies = [ 1042 | "getrandom", 1043 | "redox_syscall", 1044 | "rust-argon2", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "regex" 1049 | version = "1.3.9" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6" 1052 | dependencies = [ 1053 | "aho-corasick", 1054 | "memchr", 1055 | "regex-syntax", 1056 | "thread_local", 1057 | ] 1058 | 1059 | [[package]] 1060 | name = "regex-syntax" 1061 | version = "0.6.18" 1062 | source = "registry+https://github.com/rust-lang/crates.io-index" 1063 | checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8" 1064 | 1065 | [[package]] 1066 | name = "rusqlite" 1067 | version = "0.20.0" 1068 | source = "registry+https://github.com/rust-lang/crates.io-index" 1069 | checksum = "2a194373ef527035645a1bc21b10dc2125f73497e6e155771233eb187aedd051" 1070 | dependencies = [ 1071 | "bitflags", 1072 | "fallible-iterator", 1073 | "fallible-streaming-iterator", 1074 | "libsqlite3-sys", 1075 | "lru-cache", 1076 | "memchr", 1077 | "time", 1078 | ] 1079 | 1080 | [[package]] 1081 | name = "rust-argon2" 1082 | version = "0.7.0" 1083 | source = "registry+https://github.com/rust-lang/crates.io-index" 1084 | checksum = "2bc8af4bda8e1ff4932523b94d3dd20ee30a87232323eda55903ffd71d2fb017" 1085 | dependencies = [ 1086 | "base64", 1087 | "blake2b_simd", 1088 | "constant_time_eq", 1089 | "crossbeam-utils", 1090 | ] 1091 | 1092 | [[package]] 1093 | name = "rust-crypto" 1094 | version = "0.2.36" 1095 | source = "registry+https://github.com/rust-lang/crates.io-index" 1096 | checksum = "f76d05d3993fd5f4af9434e8e436db163a12a9d40e1a58a726f27a01dfd12a2a" 1097 | dependencies = [ 1098 | "gcc", 1099 | "libc", 1100 | "rand 0.3.23", 1101 | "rustc-serialize", 1102 | "time", 1103 | ] 1104 | 1105 | [[package]] 1106 | name = "rustc-demangle" 1107 | version = "0.1.16" 1108 | source = "registry+https://github.com/rust-lang/crates.io-index" 1109 | checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783" 1110 | 1111 | [[package]] 1112 | name = "rustc-serialize" 1113 | version = "0.3.24" 1114 | source = "registry+https://github.com/rust-lang/crates.io-index" 1115 | checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" 1116 | 1117 | [[package]] 1118 | name = "rustyline" 1119 | version = "6.2.0" 1120 | source = "registry+https://github.com/rust-lang/crates.io-index" 1121 | checksum = "3358c21cbbc1a751892528db4e1de4b7a2b6a73f001e215aaba97d712cfa9777" 1122 | dependencies = [ 1123 | "cfg-if", 1124 | "dirs-next", 1125 | "libc", 1126 | "log 0.4.8", 1127 | "memchr", 1128 | "nix", 1129 | "scopeguard", 1130 | "unicode-segmentation", 1131 | "unicode-width", 1132 | "utf8parse", 1133 | "winapi 0.3.9", 1134 | ] 1135 | 1136 | [[package]] 1137 | name = "same-file" 1138 | version = "1.0.6" 1139 | source = "registry+https://github.com/rust-lang/crates.io-index" 1140 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 1141 | dependencies = [ 1142 | "winapi-util", 1143 | ] 1144 | 1145 | [[package]] 1146 | name = "scopeguard" 1147 | version = "1.1.0" 1148 | source = "registry+https://github.com/rust-lang/crates.io-index" 1149 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1150 | 1151 | [[package]] 1152 | name = "secp256k1" 1153 | version = "0.15.5" 1154 | source = "registry+https://github.com/rust-lang/crates.io-index" 1155 | checksum = "4d311229f403d64002e9eed9964dfa5a0a0c1ac443344f7546bf48e916c6053a" 1156 | dependencies = [ 1157 | "cc", 1158 | "rand 0.6.5", 1159 | ] 1160 | 1161 | [[package]] 1162 | name = "serde" 1163 | version = "1.0.114" 1164 | source = "registry+https://github.com/rust-lang/crates.io-index" 1165 | checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3" 1166 | 1167 | [[package]] 1168 | name = "serde_cbor" 1169 | version = "0.10.2" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "f7081ed758ec726a6ed8ee7e92f5d3f6e6f8c3901b1f972e3a4a2f2599fad14f" 1172 | dependencies = [ 1173 | "byteorder", 1174 | "half", 1175 | "serde", 1176 | ] 1177 | 1178 | [[package]] 1179 | name = "serde_derive" 1180 | version = "1.0.114" 1181 | source = "registry+https://github.com/rust-lang/crates.io-index" 1182 | checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e" 1183 | dependencies = [ 1184 | "proc-macro2", 1185 | "quote", 1186 | "syn", 1187 | ] 1188 | 1189 | [[package]] 1190 | name = "simple_logger" 1191 | version = "0.5.0" 1192 | source = "registry+https://github.com/rust-lang/crates.io-index" 1193 | checksum = "2c0619150c42143a91bd79aa00b5f01f9b0a3ec38b1a59bc0b2f5aa24fc4c9bd" 1194 | dependencies = [ 1195 | "log 0.4.8", 1196 | "time", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "simplelog" 1201 | version = "0.6.0" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "aa9c948a5a26cd38340ddbeaa557a8c8a5ce4442408eb60453bee2bb3c84a3fb" 1204 | dependencies = [ 1205 | "chrono", 1206 | "log 0.4.8", 1207 | "term", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "siphasher" 1212 | version = "0.3.3" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "fa8f3741c7372e75519bd9346068370c9cdaabcc1f9599cbcf2a2719352286b7" 1215 | 1216 | [[package]] 1217 | name = "slab" 1218 | version = "0.4.2" 1219 | source = "registry+https://github.com/rust-lang/crates.io-index" 1220 | checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" 1221 | 1222 | [[package]] 1223 | name = "strsim" 1224 | version = "0.8.0" 1225 | source = "registry+https://github.com/rust-lang/crates.io-index" 1226 | checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" 1227 | 1228 | [[package]] 1229 | name = "syn" 1230 | version = "1.0.33" 1231 | source = "registry+https://github.com/rust-lang/crates.io-index" 1232 | checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd" 1233 | dependencies = [ 1234 | "proc-macro2", 1235 | "quote", 1236 | "unicode-xid", 1237 | ] 1238 | 1239 | [[package]] 1240 | name = "term" 1241 | version = "0.5.2" 1242 | source = "registry+https://github.com/rust-lang/crates.io-index" 1243 | checksum = "edd106a334b7657c10b7c540a0106114feadeb4dc314513e97df481d5d966f42" 1244 | dependencies = [ 1245 | "byteorder", 1246 | "dirs 1.0.5", 1247 | "winapi 0.3.9", 1248 | ] 1249 | 1250 | [[package]] 1251 | name = "termcolor" 1252 | version = "1.1.0" 1253 | source = "registry+https://github.com/rust-lang/crates.io-index" 1254 | checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f" 1255 | dependencies = [ 1256 | "winapi-util", 1257 | ] 1258 | 1259 | [[package]] 1260 | name = "textwrap" 1261 | version = "0.11.0" 1262 | source = "registry+https://github.com/rust-lang/crates.io-index" 1263 | checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" 1264 | dependencies = [ 1265 | "unicode-width", 1266 | ] 1267 | 1268 | [[package]] 1269 | name = "thread_local" 1270 | version = "1.0.1" 1271 | source = "registry+https://github.com/rust-lang/crates.io-index" 1272 | checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14" 1273 | dependencies = [ 1274 | "lazy_static", 1275 | ] 1276 | 1277 | [[package]] 1278 | name = "time" 1279 | version = "0.1.43" 1280 | source = "registry+https://github.com/rust-lang/crates.io-index" 1281 | checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" 1282 | dependencies = [ 1283 | "libc", 1284 | "winapi 0.3.9", 1285 | ] 1286 | 1287 | [[package]] 1288 | name = "toml" 1289 | version = "0.5.6" 1290 | source = "registry+https://github.com/rust-lang/crates.io-index" 1291 | checksum = "ffc92d160b1eef40665be3a05630d003936a3bc7da7421277846c2613e92c71a" 1292 | dependencies = [ 1293 | "serde", 1294 | ] 1295 | 1296 | [[package]] 1297 | name = "unicode-segmentation" 1298 | version = "1.6.0" 1299 | source = "registry+https://github.com/rust-lang/crates.io-index" 1300 | checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" 1301 | 1302 | [[package]] 1303 | name = "unicode-width" 1304 | version = "0.1.7" 1305 | source = "registry+https://github.com/rust-lang/crates.io-index" 1306 | checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" 1307 | 1308 | [[package]] 1309 | name = "unicode-xid" 1310 | version = "0.2.1" 1311 | source = "registry+https://github.com/rust-lang/crates.io-index" 1312 | checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" 1313 | 1314 | [[package]] 1315 | name = "unreachable" 1316 | version = "1.0.0" 1317 | source = "registry+https://github.com/rust-lang/crates.io-index" 1318 | checksum = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" 1319 | dependencies = [ 1320 | "void", 1321 | ] 1322 | 1323 | [[package]] 1324 | name = "utf8parse" 1325 | version = "0.2.0" 1326 | source = "registry+https://github.com/rust-lang/crates.io-index" 1327 | checksum = "936e4b492acfd135421d8dca4b1aa80a7bfc26e702ef3af710e0752684df5372" 1328 | 1329 | [[package]] 1330 | name = "vcpkg" 1331 | version = "0.2.10" 1332 | source = "registry+https://github.com/rust-lang/crates.io-index" 1333 | checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" 1334 | 1335 | [[package]] 1336 | name = "vec_map" 1337 | version = "0.8.2" 1338 | source = "registry+https://github.com/rust-lang/crates.io-index" 1339 | checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" 1340 | 1341 | [[package]] 1342 | name = "version_check" 1343 | version = "0.9.2" 1344 | source = "registry+https://github.com/rust-lang/crates.io-index" 1345 | checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" 1346 | 1347 | [[package]] 1348 | name = "void" 1349 | version = "1.0.2" 1350 | source = "registry+https://github.com/rust-lang/crates.io-index" 1351 | checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" 1352 | 1353 | [[package]] 1354 | name = "walkdir" 1355 | version = "2.3.1" 1356 | source = "registry+https://github.com/rust-lang/crates.io-index" 1357 | checksum = "777182bc735b6424e1a57516d35ed72cb8019d85c8c9bf536dccb3445c1a2f7d" 1358 | dependencies = [ 1359 | "same-file", 1360 | "winapi 0.3.9", 1361 | "winapi-util", 1362 | ] 1363 | 1364 | [[package]] 1365 | name = "wasi" 1366 | version = "0.9.0+wasi-snapshot-preview1" 1367 | source = "registry+https://github.com/rust-lang/crates.io-index" 1368 | checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" 1369 | 1370 | [[package]] 1371 | name = "winapi" 1372 | version = "0.2.8" 1373 | source = "registry+https://github.com/rust-lang/crates.io-index" 1374 | checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" 1375 | 1376 | [[package]] 1377 | name = "winapi" 1378 | version = "0.3.9" 1379 | source = "registry+https://github.com/rust-lang/crates.io-index" 1380 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1381 | dependencies = [ 1382 | "winapi-i686-pc-windows-gnu", 1383 | "winapi-x86_64-pc-windows-gnu", 1384 | ] 1385 | 1386 | [[package]] 1387 | name = "winapi-build" 1388 | version = "0.1.1" 1389 | source = "registry+https://github.com/rust-lang/crates.io-index" 1390 | checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" 1391 | 1392 | [[package]] 1393 | name = "winapi-i686-pc-windows-gnu" 1394 | version = "0.4.0" 1395 | source = "registry+https://github.com/rust-lang/crates.io-index" 1396 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1397 | 1398 | [[package]] 1399 | name = "winapi-util" 1400 | version = "0.1.5" 1401 | source = "registry+https://github.com/rust-lang/crates.io-index" 1402 | checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" 1403 | dependencies = [ 1404 | "winapi 0.3.9", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "winapi-x86_64-pc-windows-gnu" 1409 | version = "0.4.0" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1412 | 1413 | [[package]] 1414 | name = "ws2_32-sys" 1415 | version = "0.2.1" 1416 | source = "registry+https://github.com/rust-lang/crates.io-index" 1417 | checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e" 1418 | dependencies = [ 1419 | "winapi 0.2.8", 1420 | "winapi-build", 1421 | ] 1422 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bdk" 3 | version = "0.1.0" 4 | authors = ["BDK Team"] 5 | edition = "2018" 6 | 7 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 8 | 9 | [features] 10 | 11 | default = [] 12 | java = ["jni", "env_logger"] 13 | android = ["jni", "android_log"] 14 | 15 | [lib] 16 | name = "bdk" 17 | crate-type = ["lib","cdylib"] 18 | 19 | [dependencies] 20 | bitcoin-wallet="1.0" 21 | bitcoin={version= "0.21", features=["serde"]} 22 | bitcoin_hashes={version="0.7", features=["serde"]} 23 | byteorder = "1" 24 | clap = "2.33" 25 | dirs="2.0.2" 26 | #futures = { version = "0.3", features=["thread-pool"]} 27 | futures-preview = "=0.3.0-alpha.18" 28 | futures-timer = "0.3" 29 | hex="0.3" 30 | log="0.4" 31 | lru-cache = "0.1.2" 32 | murmel = { git = "https://github.com/rust-bitcoin/murmel" } 33 | once_cell = "1.3" 34 | rand = "0.7" 35 | rand_distr = "0.2" 36 | rusqlite={version="0.20", features=["bundled"]} 37 | serde = "1" 38 | serde_derive = "1" 39 | serde_cbor = "0.10" 40 | simplelog="0.6" 41 | siphasher="0.3" 42 | toml="0.5" 43 | 44 | ## optional 45 | android_log = { version = "0.1.3", optional = true } 46 | env_logger = { version = "0.7", optional = true } 47 | jni = { version = "0.13.1", optional = true } 48 | 49 | [profile.release] 50 | lto = true 51 | 52 | [dev-dependencies] 53 | chrono = "0.4" 54 | clap = "2" 55 | env_logger = "0.7" 56 | fern = "0.6" 57 | rustyline = "6.2.0" -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | The source code for the BDK project is based on code from the defiads project written by Tamas Blummer in 2019. 2 | The original source code for the defiads project can be found at: https://github.com/defiads/defiads -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Bitcoin Development Kit 2 | ======================= 3 | 4 | This library combines rust-bitcoin, murmel, and rust-wallet libraries to provide basic functionality for interacting with the 5 | bitcoin network. The library can be used in an android mobile app by including the optional jni module. 6 | 7 | ## Setup and Build 8 | 9 | 1. [Install rustup](https://www.rust-lang.org/learn/get-started) 10 | 11 | 1. Install rust targets (if not already installed) 12 | 13 | Android: 14 | ``` 15 | rustup target add x86_64-apple-darwin x86_64-unknown-linux-gnu x86_64-linux-android aarch64-linux-android armv7-linux-androideabi i686-linux-android 16 | ``` 17 | 18 | iOS: 19 | ``` 20 | rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios 21 | ``` 22 | 23 | 1. Install [cargo-ndk](https://docs.rs/crate/cargo-ndk/0.6.1) cargo extension: 24 | 25 | Android: 26 | ``` 27 | cargo install cargo-ndk 28 | ``` 29 | 30 | iOS: 31 | ``` 32 | cargo install cargo-lipo 33 | cargo install cbindgen 34 | ``` 35 | 36 | 1. Install Android Studio and NDK 37 | 38 | Open Android Studio -> Tools -> SDK Manager -> SDK Tools -> install "NDK (Side by side)" 39 | 40 | 1. If not already set, set environment variables needed to build rust based library files and 41 | to run local unit tests. Better yet, add these to your `.bash_profile`. 42 | 43 | Android (OSX): 44 | ``` 45 | export ANDROID_HOME=$HOME/Library/Android/sdk 46 | export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/ 47 | ``` 48 | 49 | Android (Linux): 50 | ``` 51 | export ANDROID_HOME=$HOME/Android/Sdk 52 | export ANDROID_NDK_HOME=$ANDROID_HOME/ndk/ 53 | ``` 54 | 55 | iOS (OSX): 56 | ``` 57 | ## if this fails: 58 | xcrun -k --sdk iphoneos --show-sdk-path 59 | ## run this: 60 | sudo xcode-select --switch /Applications/Xcode.app 61 | ``` 62 | 63 | 1. Set environment variables needed to build Bitcoin C++ library files. This will be unnecessary after [fix](https://github.com/bbqsrc/cargo-ndk/pull/7) to [cargo-ndk](https://docs.rs/crate/cargo-ndk/0.6.1). 64 | 65 | Android (OSX) 66 | ``` 67 | export CXX_x86_64_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android30-clang++ 68 | export CXX_aarch64_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android30-clang++ 69 | export CXX_armv7_linux_androideabi=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi30-clang++ 70 | export CXX_i686_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android30-clang++ 71 | ``` 72 | 73 | Android (Linux) 74 | ``` 75 | export CXX_x86_64_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/x86_64-linux-android30-clang++ 76 | export CXX_aarch64_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android30-clang++ 77 | export CXX_armv7_linux_androideabi=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/armv7a-linux-androideabi30-clang++ 78 | export CXX_i686_linux_android=$ANDROID_NDK_HOME/toolchains/llvm/prebuilt/linux-x86_64/bin/i686-linux-android30-clang++ 79 | ``` 80 | 81 | 82 | 1. Build Rust library files for all target platform OS architectures: 83 | 84 | ``` 85 | ./build-lib.sh 86 | ``` 87 | 88 | ## REGTEST Testing 89 | 90 | The 🍣 [Nigiri CLI](https://github.com/vulpemventures/nigiri) tool can be used to spin-up a complete `regtest` 91 | development environment that includes a `bitcoin` node, a Blockstream `electrs` explorer and the 92 | [`esplora`](https://github.com/blockstream/esplora) web-app to visualize blocks and transactions in the browser. 93 | 94 | First install [Docker-Desktop](https://www.docker.com/products/docker-desktop) on your machine. Then see the 95 | [Nigiri CLI README.md](https://github.com/vulpemventures/nigiri/blob/master/README.md) file to install via prebuilt binaries or from the 96 | project source. 97 | 98 | -------------------------------------------------------------------------------- /build-lib.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | OS=`uname` 4 | if [ "$OS" = "Darwin" ] 5 | then 6 | echo "building apple darwin x86_64 lib" 7 | cargo build --target x86_64-apple-darwin --release --features "java" 8 | elif [ "$OS" = "Linux" ] 9 | then 10 | echo "building linux x86_64 lib" 11 | cargo build --target x86_64-unknown-linux-gnu --release --features "java" 12 | fi 13 | 14 | echo "building android x86_64 lib" 15 | cargo ndk --platform 30 --target x86_64-linux-android build --release --features "android" 16 | 17 | echo "building android aarch64 lib" 18 | cargo ndk --platform 30 --target aarch64-linux-android build --release --features "android" 19 | 20 | echo "building android armv7 lib" 21 | cargo ndk --platform 30 --target armv7-linux-androideabi build --release --features "android" 22 | 23 | echo "building android i686 lib" 24 | cargo ndk --platform 30 --target i686-linux-android build --release --features "android" 25 | echo built! -------------------------------------------------------------------------------- /examples/wallet/README.md: -------------------------------------------------------------------------------- 1 | Example Wallet 2 | ============== 3 | 4 | This example wallet can be used to test the bdk library. 5 | 6 | ## Build and Run with Testnet 7 | 8 | 1. Build wallet 9 | 10 | ``` 11 | cargo build --examples 12 | ``` 13 | 14 | 1. Get wallet cli help 15 | 16 | ``` 17 | cargo run --example wallet -- -h 18 | ``` 19 | 20 | 1. Create new wallet 21 | 22 | ``` 23 | cargo run --example wallet -- -p testpass 24 | ``` 25 | 26 | 1. Check log file to see what's going on 27 | 28 | ``` 29 | tail -f testnet/wallet.log 30 | ``` 31 | 32 | 1. Once wallet is started, use the `help` command from >> prompt to get a list of sub-commands. 33 | 34 | 1. delete testnet data directory if no longer needed 35 | 36 | ``` 37 | rm -rf testnet 38 | ``` 39 | 40 | ## Regtest Testing 41 | 42 | 1. The 🍣 [Nigiri CLI](https://github.com/vulpemventures/nigiri) tool can be used to spin-up a complete `regtest` 43 | development environment that includes a `bitcoin` node, a Blockstream `electrs` explorer and the 44 | [`esplora`](https://github.com/blockstream/esplora) web-app to visualize blocks and transactions in the browser. 45 | 46 | First install [Docker-Desktop](https://www.docker.com/products/docker-desktop) on your machine. Then see the 47 | [Nigiri CLI README.md](https://github.com/vulpemventures/nigiri/blob/master/README.md) file to install via prebuilt 48 | binaries or from the project source. 49 | 50 | 1. Create new regtest wallet 51 | 52 | ``` 53 | cargo run --example wallet -- -n regtest -p testpass -a 127.0.0.1:18432 54 | ``` 55 | 56 | 1. Use the Nigiri Chopsticks API to create a new block and send the reward to `` 57 | 58 | ``` 59 | curl -X POST --data '{"address": ""}' http://localhost:3000/faucet 60 | ``` 61 | 62 | 1. Or you can connect to the Nigiri created bitcoind and use the containers bitcoin-cli to generate multiple bocks and 63 | send the reward to `` 64 | 65 | ```$xslt 66 | docker exec -it resources_bitcoin_1 bitcoin-cli -regtest -rpcport=19001 -rpcuser=admin1 -rpcpassword=123 generatetoaddress 100 67 | ``` 68 | 69 | 1. Or if you have bitcoin-cli installed on your local OS outside the Nigiri container you can use it to tell the Nigiri 70 | created bitcoind to generate multiple blocks and send the rewards to `` 71 | 72 | ``` 73 | bitcoin-cli -regtest -rpcport=18433 -rpcuser=admin1 -rpcpassword=123 generatetoaddress 100 74 | ``` 75 | 76 | 1. Check wallet log file to see what's going on 77 | 78 | ``` 79 | tail -f regtest/wallet.log 80 | ``` 81 | 82 | 1. delete the ./regtest data directory when no longer needed 83 | 84 | ``` 85 | rm -rf regtest 86 | ``` -------------------------------------------------------------------------------- /examples/wallet/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | extern crate bdk; 18 | #[macro_use] 19 | extern crate clap; 20 | 21 | use std::cmp::max; 22 | use std::convert::TryFrom; 23 | use std::net::{AddrParseError, SocketAddr}; 24 | use std::path::{PathBuf, Path}; 25 | use std::str::FromStr; 26 | use std::thread; 27 | 28 | use bitcoin::{Address, Network}; 29 | use bitcoin_hashes::core::time::Duration; 30 | use clap::App; 31 | use futures::StreamExt; 32 | use log::{debug, error, info, warn, LevelFilter}; 33 | use rustyline::Editor; 34 | use rustyline::error::ReadlineError; 35 | 36 | use bdk::api::{balance, deposit_addr, init_config, start, stop, update_config, withdraw}; 37 | use bdk::api; 38 | use bdk::config::Config; 39 | use bdk::error::Error; 40 | use std::process::ChildStderr; 41 | use chrono::Local; 42 | 43 | mod ui; 44 | 45 | const PASSPHRASE: &str = "correct horse battery staple"; 46 | const PD_PASSPHRASE_1: &str = "test123"; 47 | 48 | fn main() -> Result<(), Error> { 49 | let cli = ui::cli().get_matches(); 50 | let log_level = cli.value_of("logging").unwrap_or("info"); 51 | 52 | let connections = cli.value_of("connections").map(|c| c.parse::().unwrap()).unwrap_or(5); 53 | let directory = cli.value_of("directory").unwrap_or("."); 54 | let discovery = cli.value_of("discovery").map(|d| d == "on").unwrap_or(true); 55 | let network = cli.value_of("network").unwrap_or("testnet"); 56 | let password = cli.value_of("password").expect("password is required"); 57 | let peers = cli.values_of("peers").map(|a| a.collect::>()).unwrap_or(Vec::new()); 58 | 59 | let work_dir: PathBuf = PathBuf::from(directory); 60 | let mut log_file = work_dir.clone(); 61 | log_file.push(network); 62 | log_file.push("wallet.log"); 63 | let log_file = log_file.as_path(); 64 | let log_level = LevelFilter::from_str(log_level).unwrap(); 65 | 66 | setup_logger(log_file, log_level); 67 | 68 | let mut history_file = work_dir.clone(); 69 | history_file.push(network); 70 | history_file.push("history.txt"); 71 | let history_file = history_file.as_path(); 72 | info!("history file: {:?}", history_file); 73 | 74 | let network = network.parse::().unwrap(); 75 | 76 | println!("logging level: {}", log_level); 77 | println!("working directory: {:?}", work_dir); 78 | println!("discovery: {:?}", discovery); 79 | println!("network: {}", network); 80 | println!("peers: {:?}", peers); 81 | 82 | let init_result = api::init_config(work_dir.clone(), network, password, None); 83 | 84 | match init_result { 85 | Ok(Some(init_result)) => { 86 | println!("created new wallet, seed words: {}", init_result.mnemonic_words); 87 | println!("first deposit address: {}", init_result.deposit_address); 88 | } 89 | Ok(None) => { 90 | println!("wallet exists"); 91 | } 92 | Err(e) => { 93 | println!("config error: {:?}", e); 94 | } 95 | }; 96 | 97 | let peers = peers.into_iter() 98 | .map(|p| SocketAddr::from_str(p)) 99 | .collect::, AddrParseError>>()?; 100 | 101 | let connections = max(peers.len(), connections); 102 | 103 | println!("peer connections: {}", connections); 104 | 105 | let config = api::update_config(work_dir.clone(), network, peers, connections, discovery).unwrap(); 106 | debug!("config: {:?}", config); 107 | 108 | let mut rl = Editor::<()>::new(); 109 | 110 | if rl.load_history(history_file).is_err() { 111 | println!("No previous history."); 112 | } 113 | 114 | let p2p_thread = thread::spawn(move || { 115 | println!("starting p2p thread"); 116 | api::start(work_dir.clone(), network, false); 117 | }); 118 | 119 | loop { 120 | let readline = rl.readline(">> "); 121 | match readline { 122 | Ok(line) => { 123 | let split_line = line.split(' '); 124 | let repl_matches = ui::repl().get_matches_from_safe(split_line); 125 | if repl_matches.is_ok() { 126 | if let (c, Some(a)) = repl_matches.unwrap().subcommand() { 127 | debug!("command: {}, args: {:?}", c, a); 128 | rl.add_history_entry(line.as_str()); 129 | match c { 130 | "stop" => { 131 | break; 132 | } 133 | "balance" => { 134 | let balance_amt = api::balance().unwrap(); 135 | println!("balance: {}, confirmed: {}", balance_amt.balance, balance_amt.confirmed); 136 | } 137 | "deposit" => { 138 | let deposit_addr = api::deposit_addr(); 139 | println!("deposit address: {}", deposit_addr); 140 | } 141 | "withdraw" => { 142 | // passphrase: String, address: Address, fee_per_vbyte: u64, amount: Option 143 | let password = a.value_of("password").unwrap().to_string(); 144 | let address = Address::from_str(a.value_of("address").unwrap()).unwrap(); 145 | let fee = a.value_of("fee").unwrap().parse::().unwrap(); 146 | let amount = Some(a.value_of("amount").unwrap().parse::().unwrap()); 147 | let withdraw_tx = api::withdraw(password, address, fee, amount).unwrap(); 148 | println!("withdraw tx id: {}, fee: {}", withdraw_tx.txid, withdraw_tx.fee); 149 | } 150 | _ => { 151 | println!("command '{}' is not implemented", c); 152 | } 153 | } 154 | } 155 | } else { 156 | let err = repl_matches.err().unwrap(); 157 | println!("{}", err); 158 | } 159 | } 160 | Err(ReadlineError::Interrupted) => { 161 | println!("CTRL-C"); 162 | break; 163 | } 164 | Err(ReadlineError::Eof) => { 165 | println!("CTRL-D"); 166 | break; 167 | } 168 | Err(err) => { 169 | println!("Error: {:?}", err); 170 | break; 171 | } 172 | } 173 | } 174 | rl.save_history(history_file).unwrap(); 175 | println!("stopping"); 176 | api::stop(); 177 | p2p_thread.join().unwrap(); 178 | println!("stopped"); 179 | Ok(()) 180 | } 181 | 182 | fn setup_logger(file: &Path, level: LevelFilter) -> Result<(), fern::InitError> { 183 | fern::Dispatch::new() 184 | .format(|out, message, record| { 185 | out.finish(format_args!( 186 | "{}[{}][{}] {}", 187 | chrono::Local::now().format("[%Y-%m-%d][%H:%M:%S]"), 188 | record.target(), 189 | record.level(), 190 | message 191 | )) 192 | }) 193 | .level(level) 194 | .chain(fern::log_file(file)?) 195 | .apply()?; 196 | Ok(()) 197 | } -------------------------------------------------------------------------------- /examples/wallet/ui.rs: -------------------------------------------------------------------------------- 1 | use clap::{App, Arg, SubCommand, AppSettings}; 2 | 3 | pub fn cli<'a, 'b>() -> App<'a, 'b> { 4 | App::new("wallet") 5 | .version(option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")) 6 | .author(option_env!("CARGO_PKG_AUTHORS").unwrap_or("")) 7 | .about("Example bdk based wallet for testing") 8 | .arg(Arg::with_name("connections") 9 | .short("c") 10 | .long("connections") 11 | .value_name("NUMBER") 12 | .help("number of peer connections") 13 | .required(true) 14 | .takes_value(true) 15 | .default_value("5") 16 | ) 17 | .arg(Arg::with_name("data") 18 | .short("d") 19 | .long("data") 20 | .value_name("DIRECTORY") 21 | .help("data directory") 22 | .required(false) 23 | .takes_value(true) 24 | .default_value(".") 25 | ) 26 | .arg(Arg::with_name("discovery") 27 | .short("i") 28 | .long("discovery") 29 | .help("turn peer discovery on or off") 30 | .required(false) 31 | .takes_value(true) 32 | .default_value("on") 33 | .possible_values(&["on", "off"]) 34 | ) 35 | .arg(Arg::with_name("logging") 36 | .short("l") 37 | .long("log") 38 | .value_name("LEVEL") 39 | .help("logging level") 40 | .required(false) 41 | .takes_value(true) 42 | .default_value("info") 43 | .possible_values(&["debug", "info", "warn", "error"]) 44 | ) 45 | .arg(Arg::with_name("network") 46 | .short("n") 47 | .long("net") 48 | .value_name("NETWORK") 49 | .help("bitcoin network") 50 | .required(false) 51 | .takes_value(true) 52 | .default_value("testnet") 53 | .possible_values(&["regtest", "testnet"]) 54 | ) 55 | .arg(Arg::with_name("password") 56 | .short("p") 57 | .long("password") 58 | .value_name("PASSWORD") 59 | .help("wallet password") 60 | .required(true) 61 | .takes_value(true) 62 | ) 63 | .arg(Arg::with_name("peers") 64 | .short("a") 65 | .long("peers") 66 | .value_name("IP_ADDRESS") 67 | .help("ip addresses of peer nodes, eg. 127.0.0.1:9333") 68 | .required(false) 69 | .takes_value(true) 70 | .multiple(true) 71 | ) 72 | } 73 | 74 | pub fn repl<'a, 'b>() -> App<'a, 'b> { 75 | App::new("wallet").version(option_env!("CARGO_PKG_VERSION").unwrap_or("unknown")) 76 | .author(option_env!("CARGO_PKG_AUTHORS").unwrap_or("")) 77 | .about("Example bdk based wallet for testing") 78 | .settings(&[AppSettings::NoBinaryName, AppSettings::SubcommandRequiredElseHelp, 79 | AppSettings::VersionlessSubcommands]) 80 | .subcommands(vec![SubCommand::with_name("stop").about("Stop wallet"), 81 | SubCommand::with_name("balance").about("Display balances (in sats)"), 82 | SubCommand::with_name("deposit").about("Display deposit address"), 83 | SubCommand::with_name("withdraw").about("Withdraw sats to address") 84 | .arg(Arg::with_name("password") 85 | .short("p") 86 | .long("password") 87 | .value_name("PASSWORD") 88 | .help("wallet password") 89 | .required(true) 90 | .takes_value(true)) 91 | .arg(Arg::with_name("address") 92 | .short("d") 93 | .long("destination") 94 | .value_name("ADDRESS") 95 | .help("destination address") 96 | .required(true) 97 | .takes_value(true)) 98 | .arg(Arg::with_name("fee") 99 | .short("f") 100 | .long("fee") 101 | .value_name("SATS") 102 | .help("sats per vbyte") 103 | .required(true) 104 | .takes_value(true)) 105 | .arg(Arg::with_name("amount") 106 | .short("a") 107 | .long("amount") 108 | .value_name("SATS") 109 | .help("amount of sats to withdraw") 110 | .required(true) 111 | .takes_value(true))] 112 | ) 113 | } -------------------------------------------------------------------------------- /src/api.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::{fs, time}; 19 | use std::net::SocketAddr; 20 | use std::path::{Path, PathBuf}; 21 | use std::sync::{Arc, Mutex, RwLock}; 22 | 23 | use bitcoin::{Address, BitcoinHash, Network}; 24 | use bitcoin::hashes::core::str::FromStr; 25 | use bitcoin::util::bip32::ExtendedPubKey; 26 | use bitcoin_hashes::sha256d; 27 | use bitcoin_wallet::account::MasterAccount; 28 | use futures::{executor::ThreadPoolBuilder}; 29 | use futures_timer::Delay; 30 | use log::{info, warn}; 31 | use log::{debug, error}; 32 | use murmel::chaindb::ChainDB; 33 | use once_cell::sync::Lazy; 34 | 35 | use crate::{config, db}; 36 | use crate::config::Config; 37 | use crate::db::DB; 38 | use crate::error::Error; 39 | use crate::p2p_bitcoin::{ChainDBTrunk, P2PBitcoin}; 40 | use crate::store::{ContentStore, SharedContentStore}; 41 | use crate::trunk::Trunk; 42 | use crate::wallet::{KEY_LOOK_AHEAD, Wallet}; 43 | 44 | const CONFIG_FILE_NAME: &str = "bdk.cfg"; 45 | 46 | static CONTENT_STORE: Lazy>>> = Lazy::new(|| Arc::new(RwLock::new(None::))); 47 | 48 | // load config 49 | 50 | pub fn load_config(work_dir: PathBuf, network: Network) -> Result { 51 | let mut file_path = PathBuf::from(work_dir); 52 | file_path.push(network.to_string()); 53 | file_path.push(CONFIG_FILE_NAME); 54 | 55 | config::load(&file_path) 56 | } 57 | 58 | // remove config 59 | 60 | pub fn remove_config(work_dir: PathBuf, network: Network) -> Result { 61 | let mut config_path = PathBuf::from(work_dir); 62 | config_path.push(network.to_string()); 63 | let mut file_path = config_path.clone(); 64 | file_path.push(CONFIG_FILE_NAME); 65 | 66 | let config = config::load(&file_path)?; 67 | config::remove(&config_path)?; 68 | Ok(config) 69 | } 70 | 71 | // update config 72 | 73 | pub fn update_config(work_dir: PathBuf, network: Network, bitcoin_peers: Vec, 74 | bitcoin_connections: usize, bitcoin_discovery: bool) -> Result { 75 | let mut config_path = PathBuf::from(work_dir); 76 | config_path.push(network.to_string()); 77 | let mut file_path = config_path.clone(); 78 | file_path.push(CONFIG_FILE_NAME); 79 | 80 | let config = config::load(&file_path)?; 81 | let updated_config = config.update(bitcoin_peers, bitcoin_connections, bitcoin_discovery); 82 | config::save(&config_path, &file_path, &updated_config)?; 83 | Ok(updated_config) 84 | } 85 | 86 | // init config 87 | 88 | pub struct InitResult { 89 | pub mnemonic_words: String, 90 | pub deposit_address: Address, 91 | } 92 | 93 | impl InitResult { 94 | fn new(mnemonic_words: String, deposit_address: Address) -> InitResult { 95 | InitResult { 96 | mnemonic_words, 97 | deposit_address, 98 | } 99 | } 100 | } 101 | 102 | pub fn init_config(work_dir: PathBuf, network: Network, passphrase: &str, pd_passphrase: Option<&str>) -> Result, Error> { 103 | let mut config_path = PathBuf::from(work_dir); 104 | config_path.push(network.to_string()); 105 | fs::create_dir_all(&config_path).expect(format!("unable to create config_path: {}", &config_path.to_str().unwrap()).as_str()); 106 | 107 | let mut file_path = config_path.clone(); 108 | file_path.push(CONFIG_FILE_NAME); 109 | 110 | if let Ok(_config) = config::load(&file_path) { 111 | // do not init if a config already exists, return none 112 | Ok(Option::None) 113 | } else { 114 | // create new wallet 115 | let (mnemonic_words, deposit_address, wallet) = Wallet::new(network, passphrase, pd_passphrase); 116 | let mnemonic_words = mnemonic_words.to_string(); 117 | let deposit_address = deposit_address; 118 | 119 | let encryptedwalletkey = hex::encode(wallet.encrypted().as_slice()); 120 | let keyroot = wallet.master_public().to_string(); 121 | let lookahead = KEY_LOOK_AHEAD; 122 | let birth = wallet.birth(); 123 | 124 | // init database 125 | db::init(&config_path, &wallet.coins, &wallet.master); 126 | 127 | // save config 128 | let config = Config::new(encryptedwalletkey.as_str(), 129 | keyroot.as_str(), lookahead, birth, network); 130 | config::save(&config_path, &file_path, &config)?; 131 | 132 | Ok(Option::from(InitResult::new(mnemonic_words, deposit_address))) 133 | } 134 | } 135 | 136 | pub fn start(work_dir: PathBuf, network: Network, rescan: bool) -> Result<(), Error> { 137 | let p2p_bitcoin; 138 | let content_store; 139 | 140 | match CONTENT_STORE.write() { 141 | Err(e) => { 142 | error!("{:?}", e); 143 | return Ok(()); 144 | } 145 | Ok(mut cs) => { 146 | if cs.is_some() { 147 | debug!("content store exists"); 148 | return Ok(()); 149 | } else { 150 | debug!("content store not initialized"); 151 | 152 | let mut config_path = PathBuf::from(work_dir); 153 | config_path.push(network.to_string()); 154 | 155 | let mut config_file_path = config_path.clone(); 156 | config_file_path.push(CONFIG_FILE_NAME); 157 | 158 | info!("config file path: {}", &config_file_path.to_str().unwrap()); 159 | let config = config::load(&config_file_path).expect("can not open config file"); 160 | 161 | let mut chain_file_path = config_path.clone(); 162 | chain_file_path.push("bdk.chain"); 163 | 164 | let mut chain_db = ChainDB::new(chain_file_path.as_path(), network).expect("can not open chain db"); 165 | chain_db.init().expect("can not initialize db"); 166 | let chain_db = Arc::new(RwLock::new(chain_db)); 167 | 168 | let db = open_db(&config_path); 169 | let db = Arc::new(Mutex::new(db)); 170 | 171 | // get master account 172 | let mut bitcoin_wallet; 173 | let mut master_account = MasterAccount::from_encrypted( 174 | hex::decode(config.encryptedwalletkey).expect("encryptedwalletkey is not hex").as_slice(), 175 | ExtendedPubKey::from_str(config.keyroot.as_str()).expect("keyroot is malformed"), 176 | config.birth, 177 | ); 178 | 179 | // load wallet from master account 180 | { 181 | let mut db = db.lock().unwrap(); 182 | let mut tx = db.transaction(); 183 | let account = tx.read_account(0, 0, network, config.lookahead).expect("can not read account 0/0"); 184 | master_account.add_account(account); 185 | let account = tx.read_account(0, 1, network, config.lookahead).expect("can not read account 0/1"); 186 | master_account.add_account(account); 187 | let account = tx.read_account(1, 0, network, 0).expect("can not read account 1/0"); 188 | master_account.add_account(account); 189 | let coins = tx.read_coins(&mut master_account).expect("can not read coins"); 190 | bitcoin_wallet = Wallet::from_storage(coins, master_account); 191 | } 192 | 193 | // rescan chain if requested 194 | if rescan { 195 | let chain_db = chain_db.read().unwrap(); 196 | let mut after = None; 197 | for cached_header in chain_db.iter_trunk_rev(None) { 198 | if (cached_header.stored.header.time as u64) < config.birth { 199 | after = Some(cached_header.bitcoin_hash()); 200 | break; 201 | } 202 | } 203 | if let Some(after) = after { 204 | info!("Re-scanning after block {}", &after); 205 | let mut db = db.lock().unwrap(); 206 | let mut tx = db.transaction(); 207 | tx.rescan(&after).expect("can not re-scan"); 208 | tx.commit(); 209 | bitcoin_wallet.rescan(); 210 | } 211 | } 212 | 213 | let trunk = Arc::new(ChainDBTrunk { chaindb: chain_db.clone() }); 214 | info!("Wallet balance: {} satoshis {} available", bitcoin_wallet.balance(), bitcoin_wallet.available_balance(trunk.len(), |h| trunk.get_height(h))); 215 | 216 | content_store = 217 | Arc::new(RwLock::new( 218 | ContentStore::new(db.clone(), trunk, bitcoin_wallet).expect("can not initialize content store"))); 219 | 220 | *cs = Option::Some(content_store.clone()); 221 | 222 | p2p_bitcoin = P2PBitcoin::new(config.network, config.bitcoin_connections, config.bitcoin_peers, config.bitcoin_discovery, chain_db.clone(), db.clone(), 223 | content_store.clone(), config.birth); 224 | } 225 | } 226 | } 227 | 228 | let mut thread_pool = ThreadPoolBuilder::new().name_prefix("futures ").create().expect("can not start thread pool"); 229 | p2p_bitcoin.start(&mut thread_pool); 230 | thread_pool.run(check_stopped(content_store)); 231 | 232 | { 233 | let mut cs = CONTENT_STORE.write().unwrap(); 234 | *cs = Option::None; 235 | debug!("content store set to None"); 236 | p2p_bitcoin.shutdown() 237 | } 238 | Ok(()) 239 | } 240 | 241 | async fn check_stopped(store: Arc>) -> () { 242 | info!("start check_stopped"); 243 | let mut stopped = false; 244 | while !stopped { 245 | Delay::new(time::Duration::from_millis(100)).await.unwrap(); 246 | stopped = store.read().unwrap().get_stopped(); 247 | } 248 | warn!("stopped"); 249 | } 250 | 251 | pub fn stop() -> () { 252 | info!("stopping"); 253 | let store = CONTENT_STORE.read().unwrap().as_ref().unwrap().clone(); 254 | store.write().unwrap().set_stopped(true); 255 | } 256 | 257 | #[derive(Debug, Clone)] 258 | pub struct BalanceAmt { pub balance: u64, pub confirmed: u64 } 259 | 260 | impl BalanceAmt { 261 | fn new(balance: u64, confirmed: u64) -> BalanceAmt { 262 | BalanceAmt { balance, confirmed } 263 | } 264 | } 265 | 266 | pub fn balance() -> Result { 267 | let store = CONTENT_STORE.read().unwrap().as_ref().unwrap().clone(); 268 | let bal_vec = store.read().unwrap().balance(); 269 | Ok(BalanceAmt::new(bal_vec[0], bal_vec[1])) 270 | } 271 | 272 | pub fn deposit_addr() -> Address { 273 | let store = CONTENT_STORE.read().unwrap().as_ref().unwrap().clone(); 274 | let addr = store.write().unwrap().deposit_address(); 275 | addr 276 | } 277 | 278 | #[derive(Debug, Clone)] 279 | pub struct WithdrawTx { pub txid: sha256d::Hash, pub fee: u64 } 280 | 281 | impl WithdrawTx { 282 | fn new(txid: sha256d::Hash, fee: u64) -> WithdrawTx { 283 | WithdrawTx { txid, fee } 284 | } 285 | } 286 | 287 | pub fn withdraw(passphrase: String, address: Address, fee_per_vbyte: u64, amount: Option) -> Result { 288 | let store = CONTENT_STORE.read().unwrap().as_ref().unwrap().clone(); 289 | let withdraw = store.write().unwrap().withdraw(passphrase, address, fee_per_vbyte, amount); 290 | match withdraw { 291 | Ok((t, f)) => { 292 | Ok(WithdrawTx::new(t.txid(), f)) 293 | } 294 | Err(e) => { 295 | Err(e) 296 | } 297 | } 298 | } 299 | 300 | fn open_db(config_path: &Path) -> DB { 301 | let mut db_path = PathBuf::from(config_path); 302 | const DB_FILE_NAME: &str = "bdk.db"; 303 | db_path.push(DB_FILE_NAME); 304 | let db = DB::new(db_path.as_path()).expect(format!("Can't open DB {}", db_path.to_str().expect("can't get db_path")).as_str()); 305 | db 306 | } -------------------------------------------------------------------------------- /src/blockdownload.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | use std::{ 18 | collections::VecDeque, 19 | sync::mpsc, 20 | thread, 21 | time::Duration, 22 | }; 23 | 24 | use bitcoin::{BitcoinHash, Block, blockdata::{ 25 | block::BlockHeader, 26 | }, network::{ 27 | message::NetworkMessage, 28 | message_blockdata::{GetHeadersMessage, Inventory, InvType}, 29 | }}; 30 | use bitcoin_hashes::sha256d; 31 | use log::{debug, info, trace}; 32 | use murmel::chaindb::SharedChainDB; 33 | use murmel::downstream::SharedDownstream; 34 | use murmel::error::Error; 35 | use murmel::p2p::{P2PControl, P2PControlSender, PeerId, PeerMessage, PeerMessageReceiver, PeerMessageSender, SERVICE_BLOCKS}; 36 | use murmel::timeout::{ExpectedReply, SharedTimeout}; 37 | 38 | pub struct BlockDownload { 39 | p2p: P2PControlSender, 40 | chaindb: SharedChainDB, 41 | timeout: SharedTimeout, 42 | downstream: SharedDownstream, 43 | blocks_wanted: VecDeque<(sha256d::Hash, u32)>, 44 | blocks_asked: VecDeque<(sha256d::Hash, u32)>, 45 | block_download_peer: Option, 46 | birth: u64 47 | } 48 | 49 | impl BlockDownload { 50 | pub fn new(chaindb: SharedChainDB, p2p: P2PControlSender, timeout: SharedTimeout, downstream: SharedDownstream, processed_block: Option, birth: u64) -> PeerMessageSender { 51 | let (sender, receiver) = mpsc::sync_channel(p2p.back_pressure); 52 | 53 | let mut blocks_wanted = VecDeque::new(); 54 | { 55 | let chaindb = chaindb.read().unwrap(); 56 | if let Some(mut h) = chaindb.header_tip() { 57 | if (h.stored.header.time as u64) > birth { 58 | let stop_at = processed_block.unwrap_or_default(); 59 | let mut block_hash = h.bitcoin_hash(); 60 | while block_hash != stop_at { 61 | blocks_wanted.push_front((block_hash, h.stored.height)); 62 | block_hash = h.stored.header.prev_blockhash.clone(); 63 | if block_hash != sha256d::Hash::default() { 64 | h = chaindb.get_header(&block_hash).expect("inconsistent header cache"); 65 | if (h.stored.header.time as u64) < birth { 66 | break; 67 | } 68 | } 69 | } 70 | } 71 | } 72 | } 73 | 74 | let mut headerdownload = BlockDownload { chaindb, p2p, timeout, downstream: downstream, 75 | blocks_wanted, blocks_asked: VecDeque::new(), block_download_peer: None, birth }; 76 | 77 | thread::Builder::new().name("header download".to_string()).spawn(move || { headerdownload.run(receiver) }).unwrap(); 78 | 79 | PeerMessageSender::new(sender) 80 | } 81 | 82 | fn run(&mut self, receiver: PeerMessageReceiver) { 83 | loop { 84 | while let Ok(msg) = receiver.recv_timeout(Duration::from_millis(1000)) { 85 | match msg { 86 | PeerMessage::Connected(pid,_) => { 87 | if self.is_serving_blocks(pid) { 88 | trace!("serving blocks peer={}", pid); 89 | self.get_headers(pid); 90 | if self.block_download_peer.is_none() { 91 | debug!("new block download peer={}", pid); 92 | self.block_download_peer = Some(pid); 93 | } 94 | } 95 | } 96 | PeerMessage::Disconnected(pid,_) => { 97 | if self.block_download_peer.is_some() { 98 | if pid == self.block_download_peer.unwrap() { 99 | self.block_download_peer = None; 100 | debug!("lost block download peer={}", pid); 101 | while let Some(asked) = self.blocks_asked.pop_back() { 102 | self.blocks_wanted.push_front(asked); 103 | } 104 | } 105 | } 106 | } 107 | PeerMessage::Incoming(pid, msg) => { 108 | match msg { 109 | NetworkMessage::Headers(ref headers) => if self.is_serving_blocks(pid) { self.headers(headers, pid); }, 110 | NetworkMessage::Inv(ref inv) => if self.is_serving_blocks(pid) { self.inv(inv, pid); }, 111 | NetworkMessage::Block(ref block) => self.block(block, pid), 112 | _ => {} 113 | } 114 | if self.block_download_peer.is_none() { 115 | self.block_download_peer = Some(pid); 116 | } 117 | if pid == self.block_download_peer.unwrap() { 118 | self.ask_blocks(pid) 119 | } 120 | }, 121 | _ => {} 122 | } 123 | } 124 | self.timeout.lock().unwrap().check(vec!(ExpectedReply::Headers, ExpectedReply::Block)); 125 | } 126 | } 127 | 128 | fn ask_blocks (&mut self, pid: PeerId) { 129 | let mut timeout = self.timeout.lock().unwrap(); 130 | if !timeout.is_busy_with(pid, ExpectedReply::Block) { 131 | let mut n_entries = 0; 132 | while let Some((hash, height)) = self.blocks_wanted.pop_front() { 133 | self.blocks_asked.push_back((hash, height)); 134 | n_entries += 1; 135 | if n_entries == 1000 { 136 | break; 137 | } 138 | } 139 | if self.blocks_asked.len() > 0 { 140 | self.p2p.send_network(pid, NetworkMessage::GetData( 141 | self.blocks_asked.iter().map(|(hash, _)| 142 | Inventory { 143 | inv_type: InvType::Block, 144 | hash: hash.clone() 145 | } 146 | ).collect())); 147 | debug!("asked {} blocks from peer={}", self.blocks_asked.len(), pid); 148 | timeout.expect(pid, self.blocks_asked.len(), ExpectedReply::Block); 149 | } 150 | } 151 | else { 152 | debug!("still waiting for blocks from peer={}", pid); 153 | } 154 | } 155 | 156 | fn block (&mut self, block: &Block, pid: PeerId) { 157 | if let Some(download_peer) = self.block_download_peer { 158 | if download_peer == pid { 159 | if let Some((expected, height)) = self.blocks_asked.front() { 160 | let height = *height; 161 | if block.header.bitcoin_hash() == *expected { 162 | // will drop for out of sequence answers 163 | self.timeout.lock().unwrap().received(pid, 1, ExpectedReply::Block); 164 | 165 | self.blocks_asked.pop_front(); 166 | let mut downstream = self.downstream.lock().unwrap(); 167 | downstream.block_connected(block, height); 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | fn is_serving_blocks(&self, peer: PeerId) -> bool { 175 | if let Some(peer_version) = self.p2p.peer_version(peer) { 176 | return peer_version.services & SERVICE_BLOCKS != 0; 177 | } 178 | false 179 | } 180 | 181 | // process an incoming inventory announcement 182 | fn inv(&mut self, v: &Vec, peer: PeerId) { 183 | let mut ask_for_headers = false; 184 | for inventory in v { 185 | // only care for blocks 186 | if inventory.inv_type == InvType::Block { 187 | let chaindb = self.chaindb.read().unwrap(); 188 | if chaindb.get_header(&inventory.hash).is_none() { 189 | debug!("received inv for new block {} peer={}", inventory.hash, peer); 190 | // ask for header(s) if observing a new block 191 | ask_for_headers = true; 192 | } 193 | } 194 | } 195 | if ask_for_headers { 196 | self.get_headers(peer); 197 | } 198 | } 199 | 200 | /// get headers this peer is ahead of us 201 | fn get_headers(&mut self, peer: PeerId) { 202 | if self.timeout.lock().unwrap().is_busy_with(peer, ExpectedReply::Headers) { 203 | return; 204 | } 205 | let chaindb = self.chaindb.read().unwrap(); 206 | let locator = chaindb.header_locators(); 207 | if locator.len() > 0 { 208 | let first = if locator.len() > 0 { 209 | *locator.first().unwrap() 210 | } else { 211 | sha256d::Hash::default() 212 | }; 213 | self.timeout.lock().unwrap().expect(peer, 1, ExpectedReply::Headers); 214 | self.p2p.send_network(peer, NetworkMessage::GetHeaders(GetHeadersMessage::new(locator, first))); 215 | } 216 | } 217 | 218 | fn headers(&mut self, headers: &Vec, peer: PeerId) { 219 | self.timeout.lock().unwrap().received(peer, 1, ExpectedReply::Headers); 220 | 221 | if headers.len() > 0 { 222 | // current height 223 | let mut height; 224 | // some received headers were not yet known 225 | let mut some_new = false; 226 | let mut moved_tip = None; 227 | { 228 | let chaindb = self.chaindb.read().unwrap(); 229 | 230 | if let Some(tip) = chaindb.header_tip() { 231 | height = tip.stored.height; 232 | } else { 233 | return; 234 | } 235 | } 236 | 237 | let mut headers_queue = VecDeque::new(); 238 | headers_queue.extend(headers.iter()); 239 | while !headers_queue.is_empty() { 240 | let mut connected_headers = Vec::new(); 241 | let mut disconnected_headers = Vec::new(); 242 | { 243 | let mut chaindb = self.chaindb.write().unwrap(); 244 | while let Some(header) = headers_queue.pop_front() { 245 | // add to blockchain - this also checks proof of work 246 | match chaindb.add_header(&header) { 247 | Ok(Some((stored, unwinds, forwards))) => { 248 | connected_headers.push((stored.height, stored.header)); 249 | // POW is ok, stored top chaindb 250 | some_new = true; 251 | 252 | if let Some(forwards) = forwards { 253 | moved_tip = Some(forwards.last().unwrap().clone()); 254 | } 255 | height = stored.height; 256 | 257 | if let Some(unwinds) = unwinds { 258 | disconnected_headers.extend(unwinds.iter() 259 | .map(|h| chaindb.get_header(h).unwrap().stored.header)); 260 | break; 261 | } 262 | } 263 | Ok(None) => {} 264 | Err(Error::SpvBadProofOfWork) => { 265 | info!("Incorrect POW, banning peer={}", peer); 266 | self.p2p.ban(peer, 100); 267 | } 268 | Err(e) => { 269 | debug!("error {} processing header {} ", e, header.bitcoin_hash()); 270 | } 271 | } 272 | } 273 | chaindb.batch().unwrap(); 274 | } 275 | 276 | // call downstream outside of chaindb lock 277 | let mut downstream = self.downstream.lock().unwrap(); 278 | for header in &disconnected_headers { 279 | if (header.time as u64) > self.birth { 280 | self.blocks_wanted.pop_back(); 281 | downstream.block_disconnected(header); 282 | } 283 | } 284 | for (height, header) in &connected_headers { 285 | if (header.time as u64) > self.birth { 286 | self.blocks_wanted.push_back((header.bitcoin_hash(), *height)); 287 | downstream.header_connected(header, *height); 288 | } 289 | } 290 | } 291 | 292 | if some_new { 293 | // ask if peer knows even more 294 | self.get_headers(peer); 295 | } 296 | 297 | if let Some(new_tip) = moved_tip { 298 | info!("received {} headers new tip={} from peer={}", headers.len(), new_tip, peer); 299 | self.p2p.send(P2PControl::Height(height)); 300 | } else { 301 | debug!("received {} known or orphan headers [{} .. {}] from peer={}", headers.len(), headers[0].bitcoin_hash(), headers[headers.len()-1].bitcoin_hash(), peer); 302 | } 303 | } 304 | } 305 | } -------------------------------------------------------------------------------- /src/config.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::fs; 19 | use std::fs::File; 20 | use std::io::{Read, Write}; 21 | use std::net::SocketAddr; 22 | use std::path::Path; 23 | use crate::error::Error; 24 | 25 | use bitcoin::Network; 26 | 27 | #[derive(Serialize, Deserialize, Debug, PartialEq)] 28 | pub struct Config { 29 | pub encryptedwalletkey: String, 30 | pub keyroot: String, 31 | pub lookahead: u32, 32 | pub birth: u64, 33 | pub network: Network, 34 | pub bitcoin_peers: Vec, 35 | pub bitcoin_connections: usize, 36 | pub bitcoin_discovery: bool, 37 | } 38 | 39 | impl Config { 40 | pub fn new(encryptedwalletkey: &str, keyroot: &str, lookahead: u32, birth: u64, network: Network) -> Config { 41 | Config { 42 | encryptedwalletkey: String::from(encryptedwalletkey), 43 | keyroot: String::from(keyroot), 44 | lookahead, 45 | birth, 46 | network, 47 | bitcoin_peers: vec![], 48 | bitcoin_connections: 0, 49 | bitcoin_discovery: false, 50 | } 51 | } 52 | 53 | pub fn update(&self, bitcoin_peers: Vec, bitcoin_connections: usize, bitcoin_discovery: bool) -> Config { 54 | Config { 55 | encryptedwalletkey: self.encryptedwalletkey.clone(), 56 | keyroot: self.keyroot.clone(), 57 | lookahead: self.lookahead, 58 | birth: self.birth, 59 | network: self.network, 60 | bitcoin_peers, 61 | bitcoin_connections, 62 | bitcoin_discovery, 63 | } 64 | } 65 | } 66 | 67 | pub fn save(config_path: &Path, file_path: &Path, config: &Config) -> Result<(), Error> { 68 | fs::create_dir_all(&config_path)?; 69 | let mut file = File::create(file_path)?; 70 | let config_string = toml::to_string(config).unwrap(); 71 | 72 | file.write_all(config_string.as_bytes())?; 73 | file.sync_all()?; 74 | Ok(()) 75 | } 76 | 77 | pub fn load(file_path: &Path) -> Result { 78 | // get config (if any) 79 | let mut file = File::open(file_path)?; 80 | let mut config_string = String::new(); 81 | file.read_to_string(&mut config_string)?; 82 | match toml::from_str(config_string.as_str()) { 83 | Ok(c) => Ok(c), 84 | Err(e) => Err(e.into()) 85 | } 86 | } 87 | 88 | pub fn remove(config_path: &Path) -> Result<(), Error> { 89 | match fs::remove_dir_all(config_path) { 90 | Ok(()) => Ok(()), 91 | Err(e) => Err(e.into()) 92 | } 93 | } 94 | 95 | #[cfg(test)] 96 | mod test { 97 | use std::{fs, io}; 98 | use std::error::Error; 99 | use std::path::PathBuf; 100 | use std::str::FromStr; 101 | 102 | use bitcoin::Network; 103 | 104 | use crate::config; 105 | use crate::config::Config; 106 | 107 | #[test] 108 | fn save_load_delete() { 109 | let test_config = Config::new( 110 | "encryptedwalletkey", 111 | "keyroot", 112 | 0, 0, Network::Testnet); 113 | 114 | let workdir_path = PathBuf::from("./test1"); 115 | let mut config_path = workdir_path.clone(); 116 | config_path.push(test_config.network.to_string()); 117 | let mut file_path = config_path.clone(); 118 | file_path.push("bdk.cfg"); 119 | 120 | assert_eq!(config::save(&config_path, &file_path, &test_config).is_ok(), true); 121 | let loaded = config::load(&file_path); 122 | assert_eq!(loaded.is_ok(), true); 123 | assert_eq!(loaded.unwrap(), test_config); 124 | assert_eq!(config::remove(&workdir_path).is_ok(), true); 125 | } 126 | 127 | #[test] 128 | fn save_update_load_delete() { 129 | let test_config = Config::new( 130 | "encryptedwalletkey", 131 | "keyroot", 132 | 0, 0, Network::Testnet); 133 | 134 | let workdir_path = PathBuf::from("./test2"); 135 | let mut config_path = workdir_path.clone(); 136 | config_path.push(test_config.network.to_string()); 137 | let mut file_path = config_path.clone(); 138 | file_path.push("bdk.cfg"); 139 | 140 | assert_eq!(config::save(&config_path, &file_path, &test_config).is_ok(), true); 141 | 142 | let loaded = config::load(&file_path); 143 | assert_eq!(loaded.is_ok(), true); 144 | let loaded = loaded.unwrap(); 145 | assert_eq!(loaded, test_config); 146 | 147 | let bitcoin_peers = vec! {"127.0.0.1:8080".parse().unwrap(), "127.0.0.1:8081".parse().unwrap(), "127.0.0.1:8082".parse().unwrap()}; 148 | let updated = loaded.update(bitcoin_peers, 10, false); 149 | let saved_updated = config::save(&config_path, &file_path, &updated); 150 | assert_eq!(saved_updated.is_ok(), true); 151 | 152 | let loaded_updated = config::load(&file_path); 153 | assert_eq!(loaded_updated.is_ok(), true); 154 | 155 | let loaded_updated = loaded_updated.unwrap(); 156 | assert_eq!(updated, loaded_updated); 157 | assert_eq!(loaded_updated.bitcoin_peers.len(), 3); 158 | assert_eq!(loaded_updated.bitcoin_connections, 10); 159 | assert_eq!(loaded_updated.bitcoin_discovery, false); 160 | 161 | assert_eq!(config::remove(&workdir_path).is_ok(), true); 162 | let loaded_updated = config::load(&file_path); 163 | assert_eq!(loaded_updated.is_ok(), false); 164 | } 165 | } 166 | 167 | -------------------------------------------------------------------------------- /src/db.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::collections::HashSet; 19 | use std::hash::Hasher; 20 | use std::io; 21 | use std::net::{Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6}; 22 | use std::path::{Path, PathBuf}; 23 | use std::str::FromStr; 24 | use std::sync::{Arc, Mutex}; 25 | use std::time::SystemTime; 26 | 27 | use bitcoin::{Network, OutPoint, PublicKey, Script, TxOut}; 28 | use bitcoin::consensus::{deserialize, serialize}; 29 | use bitcoin::util::bip32::ExtendedPubKey; 30 | use bitcoin_hashes::{sha256, sha256d}; 31 | use bitcoin_hashes::hex::FromHex; 32 | use bitcoin_wallet::account::{Account, AccountAddressType, KeyDerivation, MasterAccount}; 33 | use bitcoin_wallet::coins::{Coin, Coins}; 34 | use bitcoin_wallet::proved::ProvedTransaction; 35 | use byteorder::{ByteOrder, LittleEndian}; 36 | use log::debug; 37 | use rand::{Rng, RngCore, thread_rng}; 38 | use rand_distr::Poisson; 39 | use rusqlite::{Connection, NO_PARAMS, OptionalExtension, ToSql, Transaction}; 40 | use rusqlite::types::{Null, ValueRef}; 41 | use siphasher::sip::SipHasher; 42 | 43 | use crate::error::Error; 44 | 45 | pub type SharedDB = Arc>; 46 | 47 | const ADDRESS_SLOTS: u64 = 10000; 48 | 49 | pub struct DB { 50 | connection: Connection 51 | } 52 | 53 | impl DB { 54 | pub fn memory() -> Result { 55 | Ok(DB { connection: Connection::open_in_memory()? }) 56 | } 57 | 58 | pub fn new(path: &std::path::Path) -> Result { 59 | Ok(DB { connection: Connection::open(path)? }) 60 | } 61 | 62 | pub fn transaction(&mut self) -> TX { 63 | TX { tx: self.connection.transaction().expect("can not start db transaction") } 64 | } 65 | } 66 | 67 | pub struct TX<'db> { 68 | tx: Transaction<'db> 69 | } 70 | 71 | impl<'db> TX<'db> { 72 | pub fn commit(self) { 73 | self.tx.commit().expect("failed to commit db transaction"); 74 | } 75 | 76 | pub fn rollback(self) { 77 | self.tx.rollback().expect("failed to roll back db transaction"); 78 | } 79 | 80 | pub fn create_tables(&mut self) { 81 | self.tx.execute_batch(r#" 82 | create table if not exists seed ( 83 | k0 number, 84 | k1 number 85 | ); 86 | 87 | create table if not exists address ( 88 | network text, 89 | slot number, 90 | ip text, 91 | connected number, 92 | last_seen number, 93 | banned number, 94 | primary key(network, slot) 95 | ) without rowid; 96 | 97 | create table if not exists account ( 98 | account number, 99 | sub number, 100 | address_type number, 101 | master text, 102 | instantiated blob, 103 | primary key(account, sub) 104 | ) without rowid; 105 | 106 | create table if not exists coins ( 107 | txid text, 108 | vout number, 109 | value number, 110 | script blob, 111 | account number, 112 | sub number, 113 | kix number, 114 | tweak text, 115 | csv number, 116 | proof blob, 117 | primary key(txid, vout) 118 | ) without rowid; 119 | 120 | create table if not exists processed ( 121 | block text 122 | ); 123 | 124 | create table if not exists txout ( 125 | txid text primary key, 126 | tx blob, 127 | confirmed text, 128 | publisher blob, 129 | id text, 130 | term number 131 | ) without rowid; 132 | "#).expect("failed to create db tables"); 133 | } 134 | 135 | pub fn rescan(&mut self, after: &sha256d::Hash) -> Result<(), Error> { 136 | self.tx.execute(r#" 137 | update processed set block = ?1 138 | "#, &[&after.to_string() as &dyn ToSql])?; 139 | self.tx.execute(r#" 140 | delete from txout 141 | "#, NO_PARAMS)?; 142 | self.tx.execute(r#" 143 | delete from coins 144 | "#, NO_PARAMS)?; 145 | Ok(()) 146 | } 147 | 148 | pub fn store_txout(&mut self, tx: &bitcoin::Transaction, funding: Option<(&PublicKey, &sha256::Hash, u16)>) -> Result<(), Error> { 149 | if let Some((publisher, id, term)) = funding { 150 | self.tx.execute(r#" 151 | insert or replace into txout (txid, tx, publisher, id, term) values (?1, ?2, ?3, ?4, ?5) 152 | "#, &[&tx.txid().to_string() as &dyn ToSql, 153 | &serialize(tx), 154 | &publisher.to_bytes(), &id.to_string(), &term])?; 155 | } else { 156 | self.tx.execute(r#" 157 | insert or replace into txout (txid, tx) values (?1, ?2) 158 | "#, &[&tx.txid().to_string() as &dyn ToSql, 159 | &serialize(tx)])?; 160 | } 161 | Ok(()) 162 | } 163 | 164 | pub fn read_unconfirmed(&self) -> Result)>, Error> { 165 | let mut result = Vec::new(); 166 | // remove unconfirmed spend 167 | let mut query = self.tx.prepare(r#" 168 | select tx, publisher, id, term from txout where confirmed is null 169 | "#)?; 170 | for r in query.query_map(NO_PARAMS, |r| { 171 | Ok((r.get_unwrap::>(0), 172 | match r.get_raw(1) { 173 | ValueRef::Null => None, 174 | ValueRef::Blob(publisher) => Some(publisher.to_vec()), 175 | _ => panic!("unexpected tweak type") 176 | }, 177 | match r.get_raw(2) { 178 | ValueRef::Null => None, 179 | ValueRef::Text(id) => Some(id.to_vec()), 180 | _ => panic!("unexpected tweak type") 181 | }, 182 | match r.get_raw(3) { 183 | ValueRef::Null => None, 184 | ValueRef::Integer(n) => Some(n as u16), 185 | _ => panic!("unexpected tweak type") 186 | })) 187 | })? { 188 | let (tx, publisher, id, term) = r?; 189 | result.push( 190 | (deserialize::(tx.as_slice()).expect("can not deserialize stored transaction"), 191 | if let Some(publisher) = publisher { 192 | Some((PublicKey::from_slice(publisher.as_slice()).expect("stored publisher in txout not a pubkey"), 193 | sha256::Hash::from_hex(std::str::from_utf8(id.unwrap().as_slice()).unwrap()).expect("stored id in txout not hex"), 194 | term.unwrap())) 195 | } else { None }, 196 | )); 197 | } 198 | Ok(result) 199 | } 200 | 201 | pub fn read_seed(&mut self) -> Result<(u64, u64), Error> { 202 | if let Some(seed) = self.tx.query_row(r#" 203 | select k0, k1 from seed where rowid = 1 204 | "#, NO_PARAMS, |r| Ok( 205 | (r.get_unwrap::(0) as u64, 206 | r.get_unwrap::(1) as u64))).optional()? { 207 | return Ok(seed); 208 | } else { 209 | let k0 = thread_rng().next_u64(); 210 | let k1 = thread_rng().next_u64(); 211 | self.tx.execute(r#" 212 | insert or replace into seed (rowid, k0, k1) values (1, ?1, ?2) 213 | "#, &[&(k0 as i64) as &dyn ToSql, &(k1 as i64)])?; 214 | return Ok((k0, k1)); 215 | } 216 | } 217 | 218 | pub fn read_processed(&mut self) -> Result, Error> { 219 | Ok(self.tx.query_row(r#" 220 | select block from processed where rowid = 1 221 | "#, NO_PARAMS, |r| Ok(sha256d::Hash::from_hex(r.get_unwrap::(0).as_str()) 222 | .expect("stored block not hex"))).optional()?) 223 | } 224 | 225 | pub fn store_processed(&mut self, block_id: &sha256d::Hash) -> Result<(), Error> { 226 | self.tx.execute(r#" 227 | insert or replace into processed (rowid, block) values (1, ?1) 228 | "#, &[&block_id.to_string() as &dyn ToSql])?; 229 | Ok(()) 230 | } 231 | 232 | pub fn store_coins(&mut self, coins: &Coins) -> Result<(), Error> { 233 | self.tx.execute(r#" 234 | delete from coins; 235 | "#, NO_PARAMS)?; 236 | let mut statement = self.tx.prepare(r#" 237 | insert into coins (txid, vout, value, script, account, sub, kix, tweak, csv, proof) 238 | values (?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10) 239 | "#)?; 240 | let proofs = coins.proofs(); 241 | for (outpoint, coin) in coins.confirmed() { 242 | let proof = proofs.get(&outpoint.txid).expect("inconsistent wallet, missing proof"); 243 | let tweak = if let Some(ref tweak) = coin.derivation.tweak { hex::encode(tweak) } else { "".to_string() }; 244 | 245 | statement.execute(&[ 246 | &outpoint.txid.to_string() as &dyn ToSql, &outpoint.vout, 247 | &(coin.output.value as i64), &coin.output.script_pubkey.to_bytes(), 248 | &coin.derivation.account, &coin.derivation.sub, &coin.derivation.kix, 249 | if tweak == "".to_string() { 250 | &Null 251 | } else { 252 | &tweak as &dyn ToSql 253 | }, 254 | if let Some(ref csv) = coin.derivation.csv { 255 | csv as &dyn ToSql 256 | } else { 257 | &Null 258 | }, 259 | &serde_cbor::ser::to_vec(&proof).expect("can not serialize proof") 260 | ])?; 261 | } 262 | 263 | for (unconfirmed, _) in self.read_unconfirmed()? { 264 | if let Some(proof) = proofs.values().find(|p| p.get_transaction().txid() == unconfirmed.txid()) { 265 | self.tx.execute(r#" 266 | update txout set confirmed = ?1 where txid = ?2 267 | "#, &[&proof.get_block_hash().to_string() as &dyn ToSql, &proof.get_transaction().txid().to_string()])?; 268 | } 269 | } 270 | 271 | Ok(()) 272 | } 273 | 274 | pub fn read_coins(&mut self, master_account: &mut MasterAccount) -> Result { 275 | // read confirmed 276 | let mut query = self.tx.prepare(r#" 277 | select txid, vout, value, script, account, sub, kix, tweak, csv, proof from coins 278 | "#)?; 279 | let mut coins = Coins::new(); 280 | for r in query.query_map::<(OutPoint, Coin, ProvedTransaction), &[&dyn ToSql], _>(NO_PARAMS, |r| { 281 | Ok(( 282 | OutPoint { 283 | txid: sha256d::Hash::from_hex(r.get_unwrap::(0).as_str()).expect("transaction id not hex"), 284 | vout: r.get_unwrap::(1), 285 | }, 286 | Coin { 287 | output: TxOut { 288 | script_pubkey: Script::from(r.get_unwrap::>(3)), 289 | value: r.get_unwrap::(2) as u64, 290 | }, 291 | derivation: KeyDerivation { 292 | account: r.get_unwrap::(4), 293 | sub: r.get_unwrap::(5), 294 | kix: r.get_unwrap::(6), 295 | tweak: match r.get_raw(7) { 296 | ValueRef::Null => None, 297 | ValueRef::Text(tweak) => Some(hex::decode(tweak).expect("tweak not hex")), 298 | _ => panic!("unexpected tweak type") 299 | }, 300 | csv: match r.get_raw(8) { 301 | ValueRef::Null => None, 302 | ValueRef::Integer(i) => Some(i as u16), 303 | _ => panic!("unexpected csv type") 304 | }, 305 | }, 306 | }, 307 | serde_cbor::from_slice(r.get_unwrap::>(9).as_slice()).expect("can not deserialize stored proof") 308 | )) 309 | })? { 310 | let (point, coin, proof) = r?; 311 | coins.add_confirmed(point, coin, proof); 312 | } 313 | 314 | // remove unconfirmed spend 315 | let mut query = self.tx.prepare(r#" 316 | select tx from txout where confirmed is null 317 | "#)?; 318 | for r in query.query_map(NO_PARAMS, |r| { 319 | Ok(r.get_unwrap::>(0)) 320 | })? { 321 | let tx = deserialize::(r?.as_slice()).expect("can not deserialize stored transaction"); 322 | coins.process_unconfirmed_transaction(master_account, &tx); 323 | } 324 | Ok(coins) 325 | } 326 | 327 | pub fn store_master(&mut self, master: &MasterAccount) -> Result { 328 | debug!("store master account"); 329 | self.tx.execute(r#" 330 | delete from account; 331 | "#, NO_PARAMS)?; 332 | let mut inserted = 0; 333 | for (_, account) in master.accounts().iter() { 334 | inserted += self.store_account(account)?; 335 | } 336 | Ok(inserted) 337 | } 338 | 339 | pub fn store_account(&mut self, account: &Account) -> Result { 340 | debug!("store account {}/{}", account.account_number(), account.sub_account_number()); 341 | Ok(self.tx.execute(r#" 342 | insert or replace into account (account, address_type, sub, master, instantiated) 343 | values (?1, ?2, ?3, ?4, ?5) 344 | "#, &[&account.account_number() as &dyn ToSql, 345 | &account.address_type().as_u32(), &account.sub_account_number(), &account.master_public().to_string(), 346 | &serde_cbor::ser::to_vec(&account.instantiated())?], 347 | )?) 348 | } 349 | 350 | pub fn read_account(&mut self, account_number: u32, sub: u32, network: Network, look_ahead: u32) -> Result { 351 | debug!("read account {}/{}", account_number, sub); 352 | Ok(self.tx.query_row(r#" 353 | select address_type, master, instantiated from account where account = ?1 and sub = ?2 354 | "#, &[&account_number as &dyn ToSql, &sub], |r| { 355 | Ok(Account::new_from_storage( 356 | AccountAddressType::from_u32(r.get_unwrap::(0)), 357 | account_number, 358 | sub, 359 | ExtendedPubKey::from_str(r.get_unwrap::(1).as_str()).expect("malformed master public stored"), 360 | serde_cbor::from_slice(r.get_unwrap::>(2).as_slice()).expect("malformed instantiated keys stored"), 361 | 0, 362 | look_ahead, 363 | network, 364 | )) 365 | })?) 366 | } 367 | 368 | pub fn store_address(&mut self, network: &str, address: &SocketAddr, mut connected: u64, mut last_seen: u64, mut banned: u64) -> Result { 369 | let (k0, k1) = self.read_seed()?; 370 | let mut siphasher = SipHasher::new_with_keys(k0, k1); 371 | siphasher.write(network.as_bytes()); 372 | for a in NetAddress::new(address).address.iter() { 373 | let mut buf = [0u8; 2]; 374 | LittleEndian::write_u16(&mut buf, *a); 375 | siphasher.write(&buf); 376 | } 377 | let slot = (siphasher.finish() % ADDRESS_SLOTS) as u16; 378 | if let Ok((oldip, oldconnect, oldls, oldban)) = self.tx.query_row(r#" 379 | select ip, connected, last_seen, banned from address where network = ?1 and slot = ?2 380 | "#, &[&network.to_string() as &dyn ToSql, &slot], 381 | |r| Ok( 382 | (SocketAddr::from_str(r.get_unwrap::(0).as_str()).expect("address stored in db should be parsable"), 383 | r.get_unwrap::(1) as u64, 384 | r.get_unwrap::(2) as u64, 385 | r.get_unwrap::(3) as u64))) { 386 | // do not reduce last_seen or banned fields 387 | if oldip != *address { 388 | let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); 389 | const OLD_CONNECTION: u64 = 5 * 24 * 60 * 60; 390 | if oldban > 0 || oldconnect < now - OLD_CONNECTION { 391 | return Ok( 392 | self.tx.execute(r#" 393 | insert or replace into address (network, slot, ip, connected, last_seen, banned) values (?1, ?2, ?3, ?4, ?5, ?6) 394 | "#, &[&network.to_string() as &dyn ToSql, &slot, &address.to_string(), 395 | &(connected as i64), &(last_seen as i64), &(banned as i64)])? 396 | ); 397 | } 398 | } else { 399 | connected = std::cmp::max(oldconnect as u64, connected); 400 | last_seen = std::cmp::max(oldls as u64, last_seen); 401 | banned = std::cmp::max(oldban as u64, banned); 402 | return Ok(self.tx.execute(r#" 403 | insert or replace into address (network, slot, ip, connected, last_seen, banned) values (?1, ?2, ?3, ?4, ?5, ?6) 404 | "#, &[&network.to_string() as &dyn ToSql, &slot, &address.to_string(), 405 | &(connected as i64), &(last_seen as i64), &(banned as i64)])?); 406 | } 407 | Ok(0) 408 | } else { 409 | Ok( 410 | self.tx.execute(r#" 411 | insert or replace into address (network, slot, ip, connected, last_seen, banned) values (?1, ?2, ?3, ?4, ?5, ?6) 412 | "#, &[&network.to_string() as &dyn ToSql, &slot, &address.to_string(), 413 | &(connected as i64), &(last_seen as i64), &(banned as i64)])? 414 | ) 415 | } 416 | } 417 | 418 | // get an address not banned during the last day 419 | // the probability to be selected is exponentially higher for those with higher last_seen time 420 | // TODO mark tried connections, build slots instead of storing all. Replace only if not tried for long or banned 421 | pub fn get_an_address(&self, network: &str, other_than: Arc>>) -> Result, Error> { 422 | const BAN_TIME: u64 = 60 * 60 * 24; // a day 423 | 424 | let now = SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs(); 425 | let mut statement = self.tx.prepare(r#" 426 | select ip from address where network = ?2 and banned < ?1 order by last_seen desc 427 | "#)?; 428 | let other_than = other_than.lock().unwrap(); 429 | let eligible = statement.query_map::( 430 | &[&((now - BAN_TIME) as i64) as &dyn ToSql, &network.to_string()], 431 | |row| { 432 | let s = row.get_unwrap::(0); 433 | let addr = SocketAddr::from_str(s.as_str()).expect("address stored in db should be parsable"); 434 | Ok(addr) 435 | })? 436 | .filter_map(|socket| 437 | match socket { 438 | Ok(a) => if !other_than.contains(&a) { Some(a) } else { None }, 439 | Err(_) => None 440 | }).collect::>(); 441 | let len = eligible.len(); 442 | if len == 0 { 443 | return Ok(None); 444 | } 445 | Ok(Some( 446 | eligible[ 447 | std::cmp::min(len - 1, thread_rng().sample::( 448 | Poisson::new(len as f64 / 4.0).unwrap()) as usize)])) 449 | } 450 | } 451 | 452 | 453 | pub fn init(config_path: &Path, coins: &Coins, master: &MasterAccount) { 454 | let mut db = new(&config_path); 455 | { 456 | let mut tx = db.transaction(); 457 | tx.create_tables(); 458 | tx.commit(); 459 | } 460 | { 461 | let mut tx = db.transaction(); 462 | tx.store_coins(coins).expect("can not store new wallet's coins"); 463 | tx.store_master(master).expect("can not store new master account"); 464 | tx.commit(); 465 | } 466 | } 467 | 468 | pub fn new(config_path: &Path) -> DB { 469 | let mut db_path = PathBuf::from(config_path); 470 | db_path.push("bdk.db"); 471 | DB::new(db_path.as_path()).expect("can not open database") 472 | } 473 | 474 | #[derive(Clone, Copy, Serialize, Deserialize, Hash, Default, Eq, PartialEq, Debug)] 475 | pub struct NetAddress { 476 | /// Network byte-order ipv6 address, or ipv4-mapped ipv6 address 477 | pub address: [u16; 8], 478 | /// Network port 479 | pub port: u16, 480 | } 481 | 482 | const ONION: [u16; 3] = [0xFD87, 0xD87E, 0xEB43]; 483 | 484 | impl NetAddress { 485 | /// Create an address message for a socket 486 | pub fn new(socket: &SocketAddr) -> NetAddress { 487 | let (address, port) = match socket { 488 | &SocketAddr::V4(ref addr) => (addr.ip().to_ipv6_mapped().segments(), addr.port()), 489 | &SocketAddr::V6(ref addr) => (addr.ip().segments(), addr.port()) 490 | }; 491 | NetAddress { address: address, port: port } 492 | } 493 | 494 | 495 | pub fn socket_address(&self) -> Result { 496 | let addr = &self.address; 497 | if addr[0..3] == ONION[0..3] { 498 | return Err(Error::IO(io::Error::from(io::ErrorKind::AddrNotAvailable))); 499 | } 500 | let ipv6 = Ipv6Addr::new( 501 | addr[0], addr[1], addr[2], addr[3], 502 | addr[4], addr[5], addr[6], addr[7], 503 | ); 504 | if let Some(ipv4) = ipv6.to_ipv4() { 505 | Ok(SocketAddr::V4(SocketAddrV4::new(ipv4, self.port))) 506 | } else { 507 | Ok(SocketAddr::V6(SocketAddrV6::new(ipv6, self.port, 0, 0))) 508 | } 509 | } 510 | 511 | pub fn to_string(&self) -> Result { 512 | Ok(format!("{}", self.socket_address()?)) 513 | } 514 | 515 | pub fn from_str(s: &str) -> Result { 516 | let (address, port) = match SocketAddr::from_str(s)? { 517 | SocketAddr::V4(ref addr) => (addr.ip().to_ipv6_mapped().segments(), addr.port()), 518 | SocketAddr::V6(ref addr) => (addr.ip().segments(), addr.port()) 519 | }; 520 | Ok(NetAddress { address, port }) 521 | } 522 | } -------------------------------------------------------------------------------- /src/error.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::convert; 19 | use std::fmt; 20 | use std::io; 21 | use bitcoin_wallet; 22 | use bitcoin::blockdata::script; 23 | use rusqlite; 24 | 25 | /// An error class to offer a unified error interface upstream 26 | pub enum Error { 27 | /// Unsupported 28 | Unsupported(&'static str), 29 | /// 30 | Lock(&'static str), 31 | /// wallet related error 32 | Wallet(bitcoin_wallet::error::Error), 33 | /// IO error 34 | IO(io::Error), 35 | /// DB error 36 | DB(rusqlite::Error), 37 | /// script validation error 38 | Script(script::Error), 39 | /// TOML decode error 40 | TomlDe(toml::de::Error), 41 | } 42 | 43 | impl std::error::Error for Error { 44 | fn description(&self) -> &str { 45 | match *self { 46 | Error::Unsupported(ref s) => s, 47 | Error::Lock(ref s) => s, 48 | Error::Wallet(ref err) => err.description(), 49 | Error::IO(ref err) => err.description(), 50 | Error::DB(ref err) => err.description(), 51 | Error::Script(ref err) => err.description(), 52 | Error::TomlDe(ref err) => err.description(), 53 | } 54 | } 55 | 56 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { 57 | match *self { 58 | Error::Unsupported(_) => None, 59 | Error::Lock(_) => None, 60 | Error::Wallet(ref err) => Some(err), 61 | Error::IO(ref err) => Some(err), 62 | Error::DB(ref err) => Some(err), 63 | Error::Script(ref err) => Some(err), 64 | Error::TomlDe(ref err) => Some(err), 65 | } 66 | } 67 | } 68 | 69 | impl fmt::Display for Error { 70 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 71 | match *self { 72 | // underlying errors already impl `Display`, so we defer to their implementations. 73 | Error::Unsupported(ref s) => write!(f, "Unsupported: {}", s), 74 | Error::Lock(ref s) => write!(f, "ReadLock: {}", s), 75 | Error::Wallet(ref s) => write!(f, "{}", s), 76 | Error::IO(ref s) => write!(f, "{}", s), 77 | Error::DB(ref s) => write!(f, "{}", s), 78 | Error::Script(ref s) => write!(f, "{}", s), 79 | Error::TomlDe(ref s) => write!(f, "{}", s), 80 | } 81 | } 82 | } 83 | 84 | impl fmt::Debug for Error { 85 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 86 | (self as &dyn fmt::Display).fmt(f) 87 | } 88 | } 89 | 90 | impl convert::From for Error { 91 | fn from(err: bitcoin_wallet::error::Error) -> Error { 92 | Error::Wallet(err) 93 | } 94 | } 95 | 96 | impl convert::From for Error { 97 | fn from(err: io::Error) -> Error { 98 | Error::IO(err) 99 | } 100 | } 101 | 102 | impl convert::From for Error { 103 | fn from(err: rusqlite::Error) -> Error { 104 | Error::DB(err) 105 | } 106 | } 107 | 108 | impl convert::From for Error { 109 | fn from(_: std::net::AddrParseError) -> Error { 110 | Error::IO(io::Error::from(io::ErrorKind::InvalidInput)) 111 | } 112 | } 113 | 114 | impl convert::From for Error { 115 | fn from(_: serde_cbor::error::Error) -> Error { 116 | Error::IO(io::Error::from(io::ErrorKind::InvalidInput)) 117 | } 118 | } 119 | 120 | impl convert::From for Error { 121 | fn from(_: bitcoin_hashes::Error) -> Error { 122 | Error::IO(io::Error::from(io::ErrorKind::InvalidInput)) 123 | } 124 | } 125 | 126 | impl convert::From for Error { 127 | fn from(_: bitcoin_hashes::hex::Error) -> Error { 128 | Error::IO(io::Error::from(io::ErrorKind::InvalidInput)) 129 | } 130 | } 131 | 132 | impl convert::From for Error { 133 | fn from(err: script::Error) -> Error { 134 | Error::Script(err) 135 | } 136 | } 137 | 138 | impl convert::From for Error { 139 | fn from(err: toml::de::Error) -> Error { 140 | Error::TomlDe(err) 141 | } 142 | } -------------------------------------------------------------------------------- /src/jni.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::convert::TryFrom; 19 | use std::fs; 20 | use std::net::SocketAddr; 21 | use std::path::{Path, PathBuf}; 22 | use std::str::FromStr; 23 | use std::sync::{Arc, Mutex, RwLock}; 24 | 25 | use bitcoin::{Address, Network}; 26 | use jni::JNIEnv; 27 | use jni::objects::{JObject, JString, JValue}; 28 | use jni::sys::{jboolean, jint, jlong, jobject, jobjectArray}; 29 | use log::{error, info}; 30 | 31 | use crate::api::{balance, BalanceAmt, deposit_addr, init_config, InitResult, load_config, remove_config, start, stop, update_config, withdraw, WithdrawTx}; 32 | use crate::config::Config; 33 | 34 | // public API 35 | 36 | // void org.bdk.jni.BdkLib.initLogger() 37 | 38 | #[no_mangle] 39 | #[cfg(feature = "android")] 40 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_initLogger(_: JNIEnv, _: JObject) { 41 | android_log::init("BDK").unwrap(); 42 | info!("android logger initialized"); 43 | } 44 | 45 | #[no_mangle] 46 | #[cfg(feature = "java")] 47 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_initLogger(_: JNIEnv, _: JObject) { 48 | env_logger::init(); 49 | info!("java logger initialized"); 50 | } 51 | 52 | // Optional org.bdk.jni.BdkLib.loadConfig(String workDir, int network) 53 | #[no_mangle] 54 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_loadConfig(env: JNIEnv, _: JObject, 55 | j_work_dir: JString, 56 | j_network: jint) -> jobject { 57 | let work_dir = string_from_jstring(&env, j_work_dir); 58 | let work_dir = PathBuf::from(work_dir); 59 | let network = network_from_jint(j_network); 60 | 61 | match load_config(work_dir, network) { 62 | Ok(config) => j_optional_config(&env, &config), 63 | Err(_err) => j_optional_empty(&env) 64 | } 65 | } 66 | 67 | // Optional org.bdk.jni.BdkLib.removeConfig(String workDir, int network) 68 | #[no_mangle] 69 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_removeConfig(env: JNIEnv, _: JObject, 70 | j_work_dir: JString, 71 | j_network: jint) -> jobject { 72 | let work_dir = string_from_jstring(&env, j_work_dir); 73 | let work_dir = PathBuf::from(work_dir); 74 | let network = network_from_jint(j_network); 75 | 76 | match remove_config(work_dir, network) { 77 | Ok(config) => j_optional_config(&env, &config), 78 | Err(_err) => j_optional_empty(&env) 79 | } 80 | } 81 | 82 | // Optional org.bdk.jni.BdkLib.updateConfig(String workDir, int network, String[] bitcoinPeers, int bitcoinConnections, boolean bitcoinDiscovery) 83 | #[no_mangle] 84 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_updateConfig(env: JNIEnv, _: JObject, 85 | j_work_dir: JString, 86 | j_network: jint, 87 | j_bitcoin_peers: jobjectArray, 88 | j_bitcoin_connections: jint, 89 | j_bitcoin_discovery: jboolean) -> jobject { 90 | let work_dir = string_from_jstring(&env, j_work_dir); 91 | let work_dir = PathBuf::from(work_dir); 92 | let network = network_from_jint(j_network); 93 | 94 | let bitcoin_peers_length = env.get_array_length(j_bitcoin_peers) 95 | .expect("error get_array_length j_bitcoin_peers"); 96 | 97 | let mut bitcoin_peers: Vec = Vec::new(); 98 | 99 | for i in 0..(bitcoin_peers_length) { 100 | let bitcoin_peer = env.get_object_array_element(j_bitcoin_peers, i) 101 | .expect("error get_object_array_element j_bitcoin_peers"); 102 | let bitcoin_peer = JString::try_from(bitcoin_peer) 103 | .expect("error JString::try_from j_bitcoin_peers element"); 104 | let bitcoin_peer = env.get_string(bitcoin_peer) 105 | .expect("error env.get_string bitcoin_peer"); 106 | let bitcoin_peer = bitcoin_peer.to_str() 107 | .expect("error bitcoin_peer.toStr()"); 108 | 109 | let bitcoin_peer_addr = SocketAddr::from_str(bitcoin_peer) 110 | .expect("error SocketAddr::from_str(bitcoin_peer)"); 111 | 112 | bitcoin_peers.push(bitcoin_peer_addr); 113 | } 114 | 115 | let bitcoin_connections = usize::try_from(j_bitcoin_connections).expect("usize::try_from(j_bitcoin_connections"); 116 | let bitcoin_discovery = j_bitcoin_discovery == 1; 117 | 118 | match update_config(work_dir, network, bitcoin_peers, bitcoin_connections, bitcoin_discovery) { 119 | Ok(updated_config) => j_optional_config(&env, &updated_config), 120 | Err(_err) => j_optional_empty(&env) 121 | } 122 | } 123 | 124 | // Optional org.bdk.jni.BdkLib.initConfig(String workDir, int network, String passphrase, String pdPassphrase) 125 | #[no_mangle] 126 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_initConfig(env: JNIEnv, _: JObject, 127 | j_work_dir: JString, 128 | j_network: jint, 129 | j_passphrase: JString, 130 | j_pd_passphrase: JString) -> jobject { 131 | let work_dir = string_from_jstring(&env, j_work_dir); 132 | let work_dir = PathBuf::from(work_dir); 133 | let network = network_from_jint(j_network); 134 | 135 | let passphrase = string_from_jstring(&env, j_passphrase); 136 | let passphrase = passphrase.as_str(); 137 | let pd_passphrase = env.get_string(j_pd_passphrase).ok(); 138 | let pd_passphrase = pd_passphrase.iter() 139 | .map(|pd| pd.to_str().expect("error j_pd_passphrase JavaStr.to_str()")) 140 | .next(); 141 | 142 | match init_config(work_dir, network, passphrase, pd_passphrase) { 143 | Ok(None) => { 144 | // do not init if a config already exists, return empty 145 | j_optional_empty(&env) 146 | } 147 | Ok(Some(init_result)) => { 148 | // return config 149 | j_optional_init_result(&env, init_result) 150 | } 151 | Err(_err) => { 152 | // TODO throw java exception 153 | j_optional_empty(&env) 154 | } 155 | } 156 | } 157 | 158 | // void org.bdk.jni.BdkLib.start(String workDir, int network, boolean rescan) 159 | #[no_mangle] 160 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_start(env: JNIEnv, _: JObject, j_work_dir: JString, j_network: jint, j_rescan: jboolean) { 161 | let work_dir = string_from_jstring(&env, j_work_dir); 162 | let work_dir = PathBuf::from(work_dir); 163 | let network = network_from_jint(j_network); 164 | let rescan = j_rescan == 1; 165 | 166 | match start(work_dir, network, rescan) { 167 | Ok(_) => (), 168 | Err(_e) => { 169 | // TODO throw java exception 170 | error!("Could not start wallet."); 171 | () 172 | } 173 | } 174 | } 175 | 176 | // void org.bdk.jni.BdkLib.stop() 177 | #[no_mangle] 178 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_stop(_: JNIEnv, _: JObject) { 179 | stop() 180 | } 181 | 182 | // Option org.bdk.jni.BdkLib.balance() 183 | #[no_mangle] 184 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_balance(env: JNIEnv, _: JObject) -> jobject { 185 | match balance() { 186 | Ok(balance_amt) => { 187 | // return wallet balance amt 188 | j_optional_balance_amt_result(&env, balance_amt) 189 | }, 190 | Err(_e) => { 191 | // TODO throw java exception 192 | error!("Could not get wallet balance amt."); 193 | j_optional_empty(&env) 194 | } 195 | } 196 | } 197 | 198 | // new Address(String address, int network, Optional type) 199 | // Address org.bdk.jni.BdkLib.depositAddress() 200 | #[no_mangle] 201 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_depositAddress(env: JNIEnv, _: JObject) -> jobject { 202 | let address = deposit_addr(); 203 | j_address(&env, &address) 204 | } 205 | 206 | // new WithdrawTx(String txid, long fee) 207 | // WithdrawTx org.bdk.jni.BdkLib.withdraw(String passphrase, String address, long feePerVbyte, long amount) 208 | #[no_mangle] 209 | pub unsafe extern fn Java_org_bdk_jni_BdkLib_withdraw(env: JNIEnv, _: JObject, 210 | j_passphrase: JString, 211 | j_address: JString, 212 | j_fee_per_vbyte: jlong, 213 | j_amount: jlong) -> jobject { 214 | 215 | let passphrase = string_from_jstring(&env, j_passphrase); 216 | let address = string_from_jstring(&env, j_address); 217 | let address = Address::from_str(address.as_str()).unwrap(); 218 | 219 | let fee_per_vbyte = u64::try_from(j_fee_per_vbyte).unwrap(); 220 | let amount = u64::try_from(j_amount).unwrap(); 221 | 222 | let withdraw_tx = withdraw(passphrase, address, fee_per_vbyte, Some(amount)).unwrap(); 223 | j_withdraw_tx(&env, &withdraw_tx) 224 | } 225 | 226 | 227 | // private functions 228 | 229 | fn string_from_jstring(env: &JNIEnv, j_string: JString) -> String { 230 | let java_str = env.get_string(j_string).expect("error get_string j_string"); 231 | let str = java_str.to_str().expect("error java_str.to_str"); 232 | String::from(str) 233 | } 234 | 235 | fn j_optional_empty(env: &JNIEnv) -> jobject { 236 | // Optional.empty()) 237 | let j_result = env.call_static_method( 238 | "java/util/Optional", 239 | "empty", 240 | "()Ljava/util/Optional;", 241 | &[]).expect("error Optional.empty()") 242 | .l().expect("error converting Optional.empty() jvalue to jobject"); 243 | 244 | j_result.into_inner() 245 | } 246 | 247 | fn network_from_jint(network_enum_ordinal: jint) -> Network { 248 | match network_enum_ordinal { 249 | 0 => Some(Network::Bitcoin), 250 | 1 => Some(Network::Testnet), 251 | 2 => Some(Network::Regtest), 252 | _ => None 253 | }.expect("invalid network enum ordinal") 254 | } 255 | 256 | fn jint_from_network(network: Network) -> jint { 257 | match network { 258 | Network::Bitcoin => 0, 259 | Network::Testnet => 1, 260 | Network::Regtest => 2, 261 | } 262 | } 263 | 264 | // InitResult(String mnemonicWords, Address depositAddress) 265 | fn j_optional_init_result(env: &JNIEnv, init_result: InitResult) -> jobject { 266 | let mnemonic_words = env.new_string(init_result.mnemonic_words) 267 | .expect("error new_string mnemonic_words"); 268 | let deposit_address: jobject = j_address(&env, &init_result.deposit_address); 269 | 270 | // org.bdk.jni.InitResult 271 | // Optional.of(InitResult(String mnemonicWords, String depositAddress)) 272 | let j_result = env.new_object( 273 | "org/bdk/jni/InitResult", 274 | "(Ljava/lang/String;Lorg/bdk/jni/Address;)V", 275 | &[JValue::Object(mnemonic_words.into()), JValue::Object(deposit_address.into())], 276 | ).expect("error new_object InitResult"); 277 | 278 | let j_result = env.call_static_method( 279 | "java/util/Optional", 280 | "of", 281 | "(Ljava/lang/Object;)Ljava/util/Optional;", 282 | &[JValue::Object(j_result)]).expect("error Optional.of(InitResult)") 283 | .l().expect("error converting Optional.of() jvalue to jobject"); 284 | 285 | j_result.into_inner() 286 | } 287 | 288 | // new BalanceAmt(long,long) 289 | fn j_optional_balance_amt_result(env: &JNIEnv, balance_amt: BalanceAmt) -> jobject { 290 | let bal = JValue::Long(jlong::try_from(balance_amt.balance).unwrap()); 291 | let conf = JValue::Long(jlong::try_from(balance_amt.confirmed).unwrap()); 292 | let j_result = env.new_object( 293 | "org/bdk/jni/BalanceAmt", 294 | "(JJ)V", 295 | &[bal, conf], 296 | ).expect("error new_object BalanceAmt"); 297 | 298 | let j_result = env.call_static_method( 299 | "java/util/Optional", 300 | "of", 301 | "(Ljava/lang/Object;)Ljava/util/Optional;", 302 | &[JValue::Object(j_result)]).expect("error Optional.of(BalanceAmt)") 303 | .l().expect("error converting Optional.of() jvalue to jobject"); 304 | 305 | j_result.into_inner() 306 | } 307 | 308 | // Config(int networkEnumOrdinal, String[] bitcoinPeers, int bitcoinConnections, boolean bitcoinDiscovery) 309 | fn j_optional_config(env: &JNIEnv, config: &Config) -> jobject { 310 | let j_network_enum_ordinal: JValue = jint_from_network(config.network).into(); 311 | 312 | // return peer addresses as JString vector 313 | let j_bitcoin_peer_vec: Vec = config.bitcoin_peers.iter() 314 | .map(|s| s.to_string()) 315 | .map(|a| env.new_string(a).expect("error env.new_string(a)")) 316 | .collect(); 317 | 318 | let j_bitcoin_peer_arr: jobjectArray = env.new_object_array(i32::try_from(j_bitcoin_peer_vec.len()).unwrap(), 319 | env.find_class("java/lang/String").expect("error env.find_class(String)"), 320 | env.new_string("").expect("error env.new_string()").into()) 321 | .expect("error env.new_object_array()"); 322 | 323 | 324 | for i in 0..(j_bitcoin_peer_vec.len()) { 325 | env.set_object_array_element(j_bitcoin_peer_arr, i32::try_from(i).unwrap(), 326 | j_bitcoin_peer_vec[i].into()).expect("error set_object_array_element"); 327 | } 328 | 329 | let j_bitcoin_connections: JValue = jint::try_from(config.bitcoin_connections) 330 | .expect("error converting bitcoin_connections to jint").into(); 331 | 332 | let j_bitcoin_discover: JValue = jboolean::try_from(config.bitcoin_discovery) 333 | .expect("error converting bitcoin_discovery to jboolean").into(); 334 | 335 | // org.bdk.jni.Config 336 | // Optional.of(Config()) 337 | let j_result = env.new_object( 338 | "org/bdk/jni/Config", 339 | "(I[Ljava/lang/String;IZ)V", 340 | &[j_network_enum_ordinal, JValue::Object(j_bitcoin_peer_arr.into()), 341 | j_bitcoin_connections, j_bitcoin_discover], 342 | ).expect("error new_object Config"); 343 | 344 | let j_result = env.call_static_method( 345 | "java/util/Optional", 346 | "of", 347 | "(Ljava/lang/Object;)Ljava/util/Optional;", 348 | &[JValue::Object(j_result)]).expect("error Optional.of(InitResult)") 349 | .l().expect("error converting Optional.of() jvalue to jobject"); 350 | 351 | j_result.into_inner() 352 | } 353 | 354 | fn j_optional_string(env: &JNIEnv, string: &String) -> jobject { 355 | let j_string = env.new_string(string).unwrap(); 356 | 357 | // java.lang.String 358 | // Optional.of(String) 359 | let j_result = env.call_static_method( 360 | "java/util/Optional", 361 | "of", 362 | "(Ljava/lang/Object;)Ljava/util/Optional;", 363 | &[JValue::Object(j_string.into())]).expect("error Optional.of(String)") 364 | .l().expect("error converting Optional.of() jvalue to jobject"); 365 | 366 | j_result.into_inner() 367 | } 368 | 369 | // org.bdk.jni.Address(String address, int networkEnumOrdinal, Optional type) 370 | fn j_address(env: &JNIEnv, address: &Address) -> jobject { 371 | let addr = address.to_string(); 372 | let addr = env.new_string(addr).unwrap(); 373 | let addr = JValue::Object(addr.into()); 374 | let addr_network = jint_from_network(address.network); 375 | let addr_network = JValue::Int(addr_network); 376 | let addr_type = address.address_type().map(|t| t.to_string()); 377 | let addr_type: jobject = match addr_type { 378 | Some(at) => j_optional_string(&env, &at), 379 | None => j_optional_empty(&env) 380 | }; 381 | let addr_type = JValue::Object(addr_type.into()); 382 | 383 | let j_result = env.new_object( 384 | "org/bdk/jni/Address", 385 | "(Ljava/lang/String;ILjava/util/Optional;)V", 386 | &[addr, addr_network, addr_type], 387 | ).expect("error new_object Address"); 388 | 389 | j_result.into_inner() 390 | } 391 | 392 | // org.bdk.jni.WithdrawTx(String txid, long fee) 393 | fn j_withdraw_tx(env: &JNIEnv, withdraw_tx: &WithdrawTx) -> jobject { 394 | let txid = withdraw_tx.txid.to_string(); 395 | let txid = env.new_string(txid).unwrap(); 396 | let fee = i64::try_from(withdraw_tx.fee).unwrap(); 397 | 398 | let j_result = env.new_object( 399 | "org/bdk/jni/WithdrawTx", 400 | "(Ljava/lang/String;J)V", 401 | &[JValue::Object(txid.into()), JValue::Long(fee)], 402 | ).expect("error new_object WithdrawTx"); 403 | 404 | j_result.into_inner() 405 | } 406 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | #![allow(non_snake_case)] 19 | 20 | #[macro_use] 21 | extern crate serde_derive; 22 | 23 | pub mod api; 24 | pub mod blockdownload; 25 | pub mod config; 26 | pub mod db; 27 | pub mod error; 28 | pub mod p2p_bitcoin; 29 | pub mod sendtx; 30 | pub mod store; 31 | pub mod trunk; 32 | pub mod wallet; 33 | 34 | #[cfg(any(feature = "java", feature = "android"))] 35 | pub mod jni; -------------------------------------------------------------------------------- /src/p2p_bitcoin.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use std::{ 19 | collections::HashSet, 20 | net::SocketAddr, 21 | sync::{Arc, atomic::AtomicUsize, mpsc, Mutex}, 22 | thread, 23 | time::SystemTime 24 | }; 25 | use std::collections::HashMap; 26 | use std::pin::Pin; 27 | use std::time::Duration; 28 | 29 | use bitcoin::{ 30 | Block, BlockHeader, 31 | network::{ 32 | constants::Network, 33 | message::{ 34 | NetworkMessage, 35 | RawNetworkMessage, 36 | } 37 | } 38 | }; 39 | use bitcoin_hashes::sha256d; 40 | use futures::{ 41 | executor::ThreadPool, 42 | future, 43 | Future, 44 | FutureExt, Poll as Async, 45 | StreamExt, 46 | task::{Context, SpawnExt} 47 | }; 48 | use futures_timer::Interval; 49 | use log::debug; 50 | use murmel::{ 51 | chaindb::SharedChainDB, 52 | dispatcher::Dispatcher, 53 | dns::dns_seed, 54 | downstream::Downstream, 55 | p2p::{ 56 | BitcoinP2PConfig, P2PControlSender, PeerMessage, PeerMessageReceiver, PeerMessageSender, 57 | PeerSource 58 | }, 59 | p2p::P2P, 60 | ping::Ping, 61 | timeout::Timeout 62 | }; 63 | use murmel::p2p::PeerId; 64 | use rand::{RngCore, thread_rng}; 65 | 66 | use crate::blockdownload::BlockDownload; 67 | use crate::db::SharedDB; 68 | use crate::sendtx::SendTx; 69 | use crate::store::SharedContentStore; 70 | use crate::trunk::Trunk; 71 | 72 | const MAX_PROTOCOL_VERSION: u32 = 70001; 73 | 74 | pub struct P2PBitcoin { 75 | connections: usize, 76 | peers: Vec, 77 | chain_db: SharedChainDB, 78 | network: Network, 79 | db: SharedDB, 80 | content_store: SharedContentStore, 81 | discovery: bool, 82 | birth: u64 83 | } 84 | 85 | impl P2PBitcoin { 86 | pub fn new (network: Network, connections: usize, peers: Vec, discovery: bool, chain_db: SharedChainDB, db: SharedDB, content_store: SharedContentStore, birth: u64) -> P2PBitcoin { 87 | P2PBitcoin {connections, peers, chain_db, network, db, content_store, discovery, birth} 88 | } 89 | pub fn start(&self, executor: &mut ThreadPool) { 90 | let (sender, receiver) = mpsc::sync_channel(100); 91 | 92 | let mut dispatcher = Dispatcher::new(receiver); 93 | 94 | let height = 95 | if let Some(tip) = self.chain_db.read().unwrap().header_tip() { 96 | AtomicUsize::new(tip.stored.height as usize) 97 | } 98 | else { 99 | AtomicUsize::new(0) 100 | }; 101 | 102 | let p2pconfig = BitcoinP2PConfig { 103 | nonce: thread_rng().next_u64(), 104 | network: self.network, 105 | max_protocol_version: MAX_PROTOCOL_VERSION, 106 | user_agent: "bdk 0.1.0".to_string(), 107 | server: false, 108 | height 109 | }; 110 | 111 | let (p2p, p2p_control) = P2P::new( 112 | p2pconfig, 113 | PeerMessageSender::new(sender), 114 | 10); 115 | 116 | let downstream = Arc::new(Mutex::new(BitcoinDriver{store: self.content_store.clone()})); 117 | 118 | let processed_block; 119 | { 120 | let mut db = self.db.lock().unwrap(); 121 | let mut tx = db.transaction(); 122 | processed_block = tx.read_processed().expect("can not read processed block"); 123 | } 124 | if let Some(mut processed_block) = processed_block { 125 | let mut disconnected = Vec::new(); 126 | { 127 | // re-org might have happened while this node was down 128 | let chain_db = self.chain_db.read().unwrap(); 129 | if let Some(mut header) = chain_db.get_header(&processed_block) { 130 | while chain_db.pos_on_trunk(&processed_block).is_none() { 131 | disconnected.push(header.stored.header.clone()); 132 | processed_block = header.stored.header.prev_blockhash; 133 | header = chain_db.get_header(&processed_block).expect("inconsistent header cache"); 134 | } 135 | } else { 136 | panic!("can not find header for last processed block"); 137 | } 138 | } 139 | if !disconnected.is_empty() { 140 | let mut downstream = downstream.lock().unwrap(); 141 | for h in &disconnected { 142 | downstream.block_disconnected(h); 143 | } 144 | } 145 | } 146 | 147 | let timeout = Arc::new(Mutex::new(Timeout::new(p2p_control.clone()))); 148 | 149 | if self.discovery { 150 | dispatcher.add_listener(AddressPoolMaintainer::new(p2p_control.clone(), self.db.clone(), murmel::p2p::SERVICE_BLOCKS)); 151 | } 152 | dispatcher.add_listener(BlockDownload::new(self.chain_db.clone(), p2p_control.clone(), timeout.clone(), downstream, processed_block, self.birth)); 153 | dispatcher.add_listener(Ping::new(p2p_control.clone(), timeout.clone())); 154 | 155 | let sendtx = SendTx::new(p2p_control.clone(), self.db.clone()); 156 | dispatcher.add_listener(sendtx.clone()); 157 | self.content_store.write().unwrap().set_tx_sender(sendtx); 158 | 159 | let mut earlier = HashSet::new(); 160 | let p2p = p2p.clone(); 161 | for addr in &self.peers { 162 | earlier.insert(addr.clone()); 163 | executor.spawn(p2p.add_peer("bitcoin", PeerSource::Outgoing(addr.clone())).map(|_|())).expect("can not spawn task for peers"); 164 | } 165 | 166 | let dns = dns_seed(self.network); 167 | { 168 | let mut db = self.db.lock().unwrap(); 169 | let mut tx = db.transaction(); 170 | for a in &dns { 171 | tx.store_address("bitcoin", a, 0, 0, 0).expect("can not store addresses in db"); 172 | } 173 | tx.commit(); 174 | } 175 | 176 | let keep_connected = KeepConnected { 177 | min_connections: self.connections, 178 | p2p: p2p.clone(), 179 | earlier: Arc::new(Mutex::new(earlier)), 180 | db: self.db.clone(), 181 | dns, 182 | cex: executor.clone() 183 | }; 184 | executor.spawn(Interval::new(Duration::new(10, 0)).for_each(move |_| keep_connected.clone())).expect("can not keep connected"); 185 | 186 | let p2p = p2p.clone(); 187 | let mut cex = executor.clone(); 188 | executor.spawn(future::poll_fn(move |_| { 189 | let needed_services = 0; 190 | p2p.poll_events("bitcoin", needed_services, &mut cex); 191 | Async::Ready(()) 192 | })).expect("can not spawn bitcoin event loop"); 193 | } 194 | 195 | pub fn shutdown(&self) { 196 | self.chain_db.write().unwrap().shutdown() 197 | } 198 | } 199 | 200 | #[derive(Clone)] 201 | struct KeepConnected { 202 | cex: ThreadPool, 203 | dns: Vec, 204 | db: SharedDB, 205 | earlier: Arc>>, 206 | p2p: Arc>, 207 | min_connections: usize 208 | } 209 | 210 | impl Future for KeepConnected { 211 | type Output = (); 212 | 213 | fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Async { 214 | if self.p2p.n_connected_peers() < self.min_connections { 215 | let choice; 216 | { 217 | self.p2p.connected_peers().iter().for_each(|a| {self.earlier.lock().unwrap().insert(a.clone());} ); 218 | choice = self.db.lock().unwrap().transaction().get_an_address("bitcoin", self.earlier.clone()).expect("can not read addresses from db") 219 | } 220 | if let Some(choice) = choice { 221 | self.earlier.lock().unwrap().insert(choice); 222 | let add = self.p2p.add_peer("bitcoin", PeerSource::Outgoing(choice)).map(|_| ()); 223 | self.cex.spawn(add).expect("can not add peer for outgoing connection"); 224 | } 225 | else { 226 | let eligible = self.dns.iter().cloned().filter(|a| !self.earlier.lock().unwrap().contains(&a)).collect::>(); 227 | if eligible.len() > 0 { 228 | let mut rng = thread_rng(); 229 | let choice = eligible[(rng.next_u32() as usize) % eligible.len()]; 230 | self.earlier.lock().unwrap().insert(choice); 231 | let add = self.p2p.add_peer("bitcoin", PeerSource::Outgoing(choice)).map(|_| ()); 232 | self.cex.spawn(add).expect("can not add peer for outgoing connection"); 233 | } 234 | } 235 | } 236 | Async::Ready(()) 237 | } 238 | } 239 | 240 | struct AddressPoolMaintainer { 241 | db: SharedDB, 242 | addresses: HashMap, 243 | needed_services: u64 244 | } 245 | 246 | impl AddressPoolMaintainer { 247 | pub fn new(p2p: P2PControlSender, db: SharedDB, needed_services: u64) -> PeerMessageSender { 248 | let (sender, receiver) = mpsc::sync_channel(p2p.back_pressure); 249 | let mut m = AddressPoolMaintainer { db, addresses: HashMap::new(), needed_services }; 250 | 251 | thread::Builder::new().name("address pool".to_string()).spawn(move || { m.run(receiver) }).unwrap(); 252 | 253 | PeerMessageSender::new(sender) 254 | } 255 | 256 | fn run(&mut self, receiver: PeerMessageReceiver) { 257 | while let Ok(msg) = receiver.recv () { 258 | match msg { 259 | PeerMessage::Connected(pid, addr) => { 260 | if let Some(address) = addr { 261 | self.addresses.insert(pid, address); 262 | let mut db = self.db.lock().unwrap(); 263 | let mut tx = db.transaction(); 264 | debug!("store successful connection to {} peer={}", &address, pid); 265 | let now = SystemTime::now().duration_since( 266 | SystemTime::UNIX_EPOCH).unwrap().as_secs(); 267 | tx.store_address("bitcoin", &address, now, now, 0).unwrap(); 268 | tx.commit(); 269 | } 270 | } 271 | PeerMessage::Disconnected(pid, banned) => { 272 | if banned { 273 | if let Some(address) = self.addresses.remove(&pid) { 274 | let mut db = self.db.lock().unwrap(); 275 | let mut tx = db.transaction(); 276 | let now = SystemTime::now().duration_since( 277 | SystemTime::UNIX_EPOCH).unwrap().as_secs(); 278 | debug!("store ban of {} peer={}", &address, pid); 279 | tx.store_address("bitcoin", &address, 0, 0, now).unwrap(); 280 | tx.commit(); 281 | } 282 | } 283 | } 284 | PeerMessage::Incoming(pid, msg) => { 285 | match msg { 286 | NetworkMessage::Addr(av) => { 287 | let mut db = self.db.lock().unwrap(); 288 | let mut tx = db.transaction(); 289 | for (last_seen, a) in &av { 290 | if (*last_seen as u64) < (SystemTime::now().duration_since(SystemTime::UNIX_EPOCH).unwrap().as_secs()) && 291 | a.services & self.needed_services == self.needed_services { 292 | if let Ok(addr) = a.socket_addr() { 293 | debug!("received and stored address {} peer={}", &addr, pid); 294 | tx.store_address("bitcoin", &addr, 0, *last_seen as u64, 0).unwrap(); 295 | } 296 | } 297 | } 298 | tx.commit(); 299 | } 300 | _ => { } 301 | } 302 | }, 303 | _ => {} 304 | } 305 | } 306 | } 307 | } 308 | 309 | struct BitcoinDriver { 310 | store: SharedContentStore 311 | } 312 | 313 | impl Downstream for BitcoinDriver { 314 | fn block_connected(&mut self, block: &Block, height: u32) { 315 | self.store.write().unwrap().block_connected(block, height).expect("can not add block"); 316 | } 317 | 318 | fn header_connected(&mut self, block: &BlockHeader, height: u32) { 319 | self.store.write().unwrap().add_header(height, block).expect("can not add header"); 320 | } 321 | 322 | fn block_disconnected(&mut self, header: &BlockHeader) { 323 | self.store.write().unwrap().unwind_tip(header).expect("can not unwind tip"); 324 | } 325 | } 326 | 327 | pub struct ChainDBTrunk { 328 | pub chaindb: SharedChainDB 329 | } 330 | 331 | impl Trunk for ChainDBTrunk { 332 | fn is_on_trunk(&self, block_hash: &sha256d::Hash) -> bool { 333 | self.chaindb.read().unwrap().pos_on_trunk(block_hash).is_some() 334 | } 335 | 336 | fn get_header(&self, block_hash: &sha256d::Hash) -> Option { 337 | if let Some(cached) = self.chaindb.read().unwrap().get_header(block_hash) { 338 | return Some(cached.stored.header.clone()) 339 | } 340 | None 341 | } 342 | 343 | fn get_header_for_height(&self, height: u32) -> Option { 344 | if let Some(cached) = self.chaindb.read().unwrap().get_header_for_height(height) { 345 | return Some(cached.stored.header.clone()); 346 | } 347 | None 348 | } 349 | 350 | fn get_height(&self, block_hash: &sha256d::Hash) -> Option { 351 | self.chaindb.read().unwrap().pos_on_trunk(block_hash) 352 | } 353 | 354 | fn get_tip(&self) -> Option { 355 | if let Some(cached) = self.chaindb.read().unwrap().header_tip() { 356 | return Some(cached.stored.header.clone()); 357 | } 358 | None 359 | } 360 | 361 | fn len(&self) -> u32 { 362 | if let Some(cached) = self.chaindb.read().unwrap().header_tip() { 363 | return cached.stored.height 364 | } 365 | 0 366 | } 367 | } 368 | 369 | -------------------------------------------------------------------------------- /src/sendtx.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | use std::{ 18 | collections::HashMap, 19 | sync::mpsc, 20 | thread, 21 | time::SystemTime 22 | }; 23 | 24 | use bitcoin::network::message::NetworkMessage; 25 | use bitcoin::network::message_blockdata::{Inventory, InvType}; 26 | use bitcoin::Transaction; 27 | use bitcoin_hashes::sha256d; 28 | use log::debug; 29 | use lru_cache::LruCache; 30 | use murmel::p2p::{P2PControlSender, PeerMessage, PeerMessageReceiver, PeerMessageSender}; 31 | 32 | use crate::db::SharedDB; 33 | 34 | pub struct SendTx { 35 | p2p: P2PControlSender, 36 | db: SharedDB, 37 | cache: LruCache 38 | } 39 | 40 | const CACHE_SIZE: usize=1000; 41 | 42 | impl SendTx { 43 | pub fn new(p2p: P2PControlSender, db: SharedDB) -> PeerMessageSender { 44 | let (sender, receiver) = mpsc::sync_channel(p2p.back_pressure); 45 | 46 | let mut own_unconfirmed = HashMap::new(); 47 | { 48 | let mut db = db.lock().unwrap(); 49 | let tx = db.transaction(); 50 | for (t, _) in tx.read_unconfirmed().expect("can not read unconfirmed transactions") { 51 | own_unconfirmed.insert(t.txid(), t); 52 | } 53 | } 54 | 55 | let mut txsender = SendTx { p2p, db, cache: LruCache::new(CACHE_SIZE) }; 56 | 57 | thread::Builder::new().name("sendtx".to_string()).spawn(move || { txsender.run(receiver) }).unwrap(); 58 | 59 | PeerMessageSender::new(sender) 60 | } 61 | 62 | fn run(&mut self, receiver: PeerMessageReceiver) { 63 | let mut last_announcement = SystemTime::now(); 64 | while let Ok(msg) = receiver.recv() { 65 | match msg { 66 | PeerMessage::Incoming(pid, msg) => { 67 | match msg { 68 | NetworkMessage::GetData(ref inv) => { 69 | let txs = inv.iter().filter_map(|i| if i.inv_type == InvType::Transaction { Some(i.hash) } else { None }).collect::>(); 70 | if !txs.is_empty() { 71 | let txs = txs.iter().filter_map(|h| { 72 | if let Some(cached) = self.cache.get_mut(h) { 73 | self.p2p.send_network(pid, NetworkMessage::Tx(cached.clone())); 74 | None 75 | } else { 76 | Some(*h) 77 | } 78 | }).collect::>(); 79 | 80 | if !txs.is_empty() { 81 | let mut db = self.db.lock().unwrap(); 82 | let tx = db.transaction(); 83 | for (t, _) in tx.read_unconfirmed().expect("can not read unconfirmed transactions").iter().filter(|(t, _)| txs.contains(&t.txid())) { 84 | self.p2p.send_network(pid, NetworkMessage::Tx(t.clone())); 85 | debug!("sent our transaction {} at request of peer={}", t.txid(), pid); 86 | } 87 | } 88 | } 89 | } 90 | NetworkMessage::Inv(ref inv) => { 91 | let have_not = inv.iter().filter(|i| i.inv_type == InvType::Transaction && !self.cache.contains_key(&i.hash)).cloned().collect::>(); 92 | if !have_not.is_empty() { 93 | self.p2p.send_network(pid, NetworkMessage::GetData(have_not)); 94 | } 95 | } 96 | NetworkMessage::Tx(ref tx) => { 97 | if self.cache.insert(tx.txid(), tx.clone()).is_none() { 98 | self.p2p.send_random_network(NetworkMessage::Inv(vec!(Inventory { inv_type: InvType::Transaction, hash: tx.txid() }))); 99 | } 100 | } 101 | _ => {} 102 | } 103 | }, 104 | PeerMessage::Outgoing(msg) => { 105 | match msg { 106 | NetworkMessage::Tx(ref transaction) => { 107 | let txid = transaction.txid(); 108 | self.p2p.send_random_network(NetworkMessage::Inv(vec!(Inventory { hash: txid, inv_type: InvType::Transaction }))); 109 | }, 110 | _ => {} 111 | } 112 | } 113 | _ => {} 114 | } 115 | if SystemTime::now().duration_since(last_announcement).unwrap().as_secs() > 60 { 116 | let mut db = self.db.lock().unwrap(); 117 | let tx = db.transaction(); 118 | for (transaction, _) in tx.read_unconfirmed().expect("can not read unconfirmed transactions") { 119 | if !self.cache.contains_key(&transaction.txid()) { 120 | if let Some(peer) = self.p2p.send_random_network(NetworkMessage::Inv(vec!(Inventory { hash: transaction.txid(), inv_type: InvType::Transaction }))) { 121 | debug!("announced our transaction {} to peer={}", transaction.txid(), peer); 122 | } 123 | } 124 | } 125 | last_announcement = SystemTime::now(); 126 | } 127 | } 128 | } 129 | } -------------------------------------------------------------------------------- /src/store.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | //! store 19 | 20 | use std::sync::{Arc, RwLock}; 21 | 22 | use bitcoin::{Address, BitcoinHash, Block, BlockHeader, PublicKey, Script, Transaction}; 23 | use bitcoin::{ 24 | blockdata::{ 25 | opcodes::all, 26 | script::Builder, 27 | }, 28 | network::constants::Network, 29 | }; 30 | use bitcoin::network::message::NetworkMessage; 31 | use bitcoin_hashes::{sha256, sha256d}; 32 | use log::{debug, info}; 33 | use murmel::p2p::{PeerMessage, PeerMessageSender}; 34 | 35 | use crate::db::SharedDB; 36 | use crate::error::Error; 37 | use crate::trunk::Trunk; 38 | use crate::wallet::Wallet; 39 | 40 | pub type SharedContentStore = Arc>; 41 | 42 | /// the distributed content storage 43 | pub struct ContentStore { 44 | trunk: Arc, 45 | db: SharedDB, 46 | wallet: Wallet, 47 | txout: Option>, 48 | stopped: bool 49 | } 50 | 51 | impl ContentStore { 52 | /// new content store 53 | pub fn new(db: SharedDB, trunk: Arc, wallet: Wallet) -> Result { 54 | Ok(ContentStore { 55 | trunk, 56 | db, 57 | wallet, 58 | txout: None, 59 | stopped: false 60 | }) 61 | } 62 | 63 | pub fn set_stopped(&mut self, stopped: bool) { 64 | self.stopped = stopped; 65 | } 66 | 67 | pub fn get_stopped(& self) -> bool { 68 | self.stopped 69 | } 70 | 71 | pub fn set_tx_sender(&mut self, txout: PeerMessageSender) { 72 | self.txout = Some(txout); 73 | } 74 | 75 | pub fn balance(&self) -> Vec { 76 | vec!(self.wallet.balance(), self.wallet.available_balance(self.trunk.len(), |h| self.trunk.get_height(h))) 77 | } 78 | 79 | pub fn deposit_address(&mut self) -> Address { 80 | self.wallet.master.get_mut((0, 0)).expect("can not find 0/0 account") 81 | .next_key().expect("can not generate receiver address in 0/0").address.clone() 82 | } 83 | 84 | pub fn fund(&mut self, id: &sha256::Hash, term: u16, amount: u64, fee_per_vbyte: u64, passpharse: String) -> Result<(Transaction, PublicKey, u64), Error> { 85 | let (transaction, funder, fee) = self.wallet.fund(id, term, passpharse, fee_per_vbyte, amount, self.trunk.clone(), 86 | |pk, term| Self::funding_script(pk, term.unwrap()))?; 87 | let mut db = self.db.lock().unwrap(); 88 | let mut tx = db.transaction(); 89 | tx.store_account(&self.wallet.master.get((1, 0)).unwrap())?; 90 | tx.store_txout(&transaction, Some((&funder, id, term))).expect("can not store outgoing transaction"); 91 | tx.commit(); 92 | if let Some(ref txout) = self.txout { 93 | txout.send(PeerMessage::Outgoing(NetworkMessage::Tx(transaction.clone()))); 94 | } 95 | info!("Wallet balance: {} satoshis {} available", self.wallet.balance(), self.wallet.available_balance(self.trunk.len(), |h| self.trunk.get_height(h))); 96 | Ok((transaction, funder, fee)) 97 | } 98 | 99 | pub fn funding_script(tweaked: &PublicKey, term: u16) -> Script { 100 | Builder::new() 101 | .push_int(term as i64) 102 | .push_opcode(all::OP_CSV) 103 | .push_opcode(all::OP_DROP) 104 | .push_slice(tweaked.to_bytes().as_slice()) 105 | .push_opcode(all::OP_CHECKSIG) 106 | .into_script() 107 | } 108 | 109 | pub fn funding_address(tweaked: &PublicKey, term: u16) -> Address { 110 | Address::p2wsh(&Self::funding_script(tweaked, term), Network::Bitcoin) 111 | } 112 | 113 | pub fn withdraw(&mut self, passphrase: String, address: Address, fee_per_vbyte: u64, amount: Option) -> Result<(Transaction, u64), Error> { 114 | let (transaction, fee) = self.wallet.withdraw(passphrase, address, fee_per_vbyte, amount, self.trunk.clone())?; 115 | let mut db = self.db.lock().unwrap(); 116 | let mut tx = db.transaction(); 117 | tx.store_account(&self.wallet.master.get((0, 1)).unwrap())?; 118 | tx.store_txout(&transaction, None).expect("can not store outgoing transaction"); 119 | tx.commit(); 120 | if let Some(ref txout) = self.txout { 121 | txout.send(PeerMessage::Outgoing(NetworkMessage::Tx(transaction.clone()))); 122 | } 123 | info!("Wallet balance: {} satoshis {} available", self.wallet.balance(), self.wallet.available_balance(self.trunk.len(), |h| self.trunk.get_height(h))); 124 | Ok((transaction, fee)) 125 | } 126 | 127 | pub fn get_tip(&self) -> Option { 128 | if let Some(header) = self.trunk.get_tip() { 129 | return Some(header.bitcoin_hash()); 130 | } 131 | None 132 | } 133 | 134 | pub fn block_connected(&mut self, block: &Block, height: u32) -> Result<(), Error> { 135 | debug!("processing block {} {}", height, block.header.bitcoin_hash()); 136 | // let newly_confirmed_publication; 137 | { 138 | let mut db = self.db.lock().unwrap(); 139 | let mut tx = db.transaction(); 140 | 141 | if self.wallet.process(block) { 142 | tx.store_coins(&self.wallet.coins())?; 143 | info!("New wallet balance {} satoshis {} available", self.wallet.balance(), self.wallet.available_balance(self.trunk.len(), |h| self.trunk.get_height(h))); 144 | } 145 | tx.store_processed(&block.header.bitcoin_hash())?; 146 | tx.commit(); 147 | } 148 | Ok(()) 149 | } 150 | 151 | /// add a header to the tip of the chain 152 | pub fn add_header(&mut self, height: u32, header: &BlockHeader) -> Result<(), Error> { 153 | info!("new chain tip at height {} {}", height, header.bitcoin_hash()); 154 | Ok(()) 155 | } 156 | 157 | /// unwind the tip 158 | pub fn unwind_tip(&mut self, header: &BlockHeader) -> Result<(), Error> { 159 | info!("unwind tip {}", header.bitcoin_hash()); 160 | // let mut deleted_some = false; 161 | let mut db = self.db.lock().unwrap(); 162 | let mut tx = db.transaction(); 163 | tx.store_processed(&header.prev_blockhash)?; 164 | tx.commit(); 165 | self.wallet.unwind_tip(&header.bitcoin_hash()); 166 | return Ok(()); 167 | } 168 | } 169 | 170 | #[cfg(test)] 171 | mod test { 172 | use std::{ 173 | str::FromStr, 174 | sync::{Arc, Mutex}, 175 | }; 176 | use std::time::{SystemTime, UNIX_EPOCH}; 177 | 178 | use bitcoin::{Address, BitcoinHash, Block, blockdata::opcodes::all, BlockHeader, network::constants::Network, OutPoint, Transaction, TxIn, TxOut, util::bip32::ExtendedPubKey}; 179 | use bitcoin::blockdata::constants::genesis_block; 180 | use bitcoin::blockdata::script::Builder; 181 | use bitcoin::util::hash::MerkleRoot; 182 | use bitcoin_hashes::sha256d; 183 | use bitcoin_wallet::account::{Account, AccountAddressType, Unlocker}; 184 | 185 | use crate::db::DB; 186 | use crate::trunk::Trunk; 187 | use crate::wallet::Wallet; 188 | 189 | use super::ContentStore; 190 | 191 | const NEW_COINS: u64 = 5000000000; 192 | const PASSPHRASE: &str = "whatever"; 193 | 194 | struct TestTrunk { 195 | trunk: Arc>> 196 | } 197 | 198 | impl TestTrunk { 199 | fn extend(&self, header: &BlockHeader) { 200 | self.trunk.lock().unwrap().push(header.clone()); 201 | } 202 | } 203 | 204 | impl Trunk for TestTrunk { 205 | fn is_on_trunk(&self, block_hash: &sha256d::Hash) -> bool { 206 | self.trunk.lock().unwrap().iter().any(|h| h.bitcoin_hash() == *block_hash) 207 | } 208 | 209 | fn get_header(&self, block_hash: &sha256d::Hash) -> Option { 210 | self.trunk.lock().unwrap().iter().find(|h| h.bitcoin_hash() == *block_hash).map(|h| h.clone()) 211 | } 212 | 213 | fn get_header_for_height(&self, height: u32) -> Option { 214 | self.trunk.lock().unwrap().get(height as usize).map(|h| h.clone()) 215 | } 216 | 217 | fn get_height(&self, block_hash: &sha256d::Hash) -> Option { 218 | self.trunk.lock().unwrap().iter().enumerate().find_map(|(i, h)| if h.bitcoin_hash() == *block_hash { Some(i as u32) } else { None }) 219 | } 220 | 221 | fn get_tip(&self) -> Option { 222 | let len = self.trunk.lock().unwrap().len(); 223 | if len > 0 { 224 | self.trunk.lock().unwrap().get(len - 1).map(|h| h.clone()) 225 | } else { 226 | None 227 | } 228 | } 229 | 230 | fn len(&self) -> u32 { 231 | self.trunk.lock().unwrap().len() as u32 232 | } 233 | } 234 | 235 | fn new_store(trunk: Arc) -> ContentStore { 236 | let mut memdb = DB::memory().unwrap(); 237 | { 238 | let mut tx = memdb.transaction(); 239 | tx.create_tables(); 240 | tx.commit(); 241 | } 242 | let mut wallet = Wallet::from_encrypted( 243 | hex::decode("0e05ba48bb0fdc7285dc9498202aeee5e1777ac4f55072b30f15f6a8632ad0f3fde1c41d9e162dbe5d3153282eaebd081cf3b3312336fc56f5dd18a2df6ea48c1cdd11a1ed11281cd2e0f864f02e5bed5ab03326ed24e43b8a184acff9cb4e730db484e33f2b24295a97b2ca87871a69384eb64d4160ce8b3e8b4d90234040970e531d4333a8979dbe533c2b2668bf43b6607b2d24c5b42765ebfdd075fd173c").unwrap().as_slice(), 244 | ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4XKz4vgwBmnnVmA7EgWhnXvimQ4krq94yUgcSSbroi4uC1xbZ3UGMxG9M2utmaPjdpMrWW2uKRY9Mj4DZWrrY8M4pry8shsK").unwrap(), 245 | 1567260002); 246 | let mut unlocker = Unlocker::new_for_master(&wallet.master, PASSPHRASE).unwrap(); 247 | wallet.master.add_account(Account::new(&mut unlocker, AccountAddressType::P2WPKH, 0, 0, 10).unwrap()); 248 | wallet.master.add_account(Account::new(&mut unlocker, AccountAddressType::P2WPKH, 0, 1, 10).unwrap()); 249 | wallet.master.add_account(Account::new(&mut unlocker, AccountAddressType::P2WSH(4711), 1, 0, 0).unwrap()); 250 | 251 | ContentStore::new(Arc::new(Mutex::new(memdb)), trunk, wallet).unwrap() 252 | } 253 | 254 | fn new_block(prev: &sha256d::Hash) -> Block { 255 | Block { 256 | header: BlockHeader { 257 | version: 1, 258 | time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u32, 259 | nonce: 0, 260 | bits: 0x1d00ffff, 261 | prev_blockhash: prev.clone(), 262 | merkle_root: sha256d::Hash::default(), 263 | }, 264 | txdata: Vec::new(), 265 | } 266 | } 267 | 268 | fn coin_base(miner: &Address, height: u32) -> Transaction { 269 | Transaction { 270 | version: 2, 271 | lock_time: 0, 272 | input: vec!(TxIn { 273 | sequence: 0xffffffff, 274 | witness: Vec::new(), 275 | previous_output: OutPoint { txid: sha256d::Hash::default(), vout: 0 }, 276 | script_sig: Builder::new().push_int(height as i64).into_script(), 277 | }), 278 | output: vec!(TxOut { 279 | value: NEW_COINS, 280 | script_pubkey: miner.script_pubkey(), 281 | }), 282 | } 283 | } 284 | 285 | fn add_tx(block: &mut Block, tx: Transaction) { 286 | block.txdata.push(tx); 287 | block.header.merkle_root = block.merkle_root(); 288 | } 289 | 290 | fn mine(store: &ContentStore, height: u32, miner: &Address) -> Block { 291 | let mut block = new_block(&store.trunk.get_tip().unwrap().bitcoin_hash()); 292 | add_tx(&mut block, coin_base(miner, height)); 293 | block 294 | } 295 | } -------------------------------------------------------------------------------- /src/trunk.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | use bitcoin::BlockHeader; 19 | use bitcoin_hashes::sha256d; 20 | 21 | /// access the current trunk (longest chain of headers as defined by POW) 22 | pub trait Trunk { 23 | fn is_on_trunk (&self, block_hash: &sha256d::Hash) -> bool; 24 | fn get_header (&self, block_hash: &sha256d::Hash) -> Option; 25 | fn get_header_for_height (&self, height: u32) -> Option; 26 | fn get_height (&self, block_hash: &sha256d::Hash) -> Option; 27 | fn get_tip (&self) -> Option; 28 | fn len(&self) -> u32; 29 | } 30 | -------------------------------------------------------------------------------- /src/wallet.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Tamas Blummer 3 | * Copyright 2020 BDK Team 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | use std::sync::Arc; 18 | use std::time::{SystemTime, UNIX_EPOCH}; 19 | 20 | use bitcoin::{Address, Block, PublicKey, Script, SigHashType, Transaction, TxIn, TxOut}; 21 | use bitcoin::consensus::serialize; 22 | use bitcoin::network::constants::Network; 23 | use bitcoin::util::bip32::ExtendedPubKey; 24 | use bitcoin_hashes::{sha256, sha256d}; 25 | use bitcoin_wallet::account::{Account, AccountAddressType, MasterAccount, Unlocker}; 26 | use bitcoin_wallet::coins::Coins; 27 | use bitcoin_wallet::mnemonic::Mnemonic; 28 | use bitcoin_wallet::proved::ProvedTransaction; 29 | use log::{debug, error}; 30 | use rand::{RngCore, thread_rng}; 31 | 32 | use crate::error::Error; 33 | use crate::trunk::Trunk; 34 | 35 | pub const KEY_LOOK_AHEAD: u32 = 10; 36 | const KEY_PURPOSE: u32 = 0xb1ad; 37 | const DUST: u64 = 546; 38 | const MAX_FEE_PER_VBYTE: u64 = 100; 39 | const MIN_FEE_PER_VBYTE: u64 = 1; 40 | const MAX_TERM: u16 = 6 * 24 * 30; 41 | // approx. one month. 42 | const RBF: u32 = 0xffffffff - 2; 43 | 44 | pub struct Wallet { 45 | pub coins: Coins, 46 | pub master: MasterAccount, 47 | } 48 | 49 | impl Wallet { 50 | pub fn master_public(&self) -> &ExtendedPubKey { 51 | &self.master.master_public() 52 | } 53 | 54 | pub fn encrypted(&self) -> &Vec { 55 | &self.master.encrypted() 56 | } 57 | 58 | pub fn birth(&self) -> u64 { 59 | self.master.birth() 60 | } 61 | 62 | pub fn coins(&self) -> &Coins { 63 | &self.coins 64 | } 65 | 66 | pub fn balance(&self) -> u64 { 67 | self.coins.confirmed_balance() + self.coins.unconfirmed_balance() 68 | } 69 | 70 | pub fn confirmed_balance(&self) -> u64 { 71 | self.coins.confirmed_balance() 72 | } 73 | 74 | pub fn unconfirmed_balance(&self) -> u64 { 75 | self.coins.unconfirmed_balance() 76 | } 77 | 78 | pub fn available_balance(&self, height: u32, height_for_block: H) -> u64 79 | where H: Fn(&sha256d::Hash) -> Option { 80 | self.coins.available_balance(height, height_for_block) 81 | } 82 | 83 | pub fn unwind_tip(&mut self, block_hash: &sha256d::Hash) { 84 | self.coins.unwind_tip(block_hash) 85 | } 86 | 87 | pub fn rescan(&mut self) { 88 | self.coins = Coins::new(); 89 | } 90 | 91 | pub fn process(&mut self, block: &Block) -> bool { 92 | self.coins.process(&mut self.master, block) 93 | } 94 | 95 | pub fn prove(&self, txid: &sha256d::Hash) -> Option<&ProvedTransaction> { 96 | self.coins.proofs().get(txid) 97 | } 98 | 99 | pub fn fund(&mut self, id: &sha256::Hash, mut term: u16, passpharse: String, mut fee_per_vbyte: u64, amount: u64, trunk: Arc, scripter: W) -> Result<(Transaction, PublicKey, u64), Error> 100 | where W: FnOnce(&PublicKey, Option) -> Script { 101 | let network = self.master.master_public().network; 102 | let mut unlocker = Unlocker::new( 103 | self.master.encrypted(), passpharse.as_str(), 104 | network, Some(self.master.master_public()))?; 105 | fee_per_vbyte = std::cmp::min(MAX_FEE_PER_VBYTE, std::cmp::max(MIN_FEE_PER_VBYTE, fee_per_vbyte)); 106 | term = std::cmp::min(MAX_TERM, term); 107 | let mut fee = 0; 108 | let change_address = self.master.get_mut((0, 1)).unwrap().next_key().unwrap().address.clone(); 109 | let height = trunk.len(); 110 | let coins = self.coins.choose_inputs(amount, height, |h| trunk.get_height(h)); 111 | let total_input = coins.iter().map(|(_, c, _)| c.output.value).sum::(); 112 | let contract_address; 113 | let funder; 114 | { 115 | let commit_account = self.master.get_mut((1, 0)).unwrap(); 116 | let kix = commit_account.add_script_key(scripter, Some(&id[..]), Some(term)).expect("can not commit to ad"); 117 | contract_address = commit_account.get_key(kix).unwrap().address.clone(); 118 | funder = commit_account.compute_base_public_key(kix).expect("can not compute base public key"); 119 | } 120 | if amount > total_input { 121 | return Err(Error::Unsupported("insufficient funds")); 122 | } 123 | let mut tx = Transaction { 124 | input: coins.iter().map(|(point, coin, h)| 125 | TxIn { 126 | previous_output: point.clone(), 127 | script_sig: Script::new(), 128 | sequence: if let Some(csv) = coin.derivation.csv { 129 | std::cmp::min(csv as u32, height - *h) 130 | } else { RBF }, 131 | witness: vec![], 132 | }).collect(), 133 | output: Vec::new(), 134 | version: 2, 135 | lock_time: 0, 136 | }; 137 | loop { 138 | tx.output.clear(); 139 | if amount - fee > DUST { 140 | tx.output.push(TxOut { 141 | value: amount - fee, 142 | script_pubkey: contract_address.script_pubkey(), 143 | }); 144 | } else { 145 | return Err(Error::Unsupported("withdraw amount is less than the fees needed (+DUST limit)")); 146 | } 147 | if total_input > amount && (total_input - amount) > DUST { 148 | tx.output.insert((thread_rng().next_u32() % 2) as usize, TxOut { 149 | value: total_input - amount, 150 | script_pubkey: change_address.script_pubkey(), 151 | }); 152 | } 153 | if self.master.sign(&mut tx, SigHashType::All, 154 | &|point| { 155 | coins.iter().find(|(o, _, _)| *o == *point).map(|(_, c, _)| c.output.clone()) 156 | }, &mut unlocker)? 157 | != tx.input.len() { 158 | error!("could not sign all inputs of our transaction {:?} {}", tx, hex::encode(serialize(&tx))); 159 | return Err(Error::Unsupported("could not sign for all inputs")); 160 | } 161 | if fee == 0 { 162 | fee = (tx.get_weight() as u64 * fee_per_vbyte + 3) / 4; 163 | } else { 164 | debug!("compiled transaction to withdraw {} fee {}", amount, fee); 165 | #[cfg(feature = "bitcoinconsensus")] 166 | { 167 | match tx.verify(|o| coins.iter().find_map(|(p, c, _)| if *p == *o { Some(c.output.clone()) } else { None })) { 168 | Ok(()) => {} 169 | Err(e) => { 170 | error!("our transaction does not verify {:?} {}", tx, hex::encode(serialize(&tx))); 171 | return Err(Error::Script(e)); 172 | } 173 | } 174 | } 175 | break; 176 | } 177 | } 178 | self.coins.process_unconfirmed_transaction(&mut self.master, &tx); 179 | Ok((tx, funder, fee)) 180 | } 181 | 182 | pub fn withdraw(&mut self, passphrase: String, address: Address, mut fee_per_vbyte: u64, amount: Option, trunk: Arc) -> Result<(Transaction, u64), Error> { 183 | let network = self.master.master_public().network; 184 | let mut unlocker = Unlocker::new( 185 | self.master.encrypted(), passphrase.as_str(), 186 | network, Some(self.master.master_public()))?; 187 | let height = trunk.len(); 188 | let balance = self.available_balance(height, |h| trunk.get_height(h)); 189 | let amount = amount.unwrap_or(balance); 190 | fee_per_vbyte = std::cmp::min(MAX_FEE_PER_VBYTE, std::cmp::max(MIN_FEE_PER_VBYTE, fee_per_vbyte)); 191 | let mut fee = 0; 192 | let change_address = self.master.get_mut((0, 1)).unwrap().next_key().unwrap().address.clone(); 193 | let coins = self.coins.choose_inputs(amount, height, |h| trunk.get_height(h)); 194 | let total_input = coins.iter().map(|(_, c, _)| c.output.value).sum::(); 195 | if amount > total_input { 196 | return Err(Error::Unsupported("insufficient funds")); 197 | } 198 | let mut tx = Transaction { 199 | input: coins.iter().map(|(point, coin, h)| 200 | TxIn { 201 | previous_output: point.clone(), 202 | script_sig: Script::new(), 203 | sequence: if let Some(csv) = coin.derivation.csv { 204 | std::cmp::min(csv as u32, height - *h) 205 | } else { RBF }, 206 | witness: vec![], 207 | }).collect(), 208 | output: Vec::new(), 209 | version: 2, 210 | lock_time: 0, 211 | }; 212 | loop { 213 | tx.output.clear(); 214 | if amount - fee > DUST { 215 | tx.output.push(TxOut { 216 | value: amount - fee, 217 | script_pubkey: address.script_pubkey(), 218 | }); 219 | } else { 220 | return Err(Error::Unsupported("withdraw amount is less than the fees needed (+DUST limit)")); 221 | } 222 | if total_input > amount && (total_input - amount) > DUST { 223 | tx.output.insert((thread_rng().next_u32() % 2) as usize, TxOut { 224 | value: total_input - amount, 225 | script_pubkey: change_address.script_pubkey(), 226 | }); 227 | } 228 | if self.master.sign(&mut tx, SigHashType::All, 229 | &|point| { 230 | coins.iter().find(|(o, _, _)| *o == *point).map(|(_, c, _)| c.output.clone()) 231 | }, &mut unlocker)? 232 | != tx.input.len() { 233 | error!("could not sign all inputs of our transaction {:?} {}", tx, hex::encode(serialize(&tx))); 234 | return Err(Error::Unsupported("could not sign for all inputs")); 235 | } 236 | if fee == 0 { 237 | fee = (tx.get_weight() as u64 * fee_per_vbyte + 3) / 4; 238 | } else { 239 | debug!("compiled transaction to withdraw {} fee {}", amount, fee); 240 | #[cfg(feature = "bitcoinconsensus")] 241 | { 242 | match tx.verify(|o| coins.iter().find_map(|(p, c, _)| if *p == *o { Some(c.output.clone()) } else { None })) { 243 | Ok(()) => {} 244 | Err(e) => { 245 | error!("our transaction does not verify {:?} {}", tx, hex::encode(serialize(&tx))); 246 | return Err(Error::Script(e)); 247 | } 248 | } 249 | } 250 | break; 251 | } 252 | } 253 | self.coins.process_unconfirmed_transaction(&mut self.master, &tx); 254 | Ok((tx, fee)) 255 | } 256 | 257 | pub fn from_storage(coins: Coins, mut master: MasterAccount) -> Wallet { 258 | for (_, coin) in coins.confirmed() { 259 | let ref d = coin.derivation; 260 | master.get_mut((d.account, d.sub)).unwrap().do_look_ahead(Some(d.kix)).expect("can not look ahead of storage"); 261 | } 262 | for (_, coin) in coins.unconfirmed() { 263 | let ref d = coin.derivation; 264 | master.get_mut((d.account, d.sub)).unwrap().do_look_ahead(Some(d.kix)).expect("can not look ahead of storage"); 265 | } 266 | Wallet { coins: coins, master } 267 | } 268 | 269 | pub fn from_encrypted(encrypted: &[u8], public_master_key: ExtendedPubKey, birth: u64) -> Wallet { 270 | let master = MasterAccount::from_encrypted(encrypted, public_master_key, birth); 271 | Wallet { coins: Coins::new(), master } 272 | } 273 | 274 | pub fn new(bitcoin_network: Network, passphrase: &str, pd_passphrase: Option<&str>) -> (Mnemonic, Address, Wallet) { 275 | assert!(passphrase.len() >= 8, "Password should have at least 8 characters"); 276 | let mut entropy = [0u8; 16]; 277 | thread_rng().fill_bytes(&mut entropy); 278 | let mnemonic = Mnemonic::new(&entropy).expect("can not create mnemonic"); 279 | let mut master = MasterAccount::from_mnemonic(&mnemonic, SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs(), 280 | bitcoin_network, passphrase, pd_passphrase).expect("can not generate wallet"); 281 | let mut unlocker = Unlocker::new(master.encrypted().as_slice(), 282 | passphrase, bitcoin_network, 283 | Some(&master.master_public())).expect("Internal error in wallet generation"); 284 | let receiver = Account::new(&mut unlocker, AccountAddressType::P2SHWPKH, 0, 0, KEY_LOOK_AHEAD) 285 | .expect("can not create receiver account"); 286 | master.add_account(receiver); 287 | let change = Account::new(&mut unlocker, AccountAddressType::P2SHWPKH, 0, 1, KEY_LOOK_AHEAD) 288 | .expect("can not create change account"); 289 | master.add_account(change); 290 | let commitments = Account::new(&mut unlocker, AccountAddressType::P2WSH(KEY_PURPOSE), 1, 0, 0) 291 | .expect("can not create commitments account"); 292 | master.add_account(commitments); 293 | let deposit_address = master.get((0, 0)).unwrap().get_key(0).unwrap().address.clone(); 294 | 295 | (mnemonic, deposit_address, Wallet { 296 | master, 297 | coins: Coins::new(), 298 | }) 299 | } 300 | } 301 | 302 | #[cfg(test)] 303 | mod test { 304 | use std::{ 305 | str::FromStr, 306 | sync::{Arc, Mutex}, 307 | }; 308 | use std::time::{SystemTime, UNIX_EPOCH}; 309 | 310 | use bitcoin::{Address, BitcoinHash, Block, blockdata::opcodes::all, BlockHeader, network::constants::Network, OutPoint, PublicKey, Transaction, TxIn, TxOut, util::bip32::ExtendedPubKey}; 311 | use bitcoin::blockdata::constants::genesis_block; 312 | use bitcoin::blockdata::script::Builder; 313 | use bitcoin::util::hash::MerkleRoot; 314 | use bitcoin_hashes::{sha256, sha256d}; 315 | use bitcoin_wallet::account::{Account, AccountAddressType, Unlocker}; 316 | 317 | use crate::store::ContentStore; 318 | use crate::trunk::Trunk; 319 | use crate::wallet::Wallet; 320 | 321 | const NEW_COINS: u64 = 1000000000; 322 | const PASSPHRASE: &str = "whatever"; 323 | 324 | struct TestTrunk { 325 | trunk: Arc>> 326 | } 327 | 328 | impl TestTrunk { 329 | fn extend(&self, header: &BlockHeader) { 330 | self.trunk.lock().unwrap().push(header.clone()); 331 | } 332 | } 333 | 334 | impl Trunk for TestTrunk { 335 | fn is_on_trunk(&self, block_hash: &sha256d::Hash) -> bool { 336 | self.trunk.lock().unwrap().iter().any(|h| h.bitcoin_hash() == *block_hash) 337 | } 338 | 339 | fn get_header(&self, block_hash: &sha256d::Hash) -> Option { 340 | self.trunk.lock().unwrap().iter().find(|h| h.bitcoin_hash() == *block_hash).map(|h| h.clone()) 341 | } 342 | 343 | fn get_header_for_height(&self, height: u32) -> Option { 344 | self.trunk.lock().unwrap().get(height as usize).map(|h| h.clone()) 345 | } 346 | 347 | fn get_height(&self, block_hash: &sha256d::Hash) -> Option { 348 | self.trunk.lock().unwrap().iter().enumerate().find_map(|(i, h)| if h.bitcoin_hash() == *block_hash { Some(i as u32) } else { None }) 349 | } 350 | 351 | fn get_tip(&self) -> Option { 352 | let len = self.trunk.lock().unwrap().len(); 353 | if len > 0 { 354 | self.trunk.lock().unwrap().get(len - 1).map(|h| h.clone()) 355 | } else { 356 | None 357 | } 358 | } 359 | 360 | fn len(&self) -> u32 { 361 | self.trunk.lock().unwrap().len() as u32 362 | } 363 | } 364 | 365 | fn new_wallet() -> Wallet { 366 | // let mut wallet = Wallet::from_encrypted( 367 | // hex::decode("0e05ba48bb0fdc7285dc9498202aeee5e1777ac4f55072b30f15f6a8632ad0f3fde1c41d9e162dbe5d3153282eaebd081cf3b3312336fc56f5dd18a2df6ea48c1cdd11a1ed11281cd2e0f864f02e5bed5ab03326ed24e43b8a184acff9cb4e730db484e33f2b24295a97b2ca87871a69384eb64d4160ce8b3e8b4d90234040970e531d4333a8979dbe533c2b2668bf43b6607b2d24c5b42765ebfdd075fd173c").unwrap().as_slice(), 368 | // ExtendedPubKey::from_str("tpubD6NzVbkrYhZ4XKz4vgwBmnnVmA7EgWhnXvimQ4krq94yUgcSSbroi4uC1xbZ3UGMxG9M2utmaPjdpMrWW2uKRY9Mj4DZWrrY8M4pry8shsK").unwrap(), 369 | // 1567260002); 370 | let (mnemonic, address, mut wallet) = Wallet::new(Network::Testnet, PASSPHRASE, Option::None); 371 | let mut unlocker = Unlocker::new_for_master(&wallet.master, PASSPHRASE).unwrap(); 372 | wallet.master.add_account(Account::new(&mut unlocker, AccountAddressType::P2WPKH, 0, 0, 10).unwrap()); 373 | wallet.master.add_account(Account::new(&mut unlocker, AccountAddressType::P2WPKH, 0, 1, 10).unwrap()); 374 | wallet.master.add_account(Account::new(&mut unlocker, AccountAddressType::P2WSH(4711), 1, 0, 0).unwrap()); 375 | wallet 376 | } 377 | 378 | fn new_block(prev: &sha256d::Hash) -> Block { 379 | Block { 380 | header: BlockHeader { 381 | version: 1, 382 | time: SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs() as u32, 383 | nonce: 0, 384 | bits: 0x1d00ffff, 385 | prev_blockhash: prev.clone(), 386 | merkle_root: sha256d::Hash::default(), 387 | }, 388 | txdata: Vec::new(), 389 | } 390 | } 391 | 392 | fn coin_base(miner: &Address, height: u32) -> Transaction { 393 | Transaction { 394 | version: 2, 395 | lock_time: 0, 396 | input: vec!(TxIn { 397 | sequence: 0xffffffff, 398 | witness: Vec::new(), 399 | previous_output: OutPoint { txid: sha256d::Hash::default(), vout: 0 }, 400 | script_sig: Builder::new().push_int(height as i64).into_script(), 401 | }), 402 | output: vec!(TxOut { 403 | value: NEW_COINS, 404 | script_pubkey: miner.script_pubkey(), 405 | }), 406 | } 407 | } 408 | 409 | fn add_tx(block: &mut Block, tx: Transaction) { 410 | block.txdata.push(tx); 411 | block.header.merkle_root = block.merkle_root(); 412 | } 413 | 414 | fn mine(tip: &sha256d::Hash, height: u32, miner: &Address) -> Block { 415 | let mut block = new_block(tip); 416 | add_tx(&mut block, coin_base(miner, height)); 417 | block 418 | } 419 | 420 | 421 | #[test] 422 | pub fn process_blocks_balance() { 423 | let trunk = Arc::new( 424 | TestTrunk { trunk: Arc::new(Mutex::new(Vec::new())) }); 425 | let mut wallet = new_wallet(); 426 | let genesis = genesis_block(Network::Testnet); 427 | let miner = wallet.master.get_mut((0, 0)).unwrap().next_key().unwrap().address.clone(); 428 | 429 | trunk.extend(&genesis.header); 430 | wallet.process(&genesis); 431 | 432 | let next = mine(&genesis.bitcoin_hash(), 1, &miner); 433 | trunk.extend(&next.header); 434 | wallet.process(&next); 435 | 436 | assert_eq!(wallet.balance(), NEW_COINS); 437 | 438 | let burn = Address::p2shwsh(&Builder::new().push_opcode(all::OP_VERIFY).into_script(), Network::Testnet); 439 | let (burn_half, _) = wallet.withdraw(PASSPHRASE.to_string(), burn, 1, Some(NEW_COINS / 2), trunk.clone()).unwrap(); 440 | 441 | let mut next = mine(&next.bitcoin_hash(), 2, &miner); 442 | add_tx(&mut next, burn_half); 443 | trunk.extend(&next.header); 444 | wallet.process(&next); 445 | assert_eq!(wallet.balance(), NEW_COINS + NEW_COINS / 2); 446 | 447 | let (fund, _, fee) = wallet.fund(&sha256::Hash::default(), 1, PASSPHRASE.to_string(), 5, NEW_COINS / 10, trunk.clone(), 448 | |pk: &PublicKey, term: Option| { 449 | ContentStore::funding_script(pk, term.unwrap()) 450 | }).unwrap(); 451 | 452 | let mut next = mine(&next.bitcoin_hash(), 3, &miner); 453 | add_tx(&mut next, fund); 454 | trunk.extend(&next.header); 455 | wallet.process(&next); 456 | assert_eq!(wallet.balance(), 2 * NEW_COINS + NEW_COINS / 2 - fee); 457 | assert_eq!(wallet.available_balance(3, |h| trunk.get_height(h)), 2 * NEW_COINS + NEW_COINS / 2 - NEW_COINS / 10); 458 | 459 | let next = mine(&next.bitcoin_hash(), 4, &miner); 460 | trunk.extend(&next.header); 461 | wallet.process(&next); 462 | assert_eq!(wallet.balance(), 3 * NEW_COINS + NEW_COINS / 2 - fee); 463 | assert_eq!(wallet.available_balance(4, |h| trunk.get_height(h)), 3 * NEW_COINS + NEW_COINS / 2 - fee); 464 | } 465 | } --------------------------------------------------------------------------------