From a41b13f3b2639674a09b5944f459ea2836408b2d Mon Sep 17 00:00:00 2001 From: evilny0 Date: Sun, 21 Feb 2021 22:50:11 +0100 Subject: [PATCH] Moved data sources out of wallet class. Added Blockchair support for BTC/BCH. Support for multiple operations for each ETH tx, introducing support for ERC-20. Separated DB data to "raw" and "processed". The goal is to be able to wipe processed data (so we can process again with updated rules) without requiring to analyze again input files. Updated SQL schema to match changes. --- CMakeLists.txt | 1 + .../Modules/Findsqlpp11-connector-mysql.cmake | 2 +- sql/mysql.schema.sql | 78 +- src/CMakeLists.txt | 3 +- src/datasources/blockchain_info.cpp | 137 +++ src/datasources/blockchain_info.h | 39 + src/datasources/blockchair.cpp | 245 ++++++ src/datasources/blockchair.h | 67 ++ src/datasources/datasource.cpp | 77 ++ src/datasources/datasource.h | 71 ++ src/datasources/etherscan.cpp | 323 +++++++ src/datasources/etherscan.h | 42 + src/exchanges/kraken.cpp | 4 +- src/log.cpp | 4 +- src/log.h | 14 +- src/money.cpp | 2 +- src/money.h | 2 +- src/pf.cpp | 24 +- src/pf.h | 33 +- src/pricesource.cpp | 45 +- src/sql.h | 826 +++++++++++++++++- src/wallet.cpp | 81 +- src/wallet.h | 20 +- src/wallets/bch.cpp | 316 ------- src/wallets/bch.h | 63 -- src/wallets/btc.cpp | 332 ------- src/wallets/btc.h | 63 -- src/wallets/eth.cpp | 353 -------- src/wallets/eth.h | 63 -- src/wallets/wallet_bch.cpp | 27 + src/wallets/wallet_bch.h | 36 + src/wallets/wallet_btc.cpp | 27 + src/wallets/wallet_btc.h | 36 + src/wallets/wallet_eth.cpp | 27 + src/wallets/wallet_eth.h | 36 + src/wallets/wallet_type_btc.cpp | 324 +++++++ src/wallets/wallet_type_btc.h | 48 + src/wallets/wallet_type_eth.cpp | 248 ++++++ src/wallets/wallet_type_eth.h | 48 + 39 files changed, 2891 insertions(+), 1296 deletions(-) create mode 100644 src/datasources/blockchain_info.cpp create mode 100644 src/datasources/blockchain_info.h create mode 100644 src/datasources/blockchair.cpp create mode 100644 src/datasources/blockchair.h create mode 100644 src/datasources/datasource.cpp create mode 100644 src/datasources/datasource.h create mode 100644 src/datasources/etherscan.cpp create mode 100644 src/datasources/etherscan.h delete mode 100644 src/wallets/bch.cpp delete mode 100644 src/wallets/bch.h delete mode 100644 src/wallets/btc.cpp delete mode 100644 src/wallets/btc.h delete mode 100644 src/wallets/eth.cpp delete mode 100644 src/wallets/eth.h create mode 100644 src/wallets/wallet_bch.cpp create mode 100644 src/wallets/wallet_bch.h create mode 100644 src/wallets/wallet_btc.cpp create mode 100644 src/wallets/wallet_btc.h create mode 100644 src/wallets/wallet_eth.cpp create mode 100644 src/wallets/wallet_eth.h create mode 100644 src/wallets/wallet_type_btc.cpp create mode 100644 src/wallets/wallet_type_btc.h create mode 100644 src/wallets/wallet_type_eth.cpp create mode 100644 src/wallets/wallet_type_eth.h 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