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