Merge branch 'master' of github.com:spesmilo/electrum-server
[electrum-server.git] / backends / libbitcoin / history.cpp
1 #include <system_error>
2 #include <boost/logic/tribool.hpp>
3
4 #include <bitcoin/bitcoin.hpp>
5 using namespace libbitcoin;
6
7 #include <boost/python.hpp>
8 namespace python = boost::python;
9
10 #include "memory_buffer.hpp"
11
12 namespace ph = std::placeholders;
13
14 struct info_unit
15 {
16     typedef std::vector<std::string> string_list;
17
18     // block_hash == null_hash if mempool tx
19     hash_digest tx_hash, block_hash;
20     string_list inputs, outputs;
21     size_t index;
22     uint64_t value;
23     size_t height;
24     uint64_t timestamp;
25     bool is_input;
26     // set to empty if unused
27     data_chunk raw_output_script;
28
29     // Convenient storage used by pool txs
30     message::output_point previous_output;
31 };
32
33 typedef std::shared_ptr<info_unit> info_unit_ptr;
34 typedef std::vector<info_unit_ptr> info_unit_list;
35
36 bool inputs_all_loaded(info_unit::string_list inputs)
37 {
38     for (const auto& input: inputs)
39         if (input.empty())
40             return false;
41     return true;
42 }
43
44 struct payment_entry
45 {
46     bool is_loaded()
47     {
48         if (!loaded_output)
49             return false;
50         if (input_exists && !loaded_input)
51             return false;
52         return true;
53     }
54
55     message::output_point outpoint;
56     info_unit_ptr loaded_output;
57     // indeterminate until we know whether this output has a spend
58     boost::tribool input_exists;
59     message::input_point inpoint;
60     // Ignore if input does not exist
61     info_unit_ptr loaded_input;
62 };
63
64 typedef std::shared_ptr<payment_entry> payment_entry_ptr;
65
66 typedef std::vector<payment_entry_ptr> statement_entry;
67
68 typedef std::function<
69     void (const std::error_code&, const statement_entry&,
70         const info_unit_list&)> finish_handler;
71
72 class query_history
73   : public std::enable_shared_from_this<query_history>
74 {
75 public:
76     query_history(async_service& service, blockchain_ptr chain,
77         transaction_pool_ptr txpool, memory_buffer_ptr membuf)
78       : strand_(service.get_service()), chain_(chain),
79         txpool_(txpool), membuf_(membuf), stopped_(false)
80     {
81     }
82
83     void start(const std::string& address, finish_handler handle_finish)
84     {
85         if (!address_.set_encoded(address))
86         {
87             handle_finish(make_error_code(std::errc::address_not_available),
88                 statement_entry(), info_unit_list());
89             return;
90         }
91         handle_finish_ = handle_finish;
92         chain_->fetch_outputs(address_,
93             strand_.wrap(std::bind(&query_history::check_membuf,
94                 shared_from_this(), ph::_1, ph::_2)));
95     }
96
97 private:
98     // Not thread-safe
99     void stop()
100     {
101         BITCOIN_ASSERT(stopped_ == false);
102         stopped_ = true;
103     }
104
105     bool stop_on_error(const std::error_code& ec)
106     {
107         if (ec)
108         {
109             handle_finish_(ec, statement_entry(), info_unit_list());
110             stop();
111         }
112         return stopped_;
113     }
114
115     void check_membuf(const std::error_code& ec,
116         const message::output_point_list& outpoints)
117     {
118         if (stop_on_error(ec))
119             return;
120         membuf_->check(outpoints, address_,
121             strand_.wrap(std::bind(&query_history::start_loading,
122                 shared_from_this(), ph::_1, ph::_2, outpoints)));
123     }
124
125     void start_loading(const std::error_code& ec,
126         const memory_buffer::check_result& membuf_result,
127         const message::output_point_list& outpoints)
128     {
129         if (stop_on_error(ec))
130             return;
131         else if (outpoints.empty() && membuf_result.empty())
132         {
133             handle_finish_(std::error_code(),
134                 statement_entry(), info_unit_list());
135             stop();
136             return;
137         }
138         for (auto outpoint: outpoints)
139         {
140             auto entry = std::make_shared<payment_entry>();
141             entry->outpoint = outpoint;
142             statement_.push_back(entry);
143             chain_->fetch_spend(outpoint,
144                 strand_.wrap(std::bind(&query_history::load_spend,
145                     shared_from_this(), ph::_1, ph::_2, entry)));
146             load_tx_info(outpoint, entry, false);
147         }
148         for (const auto& item: membuf_result)
149         {
150             auto info = std::make_shared<info_unit>();
151             info->tx_hash = std::move(item.tx_hash);
152             info->index = item.index;
153             info->is_input = item.is_input;
154             info->timestamp = item.timestamp;
155             membuf_result_.push_back(info);
156             txpool_->fetch(item.tx_hash,
157                 strand_.wrap(std::bind(&query_history::load_pool_tx,
158                     shared_from_this(), ph::_1, ph::_2, info)));
159         }
160     }
161
162     void load_spend(const std::error_code& ec,
163         const message::input_point inpoint, payment_entry_ptr entry)
164     {
165         // Need a custom self.stop_on_error(...) as a missing spend
166         // is not an error in this case.
167         if (ec && ec != error::unspent_output)
168             stop_on_error(ec);
169         if (stopped_)
170             return;
171
172         if (ec == error::unspent_output)
173         {
174             // This particular entry.output_point
175             // has not been spent yet
176             entry->input_exists = false;
177             finish_if_done();
178         }
179         else
180         {
181             // Has been spent
182             entry->input_exists = true;
183             entry->inpoint = inpoint;
184             load_tx_info(inpoint, entry, true);
185         }
186     }
187
188     bool find_set_value_from_prevout(info_unit_ptr info)
189     {
190         const hash_digest& prev_hash = info->previous_output.hash;
191         for (auto entry: statement_)
192         {
193             BITCOIN_ASSERT(entry->loaded_output);
194             if (entry->loaded_output->tx_hash == prev_hash)
195             {
196                 info->value = entry->loaded_output->value;
197                 return true;
198             }
199             else if (entry->input_exists &&
200                 entry->loaded_input->tx_hash == prev_hash)
201             {
202                 info->value = entry->loaded_output->value;
203                 return true;
204             }
205         }
206         for (auto other_info: membuf_result_)
207             if (other_info->tx_hash == prev_hash)
208             {
209                 info->value = other_info->value;
210                 return true;
211             }
212         return false;
213     }
214
215     void finish_if_done()
216     {
217         for (auto entry: statement_)
218             if (!entry->is_loaded())
219                 return;
220         // Finish up
221         for (auto entry: statement_)
222         {
223             if (entry->input_exists)
224             {
225                 BITCOIN_ASSERT(entry->loaded_input && entry->loaded_output);
226                 // value of the input is simply the inverse of
227                 // the corresponding output
228                 entry->loaded_input->value = entry->loaded_output->value;
229                 // Unspent outputs have a raw_output_script field
230                 // Blank this field as it isn't used
231                 entry->loaded_output->raw_output_script.clear();
232             }
233             else
234             {
235                 // Unspent outputs have a raw_output_script field
236             }
237         }
238         for (auto info: membuf_result_)
239         {
240             // Lookup prevout to set the value field
241             if (info->is_input)
242             {
243                 bool set_prevout_value = find_set_value_from_prevout(info);
244                 BITCOIN_ASSERT(set_prevout_value);
245             }
246         }
247         handle_finish_(std::error_code(), statement_, membuf_result_);
248         stop();
249     }
250
251     template <typename Point>
252     void load_tx_info(const Point& point, payment_entry_ptr entry,
253         bool is_input)
254     {
255         auto info = std::make_shared<info_unit>();
256         info->tx_hash = point.hash;
257         info->index = point.index;
258         info->is_input = is_input;
259         // Before loading the transaction, Stratum requires the hash
260         // of the parent block, so we load the block depth and then
261         // fetch the block header and hash it.
262         chain_->fetch_transaction_index(point.hash,
263             strand_.wrap(std::bind(&query_history::tx_index,
264                 shared_from_this(), ph::_1, ph::_2, ph::_3, entry, info)));
265     }
266
267     void tx_index(const std::error_code& ec, size_t block_depth,
268         size_t offset, payment_entry_ptr entry, info_unit_ptr info)
269     {
270         if (stop_on_error(ec))
271             return;
272         info->height = block_depth;
273         // And now for the block hash
274         chain_->fetch_block_header(block_depth,
275             strand_.wrap(std::bind(&query_history::block_header,
276                 shared_from_this(), ph::_1, ph::_2, entry, info)));
277     }
278
279     void block_header(const std::error_code& ec,
280         const message::block blk_head,
281         payment_entry_ptr entry, info_unit_ptr info)
282     {
283         if (stop_on_error(ec))
284             return;
285         info->timestamp = blk_head.timestamp;
286         info->block_hash = hash_block_header(blk_head);
287         // Now load the actual main transaction for this input or output
288         chain_->fetch_transaction(info->tx_hash,
289             strand_.wrap(std::bind(&query_history::load_chain_tx,
290                 shared_from_this(), ph::_1, ph::_2, entry, info)));
291     }
292
293     void load_tx(const message::transaction& tx, info_unit_ptr info)
294     {
295         // List of output addresses
296         for (const auto& tx_out: tx.outputs)
297         {
298             payment_address addr;
299             // Attempt to extract address from output script
300             if (extract(addr, tx_out.output_script))
301                 info->outputs.push_back(addr.encoded());
302             else
303                 info->outputs.push_back("Unknown");
304         }
305         // For the inputs, we need the originator address which has to
306         // be looked up in the blockchain.
307         // Create list of empty strings and then populate it.
308         // Loading has finished when list is no longer all empty strings.
309         for (const auto& tx_in: tx.inputs)
310             info->inputs.push_back("");
311         // If this transaction was loaded for an input, then we already
312         // have a source address for at least one input.
313         if (info->is_input)
314         {
315             BITCOIN_ASSERT(info->index < info->inputs.size());
316             info->inputs[info->index] = address_.encoded();
317         }
318     }
319
320     void load_chain_tx(const std::error_code& ec,
321         const message::transaction& tx,
322         payment_entry_ptr entry, info_unit_ptr info)
323     {
324         if (stop_on_error(ec))
325             return;
326         load_tx(tx, info);
327         if (!info->is_input)
328         {
329             BITCOIN_ASSERT(info->index < tx.outputs.size());
330             const auto& our_output = tx.outputs[info->index];
331             info->value = our_output.value;
332             // Save serialised output script in case this output is unspent
333             info->raw_output_script = save_script(our_output.output_script);
334         }
335         // If all the inputs are loaded
336         if (inputs_all_loaded(info->inputs))
337         {
338             // We are the sole input
339             BITCOIN_ASSERT(info->is_input);
340             entry->loaded_input = info;
341             finish_if_done();
342         }
343
344         // *********
345         // fetch_input_txs
346
347         // Load the previous_output for every input so we can get
348         // the output address
349         for (size_t input_index = 0;
350             input_index < tx.inputs.size(); ++input_index)
351         {
352             if (info->is_input && info->index == input_index)
353                 continue;
354             const auto& prevout = tx.inputs[input_index].previous_output;
355             chain_->fetch_transaction(prevout.hash,
356                 strand_.wrap(std::bind(&query_history::load_input_chain_tx,
357                     shared_from_this(), ph::_1, ph::_2, prevout.index,
358                         entry, info, input_index)));
359         }
360     }
361
362     void load_pool_tx(const std::error_code& ec,
363         const message::transaction& tx, info_unit_ptr info)
364     {
365         if (stop_on_error(ec))
366             return;
367         // block_hash = mempool:5
368         // inputs (load from prevtx)
369         // outputs (load from tx)
370         // raw_output_script (load from tx)
371         // height is always None
372         // value (get from finish_if_done)
373         load_tx(tx, info);
374         if (info->is_input)
375         {
376             BITCOIN_ASSERT(info->index < tx.inputs.size());
377             info->previous_output = tx.inputs[info->index].previous_output;
378         }
379         else
380         {
381             const auto& our_output = tx.outputs[info->index];
382             info->value = our_output.value;
383             info->raw_output_script = save_script(our_output.output_script);
384         }
385         // If all the inputs are loaded
386         if (inputs_all_loaded(info->inputs))
387         {
388             // We are the sole input
389             BITCOIN_ASSERT(info->is_input);
390             // No more inputs left to load
391             // This info has finished loading
392             info->height = 0;
393             info->block_hash = null_hash;
394             finish_if_done();
395         }
396
397         // *********
398         // fetch_input_txs
399
400         // Load the previous_output for every input so we can get
401         // the output address
402         for (size_t input_index = 0;
403             input_index < tx.inputs.size(); ++input_index)
404         {
405             if (info->is_input && info->index == input_index)
406                 continue;
407             const auto& prevout = tx.inputs[input_index].previous_output;
408             chain_->fetch_transaction(prevout.hash,
409                 strand_.wrap(std::bind(&query_history::load_input_pool_tx,
410                     shared_from_this(), ph::_1, ph::_2, prevout.index,
411                         info, input_index)));
412         }
413     }
414
415     void load_input_tx(const message::transaction& tx, size_t output_index,
416         info_unit_ptr info, size_t input_index)
417     {
418         // For our input, we load the previous tx so we can get the
419         // corresponding output.
420         // We need the output to extract the address.
421         BITCOIN_ASSERT(output_index < tx.outputs.size());
422         const auto& out_script = tx.outputs[output_index].output_script;
423         payment_address addr;
424         BITCOIN_ASSERT(input_index < info->inputs.size());
425         if (extract(addr, out_script))
426             info->inputs[input_index] = addr.encoded();
427         else
428             info->inputs[input_index] = "Unknown";
429     }
430
431     void load_input_chain_tx(const std::error_code& ec,
432         const message::transaction& tx, size_t output_index,
433         payment_entry_ptr entry, info_unit_ptr info, size_t input_index)
434     {
435         if (stop_on_error(ec))
436             return;
437         load_input_tx(tx, output_index, info, input_index);
438         // If all the inputs are loaded, then we have finished loading
439         // the info for this input-output entry pair
440         if (inputs_all_loaded(info->inputs))
441         {
442             if (info->is_input)
443                 entry->loaded_input = info;
444             else
445                 entry->loaded_output = info;
446         }
447         finish_if_done();
448     }
449
450     void load_input_pool_tx(const std::error_code& ec,
451         const message::transaction& tx, size_t output_index,
452         info_unit_ptr info, size_t input_index)
453     {
454         if (stop_on_error(ec))
455             return;
456         load_input_tx(tx, output_index, info, input_index);
457         if (inputs_all_loaded(info->inputs))
458         {
459             // No more inputs left to load
460             // This info has finished loading
461             info->height = 0;
462             info->block_hash = null_hash;
463         }
464         finish_if_done();
465     }
466
467     io_service::strand strand_;
468
469     blockchain_ptr chain_;
470     transaction_pool_ptr txpool_;
471     memory_buffer_ptr membuf_;
472     bool stopped_;
473
474     statement_entry statement_;
475     info_unit_list membuf_result_;
476     payment_address address_;
477     finish_handler handle_finish_;
478 };
479
480 typedef std::shared_ptr<query_history> query_history_ptr;
481
482 void write_xputs_strings(std::stringstream& ss, const char* xput_name,
483     const info_unit::string_list& xputs)
484 {
485     ss << '"' << xput_name << "\": [";
486     for (auto it = xputs.begin(); it != xputs.end(); ++it)
487     {
488         if (it != xputs.begin())
489             ss << ",";
490         ss << '"' << *it << '"';
491     }
492     ss << "]";
493 }
494
495 void write_info(std::string& json, info_unit_ptr info)
496 {
497     std::stringstream ss;
498     ss << "{"
499         << "\"tx_hash\": \"" << pretty_hex(info->tx_hash) << "\","
500         << "\"index\": " << info->index << ","
501         // x for received, and -x for sent amounts
502         << "\"value\": " << (info->is_input ? "-" : "") << info->value << ","
503         << "\"timestamp\": " << info->timestamp << ","
504         << "\"is_input\": " << info->is_input << ",";
505     if (info->height == 0 && info->block_hash == null_hash)
506     {
507         ss << "\"block_hash\": \"mempool\","
508             << "\"height\": null,";
509     }
510     else
511     {
512         ss << "\"block_hash\": \"" << pretty_hex(info->block_hash) << "\","
513             << "\"height\": " << info->height << ",";
514     }
515     write_xputs_strings(ss, "inputs", info->inputs);
516     ss << ",";
517     write_xputs_strings(ss, "outputs", info->outputs);
518     if (!info->raw_output_script.empty())
519         ss << ","
520             << "\"raw_output_script\": \""
521             << pretty_hex(info->raw_output_script) << "\"";
522     ss << "}";
523     json += ss.str();
524 }
525
526 void keep_query_alive_proxy(const std::error_code& ec,
527     const statement_entry& statement, const info_unit_list& membuf_result,
528     python::object handle_finish, query_history_ptr history)
529 {
530     std::string json = "[";
531     for (auto it = statement.begin(); it != statement.end(); ++it)
532     {
533         if (it != statement.begin())
534             json += ",";
535         auto entry = *it;
536         BITCOIN_ASSERT(entry->loaded_output);
537         write_info(json, entry->loaded_output);
538         if (entry->input_exists)
539         {
540             BITCOIN_ASSERT(entry->loaded_input);
541             json += ",";
542             write_info(json, entry->loaded_input);
543         }
544     }
545     // A bit of super glue
546     if (!statement.empty() && !membuf_result.empty())
547         json += ",";
548     for (auto it = membuf_result.begin(); it != membuf_result.end(); ++it)
549     {
550         if (it != membuf_result.begin())
551             json += ",";
552         write_info(json, *it);
553     }
554     json += "]";
555     pyfunction<const std::error_code&, const std::string&> f(handle_finish);
556     f(ec, json);
557 }
558
559 void payment_history(async_service_ptr service, blockchain_ptr chain,
560     transaction_pool_ptr txpool, memory_buffer_ptr membuf,
561     const std::string& address, python::object handle_finish)
562 {
563     query_history_ptr history =
564         std::make_shared<query_history>(*service, chain, txpool, membuf);
565     history->start(address,
566         std::bind(keep_query_alive_proxy, ph::_1, ph::_2, ph::_3,
567             handle_finish, history));
568 }
569
570 BOOST_PYTHON_MODULE(_history)
571 {
572     using namespace boost::python;
573     def("payment_history", payment_history);
574 }
575