You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

316 lines
9.2 KiB

/*
* 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));
}
}