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 "/home/genjix/python-bitcoin/src/primitive.h"
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,
72 blockchain_ptr chain, transaction_pool_ptr txpool)
73 : strand_(service.get_service()), chain_(chain),
74 txpool_(txpool), stopped_(false)
78 void start(const std::string& address, finish_handler handle_finish)
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)));
91 BITCOIN_ASSERT(stopped_ == false);
95 bool stop_on_error(const std::error_code& ec)
99 handle_finish_(ec, statement_entry());
105 void start_loading(const std::error_code& ec,
106 const message::output_point_list& outpoints)
108 if (stop_on_error(ec))
110 for (auto outpoint: outpoints)
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);
122 void load_spend(const std::error_code& ec,
123 const message::input_point inpoint, payment_entry_ptr entry)
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)
132 if (ec == error::unspent_output)
134 // This particular entry.output_point
135 // has not been spent yet
136 entry->input_exists = false;
142 entry->input_exists = true;
143 entry->inpoint = inpoint;
144 load_tx_info(inpoint, entry, true);
148 void finish_if_done()
150 for (auto entry: statement_)
151 if (!entry->is_loaded())
154 for (auto entry: statement_)
156 if (entry->input_exists)
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();
168 // Unspent outputs have a raw_output_script field
171 handle_finish_(std::error_code(), statement_);
175 template <typename Point>
176 void load_tx_info(const Point& point, payment_entry_ptr entry,
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)));
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)
194 if (stop_on_error(ec))
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)));
203 void block_header(const std::error_code& ec,
204 const message::block blk_head,
205 payment_entry_ptr entry, info_unit_ptr info)
207 if (stop_on_error(ec))
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)));
217 void load_tx(const message::transaction& tx, info_unit_ptr info)
219 // List of output addresses
220 for (const auto& tx_out: tx.outputs)
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());
227 info->outputs.push_back("Unknown");
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.
239 BITCOIN_ASSERT(info->index < info->inputs.size());
240 info->inputs[info->index] = address_.encoded();
244 void load_chain_tx(const std::error_code& ec,
245 const message::transaction& tx,
246 payment_entry_ptr entry, info_unit_ptr info)
248 if (stop_on_error(ec))
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);
259 // If all the inputs are loaded
260 if (inputs_all_loaded(info->inputs))
262 // We are the sole input
263 BITCOIN_ASSERT(info->is_input);
264 entry->loaded_input = info;
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)
276 if (info->is_input && info->index == input_index)
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)));
286 void load_input_tx(const message::transaction& tx, size_t output_index,
287 info_unit_ptr info, size_t input_index)
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();
299 info->inputs[input_index] = "Unknown";
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)
306 if (stop_on_error(ec))
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))
314 entry->loaded_input = info;
316 entry->loaded_output = info;
321 io_service::strand strand_;
323 blockchain_ptr chain_;
324 transaction_pool_ptr txpool_;
327 statement_entry statement_;
328 payment_address address_;
329 finish_handler handle_finish_;
332 typedef std::shared_ptr<query_history> query_history_ptr;
334 void write_xputs_strings(std::stringstream& ss, const char* xput_name,
335 const info_unit::string_list& xputs)
337 ss << '"' << xput_name << "\": [";
338 for (auto it = xputs.begin(); it != xputs.end(); ++it)
340 if (it != xputs.begin())
342 ss << '"' << *it << '"';
347 void write_info(std::string& json, info_unit_ptr info)
349 std::stringstream ss;
351 << "\"tx_hash\": \"" << pretty_hex(info->tx_hash) << "\","
352 << "\"block_hash\": \"" << pretty_hex(info->block_hash) << "\","
353 << "\"index\": " << info->index << ","
354 << "\"value\": " << info->value << ","
355 << "\"height\": " << info->height << ","
356 << "\"timestamp\": " << info->timestamp << ","
357 << "\"is_input\": " << info->is_input << ",";
358 write_xputs_strings(ss, "inputs", info->inputs);
360 write_xputs_strings(ss, "outputs", info->outputs);
361 if (!info->raw_output_script.empty())
363 << "\"raw_output_script\": \""
364 << pretty_hex(info->raw_output_script) << "\"";
369 void keep_query_alive_proxy(const std::error_code& ec,
370 const statement_entry& statement,
371 python::object handle_finish, query_history_ptr history)
373 std::string json = "[";
374 for (auto it = statement.begin(); it != statement.end(); ++it)
376 if (it != statement.begin())
379 BITCOIN_ASSERT(entry->loaded_output);
380 write_info(json, entry->loaded_output);
383 pyfunction<const std::error_code&, const std::string&> f(handle_finish);
387 void payment_history(async_service_ptr service, blockchain_ptr chain,
388 transaction_pool_ptr txpool, const std::string& address,
389 python::object handle_finish)
391 query_history_ptr history =
392 std::make_shared<query_history>(*service, chain, txpool);
393 history->start(address,
394 std::bind(keep_query_alive_proxy, ph::_1, ph::_2,
395 handle_finish, history));
398 BOOST_PYTHON_MODULE(_history)
400 using namespace boost::python;
401 def("payment_history", payment_history);