From af783b40dcd4eb458a0c9937703cfae70cc35993 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Thu, 20 Aug 2020 20:28:32 +0200 Subject: [PATCH] [XTZ] Add more precise fee and gas estimations Those are accessible through account/TxBuilderRequest API --- .../idl/wallet/tezos/tezos_like_wallet.djinni | 2 + core/src/api/TezosLikeAccount.hpp | 3 + core/src/jni/jni/TezosLikeAccount.cpp | 9 + core/src/net/HttpClient.cpp | 6 +- core/src/wallet/tezos/TezosLikeAccount.h | 5 + core/src/wallet/tezos/TezosLikeAccount2.cpp | 47 ++++- .../api_impl/TezosLikeTransactionApi.cpp | 181 +++++++++++++++++- .../tezos/api_impl/TezosLikeTransactionApi.h | 6 + .../ExternalTezosLikeBlockchainExplorer.cpp | 97 +++++++++- .../ExternalTezosLikeBlockchainExplorer.h | 7 + .../NodeTezosLikeBlockchainExplorer.cpp | 20 +- .../NodeTezosLikeBlockchainExplorer.h | 6 + .../explorers/TezosLikeBlockchainExplorer.h | 7 + core/test/integration/CMakeLists.txt | 1 + .../synchronization/tezos_synchronization.cpp | 41 ++++ 15 files changed, 432 insertions(+), 6 deletions(-) diff --git a/core/idl/wallet/tezos/tezos_like_wallet.djinni b/core/idl/wallet/tezos/tezos_like_wallet.djinni index a3b00e2a41..3b47985353 100644 --- a/core/idl/wallet/tezos/tezos_like_wallet.djinni +++ b/core/idl/wallet/tezos/tezos_like_wallet.djinni @@ -126,6 +126,8 @@ TezosLikeAccount = interface +c { getEstimatedGasLimit(address: string, callback: Callback); # Get fees from network getFees(callback: Callback); + # Get gas price from network + getGasPrice(callback: Callback); # Get originated accounts by current account getOriginatedAccounts(): list; } diff --git a/core/src/api/TezosLikeAccount.hpp b/core/src/api/TezosLikeAccount.hpp index b391a6a546..adeb6a132a 100644 --- a/core/src/api/TezosLikeAccount.hpp +++ b/core/src/api/TezosLikeAccount.hpp @@ -54,6 +54,9 @@ class LIBCORE_EXPORT TezosLikeAccount { /** Get fees from network */ virtual void getFees(const std::shared_ptr & callback) = 0; + /** Get gas price from network */ + virtual void getGasPrice(const std::shared_ptr & callback) = 0; + /** Get originated accounts by current account */ virtual std::vector> getOriginatedAccounts() = 0; }; diff --git a/core/src/jni/jni/TezosLikeAccount.cpp b/core/src/jni/jni/TezosLikeAccount.cpp index 852d362187..80a4359dff 100644 --- a/core/src/jni/jni/TezosLikeAccount.cpp +++ b/core/src/jni/jni/TezosLikeAccount.cpp @@ -83,6 +83,15 @@ CJNIEXPORT void JNICALL Java_co_ledger_core_TezosLikeAccount_00024CppProxy_nativ } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, ) } +CJNIEXPORT void JNICALL Java_co_ledger_core_TezosLikeAccount_00024CppProxy_native_1getGasPrice(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef, jobject j_callback) +{ + try { + DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef); + const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::TezosLikeAccount>(nativeRef); + ref->getGasPrice(::djinni_generated::BigIntCallback::toCpp(jniEnv, j_callback)); + } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, ) +} + CJNIEXPORT jobject JNICALL Java_co_ledger_core_TezosLikeAccount_00024CppProxy_native_1getOriginatedAccounts(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef) { try { diff --git a/core/src/net/HttpClient.cpp b/core/src/net/HttpClient.cpp index 55dd54d1da..73d08da083 100644 --- a/core/src/net/HttpClient.cpp +++ b/core/src/net/HttpClient.cpp @@ -108,6 +108,7 @@ namespace ledger { void HttpClient::setLogger(const std::shared_ptr &logger) { _logger = make_option(logger); + _logger.getValue()->set_level(spdlog::level::trace); } HttpRequest::HttpRequest(api::HttpMethod method, const std::string &url, @@ -123,6 +124,9 @@ namespace ledger { _client = client; _context = context; _logger = logger; + if (_logger) { + _logger.getValue()->set_level(spdlog::level::trace); + } } HttpRequest::ApiRequest::ApiRequest(const std::shared_ptr& self) { @@ -214,4 +218,4 @@ namespace ledger { } -} \ No newline at end of file +} diff --git a/core/src/wallet/tezos/TezosLikeAccount.h b/core/src/wallet/tezos/TezosLikeAccount.h index 8a691f8168..637d8f91c9 100644 --- a/core/src/wallet/tezos/TezosLikeAccount.h +++ b/core/src/wallet/tezos/TezosLikeAccount.h @@ -146,6 +146,11 @@ namespace ledger { void getFees(const std::shared_ptr & callback) override; FuturePtr getFees(); + void getGasPrice(const std::shared_ptr & callback) override; + FuturePtr getGasPrice(); + + FuturePtr estimateGasLimit(const std::shared_ptr& tx, double adjustment_factor = 1.1); + std::shared_ptr getAccountKeychain() override; private: diff --git a/core/src/wallet/tezos/TezosLikeAccount2.cpp b/core/src/wallet/tezos/TezosLikeAccount2.cpp index 8ba0fc9f7b..6b9761021b 100644 --- a/core/src/wallet/tezos/TezosLikeAccount2.cpp +++ b/core/src/wallet/tezos/TezosLikeAccount2.cpp @@ -369,15 +369,34 @@ namespace ledger { tx->setCounter(std::make_shared(++BigInt(saved_counter))); } return explorer->getCurrentBlock(); - }).flatMapPtr(self->getMainExecutionContext(), [self, explorer, tx, senderAddress] (const std::shared_ptr &block) { + }).flatMapPtr(self->getMainExecutionContext(), [self, explorer, tx, senderAddress] (const std::shared_ptr &block) { tx->setBlockHash(block->hash); if (senderAddress.find("KT1") == 0) { // HACK: KT Operation we use forge endpoint - return explorer->forgeKTOperation(tx).mapPtr(self->getMainExecutionContext(), [tx] (const std::vector &rawTx) { + return explorer->forgeKTOperation(tx).mapPtr(self->getMainExecutionContext(), [tx] (const std::vector &rawTx) { tx->setRawTx(rawTx); return tx; }); } + return FuturePtr::successful(tx); + }).flatMapPtr(self->getMainExecutionContext(), [self, request] (const std::shared_ptr &tx) { + if (request.gasLimit->toInt() == 0) { + auto filledTx = tx; + auto gasPrice_fut = request.fees->toInt() == 0 ? + self->getGasPrice() + : + FuturePtr::successful(request.fees); + + return gasPrice_fut.flatMapPtr(self->getMainExecutionContext(), [self, filledTx] (const std::shared_ptr&gasPrice) -> FuturePtr { + return self->estimateGasLimit(filledTx).flatMapPtr(self->getMainExecutionContext(), [filledTx, gasPrice] (const std::shared_ptr &gas) -> FuturePtr { + // 0.000001 comes from the gasPrice being in picoTez + const auto fees = std::make_shared(static_cast(1 + gas->toInt64() * static_cast(gasPrice->toInt64()) * 0.000001)); + filledTx->setGasLimit(gas); + filledTx->setFees(fees); + return FuturePtr::successful(filledTx); + }); + }); + } return FuturePtr::successful(tx); }); }); @@ -422,6 +441,30 @@ namespace ledger { return _explorer->getFees(); } + void TezosLikeAccount::getGasPrice(const std::shared_ptr & callback) { + getGasPrice().mapPtr(getMainExecutionContext(), [] (const std::shared_ptr &gasPrice) -> std::shared_ptr + { + if (!gasPrice) { + throw make_exception(api::ErrorCode::RUNTIME_ERROR, "Failed to retrieve gasPrice from network"); + } + return std::make_shared(*gasPrice); + }).callback(getMainExecutionContext(), callback); + } + + FuturePtr TezosLikeAccount::getGasPrice() { + return _explorer->getGasPrice(); + } + + FuturePtr TezosLikeAccount::estimateGasLimit(const std::shared_ptr& tx, double adjustment_factor) { + return _explorer->getEstimatedGasLimit(tx).flatMapPtr( + getMainExecutionContext(), + [adjustment_factor](const std::shared_ptr& consumedGas){ + auto adjusted_gas = static_cast(1 + consumedGas->toInt64() * adjustment_factor); + return Future>::successful( + std::make_shared(adjusted_gas)); + }); + } + std::shared_ptr TezosLikeAccount::getAccountKeychain() { return _keychain; } diff --git a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp index d3f3aa8fee..144e76c086 100644 --- a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp +++ b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp @@ -44,6 +44,9 @@ #include #include #include +#include +#include +#include namespace ledger { namespace core { @@ -328,6 +331,182 @@ namespace ledger { } return writer.toByteArray(); } + + std::vector TezosLikeTransactionApi::serializeForDryRun(const std::vector& chainId) { + BytesWriter writer; + writer.writeByteArray(serialize()); + writer.writeByteArray(chainId); + return writer.toByteArray(); + } + + std::string TezosLikeTransactionApi::serializeJsonForDryRun(const std::string &chainID) + { + using namespace rapidjson; + + Value vString(kStringType); + Document tx; + tx.SetObject(); + Document::AllocatorType &allocator = tx.GetAllocator(); + + // Chain Id + vString.SetString(chainID.c_str(), static_cast(chainID.length()), allocator); + tx.AddMember("chain_id", vString, allocator); + + // Operation + Value opObject(kObjectType); + { + // Branch + const auto hash = _block->getHash(); + vString.SetString(hash.c_str(), static_cast(hash.length()), allocator); + opObject.AddMember("branch", vString, allocator); + + // Fake sign + static const auto bogusSignature = + "edsigtkpiSSschcaCt9pUVrpNPf7TTcgvgDEDD6NCEHMy8NNQJCGnMfLZzYoQj74yLjo9wx6MPVV29" + "CvVzgi7qEcEUok3k7AuMg"; + vString.SetString( + bogusSignature, + static_cast(std::strlen(bogusSignature)), + allocator); + opObject.AddMember("signature", vString, allocator); + + Value opContents(kArrayType); + { + if (_needReveal) { + Value revealOp(kObjectType); + { + static const auto transaction_type = "reveal"; + vString.SetString( + transaction_type, + static_cast(std::strlen(transaction_type)), + allocator); + revealOp.AddMember("kind", vString, allocator); + + const auto source = _sender->toBase58(); + vString.SetString( + source.c_str(), static_cast(source.length()), allocator); + revealOp.AddMember("source", vString, allocator); + + if (_revealedPubKey.empty()) { + throw make_exception( + api::ErrorCode::UNSUPPORTED_OPERATION, + "Json serialization of reveal operation is available only if " + "revealed_pubkey is set."); + } + const auto pub_key = _revealedPubKey ; + vString.SetString( + source.c_str(), static_cast(source.length()), allocator); + revealOp.AddMember("public_key", vString, allocator); + + static const auto fee = "257000"; + vString.SetString(fee, static_cast(std::strlen(fee)), allocator); + revealOp.AddMember("fee", vString, allocator); + + const auto counter = _counter->toString(); + vString.SetString( + counter.c_str(), static_cast(counter.length()), allocator); + revealOp.AddMember("counter", vString, allocator); + + static const auto storage = "1000"; + vString.SetString( + storage, static_cast(std::strlen(storage)), allocator); + revealOp.AddMember("storage_limit", vString, allocator); + + static const auto gas = "100000"; + vString.SetString(gas, static_cast(std::strlen(gas)), allocator); + revealOp.AddMember("gas_limit", vString, allocator); + + } + opContents.PushBack(revealOp, allocator); + } + + Value innerOp(kObjectType); + { + switch (_type) { + case api::TezosOperationTag::OPERATION_TAG_TRANSACTION: { + static const auto transaction_type = "transaction"; + vString.SetString( + transaction_type, + static_cast(std::strlen(transaction_type)), + allocator); + innerOp.AddMember("kind", vString, allocator); + + const auto source = _sender->toBase58(); + vString.SetString( + source.c_str(), static_cast(source.length()), allocator); + innerOp.AddMember("source", vString, allocator); + + const auto destination = _receiver->toBase58(); + vString.SetString( + destination.c_str(), static_cast(destination.length()), allocator); + innerOp.AddMember("destination", vString, allocator); + + static const auto fee = "1"; + vString.SetString(fee, static_cast(std::strlen(fee)), allocator); + innerOp.AddMember("fee", vString, allocator); + + const auto counter = _counter->toString(); + vString.SetString( + counter.c_str(), static_cast(counter.length()), allocator); + innerOp.AddMember("counter", vString, allocator); + + const auto amount = _value->toBigInt()->toString(10); + vString.SetString( + amount.c_str(), static_cast(amount.length()), allocator); + innerOp.AddMember("amount", vString, allocator); + + static const auto storage = "1000"; + vString.SetString( + storage, static_cast(std::strlen(storage)), allocator); + innerOp.AddMember("storage_limit", vString, allocator); + + static const auto gas = "100000"; + vString.SetString(gas, static_cast(std::strlen(gas)), allocator); + innerOp.AddMember("gas_limit", vString, allocator); + break; + } + case api::TezosOperationTag::OPERATION_TAG_ORIGINATION: { + throw make_exception( + api::ErrorCode::UNSUPPORTED_OPERATION, + "Json serialization of origination operation is unavailable."); + break; + } + case api::TezosOperationTag::OPERATION_TAG_DELEGATION: { + throw make_exception( + api::ErrorCode::UNSUPPORTED_OPERATION, + "Json serialization of delegation operation is unavailable."); + break; + } + default: + throw make_exception( + api::ErrorCode::UNSUPPORTED_OPERATION, + "Json serialization of unknown operation type is unavailable."); + break; + } + } + opContents.PushBack(innerOp, allocator); + } + + opObject.AddMember("contents", opContents, allocator); + } + + tx.AddMember("operation", opObject, allocator); + +// return R"json({"chain_id": "NetXdQprcVkpaWU", "operation": { +// "branch": "BLq1UohguxXEdrvgxc4a4utkD1J8K4GTz2cypJqdN2nq8m1jbqW", +// "contents": [{"kind": "transaction", +// "source": "tz1fizckUHrisN2JXZRWEBvtq4xRQwPhoirQ", +// "destination": "tz1fizckUHrisN2JXZRWEBvtq4xRQwPhoirQ", "amount": +// "1432", "counter": "2531425", "fee": "1289", "gas_limit": "100000", +// "storage_limit": "1000"}], +// "signature": +// "edsigtkpiSSschcaCt9pUVrpNPf7TTcgvgDEDD6NCEHMy8NNQJCGnMfLZzYoQj74yLjo9wx6MPVV29CvVzgi7qEcEUok3k7AuMg"}})json"; + StringBuffer buffer; + Writer writer(buffer); + tx.Accept(writer); + return buffer.GetString(); + } + TezosLikeTransactionApi &TezosLikeTransactionApi::setFees(const std::shared_ptr &fees) { if (!fees) { throw make_exception(api::ErrorCode::INVALID_ARGUMENT, @@ -433,4 +612,4 @@ namespace ledger { return _needReveal; } } -} \ No newline at end of file +} diff --git a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h index edc187fe90..0ac0432b1d 100644 --- a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h +++ b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h @@ -65,6 +65,12 @@ namespace ledger { std::vector serialize() override; std::vector serializeWithType(api::TezosOperationTag type); + /// Serialize the transaction as json for Tezos Node run_operation JSON RPC endpoint + std::vector serializeForDryRun(const std::vector& chainID); + + /// Serialize the transaction as json for Tezos Node run_operation JSON RPC endpoint + std::string serializeJsonForDryRun(const std::string& chainID); + std::chrono::system_clock::time_point getDate() override; std::shared_ptr getCounter() override; diff --git a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp index 44f39893c2..5627ccae1b 100644 --- a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp @@ -32,7 +32,10 @@ #include #include #include +#include +#include #include +#include namespace ledger { namespace core { @@ -113,7 +116,26 @@ namespace ledger { // having really fees Factor for threshold is inspired from other XTZ // wallets return std::make_shared(std::min(std::stoi(fees), std::stoi(api::TezosConfigurationDefaults::TEZOS_DEFAULT_MAX_FEES))); - return std::make_shared(std::min(std::stoi(fees), std::stoi(api::TezosConfigurationDefaults::TEZOS_DEFAULT_MAX_FEES))); + }); + } + + Future> + ExternalTezosLikeBlockchainExplorer::getGasPrice() { + const bool parseNumbersAsString = true; + const auto gasPriceField = "gas_price"; + + return _http->GET("block/head") + .json(parseNumbersAsString).mapPtr(getContext(), [=](const HttpRequest::JsonResult &result) { + auto &json = *std::get<1>(result); + + if (!json.IsObject() || !json.HasMember(gasPriceField) || + !json[gasPriceField].IsString()) { + throw make_exception(api::ErrorCode::HTTP_ERROR, + fmt::format("Failed to get gas_price from network, no (or malformed) field \"{}\" in response", gasPriceField)); + } + const std::string apiGasPrice = json[gasPriceField].GetString(); + const std::string picoTezGasPrice = api::BigInt::fromDecimalString(apiGasPrice, 6, ".")->toString(10); + return std::make_shared(std::stoi(picoTezGasPrice)); }); } @@ -294,6 +316,79 @@ namespace ledger { ); } + Future> + ExternalTezosLikeBlockchainExplorer::getEstimatedGasLimit(const std::shared_ptr &tx) { + // ChainID is obtained by doing GET RPCNode /chains/main/chain_id + const auto strChainID = "NetXdQprcVkpaWU"; + const auto postPath = fmt::format( + "/chains/{}/blocks/head/helpers/scripts/run_operation", + strChainID); + const auto payload = tx->serializeJsonForDryRun(strChainID); + + const std::unordered_map postHeaders{ + {"Accept", "application/json"}, {"Content-Type", "application/json"}}; + + const bool parseNumbersAsString = true; + return _http + ->POST( + postPath, + std::vector(payload.cbegin(), payload.cend()), + postHeaders, + getRPCNodeEndpoint()) + .json(parseNumbersAsString) + .flatMapPtr( + getContext(), [](const HttpRequest::JsonResult &result) -> FuturePtr { + const auto &json = std::get<1>(result)->GetObject(); + if (json.HasMember("kind")) { + throw make_exception( + api::ErrorCode::HTTP_ERROR, + "failed to simulate operation: {}", json["kind"].GetString()); + } + if ( + !json["contents"].IsArray() || !(json["contents"].Size() > 0) || + !json["contents"].GetArray()[0].IsObject() || + !json["contents"].GetArray()[0].GetObject().HasMember("metadata") || + !json["contents"].GetArray()[0].GetObject()["metadata"].HasMember("operation_result") + ) { + throw make_exception( + api::ErrorCode::HTTP_ERROR, + "failed to get operation_result in simulation"); + } + auto &operationResult = json["contents"] + .GetArray()[0] + .GetObject()["metadata"] + .GetObject()["operation_result"]; + + // Fail if operation_result is not .status == "applied" + if (!operationResult.HasMember("status") || + operationResult["status"].GetString() != std::string("applied")) { + throw make_exception( + api::ErrorCode::HTTP_ERROR, + "failed to simulate the operation on the Node"); + } + + return FuturePtr::successful(std::make_shared( + BigInt::fromString(operationResult["consumed_gas"].GetString()))); + }) + .recover(getContext(), [] (const Exception &exception) { + auto ecode = exception.getErrorCode(); + // Tezos RPC returns a 500 when the transaction is not valid (bad counter, no balance, etc.) + // so we rethrow the tezos node error for easier debugging + auto body = std::static_pointer_cast(exception.getUserData().getValue()); + const auto &json = *std::get<1>(*body); + rapidjson::StringBuffer buffer; + rapidjson::Writer writer(buffer); + json.Accept(writer); + throw make_exception( + api::ErrorCode::HTTP_ERROR, + "failed to simulate operation: {}", + buffer.GetString()); + + // lambda is [noreturn], this is just left so type deduction succeeds and compiles + return std::make_shared("0"); + }); + } + Future> ExternalTezosLikeBlockchainExplorer::getStorage(const std::string &address) { return FuturePtr::successful( diff --git a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h index 8228811ad6..f1df8cbf23 100644 --- a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.h @@ -35,6 +35,7 @@ #include #include #include +#include namespace ledger { namespace core { @@ -62,6 +63,9 @@ namespace ledger { Future> getFees() override; + Future> + getGasPrice() override; + Future pushLedgerApiTransaction(const std::vector &transaction) override; Future startSession() override; @@ -93,6 +97,9 @@ namespace ledger { Future> getEstimatedGasLimit(const std::string &address) override; + Future> + getEstimatedGasLimit(const std::shared_ptr &transaction) override; + Future> getStorage(const std::string &address) override; diff --git a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp index ed432c25e9..b692f15d5f 100644 --- a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp @@ -94,6 +94,12 @@ namespace ledger { }); } + Future> NodeTezosLikeBlockchainExplorer::getGasPrice() { + throw make_exception( + api::ErrorCode::RUNTIME_ERROR, + "getGasPrice is unimplemented for NodeTezosLikeExplorer"); + } + Future NodeTezosLikeBlockchainExplorer::pushLedgerApiTransaction(const std::vector &transaction) { // TODO: TBC with backend team @@ -250,7 +256,19 @@ namespace ledger { ); } - Future> NodeTezosLikeBlockchainExplorer::getStorage(const std::string &address) { + Future> + NodeTezosLikeBlockchainExplorer::getEstimatedGasLimit( + const std::shared_ptr &transaction) + { + // TODO: add a warning that it's not a smart function + return FuturePtr::successful( + std::make_shared(api::TezosConfigurationDefaults::TEZOS_DEFAULT_GAS_LIMIT) + ); + } + + Future> NodeTezosLikeBlockchainExplorer::getStorage( + const std::string &address) + { return getHelper(fmt::format("blockchain/{}/{}/estimate_storage", getExplorerVersion(), getNetworkParameters().Identifier), diff --git a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h index f4e8545c63..dbc87c072c 100644 --- a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h @@ -59,6 +59,9 @@ namespace ledger { Future> getFees() override; + Future> + getGasPrice() override; + Future pushLedgerApiTransaction(const std::vector &transaction) override; Future startSession() override; @@ -90,6 +93,9 @@ namespace ledger { Future> getEstimatedGasLimit(const std::string &address) override; + virtual Future> + getEstimatedGasLimit(const std::shared_ptr &transaction) override; + Future> getStorage(const std::string &address) override; diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h index e9ee8b0d92..59ce226a83 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h @@ -120,9 +120,16 @@ namespace ledger { virtual Future> getFees() = 0; + /// Return the gas Price of the last block in picotez (e-12) per gas + virtual Future> + getGasPrice() = 0; + virtual Future> getEstimatedGasLimit(const std::string &address) = 0; + virtual Future> + getEstimatedGasLimit(const std::shared_ptr &transaction) = 0; + virtual Future> getStorage(const std::string &address) = 0; diff --git a/core/test/integration/CMakeLists.txt b/core/test/integration/CMakeLists.txt index 26d1476dc6..2bc45a0bc4 100644 --- a/core/test/integration/CMakeLists.txt +++ b/core/test/integration/CMakeLists.txt @@ -203,6 +203,7 @@ add_test (NAME ledger-core-integration-TezosMakeTransaction.ParseSignedRawTransa add_test (NAME ledger-core-integration-TezosMakeTransaction.ParseSignedRawRevealTransaction COMMAND ledger-core-integration-tests --gtest_filter=TezosMakeTransaction.ParseSignedRawRevealTransaction) add_test (NAME ledger-core-integration-TezosMakeTransaction.ParseSignedRawOriginationTransaction COMMAND ledger-core-integration-tests --gtest_filter=TezosMakeTransaction.ParseSignedRawOriginationTransaction) add_test (NAME ledger-core-integration-TezosLikeWalletSynchronization.MediumXpubSynchronization COMMAND ledger-core-integration-tests --gtest_filter=TezosLikeWalletSynchronization.MediumXpubSynchronization) +add_test (NAME ledger-core-integration-TezosLikeWalletSynchronization.EstimateGasLimit COMMAND ledger-core-integration-tests --gtest_filter=TezosLikeWalletSynchronization.EstimateGasLimit) add_test (NAME ledger-core-integration-CosmosLikeWalletSynchronization.MediumXpubSynchronization COMMAND ledger-core-integration-tests --gtest_filter=CosmosLikeWalletSynchronization.MediumXpubSynchronization) add_test (NAME ledger-core-integration-CosmosLikeWalletSynchronization.SuccessiveSynchronizations COMMAND ledger-core-integration-tests --gtest_filter=CosmosLikeWalletSynchronization.SuccessiveSynchronizations) diff --git a/core/test/integration/synchronization/tezos_synchronization.cpp b/core/test/integration/synchronization/tezos_synchronization.cpp index a2b088f494..f87d7f2993 100644 --- a/core/test/integration/synchronization/tezos_synchronization.cpp +++ b/core/test/integration/synchronization/tezos_synchronization.cpp @@ -37,6 +37,8 @@ #include #include #include +#include +#include #include #include #include @@ -233,3 +235,42 @@ TEST_F(TezosLikeWalletSynchronization, NonActivated) { dispatcher->waitUntilStopped(); } + +TEST_F(TezosLikeWalletSynchronization, EstimateGasLimit) { + auto pool = newDefaultPool("xtz", ""); + static std::function test = [=] ( + const std::string &walletName, + const std::string &nextWalletName, + const std::string &explorerURL) { + if (walletName.empty()) { + return; + } + auto configuration = DynamicObject::newInstance(); + configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,"44'/'/'/'/
"); + configuration->putString(api::TezosConfiguration::TEZOS_XPUB_CURVE, api::TezosConfigurationDefaults::TEZOS_XPUB_CURVE_ED25519); + configuration->putString(api::TezosConfiguration::TEZOS_NODE, "https://xtz-node.api.live.ledger.com"); + configuration->putString(api::Configuration::BLOCKCHAIN_EXPLORER_ENGINE, api::BlockchainExplorerEngines::TZSTATS_API); + configuration->putString(api::Configuration::BLOCKCHAIN_EXPLORER_API_ENDPOINT, explorerURL); + auto wallet = wait(pool->createWallet(walletName, "tezos", configuration)); + + pool->logger()->set_level(spdlog::level::trace); + auto strTx = "4fd4dca725498e819cc4dd6a87adcfb98770f600558d5d097d1a48b2324a9a2e080000bbdd4268871d1751a601fe66603324714266bf558c0bdeff4ebc50ac02a08d0601583f106387cb85212812b738cae45b497551bf9a00007dc21f46b94d6b432c881b78e1fee917ef0fd382571a5d5f1bf1c5aa90d62a02777eeebac53e3fe9bbcf4501cc2ee0cb1dbe65ec24c869a4715d84f65cfdc101"; + auto txBytes = hex::toByteArray(strTx); + auto tx = api::TezosLikeTransactionBuilder::parseRawSignedTransaction(ledger::core::currencies::TEZOS, txBytes); + auto apiImplTx = std::dynamic_pointer_cast(tx); + { + auto nextIndex = wait(wallet->getNextAccountIndex()); + EXPECT_EQ(nextIndex, 0); + + auto account = createTezosLikeAccount(wallet, nextIndex, XTZ_KEYS_INFO); + auto gasLimit = wait(account->estimateGasLimit(apiImplTx)); + EXPECT_GT(gasLimit->toUint64(), 100); + test(nextWalletName, "", explorerURL); + } + }; + + test("e847815f-488a-4301-b67c-378a5e9c8a61", "e847815f-488a-4301-b67c-378a5e9c8a60", kExplorerUrl); +}