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;
    });
}
Exemple #2
0
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();
}