-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmod.rs
170 lines (145 loc) · 4.82 KB
/
mod.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! # Bitcoin Ledger
//!
//! Mock Bitcoin ledger.
//!
//! This crate is designed to be used as immutable, because of the `RpcApi`'s
//! immutable nature.
use crate::utils;
use rusqlite::Connection;
use std::{
env,
process::Command,
sync::{Arc, Mutex},
};
pub mod address;
mod block;
pub(crate) mod errors;
mod script;
mod spending_requirements;
mod transactions;
mod utxo;
/// Mock Bitcoin ledger.
#[derive(Clone, Debug)]
pub struct Ledger {
/// Database connection.
database: Arc<Mutex<Connection>>,
}
impl Ledger {
/// Creates a new empty ledger.
///
/// An SQLite database created at OS's temp directory. Database is named
/// `path`. This can be used to identify different databases created by
/// different tests.
///
/// # Panics
///
/// Panics if SQLite connection can't be established and initial query can't
/// be run.
#[tracing::instrument]
pub fn new(path: &str) -> Self {
let path = Ledger::get_database_path(path);
let _ = utils::initialize_logger();
// Check if database has another connections.
let is_open = {
let ret = Command::new("lsof")
.args([&path])
.output()
.expect("failed to execute process");
!ret.stdout.is_empty()
};
let database = Connection::open(path.clone()).unwrap();
// If database has another connections, skip clearing.
if !is_open {
tracing::trace!("Creating new database at path {path}");
Ledger::drop_tables(&database).unwrap();
Ledger::create_tables(&database).unwrap();
}
tracing::trace!("Database connection to {path} is established");
Self {
database: Arc::new(Mutex::new(database)),
}
}
/// Connects the ledger, previously created by the `new` call. This function
/// won't clean any data from database. Therefore it is a useful function
/// for cloning.
///
/// This function is needed because `bitcoincore_rpc` doesn't provide a
/// `clone` interface. Therefore users of that library need to call `new`
/// and establish a new connection to the Bitcoin. This is a solution to
/// that problem: We won't clean any mock data and use previously created
/// database.
///
/// # Panics
///
/// Panics if SQLite connection can't be established.
#[deprecated(
since = "0.0.11",
note = "`new()` also checks for open ledgers, not needed"
)]
pub fn new_without_cleanup(path: &str) -> Self {
let path = Ledger::get_database_path(path);
let database = Connection::open(path.clone()).unwrap();
tracing::trace!("Connecting to the existing database {path} without resetting");
Self {
database: Arc::new(Mutex::new(database)),
}
}
fn get_database_path(path: &str) -> String {
env::temp_dir().to_str().unwrap().to_owned() + "/bitcoin_mock_rpc_" + path
}
fn drop_tables(database: &Connection) -> Result<(), rusqlite::Error> {
database.execute_batch(
"
DROP TABLE IF EXISTS blocks;
DROP TABLE IF EXISTS mempool;
DROP TABLE IF EXISTS transactions;
DROP TABLE IF EXISTS utxos;
",
)
}
/// This is where all the ledger data is kept. Note that it is not aimed to
/// hold all kind of information about the blockchain. Just holds enough
/// data to provide a persistent storage for the limited features that the
/// library provides.
fn create_tables(database: &Connection) -> Result<(), rusqlite::Error> {
database.execute_batch(
"
CREATE TABLE blocks
(
height INTEGER NOT NULL,
time INTEGER NOT NULL,
hash TEXT NOT NULL,
coinbase TEXT NOT NULL,
body BLOB NOT NULL
CONSTRAINT height PRIMARY KEY
);
INSERT INTO blocks (height, time, hash, coinbase, body) VALUES (0, 500000000, '00000000000000000000', 0, 0);
CREATE TABLE mempool
(
txid TEXT NOT NULL
CONSTRAINT txid PRIMARY KEY
);
CREATE TABLE transactions
(
txid TEXT NOT NULL,
block_height INTEGER NOT NULL,
body BLOB NOT NULL
CONSTRAINT txid PRIMARY KEY
);
CREATE TABLE utxos
(
txid TEXT NOT NULL,
vout INTEGER NOT NULL
);
",
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn new() {
let _should_not_panic = Ledger::new("ledger_new");
}
}