Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[COIN 584] [XTZ] Improve Fees and GasLimit using a RPC node #643

Open
wants to merge 8 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions core/idl/wallet/tezos/tezos_like_wallet.djinni
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,8 @@ TezosLikeAccount = interface +c {
getEstimatedGasLimit(address: string, callback: Callback<BigInt>);
# Get fees from network
getFees(callback: Callback<BigInt>);
# Get gas price from network
getGasPrice(callback: Callback<BigInt>);
# Get originated accounts by current account
getOriginatedAccounts(): list<TezosLikeOriginatedAccount>;
}
Expand Down
3 changes: 3 additions & 0 deletions core/src/api/TezosLikeAccount.hpp

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions core/src/jni/jni/TezosLikeAccount.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion core/src/net/HttpClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,4 +214,4 @@ namespace ledger {
}


}
}
15 changes: 12 additions & 3 deletions core/src/tezos/TezosLikeExtendedPublicKey.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<DynamicObject>();
config->putString("networkIdentifier", "xtz");
// FIXME: The key is 33bytes long even for ed25519, need to manually remove the leading byte
std::vector<uint8_t> key = _key.getPublicKey();
key.erase(key.begin());
// FIXME: Hardcoded to the edpk (ed25519 public key) prefix. It should be stored elsewhere
std::vector<uint8_t> payload = vector::concat({0x0D, 0x0F, 0x25, 0xD9}, key);
auto b58encoded = Base58::encodeWithChecksum(payload, config);
return b58encoded;
}

std::string TezosLikeExtendedPublicKey::getRootPath() {
Expand Down Expand Up @@ -123,4 +132,4 @@ namespace ledger {
}

}
}
}
5 changes: 5 additions & 0 deletions core/src/wallet/tezos/TezosLikeAccount.h
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,11 @@ namespace ledger {
void getFees(const std::shared_ptr<api::BigIntCallback> & callback) override;
FuturePtr<BigInt> getFees();

void getGasPrice(const std::shared_ptr<api::BigIntCallback> & callback) override;
FuturePtr<BigInt> getGasPrice();

FuturePtr<BigInt> estimateGasLimit(const std::shared_ptr<TezosLikeTransactionApi>& tx, double adjustment_factor = 1.1);

std::shared_ptr<api::Keychain> getAccountKeychain() override;

private:
Expand Down
47 changes: 45 additions & 2 deletions core/src/wallet/tezos/TezosLikeAccount2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -359,15 +359,34 @@ namespace ledger {
// We should increment current counter
tx->setCounter(std::make_shared<BigInt>(++(*counter)));
return explorer->getCurrentBlock();
}).flatMapPtr<api::TezosLikeTransaction>(self->getMainExecutionContext(), [self, explorer, tx, senderAddress] (const std::shared_ptr<Block> &block) {
}).flatMapPtr<TezosLikeTransactionApi>(self->getMainExecutionContext(), [self, explorer, tx, senderAddress] (const std::shared_ptr<Block> &block) {
tx->setBlockHash(block->hash);
if (senderAddress.find("KT1") == 0) {
// HACK: KT Operation we use forge endpoint
return explorer->forgeKTOperation(tx).mapPtr<api::TezosLikeTransaction>(self->getMainExecutionContext(), [tx] (const std::vector<uint8_t> &rawTx) {
return explorer->forgeKTOperation(tx).mapPtr<TezosLikeTransactionApi>(self->getMainExecutionContext(), [tx] (const std::vector<uint8_t> &rawTx) {
tx->setRawTx(rawTx);
return tx;
});
}
return FuturePtr<TezosLikeTransactionApi>::successful(tx);
}).flatMapPtr<api::TezosLikeTransaction>(self->getMainExecutionContext(), [self, request] (const std::shared_ptr<TezosLikeTransactionApi> &tx) {
if (request.gasLimit->toInt() == 0) {
auto filledTx = tx;
auto gasPriceFut = request.fees->toInt() == 0 ?
self->getGasPrice()
:
FuturePtr<BigInt>::successful(request.fees);

return gasPriceFut.flatMapPtr<api::TezosLikeTransaction>(self->getMainExecutionContext(), [self, filledTx] (const std::shared_ptr<BigInt>&gasPrice) -> FuturePtr<api::TezosLikeTransaction> {
return self->estimateGasLimit(filledTx).flatMapPtr<api::TezosLikeTransaction>(self->getMainExecutionContext(), [filledTx, gasPrice] (const std::shared_ptr<BigInt> &gas) -> FuturePtr<api::TezosLikeTransaction> {
// 0.000001 comes from the gasPrice->toInt64 being in picoTez
const auto fees = std::make_shared<BigInt>(static_cast<int64_t>(1 + static_cast<double>(gas->toInt64()) * static_cast<double>(gasPrice->toInt64()) * 0.000001));
filledTx->setGasLimit(gas);
filledTx->setFees(fees);
return FuturePtr<api::TezosLikeTransaction>::successful(filledTx);
});
});
}
return FuturePtr<api::TezosLikeTransaction>::successful(tx);
});
});
Expand Down Expand Up @@ -412,6 +431,30 @@ namespace ledger {
return _explorer->getFees();
}

void TezosLikeAccount::getGasPrice(const std::shared_ptr<api::BigIntCallback> & callback) {
getGasPrice().mapPtr<api::BigInt>(getMainExecutionContext(), [] (const std::shared_ptr<BigInt> &gasPrice) -> std::shared_ptr<api::BigInt>
{
if (!gasPrice) {
throw make_exception(api::ErrorCode::RUNTIME_ERROR, "Failed to retrieve gasPrice from network");
}
return std::make_shared<api::BigIntImpl>(*gasPrice);
}).callback(getMainExecutionContext(), callback);
}

FuturePtr<BigInt> TezosLikeAccount::getGasPrice() {
return _explorer->getGasPrice();
}

FuturePtr<BigInt> TezosLikeAccount::estimateGasLimit(const std::shared_ptr<TezosLikeTransactionApi>& tx, double adjustmentFactor) {
return _explorer->getEstimatedGasLimit(tx).flatMapPtr<BigInt>(
getMainExecutionContext(),
[adjustmentFactor](const std::shared_ptr<BigInt>& consumedGas){
auto adjustedGas = static_cast<int64_t>(1 + consumedGas->toInt64() * adjustmentFactor);
return Future<std::shared_ptr<BigInt>>::successful(
std::make_shared<BigInt>(adjustedGas));
});
}

std::shared_ptr<api::Keychain> TezosLikeAccount::getAccountKeychain() {
return _keychain;
}
Expand Down
196 changes: 195 additions & 1 deletion core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@
#include <api/TezosCurve.hpp>
#include <tezos/TezosLikeExtendedPublicKey.h>
#include <api/TezosConfigurationDefaults.hpp>
#include <rapidjson/document.h>
#include <rapidjson/stringbuffer.h>
#include <rapidjson/writer.h>
namespace ledger {
namespace core {

Expand Down Expand Up @@ -328,6 +331,197 @@ namespace ledger {
}
return writer.toByteArray();
}

std::vector<uint8_t> TezosLikeTransactionApi::serializeForDryRun(const std::vector<uint8_t>& 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<SizeType>(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<SizeType>(hash.length()), allocator);
opObject.AddMember("branch", vString, allocator);

// Fake sign
static const auto bogusSignature =
"edsigtkpiSSschcaCt9pUVrpNPf7TTcgvgDEDD6NCEHMy8NNQJCGnMfLZzYoQj74yLjo9wx6MPVV29"
"CvVzgi7qEcEUok3k7AuMg";
vString.SetString(
bogusSignature,
static_cast<SizeType>(std::strlen(bogusSignature)),
allocator);
opObject.AddMember("signature", vString, allocator);
Comment on lines +364 to +371
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to put a fake signature here / what happens if we don't? (Tezos noob question)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the node refuses to make the operation simulation with an error


Value opContents(kArrayType);
{
if (_needReveal) {
Value revealOp(kObjectType);
{
static const auto transaction_type = "reveal";
vString.SetString(
transaction_type,
static_cast<SizeType>(std::strlen(transaction_type)),
allocator);
revealOp.AddMember("kind", vString, allocator);

const auto source = _sender->toBase58();
vString.SetString(
source.c_str(), static_cast<SizeType>(source.length()), allocator);
revealOp.AddMember("source", vString, allocator);

if (_revealedPubKey.empty()) {
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.empty()
? TezosLikeExtendedPublicKey::fromRaw(
_currency,
optional<std::vector<uint8_t>>(),
_signingPubKey,
std::vector<uint8_t>(0, 32),
"",
_senderCurve)
->toBase58()
: _revealedPubKey;
vString.SetString(
pub_key.c_str(), static_cast<SizeType>(pub_key.length()), allocator);
revealOp.AddMember("public_key", vString, allocator);

static const auto fee = "257000";
vString.SetString(fee, static_cast<SizeType>(std::strlen(fee)), allocator);
revealOp.AddMember("fee", vString, allocator);

const auto counter = _counter->toString();
vString.SetString(
counter.c_str(), static_cast<SizeType>(counter.length()), allocator);
revealOp.AddMember("counter", vString, allocator);

static const auto storage = "1000";
vString.SetString(
storage, static_cast<SizeType>(std::strlen(storage)), allocator);
revealOp.AddMember("storage_limit", vString, allocator);

static const auto gas = "100000";
vString.SetString(gas, static_cast<SizeType>(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<SizeType>(std::strlen(transaction_type)),
allocator);
innerOp.AddMember("kind", vString, allocator);

const auto source = _sender->toBase58();
vString.SetString(
source.c_str(), static_cast<SizeType>(source.length()), allocator);
innerOp.AddMember("source", vString, allocator);

const auto destination = _receiver->toBase58();
vString.SetString(
destination.c_str(), static_cast<SizeType>(destination.length()), allocator);
innerOp.AddMember("destination", vString, allocator);

static const auto fee = "1";
vString.SetString(fee, static_cast<SizeType>(std::strlen(fee)), allocator);
innerOp.AddMember("fee", vString, allocator);

// Increment the counter if the reveal was prepended
const auto counter = (_needReveal ? ++(*_counter) : *_counter).toString();
vString.SetString(
counter.c_str(), static_cast<SizeType>(counter.length()), allocator);
innerOp.AddMember("counter", vString, allocator);

const auto amount = _value->toBigInt()->toString(10);
vString.SetString(
amount.c_str(), static_cast<SizeType>(amount.length()), allocator);
innerOp.AddMember("amount", vString, allocator);

static const auto storage = "1000";
vString.SetString(
storage, static_cast<SizeType>(std::strlen(storage)), allocator);
innerOp.AddMember("storage_limit", vString, allocator);

static const auto gas = "100000";
vString.SetString(gas, static_cast<SizeType>(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<StringBuffer> writer(buffer);
tx.Accept(writer);
return buffer.GetString();
}

TezosLikeTransactionApi &TezosLikeTransactionApi::setFees(const std::shared_ptr<BigInt> &fees) {
if (!fees) {
throw make_exception(api::ErrorCode::INVALID_ARGUMENT,
Expand Down Expand Up @@ -433,4 +627,4 @@ namespace ledger {
return _needReveal;
}
}
}
}
6 changes: 6 additions & 0 deletions core/src/wallet/tezos/api_impl/TezosLikeTransactionApi.h
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,12 @@ namespace ledger {
std::vector<uint8_t> serialize() override;
std::vector<uint8_t> serializeWithType(api::TezosOperationTag type);

/// Serialize the transaction as binary for Tezos Node run_operation JSON RPC endpoint
std::vector<uint8_t> serializeForDryRun(const std::vector<uint8_t>& 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<api::BigInt> getCounter() override;
Expand Down
Loading