1use {
2 bitcoin::{
3 blockdata::constants::COIN_VALUE, blockdata::script, consensus::encode::serialize,
4 hash_types::BlockHash, hashes::Hash, Amount, Block, BlockHeader, Network, OutPoint,
5 PackedLockTime, Script, Sequence, Transaction, TxIn, TxMerkleNode, TxOut, Txid, Witness, Wtxid,
6 },
7 bitcoincore_rpc_json::{GetRawTransactionResult, ListUnspentResultEntry},
8 jsonrpc_core::{IoHandler, Value},
9 jsonrpc_http_server::{CloseHandle, ServerBuilder},
10 std::collections::BTreeMap,
11 std::{
12 sync::{Arc, Mutex},
13 thread,
14 },
15};
16
17pub fn spawn() -> Handle {
18 let server = Server::new();
19 let state = server.state.clone();
20 let mut io = IoHandler::default();
21 io.extend_with(server.to_delegate());
22
23 let rpc_server = ServerBuilder::new(io)
24 .threads(1)
25 .start_http(&"127.0.0.1:0".parse().unwrap())
26 .unwrap();
27
28 let close_handle = rpc_server.close_handle();
29 let port = rpc_server.address().port();
30
31 thread::spawn(|| rpc_server.wait());
32
33 Handle {
34 close_handle: Some(close_handle),
35 port,
36 state,
37 }
38}
39
40pub struct TransactionTemplate<'a> {
41 pub input_slots: &'a [(usize, usize, usize)],
42 pub output_count: usize,
43 pub fee: u64,
44}
45
46struct State {
47 blocks: BTreeMap<BlockHash, Block>,
48 hashes: Vec<BlockHash>,
49 mempool: Vec<Transaction>,
50 nonce: u32,
51 transactions: BTreeMap<Txid, Transaction>,
52}
53
54impl State {
55 fn new() -> Self {
56 let mut hashes = Vec::new();
57 let mut blocks = BTreeMap::new();
58
59 let genesis_block = bitcoin::blockdata::constants::genesis_block(Network::Bitcoin);
60 let genesis_block_hash = genesis_block.block_hash();
61 hashes.push(genesis_block_hash);
62 blocks.insert(genesis_block_hash, genesis_block);
63
64 Self {
65 blocks,
66 hashes,
67 mempool: Vec::new(),
68 nonce: 0,
69 transactions: BTreeMap::new(),
70 }
71 }
72
73 fn push_block(&mut self) -> Block {
74 let coinbase = Transaction {
75 version: 0,
76 lock_time: PackedLockTime(0),
77 input: vec![TxIn {
78 previous_output: OutPoint::null(),
79 script_sig: script::Builder::new()
80 .push_scriptint(self.blocks.len().try_into().unwrap())
81 .into_script(),
82 sequence: Sequence(0),
83 witness: Witness::new(),
84 }],
85 output: vec![TxOut {
86 value: 50 * COIN_VALUE
87 + self
88 .mempool
89 .iter()
90 .map(|tx| {
91 tx.input
92 .iter()
93 .map(|txin| {
94 self.transactions[&txin.previous_output.txid].output
95 [txin.previous_output.vout as usize]
96 .value
97 })
98 .sum::<u64>()
99 - tx.output.iter().map(|txout| txout.value).sum::<u64>()
100 })
101 .sum::<u64>(),
102 script_pubkey: Script::new(),
103 }],
104 };
105
106 let block = Block {
107 header: BlockHeader {
108 version: 0,
109 prev_blockhash: *self.hashes.last().unwrap(),
110 merkle_root: TxMerkleNode::all_zeros(),
111 time: 0,
112 bits: 0,
113 nonce: self.nonce,
114 },
115 txdata: std::iter::once(coinbase)
116 .chain(self.mempool.drain(0..))
117 .collect(),
118 };
119
120 for tx in &block.txdata {
121 self.transactions.insert(tx.txid(), tx.clone());
122 }
123 self.blocks.insert(block.block_hash(), block.clone());
124 self.hashes.push(block.block_hash());
125 self.nonce += 1;
126
127 block
128 }
129
130 fn pop_block(&mut self) -> BlockHash {
131 let blockhash = self.hashes.pop().unwrap();
132 self.blocks.remove(&blockhash);
133
134 blockhash
135 }
136
137 fn broadcast_tx(&mut self, options: TransactionTemplate) -> Txid {
138 let mut total_value = 0;
139 let mut input = Vec::new();
140 for (height, tx, vout) in options.input_slots {
141 let tx = &self.blocks.get(&self.hashes[*height]).unwrap().txdata[*tx];
142 total_value += tx.output[*vout].value;
143 input.push(TxIn {
144 previous_output: OutPoint::new(tx.txid(), *vout as u32),
145 script_sig: Script::new(),
146 sequence: Sequence(0),
147 witness: Witness::new(),
148 });
149 }
150
151 let value_per_output = (total_value - options.fee) / options.output_count as u64;
152 assert_eq!(
153 value_per_output * options.output_count as u64 + options.fee,
154 total_value
155 );
156
157 let tx = Transaction {
158 version: 0,
159 lock_time: PackedLockTime(0),
160 input,
161 output: (0..options.output_count)
162 .map(|_| TxOut {
163 value: value_per_output,
164 script_pubkey: script::Builder::new().into_script(),
165 })
166 .collect(),
167 };
168 self.mempool.push(tx.clone());
169
170 tx.txid()
171 }
172}
173
174pub struct Server {
175 state: Arc<Mutex<State>>,
176}
177
178impl Server {
179 fn new() -> Self {
180 Self {
181 state: Arc::new(Mutex::new(State::new())),
182 }
183 }
184}
185
186#[jsonrpc_derive::rpc]
187pub trait Api {
188 #[rpc(name = "getblockhash")]
189 fn getblockhash(&self, height: usize) -> Result<BlockHash, jsonrpc_core::Error>;
190
191 #[rpc(name = "getblockheader")]
192 fn getblockheader(
193 &self,
194 blockhash: BlockHash,
195 verbose: bool,
196 ) -> Result<String, jsonrpc_core::Error>;
197
198 #[rpc(name = "getblock")]
199 fn getblock(&self, blockhash: BlockHash, verbosity: u64) -> Result<String, jsonrpc_core::Error>;
200
201 #[rpc(name = "getrawtransaction")]
202 fn get_raw_transaction(
203 &self,
204 txid: Txid,
205 verbose: bool,
206 blockhash: Option<BlockHash>,
207 ) -> Result<Value, jsonrpc_core::Error>;
208
209 #[rpc(name = "listunspent")]
210 fn list_unspent(
211 &self,
212 minconf: Option<usize>,
213 maxconf: Option<usize>,
214 address: Option<bitcoin::Address>,
215 include_unsafe: Option<bool>,
216 query_options: Option<String>,
217 ) -> Result<Vec<ListUnspentResultEntry>, jsonrpc_core::Error>;
218}
219
220impl Api for Server {
221 fn getblockhash(&self, height: usize) -> Result<BlockHash, jsonrpc_core::Error> {
222 match self.state.lock().unwrap().hashes.get(height) {
223 Some(block_hash) => Ok(*block_hash),
224 None => Err(jsonrpc_core::Error::new(
225 jsonrpc_core::types::error::ErrorCode::ServerError(-8),
226 )),
227 }
228 }
229
230 fn getblockheader(
231 &self,
232 block_hash: BlockHash,
233 verbose: bool,
234 ) -> Result<String, jsonrpc_core::Error> {
235 assert!(!verbose);
236 match self.state.lock().unwrap().blocks.get(&block_hash) {
237 Some(block) => Ok(hex::encode(serialize(&block.header))),
238 None => Err(jsonrpc_core::Error::new(
239 jsonrpc_core::types::error::ErrorCode::ServerError(-8),
240 )),
241 }
242 }
243
244 fn getblock(&self, block_hash: BlockHash, verbosity: u64) -> Result<String, jsonrpc_core::Error> {
245 assert_eq!(verbosity, 0, "Verbosity level {verbosity} is unsupported");
246 match self.state.lock().unwrap().blocks.get(&block_hash) {
247 Some(block) => Ok(hex::encode(serialize(block))),
248 None => Err(jsonrpc_core::Error::new(
249 jsonrpc_core::types::error::ErrorCode::ServerError(-8),
250 )),
251 }
252 }
253
254 fn get_raw_transaction(
255 &self,
256 txid: Txid,
257 verbose: bool,
258 blockhash: Option<BlockHash>,
259 ) -> Result<Value, jsonrpc_core::Error> {
260 assert_eq!(blockhash, None, "Blockhash param is unsupported");
261 if verbose {
262 match self.state.lock().unwrap().transactions.get(&txid) {
263 Some(_) => Ok(
264 serde_json::to_value(GetRawTransactionResult {
265 in_active_chain: None,
266 hex: Vec::new(),
267 txid: Txid::all_zeros(),
268 hash: Wtxid::all_zeros(),
269 size: 0,
270 vsize: 0,
271 version: 0,
272 locktime: 0,
273 vin: Vec::new(),
274 vout: Vec::new(),
275 blockhash: None,
276 confirmations: Some(1),
277 time: None,
278 blocktime: None,
279 })
280 .unwrap(),
281 ),
282 None => Err(jsonrpc_core::Error::new(
283 jsonrpc_core::types::error::ErrorCode::ServerError(-8),
284 )),
285 }
286 } else {
287 match self.state.lock().unwrap().transactions.get(&txid) {
288 Some(tx) => Ok(Value::String(hex::encode(serialize(tx)))),
289 None => Err(jsonrpc_core::Error::new(
290 jsonrpc_core::types::error::ErrorCode::ServerError(-8),
291 )),
292 }
293 }
294 }
295
296 fn list_unspent(
297 &self,
298 minconf: Option<usize>,
299 maxconf: Option<usize>,
300 address: Option<bitcoin::Address>,
301 include_unsafe: Option<bool>,
302 query_options: Option<String>,
303 ) -> Result<Vec<ListUnspentResultEntry>, jsonrpc_core::Error> {
304 assert_eq!(minconf, None, "minconf param not supported");
305 assert_eq!(maxconf, None, "maxconf param not supported");
306 assert_eq!(address, None, "address param not supported");
307 assert_eq!(include_unsafe, None, "include_unsafe param not supported");
308 assert_eq!(query_options, None, "query_options param not supported");
309 Ok(
310 self
311 .state
312 .lock()
313 .unwrap()
314 .transactions
315 .iter()
316 .flat_map(|(txid, tx)| {
317 (0..tx.output.len()).map(|vout| ListUnspentResultEntry {
318 txid: *txid,
319 vout: vout as u32,
320 address: None,
321 label: None,
322 redeem_script: None,
323 witness_script: None,
324 script_pub_key: Script::new(),
325 amount: Amount::default(),
326 confirmations: 0,
327 spendable: true,
328 solvable: true,
329 descriptor: None,
330 safe: true,
331 })
332 })
333 .collect(),
334 )
335 }
336}
337
338pub struct Handle {
339 close_handle: Option<CloseHandle>,
340 port: u16,
341 state: Arc<Mutex<State>>,
342}
343
344impl Handle {
345 pub fn url(&self) -> String {
346 format!("http://127.0.0.1:{}", self.port)
347 }
348
349 pub fn mine_blocks(&self, num: u64) -> Vec<Block> {
350 let mut bitcoin_rpc_data = self.state.lock().unwrap();
351 (0..num).map(|_| bitcoin_rpc_data.push_block()).collect()
352 }
353
354 pub fn broadcast_tx(&self, options: TransactionTemplate) -> Txid {
355 self.state.lock().unwrap().broadcast_tx(options)
356 }
357
358 pub fn invalidate_tip(&self) -> BlockHash {
359 self.state.lock().unwrap().pop_block()
360 }
361
362 pub fn tx(&self, bi: usize, ti: usize) -> Transaction {
363 let state = self.state.lock().unwrap();
364 state.blocks[&state.hashes[bi]].txdata[ti].clone()
365 }
366}
367
368impl Drop for Handle {
369 fn drop(&mut self) {
370 self.close_handle.take().unwrap().close();
371 }
372}