333 lines
9.5 KiB
C++
333 lines
9.5 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/btc.h"
|
|
|
|
WalletHandlerBTC::WalletHandlerBTC(int userId) : WalletHandler(userId)
|
|
{
|
|
}
|
|
|
|
WalletHandlerBTC::~WalletHandlerBTC()
|
|
{
|
|
|
|
}
|
|
|
|
void WalletHandlerBTC::analyzeUserWallets()
|
|
{
|
|
linfo << "Analyzing user " << m_userId << " BTC 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_BTC)))
|
|
{
|
|
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 WalletHandlerBTC::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 blockchain tx 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_BTC,
|
|
wallets_balances.balance = walletBalance.toBoostMpf()
|
|
));
|
|
}
|
|
|
|
void WalletHandlerBTC::addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses)
|
|
{
|
|
linfo << "Analyzing wallet " << walletId << ".";
|
|
|
|
for (const auto& a: walletAddresses)
|
|
{
|
|
AddressHandlerBTC addr(a,walletId);
|
|
addr.setWalletAddresses(walletAddresses);
|
|
addr.setUserAddresses(userAddresses);
|
|
addr.addAddressDataToDB();
|
|
}
|
|
}
|
|
|
|
AddressHandlerBTC::AddressHandlerBTC(string address, int walletId)
|
|
{
|
|
m_address = address;
|
|
m_walletId = walletId;
|
|
}
|
|
|
|
AddressHandlerBTC::~AddressHandlerBTC()
|
|
{
|
|
|
|
}
|
|
|
|
string AddressHandlerBTC::getCacheFilename()
|
|
{
|
|
string cacheFilename("data/cache/btc/" + m_address);
|
|
return cacheFilename;
|
|
}
|
|
|
|
void AddressHandlerBTC::getAddressData()
|
|
{
|
|
linfo << "Getting data for address: " << m_address << ".";
|
|
|
|
if (!bfs::exists(getCacheFilename()))
|
|
{
|
|
getBlockchainAddressData();
|
|
}
|
|
}
|
|
|
|
void AddressHandlerBTC::addAddressDataToDB()
|
|
{
|
|
linfo << "Analyzing data for address: " << m_address << ".";
|
|
|
|
getAddressData();
|
|
addCachedAddressDataToDB();
|
|
}
|
|
|
|
void AddressHandlerBTC::getBlockchainAddressData()
|
|
{
|
|
try
|
|
{
|
|
string sRequestURL = "/fr/rawaddr/";
|
|
sRequestURL += m_address;
|
|
http_client apiclient("https://blockchain.info/");
|
|
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 blockchain.info about " << m_address;
|
|
}
|
|
}
|
|
|
|
void AddressHandlerBTC::addCachedAddressDataToDB()
|
|
{
|
|
ifstream f;
|
|
f.open(getCacheFilename());
|
|
json::value jvalue = json::value::parse(f);
|
|
f.close();
|
|
|
|
addAddressDataJSONToDB(jvalue);
|
|
}
|
|
|
|
void AddressHandlerBTC::addAddressDataJSONToDB(json::value jvalue)
|
|
{
|
|
mysql::connection db(getMysqlConfig());
|
|
const auto wallets_tx = TableWalletsTx{};
|
|
|
|
string myAddr = jvalue["address"].as_string();
|
|
linfo << "Analyzing address: " << myAddr;
|
|
|
|
Money balance = jvalue["final_balance"].as_integer();;
|
|
|
|
for (int i=0;i<jvalue["txs"].size();i++)
|
|
{
|
|
string hash = jvalue["txs"][i]["hash"].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]["inputs"].size();j++)
|
|
{
|
|
string inputAddr = jvalue["txs"][i]["inputs"][j]["prev_out"]["addr"].as_string();
|
|
__int64 amount = jvalue["txs"][i]["inputs"][j]["prev_out"]["value"].as_integer();
|
|
txTotalInputAmount += amount;
|
|
|
|
if (inputAddr == myAddr)
|
|
{
|
|
bFromMyAddr = true;
|
|
txMyInputAmount += amount;
|
|
}
|
|
else
|
|
{
|
|
inputAddrList.push_back(inputAddr);
|
|
}
|
|
}
|
|
|
|
for (int j=0;j<jvalue["txs"][i]["out"].size();j++)
|
|
{
|
|
string outputAddr = jvalue["txs"][i]["out"][j]["addr"].as_string();
|
|
__int64 amount = jvalue["txs"][i]["out"][j]["value"].as_integer();
|
|
txTotalOutputAmount += amount;
|
|
|
|
if (outputAddr == myAddr)
|
|
{
|
|
bToMyAddr = true;
|
|
txMyOutputAmount += amount;
|
|
}
|
|
else
|
|
{
|
|
outputAddrList.push_back(outputAddr);
|
|
}
|
|
}
|
|
|
|
Money amount;
|
|
Money fee;
|
|
if (bFromMyAddr)
|
|
{
|
|
amount = -txMyInputAmount/100000000;
|
|
fee = txTotalInputAmount/100000000 - txTotalOutputAmount/100000000;
|
|
|
|
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/100000000;
|
|
fee = 0;
|
|
}
|
|
|
|
db(insert_into(wallets_tx).set(
|
|
wallets_tx.wallet_id = m_walletId,
|
|
wallets_tx.blockchain_tx_id = hash,
|
|
wallets_tx.amount = amount.toBoostMpf(),
|
|
wallets_tx.fee = fee.toBoostMpf(),
|
|
wallets_tx.timestamp = timestamp
|
|
));
|
|
}
|
|
}
|