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