biter: fix ?

This commit is contained in:
nym21
2024-12-14 17:39:42 +01:00
parent 0b3329ca35
commit e0a378cb81
36 changed files with 96 additions and 116 deletions
+12
View File
@@ -0,0 +1,12 @@
# v0.2.1
- Clean `.json` if necessary
- Only save `.json` if needed
- Updated benchmarks
- Updated packages
# v0.2.0
- Removed the need for an output directory path
- Changed the location of the saved json file from the previously needed output directory path to the Bitcoin data directory
- Added a save of the json file every 144 * 30 blocks instead of only at the end
+475
View File
@@ -0,0 +1,475 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "arrayvec"
version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50"
[[package]]
name = "base58ck"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f"
dependencies = [
"bitcoin-internals",
"bitcoin_hashes",
]
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "bech32"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d"
[[package]]
name = "bitcoin"
version = "0.32.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce6bc65742dea50536e35ad42492b234c27904a27f0abdcbce605015cb4ea026"
dependencies = [
"base58ck",
"bech32",
"bitcoin-internals",
"bitcoin-io",
"bitcoin-units",
"bitcoin_hashes",
"hex-conservative",
"hex_lit",
"secp256k1",
"serde",
]
[[package]]
name = "bitcoin-internals"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2"
dependencies = [
"serde",
]
[[package]]
name = "bitcoin-io"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "340e09e8399c7bd8912f495af6aa58bea0c9214773417ffaa8f6460f93aaee56"
[[package]]
name = "bitcoin-units"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2"
dependencies = [
"bitcoin-internals",
"serde",
]
[[package]]
name = "bitcoin_hashes"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb18c03d0db0247e147a21a6faafd5a7eb851c743db062de72018b6b7e8e4d16"
dependencies = [
"bitcoin-io",
"hex-conservative",
"serde",
]
[[package]]
name = "bitcoincore-rpc"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee"
dependencies = [
"bitcoincore-rpc-json",
"jsonrpc",
"log",
"serde",
"serde_json",
]
[[package]]
name = "bitcoincore-rpc-json"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a"
dependencies = [
"bitcoin",
"serde",
"serde_json",
]
[[package]]
name = "biter"
version = "0.2.1"
dependencies = [
"bitcoin",
"bitcoincore-rpc",
"crossbeam",
"derived-deref",
"rayon",
"serde",
"serde_json",
]
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]]
name = "cc"
version = "1.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2e7962b54006dcfcc61cb72735f4d89bb97061dd6a7ed882ec6b8ee53714c6f"
dependencies = [
"shlex",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "crossbeam"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1137cd7e7fc0fb5d3c5a8678be38ec56e819125d8d7907411fe24ccb943faca8"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-epoch",
"crossbeam-queue",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-queue"
version = "0.3.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df0346b5d5e76ac2fe4e327c5fd1118d6be7c51dfb18f9b7922923f287471e35"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
[[package]]
name = "derived-deref"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "805ef2023ccd65425743a91ecd11fc020979a0b01921db3104fb606d18a7b43e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0"
[[package]]
name = "getrandom"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "hex-conservative"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5313b072ce3c597065a808dbf612c4c8e8590bdbf8b579508bf7a762c5eae6cd"
dependencies = [
"arrayvec",
]
[[package]]
name = "hex_lit"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd"
[[package]]
name = "itoa"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b"
[[package]]
name = "jsonrpc"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf"
dependencies = [
"base64",
"minreq",
"serde",
"serde_json",
]
[[package]]
name = "libc"
version = "0.2.161"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memchr"
version = "2.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "minreq"
version = "2.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0"
dependencies = [
"log",
"serde",
"serde_json",
]
[[package]]
name = "ppv-lite86"
version = "0.2.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04"
dependencies = [
"zerocopy",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "ryu"
version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f"
[[package]]
name = "secp256k1"
version = "0.29.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113"
dependencies = [
"bitcoin_hashes",
"rand",
"secp256k1-sys",
"serde",
]
[[package]]
name = "secp256k1-sys"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9"
dependencies = [
"cc",
]
[[package]]
name = "serde"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "serde_json"
version = "1.0.133"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377"
dependencies = [
"itoa",
"memchr",
"ryu",
"serde",
]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "zerocopy"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
"byteorder",
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
+19
View File
@@ -0,0 +1,19 @@
[package]
name = "biter"
description = "A very fast Bitcoin block iterator"
version = "0.2.2"
license = "MIT"
repository = "https://github.com/kibo-money/kibo/tree/main/src/crates/biter"
keywords = ["bitcoin", "block", "iterator"]
categories = ["cryptography::cryptocurrencies", "encoding"]
edition = "2021"
[dependencies]
bitcoin = { version = "0.32.5", features = ["serde"] }
rayon = "1.10.0"
crossbeam = { version = "0.8.4", features = ["crossbeam-channel"] }
serde = { version = "1.0.216", features = ["derive"] }
serde_json = "1.0.133"
derived-deref = "2.1.0"
bitcoincore-rpc = "0.19.0"
# tokio = { version = "1.39.2", features = ["rt-multi-thread"] }
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 biter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+65
View File
@@ -0,0 +1,65 @@
# biter
Biter (Bitcoin Block Iterator) is a very fast and simple Rust library which reads raw block files (*blkXXXXX.dat*) from Bitcoin Core Node and creates an iterator over all the requested blocks in sequential order (0, 1, 2, ...).
The element returned by the iterator is a tuple which includes the:
- Height: `usize`
- Block: `Block` (from `bitcoin-rust`)
- Block's Hash: `BlockHash` (also from `bitcoin-rust`)
## Example
```rust
use std::path::Path;
use bitcoincore_rpc::{Auth, Client};
fn main() {
let i = std::time::Instant::now();
// Path to the Bitcoin data directory
let data_dir = "../../bitcoin";
// Inclusive starting height of the blocks received, `None` for 0
let start = Some(850_000);
// Inclusive ending height of the blocks received, `None` for the last one
let end = None;
// RPC client to filter out forks
let url = "http://localhost:8332";
let cookie = Path::new(data_dir).join(".cookie");
let auth = Auth::CookieFile(cookie);
let rpc = Client::new(url, auth).unwrap();
if cookie.is_file() {
Ok()
// Create channel receiver then iterate over the blocks
biter::new(data_dir, start, end, rpc)
.iter()
.for_each(|(height, _block, hash)| {
println!("{height}: {hash}");
});
dbg!(i.elapsed());
}
```
## Requirements
Even though it reads *blkXXXXX.dat* files, it **needs** `bitcoind` to run with the RPC server to filter out block forks.
Peak memory should be around 500MB.
## Comparaison
| | [biter](https://crates.io/crates/biter) | [bitcoin-explorer (deprecated)](https://crates.io/crates/bitcoin-explorer) | [blocks_iterator](https://crates.io/crates/blocks_iterator) |
| --- | --- | --- | --- |
| Runs **with** `bitcoind` | Yes ✅ | No ❌ | Yes ✅ |
| Runs **without** `bitcoind` | No ❌ | Yes ✅ | Yes ✅ |
| `0..=855_000` | 4mn 10s | 4mn 45s | > 2h |
| `800_000..=855_000` | 0mn 52s (4mn 10s if first run) | 0mn 55s | > 2h |
*Benchmarked on a Macbook Pro M3 Pro*
@@ -0,0 +1,127 @@
use std::{
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
fs::{self, File},
io::{BufReader, BufWriter},
path::{Path, PathBuf},
};
use derived_deref::{Deref, DerefMut};
use crate::{blk_recap::BlkRecap, BlkMetadataAndBlock};
const TARGET_BLOCKS_PER_MONTH: usize = 144 * 30;
#[derive(Deref, DerefMut, Debug)]
pub struct BlkIndexToBlkRecap {
path: PathBuf,
#[target]
tree: BTreeMap<usize, BlkRecap>,
last_safe_height: Option<usize>,
}
impl BlkIndexToBlkRecap {
pub fn import(blocks_dir: &BTreeMap<usize, PathBuf>, data_dir: &Path) -> Self {
let path = data_dir.join("blk_index_to_blk_recap.json");
let tree = {
fs::create_dir_all(data_dir).unwrap();
if let Ok(file) = File::open(&path) {
let reader = BufReader::new(file);
serde_json::from_reader(reader).unwrap_or_default()
} else {
BTreeMap::default()
}
};
let mut this = Self {
path,
tree,
last_safe_height: None,
};
this.clean_outdated(blocks_dir);
this
}
pub fn clean_outdated(&mut self, blocks_dir: &BTreeMap<usize, PathBuf>) {
let mut unprocessed_keys = self.keys().copied().collect::<BTreeSet<_>>();
blocks_dir.iter().for_each(|(blk_index, blk_path)| {
unprocessed_keys.remove(blk_index);
if let Some(blk_recap) = self.get(blk_index) {
if blk_recap.has_different_modified_time(blk_path) {
self.remove(blk_index);
}
}
});
unprocessed_keys.into_iter().for_each(|blk_index| {
self.remove(&blk_index);
});
self.last_safe_height = self.iter().map(|(_, recap)| recap.height()).max();
}
pub fn get_start_recap(&self, start: Option<usize>) -> Option<(usize, BlkRecap)> {
if let Some(start) = start {
let (last_key, last_value) = self.last_key_value()?;
if last_value.height() < start {
return Some((*last_key, *last_value));
} else if let Some((blk_index, _)) = self
.iter()
.find(|(_, blk_recap)| blk_recap.is_younger_than(start))
{
if *blk_index != 0 {
let blk_index = *blk_index - 1;
return Some((blk_index, *self.get(&blk_index).unwrap()));
}
}
}
None
}
pub fn update(&mut self, blk_metadata_and_block: &BlkMetadataAndBlock, height: usize) {
let blk_index = blk_metadata_and_block.blk_metadata.index;
if let Some(last_entry) = self.last_entry() {
match last_entry.key().cmp(&blk_index) {
Ordering::Greater => {
last_entry.remove_entry();
}
Ordering::Less => {
self.insert(blk_index, BlkRecap::from(height, blk_metadata_and_block));
}
Ordering::Equal => {}
};
} else {
if blk_index != 0 || height != 0 {
// dbg!(blk_index, height);
unreachable!();
}
self.insert(blk_index, BlkRecap::first(blk_metadata_and_block));
}
if self
.last_safe_height
.map_or(true, |safe_height| height >= safe_height)
&& (height % TARGET_BLOCKS_PER_MONTH) == 0
{
self.export();
}
}
pub fn export(&self) {
let file = File::create(&self.path).unwrap_or_else(|_| {
dbg!(&self.path);
panic!("No such file or directory")
});
serde_json::to_writer_pretty(&mut BufWriter::new(file), &self.tree).unwrap();
}
}
+18
View File
@@ -0,0 +1,18 @@
use std::path::PathBuf;
use crate::path_to_modified_time;
#[derive(Clone, Copy)]
pub struct BlkMetadata {
pub index: usize,
pub modified_time: u64,
}
impl BlkMetadata {
pub fn new(index: usize, path: &PathBuf) -> Self {
Self {
index,
modified_time: path_to_modified_time(path),
}
}
}
@@ -0,0 +1,17 @@
use bitcoin::Block;
use crate::BlkMetadata;
pub struct BlkMetadataAndBlock {
pub blk_metadata: BlkMetadata,
pub block: Block,
}
impl BlkMetadataAndBlock {
pub fn new(blk_metadata: BlkMetadata, block: Block) -> Self {
Self {
blk_metadata,
block,
}
}
}
+47
View File
@@ -0,0 +1,47 @@
use std::path::PathBuf;
use bitcoin::{hashes::Hash, BlockHash};
use serde::{Deserialize, Serialize};
use crate::{path_to_modified_time, BlkMetadataAndBlock};
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct BlkRecap {
min_continuous_height: usize,
min_continuous_prev_hash: BlockHash,
modified_time: u64,
}
impl BlkRecap {
pub fn first(blk_metadata_and_block: &BlkMetadataAndBlock) -> Self {
Self {
min_continuous_height: 0,
min_continuous_prev_hash: BlockHash::all_zeros(),
modified_time: blk_metadata_and_block.blk_metadata.modified_time,
}
}
pub fn from(height: usize, blk_metadata_and_block: &BlkMetadataAndBlock) -> Self {
Self {
min_continuous_height: height,
min_continuous_prev_hash: blk_metadata_and_block.block.header.prev_blockhash,
modified_time: blk_metadata_and_block.blk_metadata.modified_time,
}
}
pub fn has_different_modified_time(&self, blk_path: &PathBuf) -> bool {
self.modified_time != path_to_modified_time(blk_path)
}
pub fn is_younger_than(&self, height: usize) -> bool {
self.min_continuous_height > height
}
pub fn height(&self) -> usize {
self.min_continuous_height
}
pub fn prev_hash(&self) -> &BlockHash {
&self.min_continuous_prev_hash
}
}
+385
View File
@@ -0,0 +1,385 @@
use std::{
collections::{BTreeMap, BTreeSet, VecDeque},
fs::{self},
ops::ControlFlow,
path::Path,
thread,
};
use bitcoin::{
consensus::{Decodable, ReadExt},
hashes::Hash,
io::{Cursor, Read},
Block, BlockHash,
};
use bitcoincore_rpc::RpcApi;
use crossbeam::channel::{bounded, Receiver};
use rayon::prelude::*;
pub use bitcoin;
pub use bitcoincore_rpc;
mod blk_index_to_blk_recap;
mod blk_metadata;
mod blk_metadata_and_block;
mod blk_recap;
mod utils;
use blk_index_to_blk_recap::*;
use blk_metadata::*;
use blk_metadata_and_block::*;
use utils::*;
pub const NUMBER_OF_UNSAFE_BLOCKS: usize = 100;
const MAGIC_BYTES: [u8; 4] = [249, 190, 180, 217];
const BOUND_CAP: usize = 210;
enum BlockState {
Raw(Vec<u8>),
Decoded(Block),
}
///
/// Returns a crossbeam channel receiver that receives `(usize, Block, BlockHash)` tuples (with `usize` being the height) in sequential order.
///
/// # Arguments
///
/// * `data_dir` - Path to the Bitcoin data directory
/// * `start` - Inclusive starting height of the blocks received, `None` for 0
/// * `end` - Inclusive ending height of the blocks received, `None` for the last one
/// * `rpc` - RPC client to filter out forks
///
/// # Example
///
/// ```rust
/// use std::path::Path;
///
/// use bitcoincore_rpc::{Auth, Client};
///
/// fn main() {
/// let i = std::time::Instant::now();
///
/// let data_dir = Path::new("../../bitcoin");
/// let url = "http://localhost:8332";
/// let cookie = Path::new(data_dir).join(".cookie");
/// let auth = Auth::CookieFile(cookie);
/// let rpc = Client::new(url, auth).unwrap();
///
/// let start = Some(850_000);
/// let end = None;
///
/// biter::new(data_dir, start, end, rpc)
/// .iter()
/// .for_each(|(height, _block, hash)| {
/// println!("{height}: {hash}");
/// });
///
/// dbg!(i.elapsed());
///}
/// ```
///
pub fn new(
data_dir: &Path,
start: Option<usize>,
end: Option<usize>,
rpc: bitcoincore_rpc::Client,
) -> Receiver<(usize, Block, BlockHash)> {
let (send_block_reader, recv_block_reader) = bounded(BOUND_CAP);
let (send_block, recv_block) = bounded(BOUND_CAP);
let (send_height_block_hash, recv_height_block_hash) = bounded(BOUND_CAP);
let blk_index_to_blk_path = scan_blocks_dir(data_dir);
let mut blk_index_to_blk_recap = BlkIndexToBlkRecap::import(&blk_index_to_blk_path, data_dir);
let start_recap = blk_index_to_blk_recap.get_start_recap(start);
let starting_blk_index = start_recap.as_ref().map_or(0, |(index, _)| *index);
thread::spawn(move || {
blk_index_to_blk_path
.into_iter()
.filter(|(blk_index, _)| blk_index >= &starting_blk_index)
.try_for_each(move |(blk_index, blk_path)| {
let blk_metadata = BlkMetadata::new(blk_index, &blk_path);
let blk_bytes = fs::read(&blk_path).unwrap();
let blk_bytes_len = blk_bytes.len() as u64;
let mut cursor = Cursor::new(blk_bytes.as_slice());
let mut current_4bytes = [0; 4];
'parent: loop {
if cursor.position() == blk_bytes_len {
break;
}
// Read until we find a valid suite of MAGIC_BYTES
loop {
current_4bytes.rotate_left(1);
if let Ok(byte) = cursor.read_u8() {
current_4bytes[3] = byte;
} else {
break 'parent;
}
if current_4bytes == MAGIC_BYTES {
break;
}
}
let block_size = cursor.read_u32().unwrap();
let mut raw_block = vec![0u8; block_size as usize];
cursor.read_exact(&mut raw_block).unwrap();
if send_block_reader
.send((blk_metadata, BlockState::Raw(raw_block)))
.is_err()
{
return ControlFlow::Break(());
}
}
ControlFlow::Continue(())
})
});
// thread::spawn(move || {
// recv_block_reader.iter().par_bridge().try_for_each(
// move |(blk_metadata, mut block_state)| {
// let raw_block = match block_state {
// BlockState::Raw(vec) => vec,
// _ => unreachable!(),
// };
// let mut cursor = Cursor::new(raw_block);
// block_state = BlockState::Decoded(Block::consensus_decode(&mut cursor).unwrap());
// if send_block
// .send(BlkMetadataAndBlock::new(
// blk_metadata,
// match block_state {
// BlockState::Decoded(block) => block,
// _ => unreachable!(),
// },
// ))
// .is_err()
// {
// return ControlFlow::Break(());
// }
// ControlFlow::Continue(())
// },
// );
// });
// Can't use the previous code because .send() blocks all the threads if full
// And other .par_iter() are also stuck because of that
thread::spawn(move || {
let mut bulk = vec![];
let drain_and_send = |bulk: &mut Vec<_>| {
// Using a vec and sending after to not end up with stuck threads in par iter
bulk.par_iter_mut().for_each(|(_, block_state)| {
let raw_block = match block_state {
BlockState::Raw(vec) => vec,
_ => unreachable!(),
};
let mut cursor = Cursor::new(raw_block);
*block_state = BlockState::Decoded(Block::consensus_decode(&mut cursor).unwrap());
});
bulk.drain(..).try_for_each(|(blk_metadata, block_state)| {
let block = match block_state {
BlockState::Decoded(block) => block,
_ => unreachable!(),
};
if send_block
.send(BlkMetadataAndBlock::new(blk_metadata, block))
.is_err()
{
return ControlFlow::Break(());
}
ControlFlow::Continue(())
})
};
recv_block_reader.iter().try_for_each(|tuple| {
bulk.push(tuple);
if bulk.len() < BOUND_CAP / 2 {
return ControlFlow::Continue(());
}
drain_and_send(&mut bulk)
});
drain_and_send(&mut bulk)
});
// Tokio version: 1022s
// Slighlty slower than rayon version
// thread::spawn(move || {
// let rt = tokio::runtime::Runtime::new().unwrap();
// let _guard = rt.enter();
// let mut tasks = VecDeque::with_capacity(BOUND);
// recv_block_reader
// .iter()
// .try_for_each(move |(blk_metadata, block_state)| {
// let raw_block = match block_state {
// BlockState::Raw(vec) => vec,
// _ => unreachable!(),
// };
// tasks.push_back(tokio::task::spawn(async move {
// let block = Block::consensus_decode(&mut Cursor::new(raw_block)).unwrap();
// (blk_metadata, block)
// }));
// while tasks.len() > BOUND {
// let (blk_metadata, block) = rt.block_on(tasks.pop_front().unwrap()).unwrap();
// if send_block
// .send(BlkMetadataAndBlock::new(blk_metadata, block))
// .is_err()
// {
// return ControlFlow::Break(());
// }
// }
// ControlFlow::Continue(())
// });
//
// todo!("Send the rest")
// });
thread::spawn(move || {
let mut height = start_recap.map_or(0, |(_, recap)| recap.height());
let mut future_blocks = BTreeMap::default();
let mut recent_chain: VecDeque<(BlockHash, BlkMetadataAndBlock)> = VecDeque::default();
let mut recent_hashes: BTreeSet<BlockHash> = BTreeSet::default();
let mut prev_hash =
start_recap.map_or_else(BlockHash::all_zeros, |(_, recap)| *recap.prev_hash());
let mut prepare_and_send = |(hash, tuple): (BlockHash, BlkMetadataAndBlock)| {
blk_index_to_blk_recap.update(&tuple, height);
if start.map_or(true, |start| start <= height) {
send_height_block_hash
.send((height, tuple.block, hash))
.unwrap();
}
if end.map_or(false, |end| height == end) {
return ControlFlow::Break(());
}
height += 1;
ControlFlow::Continue(())
};
let mut update_tip = |prev_hash: &mut BlockHash,
recent_hashes: &mut BTreeSet<BlockHash>,
recent_chain: &mut VecDeque<(BlockHash, BlkMetadataAndBlock)>,
future_blocks: &mut BTreeMap<BlockHash, BlkMetadataAndBlock>,
tuple: BlkMetadataAndBlock| {
let mut tuple = Some(tuple);
while let Some(tuple) = tuple.take().or_else(|| future_blocks.remove(prev_hash)) {
let hash = tuple.block.block_hash();
*prev_hash = hash;
recent_hashes.insert(hash);
recent_chain.push_back((hash, tuple));
}
while recent_chain.len() > NUMBER_OF_UNSAFE_BLOCKS {
let (hash, tuple) = recent_chain.pop_front().unwrap();
recent_hashes.remove(&hash);
if prepare_and_send((hash, tuple)).is_break() {
return ControlFlow::Break(());
}
}
ControlFlow::Continue(())
};
let flow = recv_block.iter().try_for_each(|tuple| {
// block isn't next after current tip
if prev_hash != tuple.block.header.prev_blockhash {
let is_block_active =
|hash| rpc.get_block_header_info(hash).unwrap().confirmations > 0;
// block prev has already been processed
if recent_hashes.contains(&tuple.block.header.prev_blockhash) {
let hash = tuple.block.block_hash();
if is_block_active(&hash) {
let prev_index = recent_chain
.iter()
.position(|(hash, ..)| hash == &tuple.block.header.prev_blockhash)
.unwrap();
let bad_index_start = prev_index + 1;
recent_chain.drain(bad_index_start..).for_each(|(hash, _)| {
recent_hashes.remove(&hash);
});
return update_tip(
&mut prev_hash,
&mut recent_hashes,
&mut recent_chain,
&mut future_blocks,
tuple,
);
}
// Check if there was already a future block with the same prev hash
} else if let Some(prev_tuple) =
future_blocks.insert(tuple.block.header.prev_blockhash, tuple)
{
// If the previous was the active one
if is_block_active(&prev_tuple.block.block_hash()) {
// Rollback the insert
future_blocks.insert(prev_tuple.block.header.prev_blockhash, prev_tuple);
}
}
} else {
return update_tip(
&mut prev_hash,
&mut recent_hashes,
&mut recent_chain,
&mut future_blocks,
tuple,
);
}
ControlFlow::Continue(())
});
if flow.is_continue() {
// Send the last (up to 100) blocks
recent_chain.into_iter().try_for_each(prepare_and_send);
}
blk_index_to_blk_recap.export();
});
recv_height_block_hash
}
+24
View File
@@ -0,0 +1,24 @@
use std::path::Path;
use bitcoincore_rpc::{Auth, Client};
fn main() {
let i = std::time::Instant::now();
let data_dir = Path::new("../../../bitcoin");
let url = "http://localhost:8332";
let cookie = Path::new(data_dir).join(".cookie");
let auth = Auth::CookieFile(cookie);
let rpc = Client::new(url, auth).unwrap();
let start = Some(810078);
let end = None;
biter::new(data_dir, start, end, rpc)
.iter()
.for_each(|(height, _block, hash)| {
println!("{height}: {hash}");
});
dbg!(i.elapsed());
}
+48
View File
@@ -0,0 +1,48 @@
use std::{
collections::BTreeMap,
fs,
path::{Path, PathBuf},
time::UNIX_EPOCH,
};
const BLK: &str = "blk";
const DAT: &str = ".dat";
pub fn scan_blocks_dir(data_dir: &Path) -> BTreeMap<usize, PathBuf> {
let blocks_dir = data_dir.join("blocks");
fs::read_dir(blocks_dir)
.unwrap()
.map(|entry| entry.unwrap().path())
.filter(|path| {
let is_file = path.is_file();
if is_file {
let file_name = path.file_name().unwrap().to_str().unwrap();
file_name.starts_with(BLK) && file_name.ends_with(DAT)
} else {
false
}
})
.map(|path| {
let file_name = path.file_name().unwrap().to_str().unwrap();
let blk_index = file_name[BLK.len()..(file_name.len() - DAT.len())]
.parse::<usize>()
.unwrap();
(blk_index, path)
})
.collect::<BTreeMap<_, _>>()
}
pub fn path_to_modified_time(path: &PathBuf) -> u64 {
fs::metadata(path)
.unwrap()
.modified()
.unwrap()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs()
}
+60
View File
@@ -0,0 +1,60 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "proc-macro2"
version = "1.0.89"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "struct_iterable"
version = "0.1.2"
dependencies = [
"struct_iterable_derive",
"struct_iterable_internal",
]
[[package]]
name = "struct_iterable_derive"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"struct_iterable_internal",
"syn",
]
[[package]]
name = "struct_iterable_internal"
version = "0.1.1"
[[package]]
name = "syn"
version = "2.0.85"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5023162dfcd14ef8f32034d8bcd4cc5ddc61ef7a247c024a33e24e1f24d21b56"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+26
View File
@@ -0,0 +1,26 @@
[package]
name = "struct_iterable"
version = "0.1.2"
authors = ["André de Moraes <deco.moraes@icloud.com>"]
edition = "2021"
description = "A Rust library providing a proc macro to make a struct iterable."
license = "MIT"
repository = "https://github.com/decomoraes/rust_struct_iterable"
readme = "README.md"
keywords = ["proc-macro", "struct", "iterable"]
categories = ["development-tools::cargo-plugins"]
homepage = "https://github.com/decomoraes/rust_struct_iterable"
documentation = "https://docs.rs/struct_iterable"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
struct_iterable_derive = { path = "./struct_iterable_derive" }
struct_iterable_internal = { path = "./struct_iterable_internal" }
[lib]
name = "struct_iterable"
path = "src/lib.rs"
[package.metadata.docs.rs]
all-features = true
+105
View File
@@ -0,0 +1,105 @@
# Struct Iterable
`Struct Iterable` is a Rust library that provides a proc macro to make a struct iterable. This allows you to iterate over the fields of your struct in a generic way, with each iteration returning a tuple containing the name of the field as a static string and a reference to the field's value as a `dyn Any`.
## How to Use
First, add `Struct Iterable` to your `Cargo.toml`:
```toml
[dependencies]
struct_iterable = "0.1.1"
```
Next, include the library at the top of your Rust file:
```rust
use struct_iterable::Iterable;
```
Finally, add the `#[derive(Iterable)]` attribute to your struct:
```rust
#[derive(Iterable)]
struct MyStruct {
field1: u32,
field2: String,
// etc.
}
```
Now, you can iterate over the fields of an instance of your struct:
```rust
let my_instance = MyStruct {
field1: 42,
field2: "Hello, world!".to_string(),
};
for (field_name, field_value) in my_instance.iter() {
println!("{}: {:?}", field_name, field_value);
}
```
## Limitations
- Only structs with named fields are supported.
- Only structs are supported, not enums or unions.
## Implementation
Here is the implementation of the proc macro:
```rust
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
use iterable_structs::Iterable;
#[proc_macro_derive(Iterable)]
pub fn derive_iterable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident;
let fields = match input.data {
Data::Struct(data_struct) => match data_struct.fields {
Fields::Named(fields_named) => fields_named.named,
_ => panic!("Only structs with named fields are supported"),
},
_ => panic!("Only structs are supported"),
};
let fields_iter = fields.iter().map(|field| {
let field_ident = &field.ident;
let field_name = field_ident.as_ref().unwrap().to_string();
quote! {
(#field_name, &(self.#field_ident) as &dyn std::any::Any)
}
});
let expanded = quote! {
impl Iterable for #struct_name {
fn iter<'a>(&'a self) -> std::vec::IntoIter<(&'static str, &'a dyn std::any::Any)> {
vec![
#(#fields_iter),*
].into_iter()
}
}
};
TokenStream::from(expanded)
}
```
The macro takes in the TokenStream of a struct and expands it into an implementation of the Iterable trait for that struct. This trait provides an iter method that returns an iterator over tuples of field names and values.
## Contributing and License
`Struct Iterable` is an open-source project, and contributions are warmly welcomed. Whether you're fixing bugs, improving the documentation, or proposing new features, your efforts are highly appreciated!
If you're interested in contributing, please feel free to submit a pull request. For major changes, please open an issue first to discuss what you would like to change.
Please note that this project is released with a Contributor Code of Conduct. By participating in this project, you agree to abide by its terms.
`Struct Iterable` is distributed under the terms of the MIT license. As such, you're free to use, modify, distribute, and privately use it in any way you see fit, in accordance with the terms of the license.
+91
View File
@@ -0,0 +1,91 @@
/// The `Iterable` proc macro.
///
/// This macro provides a convenient way to make a struct iterable.
/// The struct fields' names are returned as static strings and their values as `dyn Any`.
/// This allows to iterate over the struct fields in a generic way.
///
/// Note that only structs with named fields are supported.
///
/// # Example
///
/// ```
/// use struct_iterable::Iterable;
///
/// #[derive(Iterable)]
/// struct MyStruct {
/// field1: i32,
/// field2: String,
/// // etc.
/// }
///
/// let my_instance = MyStruct {
/// field1: 42,
/// field2: "Hello, world!".to_string(),
/// };
///
/// for (field_name, field_value) in my_instance.iter() {
/// println!("{}: {:?}", field_name, field_value);
/// }
/// ```
pub use struct_iterable_derive::Iterable;
/// The `Iterable` trait.
///
/// This trait is implemented by the struct once the `Iterable` proc macro is derived.
/// It provides an `iter` method that returns an iterator over tuples of field names and values.
///
/// # Example
///
/// ```
/// use struct_iterable::Iterable;
///
/// #[derive(Iterable)]
/// struct MyStruct {
/// field1: i32,
/// field2: String,
/// // etc.
/// }
///
/// let my_instance = MyStruct {
/// field1: 42,
/// field2: "Hello, world!".to_string(),
/// };
///
/// for (field_name, field_value) in my_instance.iter() {
/// println!("{}: {:?}", field_name, field_value);
/// }
/// ```
pub use struct_iterable_internal::Iterable;
#[cfg(test)]
mod tests {
use super::*;
#[derive(Iterable)]
struct MyStruct {
field1: i32,
field2: String,
// etc.
}
#[test]
fn it_works() {
let mut my_instance = MyStruct {
field1: 42,
field2: "Hello, world!".to_string(),
};
for (field_name, field_value) in my_instance.iter() {
dbg!("{}: {:?}", field_name, field_value);
}
for (field_name, field_value) in my_instance.iter_mut() {
dbg!("{}: {:?}", field_name, &field_value);
if let Some(i32) = field_value.downcast_mut::<i32>() {
*i32 += 1;
dbg!(i32);
}
dbg!("{}: {:?}", field_name, &field_value);
}
}
}
@@ -0,0 +1,17 @@
[package]
name = "struct_iterable_derive"
version = "0.1.0"
authors = ["André de Moraes <deco.moraes@icloud.com>"]
edition = "2021"
description = "An internal crate for struct_iterable"
license = "MIT"
[lib]
proc-macro = true
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
syn = "2.0.85"
quote = "1.0.37"
proc-macro2 = "1.0.89"
struct_iterable_internal = { path = "../struct_iterable_internal" }
@@ -0,0 +1,7 @@
# Struct Iterable Derive
This crate is a supporting library for the `struct_iterable` crate. It provides the proc macro `Iterable` which is used in conjunction with the `struct_iterable_internal` crate to provide an easy way to make a struct iterable in Rust.
**Please note:** This crate is not intended to be used directly. If you want to make your structs iterable, please use the `struct_iterable` crate instead.
Please visit the [`struct_iterable` crate on crates.io](https://crates.io/crates/struct_iterable) for more information and usage examples.
@@ -0,0 +1,88 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, Data, DeriveInput, Fields};
/// The `Iterable` proc macro.
///
/// Deriving this macro for your struct will make it "iterable". An iterable struct allows you to iterate over its fields, returning a tuple containing the field name as a static string and a reference to the field's value as `dyn Any`.
///
/// # Limitations
///
/// - Only structs are supported, not enums or unions.
/// - Only structs with named fields are supported.
///
/// # Usage
///
/// Add the derive attribute (`#[derive(Iterable)]`) above your struct definition.
///
/// ```
/// use struct_iterable::Iterable;
///
/// #[derive(Iterable)]
/// struct MyStruct {
/// field1: i32,
/// field2: String,
/// }
/// ```
///
/// You can now call the `iter` method on instances of your struct to get an iterator over its fields:
///
/// ```
/// let my_instance = MyStruct {
/// field1: 42,
/// field2: "Hello, world!".to_string(),
/// };
///
/// for (field_name, field_value) in my_instance.iter() {
/// println!("{}: {:?}", field_name, field_value);
/// }
/// ```
#[proc_macro_derive(Iterable)]
pub fn derive_iterable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let struct_name = input.ident;
let fields = match input.data {
Data::Struct(data_struct) => match data_struct.fields {
Fields::Named(fields_named) => fields_named.named,
_ => panic!("Only structs with named fields are supported"),
},
_ => panic!("Only structs are supported"),
};
let fields_iter = fields.iter().map(|field| {
let field_ident = &field.ident;
let field_name = field_ident.as_ref().unwrap().to_string();
quote! {
(#field_name, &(self.#field_ident) as &dyn std::any::Any)
}
});
let fields_iter_mut = fields.iter().map(|field| {
let field_ident = &field.ident;
let field_name = field_ident.as_ref().unwrap().to_string();
quote! {
(#field_name, &mut (self.#field_ident) as &mut dyn std::any::Any)
}
});
let expanded = quote! {
impl Iterable for #struct_name {
fn iter<'a>(&'a self) -> std::vec::IntoIter<(&'static str, &'a dyn std::any::Any)> {
vec![
#(#fields_iter),*
].into_iter()
}
fn iter_mut<'a>(&'a mut self) -> std::vec::IntoIter<(&'static str, &'a mut dyn std::any::Any)> {
vec![
#(#fields_iter_mut),*
].into_iter()
}
}
};
TokenStream::from(expanded)
}
@@ -0,0 +1,11 @@
[package]
name = "struct_iterable_internal"
version = "0.1.1"
authors = ["André de Moraes <deco.moraes@icloud.com>"]
edition = "2021"
description = "An internal crate for struct_iterable"
license = "MIT"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
@@ -0,0 +1,7 @@
# Struct Iterable Internal
This crate is a supporting library for the `struct_iterable` crate. It provides the `Iterable` trait which is used in conjunction with the `struct_iterable_derive` crate to provide an easy way to make a struct iterable in Rust.
**Please note:** This crate is not intended to be used directly. If you want to make your structs iterable, please use the `struct_iterable` crate instead.
Please visit the [`struct_iterable` crate on crates.io](https://crates.io/crates/struct_iterable) for more information and usage examples.
@@ -0,0 +1,58 @@
/// The `Iterable` trait.
///
/// This trait is implemented for structs that derive the `Iterable` proc macro.
/// It provides the `iter` method which returns an iterator over the struct's fields as tuples, containing the field name as a static string and a reference to the field's value as `dyn Any`.
///
/// You usually don't need to implement this trait manually, as it is automatically derived when using the `#[derive(Iterable)]` proc macro.
///
/// # Example
///
/// ```
/// use struct_iterable::Iterable;
///
/// #[derive(Iterable)]
/// struct MyStruct {
/// field1: i32,
/// field2: String,
/// }
///
/// let my_instance = MyStruct {
/// field1: 42,
/// field2: "Hello, world!".to_string(),
/// };
///
/// // Iterate over the fields of `my_instance`:
/// for (field_name, field_value) in my_instance.iter() {
/// println!("{}: {:?}", field_name, field_value);
/// }
/// ```
pub trait Iterable {
/// Returns an iterator over the struct's fields as tuples.
///
/// Each tuple contains a field's name as a static string and a reference to the field's value as `dyn Any`.
///
/// # Example
///
/// ```
/// use struct_iterable::Iterable;
///
/// #[derive(Iterable)]
/// struct MyStruct {
/// field1: i32,
/// field2: String,
/// }
///
/// let my_instance = MyStruct {
/// field1: 42,
/// field2: "Hello, world!".to_string(),
/// };
///
/// // Iterate over the fields of `my_instance`:
/// for (field_name, field_value) in my_instance.iter() {
/// println!("{}: {:?}", field_name, field_value);
/// }
/// ```
fn iter(&self) -> std::vec::IntoIter<(&'static str, &'_ dyn std::any::Any)>;
fn iter_mut(&mut self) -> std::vec::IntoIter<(&'static str, &'_ mut dyn std::any::Any)>;
}
+242
View File
@@ -0,0 +1,242 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "autocfg"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "fs2"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9564fc758e15025b46aa6643b1b77d047d1a56a1aea6e01002ac0c7026876213"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "instant"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222"
dependencies = [
"cfg-if",
]
[[package]]
name = "libc"
version = "0.2.168"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d"
[[package]]
name = "lock_api"
version = "0.4.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17"
dependencies = [
"autocfg",
"scopeguard",
]
[[package]]
name = "log"
version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
[[package]]
name = "memmap2"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f"
dependencies = [
"libc",
]
[[package]]
name = "parking_lot"
version = "0.11.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99"
dependencies = [
"instant",
"lock_api",
"parking_lot_core",
]
[[package]]
name = "parking_lot_core"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a2cfe6f0ad2bfc16aefa463b497d5c7a5ecd44a23efa72aa342d90177356dc"
dependencies = [
"cfg-if",
"instant",
"libc",
"redox_syscall",
"smallvec",
"winapi",
]
[[package]]
name = "proc-macro2"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
dependencies = [
"proc-macro2",
]
[[package]]
name = "redox_syscall"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
]
[[package]]
name = "sanakirja"
version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81aaf70d064e2122209f04d01fd91e8908e7a327b516236e1cbc0c3f34ac6d11"
dependencies = [
"fs2",
"log",
"memmap2",
"parking_lot",
"sanakirja-core",
"serde",
"thiserror",
]
[[package]]
name = "sanakirja-core"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8376db34ae3eac6e7bd91168bc638450073b708ce9fb46940de676f552238bf5"
[[package]]
name = "scopeguard"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.216"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "smallvec"
version = "1.13.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "snkrj"
version = "0.1.0"
dependencies = [
"sanakirja",
]
[[package]]
name = "syn"
version = "2.0.90"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "unicode-ident"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
+12
View File
@@ -0,0 +1,12 @@
[package]
name = "snkrj"
description = "A simple wrapper around Sanakirja's database that acts as a very fast on disk BTreeMap"
version = "0.1.1"
license = "MIT"
repository = "https://github.com/kibo-money/kibo/tree/main/src/crates/snkrj"
keywords = ["database", "sanakirja", "btreemap"]
categories = ["database"]
edition = "2021"
[dependencies]
sanakirja = "1.4.3"
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 snkrj
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+37
View File
@@ -0,0 +1,37 @@
# snkrj
A simple wrapper around Sanakirja aatabase that acts as a very fast on disk BTreeMap.
## Example
```rust
use snkrj::{AnyDatabase, Database};
fn main() {
let path = std::env::temp_dir().join("./db");
let database: Database<i32, i32> = Database::open(path.clone()).unwrap();
let _ = database.destroy();
let mut database: Database<i32, i32> = Database::open(path.clone()).unwrap();
database.insert(64, 128);
database.export(false).unwrap();
let mut database: Database<i32, i32> = Database::open(path).unwrap();
database.insert(1, 2);
database.insert(128, 256);
println!("iter_ram:");
database.iter_ram().for_each(|pair| {
println!("{:?}", pair);
});
println!("iter_disk:");
database.iter_disk().for_each(|pair| {
println!("{:?}", pair.unwrap());
});
println!("iter_ram_then_disk:");
database.iter_ram_then_disk().for_each(|pair| {
println!("{:?}", pair);
});
database.export(false).unwrap();
}
```
+252
View File
@@ -0,0 +1,252 @@
// https://docs.rs/sanakirja/latest/sanakirja/index.html
// https://pijul.org/posts/2021-02-06-rethinking-sanakirja/
use std::{
collections::{BTreeMap, BTreeSet},
fmt::Debug,
fs, io, mem,
path::PathBuf,
result::Result,
};
use sanakirja::btree::{page, Db_};
pub use sanakirja::*;
///
/// A simple wrapper around Sanakirja aatabase that acts as a very fast on disk BTreeMap.
///
/// The state of the tree is uncommited until `.export()` is called during which it is unsafe to stop the program.
///
pub struct Database<Key, Value>
where
Key: Ord + Clone + Debug + Storable,
Value: Storable + PartialEq,
{
path: PathBuf,
puts: BTreeMap<Key, Value>,
dels: BTreeSet<Key>,
db: Db_<Key, Value, page::Page<Key, Value>>,
txn: MutTxn<Env, ()>,
}
const ROOT_DB: usize = 0;
const PAGE_SIZE: u64 = 4096;
impl<Key, Value> Database<Key, Value>
where
Key: Ord + Clone + Debug + Storable,
Value: Storable + PartialEq,
{
/// Open a database without a lock file where only one instance is safe to open.
pub fn open(path: PathBuf) -> Result<Self, Error> {
let env = unsafe { Env::new_nolock(&path, PAGE_SIZE, 1)? };
let mut txn = Env::mut_txn_begin(env)?;
let db = txn
.root_db(ROOT_DB)
.unwrap_or_else(|| unsafe { btree::create_db_(&mut txn).unwrap() });
Ok(Self {
path,
puts: BTreeMap::default(),
dels: BTreeSet::default(),
db,
txn,
})
}
#[inline]
pub fn get(&self, key: &Key) -> Option<&Value> {
if let Some(cached_put) = self.get_from_ram(key) {
return Some(cached_put);
}
self.get_from_disk(key)
}
/// Get only from the uncommited tree (ram) without checking the database (disk)
#[inline]
pub fn get_from_ram(&self, key: &Key) -> Option<&Value> {
self.puts.get(key)
}
/// Get mut only from the uncommited tree (ram) without checking the database (disk)
#[inline]
pub fn get_mut_from_ram(&mut self, key: &Key) -> Option<&mut Value> {
self.puts.get_mut(key)
}
/// Get only from the database (disk) without checking the uncommited tree (ram)
#[inline]
pub fn get_from_disk(&self, key: &Key) -> Option<&Value> {
let option = btree::get(&self.txn, &self.db, key, None).unwrap();
if let Some((key_found, v)) = option {
if key == key_found {
return Some(v);
}
}
None
}
#[inline]
pub fn insert(&mut self, key: Key, value: Value) -> Option<Value> {
self.dels.remove(&key);
self.insert_to_ram(key, value)
}
/// Insert without removing the key to the dels tree, so be sure that it hasn't added to the delete set
#[inline]
pub fn insert_to_ram(&mut self, key: Key, value: Value) -> Option<Value> {
self.puts.insert(key, value)
}
#[inline]
pub fn update(&mut self, key: Key, value: Value) -> Option<Value> {
self.dels.insert(key.clone());
self.puts.insert(key, value)
}
#[inline]
pub fn remove(&mut self, key: &Key) -> Option<Value> {
self.remove_from_ram(key).or_else(|| {
self.remove_later_from_disk(key);
None
})
}
/// Get only from the uncommited tree (ram) without checking the database (disk)
#[inline]
pub fn remove_from_ram(&mut self, key: &Key) -> Option<Value> {
self.puts.remove(key)
}
/// Add the key only to the dels tree without checking if it's present in the puts tree, only use if you are positive that you neither added nor updated an entry with this key
#[inline]
pub fn remove_later_from_disk(&mut self, key: &Key) {
self.dels.insert(key.clone());
}
#[inline]
pub fn is_empty(&self) -> bool {
self.iter_disk().next().is_none()
}
/// Iterate over key/value pairs from the uncommited tree (ram)
#[inline]
pub fn iter_ram(&self) -> std::collections::btree_map::Iter<'_, Key, Value> {
self.puts.iter()
}
/// Iterate over key/value pairs from the database (disk)
#[inline]
pub fn iter_disk(
&self,
) -> btree::Iter<'_, MutTxn<Env, ()>, Key, Value, page::Page<Key, Value>> {
btree::iter(&self.txn, &self.db, None).unwrap()
}
/// Iterate over key/value pairs
#[inline]
pub fn iter_ram_then_disk(&self) -> impl Iterator<Item = (&Key, &Value)> {
self.iter_ram().chain(self.iter_disk().map(|r| r.unwrap()))
}
/// Collect a **clone** of all uncommited key/value pairs (ram)
pub fn collect_ram(&self) -> BTreeMap<Key, Value>
where
Value: Clone,
{
self.puts.clone()
}
/// Collect a **clone** of all key/value pairs from the database (disk)
pub fn collect_disk(&self) -> BTreeMap<Key, Value>
where
Value: Clone,
{
self.iter_disk()
.map(|r| r.unwrap())
.map(|(key, value)| (key.clone(), value.clone()))
.collect::<_>()
}
}
pub trait AnyDatabase {
#[allow(unused)]
fn export(self, defragment: bool) -> Result<(), Error>;
fn boxed_export(self: Box<Self>, defragment: bool) -> Result<(), Error>;
#[allow(unused)]
fn destroy(self) -> io::Result<()>;
}
impl<Key, Value> AnyDatabase for Database<Key, Value>
where
Key: Ord + Clone + Debug + Storable,
Value: Storable + PartialEq + Clone,
{
/// Flush all puts and dels from the ram to disk with an option to defragment the database to save some disk space
///
/// /!\ Do not kill the program while this function is runnning /!\
fn export(self, defragment: bool) -> Result<(), Error> {
Box::new(self).boxed_export(defragment)
}
/// Flush all puts and dels from the ram to disk with an option to defragment the database to save some disk space
///
/// /!\ Do not kill the program while this function is runnning /!\
fn boxed_export(mut self: Box<Self>, defragment: bool) -> Result<(), Error> {
if defragment {
let mut btree = self.as_ref().collect_disk();
let path = self.path.to_owned();
self.dels.iter().for_each(|key| {
btree.remove(key);
});
btree.append(&mut self.puts);
self.destroy()?;
*self = Self::open(path).unwrap();
if !self.is_empty() {
panic!()
}
self.puts = btree;
}
if self.dels.is_empty() && self.puts.is_empty() {
return Ok(());
}
mem::take(&mut self.dels)
.into_iter()
.try_for_each(|key| -> Result<(), Error> {
btree::del(&mut self.txn, &mut self.db, &key, None)?;
Ok(())
})?;
mem::take(&mut self.puts).into_iter().try_for_each(
|(key, value)| -> Result<(), Error> {
btree::put(&mut self.txn, &mut self.db, &key, &value)?;
Ok(())
},
)?;
self.txn.set_root(ROOT_DB, self.db.db.into());
self.txn.commit()
}
fn destroy(self) -> io::Result<()> {
let path = self.path.to_owned();
drop(self);
fs::remove_file(&path)
}
}
+29
View File
@@ -0,0 +1,29 @@
use snkrj::{AnyDatabase, Database};
fn main() {
let path = std::env::temp_dir().join("./db");
let database: Database<i32, i32> = Database::open(path.clone()).unwrap();
let _ = database.destroy();
let mut database: Database<i32, i32> = Database::open(path.clone()).unwrap();
database.insert(64, 128);
database.export(false).unwrap();
let mut database: Database<i32, i32> = Database::open(path).unwrap();
database.insert(1, 2);
database.insert(128, 256);
println!("iter_ram:");
database.iter_ram().for_each(|pair| {
println!("{:?}", pair);
});
println!("iter_disk:");
database.iter_disk().for_each(|pair| {
println!("{:?}", pair.unwrap());
});
println!("iter_ram_then_disk:");
database.iter_ram_then_disk().for_each(|pair| {
println!("{:?}", pair);
});
database.export(false).unwrap();
}
+1 -1
View File
@@ -37,7 +37,7 @@ pub fn main(
&mut datasets,
)?;
if let Some(delay) = config.delay {
if let Some(delay) = config.delay() {
sleep(Duration::from_secs(delay))
}
+50 -21
View File
@@ -15,36 +15,36 @@ use super::MapPath;
#[command(version, about, long_about = None)]
pub struct Config {
/// Bitcoin data directory path, saved
#[arg(long, value_name = "DIR")]
#[arg(long, value_name = "PATH")]
bitcoindir: Option<String>,
/// Kibo data directory path, saved
#[arg(long, value_name = "DIR")]
/// Kibo output directory path, saved
#[arg(long, value_name = "PATH")]
kibodir: Option<String>,
/// Bitcoin RPC ip, default: localhost, saved
#[arg(long, value_name = "IP")]
pub rpcconnect: Option<String>,
rpcconnect: Option<String>,
/// Bitcoin RPC port, default: 8332, saved
#[arg(long, value_name = "PORT")]
pub rpcport: Option<u16>,
rpcport: Option<u16>,
/// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved
#[arg(long, value_name = "PATH")]
pub rpccookiefile: Option<String>,
rpccookiefile: Option<String>,
/// Bitcoin RPC username, saved
#[arg(long, value_name = "USERNAME")]
pub rpcuser: Option<String>,
rpcuser: Option<String>,
/// Bitcoin RPC password, saved
#[arg(long, value_name = "PASSWORD")]
pub rpcpassword: Option<String>,
rpcpassword: Option<String>,
/// Delay between runs, default: 0, saved
#[arg(long, value_name = "SECONDS")]
pub delay: Option<u64>,
delay: Option<u64>,
// Maximum ram you want the program to use in GB, default: 50% of total, not saved
// #[arg(long, value_name = "GB")]
@@ -181,7 +181,7 @@ impl Config {
std::process::exit(1);
}
let path = Path::new(self.bitcoindir.as_ref().unwrap());
let path = self.path_bitcoindir();
if !path.is_dir() {
println!("Expect path '{:#?}' to be a directory.", path);
std::process::exit(1);
@@ -206,18 +206,10 @@ impl Config {
}
pub fn to_rpc_auth(&self) -> color_eyre::Result<Auth> {
let cookie = Path::new(self.bitcoindir.as_ref().unwrap()).join(".cookie");
let cookie = self.path_cookiefile();
if cookie.is_file() {
Ok(Auth::CookieFile(cookie))
} else if self
.rpccookiefile
.as_ref()
.is_some_and(|cookie| Path::new(cookie).is_file())
{
Ok(Auth::CookieFile(PathBuf::from(
self.rpccookiefile.as_ref().unwrap(),
)))
} else if self.rpcuser.is_some() && self.rpcpassword.is_some() {
Ok(Auth::UserPass(
self.rpcuser.clone().unwrap(),
@@ -228,6 +220,18 @@ impl Config {
}
}
pub fn rpcconnect(&self) -> Option<&String> {
self.rpcconnect.as_ref()
}
pub fn rpcport(&self) -> Option<u16> {
self.rpcport
}
pub fn delay(&self) -> Option<u64> {
self.delay
}
pub fn dry_run(&self) -> bool {
self.dry_run.is_some_and(|b| b)
}
@@ -241,11 +245,36 @@ impl Config {
}
pub fn path_bitcoindir(&self) -> PathBuf {
PathBuf::from(self.bitcoindir.as_ref().unwrap())
Self::fix_user_path(self.bitcoindir.as_ref().unwrap().as_ref())
}
fn path_kibodir(&self) -> PathBuf {
PathBuf::from(self.kibodir.as_ref().unwrap())
Self::fix_user_path(self.kibodir.as_ref().unwrap().as_ref())
}
fn path_cookiefile(&self) -> PathBuf {
self.rpccookiefile.as_ref().map_or_else(
|| self.path_bitcoindir().join(".cookie"),
|p| Self::fix_user_path(p.as_str()),
)
}
fn fix_user_path(path: &str) -> PathBuf {
let fix = move |pattern: &str| {
if path.starts_with(pattern) {
let path = &path
.replace(&format!("{pattern}/"), "")
.replace(pattern, "");
let home = std::env::var("HOME").unwrap();
Some(Path::new(&home).join(path))
} else {
None
}
};
fix("~").unwrap_or_else(|| fix("$HOME").unwrap_or_else(|| PathBuf::from(&path)))
}
pub fn path_datasets(&self) -> MapPath {
+2 -5
View File
@@ -12,11 +12,8 @@ fn create_rpc(config: &Config) -> color_eyre::Result<Client> {
Ok(Client::new(
&format!(
"http://{}:{}",
config
.rpcconnect
.as_ref()
.unwrap_or(&"localhost".to_owned()),
config.rpcport.unwrap_or(8332)
config.rpcconnect().unwrap_or(&"localhost".to_owned()),
config.rpcport().unwrap_or(8332)
),
config.to_rpc_auth().unwrap(),
)?)