317 lines
9.2 KiB
C++
317 lines
9.2 KiB
C++
/*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
#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<int, list<string>> userWallets;
|
|
list<string> 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<string> 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<string> walletAddresses, list<string> 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<json::value> 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<string> inputAddrList;
|
|
list<string> 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));
|
|
}
|
|
}
|