/* * Copyright (c) 2018, evilny0 * * This file is part of cpfm. * * cpfm is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * cpm is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with cpfm. If not, see . * */ #include "wallets/bch.h" WalletHandlerBCH::WalletHandlerBCH(int userId) : WalletHandler(userId) { } WalletHandlerBCH::~WalletHandlerBCH() { } void WalletHandlerBCH::analyzeUserWallets() { linfo << "Analyzing user " << m_userId << " BCH wallets."; mysql::connection db(getMysqlConfig()); const auto wallets = TableWallets{}; const auto wallets_addresses = TableWalletsAddresses{}; map> userWallets; list userAddresses; for (const auto &row : db.run(select(wallets.wallet_id, wallets.type_id, wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.user_id == m_userId and wallets.wallet_id == wallets_addresses.wallet_id and wallets.type_id == CPFM_WALLET_TYPE_ID_BCH))) { userWallets[row.wallet_id].push_back(row.address); userAddresses.push_back(row.address); } for (const auto &w : userWallets) { addUserWalletDataToDB(w.first, w.second, userAddresses); analyzeUserWalletData(w.first); } } void WalletHandlerBCH::analyzeUserWalletData(int walletId) { mysql::connection db(getMysqlConfig()); const auto wallets_tx = TableWalletsTx{}; const auto wallets_balances = TableWalletsBalances{}; // Loop is on blockchain tx id, so first, get blockhain tx id list. list blockchainTxs; for (const auto &row : db.run(select(wallets_tx.blockchain_tx_id).from(wallets_tx).where(wallets_tx.wallet_id == walletId).group_by(wallets_tx.blockchain_tx_id).order_by(wallets_tx.timestamp.asc()))) { blockchainTxs.push_back(row.blockchain_tx_id); } ldebug << "------------------------------------------------------------"; Money walletBalance = 0; Money walletInputs = 0; Money walletOutputs = 0; Money walletOutputFees = 0; for (const auto &txId: blockchainTxs) { Money txAmount = 0; Money txFee = 0; for (const auto &row : db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.blockchain_tx_id == txId and wallets_tx.wallet_id == walletId))) { Money amount(row.amount); Money fee(row.fee); if (txFee == 0) txFee = fee; txAmount += amount; } txAmount += txFee; if (txAmount < 0) walletOutputs += txAmount; else walletInputs += txAmount; walletOutputFees += txFee; walletBalance = walletInputs + walletOutputs - walletOutputFees; string sReason = "?"; if (txAmount < 0) { } /* ldebug << "Tx: " << txId << ". Amount: " << txAmount << ". Outputs: " << walletOutputs << ". Inputs: " << walletInputs << ". Fees: " << txFee << ". Balance: " << walletBalance << ". Reason: " << sReason; */ } linfo << "Wallet " << walletId << " balance is: " << walletBalance; db(insert_into(wallets_balances).set(wallets_balances.wallet_id = walletId, wallets_balances.coin_id = CPFM_COIN_ID_BCH, wallets_balances.balance = walletBalance.toBoostMpf())); } void WalletHandlerBCH::addUserWalletDataToDB(int walletId, list walletAddresses, list userAddresses) { linfo << "Analyzing wallet " << walletId << "."; for (const auto &a : walletAddresses) { AddressHandlerBCH addr(a, walletId); addr.setWalletAddresses(walletAddresses); addr.setUserAddresses(userAddresses); addr.addAddressDataToDB(); } } AddressHandlerBCH::AddressHandlerBCH(string address, int walletId) { m_address = address; m_walletId = walletId; } AddressHandlerBCH::~AddressHandlerBCH() { } string AddressHandlerBCH::getCacheFilename() { string cacheFilename("data/cache/bch/" + m_address); return cacheFilename; } void AddressHandlerBCH::getAddressData() { linfo << "Getting data for address: " << m_address << "."; if (!bfs::exists(getCacheFilename())) { getBlockchainAddressData(); } } void AddressHandlerBCH::addAddressDataToDB() { linfo << "Analyzing data for address: " << m_address << "."; getAddressData(); addCachedAddressDataToDB(); } void AddressHandlerBCH::getBlockchainAddressData() { try { string sRequestURL = "/insight-api/txs/?address="; sRequestURL += m_address; http_client apiclient("https://cashexplorer.bitcoin.com"); apiclient.request(methods::GET, sRequestURL).then([](http_response response) { if (response.status_code() == status_codes::OK) { return response.extract_json(); } return pplx::task_from_result(json::value()); }) .then([this](pplx::task previousTask) { ofstream f; f.open(getCacheFilename()); f << previousTask.get(); f.close(); }) .wait(); } catch (const http::http_exception &e) { lerr << "Failed to query cashexplorer.bitcoin.com about " << m_address; } } void AddressHandlerBCH::addCachedAddressDataToDB() { ifstream f; f.open(getCacheFilename()); json::value jvalue = json::value::parse(f); f.close(); addAddressDataJSONToDB(jvalue); } void AddressHandlerBCH::addAddressDataJSONToDB(json::value jvalue) { mysql::connection db(getMysqlConfig()); const auto wallets_tx = TableWalletsTx{}; string myAddr = m_address; linfo << "Analyzing address: " << myAddr; for (int i = 0; i < jvalue["txs"].size(); i++) { string txid = jvalue["txs"][i]["txid"].as_string(); int timestamp = jvalue["txs"][i]["time"].as_integer(); Money txTotalOutputAmount; Money txTotalInputAmount; Money txMyOutputAmount; Money txMyInputAmount; bool bToMyAddr = false; bool bFromMyAddr = false; list inputAddrList; list outputAddrList; for (int j = 0; j < jvalue["txs"][i]["vin"].size(); j++) { string inputAddr = jvalue["txs"][i]["vin"][j]["addr"].as_string(); Money amount(jvalue["txs"][i]["vin"][j]["value"]); txTotalInputAmount += amount; if (inputAddr == myAddr) { bFromMyAddr = true; txMyInputAmount += amount; } else { inputAddrList.push_back(inputAddr); } } for (int j = 0; j < jvalue["txs"][i]["vout"].size(); j++) { string outputAddr = jvalue["txs"][i]["vout"][j]["scriptPubKey"]["addresses"][0].as_string(); Money amount(jvalue["txs"][i]["vout"][j]["value"]); txTotalOutputAmount += amount; if (outputAddr == myAddr) { bToMyAddr = true; txMyOutputAmount += amount; } else { outputAddrList.push_back(outputAddr); } } Money amount; Money fee; if (bFromMyAddr) { amount = -txMyInputAmount; fee = txTotalInputAmount - txTotalOutputAmount; for (auto const &addr : inputAddrList) { bool bFoundInUserAddresses = false; for (auto const &userAddr : m_userAddresses) { if (!userAddr.compare(addr)) { bFoundInUserAddresses = true; break; } } if (!bFoundInUserAddresses) { lwarn << "User configuration is missing this address: " << addr; } } for (auto const &addr : outputAddrList) { bool bFoundInUserAddresses = false; for (auto const &userAddr : m_userAddresses) { if (!userAddr.compare(addr)) { bFoundInUserAddresses = true; break; } } if (!bFoundInUserAddresses) { linfo << "Potential other address to analyze: " << L_Cyan << addr; } } } if (bToMyAddr) { amount = txMyOutputAmount; fee = 0; } db(insert_into(wallets_tx).set(wallets_tx.wallet_id = m_walletId, wallets_tx.blockchain_tx_id = txid, wallets_tx.amount = amount.toBoostMpf(), wallets_tx.fee = fee.toBoostMpf(), wallets_tx.timestamp = timestamp)); } }