size_t satoshi_raw_size(const block& packet) { size_t block_size = 80 + variable_uint_size(packet.transactions.size()); for (const message::transaction& tx: packet.transactions) block_size += satoshi_raw_size(tx); return block_size; }
bool leveldb_common::save_transaction(leveldb_transaction_batch& batch, uint32_t block_height, uint32_t tx_index, const hash_digest& tx_hash, const transaction_type& block_tx) { if (duplicate_exists(tx_hash, block_height, tx_index)) return true; data_chunk tx_data(8 + satoshi_raw_size(block_tx)); // Serialize tx. auto serial = make_serializer(tx_data.begin()); serial.write_4_bytes(block_height); serial.write_4_bytes(tx_index); // Actual tx data. auto end_iter = satoshi_save(block_tx, serial.iterator()); BITCOIN_ASSERT( std::distance(tx_data.begin(), end_iter) == 8 + satoshi_raw_size(block_tx)); // Save tx to leveldb batch.tx.Put(slice(tx_hash), slice(tx_data)); // Add inputs to spends database. // Coinbase inputs do not spend anything. if (!is_coinbase(block_tx)) for (uint32_t input_index = 0; input_index < block_tx.inputs.size(); ++input_index) { const transaction_input_type& input = block_tx.inputs[input_index]; const input_point inpoint{tx_hash, input_index}; if (!mark_spent_outputs(batch.spend, input.previous_output, inpoint)) return false; if (!add_debit(batch.debit, input, {tx_hash, input_index}, block_height)) return false; } // Save address -> output mappings. for (uint32_t output_index = 0; output_index < block_tx.outputs.size(); ++output_index) { const transaction_output_type& output = block_tx.outputs[output_index]; if (!add_credit(batch.credit, output, {tx_hash, output_index}, block_height)) return false; } return true; }
hash_digest hash_transaction_impl(const message::transaction& tx, uint32_t* hash_type_code) { data_chunk serialized_tx(satoshi_raw_size(tx)); satoshi_save(tx, serialized_tx.begin()); if (hash_type_code != nullptr) extend_data(serialized_tx, uncast_type(*hash_type_code)); return generate_sha256_hash(serialized_tx); }
hash_digest hash_transaction_impl(const transaction_type& tx, uint32_t* hash_type_code) { data_chunk serialized_tx(satoshi_raw_size(tx)); satoshi_save(tx, serialized_tx.begin()); if (hash_type_code != nullptr) extend_data(serialized_tx, to_little_endian(*hash_type_code)); return bitcoin_hash(serialized_tx); }
data_chunk create_raw_message(const Message& packet) { data_chunk payload(satoshi_raw_size(packet)); satoshi_save(packet, payload.begin()); // Make the header packet and serialise it message::header head; head.magic = magic_value; head.command = satoshi_command(packet); head.payload_length = payload.size(); head.checksum = generate_sha256_checksum(payload); data_chunk raw_header(satoshi_raw_size(head)); satoshi_save(head, raw_header.begin()); // Construct completed packet with header + payload data_chunk whole_message = raw_header; extend_data(whole_message, payload); // Probably not the right place for this // Networking output in an exporter log_info(log_domain::network) << "s: " << head.command << " (" << payload.size() << " bytes)"; return whole_message; }
data_chunk create_raw_message(const Message& packet) { data_chunk payload(satoshi_raw_size(packet)); satoshi_save(packet, payload.begin()); // Make the header packet and serialise it header_type head; head.magic = magic_value(); head.command = satoshi_command(packet); head.payload_length = static_cast<uint32_t>(payload.size()); head.checksum = bitcoin_checksum(payload); data_chunk raw_header(satoshi_raw_size(head)); satoshi_save(head, raw_header.begin()); // Construct completed packet with header + payload data_chunk whole_message = raw_header; extend_data(whole_message, payload); // Probably not the right place for this // Networking output in an exporter log_debug(LOG_NETWORK) << "s: " << head.command << " (" << payload.size() << " bytes)"; return whole_message; }
Status watcherBridgeRawTx(Wallet &self, const char *szTxID, DataChunk &result) { Watcher *watcher = nullptr; ABC_CHECK(watcherFind(watcher, self)); bc::hash_digest txid; if (!bc::decode_hash(txid, szTxID)) return ABC_ERROR(ABC_CC_ParseError, "Bad txid"); auto tx = watcher->find_tx(txid); result.resize(satoshi_raw_size(tx)); bc::satoshi_save(tx, result.begin()); return Status(); }
bool leveldb_common::get_transaction(leveldb_tx_info& tx_info, const hash_digest& tx_hash, bool read_parent, bool read_tx) { // First we try to read the bytes from the database. std::string value; leveldb::Status status = db_.tx->Get( leveldb::ReadOptions(), slice(tx_hash), &value); if (status.IsNotFound()) return false; else if (!status.ok()) { log_fatal(LOG_BLOCKCHAIN) << "get_transaction(" << tx_hash << "): " << status.ToString(); return false; } // Read the parent block height and our index in that block (if neccessary). BITCOIN_ASSERT(value.size() > 8); if (read_parent) { auto deserial = make_deserializer(value.begin(), value.begin() + 8); tx_info.height = deserial.read_4_bytes(); tx_info.index = deserial.read_4_bytes(); } if (!read_tx) return true; // Read the actual transaction (if neccessary). try { BITCOIN_ASSERT(value.size() > 8); satoshi_load(value.begin() + 8, value.end(), tx_info.tx); } catch (end_of_stream) { return false; } BITCOIN_ASSERT(satoshi_raw_size(tx_info.tx) + 8 == value.size()); BITCOIN_ASSERT(hash_transaction(tx_info.tx) == tx_hash); return true; }
static tABC_CC ABC_BridgeDoSweep(WatcherInfo *watcherInfo, PendingSweep& sweep, tABC_Error *pError) { tABC_CC cc = ABC_CC_Ok; char *szID = NULL; char *szAddress = NULL; uint64_t funds = 0; abcd::unsigned_transaction utx; bc::transaction_output_type output; abcd::key_table keys; std::string malTxId, txId; // Find utxos for this address: auto utxos = watcherInfo->watcher.get_utxos(sweep.address); // Bail out if there are no funds to sweep: if (!utxos.size()) { // Tell the GUI if there were funds in the past: if (watcherInfo->watcher.db().has_history(sweep.address)) { if (sweep.fCallback) { sweep.fCallback(ABC_CC_Ok, NULL, 0); } else { if (watcherInfo->fAsyncCallback) { tABC_AsyncBitCoinInfo info; info.eventType = ABC_AsyncEventType_IncomingSweep; info.sweepSatoshi = 0; info.szTxID = NULL; watcherInfo->fAsyncCallback(&info); } } sweep.done = true; } return ABC_CC_Ok; } // There are some utxos, so send them to ourselves: tABC_TxDetails details; memset(&details, 0, sizeof(tABC_TxDetails)); details.amountSatoshi = 0; details.amountCurrency = 0; details.amountFeesAirbitzSatoshi = 0; details.amountFeesMinersSatoshi = 0; details.szName = const_cast<char*>(""); details.szCategory = const_cast<char*>(""); details.szNotes = const_cast<char*>(""); details.attributes = 0x2; // Create a new receive request: ABC_CHECK_RET(ABC_TxCreateReceiveRequest(watcherInfo->wallet, &details, &szID, false, pError)); ABC_CHECK_RET(ABC_TxGetRequestAddress(watcherInfo->wallet, szID, &szAddress, pError)); // Build a transaction: utx.tx.version = 1; utx.tx.locktime = 0; for (auto &utxo : utxos) { bc::transaction_input_type input; input.sequence = 0xffffffff; input.previous_output = utxo.point; funds += utxo.value; utx.tx.inputs.push_back(input); } if (10000 < funds) funds -= 10000; // Ugh, hard-coded mining fee ABC_CHECK_ASSERT(!outputIsDust(funds), ABC_CC_InsufficientFunds, "Not enough funds"); output.value = funds; ABC_CHECK_NEW(outputScriptForAddress(output.script, szAddress)); utx.tx.outputs.push_back(output); // Now sign that: keys[sweep.address] = sweep.key; ABC_CHECK_SYS(abcd::gather_challenges(utx, watcherInfo->watcher), "gather_challenges"); ABC_CHECK_SYS(abcd::sign_tx(utx, keys), "sign_tx"); // Send: { bc::data_chunk raw_tx(satoshi_raw_size(utx.tx)); bc::satoshi_save(utx.tx, raw_tx.begin()); ABC_CHECK_NEW(broadcastTx(raw_tx)); } // Save the transaction in the database: malTxId = bc::encode_hash(bc::hash_transaction(utx.tx)); txId = ABC_BridgeNonMalleableTxId(utx.tx); ABC_CHECK_RET(ABC_TxSweepSaveTransaction(watcherInfo->wallet, txId.c_str(), malTxId.c_str(), funds, &details, pError)); // Done: if (sweep.fCallback) { sweep.fCallback(ABC_CC_Ok, txId.c_str(), output.value); } else { if (watcherInfo->fAsyncCallback) { tABC_AsyncBitCoinInfo info; info.eventType = ABC_AsyncEventType_IncomingSweep; info.sweepSatoshi = output.value; info.szTxID = stringCopy(txId); watcherInfo->fAsyncCallback(&info); ABC_FREE_STR(info.szTxID); } } sweep.done = true; watcherInfo->watcher.send_tx(utx.tx); exit: ABC_FREE_STR(szID); ABC_FREE_STR(szAddress); return cc; }
bool leveldb_common::save_transaction(leveldb_transaction_batch& batch, uint32_t block_height, uint32_t tx_index, const hash_digest& tx_hash, const transaction_type& block_tx) { if (is_special_duplicate(block_height, tx_index)) return true; data_chunk tx_data(8 + satoshi_raw_size(block_tx)); // Serialize tx. auto serial = make_serializer(tx_data.begin()); serial.write_4_bytes(block_height); serial.write_4_bytes(tx_index); // Actual tx data. auto end_iter = satoshi_save(block_tx, serial.iterator()); BITCOIN_ASSERT( tx_data.begin() + 8 + satoshi_raw_size(block_tx) == end_iter); // Save tx to leveldb batch.tx.Put(slice(tx_hash), slice(tx_data)); // Add inputs to spends database. // Coinbase inputs do not spend anything. if (!is_coinbase(block_tx)) for (uint32_t input_index = 0; input_index < block_tx.inputs.size(); ++input_index) { const transaction_input_type& input = block_tx.inputs[input_index]; const input_point inpoint{tx_hash, input_index}; if (!mark_spent_outputs(batch.spend, input.previous_output, inpoint)) return false; if (!add_debit(batch.debit, input, {tx_hash, input_index}, block_height)) return false; } // A stack of size 1. Keep the stealth_data from // one iteration to the next. data_chunk stealth_data_store; auto unload_stealth_store = [&]() { return std::move(stealth_data_store); }; // Save address -> output mappings. for (uint32_t output_index = 0; output_index < block_tx.outputs.size(); ++output_index) { const transaction_output_type& output = block_tx.outputs[output_index]; // If a stealth output then skip processing. if (process_stealth_output_info(output, stealth_data_store)) continue; data_chunk stealth_data = unload_stealth_store(); // Try to extract an address. payment_address address; if (!extract(address, output.script)) continue; // Process this output. if (!stealth_data.empty()) add_stealth_info(stealth_data, address, tx_hash, *db_stealth_); if (!add_credit(batch.credit, address, output.value, {tx_hash, output_index}, block_height)) return false; } return true; }
static Status bridgeDoSweep(Wallet &wallet, PendingSweep &sweep, tABC_BitCoin_Event_Callback fAsyncCallback, void *pData) { // Find utxos for this address: AddressSet addresses; addresses.insert(sweep.address); auto utxos = wallet.txCache.get_utxos(addresses); // Bail out if there are no funds to sweep: if (!utxos.size()) { // Tell the GUI if there were funds in the past: if (wallet.txCache.has_history(sweep.address)) { ABC_DebugLog("IncomingSweep callback: wallet %s, value: 0", wallet.id().c_str()); tABC_AsyncBitCoinInfo info; info.pData = pData; info.eventType = ABC_AsyncEventType_IncomingSweep; Status().toError(info.status, ABC_HERE()); info.szWalletUUID = wallet.id().c_str(); info.szTxID = nullptr; info.sweepSatoshi = 0; fAsyncCallback(&info); sweep.done = true; } return Status(); } // Build a transaction: bc::transaction_type tx; tx.version = 1; tx.locktime = 0; // Set up the output: Address address; wallet.addresses.getNew(address); bc::transaction_output_type output; ABC_CHECK(outputScriptForAddress(output.script, address.address)); tx.outputs.push_back(output); // Set up the inputs: uint64_t fee, funds; ABC_CHECK(inputsPickMaximum(fee, funds, tx, utxos)); if (outputIsDust(funds)) return ABC_ERROR(ABC_CC_InsufficientFunds, "Not enough funds"); tx.outputs[0].value = funds; // Now sign that: KeyTable keys; keys[sweep.address] = sweep.key; ABC_CHECK(signTx(tx, wallet.txCache, keys)); // Send: bc::data_chunk raw_tx(satoshi_raw_size(tx)); bc::satoshi_save(tx, raw_tx.begin()); ABC_CHECK(broadcastTx(wallet, raw_tx)); // Calculate transaction information: const auto info = wallet.txCache.txInfo(tx, wallet.addresses.list()); // Save the transaction metadata: Tx meta; meta.ntxid = info.ntxid; meta.txid = info.txid; meta.timeCreation = time(nullptr); meta.internal = true; meta.metadata.amountSatoshi = funds; meta.metadata.amountFeesAirbitzSatoshi = 0; ABC_CHECK(gContext->exchangeCache.satoshiToCurrency( meta.metadata.amountCurrency, info.balance, static_cast<Currency>(wallet.currency()))); ABC_CHECK(wallet.txs.save(meta)); // Update the transaction cache: if (wallet.txCache.insert(tx)) watcherSave(wallet).log(); // Failure is not fatal wallet.balanceDirty(); ABC_CHECK(wallet.addresses.markOutputs(info.ios)); // Done: ABC_DebugLog("IncomingSweep callback: wallet %s, txid: %s, value: %d", wallet.id().c_str(), info.txid.c_str(), output.value); tABC_AsyncBitCoinInfo async; async.pData = pData; async.eventType = ABC_AsyncEventType_IncomingSweep; Status().toError(async.status, ABC_HERE()); async.szWalletUUID = wallet.id().c_str(); async.szTxID = info.txid.c_str(); async.sweepSatoshi = output.value; fAsyncCallback(&async); sweep.done = true; return Status(); }