From b32b3091f02716b8b482d43bcddfe68ac3745b8e Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Tue, 18 Aug 2020 12:23:05 +0200 Subject: [PATCH 1/8] XTZ return the mean network fees --- .../ExternalTezosLikeBlockchainExplorer.cpp | 39 ++++++++++++++----- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp index afc8bb0296..44f39893c2 100644 --- a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp @@ -69,11 +69,23 @@ namespace ledger { Future> ExternalTezosLikeBlockchainExplorer::getFees() { const bool parseNumbersAsString = true; - auto feesField = + const auto feesField = getConfiguration()->getString(api::Configuration::BLOCKCHAIN_EXPLORER_API_ENDPOINT) .value_or(api::TezosConfigurationDefaults::TEZOS_DEFAULT_API_ENDPOINT) == api::TezosConfigurationDefaults::TZSTATS_API_ENDPOINT ? "fee" : "fees"; + // The best value is probably + // divider_tx = (n_ops - n_ops_failed - n_ops_contract - + // n_seed_nonce_revelation - n_double_baking_evidence - + // n_double_endorsement_evidence - n_endorsement - n_reveal) + // so we are counting all + // n_delegation + n_origination + n_activation + n_tx + // But the deviation of fees in all those transactions is too high so the mean fees value would not be + // accurate anyway. + // + // Therefore, we only return totalFees/n_tx and leave the caller make some adjustments on the value + // afterwards + const auto txCountField = "n_tx"; return _http->GET("block/head") .json(parseNumbersAsString).mapPtr(getContext(), [=](const HttpRequest::JsonResult &result) { @@ -83,17 +95,24 @@ namespace ledger { if (!json.IsObject() || !json.HasMember(feesField) || !json[feesField].IsString()) { throw make_exception(api::ErrorCode::HTTP_ERROR, - "Failed to get fees from network, no (or malformed) field \"result\" in response"); + fmt::format("Failed to get fees from network, no (or malformed) field \"{}\" in response", feesField)); } - std::string fees = json[feesField].GetString(); - // Sometimes network is sending 0 for fees - if (fees == "0") { - fees = api::TezosConfigurationDefaults::TEZOS_DEFAULT_FEES; - } else if (fees.find('.') != std::string::npos) { - fees = api::BigInt::fromDecimalString(fees, 6, ".")->toString(10); + + // Return 0 if the block had no transaction at all + if (!json.HasMember(txCountField) || !json[txCountField].IsString()) { + return std::make_shared(0); + } + + const auto totalTx = api::BigInt::fromIntegerString(json[txCountField].GetString(), 10); + const auto totalFees = api::BigInt::fromDecimalString(json[feesField].GetString(), 6, "."); + std::string fees = api::TezosConfigurationDefaults::TEZOS_DEFAULT_FEES; + if (fees != "0" && totalTx->intValue() != 0) { + fees = totalFees->divide(totalTx)->toString(10); } - // Since nodes are giving some awkward values, we set a threshold to avoid having really fees - // Factor for threshold is inspired from other XTZ wallets + // Since nodes are giving some awkward values, we set a threshold to avoid + // 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))); }); } From 68ec249ebd3dc7146c6c6c181f795ced4e49dbab Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Thu, 20 Aug 2020 20:28:32 +0200 Subject: [PATCH 2/8] [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 | 2 +- core/src/wallet/tezos/TezosLikeAccount.h | 5 + core/src/wallet/tezos/TezosLikeAccount2.cpp | 47 ++++- .../api_impl/TezosLikeTransactionApi.cpp | 184 +++++++++++++++++- .../tezos/api_impl/TezosLikeTransactionApi.h | 6 + .../ExternalTezosLikeBlockchainExplorer.cpp | 97 ++++++++- .../ExternalTezosLikeBlockchainExplorer.h | 7 + .../NodeTezosLikeBlockchainExplorer.cpp | 85 +++++++- .../NodeTezosLikeBlockchainExplorer.h | 7 + .../explorers/TezosLikeBlockchainExplorer.h | 7 + .../synchronization/tezos_synchronization.cpp | 2 + 14 files changed, 457 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..e29aebeba8 100644 --- a/core/src/net/HttpClient.cpp +++ b/core/src/net/HttpClient.cpp @@ -214,4 +214,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 e976524750..d109d7be48 100644 --- a/core/src/wallet/tezos/TezosLikeAccount2.cpp +++ b/core/src/wallet/tezos/TezosLikeAccount2.cpp @@ -359,15 +359,34 @@ namespace ledger { // We should increment current counter tx->setCounter(std::make_shared(++(*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); }); }); @@ -412,6 +431,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..89130ffc9b 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,185 @@ 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); + + // Example of valid payload in raw string + /* + 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 +615,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..f78b0be3c1 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,84 @@ namespace ledger { ); } - Future> NodeTezosLikeBlockchainExplorer::getStorage(const std::string &address) { + Future> + NodeTezosLikeBlockchainExplorer::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> 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..fc7c9c3502 100644 --- a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.h @@ -38,6 +38,7 @@ #include #include #include +#include namespace ledger { namespace core { @@ -59,6 +60,9 @@ namespace ledger { Future> getFees() override; + Future> + getGasPrice() override; + Future pushLedgerApiTransaction(const std::vector &transaction) override; Future startSession() override; @@ -90,6 +94,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/synchronization/tezos_synchronization.cpp b/core/test/integration/synchronization/tezos_synchronization.cpp index a2b088f494..718dc40020 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 From 6835c37cb1db6df45c5e01ec5193cb7668d6f7d3 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Tue, 1 Sep 2020 16:15:56 +0200 Subject: [PATCH 3/8] Review --- core/src/wallet/tezos/TezosLikeAccount2.cpp | 16 ++-- .../api_impl/TezosLikeTransactionApi.cpp | 2 +- .../tezos/api_impl/TezosLikeTransactionApi.h | 2 +- .../ExternalTezosLikeBlockchainExplorer.cpp | 79 +---------------- .../NodeTezosLikeBlockchainExplorer.cpp | 70 +-------------- .../explorers/TezosLikeBlockchainExplorer.cpp | 85 ++++++++++++++++++- .../explorers/TezosLikeBlockchainExplorer.h | 9 +- .../synchronization/tezos_synchronization.cpp | 2 - 8 files changed, 105 insertions(+), 160 deletions(-) diff --git a/core/src/wallet/tezos/TezosLikeAccount2.cpp b/core/src/wallet/tezos/TezosLikeAccount2.cpp index d109d7be48..a3c374534d 100644 --- a/core/src/wallet/tezos/TezosLikeAccount2.cpp +++ b/core/src/wallet/tezos/TezosLikeAccount2.cpp @@ -372,15 +372,15 @@ namespace ledger { }).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 ? + auto gasPriceFut = 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 gasPriceFut.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)); + // 0.000001 comes from the gasPrice->toInt64 being in picoTez + const auto fees = std::make_shared(static_cast(1 + static_cast(gas->toInt64()) * static_cast(gasPrice->toInt64()) * 0.000001)); filledTx->setGasLimit(gas); filledTx->setFees(fees); return FuturePtr::successful(filledTx); @@ -445,13 +445,13 @@ namespace ledger { return _explorer->getGasPrice(); } - FuturePtr TezosLikeAccount::estimateGasLimit(const std::shared_ptr& tx, double adjustment_factor) { + FuturePtr TezosLikeAccount::estimateGasLimit(const std::shared_ptr& tx, double adjustmentFactor) { return _explorer->getEstimatedGasLimit(tx).flatMapPtr( getMainExecutionContext(), - [adjustment_factor](const std::shared_ptr& consumedGas){ - auto adjusted_gas = static_cast(1 + consumedGas->toInt64() * adjustment_factor); + [adjustmentFactor](const std::shared_ptr& consumedGas){ + auto adjustedGas = static_cast(1 + consumedGas->toInt64() * adjustmentFactor); return Future>::successful( - std::make_shared(adjusted_gas)); + std::make_shared(adjustedGas)); }); } diff --git a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp index 89130ffc9b..7e55d2344e 100644 --- a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp +++ b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp @@ -395,7 +395,7 @@ namespace ledger { } const auto pub_key = _revealedPubKey ; vString.SetString( - source.c_str(), static_cast(source.length()), allocator); + pub_key.c_str(), static_cast(pub_key.length()), allocator); revealOp.AddMember("public_key", vString, allocator); static const auto fee = "257000"; diff --git a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h index 0ac0432b1d..992decfdf3 100644 --- a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h +++ b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h @@ -65,7 +65,7 @@ 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 + /// Serialize the transaction as binary 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 diff --git a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp index 5627ccae1b..33b1cd148a 100644 --- a/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/ExternalTezosLikeBlockchainExplorer.cpp @@ -31,11 +31,6 @@ #include "ExternalTezosLikeBlockchainExplorer.h" #include #include -#include -#include -#include -#include -#include namespace ledger { namespace core { @@ -113,8 +108,8 @@ namespace ledger { fees = totalFees->divide(totalTx)->toString(10); } // Since nodes are giving some awkward values, we set a threshold to avoid - // having really fees Factor for threshold is inspired from other XTZ - // wallets + // 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))); }); } @@ -318,75 +313,7 @@ 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"); - }); + return TezosLikeBlockchainExplorer::getEstimatedGasLimit(_http, getContext(), tx); } Future> diff --git a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp index f78b0be3c1..c6c442c7ee 100644 --- a/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/NodeTezosLikeBlockchainExplorer.cpp @@ -260,75 +260,7 @@ namespace ledger { NodeTezosLikeBlockchainExplorer::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"); - }); + return TezosLikeBlockchainExplorer::getEstimatedGasLimit(_http, getContext(), tx); } Future> NodeTezosLikeBlockchainExplorer::getStorage( diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp index 3064ca0f7e..7bc19fa179 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp @@ -33,6 +33,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace ledger { namespace core { @@ -152,5 +157,83 @@ namespace ledger { return false; }); } + + Future> TezosLikeBlockchainExplorer::getEstimatedGasLimit( + const std::shared_ptr &_http, + const std::shared_ptr &context, + 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( + context, + [](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(context, [](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"); + }); + } } -} \ No newline at end of file +} diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h index 59ce226a83..b82dd064cd 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h @@ -127,8 +127,13 @@ namespace ledger { virtual Future> getEstimatedGasLimit(const std::string &address) = 0; - virtual Future> - getEstimatedGasLimit(const std::shared_ptr &transaction) = 0; + virtual Future> getEstimatedGasLimit( + const std::shared_ptr &tx) = 0; + + Future> getEstimatedGasLimit( + const std::shared_ptr &_http, + const std::shared_ptr &context, + const std::shared_ptr &transaction); virtual Future> getStorage(const std::string &address) = 0; diff --git a/core/test/integration/synchronization/tezos_synchronization.cpp b/core/test/integration/synchronization/tezos_synchronization.cpp index 718dc40020..a2b088f494 100644 --- a/core/test/integration/synchronization/tezos_synchronization.cpp +++ b/core/test/integration/synchronization/tezos_synchronization.cpp @@ -37,8 +37,6 @@ #include #include #include -#include -#include #include #include #include From 7c31a70e93f4d0c8d71da9afa8b31dc5cf441178 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Tue, 1 Sep 2020 18:54:56 +0200 Subject: [PATCH 4/8] More review --- .../wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp | 4 ++-- core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp index 7bc19fa179..bed1b5f411 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp @@ -159,7 +159,7 @@ namespace ledger { } Future> TezosLikeBlockchainExplorer::getEstimatedGasLimit( - const std::shared_ptr &_http, + const std::shared_ptr &http, const std::shared_ptr &context, const std::shared_ptr &tx) { @@ -173,7 +173,7 @@ namespace ledger { {"Accept", "application/json"}, {"Content-Type", "application/json"}}; const bool parseNumbersAsString = true; - return _http + return http ->POST( postPath, std::vector(payload.cbegin(), payload.cend()), diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h index b82dd064cd..0444a0359b 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.h @@ -131,7 +131,7 @@ namespace ledger { const std::shared_ptr &tx) = 0; Future> getEstimatedGasLimit( - const std::shared_ptr &_http, + const std::shared_ptr &http, const std::shared_ptr &context, const std::shared_ptr &transaction); From 38b2b6f3731d0a9f42e2ab8661dc20e7b2372b0e Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Mon, 5 Oct 2020 17:33:52 +0200 Subject: [PATCH 5/8] Add fixme for dirty trick --- core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp index bed1b5f411..747c047fbf 100644 --- a/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp +++ b/core/src/wallet/tezos/explorers/TezosLikeBlockchainExplorer.cpp @@ -163,6 +163,7 @@ namespace ledger { const std::shared_ptr &context, const std::shared_ptr &tx) { + // FIXME: await the correct chain id instead of hardcoded value // ChainID is obtained by doing GET RPCNode /chains/main/chain_id const auto strChainID = "NetXdQprcVkpaWU"; const auto postPath = From f8694f6bf06ee48ff6e452926d3cf1ae87a0f7f0 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Mon, 5 Oct 2020 17:34:17 +0200 Subject: [PATCH 6/8] Fallback to signing_key if revealed pubkey is null --- .../api_impl/TezosLikeTransactionApi.cpp | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp index 7e55d2344e..4e42e6142b 100644 --- a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp +++ b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp @@ -388,12 +388,23 @@ namespace ledger { 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."); + if (_signingPubKey.empty()) { + throw make_exception( + api::ErrorCode::UNSUPPORTED_OPERATION, + "Json serialization of reveal operation is available only if " + "revealed_pubkey or signing_pubkey is set."); + } } - const auto pub_key = _revealedPubKey ; + const auto pub_key = _revealedPubKey.empty() + ? TezosLikeExtendedPublicKey::fromRaw( + _currency, + optional>(), + _signingPubKey, + std::vector(0, 32), + "", + _senderCurve) + ->toBase58() + : _revealedPubKey; vString.SetString( pub_key.c_str(), static_cast(pub_key.length()), allocator); revealOp.AddMember("public_key", vString, allocator); From 95e31fbe347446af5d6ae7161cee80e85456b170 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Mon, 5 Oct 2020 19:03:20 +0200 Subject: [PATCH 7/8] Increment the counter in fake tx if reveal needed --- core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp index 4e42e6142b..2e0ba44a0a 100644 --- a/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp +++ b/core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp @@ -456,7 +456,8 @@ namespace ledger { vString.SetString(fee, static_cast(std::strlen(fee)), allocator); innerOp.AddMember("fee", vString, allocator); - const auto counter = _counter->toString(); + // Increment the counter if the reveal was prepended + const auto counter = (_needReveal ? ++(*_counter) : *_counter).toString(); vString.SetString( counter.c_str(), static_cast(counter.length()), allocator); innerOp.AddMember("counter", vString, allocator); From dfb5a02a14d3864e183a770a2dc333ec322567f1 Mon Sep 17 00:00:00 2001 From: Gerry Agbobada Date: Mon, 5 Oct 2020 19:03:59 +0200 Subject: [PATCH 8/8] Add and document hacks around failing toBase58 --- core/src/tezos/TezosLikeExtendedPublicKey.cpp | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/core/src/tezos/TezosLikeExtendedPublicKey.cpp b/core/src/tezos/TezosLikeExtendedPublicKey.cpp index e5a36e4ea0..b531b4801a 100644 --- a/core/src/tezos/TezosLikeExtendedPublicKey.cpp +++ b/core/src/tezos/TezosLikeExtendedPublicKey.cpp @@ -81,8 +81,17 @@ namespace ledger { return _key.getPublicKeyBlake2b(_curve == api::TezosCurve::ED25519); } - std::string TezosLikeExtendedPublicKey::toBase58() { - return TezosExtendedPublicKey::toBase58(); + std::string TezosLikeExtendedPublicKey::toBase58() + { + auto config = std::make_shared(); + config->putString("networkIdentifier", "xtz"); + // FIXME: The key is 33bytes long even for ed25519, need to manually remove the leading byte + std::vector key = _key.getPublicKey(); + key.erase(key.begin()); + // FIXME: Hardcoded to the edpk (ed25519 public key) prefix. It should be stored elsewhere + std::vector payload = vector::concat({0x0D, 0x0F, 0x25, 0xD9}, key); + auto b58encoded = Base58::encodeWithChecksum(payload, config); + return b58encoded; } std::string TezosLikeExtendedPublicKey::getRootPath() { @@ -123,4 +132,4 @@ namespace ledger { } } -} \ No newline at end of file +}