Browse Source
Added Blockchair support for BTC/BCH. Support for multiple operations for each ETH tx, introducing support for ERC-20. Separated DB data to "raw" and "processed". The goal is to be able to wipe processed data (so we can process again with updated rules) without requiring to analyze again input files. Updated SQL schema to match changes.master
evilny0
3 years ago
39 changed files with 2891 additions and 1296 deletions
@ -1,6 +1,7 @@
|
||||
aux_source_directory(. SOURCE_LIST) |
||||
aux_source_directory(wallets SOURCE_LIST_WALLETS) |
||||
aux_source_directory(exchanges SOURCE_LIST_EXCHANGES) |
||||
aux_source_directory(datasources SOURCE_LIST_DATASOURCES) |
||||
|
||||
add_executable(${EXECUTABLE} ${SOURCE_LIST} ${SOURCE_LIST_WALLETS} ${SOURCE_LIST_EXCHANGES}) |
||||
add_executable(${EXECUTABLE} ${SOURCE_LIST} ${SOURCE_LIST_WALLETS} ${SOURCE_LIST_EXCHANGES} ${SOURCE_LIST_DATASOURCES}) |
||||
target_link_libraries(${EXECUTABLE} ${LIBS}) |
||||
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 "datasources/blockchain_info.h" |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceBTC_BlockchainInfo::getTxDetailsListForAddress(string address) |
||||
{ |
||||
linfo << "Blockchain.info : Analyzing address: " << address; |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> l; |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address))) |
||||
{ |
||||
saveBlockchainAddressDataToCacheFile(address); |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address))) |
||||
{ |
||||
lerr << "Blockchain.info : cache file could not be found. Address data was not retrieved."; |
||||
return l; |
||||
} |
||||
|
||||
ifstream f; |
||||
f.open(getCacheFilenameForAddress(address)); |
||||
json::value jvalue = json::value::parse(f); |
||||
f.close(); |
||||
|
||||
linfo << "Blockchain.info : analyzing address: " << jvalue["address"].as_string(); |
||||
|
||||
for (int i=0;i<jvalue["txs"].size();i++) |
||||
{ |
||||
BlockchainTxDetailsTypeBTC tx; |
||||
|
||||
tx.hash = jvalue["txs"][i]["hash"].as_string(); |
||||
tx.time.setFromUnixTime(jvalue["txs"][i]["time"].as_integer()); |
||||
|
||||
for (int j=0;j<jvalue["txs"][i]["inputs"].size();j++) |
||||
{ |
||||
string addr = jvalue["txs"][i]["inputs"][j]["prev_out"]["addr"].as_string(); |
||||
__int64 amount = jvalue["txs"][i]["inputs"][j]["prev_out"]["value"].as_integer(); |
||||
|
||||
Money m = amount; |
||||
Money mdecimals = m/100000000; |
||||
|
||||
// The same address can be multiple time in the inputs, so we need to sum the amounts.
|
||||
tx.inputs[addr] += mdecimals; |
||||
} |
||||
|
||||
for (int j=0;j<jvalue["txs"][i]["out"].size();j++) |
||||
{ |
||||
string addr = jvalue["txs"][i]["out"][j]["addr"].as_string(); |
||||
__int64 amount = jvalue["txs"][i]["out"][j]["value"].as_integer(); |
||||
|
||||
Money m = amount; |
||||
Money mdecimals = m/100000000; |
||||
|
||||
// The same address can only be once in the outputs.
|
||||
tx.outputs[addr] = mdecimals; |
||||
} |
||||
|
||||
l.push_back(tx); |
||||
} |
||||
|
||||
return l; |
||||
} |
||||
|
||||
string BlockchainDataSourceBTC_BlockchainInfo::getCacheFilenameForAddress(string address) |
||||
{ |
||||
string cacheFilename("data/cache/blockchain.info/btc/" + address); |
||||
return cacheFilename; |
||||
} |
||||
|
||||
void BlockchainDataSourceBTC_BlockchainInfo::saveBlockchainAddressDataToCacheFile(string address) |
||||
{ |
||||
try |
||||
{ |
||||
linfo << "Blockchain.info : querying API about " << address; |
||||
|
||||
string sRequestURL = "/rawaddr/"; |
||||
sRequestURL += address; |
||||
http_client apiclient("https://blockchain.info/"); |
||||
|
||||
m_currentRequestCacheFilename = getCacheFilenameForAddress(address); |
||||
|
||||
apiclient.request(methods::GET,sRequestURL).then([](http_response response) |
||||
{ |
||||
if (response.status_code() == status_codes::OK) |
||||
{ |
||||
ldebug << "Blockchain.info : response OK."; |
||||
return response.extract_json(); |
||||
} |
||||
else if (response.status_code() == status_codes::TooManyRequests) |
||||
{ |
||||
lwarn << "Blockchain.info : too many queries! We are being rate limited."; |
||||
pplx::task_from_result(json::value()); |
||||
} |
||||
return pplx::task_from_result(json::value()); |
||||
}) |
||||
.then([this](pplx::task<json::value> previousTask) |
||||
{ |
||||
if (previousTask.get() != json::value::null()) |
||||
{ |
||||
linfo << "Blockchain.info : saving query result to " << m_currentRequestCacheFilename; |
||||
ofstream f; |
||||
f.open(m_currentRequestCacheFilename); |
||||
f << previousTask.get(); |
||||
f.close(); |
||||
} |
||||
else |
||||
{ |
||||
lerr << "Blockchain.info : query result is empty. Nothing will be saved to the cache file."; |
||||
} |
||||
}) |
||||
.wait(); |
||||
} |
||||
catch(const http::http_exception& e) |
||||
{ |
||||
lerr << "Blockchain.info : failed to query API about " << address; |
||||
} |
||||
} |
@ -0,0 +1,39 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED |
||||
#define CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED |
||||
|
||||
#include "datasources/datasource.h" |
||||
|
||||
|
||||
class BlockchainDataSourceBTC_BlockchainInfo |
||||
{ |
||||
public: |
||||
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddress(string address); |
||||
|
||||
private: |
||||
string getCacheFilenameForAddress(string address); |
||||
void saveBlockchainAddressDataToCacheFile(string address); |
||||
|
||||
string m_currentRequestCacheFilename; |
||||
}; |
||||
|
||||
#endif // CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED
|
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 "datasources/blockchair.h" |
||||
|
||||
|
||||
Time BlockchainDataSourceTypeBTC_Blockchair::getTimeFromString(string s) |
||||
{ |
||||
std::istringstream in{s}; |
||||
date::sys_time<std::chrono::seconds> tp; |
||||
in >> date::parse("%F %T", tp); |
||||
|
||||
Time t; |
||||
t.setFromUnixTime(std::chrono::system_clock::to_time_t(tp)); |
||||
|
||||
return t; |
||||
} |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceTypeBTC_Blockchair::getTxDetailsListForAddresses(list<string> addresses) |
||||
{ |
||||
linfo << "Blockchair : analyzing address list"; |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> l; |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddresses(addresses))) |
||||
{ |
||||
saveBlockchainAddressesDataToCacheFile(addresses); |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddresses(addresses))) |
||||
{ |
||||
lerr << "Blockchair : cache file could not be found. Addresses data was not retrieved."; |
||||
return l; |
||||
} |
||||
|
||||
ifstream f; |
||||
f.open(getCacheFilenameForAddresses(addresses)); |
||||
json::value jvalue = json::value::parse(f); |
||||
f.close(); |
||||
|
||||
for (int i=0;i<jvalue["data"]["transactions"].size();i++) |
||||
{ |
||||
string txHash = jvalue["data"]["transactions"][i].as_string(); |
||||
|
||||
linfo << "Blockchair : found tx " << txHash << ". Starting analysis."; |
||||
|
||||
if (!bfs::exists(getCacheFilenameForTx(txHash))) |
||||
{ |
||||
saveBlockchainTxDataToCacheFile(txHash); |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForTx(txHash))) |
||||
{ |
||||
lerr << "Blockchair : cache file for tx " << txHash << " could not be found. Tx data was not retrieved."; |
||||
} |
||||
|
||||
ifstream fTx; |
||||
fTx.open(getCacheFilenameForTx(txHash)); |
||||
json::value jvalueTx = json::value::parse(fTx); |
||||
fTx.close(); |
||||
|
||||
BlockchainTxDetailsTypeBTC tx; |
||||
|
||||
tx.hash = txHash; |
||||
tx.time = getTimeFromString (jvalueTx["data"][txHash]["transaction"]["time"].as_string()); |
||||
|
||||
for (int j=0;j<jvalueTx["data"][txHash]["inputs"].size();j++) |
||||
{ |
||||
string addr = jvalueTx["data"][txHash]["inputs"][j]["recipient"].as_string(); |
||||
__int64 amount = jvalueTx["data"][txHash]["inputs"][j]["value"].as_integer(); |
||||
|
||||
Money m = amount; |
||||
Money mdecimals = m/100000000; |
||||
|
||||
// The same address can be multiple time in the inputs, so we need to sum the amounts.
|
||||
tx.inputs[addr] += mdecimals; |
||||
} |
||||
|
||||
for (int j=0;j<jvalueTx["data"][txHash]["outputs"].size();j++) |
||||
{ |
||||
string addr = jvalueTx["data"][txHash]["outputs"][j]["recipient"].as_string(); |
||||
__int64 amount = jvalueTx["data"][txHash]["outputs"][j]["value"].as_integer(); |
||||
|
||||
Money m = amount; |
||||
Money mdecimals = m/100000000; |
||||
|
||||
// The same address can only be once in the outputs.
|
||||
tx.outputs[addr] = mdecimals; |
||||
} |
||||
|
||||
l.push_back(tx); |
||||
|
||||
linfo << "Blockchair : finished analysis for tx " << txHash << "."; |
||||
} |
||||
|
||||
linfo << "Blockchair : finished analysis for address list."; |
||||
|
||||
return l; |
||||
} |
||||
|
||||
string BlockchainDataSourceTypeBTC_Blockchair::getCacheFilenameForAddresses(list<string> addresses) |
||||
{ |
||||
string s; |
||||
|
||||
for (const auto a: addresses) |
||||
s = s+a; |
||||
|
||||
string cacheFilename("data/cache/blockchair/"+m_blockchainName+"/addresses/" + s); |
||||
return cacheFilename; |
||||
} |
||||
|
||||
string BlockchainDataSourceTypeBTC_Blockchair::getCacheFilenameForTx(string txHash) |
||||
{ |
||||
string cacheFilename("data/cache/blockchair/"+m_blockchainName+"/tx/" + txHash); |
||||
return cacheFilename; |
||||
} |
||||
|
||||
void BlockchainDataSourceTypeBTC_Blockchair::saveBlockchainAddressesDataToCacheFile(list<string> addresses) |
||||
{
|
||||
try |
||||
{ |
||||
string s; |
||||
|
||||
for (const auto a: addresses) |
||||
{ |
||||
if (s.length()) |
||||
s = s+","+a; |
||||
else |
||||
s = a; |
||||
} |
||||
|
||||
linfo << "Blockchair : querying API about addresses : " << s; |
||||
|
||||
string sRequestURL = "/"+m_blockchainName+"/dashboards/addresses/"; |
||||
sRequestURL += s; |
||||
http_client apiclient("https://api.blockchair.com/"); |
||||
|
||||
m_currentRequestCacheFilename = getCacheFilenameForAddresses(addresses); |
||||
|
||||
ltrace << "Blockchair : query : " << sRequestURL; |
||||
|
||||
apiclient.request(methods::GET,sRequestURL).then([](http_response response) |
||||
{ |
||||
if (response.status_code() == status_codes::OK) |
||||
{ |
||||
ldebug << "Blockchair : response OK."; |
||||
return response.extract_json(); |
||||
} |
||||
else if (response.status_code() == status_codes::TooManyRequests) |
||||
{ |
||||
lwarn << "Blockchair : too many queries! We are being rate limited."; |
||||
pplx::task_from_result(json::value()); |
||||
} |
||||
return pplx::task_from_result(json::value()); |
||||
}) |
||||
.then([this](pplx::task<json::value> previousTask) |
||||
{ |
||||
if (previousTask.get() != json::value::null()) |
||||
{ |
||||
linfo << "Blockchair : saving query result to " << m_currentRequestCacheFilename; |
||||
ofstream f; |
||||
f.open(m_currentRequestCacheFilename); |
||||
f << previousTask.get(); |
||||
f.close(); |
||||
} |
||||
else |
||||
{ |
||||
lerr << "Blockchair : query result is empty. Nothing will be saved to the cache file."; |
||||
} |
||||
}) |
||||
.wait(); |
||||
} |
||||
catch(const http::http_exception& e) |
||||
{ |
||||
lerr << "Blockchair : failed to query API"; |
||||
} |
||||
} |
||||
|
||||
void BlockchainDataSourceTypeBTC_Blockchair::saveBlockchainTxDataToCacheFile(string txHash) |
||||
{ |
||||
try |
||||
{ |
||||
linfo << "Blockchair : querying API about tx : " << txHash; |
||||
|
||||
string sRequestURL = "/"+m_blockchainName+"/dashboards/transaction/"; |
||||
sRequestURL += txHash; |
||||
http_client apiclient("https://api.blockchair.com/"); |
||||
|
||||
m_currentRequestCacheFilename = getCacheFilenameForTx(txHash); |
||||
|
||||
ltrace << "Blockchair : query : " << sRequestURL; |
||||
|
||||
apiclient.request(methods::GET,sRequestURL).then([](http_response response) |
||||
{ |
||||
if (response.status_code() == status_codes::OK) |
||||
{ |
||||
ldebug << "Blockchair : response OK."; |
||||
return response.extract_json(); |
||||
} |
||||
else if (response.status_code() == status_codes::TooManyRequests) |
||||
{ |
||||
lwarn << "Blockchair : too many queries! We are being rate limited."; |
||||
pplx::task_from_result(json::value()); |
||||
} |
||||
return pplx::task_from_result(json::value()); |
||||
}) |
||||
.then([this](pplx::task<json::value> previousTask) |
||||
{ |
||||
if (previousTask.get() != json::value::null()) |
||||
{ |
||||
linfo << "Blockchair : saving query result to " << m_currentRequestCacheFilename; |
||||
ofstream f; |
||||
f.open(m_currentRequestCacheFilename); |
||||
f << previousTask.get(); |
||||
f.close(); |
||||
} |
||||
else |
||||
{ |
||||
lerr << "Blockchair : query result is empty. Nothing will be saved to the cache file."; |
||||
} |
||||
}) |
||||
.wait(); |
||||
} |
||||
catch(const http::http_exception& e) |
||||
{ |
||||
lerr << "Blockchair : failed to query API about " << txHash; |
||||
} |
||||
} |
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED |
||||
#define CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED |
||||
|
||||
#include "datasources/datasource.h" |
||||
|
||||
|
||||
class BlockchainDataSourceTypeBTC_Blockchair |
||||
{ |
||||
public: |
||||
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses); |
||||
|
||||
private: |
||||
string getCacheFilenameForAddresses(list<string> address); |
||||
string getCacheFilenameForTx(string txHash); |
||||
|
||||
void saveBlockchainAddressesDataToCacheFile(list<string> address); |
||||
void saveBlockchainTxDataToCacheFile(string txHash); |
||||
|
||||
Time getTimeFromString(string s); |
||||
|
||||
string m_currentRequestCacheFilename; |
||||
|
||||
protected: |
||||
string m_blockchainName; |
||||
|
||||
virtual void dummyMakeAbstract() = 0; |
||||
}; |
||||
|
||||
class BlockchainDataSourceBTC_Blockchair : public BlockchainDataSourceTypeBTC_Blockchair |
||||
{ |
||||
public: |
||||
BlockchainDataSourceBTC_Blockchair() { m_blockchainName = "bitcoin"; } |
||||
|
||||
protected: |
||||
virtual void dummyMakeAbstract(){}; |
||||
}; |
||||
|
||||
class BlockchainDataSourceBCH_Blockchair : public BlockchainDataSourceTypeBTC_Blockchair |
||||
{ |
||||
public: |
||||
BlockchainDataSourceBCH_Blockchair() { m_blockchainName = "bitcoin-cash"; } |
||||
|
||||
protected: |
||||
virtual void dummyMakeAbstract(){}; |
||||
}; |
||||
|
||||
#endif // CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED
|
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 "datasources/datasource.h" |
||||
#include "datasources/blockchain_info.h" |
||||
#include "datasources/blockchair.h" |
||||
#include "datasources/etherscan.h" |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceBTC::getTxDetailsListForAddresses(list<string> addresses) |
||||
{ |
||||
list<BlockchainTxDetailsTypeBTC> l; |
||||
|
||||
// Blockchain.info
|
||||
/*
|
||||
BlockchainDataSourceBTC_BlockchainInfo bci; |
||||
list<string> hashList; |
||||
|
||||
for (auto const address: addresses) |
||||
{ |
||||
list<BlockchainTxDetailsTypeBTC> listForSingleAddress = bci.getTxDetailsListForAddress(address); |
||||
|
||||
for (auto const tx: listForSingleAddress) |
||||
{ |
||||
if (std::find(hashList.begin(), hashList.end(), tx.hash) == hashList.end()) |
||||
{ |
||||
hashList.push_back(tx.hash); |
||||
l.push_back(tx); |
||||
} |
||||
} |
||||
} |
||||
*/ |
||||
|
||||
// Blockchair
|
||||
BlockchainDataSourceBTC_Blockchair bc; |
||||
l = bc.getTxDetailsListForAddresses(addresses); |
||||
|
||||
return l; |
||||
} |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> BlockchainDataSourceBCH::getTxDetailsListForAddresses(list<string> addresses) |
||||
{ |
||||
list<BlockchainTxDetailsTypeBTC> l; |
||||
|
||||
// Blockchair
|
||||
BlockchainDataSourceBCH_Blockchair bc; |
||||
l = bc.getTxDetailsListForAddresses(addresses); |
||||
|
||||
return l; |
||||
} |
||||
|
||||
list<BlockchainTxDetailsTypeETH> BlockchainDataSourceETH::getTxDetailsListForAddress(string address) |
||||
{ |
||||
list<BlockchainTxDetailsTypeETH> l; |
||||
|
||||
// Etherscan
|
||||
BlockchainDataSourceETH_Etherscan bc; |
||||
l = bc.getTxDetailsListForAddress(address); |
||||
|
||||
return l; |
||||
} |
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_DATASOURCE_H_INCLUDED |
||||
#define CPFM_DATASOURCE_H_INCLUDED |
||||
|
||||
#include "pf.h" |
||||
|
||||
class BlockchainTxDetailsTypeBTC |
||||
{ |
||||
public: |
||||
string hash; |
||||
Time time; |
||||
map <string,Money> inputs; |
||||
map <string,Money> outputs; |
||||
}; |
||||
|
||||
class BlockchainTxOperationTypeETH |
||||
{ |
||||
public: |
||||
string addressFrom; |
||||
string addressTo; |
||||
Money amount; |
||||
int amountCoinId; |
||||
}; |
||||
|
||||
class BlockchainTxDetailsTypeETH |
||||
{ |
||||
public: |
||||
string hash; |
||||
Time time; |
||||
list<BlockchainTxOperationTypeETH> operations; |
||||
Money fee; |
||||
}; |
||||
|
||||
class BlockchainDataSourceBTC |
||||
{ |
||||
public: |
||||
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses); |
||||
}; |
||||
|
||||
class BlockchainDataSourceBCH |
||||
{ |
||||
public: |
||||
list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses); |
||||
}; |
||||
|
||||
class BlockchainDataSourceETH |
||||
{ |
||||
public: |
||||
list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address); |
||||
}; |
||||
|
||||
#endif // CPFM_DATASOURCE_H_INCLUDED
|
@ -0,0 +1,323 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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 "datasources/etherscan.h" |
||||
|
||||
#define TXLIST_NORMAL "txlist" |
||||
#define TXLIST_INTERNAL "txlistinternal" |
||||
#define TXLIST_TOKENS "tokentx" |
||||
|
||||
list<BlockchainTxDetailsTypeETH> BlockchainDataSourceETH_Etherscan::getTxDetailsListForAddress(string address) |
||||
{ |
||||
linfo << "Etherscan : Analyzing address: " << address; |
||||
|
||||
if (!retrieveCacheFilesForAddress(address)) |
||||
{ |
||||
list<BlockchainTxDetailsTypeETH> l; |
||||
lerr << "Etherscan : cache files could not be found. Address data was not retrieved."; |
||||
return l; |
||||
} |
||||
|
||||
list<string> hashListNormal; |
||||
list<string> hashListInternal; |
||||
list<string> hashListToken; |
||||
|
||||
list<BlockchainTxDetailsTypeETH> lNormal = getTxDetailsListForAddressTxType(address,TXLIST_NORMAL,hashListNormal); |
||||
list<BlockchainTxDetailsTypeETH> lInternal = getTxDetailsListForAddressTxType(address,TXLIST_INTERNAL,hashListInternal); |
||||
list<BlockchainTxDetailsTypeETH> lToken = getTxDetailsListForAddressTxType(address,TXLIST_TOKENS,hashListToken); |
||||
|
||||
list<BlockchainTxDetailsTypeETH> l = lNormal; |
||||
|
||||
// Add internals tx to the list
|
||||
for (auto const tx: lInternal) |
||||
{ |
||||
BlockchainTxDetailsTypeETH txNew = tx; |
||||
|
||||
list<BlockchainTxDetailsTypeETH>::iterator it; |
||||
for (it=l.begin();it!=l.end();it++) |
||||
{ |
||||
if (it->hash == tx.hash) |
||||
{ |
||||
txNew = (*it); |
||||
for (const auto op: tx.operations) |
||||
{ |
||||
txNew.operations.push_back(op); |
||||
} |
||||
|
||||
l.erase(it); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
l.push_back(txNew); |
||||
} |
||||
|
||||
// Add tokens tx to the list
|
||||
for (auto const tx: lToken) |
||||
{ |
||||
BlockchainTxDetailsTypeETH txNew = tx; |
||||
|
||||
list<BlockchainTxDetailsTypeETH>::iterator it; |
||||
for (it=l.begin();it!=l.end();it++) |
||||
{ |
||||
if (it->hash == tx.hash) |
||||
{ |
||||
txNew = (*it); |
||||
for (const auto op: tx.operations) |
||||
{ |
||||
txNew.operations.push_back(op); |
||||
} |
||||
|
||||
l.erase(it); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
l.push_back(txNew); |
||||
} |
||||
|
||||
return l; |
||||
} |
||||
|
||||
list<BlockchainTxDetailsTypeETH> BlockchainDataSourceETH_Etherscan::getTxDetailsListForAddressTxType(string address, string type, list<string>& hashList) |
||||
{ |
||||
list<BlockchainTxDetailsTypeETH> l; |
||||
|
||||
ifstream f; |
||||
f.open(getCacheFilenameForAddress(address,type)); |
||||
json::value jvalue = json::value::parse(f); |
||||
f.close(); |
||||
|
||||
linfo << "Etherscan : analyzing " << type << " for address: " << address; |
||||
|
||||
if (!jvalue["status"].as_string().compare("1")) |
||||
{ |
||||
for (int i=0;i<jvalue["result"].size();i++) |
||||
{ |
||||
ltrace << "Found tx " << jvalue["result"][i]["hash"].as_string(); |
||||
|
||||
BlockchainTxDetailsTypeETH tx; |
||||
|
||||
// Search if the hash is already in the list. If yes, remove the element.
|
||||
// It will be added after the operation list is updated.
|
||||
list<BlockchainTxDetailsTypeETH>::iterator it; |
||||
for (it=l.begin();it!=l.end();it++) |
||||
{ |
||||
if (it->hash == jvalue["result"][i]["hash"].as_string()) |
||||
{ |
||||
ltrace << "Tx already existed. Removing from list so it can be updated with new values."; |
||||
tx = (*it); |
||||
l.erase(it); |
||||
break; |
||||
} |
||||
} |
||||
|
||||
tx.hash = jvalue["result"][i]["hash"].as_string(); |
||||
tx.time.setFromUnixTime(strtol(jvalue["result"][i]["timeStamp"].as_string().c_str(),NULL,10)); |
||||
|
||||
Money gasUsed (jvalue["result"][i]["gasUsed"]); |
||||
Money gasPrice (jvalue["result"][i]["gasPrice"]); |
||||
Money fee = gasPrice*gasUsed; |
||||
Money feeDivider("1000000000000000000"); |
||||
fee /= feeDivider; |
||||
tx.fee = fee; |
||||
|
||||
if (type == TXLIST_NORMAL) |
||||
{ |
||||
BlockchainTxOperationTypeETH op; |
||||
op.addressFrom = jvalue["result"][i]["from"].as_string(); |
||||
op.addressTo = jvalue["result"][i]["to"].as_string(); |
||||
op.amountCoinId = CPFM_COIN_ID_ETH; |
||||
|
||||
Money divider("1000000000000000000"); |
||||
Money m(jvalue["result"][i]["value"]); |
||||
m /= divider; |
||||
op.amount = m; |
||||
|
||||
ltrace << "Operation. Amount: " << op.amount << ". Coin : " << getCoinName(op.amountCoinId); |
||||
|
||||
tx.operations.push_back(op); |
||||
} |
||||
else if (type == TXLIST_TOKENS) |
||||
{ |
||||
BlockchainTxOperationTypeETH op; |
||||
op.addressFrom = jvalue["result"][i]["from"].as_string(); |
||||
op.addressTo = jvalue["result"][i]["to"].as_string(); |
||||
op.amountCoinId = GetCoinIdFromContractAddress(jvalue["result"][i]["contractAddress"].as_string()); |
||||
|
||||
if (!op.amountCoinId) |
||||
{ |
||||
lerr << "Unknown token : " << jvalue["result"][i]["tokenSymbol"].as_string() << ". Tx will be ignored."; |
||||
continue; |
||||
} |
||||
|
||||
Money divider = 1; |
||||
int multiplier = strtol(jvalue["result"][i]["tokenDecimal"].as_string().c_str(),NULL,10); |
||||
for (int i=0;i<multiplier;i++) |
||||
{ |
||||
divider*=10; |
||||
} |
||||
|
||||
Money m(jvalue["result"][i]["value"]); |
||||
m /= divider; |
||||
op.amount = m; |
||||
|
||||
ltrace << "Operation. Amount: " << op.amount << ". Coin : " << getCoinName(op.amountCoinId); |
||||
|
||||
tx.operations.push_back(op); |
||||
} |
||||
else if (type == TXLIST_TOKENS) |
||||
{ |
||||
lerr << "Internal tx are not supported."; |
||||
continue; |
||||
} |
||||
else |
||||
{ |
||||
lerr << "Unknown tx type : " << type << ". Tx will be ignored."; |
||||
continue; |
||||
} |
||||
|
||||
hashList.push_back(tx.hash); |
||||
l.push_back(tx); |
||||
} |
||||
} |
||||
|
||||
return l; |
||||
} |
||||
|
||||
bool BlockchainDataSourceETH_Etherscan::retrieveCacheFilesForAddress(string address) |
||||
{ |
||||
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_NORMAL))) |
||||
{ |
||||
saveBlockchainAddressDataToCacheFile(address,TXLIST_NORMAL); |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_NORMAL))) |
||||
{ |
||||
lerr << "Etherscan : cache file for " << TXLIST_NORMAL << " could not be found."; |
||||
return false; |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_INTERNAL))) |
||||
{ |
||||
saveBlockchainAddressDataToCacheFile(address,TXLIST_INTERNAL); |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_INTERNAL))) |
||||
{ |
||||
lerr << "Etherscan : cache file for " << TXLIST_INTERNAL << " could not be found."; |
||||
return false; |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_TOKENS))) |
||||
{ |
||||
saveBlockchainAddressDataToCacheFile(address,TXLIST_TOKENS); |
||||
} |
||||
|
||||
if (!bfs::exists(getCacheFilenameForAddress(address,TXLIST_TOKENS))) |
||||
{ |
||||
lerr << "Etherscan : cache file for " << TXLIST_TOKENS << " could not be found."; |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
string BlockchainDataSourceETH_Etherscan::getCacheFilenameForAddress(string address, string type) |
||||
{ |
||||
string cacheFilename("data/cache/etherscan.io/" + type + "/" + address); |
||||
return cacheFilename; |
||||
} |
||||
|
||||
void BlockchainDataSourceETH_Etherscan::saveBlockchainAddressDataToCacheFile(string address, string type) |
||||
{ |
||||
try |
||||
{ |
||||
sleep (5); |
||||
|
||||
linfo << "Etherscan : querying API about " << type << " for " << address; |
||||
|
||||
string sRequestURL = "/api?module=account&action="+type+"&address="; |
||||
sRequestURL += address; |
||||
http_client apiclient("https://api.etherscan.io"); |
||||
|
||||
m_currentRequestCacheFilename = getCacheFilenameForAddress(address,type); |
||||
|
||||
apiclient.request(methods::GET,sRequestURL).then([](http_response response) |
||||
{ |
||||
if (response.status_code() == status_codes::OK) |
||||
{ |
||||
ldebug << "Etherscan : response OK."; |
||||
return response.extract_json(); |
||||
} |
||||
return pplx::task_from_result(json::value()); |
||||
}) |
||||
.then([this](pplx::task<json::value> previousTask) |
||||
{ |
||||
if (previousTask.get() != json::value::null()) |
||||
{ |
||||
linfo << "Etherscan : saving query result to " << m_currentRequestCacheFilename; |
||||
ofstream f; |
||||
f.open(m_currentRequestCacheFilename); |
||||
f << previousTask.get(); |
||||
f.close(); |
||||
} |
||||
else |
||||
{ |
||||
lerr << "Etherscan : query result is empty. Nothing will be saved to the cache file."; |
||||
} |
||||
}) |
||||
.wait(); |
||||
} |
||||
catch(const http::http_exception& e) |
||||
{ |
||||
lerr << "Etherscan : failed to query API about " << address; |
||||
} |
||||
} |
||||
|
||||
int BlockchainDataSourceETH_Etherscan::GetCoinIdFromContractAddress(string contractAddress) |
||||
{ |
||||
// LPT - Livepeer Token
|
||||
if (contractAddress == "0x58b6a8a3302369daec383334672404ee733ab239") |
||||
return CPFM_COIN_ID_LPT; |
||||
|
||||
// LEND - EthLend - Migrated to AAVE.
|
||||
if (contractAddress == "0x80fb784b7ed66730e8b1dbd9820afd29931aab03") |
||||
return CPFM_COIN_ID_LEND; |
||||
|
||||
// AAVE
|
||||
if (contractAddress == "0x7fc66500c84a76ad7e9c93437bfc5ac33e2ddae9") |
||||
return CPFM_COIN_ID_AAVE; |
||||
|
||||
// BCDT - Blockchain Certified Data Token.
|
||||
if (contractAddress == "0xacfa209fb73bf3dd5bbfb1101b9bc999c49062a5") |
||||
return CPFM_COIN_ID_BCDT; |
||||
|
||||
// KNC - Kyber Network
|
||||
if (contractAddress == "0xdd974d5c2e2928dea5f71b9825b8b646686bd200") |
||||
return CPFM_COIN_ID_KNC; |
||||
|
||||
// AIR - AirToken
|
||||
if (contractAddress == "0x27dce1ec4d3f72c3e457cc50354f1f975ddef488") |
||||
return CPFM_COIN_ID_AIR; |
||||
|
||||
lerr << "Unsupported contact address " << contractAddress; |
||||
return 0; |
||||
} |
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED |
||||
#define CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED |
||||
|
||||
#include "datasources/datasource.h" |
||||
|
||||
|
||||
class BlockchainDataSourceETH_Etherscan |
||||
{ |
||||
public: |
||||
list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address); |
||||
|
||||
private: |
||||
list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddressTxType(string address, string type, list<string>& hashList); |
||||
void saveBlockchainAddressDataToCacheFile(string address, string type); |
||||
string getCacheFilenameForAddress(string address, string type); |
||||
bool retrieveCacheFilesForAddress(string address); |
||||
int GetCoinIdFromContractAddress(string contractAddress); |
||||
|
||||
string m_currentRequestCacheFilename; |
||||
}; |
||||
|
||||
#endif // CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED
|
@ -1,316 +0,0 @@
|
||||
/*
|
||||
* 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)); |
||||
} |
||||
} |
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_BCH_H_INCLUDED |
||||
#define CPFM_WALLET_BCH_H_INCLUDED |
||||
|
||||
#include "wallet.h" |
||||
|
||||
|
||||
class WalletHandlerBCH : public WalletHandler |
||||
{ |
||||
public: |
||||
WalletHandlerBCH(int userId); |
||||
virtual ~WalletHandlerBCH(); |
||||
|
||||
virtual void analyzeUserWallets(); |
||||
|
||||
private: |
||||
void addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses); |
||||
void analyzeUserWalletData(int walletId); |
||||
}; |
||||
|
||||
class AddressHandlerBCH |
||||
{ |
||||
public: |
||||
AddressHandlerBCH(string address, int walletId); |
||||
virtual ~AddressHandlerBCH(); |
||||
|
||||
void getAddressData(); |
||||
void addAddressDataToDB(); |
||||
void setWalletAddresses(list<string> walletAddresses) { m_walletAddresses = walletAddresses; } |
||||
void setUserAddresses(list<string> userAddresses) { m_userAddresses = userAddresses; } |
||||
|
||||
private: |
||||
void getBlockchainAddressData(); |
||||
void addCachedAddressDataToDB(); |
||||
void addAddressDataJSONToDB(json::value jvalue); |
||||
string getCacheFilename(); |
||||
|
||||
string m_address; |
||||
int m_walletId; |
||||
list<string> m_userAddresses; |
||||
list<string> m_walletAddresses; |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_BTC_H_INCLUDED
|
@ -1,332 +0,0 @@
|
||||
/*
|
||||
* 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 |
||||
));
|
||||
} |
||||
} |
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_BTC_H_INCLUDED |
||||
#define CPFM_WALLET_BTC_H_INCLUDED |
||||
|
||||
#include "wallet.h" |
||||
|
||||
|
||||
class WalletHandlerBTC : public WalletHandler |
||||
{ |
||||
public: |
||||
WalletHandlerBTC(int userId); |
||||
virtual ~WalletHandlerBTC(); |
||||
|
||||
virtual void analyzeUserWallets(); |
||||
|
||||
private: |
||||
void addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses); |
||||
void analyzeUserWalletData(int walletId); |
||||
}; |
||||
|
||||
class AddressHandlerBTC |
||||
{ |
||||
public: |
||||
AddressHandlerBTC(string address, int walletId); |
||||
virtual ~AddressHandlerBTC(); |
||||
|
||||
void getAddressData(); |
||||
void addAddressDataToDB(); |
||||
void setWalletAddresses(list<string> walletAddresses) { m_walletAddresses = walletAddresses; } |
||||
void setUserAddresses(list<string> userAddresses) { m_userAddresses = userAddresses; } |
||||
|
||||
private: |
||||
void getBlockchainAddressData(); |
||||
void addCachedAddressDataToDB(); |
||||
void addAddressDataJSONToDB(json::value jvalue); |
||||
string getCacheFilename(); |
||||
|
||||
string m_address; |
||||
int m_walletId; |
||||
list<string> m_userAddresses; |
||||
list<string> m_walletAddresses; |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_BTC_H_INCLUDED
|
@ -1,353 +0,0 @@
|
||||
/*
|
||||
* 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 |
||||
));
|
||||
} |
||||
*/ |
||||
} |
@ -1,63 +0,0 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_ETH_H_INCLUDED |
||||
#define CPFM_WALLET_ETH_H_INCLUDED |
||||
|
||||
#include "wallet.h" |
||||
|
||||
|
||||
class WalletHandlerETH : public WalletHandler |
||||
{ |
||||
public: |
||||
WalletHandlerETH(int userId); |
||||
virtual ~WalletHandlerETH(); |
||||
|
||||
virtual void analyzeUserWallets(); |
||||
|
||||
private: |
||||
void addUserWalletDataToDB(int walletId, list<string> walletAddresses, list<string> userAddresses); |
||||
void analyzeUserWalletData(int walletId); |
||||
}; |
||||
|
||||
class AddressHandlerETH |
||||
{ |
||||
public: |
||||
AddressHandlerETH(string address, int walletId); |
||||
virtual ~AddressHandlerETH(); |
||||
|
||||
void getAddressData(); |
||||
void addAddressDataToDB(); |
||||
void setWalletAddresses(list<string> walletAddresses) { m_walletAddresses = walletAddresses; } |
||||
void setUserAddresses(list<string> userAddresses) { m_userAddresses = userAddresses; } |
||||
|
||||
private: |
||||
void getBlockchainAddressData(); |
||||
void addCachedAddressDataToDB(); |
||||
void addAddressDataJSONToDB(json::value jvalue); |
||||
string getCacheFilename(); |
||||
|
||||
string m_address; |
||||
int m_walletId; |
||||
list<string> m_userAddresses; |
||||
list<string> m_walletAddresses; |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_ETH_H_INCLUDED
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/wallet_bch.h" |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> WalletBCH::getTxDetailsListForAddresses(list<string> addresses) |
||||
{ |
||||
BlockchainDataSourceBCH src; |
||||
return src.getTxDetailsListForAddresses(addresses); |
||||
} |
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_BCH_H_INCLUDED |
||||
#define CPFM_WALLET_BCH_H_INCLUDED |
||||
|
||||
#include "wallets/wallet_type_btc.h" |
||||
#include "datasources/datasource.h" |
||||
|
||||
class WalletBCH : public WalletTypeBTC |
||||
{ |
||||
public: |
||||
WalletBCH(int walletId):WalletTypeBTC(walletId) { m_coinId = CPFM_COIN_ID_BCH; m_blockchainId = CPFM_BLOCKCHAIN_ID_BCH; m_walletTypeId = CPFM_WALLET_TYPE_ID_BCH; } |
||||
|
||||
protected: |
||||
virtual list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses); |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_BCH_H_INCLUDED
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/wallet_btc.h" |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> WalletBTC::getTxDetailsListForAddresses(list<string> addresses) |
||||
{ |
||||
BlockchainDataSourceBTC src; |
||||
return src.getTxDetailsListForAddresses(addresses); |
||||
} |
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_BTC_H_INCLUDED |
||||
#define CPFM_WALLET_BTC_H_INCLUDED |
||||
|
||||
#include "wallets/wallet_type_btc.h" |
||||
#include "datasources/datasource.h" |
||||
|
||||
class WalletBTC : public WalletTypeBTC |
||||
{ |
||||
public: |
||||
WalletBTC(int walletId):WalletTypeBTC(walletId) { m_coinId = CPFM_COIN_ID_BTC; m_blockchainId = CPFM_BLOCKCHAIN_ID_BTC; m_walletTypeId = CPFM_WALLET_TYPE_ID_BTC; } |
||||
|
||||
protected: |
||||
virtual list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses); |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_BTC_H_INCLUDED
|
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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/wallet_eth.h" |
||||
|
||||
list<BlockchainTxDetailsTypeETH> WalletETH::getTxDetailsListForAddress(string address) |
||||
{ |
||||
BlockchainDataSourceETH src; |
||||
return src.getTxDetailsListForAddress(address); |
||||
} |
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_ETH_H_INCLUDED |
||||
#define CPFM_WALLET_ETH_H_INCLUDED |
||||
|
||||
#include "wallets/wallet_type_eth.h" |
||||
#include "datasources/datasource.h" |
||||
|
||||
class WalletETH : public WalletTypeETH |
||||
{ |
||||
public: |
||||
WalletETH(int walletId):WalletTypeETH(walletId) { m_coinId = CPFM_COIN_ID_ETH; m_blockchainId = CPFM_BLOCKCHAIN_ID_ETH; m_walletTypeId = CPFM_WALLET_TYPE_ID_ETH; } |
||||
|
||||
protected: |
||||
virtual list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address); |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_ETH_H_INCLUDED
|
@ -0,0 +1,324 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/wallet_type_btc.h" |
||||
|
||||
SQLPP_ALIAS_PROVIDER(max_raw_tx_id); |
||||
|
||||
void WalletTypeBTC::update() |
||||
{ |
||||
if (!m_walletId) |
||||
{ |
||||
lerr << "Wallet id not defined. Ignoring update."; |
||||
} |
||||
|
||||
linfo << "Analyzing " << getCoinName(m_coinId) << " wallet " << m_walletId; |
||||
|
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets = TableWallets{}; |
||||
const auto wallets_addresses = TableWalletsAddresses{}; |
||||
const auto wallets_tx = TableWalletsTx{}; |
||||
|
||||
// Load wallet addresses
|
||||
list<string> addresses; |
||||
for (const auto& row: db.run(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.wallet_id == wallets_addresses.wallet_id and wallets.wallet_id == m_walletId and wallets.type_id == m_walletTypeId))) |
||||
{ |
||||
addresses.push_back(row.address); |
||||
} |
||||
|
||||
// Get wallet tx data from data source and insert it in the DB raw tables.
|
||||
list<BlockchainTxDetailsTypeBTC> l; |
||||
l = getTxDetailsListForAddresses(addresses); |
||||
addTxDetailsListToRawDB(m_walletId,l); |
||||
|
||||
// Load tx data from DB raw tables, analyze data, and insert it in DB tables.
|
||||
l = getTxDetailsListFromRawDB(m_walletId); |
||||
|
||||
db(remove_from(wallets_tx).where(wallets_tx.wallet_id == m_walletId)); |
||||
|
||||
addTxDetailsListToDB(m_walletId,addresses,l); |
||||
updateBalanceFromTxDetailsInDB(m_walletId); |
||||
} |
||||
|
||||
void WalletTypeBTC::addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeBTC>& l) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_btc_raw_tx = TableWalletsBtcRawTx{}; |
||||
const auto blockchain_btc_raw_tx = TableBlockchainBtcRawTx{}; |
||||
const auto blockchain_btc_raw_tx_details = TableBlockchainBtcRawTxDetails{}; |
||||
|
||||
linfo << "Clearing tx raw data links for " << getCoinName(m_coinId) << " wallet " << walletId << "."; |
||||
|
||||
const auto result = db(remove_from(wallets_btc_raw_tx).where(wallets_btc_raw_tx.wallet_id == walletId)); |
||||
|
||||
linfo << "Now adding raw data to DB for " << getCoinName(m_coinId) << " wallet " << walletId << "."; |
||||
|
||||
for (const auto& tx: l) |
||||
{ |
||||
string hash = tx.hash; |
||||
|
||||
ldebug << "Adding raw data to DB for tx " << hash << "."; |
||||
|
||||
const auto result = db(select(blockchain_btc_raw_tx.raw_tx_id).from(blockchain_btc_raw_tx).where(blockchain_btc_raw_tx.hash == hash and blockchain_btc_raw_tx.blockchain_id == m_blockchainId)); |
||||
if (!result.empty()) |
||||
{ |
||||
ldebug << "Raw data for tx " << hash << " is already in DB. Ignoring."; |
||||
|
||||
db(insert_into(wallets_btc_raw_tx).set( |
||||
wallets_btc_raw_tx.raw_tx_id = result.front().raw_tx_id, |
||||
wallets_btc_raw_tx.wallet_id = walletId |
||||
)); |
||||
} |
||||
else |
||||
{ |
||||
db(insert_into(blockchain_btc_raw_tx).set( |
||||
blockchain_btc_raw_tx.hash = hash, |
||||
blockchain_btc_raw_tx.unix_time = tx.time.toUnixTime(), |
||||
blockchain_btc_raw_tx.blockchain_id = m_blockchainId |
||||
)); |
||||
|
||||
int rawTxId = db(select(max(blockchain_btc_raw_tx.raw_tx_id).as(max_raw_tx_id)).from(blockchain_btc_raw_tx).unconditionally()).front().max_raw_tx_id; |
||||
|
||||
ldebug << "Raw tax id created : " << rawTxId << "."; |
||||
|
||||
ldebug << "Adding inputs raw data to DB for tx " << hash << "."; |
||||
|
||||
for (const auto& input : tx.inputs) |
||||
{ |
||||
// Inputs are positive.
|
||||
Money amount = input.second; |
||||
|
||||
db(insert_into(blockchain_btc_raw_tx_details).set( |
||||
blockchain_btc_raw_tx_details.raw_tx_id = rawTxId, |
||||
blockchain_btc_raw_tx_details.amount = amount.toBoostMpf(), |
||||
blockchain_btc_raw_tx_details.address = input.first |
||||
)); |
||||
} |
||||
|
||||
ldebug << "Adding outputs raw data to DB for tx " << hash << "."; |
||||
|
||||
for (const auto& output : tx.outputs) |
||||
{ |
||||
// Outputs are negative.
|
||||
Money amount = 0-output.second; |
||||
|
||||
db(insert_into(blockchain_btc_raw_tx_details).set( |
||||
blockchain_btc_raw_tx_details.raw_tx_id = rawTxId, |
||||
blockchain_btc_raw_tx_details.amount = amount.toBoostMpf(), |
||||
blockchain_btc_raw_tx_details.address = output.first |
||||
)); |
||||
} |
||||
|
||||
db(insert_into(wallets_btc_raw_tx).set( |
||||
wallets_btc_raw_tx.raw_tx_id = rawTxId, |
||||
wallets_btc_raw_tx.wallet_id = walletId |
||||
)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> WalletTypeBTC::getTxDetailsListFromRawDB(const int walletId) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_btc_raw_tx = TableWalletsBtcRawTx{}; |
||||
const auto blockchain_btc_raw_tx = TableBlockchainBtcRawTx{}; |
||||
const auto blockchain_btc_raw_tx_details = TableBlockchainBtcRawTxDetails{}; |
||||
|
||||
linfo << "Retrieving data from raw DB for " << getCoinName(m_coinId) << " wallet " << walletId << "."; |
||||
|
||||
list<BlockchainTxDetailsTypeBTC> l; |
||||
for (const auto& rowTx: db.run(select(blockchain_btc_raw_tx.raw_tx_id,blockchain_btc_raw_tx.hash,blockchain_btc_raw_tx.unix_time).from(blockchain_btc_raw_tx.cross_join(wallets_btc_raw_tx)).where(blockchain_btc_raw_tx.raw_tx_id == wallets_btc_raw_tx.raw_tx_id and wallets_btc_raw_tx.wallet_id == walletId))) |
||||
{ |
||||
ldebug << "Retrieving data from raw DB for tx " << rowTx.hash << "."; |
||||
|
||||
BlockchainTxDetailsTypeBTC tx; |
||||
|
||||
tx.hash = rowTx.hash; |
||||
tx.time.setFromUnixTime(rowTx.unix_time); |
||||
|
||||
ldebug << "Retrieving inputs/outputs data from raw DB for tx " << rowTx.hash << "."; |
||||
|
||||
for (const auto& row: db.run(select(blockchain_btc_raw_tx_details.amount,blockchain_btc_raw_tx_details.address).from(blockchain_btc_raw_tx_details).where(blockchain_btc_raw_tx_details.raw_tx_id == rowTx.raw_tx_id))) |
||||
{ |
||||
Money m (row.amount); |
||||
|
||||
if (m >= 0) |
||||
tx.inputs[row.address] = m; |
||||
else |
||||
tx.outputs[row.address] = 0-m; |
||||
} |
||||
|
||||
l.push_back(tx); |
||||
} |
||||
|
||||
return l; |
||||
} |
||||
|
||||
void WalletTypeBTC::addTxDetailsListToDB(const int walletId, const list<string> walletAddresses, const list<BlockchainTxDetailsTypeBTC>& l) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_tx = TableWalletsTx{}; |
||||
|
||||
linfo << "Now adding data to DB for " << getCoinName(m_coinId) << " wallet " << walletId << "."; |
||||
|
||||
for (const auto& tx: l) |
||||
{ |
||||
Money txTotalOutputAmount; |
||||
Money txTotalInputAmount; |
||||
Money txWalletOutputAmount; |
||||
Money txWalletInputAmount; |
||||
bool bToWallet = false; |
||||
bool bFromWallet = false; |
||||
list<string> inputAddresses; |
||||
list<string> outputAddresses; |
||||
|
||||
ltrace << "Now adding data to DB for tx " << tx.hash << "."; |
||||
|
||||
for (const auto& input : tx.inputs) |
||||
{ |
||||
//ltrace << "Input : " << input.first << ", amount : " << input.second;
|
||||
|
||||
txTotalInputAmount += input.second; |
||||
|
||||
if (std::find(walletAddresses.begin(), walletAddresses.end(), input.first) != walletAddresses.end()) |
||||
{ |
||||
bFromWallet = true; |
||||
txWalletInputAmount += input.second; |
||||
} |
||||
else |
||||
{ |
||||
inputAddresses.push_back(input.first); |
||||
} |
||||
} |
||||
|
||||
for (const auto& output : tx.outputs) |
||||
{ |
||||
//ltrace << "Output : " << output.first << ", amount : " << output.second;
|
||||
|
||||
txTotalOutputAmount += output.second; |
||||
|
||||
if (std::find(walletAddresses.begin(), walletAddresses.end(), output.first) != walletAddresses.end()) |
||||
{ |
||||
bToWallet = true; |
||||
txWalletOutputAmount += output.second; |
||||
} |
||||
else |
||||
{ |
||||
outputAddresses.push_back(output.first); |
||||
} |
||||
} |
||||
|
||||
if (bFromWallet) |
||||
{ |
||||
// In case at least one address was in the inputs, then ALL other inputs should be.
|
||||
for (auto const& addr: inputAddresses) |
||||
{ |
||||
if (std::find(walletAddresses.begin(), walletAddresses.end(), addr) == walletAddresses.end()) |
||||
{ |
||||
lwarn << "User configuration is missing this address: " << addr; |
||||
} |
||||
} |
||||
|
||||
// In case at least one address was in the inputs, the outputs might be either another wallet for this user,
|
||||
// or one of the wallet addresses might be missing from the configuration.
|
||||
for (auto const& addr: outputAddresses) |
||||
{ |
||||
if (std::find(walletAddresses.begin(), walletAddresses.end(), addr) == walletAddresses.end()) |
||||
{ |
||||
linfo << "Potential other address to analyze: " << L_Cyan << addr; |
||||
} |
||||
} |
||||
} |
||||
|
||||
Money amount; |
||||
Money fee; |
||||
|
||||
if (bFromWallet) |
||||
{ |
||||
// The fee only applies in case this tx was initiated by the wallet
|
||||
fee = txTotalInputAmount - txTotalOutputAmount; |
||||
|
||||
amount = txWalletOutputAmount - txWalletInputAmount + fee; |
||||
} |
||||
else |
||||
{ |
||||
amount = txWalletOutputAmount; |
||||
fee = 0; |
||||
} |
||||
|
||||
db(insert_into(wallets_tx).set( |
||||
wallets_tx.wallet_id = walletId, |
||||
wallets_tx.amount = amount.toBoostMpf(), |
||||
wallets_tx.fee = fee.toBoostMpf(), |
||||
wallets_tx.unix_time = tx.time.toUnixTime(), |
||||
wallets_tx.amount_coin_id = m_coinId, |
||||
wallets_tx.fee_coin_id = m_coinId, |
||||
wallets_tx.operation_type = CPFM_WALLET_OPERATION_UNKNOWN |
||||
)); |
||||
} |
||||
} |
||||
|
||||
void WalletTypeBTC::updateBalanceFromTxDetailsInDB(int walletId) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_tx = TableWalletsTx{}; |
||||
const auto wallets_balances = TableWalletsBalances{}; |
||||
|
||||
ldebug << "------------------------------------------------------------"; |
||||
|
||||
Money walletBalance = 0; |
||||
Money walletInputs = 0; |
||||
Money walletOutputs = 0; |
||||
Money walletFees = 0; |
||||
|
||||
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.wallet_id == walletId).order_by(wallets_tx.unix_time.asc()))) |
||||
{ |
||||
Money txAmount(row.amount); |
||||
Money txFee(row.fee); |
||||
|
||||
if (txAmount<0) |
||||
walletOutputs += txAmount; |
||||
else |
||||
walletInputs += txAmount; |
||||
|
||||
walletFees += txFee; |
||||
|
||||
walletBalance = walletInputs + walletOutputs - walletFees; |
||||
|
||||
string sReason = "?"; |
||||
|
||||
ltrace << "Tx Amount: " << txAmount |
||||
<< ". Reason: " << sReason |
||||
<< ". Tx Fees: " << txFee |
||||
<< ". Total outputs: " << walletOutputs |
||||
<< ". Total inputs: " << walletInputs |
||||
<< ". Total Balance: " << walletBalance; |
||||
} |
||||
|
||||
linfo << "Wallet " << walletId << " balance is: " << walletBalance; |
||||
|
||||
db(remove_from(wallets_balances).where(wallets_balances.wallet_id == walletId)); |
||||
|
||||
db(insert_into(wallets_balances).set( |
||||
wallets_balances.wallet_id = walletId, |
||||
wallets_balances.coin_id = m_coinId, |
||||
wallets_balances.balance = walletBalance.toBoostMpf() |
||||
));
|
||||
} |
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_TYPE_BTC_H_INCLUDED |
||||
#define CPFM_WALLET_TYPE_BTC_H_INCLUDED |
||||
|
||||
#include "wallet.h" |
||||
#include "datasources/datasource.h" |
||||
|
||||
class WalletTypeBTC : public Wallet |
||||
{ |
||||
public: |
||||
WalletTypeBTC(int walletId):Wallet(walletId) {} |
||||
|
||||
virtual void update(); |
||||
|
||||
private: |
||||
void addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeBTC>& l); |
||||
void addTxDetailsListToDB(const int walletId, const list<string> walletAddresses, const list<BlockchainTxDetailsTypeBTC>& l); |
||||
list<BlockchainTxDetailsTypeBTC> getTxDetailsListFromRawDB(const int walletId); |
||||
void updateBalanceFromTxDetailsInDB(int walletId); |
||||
|
||||
protected: |
||||
virtual list<BlockchainTxDetailsTypeBTC> getTxDetailsListForAddresses(list<string> addresses) = 0; |
||||
|
||||
int m_coinId; |
||||
int m_walletTypeId; |
||||
int m_blockchainId; |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_TYPE_BTC_H_INCLUDED
|
@ -0,0 +1,248 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/wallet_type_eth.h" |
||||
|
||||
SQLPP_ALIAS_PROVIDER(max_raw_tx_id); |
||||
|
||||
void WalletTypeETH::update() |
||||
{ |
||||
if (!m_walletId) |
||||
{ |
||||
lerr << "Wallet id not defined. Ignoring update."; |
||||
} |
||||
|
||||
linfo << "Analyzing " << getBlockchainName(m_blockchainId) << " wallet " << m_walletId; |
||||
|
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets = TableWallets{}; |
||||
const auto wallets_addresses = TableWalletsAddresses{}; |
||||
const auto wallets_tx = TableWalletsTx{}; |
||||
|
||||
// Load wallet address
|
||||
string address = db(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.wallet_id == wallets_addresses.wallet_id and wallets.wallet_id == m_walletId and wallets.type_id == m_walletTypeId)).front().address; |
||||
|
||||
// Get wallet tx data from data source and insert it in the DB raw tables.
|
||||
list<BlockchainTxDetailsTypeETH> l; |
||||
l = getTxDetailsListForAddress(address); |
||||
addTxDetailsListToRawDB(m_walletId,l); |
||||
|
||||
// Load tx data from DB raw tables, analyze data, and insert it in DB tables.
|
||||
l = getTxDetailsListFromRawDB(m_walletId); |
||||
|
||||
db(remove_from(wallets_tx).where(wallets_tx.wallet_id == m_walletId)); |
||||
|
||||
addTxDetailsListToDB(m_walletId,address,l); |
||||
updateBalanceFromTxDetailsInDB(m_walletId); |
||||
} |
||||
|
||||
void WalletTypeETH::addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeETH>& l) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_eth_raw_tx = TableWalletsEthRawTx{}; |
||||
const auto blockchain_eth_raw_tx = TableBlockchainEthRawTx{}; |
||||
const auto blockchain_eth_raw_tx_details = TableBlockchainEthRawTxDetails{}; |
||||
|
||||
linfo << "Clearing tx raw data links for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << "."; |
||||
|
||||
const auto result = db(remove_from(wallets_eth_raw_tx).where(wallets_eth_raw_tx.wallet_id == walletId)); |
||||
|
||||
linfo << "Now adding raw data to DB for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << "."; |
||||
|
||||
for (const auto& tx: l) |
||||
{ |
||||
string hash = tx.hash; |
||||
|
||||
ldebug << "Adding raw data to DB for tx " << hash << "."; |
||||
|
||||
const auto result = db(select(blockchain_eth_raw_tx.raw_tx_id).from(blockchain_eth_raw_tx).where(blockchain_eth_raw_tx.hash == hash and blockchain_eth_raw_tx.blockchain_id == m_blockchainId)); |
||||
if (!result.empty()) |
||||
{ |
||||
ldebug << "Raw data for tx " << hash << " is already in DB. Ignoring."; |
||||
|
||||
db(insert_into(wallets_eth_raw_tx).set( |
||||
wallets_eth_raw_tx.raw_tx_id = result.front().raw_tx_id, |
||||
wallets_eth_raw_tx.wallet_id = walletId |
||||
)); |
||||
} |
||||
else |
||||
{ |
||||
Money fee = tx.fee; |
||||
|
||||
db(insert_into(blockchain_eth_raw_tx).set( |
||||
blockchain_eth_raw_tx.hash = hash, |
||||
blockchain_eth_raw_tx.unix_time = tx.time.toUnixTime(), |
||||
blockchain_eth_raw_tx.blockchain_id = m_blockchainId, |
||||
blockchain_eth_raw_tx.fee = fee.toBoostMpf() |
||||
)); |
||||
|
||||
int rawTxId = db(select(max(blockchain_eth_raw_tx.raw_tx_id).as(max_raw_tx_id)).from(blockchain_eth_raw_tx).unconditionally()).front().max_raw_tx_id; |
||||
|
||||
ldebug << "Raw tax id created : " << rawTxId << "."; |
||||
|
||||
ldebug << "Adding raw data operations to DB for tx " << hash << "."; |
||||
|
||||
for (const auto& op : tx.operations) |
||||
{ |
||||
Money amount = op.amount; |
||||
|
||||
db(insert_into(blockchain_eth_raw_tx_details).set( |
||||
blockchain_eth_raw_tx_details.raw_tx_id = rawTxId, |
||||
blockchain_eth_raw_tx_details.address_from = op.addressFrom, |
||||
blockchain_eth_raw_tx_details.address_to = op.addressTo, |
||||
blockchain_eth_raw_tx_details.amount = amount.toBoostMpf(), |
||||
blockchain_eth_raw_tx_details.amount_coin_id = op.amountCoinId |
||||
)); |
||||
} |
||||
|
||||
db(insert_into(wallets_eth_raw_tx).set( |
||||
wallets_eth_raw_tx.raw_tx_id = rawTxId, |
||||
wallets_eth_raw_tx.wallet_id = walletId |
||||
)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
list<BlockchainTxDetailsTypeETH> WalletTypeETH::getTxDetailsListFromRawDB(const int walletId) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_eth_raw_tx = TableWalletsEthRawTx{}; |
||||
const auto blockchain_eth_raw_tx = TableBlockchainEthRawTx{}; |
||||
const auto blockchain_eth_raw_tx_details = TableBlockchainEthRawTxDetails{}; |
||||
|
||||
linfo << "Retrieving data from raw DB for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << "."; |
||||
|
||||
list<BlockchainTxDetailsTypeETH> l; |
||||
for (const auto& rowTx: db.run(select(blockchain_eth_raw_tx.raw_tx_id,blockchain_eth_raw_tx.hash,blockchain_eth_raw_tx.unix_time,blockchain_eth_raw_tx.fee).from(blockchain_eth_raw_tx.cross_join(wallets_eth_raw_tx)).where(blockchain_eth_raw_tx.raw_tx_id == wallets_eth_raw_tx.raw_tx_id and wallets_eth_raw_tx.wallet_id == walletId))) |
||||
{ |
||||
ldebug << "Retrieving data from raw DB for tx " << rowTx.hash << "."; |
||||
|
||||
BlockchainTxDetailsTypeETH tx; |
||||
|
||||
tx.hash = rowTx.hash; |
||||
tx.time.setFromUnixTime(rowTx.unix_time); |
||||
tx.fee = rowTx.fee; |
||||
|
||||
ldebug << "Retrieving operations data from raw DB for tx " << rowTx.hash << "."; |
||||
|
||||
for (const auto& rowOp: db.run(select(blockchain_eth_raw_tx_details.amount,blockchain_eth_raw_tx_details.amount_coin_id,blockchain_eth_raw_tx_details.address_from,blockchain_eth_raw_tx_details.address_to).from(blockchain_eth_raw_tx_details).where(blockchain_eth_raw_tx_details.raw_tx_id == rowTx.raw_tx_id))) |
||||
{ |
||||
BlockchainTxOperationTypeETH op; |
||||
op.addressFrom = rowOp.address_from; |
||||
op.addressTo = rowOp.address_to; |
||||
op.amount = rowOp.amount; |
||||
op.amountCoinId = rowOp.amount_coin_id; |
||||
|
||||
tx.operations.push_back(op); |
||||
} |
||||
|
||||
l.push_back(tx); |
||||
} |
||||
|
||||
return l; |
||||
} |
||||
|
||||
void WalletTypeETH::addTxDetailsListToDB(const int walletId, string address, const list<BlockchainTxDetailsTypeETH>& l) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_tx = TableWalletsTx{}; |
||||
|
||||
linfo << "Now adding data to DB for " << getBlockchainName(m_blockchainId) << " wallet " << walletId << "."; |
||||
|
||||
for (const auto& tx: l) |
||||
{
|
||||
ltrace << "Now adding data to DB for tx " << tx.hash << "."; |
||||
|
||||
bool bFeeAdded = false; |
||||
|
||||
for (const auto& op: tx.operations) |
||||
{ |
||||
Money amount; |
||||
Money fee; |
||||
|
||||
if (address == op.addressFrom) |
||||
{ |
||||
// Even if we have multiple operations for the same tx, the fee only applies once.
|
||||
if (!bFeeAdded) |
||||
{ |
||||
// The fee only applies in case this tx was initiated by the wallet
|
||||
fee = tx.fee; |
||||
} |
||||
|
||||
amount = 0-op.amount; |
||||
} |
||||
else |
||||
{ |
||||
amount = op.amount; |
||||
} |
||||
|
||||
db(insert_into(wallets_tx).set( |
||||
wallets_tx.wallet_id = walletId, |
||||
wallets_tx.amount = amount.toBoostMpf(), |
||||
wallets_tx.fee = fee.toBoostMpf(), |
||||
wallets_tx.unix_time = tx.time.toUnixTime(), |
||||
wallets_tx.amount_coin_id = op.amountCoinId, |
||||
wallets_tx.fee_coin_id = m_coinId, |
||||
wallets_tx.operation_type = CPFM_WALLET_OPERATION_UNKNOWN |
||||
)); |
||||
} |
||||
} |
||||
} |
||||
|
||||
void WalletTypeETH::updateBalanceFromTxDetailsInDB(int walletId) |
||||
{ |
||||
mysql::connection db(getMysqlConfig()); |
||||
const auto wallets_tx = TableWalletsTx{}; |
||||
const auto wallets_balances = TableWalletsBalances{}; |
||||
|
||||
ldebug << "------------------------------------------------------------"; |
||||
|
||||
map<int,Money> walletBalances; |
||||
|
||||
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.amount_coin_id, wallets_tx.fee).from(wallets_tx).where(wallets_tx.wallet_id == walletId).order_by(wallets_tx.unix_time.asc()))) |
||||
{ |
||||
Money txAmount(row.amount); |
||||
Money txFee(row.fee); |
||||
|
||||
walletBalances[row.amount_coin_id] += txAmount; |
||||
walletBalances[m_coinId] -= txFee; |
||||
|
||||
string sReason = "?"; |
||||
|
||||
ltrace << "Tx Amount: " << txAmount |
||||
<< ", Coin: " << getCoinName(row.amount_coin_id) |
||||
<< ". Reason: " << sReason |
||||
<< ". Tx Fees: " << txFee; |
||||
} |
||||
|
||||
db(remove_from(wallets_balances).where(wallets_balances.wallet_id == walletId)); |
||||
|
||||
for (const auto b: walletBalances) |
||||
{ |
||||
linfo << "Wallet " << walletId << " " << getCoinName(b.first) << " balance is: " << b.second; |
||||
|
||||
Money m = b.second; |
||||
db(insert_into(wallets_balances).set( |
||||
wallets_balances.wallet_id = walletId, |
||||
wallets_balances.coin_id = b.first, |
||||
wallets_balances.balance = m.toBoostMpf() |
||||
));
|
||||
}
|
||||
} |
@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2021, 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/>.
|
||||
*
|
||||
*/ |
||||
|
||||
#ifndef CPFM_WALLET_TYPE_ETH_H_INCLUDED |
||||
#define CPFM_WALLET_TYPE_ETH_H_INCLUDED |
||||
|
||||
#include "wallet.h" |
||||
#include "datasources/datasource.h" |
||||
|
||||
class WalletTypeETH : public Wallet |
||||
{ |
||||
public: |
||||
WalletTypeETH(int walletId):Wallet(walletId) {} |
||||
|
||||
virtual void update(); |
||||
|
||||
private: |
||||
void addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeETH>& l); |
||||
void addTxDetailsListToDB(const int walletId, string address, const list<BlockchainTxDetailsTypeETH>& l); |
||||
list<BlockchainTxDetailsTypeETH> getTxDetailsListFromRawDB(const int walletId); |
||||
void updateBalanceFromTxDetailsInDB(int walletId); |
||||
|
||||
protected: |
||||
virtual list<BlockchainTxDetailsTypeETH> getTxDetailsListForAddress(string address) = 0; |
||||
|
||||
int m_walletTypeId; |
||||
int m_blockchainId; |
||||
int m_coinId; |
||||
}; |
||||
|
||||
#endif // CPFM_WALLET_TYPE_ETH_H_INCLUDED
|
Loading…
Reference in new issue