1 #include <system_error>
2 #include <boost/logic/tribool.hpp>
4 #include <bitcoin/bitcoin.hpp>
5 using namespace libbitcoin;
7 #include <boost/python.hpp>
8 namespace python = boost::python;
10 #include "memory_buffer.hpp"
12 namespace ph = std::placeholders;
16 typedef std::vector<std::string> string_list;
18 // block_hash == null_hash if mempool tx
19 hash_digest tx_hash, block_hash;
20 string_list inputs, outputs;
26 // set to empty if unused
27 data_chunk raw_output_script;
29 // Convenient storage used by pool txs
30 message::output_point previous_output;
33 typedef std::shared_ptr<info_unit> info_unit_ptr;
34 typedef std::vector<info_unit_ptr> info_unit_list;
36 bool inputs_all_loaded(info_unit::string_list inputs)
38 for (const auto& input: inputs)
50 if (input_exists && !loaded_input)
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;
64 typedef std::shared_ptr<payment_entry> payment_entry_ptr;
66 typedef std::vector<payment_entry_ptr> statement_entry;
68 typedef std::function<
69 void (const std::error_code&, const statement_entry&,
70 const info_unit_list&)> finish_handler;
73 : public std::enable_shared_from_this<query_history>
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)
83 void start(const std::string& address, finish_handler handle_finish)
85 if (!address_.set_encoded(address))
87 handle_finish(make_error_code(std::errc::address_not_available),
88 statement_entry(), info_unit_list());
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)));
101 BITCOIN_ASSERT(stopped_ == false);
105 bool stop_on_error(const std::error_code& ec)
109 handle_finish_(ec, statement_entry(), info_unit_list());
115 void check_membuf(const std::error_code& ec,
116 const message::output_point_list& outpoints)
118 if (stop_on_error(ec))
120 membuf_->check(outpoints, address_,
121 strand_.wrap(std::bind(&query_history::start_loading,
122 shared_from_this(), ph::_1, ph::_2, outpoints)));
125 void start_loading(const std::error_code& ec,
126 const memory_buffer::check_result& membuf_result,
127 const message::output_point_list& outpoints)
129 if (stop_on_error(ec))
131 else if (outpoints.empty() && membuf_result.empty())
133 handle_finish_(std::error_code(),
134 statement_entry(), info_unit_list());
138 for (auto outpoint: outpoints)
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);
148 for (const auto& item: membuf_result)
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)));
162 void load_spend(const std::error_code& ec,
163 const message::input_point inpoint, payment_entry_ptr entry)
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)
172 if (ec == error::unspent_output)
174 // This particular entry.output_point
175 // has not been spent yet
176 entry->input_exists = false;
182 entry->input_exists = true;
183 entry->inpoint = inpoint;
184 load_tx_info(inpoint, entry, true);
188 bool find_set_value_from_prevout(info_unit_ptr info)
190 const hash_digest& prev_hash = info->previous_output.hash;
191 for (auto entry: statement_)
193 BITCOIN_ASSERT(entry->loaded_output);
194 if (entry->loaded_output->tx_hash == prev_hash)
196 info->value = entry->loaded_output->value;
199 else if (entry->input_exists &&
200 entry->loaded_input->tx_hash == prev_hash)
202 info->value = entry->loaded_output->value;
206 for (auto other_info: membuf_result_)
207 if (other_info->tx_hash == prev_hash)
209 info->value = other_info->value;
215 void finish_if_done()
217 for (auto entry: statement_)
218 if (!entry->is_loaded())
221 for (auto entry: statement_)
223 if (entry->input_exists)
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();
235 // Unspent outputs have a raw_output_script field
238 for (auto info: membuf_result_)
240 // Lookup prevout to set the value field
243 bool set_prevout_value = find_set_value_from_prevout(info);
244 BITCOIN_ASSERT(set_prevout_value);
247 handle_finish_(std::error_code(), statement_, membuf_result_);
251 template <typename Point>
252 void load_tx_info(const Point& point, payment_entry_ptr entry,
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)));
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)
270 if (stop_on_error(ec))
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)));
279 void block_header(const std::error_code& ec,
280 const message::block blk_head,
281 payment_entry_ptr entry, info_unit_ptr info)
283 if (stop_on_error(ec))
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)));
293 void load_tx(const message::transaction& tx, info_unit_ptr info)
295 // List of output addresses
296 for (const auto& tx_out: tx.outputs)
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());
303 info->outputs.push_back("Unknown");
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.
315 BITCOIN_ASSERT(info->index < info->inputs.size());
316 info->inputs[info->index] = address_.encoded();
320 void load_chain_tx(const std::error_code& ec,
321 const message::transaction& tx,
322 payment_entry_ptr entry, info_unit_ptr info)
324 if (stop_on_error(ec))
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);
335 // If all the inputs are loaded
336 if (inputs_all_loaded(info->inputs))
338 // We are the sole input
339 BITCOIN_ASSERT(info->is_input);
340 entry->loaded_input = info;
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)
352 if (info->is_input && info->index == input_index)
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)));
362 void load_pool_tx(const std::error_code& ec,
363 const message::transaction& tx, info_unit_ptr info)
365 if (stop_on_error(ec))
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)
376 BITCOIN_ASSERT(info->index < tx.inputs.size());
377 info->previous_output = tx.inputs[info->index].previous_output;
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);
385 // If all the inputs are loaded
386 if (inputs_all_loaded(info->inputs))
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
393 info->block_hash = null_hash;
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)
405 if (info->is_input && info->index == input_index)
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)));
415 void load_input_tx(const message::transaction& tx, size_t output_index,
416 info_unit_ptr info, size_t input_index)
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();
428 info->inputs[input_index] = "Unknown";
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)
435 if (stop_on_error(ec))
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))
443 entry->loaded_input = info;
445 entry->loaded_output = info;
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)
454 if (stop_on_error(ec))
456 load_input_tx(tx, output_index, info, input_index);
457 if (inputs_all_loaded(info->inputs))
459 // No more inputs left to load
460 // This info has finished loading
462 info->block_hash = null_hash;
467 io_service::strand strand_;
469 blockchain_ptr chain_;
470 transaction_pool_ptr txpool_;
471 memory_buffer_ptr membuf_;
474 statement_entry statement_;
475 info_unit_list membuf_result_;
476 payment_address address_;
477 finish_handler handle_finish_;
480 typedef std::shared_ptr<query_history> query_history_ptr;
482 void write_xputs_strings(std::stringstream& ss, const char* xput_name,
483 const info_unit::string_list& xputs)
485 ss << '"' << xput_name << "\": [";
486 for (auto it = xputs.begin(); it != xputs.end(); ++it)
488 if (it != xputs.begin())
490 ss << '"' << *it << '"';
495 void write_info(std::string& json, info_unit_ptr info)
497 std::stringstream 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)
507 ss << "\"block_hash\": \"mempool\","
508 << "\"height\": null,";
512 ss << "\"block_hash\": \"" << pretty_hex(info->block_hash) << "\","
513 << "\"height\": " << info->height << ",";
515 write_xputs_strings(ss, "inputs", info->inputs);
517 write_xputs_strings(ss, "outputs", info->outputs);
518 if (!info->raw_output_script.empty())
520 << "\"raw_output_script\": \""
521 << pretty_hex(info->raw_output_script) << "\"";
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)
530 std::string json = "[";
531 for (auto it = statement.begin(); it != statement.end(); ++it)
533 if (it != statement.begin())
536 BITCOIN_ASSERT(entry->loaded_output);
537 write_info(json, entry->loaded_output);
538 if (entry->input_exists)
540 BITCOIN_ASSERT(entry->loaded_input);
542 write_info(json, entry->loaded_input);
545 // A bit of super glue
546 if (!statement.empty() && !membuf_result.empty())
548 for (auto it = membuf_result.begin(); it != membuf_result.end(); ++it)
550 if (it != membuf_result.begin())
552 write_info(json, *it);
555 pyfunction<const std::error_code&, const std::string&> f(handle_finish);
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)
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));
570 BOOST_PYTHON_MODULE(_history)
572 using namespace boost::python;
573 def("payment_history", payment_history);