diff --git a/CMakeLists.txt b/CMakeLists.txt index 3a59c45..60e7a36 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -38,6 +38,7 @@ set(LIBS ${LIBS} "-lcpprest") include_directories(src) include_directories(src/wallets) include_directories(src/exchanges) +include_directories(src/datasources) set(CMAKE_CXX_STANDARD 11) diff --git a/cmake/Modules/Findsqlpp11-connector-mysql.cmake b/cmake/Modules/Findsqlpp11-connector-mysql.cmake index 301fb2e..20e509a 100644 --- a/cmake/Modules/Findsqlpp11-connector-mysql.cmake +++ b/cmake/Modules/Findsqlpp11-connector-mysql.cmake @@ -29,7 +29,7 @@ if (SQLPP11_MYSQL_MAIN_HEADER) ) if("${check_result}" STREQUAL "") - string(APPEND SQLPP11_NOT_FOUND_MESSAGE "\nRejecting found '${SQLPP11_MYSQL_MAIN_HEADER}', it seems to be invalid.") + string(APPEND SQLPP11_MYSQL_NOT_FOUND_MESSAGE "\nRejecting found '${SQLPP11_MYSQL_MAIN_HEADER}', it seems to be invalid.") unset(SQLPP11_MYSQL_INCLUDE_DIR CACHE) else() # Check succeeded, create target diff --git a/sql/mysql.schema.sql b/sql/mysql.schema.sql index 2c0006f..cffd9a4 100644 --- a/sql/mysql.schema.sql +++ b/sql/mysql.schema.sql @@ -7,6 +7,37 @@ SET time_zone = "+00:00"; /*!40101 SET NAMES utf8mb4 */; +CREATE TABLE `blockchain_btc_raw_tx` ( + `raw_tx_id` int(11) NOT NULL, + `blockchain_id` int(11) NOT NULL, + `hash` varchar(200) NOT NULL, + `unix_time` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `blockchain_btc_raw_tx_details` ( + `raw_tx_detail_id` int(11) NOT NULL, + `raw_tx_id` int(11) NOT NULL, + `amount` decimal(40,18) NOT NULL, + `address` varchar(100) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `blockchain_eth_raw_tx` ( + `raw_tx_id` int(11) NOT NULL, + `blockchain_id` int(11) NOT NULL, + `hash` varchar(200) NOT NULL, + `unix_time` int(11) NOT NULL, + `fee` decimal(40,18) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `blockchain_eth_raw_tx_details` ( + `raw_tx_detail_id` int(11) NOT NULL, + `raw_tx_id` int(11) NOT NULL, + `address_from` varchar(200) NOT NULL, + `address_to` varchar(200) NOT NULL, + `amount` decimal(40,18) NOT NULL, + `amount_coin_id` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + CREATE TABLE `coins` ( `coin_id` int(11) NOT NULL, `coin_name` varchar(50) NOT NULL, @@ -32,7 +63,7 @@ CREATE TABLE `exchanges_ledgers` ( `account_id` int(11) NOT NULL, `exchange_ledger_id` varchar(50) NOT NULL, `exchange_reference_id` varchar(50) NOT NULL, - `timestamp` int(11) NOT NULL, + `unix_time` int(11) NOT NULL, `operation_type` int(11) NOT NULL, `coin_id` int(11) NOT NULL, `amount` decimal(40,18) NOT NULL, @@ -46,7 +77,7 @@ CREATE TABLE `exchanges_trades` ( `exchange_order_id` varchar(50) NOT NULL, `base_coin_id` int(11) NOT NULL, `quote_coin_id` int(11) NOT NULL, - `timestamp` int(11) NOT NULL, + `unix_time` int(11) NOT NULL, `type` int(11) NOT NULL, `order_type` int(11) NOT NULL, `price` decimal(40,18) NOT NULL, @@ -60,7 +91,8 @@ CREATE TABLE `wallets` ( `wallet_id` int(11) NOT NULL, `user_id` int(11) NOT NULL, `type_id` int(11) NOT NULL, - `wallet_name` varchar(50) NOT NULL + `wallet_name` varchar(50) NOT NULL, + `active` tinyint(1) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE `wallets_addresses` ( @@ -77,16 +109,40 @@ CREATE TABLE `wallets_balances` ( `balance` decimal(48,18) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE `wallets_btc_raw_tx` ( + `raw_tx_id` int(11) NOT NULL, + `wallet_id` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `wallets_eth_raw_tx` ( + `raw_tx_id` int(11) NOT NULL, + `wallet_id` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + CREATE TABLE `wallets_tx` ( `tx_id` int(11) NOT NULL, - `blockchain_tx_id` varchar(200) NOT NULL, `wallet_id` int(11) NOT NULL, `amount` decimal(40,18) NOT NULL, `fee` decimal(40,18) NOT NULL, - `timestamp` int(11) NOT NULL + `amount_coin_id` int(11) NOT NULL, + `fee_coin_id` int(11) NOT NULL, + `unix_time` int(11) NOT NULL, + `operation_type` int(11) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +ALTER TABLE `blockchain_btc_raw_tx` + ADD PRIMARY KEY (`raw_tx_id`); + +ALTER TABLE `blockchain_btc_raw_tx_details` + ADD PRIMARY KEY (`raw_tx_detail_id`); + +ALTER TABLE `blockchain_eth_raw_tx` + ADD PRIMARY KEY (`raw_tx_id`); + +ALTER TABLE `blockchain_eth_raw_tx_details` + ADD PRIMARY KEY (`raw_tx_detail_id`); + ALTER TABLE `exchanges_accounts` ADD PRIMARY KEY (`account_id`); @@ -103,12 +159,20 @@ ALTER TABLE `wallets_addresses` ADD PRIMARY KEY (`address_id`); ALTER TABLE `wallets_balances` - ADD PRIMARY KEY (`wallet_id`); + ADD PRIMARY KEY (`wallet_id`,`coin_id`); ALTER TABLE `wallets_tx` ADD PRIMARY KEY (`tx_id`); +ALTER TABLE `blockchain_btc_raw_tx` + MODIFY `raw_tx_id` int(11) NOT NULL AUTO_INCREMENT; +ALTER TABLE `blockchain_btc_raw_tx_details` + MODIFY `raw_tx_detail_id` int(11) NOT NULL AUTO_INCREMENT; +ALTER TABLE `blockchain_eth_raw_tx` + MODIFY `raw_tx_id` int(11) NOT NULL AUTO_INCREMENT; +ALTER TABLE `blockchain_eth_raw_tx_details` + MODIFY `raw_tx_detail_id` int(11) NOT NULL AUTO_INCREMENT; ALTER TABLE `exchanges_accounts` MODIFY `account_id` int(11) NOT NULL AUTO_INCREMENT; ALTER TABLE `exchanges_ledgers` @@ -123,4 +187,4 @@ ALTER TABLE `wallets_tx` MODIFY `tx_id` int(11) NOT NULL AUTO_INCREMENT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; -/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 852e4ef..027fee8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -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}) diff --git a/src/datasources/blockchain_info.cpp b/src/datasources/blockchain_info.cpp new file mode 100644 index 0000000..47d2d87 --- /dev/null +++ b/src/datasources/blockchain_info.cpp @@ -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 . + * + */ + +#include "datasources/blockchain_info.h" + +list BlockchainDataSourceBTC_BlockchainInfo::getTxDetailsListForAddress(string address) +{ + linfo << "Blockchain.info : Analyzing address: " << address; + + list 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 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; + } +} diff --git a/src/datasources/blockchain_info.h b/src/datasources/blockchain_info.h new file mode 100644 index 0000000..3a9e062 --- /dev/null +++ b/src/datasources/blockchain_info.h @@ -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 . + * + */ + +#ifndef CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED +#define CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED + +#include "datasources/datasource.h" + + +class BlockchainDataSourceBTC_BlockchainInfo +{ +public: + list getTxDetailsListForAddress(string address); + +private: + string getCacheFilenameForAddress(string address); + void saveBlockchainAddressDataToCacheFile(string address); + + string m_currentRequestCacheFilename; +}; + +#endif // CPFM_DATASOURCE_BLOCKCHAIN_INFO_H_INCLUDED \ No newline at end of file diff --git a/src/datasources/blockchair.cpp b/src/datasources/blockchair.cpp new file mode 100644 index 0000000..0fcc8b0 --- /dev/null +++ b/src/datasources/blockchair.cpp @@ -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 . + * + */ + +#include "datasources/blockchair.h" + + +Time BlockchainDataSourceTypeBTC_Blockchair::getTimeFromString(string s) +{ + std::istringstream in{s}; + date::sys_time tp; + in >> date::parse("%F %T", tp); + + Time t; + t.setFromUnixTime(std::chrono::system_clock::to_time_t(tp)); + + return t; +} + +list BlockchainDataSourceTypeBTC_Blockchair::getTxDetailsListForAddresses(list addresses) +{ + linfo << "Blockchair : analyzing address list"; + + list 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 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 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 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 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; + } +} diff --git a/src/datasources/blockchair.h b/src/datasources/blockchair.h new file mode 100644 index 0000000..d4ed472 --- /dev/null +++ b/src/datasources/blockchair.h @@ -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 . + * + */ + +#ifndef CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED +#define CPFM_DATASOURCE_BLOCKCHAIR_H_INCLUDED + +#include "datasources/datasource.h" + + +class BlockchainDataSourceTypeBTC_Blockchair +{ +public: + list getTxDetailsListForAddresses(list addresses); + +private: + string getCacheFilenameForAddresses(list address); + string getCacheFilenameForTx(string txHash); + + void saveBlockchainAddressesDataToCacheFile(list 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 \ No newline at end of file diff --git a/src/datasources/datasource.cpp b/src/datasources/datasource.cpp new file mode 100644 index 0000000..48648a1 --- /dev/null +++ b/src/datasources/datasource.cpp @@ -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 . + * + */ + +#include "datasources/datasource.h" +#include "datasources/blockchain_info.h" +#include "datasources/blockchair.h" +#include "datasources/etherscan.h" + +list BlockchainDataSourceBTC::getTxDetailsListForAddresses(list addresses) +{ + list l; + + // Blockchain.info + /* + BlockchainDataSourceBTC_BlockchainInfo bci; + list hashList; + + for (auto const address: addresses) + { + list 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 BlockchainDataSourceBCH::getTxDetailsListForAddresses(list addresses) +{ + list l; + + // Blockchair + BlockchainDataSourceBCH_Blockchair bc; + l = bc.getTxDetailsListForAddresses(addresses); + + return l; +} + +list BlockchainDataSourceETH::getTxDetailsListForAddress(string address) +{ + list l; + + // Etherscan + BlockchainDataSourceETH_Etherscan bc; + l = bc.getTxDetailsListForAddress(address); + + return l; +} diff --git a/src/datasources/datasource.h b/src/datasources/datasource.h new file mode 100644 index 0000000..a7da1de --- /dev/null +++ b/src/datasources/datasource.h @@ -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 . + * + */ + +#ifndef CPFM_DATASOURCE_H_INCLUDED +#define CPFM_DATASOURCE_H_INCLUDED + +#include "pf.h" + +class BlockchainTxDetailsTypeBTC +{ +public: + string hash; + Time time; + map inputs; + map outputs; +}; + +class BlockchainTxOperationTypeETH +{ +public: + string addressFrom; + string addressTo; + Money amount; + int amountCoinId; +}; + +class BlockchainTxDetailsTypeETH +{ +public: + string hash; + Time time; + list operations; + Money fee; +}; + +class BlockchainDataSourceBTC +{ +public: + list getTxDetailsListForAddresses(list addresses); +}; + +class BlockchainDataSourceBCH +{ +public: + list getTxDetailsListForAddresses(list addresses); +}; + +class BlockchainDataSourceETH +{ +public: + list getTxDetailsListForAddress(string address); +}; + +#endif // CPFM_DATASOURCE_H_INCLUDED \ No newline at end of file diff --git a/src/datasources/etherscan.cpp b/src/datasources/etherscan.cpp new file mode 100644 index 0000000..a28218f --- /dev/null +++ b/src/datasources/etherscan.cpp @@ -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 . + * + */ + +#include "datasources/etherscan.h" + +#define TXLIST_NORMAL "txlist" +#define TXLIST_INTERNAL "txlistinternal" +#define TXLIST_TOKENS "tokentx" + +list BlockchainDataSourceETH_Etherscan::getTxDetailsListForAddress(string address) +{ + linfo << "Etherscan : Analyzing address: " << address; + + if (!retrieveCacheFilesForAddress(address)) + { + list l; + lerr << "Etherscan : cache files could not be found. Address data was not retrieved."; + return l; + } + + list hashListNormal; + list hashListInternal; + list hashListToken; + + list lNormal = getTxDetailsListForAddressTxType(address,TXLIST_NORMAL,hashListNormal); + list lInternal = getTxDetailsListForAddressTxType(address,TXLIST_INTERNAL,hashListInternal); + list lToken = getTxDetailsListForAddressTxType(address,TXLIST_TOKENS,hashListToken); + + list l = lNormal; + + // Add internals tx to the list + for (auto const tx: lInternal) + { + BlockchainTxDetailsTypeETH txNew = tx; + + list::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::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 BlockchainDataSourceETH_Etherscan::getTxDetailsListForAddressTxType(string address, string type, list& hashList) +{ + list 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::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 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; +} \ No newline at end of file diff --git a/src/datasources/etherscan.h b/src/datasources/etherscan.h new file mode 100644 index 0000000..0aaf10a --- /dev/null +++ b/src/datasources/etherscan.h @@ -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 . + * + */ + +#ifndef CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED +#define CPFM_DATASOURCE_ETHERSCAN_H_INCLUDED + +#include "datasources/datasource.h" + + +class BlockchainDataSourceETH_Etherscan +{ +public: + list getTxDetailsListForAddress(string address); + +private: + list getTxDetailsListForAddressTxType(string address, string type, list& 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 \ No newline at end of file diff --git a/src/exchanges/kraken.cpp b/src/exchanges/kraken.cpp index e118193..ee920f5 100644 --- a/src/exchanges/kraken.cpp +++ b/src/exchanges/kraken.cpp @@ -47,10 +47,10 @@ void ExchangeHandlerKraken::analyzeUserData() getAccountBalance(); analyzeAccountBalance(); - //getAccountTrades(); + getAccountTrades(); //analyzeAccountTrades(); - //getAccountLedgers(); + getAccountLedgers(); //analyzeAccountLedgers(); } diff --git a/src/log.cpp b/src/log.cpp index 8f878e9..0594825 100644 --- a/src/log.cpp +++ b/src/log.cpp @@ -23,7 +23,7 @@ #include #include "log.h" -using namespace farm; +using namespace cpfm; using namespace log; const char* ErrorLogger::name() { return "ERROR"; } @@ -34,6 +34,8 @@ const char* InfoLogger::name() { return "INFO "; } const char* InfoLogger::color() { return L_White; } const char* DebugLogger::name() { return "DEBUG"; } const char* DebugLogger::color() { return L_Teal; } +const char* TraceLogger::name() { return "TRACE"; } +const char* TraceLogger::color() { return L_Violet; } LoggerBase::LoggerBase() { diff --git a/src/log.h b/src/log.h index bb15bc2..bf56959 100644 --- a/src/log.h +++ b/src/log.h @@ -28,13 +28,14 @@ using namespace std; -#define llog(X) farm::log::Logger() -#define lerr llog(farm::log::ErrorLogger) -#define linfo llog(farm::log::InfoLogger) -#define lwarn llog(farm::log::WarningLogger) -#define ldebug llog(farm::log::DebugLogger) +#define llog(X) cpfm::log::Logger() +#define lerr llog(cpfm::log::ErrorLogger) +#define linfo llog(cpfm::log::InfoLogger) +#define lwarn llog(cpfm::log::WarningLogger) +#define ldebug llog(cpfm::log::DebugLogger) +#define ltrace llog(cpfm::log::TraceLogger) -namespace farm { namespace log { +namespace cpfm { namespace log { #define L_Reset "\x1b[0m" // Text Reset @@ -107,6 +108,7 @@ struct ErrorLogger: public DefaultLogger { static const char* name(); static con struct WarningLogger: public DefaultLogger { static const char* name(); static const char* color(); }; struct InfoLogger: public DefaultLogger { static const char* name(); static const char* color(); }; struct DebugLogger: public DefaultLogger { static const char* name(); static const char* color(); }; +struct TraceLogger: public DefaultLogger { static const char* name(); static const char* color(); }; class LoggerBase { diff --git a/src/money.cpp b/src/money.cpp index 38a7894..6d3cd3a 100644 --- a/src/money.cpp +++ b/src/money.cpp @@ -23,7 +23,7 @@ Money::Money () { - + m_amount = 0; } Money::Money (bmp::mpf_float_50 x) diff --git a/src/money.h b/src/money.h index 3d63826..ae273b1 100644 --- a/src/money.h +++ b/src/money.h @@ -54,7 +54,7 @@ public: Money& operator/=(const Money& x); friend Money operator/(Money a, const Money& b) { a /= b; return a; } - Money operator-() { return Money(-m_amount); } + //Money operator-() { return Money(-m_amount); } friend bool operator==(const Money& a, const int& b); friend bool operator!=(const Money& a, const int& b) { return !operator==(a,b); } diff --git a/src/pf.cpp b/src/pf.cpp index 2f8b497..09dce7e 100644 --- a/src/pf.cpp +++ b/src/pf.cpp @@ -40,8 +40,8 @@ void PortfolioManager::doTestStuff() // Test user 1 emptyUserBalances(1); - ExchangesManager exchangesManager(1); - exchangesManager.analyzeUserAccounts(); +// ExchangesManager exchangesManager(1); +// exchangesManager.analyzeUserAccounts(); WalletsManager walletsManager(1); walletsManager.analyzeUserWallets(); @@ -49,6 +49,7 @@ void PortfolioManager::doTestStuff() displayUserBalances(1); // Test user 2 + /* emptyUserBalances(2); ExchangesManager exchangesManagerTwo(2); @@ -58,6 +59,7 @@ void PortfolioManager::doTestStuff() walletsManagerTwo.analyzeUserWallets(); displayUserBalances(2); + */ } void PortfolioManager::emptyUserBalances(int userId) @@ -155,6 +157,24 @@ string getCoinName(int coinId) case CPFM_COIN_ID_BTG: s = "BTG"; break; + case CPFM_COIN_ID_LPT: + s = "LPT"; + break; + case CPFM_COIN_ID_BCDT: + s = "BCDT"; + break; + case CPFM_COIN_ID_AIR: + s = "AIR"; + break; + case CPFM_COIN_ID_AAVE: + s = "AAVE"; + break; + case CPFM_COIN_ID_LEND: + s = "LEND"; + break; + case CPFM_COIN_ID_KNC: + s = "KNC"; + break; default: s = "Error!"; break; diff --git a/src/pf.h b/src/pf.h index e474951..85b3f9d 100644 --- a/src/pf.h +++ b/src/pf.h @@ -39,14 +39,31 @@ #include "log.h" #include "money.h" -#define CPFM_COIN_ID_BTC 1 -#define CPFM_COIN_ID_LTC 2 -#define CPFM_COIN_ID_EUR 3 -#define CPFM_COIN_ID_ICN 4 -#define CPFM_COIN_ID_ETC 5 -#define CPFM_COIN_ID_ETH 6 -#define CPFM_COIN_ID_BCH 7 -#define CPFM_COIN_ID_BTG 8 +#define CPFM_COIN_ID_BTC 1 +#define CPFM_COIN_ID_LTC 2 +#define CPFM_COIN_ID_EUR 3 +#define CPFM_COIN_ID_ICN 4 +#define CPFM_COIN_ID_ETC 5 +#define CPFM_COIN_ID_ETH 6 +#define CPFM_COIN_ID_BCH 7 +#define CPFM_COIN_ID_BTG 8 +#define CPFM_COIN_ID_KNC 9 +#define CPFM_COIN_ID_AIR 10 +#define CPFM_COIN_ID_LEND 11 +#define CPFM_COIN_ID_AAVE 12 +#define CPFM_COIN_ID_LPT 13 +#define CPFM_COIN_ID_BCDT 14 + +class Time +{ +public: + void setFromUnixTime(time_t unixTime) { m_unixTime = unixTime; } + const time_t toUnixTime() const { return m_unixTime; } + +private: + time_t m_unixTime; + //date::sys_time +}; namespace bmp = boost::multiprecision; namespace bfs = boost::filesystem; diff --git a/src/pricesource.cpp b/src/pricesource.cpp index 8241638..a2f0136 100644 --- a/src/pricesource.cpp +++ b/src/pricesource.cpp @@ -115,7 +115,7 @@ Money PriceSourceCryptoWatch::getCoinPriceFromData(int coinId) { Money x (jvalue["result"]["market:kraken:icnbtc"]); Money y (jvalue["result"]["market:kraken:btceur"]); - ldebug << "ICN is not quoted in EUR. BTC price is : " << x; + // ldebug << "ICN is not quoted in EUR. BTC price is : " << x; m = x*y; break; } @@ -141,10 +141,51 @@ Money PriceSourceCryptoWatch::getCoinPriceFromData(int coinId) { Money x (jvalue["result"]["market:bitfinex:btgbtc"]); Money y (jvalue["result"]["market:kraken:btceur"]); - ldebug << "BTG is not quoted in EUR. BTC price is : " << x; + // ldebug << "BTG is not quoted in EUR. BTC price is : " << x; m = x*y; break; } + case CPFM_COIN_ID_KNC: + { + Money x (jvalue["result"]["market:binance:kncbtc"]); + Money y (jvalue["result"]["market:kraken:btceur"]); + // ldebug << "KNC is not quoted in EUR. BTC price is : " << x; + m = x*y; + break; + } + case CPFM_COIN_ID_AIR: + { + // ldebug << "AIR is not quoted. Returning 0."; + m = 0; + break; + } + case CPFM_COIN_ID_LEND: + { + Money x (jvalue["result"]["market:kraken:aaveeur"]); + // ldebug << "LEND is not quoted. Using AAVE."; + m = x/100; + break; + } + case CPFM_COIN_ID_AAVE: + { + Money x (jvalue["result"]["market:kraken:aaveeur"]); + m = x; + break; + } + case CPFM_COIN_ID_LPT: + { + Money x (jvalue["result"]["market:poloniex:lptbtc"]); + Money y (jvalue["result"]["market:kraken:btceur"]); + // ldebug << "LPT is not quoted in EUR. BTC price is : " << x; + m = x*y; + break; + } + case CPFM_COIN_ID_BCDT: + { + // ldebug << "BCDT is not quoted. Returning 0."; + m = 0; + break; + } default: { m = 0; diff --git a/src/sql.h b/src/sql.h index f717d63..3420c17 100644 --- a/src/sql.h +++ b/src/sql.h @@ -216,9 +216,32 @@ namespace TableWallets_ }; using _traits = sqlpp::make_traits; }; + + struct Active + { + struct _alias_t + { + static constexpr const char _literal[] = "active"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T active; + T& operator()() + { + return active; + } + const T& operator()() const + { + return active; + } + }; + }; + using _traits = sqlpp::make_traits; + }; } -struct TableWallets : sqlpp::table_t +struct TableWallets : sqlpp::table_t { using _value_type = sqlpp::no_value_t; struct _alias_t @@ -430,92 +453,138 @@ namespace TableWalletsTx_ using _traits = sqlpp::make_traits; }; - struct BlockchainTxId + struct Amount { struct _alias_t { - static constexpr const char _literal[] = "blockchain_tx_id"; + static constexpr const char _literal[] = "amount"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T blockchain_tx_id; + T amount; T& operator()() { - return blockchain_tx_id; + return amount; } const T& operator()() const { - return blockchain_tx_id; + return amount; } }; }; - using _traits = sqlpp::make_traits; + using _traits = sqlpp::make_traits; }; - struct Amount + struct Fee { struct _alias_t { - static constexpr const char _literal[] = "amount"; + static constexpr const char _literal[] = "fee"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T amount; + T fee; T& operator()() { - return amount; + return fee; } const T& operator()() const { - return amount; + return fee; } }; }; using _traits = sqlpp::make_traits; }; - struct Fee + struct AmountCoinId { struct _alias_t { - static constexpr const char _literal[] = "fee"; + static constexpr const char _literal[] = "amount_coin_id"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T fee; + T amount_coin_id; T& operator()() { - return fee; + return amount_coin_id; } const T& operator()() const { - return fee; + return amount_coin_id; } }; }; - using _traits = sqlpp::make_traits; + using _traits = sqlpp::make_traits; + }; + + struct FeeCoinId + { + struct _alias_t + { + static constexpr const char _literal[] = "fee_coin_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T fee_coin_id; + T& operator()() + { + return fee_coin_id; + } + const T& operator()() const + { + return fee_coin_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct UnixTime + { + struct _alias_t + { + static constexpr const char _literal[] = "unix_time"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T unix_time; + T& operator()() + { + return unix_time; + } + const T& operator()() const + { + return unix_time; + } + }; + }; + using _traits = sqlpp::make_traits; }; - struct Timestamp + struct OperationType { struct _alias_t { - static constexpr const char _literal[] = "timestamp"; + static constexpr const char _literal[] = "operation_type"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T timestamp; + T operation_type; T& operator()() { - return timestamp; + return operation_type; } const T& operator()() const { - return timestamp; + return operation_type; } }; }; @@ -523,7 +592,7 @@ namespace TableWalletsTx_ }; } -struct TableWalletsTx : sqlpp::table_t +struct TableWalletsTx : sqlpp::table_t { using _value_type = sqlpp::no_value_t; struct _alias_t @@ -546,6 +615,691 @@ struct TableWalletsTx : sqlpp::table_t; + template + struct _member_t + { + T raw_tx_id; + T& operator()() + { + return raw_tx_id; + } + const T& operator()() const + { + return raw_tx_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct WalletId + { + struct _alias_t + { + static constexpr const char _literal[] = "wallet_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T wallet_id; + T& operator()() + { + return wallet_id; + } + const T& operator()() const + { + return wallet_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; +} + +struct TableWalletsBtcRawTx : sqlpp::table_t +{ + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "wallets_btc_raw_tx"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T wallets_btc_raw_tx; + T& operator()() + { + return wallets_btc_raw_tx; + } + const T& operator()() const + { + return wallets_btc_raw_tx; + } + }; + }; +}; + +namespace TableWalletsEthRawTx_ +{ + struct RawTxId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_id; + T& operator()() + { + return raw_tx_id; + } + const T& operator()() const + { + return raw_tx_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct WalletId + { + struct _alias_t + { + static constexpr const char _literal[] = "wallet_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T wallet_id; + T& operator()() + { + return wallet_id; + } + const T& operator()() const + { + return wallet_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; +} + +struct TableWalletsEthRawTx : sqlpp::table_t +{ + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "wallets_eth_raw_tx"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T wallets_eth_raw_tx; + T& operator()() + { + return wallets_eth_raw_tx; + } + const T& operator()() const + { + return wallets_eth_raw_tx; + } + }; + }; +}; + +namespace TableBlockchainBtcRawTx_ +{ + struct RawTxId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_id; + T& operator()() + { + return raw_tx_id; + } + const T& operator()() const + { + return raw_tx_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct BlockchainId + { + struct _alias_t + { + static constexpr const char _literal[] = "blockchain_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T blockchain_id; + T& operator()() + { + return blockchain_id; + } + const T& operator()() const + { + return blockchain_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct Hash + { + struct _alias_t + { + static constexpr const char _literal[] = "hash"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T hash; + T& operator()() + { + return hash; + } + const T& operator()() const + { + return hash; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct UnixTime + { + struct _alias_t + { + static constexpr const char _literal[] = "unix_time"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T unix_time; + T& operator()() + { + return unix_time; + } + const T& operator()() const + { + return unix_time; + } + }; + }; + using _traits = sqlpp::make_traits; + }; +} + +struct TableBlockchainBtcRawTx : sqlpp::table_t +{ + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "blockchain_btc_raw_tx"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T blockchain_btc_raw_tx; + T& operator()() + { + return blockchain_btc_raw_tx; + } + const T& operator()() const + { + return blockchain_btc_raw_tx; + } + }; + }; +}; + +namespace TableBlockchainBtcRawTxDetails_ +{ + struct RawTxDetailId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_detail_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_detail_id; + T& operator()() + { + return raw_tx_detail_id; + } + const T& operator()() const + { + return raw_tx_detail_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct RawTxId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_id; + T& operator()() + { + return raw_tx_id; + } + const T& operator()() const + { + return raw_tx_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct Amount + { + struct _alias_t + { + static constexpr const char _literal[] = "amount"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T amount; + T& operator()() + { + return amount; + } + const T& operator()() const + { + return amount; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct Address + { + struct _alias_t + { + static constexpr const char _literal[] = "address"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T address; + T& operator()() + { + return address; + } + const T& operator()() const + { + return address; + } + }; + }; + using _traits = sqlpp::make_traits; + }; +} + +struct TableBlockchainBtcRawTxDetails : sqlpp::table_t +{ + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "blockchain_btc_raw_tx_details"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T blockchain_btc_raw_tx_details; + T& operator()() + { + return blockchain_btc_raw_tx_details; + } + const T& operator()() const + { + return blockchain_btc_raw_tx_details; + } + }; + }; +}; + +namespace TableBlockchainEthRawTx_ +{ + struct RawTxId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_id; + T& operator()() + { + return raw_tx_id; + } + const T& operator()() const + { + return raw_tx_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct BlockchainId + { + struct _alias_t + { + static constexpr const char _literal[] = "blockchain_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T blockchain_id; + T& operator()() + { + return blockchain_id; + } + const T& operator()() const + { + return blockchain_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct Hash + { + struct _alias_t + { + static constexpr const char _literal[] = "hash"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T hash; + T& operator()() + { + return hash; + } + const T& operator()() const + { + return hash; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct UnixTime + { + struct _alias_t + { + static constexpr const char _literal[] = "unix_time"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T unix_time; + T& operator()() + { + return unix_time; + } + const T& operator()() const + { + return unix_time; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct Fee + { + struct _alias_t + { + static constexpr const char _literal[] = "fee"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T fee; + T& operator()() + { + return fee; + } + const T& operator()() const + { + return fee; + } + }; + }; + using _traits = sqlpp::make_traits; + }; +} + +struct TableBlockchainEthRawTx : sqlpp::table_t +{ + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "blockchain_eth_raw_tx"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T blockchain_eth_raw_tx; + T& operator()() + { + return blockchain_eth_raw_tx; + } + const T& operator()() const + { + return blockchain_eth_raw_tx; + } + }; + }; +}; + +namespace TableBlockchainEthRawTxDetails_ +{ + struct RawTxDetailId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_detail_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_detail_id; + T& operator()() + { + return raw_tx_detail_id; + } + const T& operator()() const + { + return raw_tx_detail_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct RawTxId + { + struct _alias_t + { + static constexpr const char _literal[] = "raw_tx_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T raw_tx_id; + T& operator()() + { + return raw_tx_id; + } + const T& operator()() const + { + return raw_tx_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct AddressFrom + { + struct _alias_t + { + static constexpr const char _literal[] = "address_from"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T address_from; + T& operator()() + { + return address_from; + } + const T& operator()() const + { + return address_from; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct AddressTo + { + struct _alias_t + { + static constexpr const char _literal[] = "address_to"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T address_to; + T& operator()() + { + return address_to; + } + const T& operator()() const + { + return address_to; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct AmountCoinId + { + struct _alias_t + { + static constexpr const char _literal[] = "amount_coin_id"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T amount_coin_id; + T& operator()() + { + return amount_coin_id; + } + const T& operator()() const + { + return amount_coin_id; + } + }; + }; + using _traits = sqlpp::make_traits; + }; + + struct Amount + { + struct _alias_t + { + static constexpr const char _literal[] = "amount"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T amount; + T& operator()() + { + return amount; + } + const T& operator()() const + { + return amount; + } + }; + }; + using _traits = sqlpp::make_traits; + }; +}; + +struct TableBlockchainEthRawTxDetails : sqlpp::table_t +{ + using _value_type = sqlpp::no_value_t; + struct _alias_t + { + static constexpr const char _literal[] = "blockchain_eth_raw_tx_details"; + using _name_t = sqlpp::make_char_sequence; + template + struct _member_t + { + T blockchain_eth_raw_tx_details; + T& operator()() + { + return blockchain_eth_raw_tx_details; + } + const T& operator()() const + { + return blockchain_eth_raw_tx_details; + } + }; + }; +}; + namespace TableExchangesLedgers_ { struct LedgerId @@ -663,23 +1417,23 @@ namespace TableExchangesLedgers_ using _traits = sqlpp::make_traits; }; - struct Timestamp + struct UnixTime { struct _alias_t { - static constexpr const char _literal[] = "timestamp"; + static constexpr const char _literal[] = "unix_time"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T timestamp; + T unix_time; T& operator()() { - return timestamp; + return unix_time; } const T& operator()() const { - return timestamp; + return unix_time; } }; }; @@ -756,7 +1510,7 @@ namespace TableExchangesLedgers_ }; } -struct TableExchangesLedgers : sqlpp::table_t +struct TableExchangesLedgers : sqlpp::table_t { using _value_type = sqlpp::no_value_t; struct _alias_t @@ -919,23 +1673,23 @@ namespace TableExchangesTrades_ using _traits = sqlpp::make_traits; }; - struct Timestamp + struct UnixTime { struct _alias_t { - static constexpr const char _literal[] = "timestamp"; + static constexpr const char _literal[] = "unix_time"; using _name_t = sqlpp::make_char_sequence; template struct _member_t { - T timestamp; + T unix_time; T& operator()() { - return timestamp; + return unix_time; } const T& operator()() const { - return timestamp; + return unix_time; } }; }; @@ -1104,7 +1858,7 @@ namespace TableExchangesTrades_ }; } -struct TableExchangesTrades : sqlpp::table_t +struct TableExchangesTrades : sqlpp::table_t { using _value_type = sqlpp::no_value_t; struct _alias_t diff --git a/src/wallet.cpp b/src/wallet.cpp index 80af3d6..4221fe9 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -19,9 +19,32 @@ */ #include "wallet.h" -#include "wallets/btc.h" -#include "wallets/bch.h" -#include "wallets/eth.h" +#include "wallets/wallet_btc.h" +#include "wallets/wallet_bch.h" +#include "wallets/wallet_eth.h" + +string getBlockchainName(int blockchainId) +{ + string s; + + switch(blockchainId) + { + case CPFM_BLOCKCHAIN_ID_BTC: + s = "BTC"; + break; + case CPFM_BLOCKCHAIN_ID_BCH: + s = "BCH"; + break; + case CPFM_BLOCKCHAIN_ID_ETH: + s = "ETH"; + break; + default: + s = "Error!"; + break; + } + + return s; +} WalletsManager::WalletsManager(int userId) { @@ -38,38 +61,34 @@ void WalletsManager::analyzeUserWallets() mysql::connection db(getMysqlConfig()); const auto wallets = TableWallets{}; - // Identify coins, we need one analyzer object per coin. - list handlers; - for (const auto& row: db.run(select(wallets.type_id).from(wallets).where(wallets.user_id == m_userId).group_by(wallets.type_id))) + list userWallets; + for (const auto& row: db.run(select(wallets.type_id,wallets.wallet_id).from(wallets).where(wallets.user_id == m_userId and wallets.active == true))) { - int typeId = row.type_id; - WalletHandler* walletHandler; - switch (typeId) + Wallet* wallet; + switch (row.type_id) { case CPFM_WALLET_TYPE_ID_BTC: - walletHandler = new WalletHandlerBTC(m_userId); - handlers.push_back(walletHandler); + wallet = new WalletBTC(row.wallet_id); + userWallets.push_back(wallet); break; case CPFM_WALLET_TYPE_ID_BCH: - walletHandler = new WalletHandlerBCH(m_userId); - handlers.push_back(walletHandler); + wallet = new WalletBCH(row.wallet_id); + userWallets.push_back(wallet); break; case CPFM_WALLET_TYPE_ID_ETH: - walletHandler = new WalletHandlerETH(m_userId); - handlers.push_back(walletHandler); + wallet = new WalletETH(row.wallet_id); + userWallets.push_back(wallet); break; default: - lerr << "Unsupported wallet type: " << typeId; + lerr << "Unsupported wallet type: " << row.type_id; break; } } - emptyWalletsTx(); - - for (auto const& handler: handlers) + for (auto const& wallet: userWallets) { - handler->analyzeUserWallets(); - delete handler; + wallet->update(); + delete wallet; } } @@ -85,20 +104,16 @@ void WalletsManager::emptyWalletsTx() userWallets.push_back(row.wallet_id); } - for (const auto& id: userWallets) + for (const auto& idWallet: userWallets) { - db.run(remove_from(wallets_tx).where(wallets_tx.wallet_id == id)); + list userWalletTx; + for (const auto& row: db.run(select(wallets_tx.tx_id).from(wallets_tx).where(wallets_tx.wallet_id == idWallet))) + { + userWalletTx.push_back(row.tx_id); + } + + db.run(remove_from(wallets_tx).where(wallets_tx.wallet_id == idWallet)); } linfo << "Emptied Wallets Tx DB table for user " << m_userId; } - -WalletHandler::WalletHandler(int userId) -{ - m_userId = userId; -} - -WalletHandler::~WalletHandler() -{ - -} diff --git a/src/wallet.h b/src/wallet.h index 970566d..4f746de 100644 --- a/src/wallet.h +++ b/src/wallet.h @@ -27,6 +27,17 @@ #define CPFM_WALLET_TYPE_ID_ETH 2 #define CPFM_WALLET_TYPE_ID_BCH 3 +#define CPFM_WALLET_OPERATION_UNKNOWN 0 +#define CPFM_WALLET_OPERATION_MINING_MINTED 1 +#define CPFM_WALLET_OPERATION_MINING_POOL 2 +#define CPFM_WALLET_OPERATION_TRANSFERT 3 + +#define CPFM_BLOCKCHAIN_ID_BTC 1 +#define CPFM_BLOCKCHAIN_ID_BCH 2 +#define CPFM_BLOCKCHAIN_ID_ETH 3 + +string getBlockchainName(int blockchainId); + class WalletsManager { public: @@ -41,16 +52,15 @@ private: int m_userId; }; -class WalletHandler +class Wallet { public: - WalletHandler(int userId); - virtual ~WalletHandler(); + Wallet (int walletId) { m_walletId = walletId; } - virtual void analyzeUserWallets() = 0; + virtual void update() = 0; protected: - int m_userId; + int m_walletId; }; #endif // CPFM_WALLET_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/bch.cpp b/src/wallets/bch.cpp deleted file mode 100644 index d4c71de..0000000 --- a/src/wallets/bch.cpp +++ /dev/null @@ -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 . - * - */ - -#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> userWallets; - list 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 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 walletAddresses, list 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 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 inputAddrList; - list 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)); - } -} diff --git a/src/wallets/bch.h b/src/wallets/bch.h deleted file mode 100644 index ddbd080..0000000 --- a/src/wallets/bch.h +++ /dev/null @@ -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 . - * - */ - -#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 walletAddresses, list userAddresses); - void analyzeUserWalletData(int walletId); -}; - -class AddressHandlerBCH -{ -public: - AddressHandlerBCH(string address, int walletId); - virtual ~AddressHandlerBCH(); - - void getAddressData(); - void addAddressDataToDB(); - void setWalletAddresses(list walletAddresses) { m_walletAddresses = walletAddresses; } - void setUserAddresses(list userAddresses) { m_userAddresses = userAddresses; } - -private: - void getBlockchainAddressData(); - void addCachedAddressDataToDB(); - void addAddressDataJSONToDB(json::value jvalue); - string getCacheFilename(); - - string m_address; - int m_walletId; - list m_userAddresses; - list m_walletAddresses; -}; - -#endif // CPFM_WALLET_BTC_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/btc.cpp b/src/wallets/btc.cpp deleted file mode 100644 index 8b3c1da..0000000 --- a/src/wallets/btc.cpp +++ /dev/null @@ -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 . - * - */ - -#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> userWallets; - list 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 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 walletAddresses, list 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 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 inputAddrList; - list outputAddrList; - - for (int j=0;j. - * - */ - -#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 walletAddresses, list userAddresses); - void analyzeUserWalletData(int walletId); -}; - -class AddressHandlerBTC -{ -public: - AddressHandlerBTC(string address, int walletId); - virtual ~AddressHandlerBTC(); - - void getAddressData(); - void addAddressDataToDB(); - void setWalletAddresses(list walletAddresses) { m_walletAddresses = walletAddresses; } - void setUserAddresses(list userAddresses) { m_userAddresses = userAddresses; } - -private: - void getBlockchainAddressData(); - void addCachedAddressDataToDB(); - void addAddressDataJSONToDB(json::value jvalue); - string getCacheFilename(); - - string m_address; - int m_walletId; - list m_userAddresses; - list m_walletAddresses; -}; - -#endif // CPFM_WALLET_BTC_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/eth.cpp b/src/wallets/eth.cpp deleted file mode 100644 index 9809db8..0000000 --- a/src/wallets/eth.cpp +++ /dev/null @@ -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 . - * - */ - -#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> userWallets; - list 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 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 walletAddresses, list 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 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 inputAddrList; - list outputAddrList; - - for (int j=0;j. - * - */ - -#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 walletAddresses, list userAddresses); - void analyzeUserWalletData(int walletId); -}; - -class AddressHandlerETH -{ -public: - AddressHandlerETH(string address, int walletId); - virtual ~AddressHandlerETH(); - - void getAddressData(); - void addAddressDataToDB(); - void setWalletAddresses(list walletAddresses) { m_walletAddresses = walletAddresses; } - void setUserAddresses(list userAddresses) { m_userAddresses = userAddresses; } - -private: - void getBlockchainAddressData(); - void addCachedAddressDataToDB(); - void addAddressDataJSONToDB(json::value jvalue); - string getCacheFilename(); - - string m_address; - int m_walletId; - list m_userAddresses; - list m_walletAddresses; -}; - -#endif // CPFM_WALLET_ETH_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/wallet_bch.cpp b/src/wallets/wallet_bch.cpp new file mode 100644 index 0000000..91c4fbd --- /dev/null +++ b/src/wallets/wallet_bch.cpp @@ -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 . + * + */ + +#include "wallets/wallet_bch.h" + +list WalletBCH::getTxDetailsListForAddresses(list addresses) +{ + BlockchainDataSourceBCH src; + return src.getTxDetailsListForAddresses(addresses); +} diff --git a/src/wallets/wallet_bch.h b/src/wallets/wallet_bch.h new file mode 100644 index 0000000..096c9c4 --- /dev/null +++ b/src/wallets/wallet_bch.h @@ -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 . + * + */ + +#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 getTxDetailsListForAddresses(list addresses); +}; + +#endif // CPFM_WALLET_BCH_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/wallet_btc.cpp b/src/wallets/wallet_btc.cpp new file mode 100644 index 0000000..99b3ae7 --- /dev/null +++ b/src/wallets/wallet_btc.cpp @@ -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 . + * + */ + +#include "wallets/wallet_btc.h" + +list WalletBTC::getTxDetailsListForAddresses(list addresses) +{ + BlockchainDataSourceBTC src; + return src.getTxDetailsListForAddresses(addresses); +} diff --git a/src/wallets/wallet_btc.h b/src/wallets/wallet_btc.h new file mode 100644 index 0000000..b445e92 --- /dev/null +++ b/src/wallets/wallet_btc.h @@ -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 . + * + */ + +#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 getTxDetailsListForAddresses(list addresses); +}; + +#endif // CPFM_WALLET_BTC_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/wallet_eth.cpp b/src/wallets/wallet_eth.cpp new file mode 100644 index 0000000..69734fe --- /dev/null +++ b/src/wallets/wallet_eth.cpp @@ -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 . + * + */ + +#include "wallets/wallet_eth.h" + +list WalletETH::getTxDetailsListForAddress(string address) +{ + BlockchainDataSourceETH src; + return src.getTxDetailsListForAddress(address); +} diff --git a/src/wallets/wallet_eth.h b/src/wallets/wallet_eth.h new file mode 100644 index 0000000..0d0c35e --- /dev/null +++ b/src/wallets/wallet_eth.h @@ -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 . + * + */ + +#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 getTxDetailsListForAddress(string address); +}; + +#endif // CPFM_WALLET_ETH_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/wallet_type_btc.cpp b/src/wallets/wallet_type_btc.cpp new file mode 100644 index 0000000..c857e05 --- /dev/null +++ b/src/wallets/wallet_type_btc.cpp @@ -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 . + * + */ + +#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 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 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& 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 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 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 walletAddresses, const list& 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 inputAddresses; + list 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() + )); +} diff --git a/src/wallets/wallet_type_btc.h b/src/wallets/wallet_type_btc.h new file mode 100644 index 0000000..3bfdde3 --- /dev/null +++ b/src/wallets/wallet_type_btc.h @@ -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 . + * + */ + +#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& l); + void addTxDetailsListToDB(const int walletId, const list walletAddresses, const list& l); + list getTxDetailsListFromRawDB(const int walletId); + void updateBalanceFromTxDetailsInDB(int walletId); + +protected: + virtual list getTxDetailsListForAddresses(list addresses) = 0; + + int m_coinId; + int m_walletTypeId; + int m_blockchainId; +}; + +#endif // CPFM_WALLET_TYPE_BTC_H_INCLUDED \ No newline at end of file diff --git a/src/wallets/wallet_type_eth.cpp b/src/wallets/wallet_type_eth.cpp new file mode 100644 index 0000000..8fd62e8 --- /dev/null +++ b/src/wallets/wallet_type_eth.cpp @@ -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 . + * + */ + +#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 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& 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 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 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& 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 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() + )); + } +} diff --git a/src/wallets/wallet_type_eth.h b/src/wallets/wallet_type_eth.h new file mode 100644 index 0000000..69b12c7 --- /dev/null +++ b/src/wallets/wallet_type_eth.h @@ -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 . + * + */ + +#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& l); + void addTxDetailsListToDB(const int walletId, string address, const list& l); + list getTxDetailsListFromRawDB(const int walletId); + void updateBalanceFromTxDetailsInDB(int walletId); + +protected: + virtual list getTxDetailsListForAddress(string address) = 0; + + int m_walletTypeId; + int m_blockchainId; + int m_coinId; +}; + +#endif // CPFM_WALLET_TYPE_ETH_H_INCLUDED \ No newline at end of file