diff --git a/core/src/wallet/common/AbstractWallet.cpp b/core/src/wallet/common/AbstractWallet.cpp index 201a71a7df..0959784b83 100644 --- a/core/src/wallet/common/AbstractWallet.cpp +++ b/core/src/wallet/common/AbstractWallet.cpp @@ -375,7 +375,6 @@ namespace ledger { return asInstanceOf(); } - Option AbstractWallet::getBalanceFromCache(size_t accountIndex) { return _balanceCache.get(fmt::format("{}-{}", _currency.name, accountIndex)); } @@ -392,5 +391,9 @@ namespace ledger { return getConfig(); } + TTLCache& AbstractWallet::getBalanceCache() { + return _balanceCache; + } + } } diff --git a/core/src/wallet/common/AbstractWallet.hpp b/core/src/wallet/common/AbstractWallet.hpp index ee4a162e48..57402495e9 100644 --- a/core/src/wallet/common/AbstractWallet.hpp +++ b/core/src/wallet/common/AbstractWallet.hpp @@ -161,6 +161,7 @@ namespace ledger { protected: virtual std::shared_ptr createAccountInstance(soci::session& sql, const std::string& accountUid) = 0; void addAccountInstanceToInstanceCache(const std::shared_ptr& account); + TTLCache& getBalanceCache(); private: std::string _name; diff --git a/core/src/wallet/tezos/TezosLikeAccount.cpp b/core/src/wallet/tezos/TezosLikeAccount.cpp index b7aa85900a..c3bff23f63 100644 --- a/core/src/wallet/tezos/TezosLikeAccount.cpp +++ b/core/src/wallet/tezos/TezosLikeAccount.cpp @@ -218,10 +218,9 @@ namespace ledger { if (cachedBalance.hasValue()) { return FuturePtr::successful(std::make_shared(cachedBalance.getValue())); } - std::vector listAddresses{_keychain->getAddress()}; auto currency = getWallet()->getCurrency(); auto self = getSelf(); - return _explorer->getBalance(listAddresses).mapPtr(getMainExecutionContext(), [self, currency]( + return _explorer->getBalance(_keychain->getAddress()->toBase58()).mapPtr(getMainExecutionContext(), [self, currency]( const std::shared_ptr &balance) -> std::shared_ptr { Amount b(currency, 0, BigInt(balance->toString())); self->getWallet()->updateBalanceCache(self->getIndex(), b); diff --git a/core/src/wallet/tezos/TezosLikeAccount2.cpp b/core/src/wallet/tezos/TezosLikeAccount2.cpp index e976524750..09d0c26349 100644 --- a/core/src/wallet/tezos/TezosLikeAccount2.cpp +++ b/core/src/wallet/tezos/TezosLikeAccount2.cpp @@ -243,7 +243,7 @@ namespace ledger { // Check if balance is sufficient auto currency = self->getWallet()->getCurrency(); auto accountAddress = TezosLikeAddress::fromBase58(senderAddress, currency); - return explorer->getBalance(std::vector>{accountAddress}).flatMapPtr( + return explorer->getBalance(senderAddress).flatMapPtr( self->getMainExecutionContext(), [self, request, explorer, accountAddress, currency, senderAddress](const std::shared_ptr &balance) { // Check if all needed values are set diff --git a/core/src/wallet/tezos/TezosLikeWallet.cpp b/core/src/wallet/tezos/TezosLikeWallet.cpp index 85052e1e36..0dc3062e39 100644 --- a/core/src/wallet/tezos/TezosLikeWallet.cpp +++ b/core/src/wallet/tezos/TezosLikeWallet.cpp @@ -159,5 +159,21 @@ namespace ledger { return _explorer; } + const std::string TezosLikeWallet::getCacheKey(size_t accountIndex, const std::string& originatedAccount) { + return fmt::format("{}-{}-{}", getCurrency().name, accountIndex, originatedAccount); + } + + Option TezosLikeWallet::getBalanceFromCache(size_t accountIndex, const std::string& originatedAccount) { + return getBalanceCache().get(getCacheKey(accountIndex, originatedAccount)); + } + + void TezosLikeWallet::updateBalanceCache(size_t accountIndex, const std::string& originatedAccount, Amount balance) { + getBalanceCache().put(getCacheKey(accountIndex, originatedAccount), balance); + } + + void TezosLikeWallet::invalidateBalanceCache(size_t accountIndex, const std::string& originatedAccount) { + getBalanceCache().erase(getCacheKey(accountIndex, originatedAccount)); + } + } } diff --git a/core/src/wallet/tezos/TezosLikeWallet.h b/core/src/wallet/tezos/TezosLikeWallet.h index 00a48d2567..673ed1933d 100644 --- a/core/src/wallet/tezos/TezosLikeWallet.h +++ b/core/src/wallet/tezos/TezosLikeWallet.h @@ -73,6 +73,14 @@ namespace ledger { std::shared_ptr getBlockchainExplorer(); + const std::string getCacheKey(size_t accountIndex, const std::string& originatedAccount); + + Option getBalanceFromCache(size_t accountIndex, const std::string& originatedAccount); + + void updateBalanceCache(size_t accountIndex, const std::string& originatedAccount, Amount balance); + + void invalidateBalanceCache(size_t accountIndex, const std::string& originatedAccount); + protected: std::shared_ptr createAccountInstance(soci::session &sql, const std::string &accountUid) override; diff --git a/core/src/wallet/tezos/delegation/TezosLikeOriginatedAccount.cpp b/core/src/wallet/tezos/delegation/TezosLikeOriginatedAccount.cpp index 887226309a..fe48fa9534 100644 --- a/core/src/wallet/tezos/delegation/TezosLikeOriginatedAccount.cpp +++ b/core/src/wallet/tezos/delegation/TezosLikeOriginatedAccount.cpp @@ -40,6 +40,7 @@ #include #include #include +#include namespace ledger { namespace core { @@ -62,9 +63,11 @@ namespace ledger { } static inline void update_balance(std::shared_ptr const& op, BigInt& sum) { - auto value = BigInt(op->getAmount()->toString()); auto tzOp = op->asTezosLikeOperation(); auto tzTx = tzOp->getTransaction(); + auto value = (tzTx->getType() == api::TezosOperationTag::OPERATION_TAG_TRANSACTION) + ? BigInt(op->getAmount()->toString()) + : BigInt::ZERO; switch (op->getOperationType()) { case api::OperationType::RECEIVE: { @@ -142,19 +145,24 @@ namespace ledger { } FuturePtr TezosLikeOriginatedAccount::getBalance(const std::shared_ptr& context) { - return std::dynamic_pointer_cast(queryOperations()->complete())->execute() - .mapPtr(context, [] (const std::vector> &ops) { - auto result = BigInt::ZERO; - - for (auto const &op : ops) { - OperationStrategy::update_balance(op, result); - } - - return std::make_shared(currencies::TEZOS, 0, result); + auto localAccount = _originatorAccount.lock(); + if (!localAccount) { + throw make_exception(api::ErrorCode::NULL_POINTER, "Account was released."); + } + auto wallet = std::dynamic_pointer_cast(localAccount->getWallet()); + auto cachedBalance = wallet->getBalanceFromCache(localAccount->getIndex(), _address); + if (cachedBalance.hasValue()) { + return FuturePtr::successful(std::make_shared(cachedBalance.getValue())); + } + const auto address = _address; + return wallet->getBlockchainExplorer()->getBalance(_address).mapPtr(context, [wallet, localAccount, address]( + const std::shared_ptr &balance) -> std::shared_ptr { + Amount b(wallet->getCurrency(), 0, BigInt(balance->toString())); + wallet->updateBalanceCache(localAccount->getIndex(), address, b); + return std::make_shared(b); }); } - void TezosLikeOriginatedAccount::getBalanceHistory(const std::chrono::system_clock::time_point & start, const std::chrono::system_clock::time_point & end, api::TimePeriod period, diff --git a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp index afc8bb0296..74be34cace 100644 --- a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp @@ -49,15 +49,8 @@ namespace ledger { Future> - ExternalTezosLikeBlockchainExplorer::getBalance(const std::vector &addresses) { - auto size = addresses.size(); - if (size != 1) { - throw make_exception(api::ErrorCode::INVALID_ARGUMENT, - "Can only get balance of 1 address from Tezos Node, but got {} addresses", - addresses.size()); - } - std::string addressesStr = addresses[0]->toString(); - return getHelper(fmt::format("account/{}", addressesStr), + ExternalTezosLikeBlockchainExplorer::getBalance(const std::string &address) { + return getHelper(fmt::format("account/{}", address), "total_balance", std::unordered_map{}, "0", @@ -224,6 +217,7 @@ namespace ledger { const std::string &forceUrl, bool isDecimal) { const bool parseNumbersAsString = true; + const bool ignoreStatusCode = true; auto networkId = getNetworkParameters().Identifier; std::string p, separator = "?"; @@ -235,9 +229,19 @@ namespace ledger { return _http->GET(url + p, std::unordered_map(), forceUrl) - .json(parseNumbersAsString) + .json(parseNumbersAsString, ignoreStatusCode) .mapPtr(getContext(), [field, networkId, fallbackValue, isDecimal](const HttpRequest::JsonResult &result) { + auto& connection = *std::get<0>(result); + if (connection.getStatusCode() == 404) { + // it means that it’s a “logical” error (i.e. some resources not found), which + // in this case we fallback to a given value + return std::make_shared(!fallbackValue.empty() ? fallbackValue : "0"); + } + else if (connection.getStatusCode() < 200 || connection.getStatusCode() >= 300) { + throw Exception(api::ErrorCode::HTTP_ERROR, connection.getStatusText()); + } + auto &json = *std::get<1>(result); if ((!json.IsObject() || !json.HasMember(field.c_str()) || @@ -253,19 +257,7 @@ namespace ledger { value = api::BigInt::fromDecimalString(value, 6, ".")->toString(10); } return std::make_shared(value); - }) - .recover(getContext(), [fallbackValue] (const Exception &exception) { - auto ecode = exception.getErrorCode(); - if (ecode == api::ErrorCode::UNABLE_TO_CONNECT_TO_HOST) { - // if it’s an HTTP error, it might be due to the host not being reachable or such, - // so we re-run the error - throw exception; - } - - // otherwise, it means that it’s a “logical” error (i.e. some resources not found), which - // in this case we fallback to a given value - return std::make_shared(!fallbackValue.empty() ? fallbackValue : "0"); - }); + }); } Future> diff --git a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h index 8228811ad6..f57563097e 100644 --- a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h @@ -57,7 +57,7 @@ namespace ledger { const std::shared_ptr &configuration); Future> - getBalance(const std::vector &addresses) override; + getBalance(const std::string &address) override; Future> getFees() override; diff --git a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp index ed432c25e9..b574aac3e3 100644 --- a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp @@ -50,23 +50,17 @@ namespace ledger { Future> - NodeTezosLikeBlockchainExplorer::getBalance(const std::vector &addresses) { - auto size = addresses.size(); - if (size != 1) { - throw make_exception(api::ErrorCode::INVALID_ARGUMENT, - "Can only get balance of 1 address from Tezos Node, but got {} addresses", addresses.size()); - } + NodeTezosLikeBlockchainExplorer::getBalance(const std::string& address) { bool parseNumbersAsString = true; - std::string addressesStr = addresses[0]->toBase58(); return _http->GET(fmt::format("blockchain/{}/{}/balance/{}", getExplorerVersion(), getNetworkParameters().Identifier, - addressesStr)) + address)) .json(parseNumbersAsString) - .mapPtr(getContext(), [addressesStr](const HttpRequest::JsonResult &result) { + .mapPtr(getContext(), [address](const HttpRequest::JsonResult &result) { auto &json = *std::get<1>(result); if (!json.IsArray() && json.Size() == 1 && json[0].IsString()) { - throw make_exception(api::ErrorCode::HTTP_ERROR, "Failed to get balance for {}", addressesStr); + throw make_exception(api::ErrorCode::HTTP_ERROR, "Failed to get balance for {}", address); } auto info = json[0].GetString(); return std::make_shared(info); diff --git a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h index f4e8545c63..b635c55ee1 100644 --- a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h @@ -54,7 +54,7 @@ namespace ledger { const std::shared_ptr &configuration); Future> - getBalance(const std::vector &addresses) override; + getBalance(const std::string &address) override; Future> getFees() override; diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h index e9ee8b0d92..6c2e3c569c 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h @@ -115,7 +115,7 @@ namespace ledger { const std::vector &matchableKeys); virtual Future> - getBalance(const std::vector &addresses) = 0; + getBalance(const std::string &address) = 0; virtual Future> getFees() = 0; diff --git a/core/test/integration/synchronization/tezos_synchronization.cpp b/core/test/integration/synchronization/tezos_synchronization.cpp index a2b088f494..c3e709c9c0 100644 --- a/core/test/integration/synchronization/tezos_synchronization.cpp +++ b/core/test/integration/synchronization/tezos_synchronization.cpp @@ -99,7 +99,7 @@ TEST_F(TezosLikeWalletSynchronization, MediumXpubSynchronization) { api::EventCode::SYNCHRONIZATION_SUCCEED); auto balance = wait(account->getBalance()); - EXPECT_NE(balance->toLong(), 0L); + EXPECT_GT(balance->toLong(), 0L); auto originatedAccounts = account->getOriginatedAccounts(); EXPECT_GE(originatedAccounts.size(), 2); @@ -112,7 +112,7 @@ TEST_F(TezosLikeWalletSynchronization, MediumXpubSynchronization) { std::cout << ">>> Nb of originated ops: " << origOps.size() << std::endl; auto origBalance = wait(std::dynamic_pointer_cast(origAccount)->getBalance(dispatcher->getMainExecutionContext())); - EXPECT_NE(origBalance->toLong(), 0L); + EXPECT_GT(origBalance->toLong(), 0L); std::cout << ">>> Originated Balance: " << origBalance->toString() << std::endl; auto fromDate = DateUtils::fromJSON("2019-02-01T13:38:23Z");