Status HttpRequest::get(HttpReply &result, const std::string &url) { if (!status_) return status_; // Final options: ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_WRITEDATA, &result.body)); ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_WRITEFUNCTION, curlDataCallback)); ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_URL, url.c_str())); if (headers_) ABC_CHECK_CURL(curl_easy_setopt(handle_, CURLOPT_HTTPHEADER, headers_)); // Make the request: ABC_CHECK_CURL(curl_easy_perform(handle_)); ABC_CHECK_CURL(curl_easy_getinfo(handle_, CURLINFO_RESPONSE_CODE, &result.code)); if (result.codeOk()) ABC_DebugLog("%s (%d)", url.c_str(), result.code); else ABC_DebugLog("%s (%d)\n%s", url.c_str(), result.code, result.body.c_str()); return Status(); }
tABC_CC ABC_BridgePrioritizeAddress(Wallet &self, const char *szAddress, tABC_Error *pError) { tABC_CC cc = ABC_CC_Ok; bc::payment_address addr; Watcher *watcher = nullptr; ABC_CHECK_NEW(watcherFind(watcher, self)); if (szAddress) { if (!addr.set_encoded(szAddress)) { cc = ABC_CC_Error; ABC_DebugLog("Invalid szAddress %s\n", szAddress); goto exit; } } watcher->prioritize_address(addr); exit: return cc; }
tABC_CC ABC_BridgeWatcherConnect(Wallet &self, tABC_Error *pError) { tABC_CC cc = ABC_CC_Ok; tABC_GeneralInfo *ppInfo = NULL; const char *szServer = FALLBACK_OBELISK; Watcher *watcher = nullptr; ABC_CHECK_NEW(watcherFind(watcher, self)); // Pick a server: if (isTestnet()) { szServer = TESTNET_OBELISK; } else if (ABC_CC_Ok == ABC_GeneralGetInfo(&ppInfo, pError) && 0 < ppInfo->countObeliskServers) { ++gLastObelisk; if (ppInfo->countObeliskServers <= gLastObelisk) gLastObelisk = 0; szServer = ppInfo->aszObeliskServers[gLastObelisk]; } // Connect: ABC_DebugLog("Wallet %s connecting to %s", self.id().c_str(), szServer); watcher->connect(szServer); exit: ABC_GeneralFreeInfo(ppInfo); return cc; }
static void bridgeQuietCallback(WatcherInfo *watcherInfo, tABC_BitCoin_Event_Callback fAsyncCallback, void *pData) { // If we are sweeping any keys, do that now: for (auto &sweep: watcherInfo->sweeping) { auto s = bridgeDoSweep(watcherInfo->wallet, sweep, fAsyncCallback, pData).log(); if (!s) { ABC_DebugLog("IncomingSweep callback: wallet %s, status: %d", watcherInfo->wallet.id().c_str(), s.value()); tABC_AsyncBitCoinInfo info; info.pData = pData; info.eventType = ABC_AsyncEventType_IncomingSweep; s.toError(info.status, ABC_HERE()); info.szWalletUUID = watcherInfo->wallet.id().c_str(); info.szTxID = nullptr; info.sweepSatoshi = 0; fAsyncCallback(&info); sweep.done = true; } } // Remove completed ones: watcherInfo->sweeping.remove_if([](const PendingSweep& sweep) { return sweep.done; }); }
Status broadcastTx(Wallet &self, DataSlice rawTx) { // Create communication resources: auto syncer = std::make_shared<Syncer>(); auto s1 = std::make_shared<DelayedStatus>(); auto s2 = std::make_shared<DelayedStatus>(); auto s3 = std::make_shared<DelayedStatus>(); // Launch the broadcasts: DataChunk tx(rawTx.begin(), rawTx.end()); if (!self.bOverrideBitcoinServers) { std::thread(broadcastTask<blockchainPostTx>, syncer, s1, tx).detach(); std::thread(broadcastTask<insightPostTx>, syncer, s2, tx).detach(); } // Queue up an async broadcast over the TxUpdater: auto updaterDone = [syncer, s3](Status s) { { std::lock_guard<std::mutex> lock(syncer->mutex); s3->status = s; s3->done = true; if (s) ABC_DebugLog("Stratum broadcast OK"); else s.log(); } syncer->cv.notify_all(); }; watcherSend(self, updaterDone, rawTx).log(); // Loop as long as any thread is still running: while (true) { // Wait for the condition variable, which also acquires the lock: std::unique_lock<std::mutex> lock(syncer->mutex); syncer->cv.wait(lock); // Stop waiting if any broadcast has succeeded: if (s1->done && s1->status) break; if (s2->done && s2->status) break; if (s3->done && s3->status) break; // If they are all done, we have an error: if (s1->done && s2->done && s3->done) return s1->status; } return Status(); }
Status syncRepo(const std::string &syncDir, const std::string &syncKey, bool &dirty) { AutoSyncLock lock(gSyncMutex); AutoFree<git_repository, git_repository_free> repo; ABC_CHECK_GIT(git_repository_open(&repo.get(), syncDir.c_str())); std::string url; ABC_CHECK(syncUrl(url, syncKey)); for (int i = 0; i < syncServers.size(); i++) { ABC_CHECK(syncUrl(url, syncKey, true)); if (sync_fetch(repo, url.c_str()) >= 0) { ABC_DebugLog("Syncing to: %s", url.c_str()); break; } else { ABC_DebugLog("FAIlED Syncing to: %s", url.c_str()); } } int files_changed, need_push; ABC_CHECK_GIT(sync_master(repo, &files_changed, &need_push)); if (need_push) ABC_CHECK_GIT(sync_push(repo, url.c_str())); // If this fails, the app has been shut down, leaving us for dead. // We will crash anyhow, but this at least makes it official: assert(gContext); dirty = !!files_changed; return Status(); }
static int curlDebugCallback(CURL *handle, curl_infotype type, char *data, size_t size, void *userp) { std::string payload(data, size); switch (type) { case CURLINFO_HEADER_OUT: ABC_DebugLog("cURL header out: %s", payload.c_str()); return 0; case CURLINFO_DATA_OUT: ABC_DebugLog("cURL data out: %s", payload.c_str()); return 0; case CURLINFO_HEADER_IN: ABC_DebugLog("cURL header in: %s", payload.c_str()); return 0; case CURLINFO_DATA_IN: ABC_DebugLog("cURL data in: %s", payload.c_str()); return 0; default: return 0; } }
tABC_CC ABC_BridgeWatchAddr(Wallet &self, const char *pubAddress, tABC_Error *pError) { tABC_CC cc = ABC_CC_Ok; ABC_DebugLog("Watching %s for %s", pubAddress, self.id().c_str()); bc::payment_address addr; WatcherInfo *watcherInfo = nullptr; ABC_CHECK_NEW(watcherFind(watcherInfo, self)); if (!addr.set_encoded(pubAddress)) { cc = ABC_CC_Error; ABC_DebugLog("Invalid pubAddress %s\n", pubAddress); goto exit; } watcherInfo->addresses.insert(pubAddress); watcherInfo->watcher.watch_address(addr); exit: return cc; }
void Status::toError(tABC_Error &error) { std::stringstream s; s << *this; ABC_DebugLog("%s", s.str().c_str()); error.code = value_; strncpy(error.szDescription, message_.c_str(), ABC_MAX_STRING_LENGTH); strncpy(error.szSourceFunc, function_, ABC_MAX_STRING_LENGTH); strncpy(error.szSourceFile, file_, ABC_MAX_STRING_LENGTH); error.nSourceLine = line_; error.szDescription[ABC_MAX_STRING_LENGTH] = 0; error.szSourceFunc[ABC_MAX_STRING_LENGTH] = 0; error.szSourceFile[ABC_MAX_STRING_LENGTH] = 0; }
Status AddressDb::load() { std::lock_guard<std::mutex> lock(mutex_); addresses_.clear(); files_.clear(); // Open the directory: DIR *dir = opendir(dir_.c_str()); if (dir) { struct dirent *de; while (nullptr != (de = readdir(dir))) { if (!fileIsJson(de->d_name)) continue; // Try to load the address: Address address; AddressJson json; if (json.load(dir_ + de->d_name, wallet_.dataKey()).log() && json.unpack(address).log()) { if (path(address) != dir_ + de->d_name) ABC_DebugLog("Filename %s does not match address", de->d_name); addresses_[address.address] = address; files_[address.address] = json; } } closedir(dir); } ABC_CHECK(stockpile()); return Status(); }
static void ABC_BridgeTxCallback(WatcherInfo *watcherInfo, const libbitcoin::transaction_type& tx, tABC_BitCoin_Event_Callback fAsyncBitCoinEventCallback, void *pData) { tABC_CC cc = ABC_CC_Ok; tABC_Error error; int64_t fees = 0; int64_t totalInSatoshi = 0, totalOutSatoshi = 0, totalMeSatoshi = 0, totalMeInSatoshi = 0; tABC_TxOutput **iarr = NULL, **oarr = NULL; unsigned int idx = 0, iCount = 0, oCount = 0; std::string txId, malTxId; if (watcherInfo == NULL) { cc = ABC_CC_Error; goto exit; } txId = ABC_BridgeNonMalleableTxId(tx); malTxId = bc::encode_hash(bc::hash_transaction(tx)); idx = 0; iCount = tx.inputs.size(); iarr = (tABC_TxOutput **) malloc(sizeof(tABC_TxOutput *) * iCount); for (auto i : tx.inputs) { bc::payment_address addr; bc::extract(addr, i.script); auto prev = i.previous_output; // Create output tABC_TxOutput *out = (tABC_TxOutput *) malloc(sizeof(tABC_TxOutput)); out->input = true; out->szTxId = stringCopy(bc::encode_hash(prev.hash)); out->szAddress = stringCopy(addr.encoded()); // Check prevouts for values auto tx = watcherInfo->watcher.find_tx(prev.hash); if (prev.index < tx.outputs.size()) { out->value = tx.outputs[prev.index].value; totalInSatoshi += tx.outputs[prev.index].value; auto row = watcherInfo->addresses.find(addr.encoded()); if (row != watcherInfo->addresses.end()) totalMeInSatoshi += tx.outputs[prev.index].value; } iarr[idx] = out; idx++; } idx = 0; oCount = tx.outputs.size(); oarr = (tABC_TxOutput **) malloc(sizeof(tABC_TxOutput *) * oCount); for (auto o : tx.outputs) { bc::payment_address addr; bc::extract(addr, o.script); // Create output tABC_TxOutput *out = (tABC_TxOutput *) malloc(sizeof(tABC_TxOutput)); out->input = false; out->value = o.value; out->szAddress = stringCopy(addr.encoded()); out->szTxId = stringCopy(malTxId); // Do we own this address? auto row = watcherInfo->addresses.find(addr.encoded()); if (row != watcherInfo->addresses.end()) { totalMeSatoshi += o.value; } totalOutSatoshi += o.value; oarr[idx] = out; idx++; } if (totalMeSatoshi == 0 && totalMeInSatoshi == 0) { ABC_DebugLog("values == 0, this tx does not concern me.\n"); goto exit; } fees = totalInSatoshi - totalOutSatoshi; totalMeSatoshi -= totalMeInSatoshi; ABC_DebugLog("calling ABC_TxReceiveTransaction"); ABC_DebugLog("Total Me: %s, Total In: %s, Total Out: %s, Fees: %s", std::to_string(totalMeSatoshi).c_str(), std::to_string(totalInSatoshi).c_str(), std::to_string(totalOutSatoshi).c_str(), std::to_string(fees).c_str()); ABC_CHECK_RET( ABC_TxReceiveTransaction( watcherInfo->wallet, totalMeSatoshi, fees, iarr, iCount, oarr, oCount, txId.c_str(), malTxId.c_str(), fAsyncBitCoinEventCallback, pData, &error)); watcherSave(watcherInfo->wallet); // Failure is not fatal exit: ABC_FREE(oarr); ABC_FREE(iarr); }
Status onReceive(Wallet &wallet, const TxInfo &info, tABC_BitCoin_Event_Callback fCallback, void *pData) { wallet.balanceDirty(); ABC_CHECK(wallet.addresses.markOutputs(info)); // Does the transaction already exist? TxMeta meta; if (!wallet.txs.get(meta, info.ntxid)) { const auto balance = wallet.addresses.balance(info); meta.ntxid = info.ntxid; meta.txid = info.txid; meta.timeCreation = time(nullptr); meta.internal = false; meta.airbitzFeeWanted = 0; meta.airbitzFeeSent = 0; // Receives can accumulate Airbitz fees: const auto airbitzFeeInfo = generalAirbitzFeeInfo(); meta.airbitzFeeWanted = airbitzFeeIncoming(airbitzFeeInfo, balance); logInfo("Airbitz fee: " + std::to_string(meta.airbitzFeeWanted) + " wanted, " + std::to_string(wallet.txs.airbitzFeePending()) + " pending"); // Grab metadata from the address: for (const auto &io: info.ios) { AddressMeta address; if (wallet.addresses.get(address, io.address)) meta.metadata = address.metadata; } ABC_CHECK(gContext->exchangeCache.satoshiToCurrency( meta.metadata.amountCurrency, balance, static_cast<Currency>(wallet.currency()))); // Save the metadata: ABC_CHECK(wallet.txs.save(meta, balance, info.fee)); // Update the GUI: ABC_DebugLog("IncomingBitCoin callback: wallet %s, txid: %s", wallet.id().c_str(), info.txid.c_str()); tABC_AsyncBitCoinInfo async; async.pData = pData; async.eventType = ABC_AsyncEventType_IncomingBitCoin; Status().toError(async.status, ABC_HERE()); async.szWalletUUID = wallet.id().c_str(); async.szTxID = info.txid.c_str(); async.sweepSatoshi = 0; fCallback(&async); } else { // Update the GUI: ABC_DebugLog("BalanceUpdate callback: wallet %s, txid: %s", wallet.id().c_str(), info.txid.c_str()); tABC_AsyncBitCoinInfo async; async.pData = pData; async.eventType = ABC_AsyncEventType_BalanceUpdate; Status().toError(async.status, ABC_HERE()); async.szWalletUUID = wallet.id().c_str(); async.szTxID = info.txid.c_str(); async.sweepSatoshi = 0; fCallback(&async); } return Status(); }
static Status bridgeTxCallback(Wallet &wallet, const libbitcoin::transaction_type &tx, tABC_BitCoin_Event_Callback fAsyncCallback, void *pData) { const auto addresses = wallet.addresses.list(); const auto info = wallet.txCache.txInfo(tx, addresses); // Does this transaction concern us? if (wallet.txCache.isRelevant(tx, addresses)) { // Does the transaction already exist? Tx meta; if (!wallet.txs.get(meta, info.ntxid)) { meta.ntxid = info.ntxid; meta.txid = info.txid; meta.timeCreation = time(nullptr); meta.internal = false; // Grab metadata from the address: TxMetadata metadata; for (const auto &io: info.ios) { Address address; if (wallet.addresses.get(address, io.address)) meta.metadata = address.metadata; } meta.metadata.amountSatoshi = info.balance; meta.metadata.amountFeesMinersSatoshi = info.fee; ABC_CHECK(gContext->exchangeCache.satoshiToCurrency( meta.metadata.amountCurrency, info.balance, static_cast<Currency>(wallet.currency()))); // Save the metadata: ABC_CHECK(wallet.txs.save(meta)); // Update the transaction cache: watcherSave(wallet).log(); // Failure is not fatal wallet.balanceDirty(); ABC_CHECK(wallet.addresses.markOutputs(info.ios)); // Update the GUI: ABC_DebugLog("IncomingBitCoin callback: wallet %s, txid: %s", wallet.id().c_str(), info.txid.c_str()); tABC_AsyncBitCoinInfo async; async.pData = pData; async.eventType = ABC_AsyncEventType_IncomingBitCoin; Status().toError(async.status, ABC_HERE()); async.szWalletUUID = wallet.id().c_str(); async.szTxID = info.txid.c_str(); async.sweepSatoshi = 0; fAsyncCallback(&async); } else { // Update the transaction cache: watcherSave(wallet).log(); // Failure is not fatal wallet.balanceDirty(); ABC_CHECK(wallet.addresses.markOutputs(info.ios)); // Update the GUI: ABC_DebugLog("BalanceUpdate callback: wallet %s, txid: %s", wallet.id().c_str(), info.txid.c_str()); tABC_AsyncBitCoinInfo async; async.pData = pData; async.eventType = ABC_AsyncEventType_BalanceUpdate; Status().toError(async.status, ABC_HERE()); async.szWalletUUID = wallet.id().c_str(); async.szTxID = info.txid.c_str(); async.sweepSatoshi = 0; fAsyncCallback(&async); } } else { ABC_DebugLog("New (irrelevant) transaction: wallet %s, txid: %s", wallet.id().c_str(), info.txid.c_str()); } return Status(); }
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(); }
Status bridgeWatcherLoop(Wallet &self, tABC_BitCoin_Event_Callback fCallback, void *pData) { WatcherInfo *watcherInfo = nullptr; ABC_CHECK(watcherFind(watcherInfo, self)); // Set up the address-changed callback: auto wakeupCallback = [watcherInfo]() { watcherInfo->watcher.sendWakeup(); }; self.addressCache.wakeupCallbackSet(wakeupCallback); // Set up the address-synced callback: auto doneCallback = [watcherInfo, fCallback, pData]() { ABC_DebugLog("AddressCheckDone callback: wallet %s", watcherInfo->wallet.id().c_str()); tABC_AsyncBitCoinInfo info; info.pData = pData; info.eventType = ABC_AsyncEventType_AddressCheckDone; Status().toError(info.status, ABC_HERE()); info.szWalletUUID = watcherInfo->wallet.id().c_str(); info.szTxID = nullptr; info.sweepSatoshi = 0; fCallback(&info); }; self.addressCache.doneCallbackSet(doneCallback); // Set up new-transaction callback: auto txCallback = [watcherInfo, fCallback, pData] (const libbitcoin::transaction_type &tx) { bridgeTxCallback(watcherInfo->wallet, tx, fCallback, pData).log(); }; watcherInfo->watcher.set_tx_callback(txCallback); // Set up new-block callback: auto heightCallback = [watcherInfo, fCallback, pData](const size_t height) { // Update the GUI: ABC_DebugLog("BlockHeightChange callback: wallet %s", watcherInfo->wallet.id().c_str()); tABC_AsyncBitCoinInfo info; info.pData = pData; info.eventType = ABC_AsyncEventType_BlockHeightChange; Status().toError(info.status, ABC_HERE()); info.szWalletUUID = watcherInfo->wallet.id().c_str(); info.szTxID = nullptr; info.sweepSatoshi = 0; fCallback(&info); watcherSave(watcherInfo->wallet).log(); // Failure is not fatal }; watcherInfo->watcher.set_height_callback(heightCallback); // Set up sweep-trigger callback: auto onQuiet = [watcherInfo, fCallback, pData]() { bridgeQuietCallback(watcherInfo, fCallback, pData); }; watcherInfo->watcher.set_quiet_callback(onQuiet); // Do the loop: watcherInfo->watcher.loop(); // Cancel all callbacks: watcherInfo->watcher.set_quiet_callback(nullptr); watcherInfo->watcher.set_height_callback(nullptr); watcherInfo->watcher.set_tx_callback(nullptr); self.addressCache.wakeupCallbackSet(nullptr); self.addressCache.doneCallbackSet(nullptr); return Status(); }
/** * Generate the QR code for a previously created receive request. * * @param szRequestID ID of this request * @param pszURI Pointer to string to store URI(optional) * @param paData Pointer to store array of data bytes (0x0 white, 0x1 black) * @param pWidth Pointer to store width of image (image will be square) * @param pError A pointer to the location to store the error if there is one */ tABC_CC ABC_TxGenerateRequestQRCode(Wallet &self, const char *szRequestID, char **pszURI, unsigned char **paData, unsigned int *pWidth, tABC_Error *pError) { tABC_CC cc = ABC_CC_Ok; AutoCoreLock lock(gCoreMutex); QRcode *qr = NULL; unsigned char *aData = NULL; unsigned int length = 0; char *szURI = NULL; // load the request/address Address address; ABC_CHECK_NEW(self.addresses.get(address, szRequestID)); // Get the URL string for this info tABC_BitcoinURIInfo infoURI; memset(&infoURI, 0, sizeof(tABC_BitcoinURIInfo)); infoURI.amountSatoshi = address.metadata.amountSatoshi; infoURI.szAddress = address.address.c_str(); // Set the label if there is one ABC_CHECK_RET(ABC_TxBuildFromLabel(self, &(infoURI.szLabel), pError)); // if there is a note if (!address.metadata.notes.empty()) { infoURI.szMessage = address.metadata.notes.c_str(); } ABC_CHECK_RET(ABC_BridgeEncodeBitcoinURI(&szURI, &infoURI, pError)); // encode our string ABC_DebugLog("Encoding: %s", szURI); qr = QRcode_encodeString(szURI, 0, QR_ECLEVEL_L, QR_MODE_8, 1); ABC_CHECK_ASSERT(qr != NULL, ABC_CC_Error, "Unable to create QR code"); length = qr->width * qr->width; ABC_ARRAY_NEW(aData, length, unsigned char); for (unsigned i = 0; i < length; i++) { aData[i] = qr->data[i] & 0x1; } *pWidth = qr->width; *paData = aData; aData = NULL; if (pszURI != NULL) { *pszURI = stringCopy(szURI); } exit: ABC_FREE_STR(szURI); QRcode_free(qr); ABC_CLEAR_FREE(aData, length); return cc; }