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;
30 typedef std::shared_ptr<info_unit> info_unit_ptr;
32 bool inputs_all_loaded(info_unit::string_list inputs)
34 for (const auto& input: inputs)
46 if (input_exists && !loaded_input)
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;
60 typedef std::shared_ptr<payment_entry> payment_entry_ptr;
62 typedef std::vector<payment_entry_ptr> statement_entry;
64 typedef std::function<
65 void (const std::error_code&, const statement_entry&)> finish_handler;
68 : public std::enable_shared_from_this<query_history>
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)
78 void start(const std::string& address, finish_handler handle_finish)
80 if (!address_.set_encoded(address))
82 handle_finish(make_error_code(std::errc::address_not_available),
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)));
96 BITCOIN_ASSERT(stopped_ == false);
100 bool stop_on_error(const std::error_code& ec)
104 handle_finish_(ec, statement_entry());
110 void start_loading(const std::error_code& ec,
111 const message::output_point_list& outpoints)
113 if (stop_on_error(ec))
115 else if (outpoints.empty())
117 handle_finish_(std::error_code(), statement_entry());
120 for (auto outpoint: outpoints)
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);
132 void load_spend(const std::error_code& ec,
133 const message::input_point inpoint, payment_entry_ptr entry)
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)
142 if (ec == error::unspent_output)
144 // This particular entry.output_point
145 // has not been spent yet
146 entry->input_exists = false;
152 entry->input_exists = true;
153 entry->inpoint = inpoint;
154 load_tx_info(inpoint, entry, true);
158 void finish_if_done()
160 for (auto entry: statement_)
161 if (!entry->is_loaded())
164 for (auto entry: statement_)
166 if (entry->input_exists)
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();
178 // Unspent outputs have a raw_output_script field
181 handle_finish_(std::error_code(), statement_);
185 template <typename Point>
186 void load_tx_info(const Point& point, payment_entry_ptr entry,
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)));
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)
204 if (stop_on_error(ec))
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)));
213 void block_header(const std::error_code& ec,
214 const message::block blk_head,
215 payment_entry_ptr entry, info_unit_ptr info)
217 if (stop_on_error(ec))
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)));
227 void load_tx(const message::transaction& tx, info_unit_ptr info)
229 // List of output addresses
230 for (const auto& tx_out: tx.outputs)
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());
237 info->outputs.push_back("Unknown");
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.
249 BITCOIN_ASSERT(info->index < info->inputs.size());
250 info->inputs[info->index] = address_.encoded();
254 void load_chain_tx(const std::error_code& ec,
255 const message::transaction& tx,
256 payment_entry_ptr entry, info_unit_ptr info)
258 if (stop_on_error(ec))
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);
269 // If all the inputs are loaded
270 if (inputs_all_loaded(info->inputs))
272 // We are the sole input
273 BITCOIN_ASSERT(info->is_input);
274 entry->loaded_input = info;
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)
286 if (info->is_input && info->index == input_index)
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)));
296 void load_input_tx(const message::transaction& tx, size_t output_index,
297 info_unit_ptr info, size_t input_index)
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();
309 info->inputs[input_index] = "Unknown";
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)
316 if (stop_on_error(ec))
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))
324 entry->loaded_input = info;
326 entry->loaded_output = info;
331 io_service::strand strand_;
333 blockchain_ptr chain_;
334 transaction_pool_ptr txpool_;
337 statement_entry statement_;
338 payment_address address_;
339 finish_handler handle_finish_;
342 typedef std::shared_ptr<query_history> query_history_ptr;
344 void write_xputs_strings(std::stringstream& ss, const char* xput_name,
345 const info_unit::string_list& xputs)
347 ss << '"' << xput_name << "\": [";
348 for (auto it = xputs.begin(); it != xputs.end(); ++it)
350 if (it != xputs.begin())
352 ss << '"' << *it << '"';
357 void write_info(std::string& json, info_unit_ptr info)
359 std::stringstream 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);
371 write_xputs_strings(ss, "outputs", info->outputs);
372 if (!info->raw_output_script.empty())
374 << "\"raw_output_script\": \""
375 << pretty_hex(info->raw_output_script) << "\"";
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)
384 std::string json = "[";
385 for (auto it = statement.begin(); it != statement.end(); ++it)
387 if (it != statement.begin())
390 BITCOIN_ASSERT(entry->loaded_output);
391 write_info(json, entry->loaded_output);
392 if (entry->input_exists)
394 BITCOIN_ASSERT(entry->loaded_input);
396 write_info(json, entry->loaded_input);
400 pyfunction<const std::error_code&, const std::string&> f(handle_finish);
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)
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));
415 BOOST_PYTHON_MODULE(_history)
417 using namespace boost::python;
418 def("payment_history", payment_history);