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.
353 lines
10 KiB
353 lines
10 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/eth.h" |
|
|
|
WalletHandlerETH::WalletHandlerETH(int userId) : WalletHandler(userId) |
|
{ |
|
} |
|
|
|
WalletHandlerETH::~WalletHandlerETH() |
|
{ |
|
|
|
} |
|
|
|
void WalletHandlerETH::analyzeUserWallets() |
|
{ |
|
linfo << "Analyzing user " << m_userId << " ETH 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_ETH))) |
|
{ |
|
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 WalletHandlerETH::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_ETH, |
|
wallets_balances.balance = walletBalance.toBoostMpf() |
|
)); |
|
} |
|
|
|
void WalletHandlerETH::addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses) |
|
{ |
|
linfo << "Analyzing wallet " << walletId << "."; |
|
|
|
for (const auto& a: walletAddresses) |
|
{ |
|
AddressHandlerETH addr(a,walletId); |
|
addr.setWalletAddresses(walletAddresses); |
|
addr.setUserAddresses(userAddresses); |
|
addr.addAddressDataToDB(); |
|
} |
|
} |
|
|
|
AddressHandlerETH::AddressHandlerETH(string address, int walletId) |
|
{ |
|
m_address = address; |
|
m_walletId = walletId; |
|
} |
|
|
|
AddressHandlerETH::~AddressHandlerETH() |
|
{ |
|
|
|
} |
|
|
|
string AddressHandlerETH::getCacheFilename() |
|
{ |
|
string cacheFilename("data/cache/eth/" + m_address); |
|
return cacheFilename; |
|
} |
|
|
|
void AddressHandlerETH::getAddressData() |
|
{ |
|
linfo << "Getting data for address: " << m_address << "."; |
|
|
|
if (!bfs::exists(getCacheFilename())) |
|
{ |
|
getBlockchainAddressData(); |
|
} |
|
} |
|
|
|
void AddressHandlerETH::addAddressDataToDB() |
|
{ |
|
linfo << "Analyzing data for address: " << m_address << "."; |
|
|
|
getAddressData(); |
|
addCachedAddressDataToDB(); |
|
} |
|
|
|
void AddressHandlerETH::getBlockchainAddressData() |
|
{ |
|
try |
|
{ |
|
string sRequestURL = "/api?module=account&action=balance&address="; |
|
sRequestURL += m_address; |
|
http_client apiclient("https://api.etherscan.io"); |
|
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 AddressHandlerETH::addCachedAddressDataToDB() |
|
{ |
|
ifstream f; |
|
f.open(getCacheFilename()); |
|
json::value jvalue = json::value::parse(f); |
|
f.close(); |
|
|
|
addAddressDataJSONToDB(jvalue); |
|
} |
|
|
|
void AddressHandlerETH::addAddressDataJSONToDB(json::value jvalue) |
|
{ |
|
mysql::connection db(getMysqlConfig()); |
|
const auto wallets_tx = TableWalletsTx{}; |
|
|
|
string myAddr = m_address; |
|
linfo << "Analyzing address: " << myAddr; |
|
|
|
if (!jvalue["message"].as_string().compare("OK") && !jvalue["status"].as_string().compare("1")) |
|
{ |
|
Money walletBalance (jvalue["result"]); |
|
Money divider("1000000000000000000"); |
|
walletBalance /= divider; |
|
|
|
linfo << "Wallet " << m_walletId << " balance is: " << walletBalance; |
|
|
|
mysql::connection db(getMysqlConfig()); |
|
const auto wallets_tx = TableWalletsTx{}; |
|
const auto wallets_balances = TableWalletsBalances{}; |
|
|
|
db(insert_into(wallets_balances).set( |
|
wallets_balances.wallet_id = m_walletId, |
|
wallets_balances.coin_id = CPFM_COIN_ID_ETH, |
|
wallets_balances.balance = walletBalance.toBoostMpf() |
|
)); |
|
} |
|
|
|
/* |
|
//Money balance = jvalue["final_balance"].as_integer();; |
|
|
|
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.tx_hash = txid, |
|
wallets_tx.amount = amount.toBoostMpf(), |
|
wallets_tx.fee = fee.toBoostMpf(), |
|
wallets_tx.timestamp = timestamp |
|
)); |
|
} |
|
*/ |
|
}
|
|
|