C++ core with working memory pool
[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
30 typedef std::shared_ptr<info_unit> info_unit_ptr;
31
32 bool inputs_all_loaded(info_unit::string_list inputs)
33 {
34     for (const auto& input: inputs)
35         if (input.empty())
36             return false;
37     return true;
38 }
39
40 struct payment_entry
41 {
42     bool is_loaded()
43     {
44         if (!loaded_output)
45             return false;
46         if (input_exists && !loaded_input)
47             return false;
48         return true;
49     }
50
51     message::output_point outpoint;
52     info_unit_ptr loaded_output;
53     // indeterminate until we know whether this output has a spend
54     boost::tribool input_exists;
55     message::input_point inpoint;
56     // Ignore if input does not exist
57     info_unit_ptr loaded_input;
58 };
59
60 typedef std::shared_ptr<payment_entry> payment_entry_ptr;
61
62 typedef std::vector<payment_entry_ptr> statement_entry;
63
64 typedef std::function<
65     void (const std::error_code&, const statement_entry&)> finish_handler;
66
67 class query_history
68   : public std::enable_shared_from_this<query_history>
69 {
70 public:
71     query_history(async_service& service, blockchain_ptr chain,
72         transaction_pool_ptr txpool, memory_buffer_ptr membuf)
73       : strand_(service.get_service()), chain_(chain),
74         txpool_(txpool), stopped_(false)
75     {
76     }
77
78     void start(const std::string& address, finish_handler handle_finish)
79     {
80         address_.set_encoded(address);
81         handle_finish_ = handle_finish;
82         chain_->fetch_outputs(address_,
83             strand_.wrap(std::bind(&query_history::start_loading,
84                 shared_from_this(), ph::_1, ph::_2)));
85     }
86
87 private:
88     // Not thread-safe
89     void stop()
90     {
91         BITCOIN_ASSERT(stopped_ == false);
92         stopped_ = true;
93     }
94
95     bool stop_on_error(const std::error_code& ec)
96     {
97         if (ec)
98         {
99             handle_finish_(ec, statement_entry());
100             stop();
101         }
102         return stopped_;
103     }
104
105     void start_loading(const std::error_code& ec,
106         const message::output_point_list& outpoints)
107     {
108         if (stop_on_error(ec))
109             return;
110         for (auto outpoint: outpoints)
111         {
112             auto entry = std::make_shared<payment_entry>();
113             entry->outpoint = outpoint;
114             statement_.push_back(entry);
115             chain_->fetch_spend(outpoint,
116                 strand_.wrap(std::bind(&query_history::load_spend,
117                     shared_from_this(), ph::_1, ph::_2, entry)));
118             load_tx_info(outpoint, entry, false);
119         }
120     }
121
122     void load_spend(const std::error_code& ec,
123         const message::input_point inpoint, payment_entry_ptr entry)
124     {
125         // Need a custom self.stop_on_error(...) as a missing spend
126         // is not an error in this case.
127         if (ec && ec != error::unspent_output)
128             stop_on_error(ec);
129         if (stopped_)
130             return;
131
132         if (ec == error::unspent_output)
133         {
134             // This particular entry.output_point
135             // has not been spent yet
136             entry->input_exists = false;
137             finish_if_done();
138         }
139         else
140         {
141             // Has been spent
142             entry->input_exists = true;
143             entry->inpoint = inpoint;
144             load_tx_info(inpoint, entry, true);
145         }
146     }
147
148     void finish_if_done()
149     {
150         for (auto entry: statement_)
151             if (!entry->is_loaded())
152                 return;
153         // Finish up
154         for (auto entry: statement_)
155         {
156             if (entry->input_exists)
157             {
158                 BITCOIN_ASSERT(entry->loaded_input && entry->loaded_output);
159                 // value of the input is simply the inverse of
160                 // the corresponding output
161                 entry->loaded_input->value = entry->loaded_output->value;
162                 // Unspent outputs have a raw_output_script field
163                 // Blank this field as it isn't used
164                 entry->loaded_output->raw_output_script.clear();
165             }
166             else
167             {
168                 // Unspent outputs have a raw_output_script field
169             }
170         }
171         handle_finish_(std::error_code(), statement_);
172         stop();
173     }
174
175     template <typename Point>
176     void load_tx_info(const Point& point, payment_entry_ptr entry,
177         bool is_input)
178     {
179         auto info = std::make_shared<info_unit>();
180         info->tx_hash = point.hash;
181         info->index = point.index;
182         info->is_input = is_input;
183         // Before loading the transaction, Stratum requires the hash
184         // of the parent block, so we load the block depth and then
185         // fetch the block header and hash it.
186         chain_->fetch_transaction_index(point.hash,
187             strand_.wrap(std::bind(&query_history::tx_index,
188                 shared_from_this(), ph::_1, ph::_2, ph::_3, entry, info)));
189     }
190
191     void tx_index(const std::error_code& ec, size_t block_depth,
192         size_t offset, payment_entry_ptr entry, info_unit_ptr info)
193     {
194         if (stop_on_error(ec))
195             return;
196         info->height = block_depth;
197         // And now for the block hash
198         chain_->fetch_block_header(block_depth,
199             strand_.wrap(std::bind(&query_history::block_header,
200                 shared_from_this(), ph::_1, ph::_2, entry, info)));
201     }
202
203     void block_header(const std::error_code& ec,
204         const message::block blk_head,
205         payment_entry_ptr entry, info_unit_ptr info)
206     {
207         if (stop_on_error(ec))
208             return;
209         info->timestamp = blk_head.timestamp;
210         info->block_hash = hash_block_header(blk_head);
211         // Now load the actual main transaction for this input or output
212         chain_->fetch_transaction(info->tx_hash,
213             strand_.wrap(std::bind(&query_history::load_chain_tx,
214                 shared_from_this(), ph::_1, ph::_2, entry, info)));
215     }
216
217     void load_tx(const message::transaction& tx, info_unit_ptr info)
218     {
219         // List of output addresses
220         for (const auto& tx_out: tx.outputs)
221         {
222             payment_address addr;
223             // Attempt to extract address from output script
224             if (extract(addr, tx_out.output_script))
225                 info->outputs.push_back(addr.encoded());
226             else
227                 info->outputs.push_back("Unknown");
228         }
229         // For the inputs, we need the originator address which has to
230         // be looked up in the blockchain.
231         // Create list of empty strings and then populate it.
232         // Loading has finished when list is no longer all empty strings.
233         for (const auto& tx_in: tx.inputs)
234             info->inputs.push_back("");
235         // If this transaction was loaded for an input, then we already
236         // have a source address for at least one input.
237         if (info->is_input)
238         {
239             BITCOIN_ASSERT(info->index < info->inputs.size());
240             info->inputs[info->index] = address_.encoded();
241         }
242     }
243
244     void load_chain_tx(const std::error_code& ec,
245         const message::transaction& tx,
246         payment_entry_ptr entry, info_unit_ptr info)
247     {
248         if (stop_on_error(ec))
249             return;
250         load_tx(tx, info);
251         if (!info->is_input)
252         {
253             BITCOIN_ASSERT(info->index < tx.outputs.size());
254             const auto& our_output = tx.outputs[info->index];
255             info->value = our_output.value;
256             // Save serialised output script in case this output is unspent
257             info->raw_output_script = save_script(our_output.output_script);
258         }
259         // If all the inputs are loaded
260         if (inputs_all_loaded(info->inputs))
261         {
262             // We are the sole input
263             BITCOIN_ASSERT(info->is_input);
264             entry->loaded_input = info;
265             finish_if_done();
266         }
267
268         // *********
269         // fetch_input_txs
270
271         // Load the previous_output for every input so we can get
272         // the output address
273         for (size_t input_index = 0;
274             input_index < tx.inputs.size(); ++input_index)
275         {
276             if (info->is_input && info->index == input_index)
277                 continue;
278             const auto& prevout = tx.inputs[input_index].previous_output;
279             chain_->fetch_transaction(prevout.hash,
280                 strand_.wrap(std::bind(&query_history::load_input_chain_tx,
281                     shared_from_this(), ph::_1, ph::_2, prevout.index,
282                         entry, info, input_index)));
283         }
284     }
285
286     void load_input_tx(const message::transaction& tx, size_t output_index,
287         info_unit_ptr info, size_t input_index)
288     {
289         // For our input, we load the previous tx so we can get the
290         // corresponding output.
291         // We need the output to extract the address.
292         BITCOIN_ASSERT(output_index < tx.outputs.size());
293         const auto& out_script = tx.outputs[output_index].output_script;
294         payment_address addr;
295         BITCOIN_ASSERT(input_index < info->inputs.size());
296         if (extract(addr, out_script))
297             info->inputs[input_index] = addr.encoded();
298         else
299             info->inputs[input_index] = "Unknown";
300     }
301
302     void load_input_chain_tx(const std::error_code& ec,
303         const message::transaction& tx, size_t output_index,
304         payment_entry_ptr entry, info_unit_ptr info, size_t input_index)
305     {
306         if (stop_on_error(ec))
307             return;
308         load_input_tx(tx, output_index, info, input_index);
309         // If all the inputs are loaded, then we have finished loading
310         // the info for this input-output entry pair
311         if (inputs_all_loaded(info->inputs))
312         {
313             if (info->is_input)
314                 entry->loaded_input = info;
315             else
316                 entry->loaded_output = info;
317         }
318         finish_if_done();
319     }
320
321     io_service::strand strand_;
322
323     blockchain_ptr chain_;
324     transaction_pool_ptr txpool_;
325     bool stopped_;
326
327     statement_entry statement_;
328     payment_address address_;
329     finish_handler handle_finish_;
330 };
331
332 typedef std::shared_ptr<query_history> query_history_ptr;
333
334 void write_xputs_strings(std::stringstream& ss, const char* xput_name,
335     const info_unit::string_list& xputs)
336 {
337     ss << '"' << xput_name << "\": [";
338     for (auto it = xputs.begin(); it != xputs.end(); ++it)
339     {
340         if (it != xputs.begin())
341             ss << ",";
342         ss << '"' << *it << '"';
343     }
344     ss << "]";
345 }
346
347 void write_info(std::string& json, info_unit_ptr info)
348 {
349     std::stringstream ss;
350     ss << "{"
351         << "\"tx_hash\": \"" << pretty_hex(info->tx_hash) << "\","
352         << "\"block_hash\": \"" << pretty_hex(info->block_hash) << "\","
353         << "\"index\": " << info->index << ","
354         // x for received, and -x for sent amounts
355         << "\"value\": " << (info->is_input ? "-" : "") << info->value << ","
356         << "\"height\": " << info->height << ","
357         << "\"timestamp\": " << info->timestamp << ","
358         << "\"is_input\": " << info->is_input << ",";
359     write_xputs_strings(ss, "inputs", info->inputs);
360     ss << ",";
361     write_xputs_strings(ss, "outputs", info->outputs);
362     if (!info->raw_output_script.empty())
363         ss << ","
364             << "\"raw_output_script\": \""
365             << pretty_hex(info->raw_output_script) << "\"";
366     ss << "}";
367     json += ss.str();
368 }
369
370 void keep_query_alive_proxy(const std::error_code& ec,
371     const statement_entry& statement,
372     python::object handle_finish, query_history_ptr history)
373 {
374     std::string json = "[";
375     for (auto it = statement.begin(); it != statement.end(); ++it)
376     {
377         if (it != statement.begin())
378             json += ",";
379         auto entry = *it;
380         BITCOIN_ASSERT(entry->loaded_output);
381         write_info(json, entry->loaded_output);
382         if (entry->input_exists)
383         {
384             BITCOIN_ASSERT(entry->loaded_input);
385             json += ",";
386             write_info(json, entry->loaded_input);
387         }
388     }
389     json += "]";
390     pyfunction<const std::error_code&, const std::string&> f(handle_finish);
391     f(ec, json);
392 }
393
394 void payment_history(async_service_ptr service, blockchain_ptr chain,
395     transaction_pool_ptr txpool, memory_buffer_ptr membuf,
396     const std::string& address, python::object handle_finish)
397 {
398     query_history_ptr history =
399         std::make_shared<query_history>(*service, chain, txpool, membuf);
400     history->start(address,
401         std::bind(keep_query_alive_proxy, ph::_1, ph::_2,
402             handle_finish, history));
403 }
404
405 BOOST_PYTHON_MODULE(_history)
406 {
407     using namespace boost::python;
408     def("payment_history", payment_history);
409 }
410