You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

324 lines
12 KiB

/*
* Copyright (c) 2021, evilny0
*
* This file is part of cpfm.
*
* cpfm is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* cpm is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with cpfm. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "wallets/wallet_type_btc.h"
SQLPP_ALIAS_PROVIDER(max_raw_tx_id);
void WalletTypeBTC::update()
{
if (!m_walletId)
{
lerr << "Wallet id not defined. Ignoring update.";
}
linfo << "Analyzing " << getCoinName(m_coinId) << " wallet " << m_walletId;
mysql::connection db(getMysqlConfig());
const auto wallets = TableWallets{};
const auto wallets_addresses = TableWalletsAddresses{};
const auto wallets_tx = TableWalletsTx{};
// Load wallet addresses
list<string> addresses;
for (const auto& row: db.run(select(wallets.wallet_id,wallets.type_id,wallets_addresses.address).from(wallets.cross_join(wallets_addresses)).where(wallets.wallet_id == wallets_addresses.wallet_id and wallets.wallet_id == m_walletId and wallets.type_id == m_walletTypeId)))
{
addresses.push_back(row.address);
}
// Get wallet tx data from data source and insert it in the DB raw tables.
list<BlockchainTxDetailsTypeBTC> l;
l = getTxDetailsListForAddresses(addresses);
addTxDetailsListToRawDB(m_walletId,l);
// Load tx data from DB raw tables, analyze data, and insert it in DB tables.
l = getTxDetailsListFromRawDB(m_walletId);
db(remove_from(wallets_tx).where(wallets_tx.wallet_id == m_walletId));
addTxDetailsListToDB(m_walletId,addresses,l);
updateBalanceFromTxDetailsInDB(m_walletId);
}
void WalletTypeBTC::addTxDetailsListToRawDB(const int walletId, const list<BlockchainTxDetailsTypeBTC>& l)
{
mysql::connection db(getMysqlConfig());
const auto wallets_btc_raw_tx = TableWalletsBtcRawTx{};
const auto blockchain_btc_raw_tx = TableBlockchainBtcRawTx{};
const auto blockchain_btc_raw_tx_details = TableBlockchainBtcRawTxDetails{};
linfo << "Clearing tx raw data links for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
const auto result = db(remove_from(wallets_btc_raw_tx).where(wallets_btc_raw_tx.wallet_id == walletId));
linfo << "Now adding raw data to DB for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
for (const auto& tx: l)
{
string hash = tx.hash;
ldebug << "Adding raw data to DB for tx " << hash << ".";
const auto result = db(select(blockchain_btc_raw_tx.raw_tx_id).from(blockchain_btc_raw_tx).where(blockchain_btc_raw_tx.hash == hash and blockchain_btc_raw_tx.blockchain_id == m_blockchainId));
if (!result.empty())
{
ldebug << "Raw data for tx " << hash << " is already in DB. Ignoring.";
db(insert_into(wallets_btc_raw_tx).set(
wallets_btc_raw_tx.raw_tx_id = result.front().raw_tx_id,
wallets_btc_raw_tx.wallet_id = walletId
));
}
else
{
db(insert_into(blockchain_btc_raw_tx).set(
blockchain_btc_raw_tx.hash = hash,
blockchain_btc_raw_tx.unix_time = tx.time.toUnixTime(),
blockchain_btc_raw_tx.blockchain_id = m_blockchainId
));
int rawTxId = db(select(max(blockchain_btc_raw_tx.raw_tx_id).as(max_raw_tx_id)).from(blockchain_btc_raw_tx).unconditionally()).front().max_raw_tx_id;
ldebug << "Raw tax id created : " << rawTxId << ".";
ldebug << "Adding inputs raw data to DB for tx " << hash << ".";
for (const auto& input : tx.inputs)
{
// Inputs are positive.
Money amount = input.second;
db(insert_into(blockchain_btc_raw_tx_details).set(
blockchain_btc_raw_tx_details.raw_tx_id = rawTxId,
blockchain_btc_raw_tx_details.amount = amount.toBoostMpf(),
blockchain_btc_raw_tx_details.address = input.first
));
}
ldebug << "Adding outputs raw data to DB for tx " << hash << ".";
for (const auto& output : tx.outputs)
{
// Outputs are negative.
Money amount = 0-output.second;
db(insert_into(blockchain_btc_raw_tx_details).set(
blockchain_btc_raw_tx_details.raw_tx_id = rawTxId,
blockchain_btc_raw_tx_details.amount = amount.toBoostMpf(),
blockchain_btc_raw_tx_details.address = output.first
));
}
db(insert_into(wallets_btc_raw_tx).set(
wallets_btc_raw_tx.raw_tx_id = rawTxId,
wallets_btc_raw_tx.wallet_id = walletId
));
}
}
}
list<BlockchainTxDetailsTypeBTC> WalletTypeBTC::getTxDetailsListFromRawDB(const int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_btc_raw_tx = TableWalletsBtcRawTx{};
const auto blockchain_btc_raw_tx = TableBlockchainBtcRawTx{};
const auto blockchain_btc_raw_tx_details = TableBlockchainBtcRawTxDetails{};
linfo << "Retrieving data from raw DB for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
list<BlockchainTxDetailsTypeBTC> l;
for (const auto& rowTx: db.run(select(blockchain_btc_raw_tx.raw_tx_id,blockchain_btc_raw_tx.hash,blockchain_btc_raw_tx.unix_time).from(blockchain_btc_raw_tx.cross_join(wallets_btc_raw_tx)).where(blockchain_btc_raw_tx.raw_tx_id == wallets_btc_raw_tx.raw_tx_id and wallets_btc_raw_tx.wallet_id == walletId)))
{
ldebug << "Retrieving data from raw DB for tx " << rowTx.hash << ".";
BlockchainTxDetailsTypeBTC tx;
tx.hash = rowTx.hash;
tx.time.setFromUnixTime(rowTx.unix_time);
ldebug << "Retrieving inputs/outputs data from raw DB for tx " << rowTx.hash << ".";
for (const auto& row: db.run(select(blockchain_btc_raw_tx_details.amount,blockchain_btc_raw_tx_details.address).from(blockchain_btc_raw_tx_details).where(blockchain_btc_raw_tx_details.raw_tx_id == rowTx.raw_tx_id)))
{
Money m (row.amount);
if (m >= 0)
tx.inputs[row.address] = m;
else
tx.outputs[row.address] = 0-m;
}
l.push_back(tx);
}
return l;
}
void WalletTypeBTC::addTxDetailsListToDB(const int walletId, const list<string> walletAddresses, const list<BlockchainTxDetailsTypeBTC>& l)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
linfo << "Now adding data to DB for " << getCoinName(m_coinId) << " wallet " << walletId << ".";
for (const auto& tx: l)
{
Money txTotalOutputAmount;
Money txTotalInputAmount;
Money txWalletOutputAmount;
Money txWalletInputAmount;
bool bToWallet = false;
bool bFromWallet = false;
list<string> inputAddresses;
list<string> outputAddresses;
ltrace << "Now adding data to DB for tx " << tx.hash << ".";
for (const auto& input : tx.inputs)
{
//ltrace << "Input : " << input.first << ", amount : " << input.second;
txTotalInputAmount += input.second;
if (std::find(walletAddresses.begin(), walletAddresses.end(), input.first) != walletAddresses.end())
{
bFromWallet = true;
txWalletInputAmount += input.second;
}
else
{
inputAddresses.push_back(input.first);
}
}
for (const auto& output : tx.outputs)
{
//ltrace << "Output : " << output.first << ", amount : " << output.second;
txTotalOutputAmount += output.second;
if (std::find(walletAddresses.begin(), walletAddresses.end(), output.first) != walletAddresses.end())
{
bToWallet = true;
txWalletOutputAmount += output.second;
}
else
{
outputAddresses.push_back(output.first);
}
}
if (bFromWallet)
{
// In case at least one address was in the inputs, then ALL other inputs should be.
for (auto const& addr: inputAddresses)
{
if (std::find(walletAddresses.begin(), walletAddresses.end(), addr) == walletAddresses.end())
{
lwarn << "User configuration is missing this address: " << addr;
}
}
// In case at least one address was in the inputs, the outputs might be either another wallet for this user,
// or one of the wallet addresses might be missing from the configuration.
for (auto const& addr: outputAddresses)
{
if (std::find(walletAddresses.begin(), walletAddresses.end(), addr) == walletAddresses.end())
{
linfo << "Potential other address to analyze: " << L_Cyan << addr;
}
}
}
Money amount;
Money fee;
if (bFromWallet)
{
// The fee only applies in case this tx was initiated by the wallet
fee = txTotalInputAmount - txTotalOutputAmount;
amount = txWalletOutputAmount - txWalletInputAmount + fee;
}
else
{
amount = txWalletOutputAmount;
fee = 0;
}
db(insert_into(wallets_tx).set(
wallets_tx.wallet_id = walletId,
wallets_tx.amount = amount.toBoostMpf(),
wallets_tx.fee = fee.toBoostMpf(),
wallets_tx.unix_time = tx.time.toUnixTime(),
wallets_tx.amount_coin_id = m_coinId,
wallets_tx.fee_coin_id = m_coinId,
wallets_tx.operation_type = CPFM_WALLET_OPERATION_UNKNOWN
));
}
}
void WalletTypeBTC::updateBalanceFromTxDetailsInDB(int walletId)
{
mysql::connection db(getMysqlConfig());
const auto wallets_tx = TableWalletsTx{};
const auto wallets_balances = TableWalletsBalances{};
ldebug << "------------------------------------------------------------";
Money walletBalance = 0;
Money walletInputs = 0;
Money walletOutputs = 0;
Money walletFees = 0;
for (const auto& row: db.run(select(wallets_tx.amount, wallets_tx.fee).from(wallets_tx).where(wallets_tx.wallet_id == walletId).order_by(wallets_tx.unix_time.asc())))
{
Money txAmount(row.amount);
Money txFee(row.fee);
if (txAmount<0)
walletOutputs += txAmount;
else
walletInputs += txAmount;
walletFees += txFee;
walletBalance = walletInputs + walletOutputs - walletFees;
string sReason = "?";
ltrace << "Tx Amount: " << txAmount
<< ". Reason: " << sReason
<< ". Tx Fees: " << txFee
<< ". Total outputs: " << walletOutputs
<< ". Total inputs: " << walletInputs
<< ". Total Balance: " << walletBalance;
}
linfo << "Wallet " << walletId << " balance is: " << walletBalance;
db(remove_from(wallets_balances).where(wallets_balances.wallet_id == walletId));
db(insert_into(wallets_balances).set(
wallets_balances.wallet_id = walletId,
wallets_balances.coin_id = m_coinId,
wallets_balances.balance = walletBalance.toBoostMpf()
));
}