<|start_filename|>core/src/wallet/common/database/BulkInsertDatabaseHelper.hpp<|end_filename|>
/*
 *
 * BulkInsertDatabaseHelper.hpp
 * ledger-core
 *
 * Created by <NAME> on 05/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_BULKINSERTDATABASEHELPER_HPP
#define LEDGER_CORE_BULKINSERTDATABASEHELPER_HPP

#include <soci.h>
#include <wallet/common/Operation.h>
#include <database/PreparedStatement.hpp>

namespace ledger {
    namespace core {
        struct OperationBinding {
            std::vector <std::string> type;
            std::vector <std::string> senders;
            std::vector <std::string> receivers;
            std::vector <std::string> amount;
            std::vector <std::string> fees;
            std::vector <Option<std::string>> blockUid;
            std::vector <std::string> serializedTrust;

            std::vector <std::string> uid;
            std::vector <std::string> accountUid;
            std::vector <std::string> walletUid;
            std::vector <std::chrono::system_clock::time_point> date;
            std::vector <std::string> currencyName;

            void update(const Operation& operation);
            void reset();
        };

        struct BlockBinding {
            std::vector<std::string> uid;
            std::vector<std::string> hash;
            std::vector<uint64_t> height;
            std::vector<std::chrono::system_clock::time_point> time;
            std::vector<std::string> currencyName;

            void update(const Block &b);
            void clear();
        };

        class BulkInsertDatabaseHelper {
        public:
            static const StatementDeclaration<OperationBinding> UPSERT_OPERATION;
            static const StatementDeclaration<BlockBinding> UPSERT_BLOCK;
            static void updateBlock(soci::session& sql, const Block& block);
        };
    }
}

#endif //LEDGER_CORE_BULKINSERTDATABASEHELPER_HPP


<|start_filename|>core/src/wallet/bitcoin/database/BitcoinLikeOperationDatabaseHelper.cpp<|end_filename|>
/*
 *
 * BitcoinLikeOperationDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 30/12/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "BitcoinLikeOperationDatabaseHelper.hpp"
#include <database/PreparedStatement.hpp>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <api/BigInt.hpp>
#include <crypto/SHA256.hpp>
#include <wallet/bitcoin/database/BitcoinLikeTransactionDatabaseHelper.h>
#include <unordered_set>
#include <database/soci-backend-utils.h>
#include <debug/Benchmarker.h>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>

using namespace soci;

namespace {
    using namespace ledger::core;

    // Bitcoin operations
    struct BitcoinOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> txHash;

        void update(const std::string& opUid, const std::string& transactionUid,
                const std::string& transactionHash) {
            uid.push_back(opUid);
            txUid.push_back(transactionUid);
            txHash.push_back(transactionHash);
        }

        void clear() {
            uid.clear();
            txUid.clear();
            txHash.clear();
        }

    };

    const auto UPSERT_BITCOIN_OPERATION = db::stmt<BitcoinOperationBinding>(
            "INSERT INTO bitcoin_operations VALUES(:uid, :tx_uid, :tx_hash) "
            "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, use(b.uid), use(b.txUid), use(b.txHash);
            });

    // Transaction
    struct TransactionBinding {
        std::vector<std::string> uid;
        std::vector<std::string> hash;
        std::vector<uint32_t> version;
        std::vector<std::chrono::system_clock::time_point> date;
        std::vector<uint64_t> lockTime;
        std::vector<Option<std::string>> blockUid;

        void update(const BitcoinLikeBlockchainExplorerTransaction& tx, const std::string& txUid) {
            uid.push_back(txUid);
            hash.push_back(tx.hash);
            version.push_back(tx.version);
            date.push_back(tx.receivedAt);
            lockTime.push_back(tx.lockTime);
            blockUid.push_back(tx.block.map<std::string>([] (const BitcoinLikeBlockchainExplorer::Block& block) {
                return block.getUid();
            }));
        }

        void clear() {
            uid.clear();
            hash.clear();
            version.clear();
            date.clear();
            lockTime.clear();
            blockUid.clear();
        }
    };

    const auto UPSERT_TRANSACTION = db::stmt<TransactionBinding>(
            "INSERT INTO bitcoin_transactions VALUES("
            ":tx_uid, :hash, :version, :block_uid, :time, :locktime)"
            " ON CONFLICT(transaction_uid) DO UPDATE SET block_uid = :block_uid", [] (auto& s, auto&  b) {
                s, use(b.uid, "tx_uid"), use(b.hash, "hash"),
                use(b.version, "version"),
                use(b.blockUid, "block_uid"), use(b.date, "time"),
                use(b.lockTime, "locktime");
            });

    // Input
    struct InputBinding {
        std::vector<BitcoinLikeBlockchainExplorerInput> input;
        std::vector<std::string> uid;
        std::vector<std::string> prevBtcTxUid;
        std::vector<Option<uint64_t>> amount;
        std::vector<Option<uint32_t>> previousTxOutputIndex;
        std::vector<Option<std::string>> previousTxHash;
        std::vector<Option<std::string>> address;
        std::vector<Option<std::string>> coinbase;
        std::vector<uint32_t> sequence;

        void update(const BitcoinLikeBlockchainExplorerInput& in, const std::string& inputUid,
                const std::string& previousTxUid,
                const std::string& accountUid) {
            uid.push_back(inputUid);
            input.push_back(in);
            amount.push_back(in.value.map<uint64_t>([] (const BigInt& v) {
                return v.toUint64();
            }));
            prevBtcTxUid.push_back(previousTxUid);
            previousTxOutputIndex.push_back(in.previousTxOutputIndex);
            previousTxHash.push_back(in.previousTxHash);
            address.push_back(in.address);
            coinbase.push_back(in.coinbase);
            sequence.push_back(in.sequence);
        }

        void clear() {
            input.clear();
            uid.clear();
            prevBtcTxUid.clear();
            amount.clear();
            previousTxOutputIndex.clear();
            previousTxHash.clear();
            address.clear();
            coinbase.clear();
            sequence.clear();
        }
    };

    const auto UPSERT_INPUT = db::stmt<InputBinding>(
            "INSERT INTO bitcoin_inputs VALUES(:uid, :idx, :hash, :prev_tx_uid, :amount, :address, "
            ":coinbase, :sequence) ON CONFLICT DO NOTHING", [] (auto& s, auto&  b) {
                s, use(b.uid), use(b.previousTxOutputIndex), use(b.previousTxHash),
                use(b.prevBtcTxUid), use(b.amount), use(b.address), use(b.coinbase),
                use(b.sequence);
            });

    // Transaction inputs
    struct TransactionInputBinding {
        std::vector<std::string> txUid;
        std::vector<std::string> txHash;
        std::vector<std::string> inputUid;
        std::vector<int> inputIdx;

        void update(const std::string& tUid, const std::string& tHash, const std::string& iUid,
                    int iIdx) {
            txUid.push_back(tUid);
            txHash.push_back(tHash);
            inputUid.push_back(iUid);
            inputIdx.push_back(iIdx);
        }

        void clear() {
            txUid.clear();
            txHash.clear();
            inputUid.clear();
            inputIdx.clear();
        }

    };

    const auto UPSERT_TRANSACTION_INPUT = db::stmt<TransactionInputBinding>(
            "INSERT INTO bitcoin_transaction_inputs VALUES(:tx_uid, :tx_hash, :input_uid, "
            ":input_idx) ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, use(b.txUid), use(b.txHash), use(b.inputUid), use(b.inputIdx);
            });

    // Output
    struct OutputBinding {
        std::vector<uint64_t> amount;
        std::vector<std::string> txUid;
        std::vector<std::string> txHash;
        std::vector<int> index;
        std::vector<std::string> script;
        std::vector<Option<std::string>> address;
        std::vector<Option<std::string>> accountUid;
        std::vector<Option<uint64_t>> blockHeight;
        std::vector<BitcoinLikeBlockchainExplorerOutput> output;
        std::vector<int> replaceable;

        void update(const BitcoinLikeBlockchainExplorerOutput& o, bool isReplaceable,
                const std::string& tUid, const std::string& tHash) {
            amount.push_back(o.value.toUint64());
            replaceable.push_back(isReplaceable ? 1 : 0);
            txUid.push_back(tUid);
            txHash.push_back(tHash);
            index.push_back(o.index);
            script.push_back(o.script);
            blockHeight.push_back(o.blockHeight);
            accountUid.push_back(o.accountUid);
            address.push_back(o.address);
        }

        void clear() {
            amount.clear();
            replaceable.clear();
            txUid.clear();
            txHash.clear();
            output.clear();
            script.clear();
            index.clear();
            blockHeight.clear();
            accountUid.clear();
            address.clear();
        }
    };

    const auto UPSERT_OUTPUT = db::stmt<OutputBinding>(
            "INSERT INTO bitcoin_outputs VALUES(:idx, :tx_uid, :hash, :amount, :script, :address, "
            ":account_uid, :block_height, :replaceable) "
            "ON CONFLICT DO NOTHING", [] (auto& s, auto&  b) {
                s, use(b.index), use(b.txUid), use(b.txHash), use(b.amount),
                use(b.script), use(b.address), use(b.accountUid),
                use(b.blockHeight), use(b.replaceable);
            });
}

namespace ledger {
    namespace core {

        void BitcoinLikeOperationDatabaseHelper::bulkInsert(soci::session &sql,
                const std::vector<Operation> &operations) {
            if (operations.empty())
                return;
            Benchmarker rawInsert("raw_db_insert", nullptr);
            rawInsert.start();
            PreparedStatement<OperationBinding> operationStmt;
            PreparedStatement<BlockBinding> blockStmt;
            PreparedStatement<BitcoinOperationBinding> bitcoinOpStmt;
            PreparedStatement<TransactionInputBinding> transactionInputStmt;
            PreparedStatement<TransactionBinding> transactionStmt;
            PreparedStatement<InputBinding> inputStmt;
            PreparedStatement<OutputBinding> outputStmt;

            BulkInsertDatabaseHelper::UPSERT_OPERATION(sql, operationStmt);
            BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
            UPSERT_BITCOIN_OPERATION(sql, bitcoinOpStmt);
            UPSERT_TRANSACTION(sql, transactionStmt);
            UPSERT_TRANSACTION_INPUT(sql, transactionInputStmt);
            UPSERT_INPUT(sql, inputStmt);
            UPSERT_OUTPUT(sql, outputStmt);

            for (const auto& op : operations) {
                if (op.block.hasValue()) {
                    blockStmt.bindings.update(op.block.getValue());
                }
                // Upsert operation
                operationStmt.bindings.update(op);
                // Upsert transaction
                auto& tx = op.bitcoinTransaction.getValue();
                const auto txUid =  BitcoinLikeTransactionDatabaseHelper::createBitcoinTransactionUid(op.accountUid, tx.hash);
                transactionStmt.bindings.update(tx, txUid);
                // Upsert Input
                bool replaceable = false;
                for (const auto& input : tx.inputs) {
                    replaceable = replaceable || (input.sequence < std::numeric_limits<uint32_t>::max());

                    /*
                     * In case transactions are issued with respect to zero knowledge protocol,
                     * previousTxHash is empty which causes conflict in bitcoin_inputs table
                     * Right now we generate a random 'hash' to compute inputUid, should be improved
                     * (e.g. use scriptSig of each input and sha256 it ...)
                    */
                    std::string hash;

                    //Returned by explorers when tx from zk protocol
                    std::string emptyPreviousTxHash = "0000000000000000000000000000000000000000000000000000000000000000";
                    auto previousTxHash = input.previousTxHash.getValueOr(emptyPreviousTxHash);
                    if (previousTxHash == emptyPreviousTxHash && input.signatureScript.nonEmpty()) {
                        previousTxHash =  SHA256::stringToHexHash(input.signatureScript.getValue());
                    }

                    auto inputUid = BitcoinLikeTransactionDatabaseHelper::createInputUid(op.accountUid,
                                              input.previousTxOutputIndex.getValueOr(0),
                                              previousTxHash,
                                              input.coinbase.getValueOr(""));

                    std::string prevBtcTxUid;
                    if (input.previousTxHash.nonEmpty() && input.previousTxHash.getValue() != emptyPreviousTxHash) {
                        prevBtcTxUid = BitcoinLikeTransactionDatabaseHelper::createBitcoinTransactionUid(op.accountUid, input.previousTxHash.getValue());
                    }
                    inputStmt.bindings.update(input, inputUid, prevBtcTxUid, op.accountUid);
                    transactionInputStmt.bindings.update(txUid, tx.hash, inputUid, input.index);
                }
                // Upsert output
                for (const auto& output : tx.outputs) {
                    outputStmt.bindings.update(output, replaceable && tx.block.isEmpty(), txUid, tx.hash);
                }
                // Bitcoin operation
                bitcoinOpStmt.bindings.update(op.uid, txUid, tx.hash);
            }
            // Bulk insert block (dependency for operation and bitcoin transaction)
            if (!blockStmt.bindings.uid.empty())
                blockStmt.execute();
            // Bulk insert transaction (dependency of bitcoin_input,
            // bitcoin_transaction_input and  bitcoin_output)
            transactionStmt.execute();
            // Bulk insert outputs
            outputStmt.execute();
            // Bulk insert bitcoin_input (dependency of bitcoin_transaction_inputs)
            inputStmt.execute();
            // Bulk insert  bitcoin_transaction_inputs
            transactionInputStmt.execute();
            // Bulk insert operations (dependency of  bitcoin operations)
            operationStmt.execute();
            // Bulk insert bitcoin operations
            bitcoinOpStmt.execute();

            rawInsert.stop();
        }
    }
}

<|start_filename|>core/src/wallet/bitcoin/networks.cpp<|end_filename|>
/*
 *
 * networks
 * ledger-core
 *
 * Created by <NAME> on 27/01/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "networks.hpp"
#include <utils/Exception.hpp>
namespace ledger {
    namespace core {

        namespace networks {

            const BIP115Parameters BIP115_PARAMETERS = {
                    "9ec9845acb02fab24e1c0368b3b517c1a4488fba97f0e3459ac053ea01000000",
                    {0xC0,0x1F,0x02}
            };

            //Zcash overwinter
            const ZIPParameters ZIP143_PARAMETERS = {
                    3,
                    {0x80},
                    {0x03, 0xC4, 0x82, 0x70},
                    347500
            };
            //Zcash Sapling (starting from block 419200)
            const ZIPParameters ZIP_SAPLING_PARAMETERS = {
                    4,
                    {0x80},
                    {0x89, 0x2F, 0x20, 0x85},
                    419200
            };

            const api::BitcoinLikeNetworkParameters getNetworkParameters(const std::string &networkName, int version) {
                if (networkName == "bitcoin") {
                    static const api::BitcoinLikeNetworkParameters BITCOIN(
                            "btc",
                            {0x00},
                            {0x05},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            3000,
                            api::BitcoinLikeDustPolicy::PER_KBYTE, 
                            "Bitcoin signed message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return BITCOIN;
                } else if (networkName == "bitcoin_testnet") {
                    static const api::BitcoinLikeNetworkParameters BITCOIN_TESTNET(
                            "btc_testnet",
                            {0x6F},
                            {0xC4},
                            {0x04, 0x35, 0x87, 0xCF},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            3000,
                            api::BitcoinLikeDustPolicy::PER_KBYTE,
                            "Bitcoin signed message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return BITCOIN_TESTNET;
                } else if (networkName == "bitcoin_cash") {
                    static const api::BitcoinLikeNetworkParameters BITCOIN_CASH(
                            "abc",
                            {0x00},
                            {0x05},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            5430,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Bitcoin signed message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL | sigHashType::SIGHASH_FORKID},
                            {}
                    );
                    return BITCOIN_CASH;
                } else if (networkName == "bitcoin_gold") {
                    static const api::BitcoinLikeNetworkParameters BITCOIN_GOLD(
                            "btg",
                            {0x26},
                            {0x17},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            5430,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Bitcoin gold signed message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL | sigHashType::SIGHASH_FORKID},
                            {}
                    );
                    return BITCOIN_GOLD;
                } else if (networkName == "zcash") {
                    static const api::BitcoinLikeNetworkParameters ZCASH(
                            "zec",
                            {0x1C, 0xB8},
                            {0x1C, 0xBD},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Zcash Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {"ZIP"}
                    );
                    return ZCASH;
                } else if (networkName == "zencash") {
                    static const api::BitcoinLikeNetworkParameters ZENCASH(
                            "zen",
                            {0x20, 0x89},
                            {0x20, 0x96},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Zencash Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {"BIP115"}
                    );
                    return ZENCASH;
                } else if (networkName == "litecoin") {
                    static const api::BitcoinLikeNetworkParameters LITECOIN(
                            "ltc",
                            {0x30},
                            {0x32},
                            {0x01, 0x9D, 0xA4, 0x62},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Litecoin Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return LITECOIN;
                } else if (networkName == "peercoin") {
                    static const api::BitcoinLikeNetworkParameters PEERCOIN(
                            "ppc",
                            {0x37},
                            {0x75},
                            {0xE6, 0xE8, 0xE9, 0xE5},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "PPCoin Signed Message:\n",
                            true,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return PEERCOIN;
                } else if (networkName == "digibyte") {
                    static const api::BitcoinLikeNetworkParameters DIGIBYTE(
                            "dgb",
                            {0x1E},
                            {0x3F},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "DigiByte Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return DIGIBYTE;
                } else if (networkName == "hcash") {
                    static const api::BitcoinLikeNetworkParameters HCASH(
                            "hsr",
                            {0x28},
                            {0x78},
                            {0x04, 0x88, 0xC2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "HShare Signed Message:\n",
                            true,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return HCASH;
                } else if (networkName == "qtum") {
                    static const api::BitcoinLikeNetworkParameters QTUM(
                            "qtum",
                            {0x3A},
                            {0x32},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Qtum Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return QTUM;
                } else if (networkName == "stealthcoin") {
                    api::BitcoinLikeNetworkParameters STEALTHCOIN(
                            "xst",
                            {0x3E},
                            {0x55},
                            {0x8F, 0x62, 0x4B, 0x66},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "StealthCoin Signed Message:\n",
                            true,
                            15,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    migrateParameters(STEALTHCOIN, 1, version);
                    return STEALTHCOIN;
                } else if (networkName == "vertcoin") {
                    static const api::BitcoinLikeNetworkParameters VERTCOIN(
                            "vtc",
                            {0x47},
                            {0x05},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "VertCoin Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return VERTCOIN;
                } else if (networkName == "viacoin") {
                    static const api::BitcoinLikeNetworkParameters VIACOIN(
                            "via",
                            {0x47},
                            {0x21},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "ViaCoin Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return VIACOIN;
                } else if (networkName == "dash") {
                    static const api::BitcoinLikeNetworkParameters DASH(
                            "dash",
                            {0x4C},
                            {0x01},
                            {0x02, 0xFE, 0x52, 0xF8},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "DarkCoin Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return DASH;
                } else if (networkName == "dogecoin") {
                    static const api::BitcoinLikeNetworkParameters DOGECOIN(
                            "doge",
                            {0x1E},
                            {0x16},
                            {0x02, 0xFA, 0xCA, 0xFD},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "DogeCoin Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return DOGECOIN;
                } else if (networkName == "stratis") {
                    static const api::BitcoinLikeNetworkParameters STRATIS(
                            "strat",
                            {0x3F},
                            {0x7D},
                            {0x04, 0x88, 0xC2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Stratis Signed Message:\n",
                            true,
                            15,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return STRATIS;
                } else if (networkName == "komodo") {
                    static const api::BitcoinLikeNetworkParameters KOMODO(
                            "kmd",
                            {0x3C},
                            {0x55},
                            {0xF9, 0xEE, 0xE4, 0x8D},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Komodo Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {"ZIP"}
                    );
                    return KOMODO;
                } else if (networkName == "poswallet") {
                    static const api::BitcoinLikeNetworkParameters POSWALLET(
                            "posw",
                            {0x37},
                            {0x55},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "PosWallet Signed Message:\n",
                            true,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return POSWALLET;
                } else if (networkName == "pivx") {
                    static const api::BitcoinLikeNetworkParameters PIVX(
                            "pivx",
                            {0x1E},
                            {0x0D},
                            {0x02, 0x2D, 0x25, 0x33},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "DarkNet Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return PIVX;
                } else if (networkName == "clubcoin") {
                    static const api::BitcoinLikeNetworkParameters CLUBCOIN(
                            "club",
                            {0x1C},
                            {0x55},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Clubcoin Signed Message:\n",
                            true,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return CLUBCOIN;
                } else if (networkName == "decred") {
                    //02fda926
                    static const api::BitcoinLikeNetworkParameters DECRED(
                            "dcr",
                            {0x07, 0x3F},
                            {0x07, 0x1A},
                            {0x02, 0xFD, 0xA9, 0x26},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Decred Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return DECRED;
                } else if (networkName == "stakenet") {
                    static const api::BitcoinLikeNetworkParameters STAKENET(
                            "xsn",
                            {0x4C},
                            {0x10},
                            {0x04, 0x88, 0xB2, 0x1E},
                            api::BitcoinLikeFeePolicy::PER_BYTE,
                            10000,
                            api::BitcoinLikeDustPolicy::FIXED,
                            "Stakenet Signed Message:\n",
                            false,
                            0,
                            {sigHashType::SIGHASH_ALL},
                            {}
                    );
                    return STAKENET;
                }

                throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "No network parameters set for {}", networkName);
            }

            template <> void migrateParameters<1>(api::BitcoinLikeNetworkParameters &params) {}

            template <> void migrateParameters<2>(api::BitcoinLikeNetworkParameters &params) {
                if (params.Identifier == "xst") {
                    params.UsesTimestampedTransaction = false;
                }
            }

            void migrateParameters(api::BitcoinLikeNetworkParameters &params, int migration) {
                switch (migration) {
                    case 1:
                        migrateParameters<1>(params);
                    case 2:
                        migrateParameters<2>(params);
                    default:
                        return;
                }
            };

            const std::vector<api::BitcoinLikeNetworkParameters> ALL
            ({
                getNetworkParameters("bitcoin"),
                getNetworkParameters("bitcoin_testnet"),
                getNetworkParameters("bitcoin_cash"),
                getNetworkParameters("bitcoin_gold"),
                getNetworkParameters("zcash"),
                getNetworkParameters("zencash"),
                getNetworkParameters("litecoin"),
                getNetworkParameters("peercoin"),
                getNetworkParameters("digibyte"),
                getNetworkParameters("hcash"),
                getNetworkParameters("qtum"),
                getNetworkParameters("stealthcoin"),
                getNetworkParameters("vertcoin"),
                getNetworkParameters("viacoin"),
                getNetworkParameters("dash"),
                getNetworkParameters("dogecoin"),
                getNetworkParameters("stratis"),
                getNetworkParameters("komodo"),
                getNetworkParameters("poswallet"),
                getNetworkParameters("pivx"),
                getNetworkParameters("clubcoin"),
                getNetworkParameters("decred"),
                getNetworkParameters("stakenet")
            });
        }
    }
}


<|start_filename|>core/test/integration/synchronization/ripple_synchronization.cpp<|end_filename|>
/*
 *
 * ripple_synchronization
 *
 * Created by <NAME> on 06/01/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <gtest/gtest.h>
#include "../BaseFixture.h"
#include <set>
#include <api/KeychainEngines.hpp>
#include <utils/DateUtils.hpp>
#include <wallet/ripple/database/RippleLikeAccountDatabaseHelper.h>
#include <wallet/ripple/transaction_builders/RippleLikeTransactionBuilder.h>
#include <iostream>
#include <api/BlockchainExplorerEngines.hpp>
#include <api/RippleLikeOperation.hpp>
#include <api/RippleLikeTransaction.hpp>

using namespace std;

class RippleLikeWalletSynchronization : public BaseFixture {

};

TEST_F(RippleLikeWalletSynchronization, MediumXpubSynchronization) {
    auto pool = newDefaultPool();
    {
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,
                                 "44'/<coin_type>'/<account>'/<node>/<address>");
        auto wallet = uv::wait(pool->createWallet("e847815f-488a-4301-b67c-378a5e9c8a61", "ripple", configuration));
        {
            auto nextIndex = uv::wait(wallet->getNextAccountIndex());
            EXPECT_EQ(nextIndex, 0);

            auto account = createRippleLikeAccount(wallet, nextIndex, XRP_KEYS_INFO);

            auto fees = uv::wait(account->getFees());
            EXPECT_GT(fees->toLong(), 0L);
            auto baseReserve = uv::wait(account->getBaseReserve());
            EXPECT_GT(baseReserve->toLong(), 0L);

            auto receiver = make_receiver([=](const std::shared_ptr<api::Event> &event) {
                fmt::print("Received event {}\n", api::to_string(event->getCode()));
                if (event->getCode() == api::EventCode::SYNCHRONIZATION_STARTED)
                    return;
                EXPECT_NE(event->getCode(), api::EventCode::SYNCHRONIZATION_FAILED);
                EXPECT_EQ(event->getCode(),
                          api::EventCode::SYNCHRONIZATION_SUCCEED);

                auto balance = uv::wait(account->getBalance());
                std::cout << "Balance: " << balance->toString() << std::endl;
                auto txBuilder = std::dynamic_pointer_cast<RippleLikeTransactionBuilder>(account->buildTransaction());
                getTestExecutionContext()->stop();
            });

            auto restoreKey = account->getRestoreKey();
            auto bus = account->synchronize();
            bus->subscribe(getTestExecutionContext(), receiver);

            getTestExecutionContext()->waitUntilStopped();

            auto ops = uv::wait(
                    std::dynamic_pointer_cast<OperationQuery>(account->queryOperations()->complete()
                    ->addOrder(api::OperationOrderKey::DATE,false))->execute());
            std::cout << "Ops: " << ops.size() << std::endl;

            auto firstOp = ops.front();
            auto firstXrpOp = firstOp->asRippleLikeOperation();

            EXPECT_EQ(firstOp->getSenders()[0], "rPVMhWBsfF9iMXYj3aAzJVkPDTFNSyWdKy");
            EXPECT_EQ(firstOp->getRecipients()[0], "rageXHB6Q4VbvvWdTzKANwjeCT4HXFCKX7");
            EXPECT_EQ(firstOp->getBlockHeight().value(), 48602088);
            EXPECT_EQ(firstOp->getAmount()->toLong(), 49000000);
            EXPECT_EQ(firstOp->getFees()->toLong(), 6235);
            EXPECT_EQ(firstXrpOp->getTransaction()->getDestinationTag().value(), 0);
            EXPECT_EQ(firstXrpOp->getTransaction()->getLedgerSequence()->intValue(), 48602088);
            EXPECT_EQ(firstXrpOp->getTransaction()->getDate(), DateUtils::fromJSON("2019-07-12T11:05:20Z"));

            for (auto const& op : ops) {
                auto xrpOp = op->asRippleLikeOperation();
                EXPECT_FALSE(xrpOp == nullptr);
                EXPECT_FALSE(xrpOp->getTransaction()->getSequence() == nullptr);
                EXPECT_TRUE(std::chrono::duration_cast<std::chrono::hours>(xrpOp->getTransaction()->getDate().time_since_epoch()).count() != 0);
            }

            EXPECT_EQ(uv::wait(account->isAddressActivated("rageXHB6Q4VbvvWdTzKANwjeCT4HXFCKX7")), true);
            EXPECT_EQ(uv::wait(account->isAddressActivated("rageXHB6Q4VbvvWdTzKANwjeCT4HXFCK")), false);
            EXPECT_EQ(uv::wait(account->isAddressActivated("rf1pjatD8LyyevP1BqQJtHoz5edC5vE77Q")), false);

            auto block = uv::wait(account->getLastBlock());
            EXPECT_GT(block.height, 0);
            EXPECT_LT(block.height, (std::numeric_limits<int32_t>::max)());
        }
    }
}

TEST_F(RippleLikeWalletSynchronization, BalanceHistory) {
    auto pool = newDefaultPool();
    {
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,
                                 "44'/<coin_type>'/<account>'/<node>/<address>");
        auto wallet = uv::wait(pool->createWallet("e847815f-488a-4301-b67c-378a5e9c8a61", "ripple", configuration));
        {
            auto nextIndex = uv::wait(wallet->getNextAccountIndex());
            EXPECT_EQ(nextIndex, 0);

            auto account = createRippleLikeAccount(wallet, nextIndex, XRP_KEYS_INFO);

            std::shared_ptr<Amount> balance;

            auto receiver = make_receiver([&](const std::shared_ptr<api::Event> &event) {
                fmt::print("Received event {}\n", api::to_string(event->getCode()));
                if (event->getCode() == api::EventCode::SYNCHRONIZATION_STARTED)
                    return;
                EXPECT_NE(event->getCode(), api::EventCode::SYNCHRONIZATION_FAILED);
                EXPECT_EQ(event->getCode(),
                          api::EventCode::SYNCHRONIZATION_SUCCEED);

                balance = uv::wait(account->getBalance());
                std::cout << "Balance: " << balance->toString() << std::endl;
                auto txBuilder = std::dynamic_pointer_cast<RippleLikeTransactionBuilder>(account->buildTransaction());
                getTestExecutionContext()->stop();
            });

            auto restoreKey = account->getRestoreKey();
            auto bus = account->synchronize();
            bus->subscribe(getTestExecutionContext(), receiver);

            getTestExecutionContext()->waitUntilStopped();
            
            auto now = std::time(nullptr);
            char now_str[256];
            std::strftime(now_str, sizeof(now_str), "%Y-%m-%dT%H:%M:%SZ", std::localtime(&now));

            auto history = uv::wait(account->getBalanceHistory(
                "2019-09-20T00:00:00Z",
                now_str,
                api::TimePeriod::DAY
            ));

            EXPECT_EQ(history.back()->toString(), balance->toString());

            auto zero = std::make_shared<api::BigIntImpl>(BigInt::ZERO);
            for (auto const& balance : history) {
                EXPECT_TRUE(balance->toBigInt()->compare(zero) > 0);
            }
        }
    }
}

TEST_F(RippleLikeWalletSynchronization, VaultAccountSynchronization) {
    auto pool = newDefaultPool();
    auto configuration = DynamicObject::newInstance();
    configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,
                             "44'/<coin_type>'/<account>'/<node>/<address>");
    auto wallet = uv::wait(pool->createWallet("e847815f-488a-4301-b67c-378a5e9c8a61", "ripple", configuration));
    auto nextIndex = uv::wait(wallet->getNextAccountIndex());
    auto account = createRippleLikeAccount(wallet, nextIndex, VAULT_XRP_KEYS_INFO);
    auto receiver = make_receiver([=](const std::shared_ptr<api::Event> &event) {
        fmt::print("Received event {}\n", api::to_string(event->getCode()));
        if (event->getCode() == api::EventCode::SYNCHRONIZATION_STARTED)
            return;
        EXPECT_NE(event->getCode(), api::EventCode::SYNCHRONIZATION_FAILED);
        EXPECT_EQ(event->getCode(),
                  api::EventCode::SYNCHRONIZATION_SUCCEED);

        dispatcher->stop();
    });

    auto bus = account->synchronize();
    bus->subscribe(getTestExecutionContext(), receiver);
    dispatcher->waitUntilStopped();

    auto ops = uv::wait(
            std::dynamic_pointer_cast<OperationQuery>(account->queryOperations()->complete())->execute());
    std::cout << "Ops: " << ops.size() << std::endl;

    uint32_t destinationTag = 0;
    for (auto const& op : ops) {
        auto xrpOp = op->asRippleLikeOperation();

        if (xrpOp->getTransaction()->getHash() == "EE38840B83CAB39216611D2F6E4F9828818514C3EA47504AE2521D8957331D3C" ) {
          destinationTag = xrpOp->getTransaction()->getDestinationTag().value_or(0);
          break;
        }
    }

    EXPECT_TRUE(destinationTag == 123456789);
}


<|start_filename|>core/src/wallet/common/database/BulkInsertDatabaseHelper.cpp<|end_filename|>
/*
 *
 * BulkInsertDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 05/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "BulkInsertDatabaseHelper.hpp"
#include <bytes/serialization.hpp>
#include <wallet/common/TrustIndicator.h>
#include <wallet/common/database/BlockDatabaseHelper.h>
#include <database/soci-date.h>
#include <database/soci-option.h>

namespace ledger {
    namespace core {
        using namespace soci;

        const StatementDeclaration<OperationBinding> BulkInsertDatabaseHelper::UPSERT_OPERATION =
                db::stmt<OperationBinding>(
                        "INSERT INTO operations VALUES("
                        ":uid, :account_uid, :wallet_uid, :type, :date, :senders, :recipients, :amount,"
                        ":fees, :block_uid, :currency_name, :trust"
                        ") ON CONFLICT(uid) DO UPDATE SET block_uid = :block_uid, trust = :trust,"
                        " amount = :amount", [] (auto& s, auto& b) {
                            s, use(b.uid, "uid"), use(b.accountUid, "account_uid"),
                                    use(b.walletUid, "wallet_uid"), use(b.type, "type"),
                                    use(b.date, "date"), use(b.senders, "senders"),
                                    use(b.receivers, "recipients"), use(b.amount, "amount"),
                                    use(b.fees, "fees"), use(b.blockUid, "block_uid"),
                                    use(b.currencyName, "currency_name"),
                                    use(b.serializedTrust, "trust");
                        });
        const StatementDeclaration<BlockBinding> BulkInsertDatabaseHelper::UPSERT_BLOCK =
                db::stmt<BlockBinding>(
                        "INSERT INTO blocks VALUES(:uid, :hash, :height, :time, :currency_name)"
                        " ON CONFLICT DO NOTHING",
                        [] (auto& s, auto&  b) {
                            s, use(b.uid), use(b.hash), use(b.height), use(b.time),
                                    use(b.currencyName);
                        });

        void BulkInsertDatabaseHelper::updateBlock(soci::session& sql, const Block &block) {
            PreparedStatement<BlockBinding> stmt;
            UPSERT_BLOCK(sql, stmt);
            stmt.bindings.update(block);
            stmt.execute();
        }


        void OperationBinding::update(const Operation &operation) {
            amount.push_back(operation.amount.toHexString());
            blockUid.push_back(operation.block.map<std::string>([] (const Block& block) {
                return block.getUid();
            }));
            std::string trust;
            serialization::saveBase64<TrustIndicator>(*operation.trust, trust);
            serializedTrust.push_back(trust);
            std::string separator(",");
            std::stringstream sndrs;
            std::stringstream rcvrs;
            strings::join(operation.senders, sndrs, separator);
            strings::join(operation.recipients, rcvrs, separator);
            senders.push_back(sndrs.str());
            receivers.push_back(rcvrs.str());
            fees.push_back(operation.fees.getValueOr(BigInt::ZERO).toHexString());

            uid.push_back(operation.uid);
            accountUid.push_back(operation.accountUid);
            walletUid.push_back(operation.walletUid);
            date.push_back(operation.date);
            currencyName.push_back(operation.currencyName);
            type.push_back(api::to_string(operation.type));
        }

        void OperationBinding::reset() {
            type.clear();
            senders.clear();
            receivers.clear();
            amount.clear();
            fees.clear();
            blockUid.clear();
            serializedTrust.clear();
            uid.clear();
            accountUid.clear();
            walletUid.clear();
            date.clear();
            currencyName.clear();
        }

        void BlockBinding::update(const Block &b) {
            auto u = BlockDatabaseHelper::createBlockUid(b);
            uid.push_back(u);
            hash.push_back(b.hash);
            height.push_back(b.height);
            time.push_back(b.time);
            currencyName.push_back(b.currencyName);
        }

        void BlockBinding::clear() {
            uid.clear();
            hash.clear();
            height.clear();
            time.clear();
            currencyName.clear();
        }
    }
}

<|start_filename|>core/src/database/PostgreSQLBackend.h<|end_filename|>
/*
 *
 * PostgreSQL
 *
 * Created by <NAME> on 06/12/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#pragma once

#include "DatabaseBackend.hpp"
#include <memory>
#include <mutex>
namespace ledger {
    namespace core {
        class PostgreSQLBackend : public DatabaseBackend {
        public:
            PostgreSQLBackend();
            PostgreSQLBackend(int32_t connectionPoolSize, int32_t readonlyConnectionPoolSize);

            int32_t getConnectionPoolSize() override;
            int32_t getReadonlyConnectionPoolSize() override;

            void init(const std::shared_ptr<api::PathResolver> &resolver,
                      const std::string &dbName,
                      const std::string &password,
                      soci::session &session) override;

            void setPassword(const std::string &password,
                             soci::session &session) override;

            void changePassword(const std::string & oldPassword,
                                const std::string & newPassword,
                                soci::session &session) override;
            static std::once_flag sslFlag;
            static void initSSLLibraries();
        private:
            // Resolved path to db
            std::string _dbName;
            int32_t _connectionPoolSize;
            int32_t _readonlyConnectionPoolSize;
        };
    }
}


<|start_filename|>core/src/utils/Concurrency.hpp<|end_filename|>
/*
 *
 * Concurrency
 * ledger-core
 *
 * Created by <NAME> on 28/12/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#ifndef LEDGER_CORE_CONCURRENCY_H
#define LEDGER_CORE_CONCURRENCY_H

#include <thread>
#include <mutex>

namespace ledger {
    namespace core {
        class Concurrency {
        public:
            template <typename InputIter, typename UnaryFunction>
            static void parallel_for_each(InputIter first, InputIter last, UnaryFunction f)
            {
                auto range = std::distance(first, last);
                size_t approxNumThreads = std::thread::hardware_concurrency();
                // number of elements for one thread
                size_t jobsForThread = range / approxNumThreads;
                // number of elements for one thread + remainder
                size_t jobsForMainThread = range % approxNumThreads + jobsForThread;
                // minus main thread
                std::vector<std::thread> threads(approxNumThreads - 1);
                InputIter block_start = first;
                InputIter block_end = first + jobsForThread;
                for(size_t i = 0; i < approxNumThreads - 1; ++i)
                {
                    threads[i] = std::thread(std::for_each<InputIter, UnaryFunction>,
                                             block_start, block_end, f);
                    block_start = block_end;
                    block_end += jobsForThread;
                }
                std::for_each(block_start, block_start + jobsForMainThread, f);
                for(auto& t : threads)
                {
                    if(t.joinable())
                    {
                        t.join();
                    }
                }
            }
        };
    }
}


#endif //  LEDGER_CORE_CONCURRENCY_H


<|start_filename|>core/test/fixtures/medium_xpub_fixtures.h<|end_filename|>
// This file was GENERATED by command:
//     generate_bitcoin_fixtures.py
// DO NOT EDIT BY HAND!!!
#ifndef LEDGER_FIXTURES_MEDIUM_XPUB
#define LEDGER_FIXTURES_MEDIUM_XPUB
#include <gtest/gtest.h>
#include <UvThreadDispatcher.hpp>
#include <src/database/DatabaseSessionPool.hpp>
#include <NativePathResolver.hpp>
#include <unordered_set>
#include <src/wallet/pool/WalletPool.hpp>
#include <CoutLogPrinter.hpp>
#include <src/api/DynamicObject.hpp>
#include <wallet/common/CurrencyBuilder.hpp>
#include <wallet/bitcoin/BitcoinLikeWallet.hpp>
#include <wallet/bitcoin/database/BitcoinLikeWalletDatabase.h>
#include <wallet/bitcoin/database/BitcoinLikeTransactionDatabaseHelper.h>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <wallet/pool/database/PoolDatabaseHelper.hpp>
#include <utils/JSONUtils.h>
#include <wallet/bitcoin/explorers/api/TransactionParser.hpp>
#include <wallet/bitcoin/BitcoinLikeAccount.hpp>
#include <api/BitcoinLikeOperation.hpp>
#include <api/BitcoinLikeTransaction.hpp>
#include <api/BitcoinLikeInput.hpp>
#include <api/BitcoinLikeOutput.hpp>
#include <api/BigInt.hpp>
#include <CppHttpLibClient.hpp>
#include <events/LambdaEventReceiver.hpp>
#include <soci.h>
#include <api/Account.hpp>
#include <api/BitcoinLikeAccount.hpp>

namespace ledger {
	namespace testing {
		namespace medium_xpub {
			extern core::api::ExtendedKeyAccountCreationInfo XPUB_INFO;
			extern const std::string TX_1;
			extern const std::string TX_2;
			extern const std::string TX_3;
			extern const std::string TX_4;
			extern const std::string TX_5;
			extern const std::string TX_6;
			extern const std::string TX_7;
			extern const std::string TX_8;
			extern const std::string TX_9;
			extern const std::string TX_10;
			extern const std::string TX_11;
			extern const std::string TX_12;
			extern const std::string TX_13;
			extern const std::string TX_14;
			extern const std::string TX_15;
			extern const std::string TX_16;
			extern const std::string TX_17;
			extern const std::string TX_18;
			extern const std::string TX_19;
			extern const std::string TX_20;
			extern const std::string TX_21;
			extern const std::string TX_22;
			extern const std::string TX_23;
			extern const std::string TX_24;
			extern const std::string TX_25;
			extern const std::string TX_26;
			extern const std::string TX_27;
			extern const std::string TX_28;
			extern const std::string TX_29;
			extern const std::string TX_30;
			extern const std::string TX_31;
			extern const std::string TX_32;
			extern const std::string TX_33;
			extern const std::string TX_34;
			extern const std::string TX_35;
			extern const std::string TX_36;
			extern const std::string TX_37;
			extern const std::string TX_38;
			extern const std::string TX_39;
			extern const std::string TX_40;
			extern const std::string TX_41;
			extern const std::string TX_42;
			extern const std::string TX_43;
			extern const std::string TX_44;
			extern const std::string TX_45;
			extern const std::string TX_46;
			extern const std::string TX_47;
			extern const std::string TX_48;
			extern const std::string TX_49;
			extern const std::string TX_50;
			extern const std::string TX_51;
			extern const std::string TX_52;
			extern const std::string TX_53;
			extern const std::string TX_54;
			extern const std::string TX_55;
			extern const std::string TX_56;
			extern const std::string TX_57;
			extern const std::string TX_58;
			extern const std::string TX_59;
			extern const std::string TX_60;
			extern const std::string TX_61;
			extern const std::string TX_62;
			extern const std::string TX_63;
			extern const std::string TX_64;
			extern const std::string TX_65;
			extern const std::string TX_66;
			extern const std::string TX_67;
			extern const std::string TX_68;
			extern const std::string TX_69;
			extern const std::string TX_70;
			extern const std::string TX_71;
			extern const std::string TX_72;
			extern const std::string TX_73;
			extern const std::string TX_74;
			extern const std::string TX_75;
			extern const std::string TX_76;
			extern const std::string TX_77;
			extern const std::string TX_78;
			extern const std::string TX_79;
			extern const std::string TX_80;
			extern const std::string TX_81;
			extern const std::string TX_82;
			extern const std::string TX_83;
			extern const std::string TX_84;
			extern const std::string TX_85;
			extern const std::string TX_86;
			extern const std::string TX_87;
			extern const std::string TX_88;
			extern const std::string TX_89;
			extern const std::string TX_90;
			extern const std::string TX_91;
			extern const std::string TX_92;
			extern const std::string TX_93;
			extern const std::string TX_94;
			extern const std::string TX_95;
			extern const std::string TX_96;
			extern const std::string TX_97;
			extern const std::string TX_98;
			extern const std::string TX_99;
			extern const std::string TX_100;
			extern const std::string TX_101;
			extern const std::string TX_102;
			extern const std::string TX_103;
			extern const std::string TX_104;
			extern const std::string TX_105;
			extern const std::string TX_106;
			extern const std::string TX_107;
			extern const std::string TX_108;
			extern const std::string TX_109;
			extern const std::string TX_110;
			extern const std::string TX_111;
			extern const std::string TX_112;
			extern const std::string TX_113;
			extern const std::string TX_114;
			extern const std::string TX_115;
			extern const std::string TX_116;
			extern const std::string TX_117;
			extern const std::string TX_118;
			extern const std::string TX_119;
			extern const std::string TX_120;
			extern const std::string TX_121;
			extern const std::string TX_122;
			extern const std::string TX_123;
			extern const std::string TX_124;
			extern const std::string TX_125;
			extern const std::string TX_126;
			extern const std::string TX_127;
			extern const std::string TX_128;
			extern const std::string TX_129;
			extern const std::string TX_130;
			extern const std::string TX_131;
			extern const std::string TX_132;
			extern const std::string TX_133;
			extern const std::string TX_134;
			extern const std::string TX_135;
			extern const std::string TX_136;
			extern const std::string TX_137;
			extern const std::string TX_138;
			extern const std::string TX_139;
			extern const std::string TX_140;
			extern const std::string TX_141;
			extern const std::string TX_142;
			extern const std::string TX_143;
			extern const std::string TX_144;
			extern const std::string TX_145;
			extern const std::string TX_146;
			extern const std::string TX_147;
			extern const std::string TX_148;
			extern const std::string TX_149;
			extern const std::string TX_150;
			extern const std::string TX_151;
			extern const std::string TX_152;
			extern const std::string TX_153;
			extern const std::string TX_154;
			extern const std::string TX_155;
			extern const std::string TX_156;
			extern const std::string TX_157;
			extern const std::string TX_158;
			extern const std::string TX_159;
			extern const std::string TX_160;
			extern const std::string TX_161;
			extern const std::string TX_162;
			extern const std::string TX_163;
			extern const std::string TX_164;
			extern const std::string TX_165;
			extern const std::string TX_166;
			extern const std::string TX_167;
			extern const std::string TX_168;
			extern const std::string TX_169;
			extern const std::string TX_170;
			extern const std::string TX_171;
			extern const std::string TX_172;
			extern const std::string TX_173;
			extern const std::string TX_174;
			extern const std::string TX_175;
			extern const std::string TX_176;
			extern const std::string TX_177;
			extern const std::string TX_178;
			extern const std::string TX_179;
			extern const std::string TX_180;
			extern const std::string TX_181;
			extern const std::string TX_182;
			extern const std::string TX_183;
			extern const std::string TX_184;
			extern const std::string TX_185;
			extern const std::string TX_186;
			extern const std::string TX_187;
			extern const std::string TX_188;
			extern const std::string TX_189;
			extern const std::string TX_190;
			extern const std::string TX_191;
			extern const std::string TX_192;
			extern const std::string TX_193;
			extern const std::string TX_194;
			extern const std::string TX_195;
			extern const std::string TX_196;
			extern const std::string TX_197;
			extern const std::string TX_198;
			extern const std::string TX_199;
			extern const std::string TX_200;
			extern const std::string TX_201;
			extern const std::string TX_202;
			extern const std::string TX_203;
			extern const std::string TX_204;
			extern const std::string TX_205;
			extern const std::string TX_206;
			extern const std::string TX_207;
			extern const std::string TX_208;
			extern const std::string TX_209;
			extern const std::string TX_210;
			extern const std::string TX_211;
			extern const std::string TX_212;
			extern const std::string TX_213;
			extern const std::string TX_214;
			extern const std::string TX_215;
			extern const std::string TX_216;
			extern const std::string TX_217;
			extern const std::string TX_218;
			extern const std::string TX_219;
			extern const std::string TX_220;
			extern const std::string TX_221;
			extern const std::string TX_222;
			extern const std::string TX_223;
			extern const std::string TX_224;
			extern const std::string TX_225;
			extern const std::string TX_226;
			extern const std::string TX_227;
			extern const std::string TX_228;
			extern const std::string TX_229;
			extern const std::string TX_230;
			extern const std::string TX_231;
			extern const std::string TX_232;
			extern const std::string TX_233;
			extern const std::string TX_234;
			extern const std::string TX_235;
			extern const std::string TX_236;
			extern const std::string TX_237;
			extern const std::string TX_238;
			extern const std::string TX_239;
			extern const std::string TX_240;
			extern const std::string TX_241;
			extern const std::string TX_242;
			extern const std::string TX_243;
			extern const std::string TX_244;
			extern const std::string TX_245;
			extern const std::string TX_246;
			extern const std::string TX_247;
			extern const std::string TX_248;
			extern const std::string TX_249;
			extern const std::string TX_250;
			extern const std::string TX_251;
			extern const std::string TX_252;
			extern const std::string TX_253;
			extern const std::string TX_254;
			extern const std::string TX_255;
			extern const std::string TX_256;
			extern const std::string TX_257;
			extern const std::string TX_258;
			extern const std::string TX_259;
			extern const std::string TX_260;
			extern const std::string TX_261;
			extern const std::string TX_262;
			extern const std::string TX_263;
			extern const std::string TX_264;
			extern const std::string TX_265;
			extern const std::string TX_266;
			extern const std::string TX_267;
			extern const std::string TX_268;
			extern const std::string TX_269;
			extern const std::string TX_270;
			extern const std::string TX_271;
			extern const std::string TX_272;
			extern const std::string TX_273;
			extern const std::string TX_274;
			extern const std::string TX_275;
			extern const std::string TX_276;
			extern const std::string TX_277;
			extern const std::string TX_278;
			extern const std::string TX_279;
			extern const std::string TX_280;
			extern const std::string TX_281;
			extern const std::string TX_282;
			extern const std::string TX_283;
			extern const std::string TX_284;
			extern const std::string TX_285;
			extern const std::string TX_286;
			extern const std::string TX_287;
			extern const std::string TX_288;
			extern const std::string TX_289;
			extern const std::string TX_290;
			extern const std::string TX_291;
			extern const std::string TX_292;
			extern const std::string TX_293;
			extern const std::string TX_294;
			extern const std::string TX_295;
			extern const std::string TX_296;
			extern const std::string TX_297;
			extern const std::string TX_298;
			extern const std::string TX_299;
			extern const std::string TX_300;
			extern const std::string TX_301;
			extern const std::string TX_302;
			extern const std::string TX_303;
			extern const std::string TX_304;
			extern const std::string TX_305;
			extern const std::string TX_306;
			extern const std::string TX_307;
			extern const std::string TX_308;
			extern const std::string TX_309;
			extern const std::string TX_310;
			extern const std::string TX_311;
			extern const std::string TX_312;
			extern const std::string TX_313;
			extern const std::string TX_314;
			extern const std::string TX_315;
			extern const std::string TX_316;
			extern const std::string TX_317;
			extern const std::string TX_318;
			extern const std::string TX_319;
			extern const std::string TX_320;
			extern const std::string TX_321;
			extern const std::string TX_322;
			extern const std::string TX_323;
			extern const std::string TX_324;
			extern const std::string TX_325;
			extern const std::string TX_326;
			extern const std::string TX_327;
			extern const std::string TX_328;
			extern const std::string TX_329;
			extern const std::string TX_330;
			extern const std::string TX_331;
			extern const std::string TX_332;
			extern const std::string TX_333;
			extern const std::string TX_334;
			extern const std::string TX_335;

			std::shared_ptr<core::BitcoinLikeAccount> inflate(const std::shared_ptr<core::WalletPool>& pool, const std::shared_ptr<core::AbstractWallet>& wallet);
		}
	}
}

#endif // LEDGER_FIXTURES_MEDIUM_XPUB


<|start_filename|>core/src/wallet/bitcoin/transaction_builders/BitcoinLikeUtxoPicker.cpp<|end_filename|>
/*
 *
 * BitcoinLikeUtxoPicker.cpp
 * ledger-core
 *
 * Created by <NAME> on 28/03/2018.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "BitcoinLikeUtxoPicker.h"
#include <async/Promise.hpp>
#include <api/BitcoinLikeScript.hpp>
#include <api/BitcoinLikeScriptChunk.hpp>
#include <wallet/bitcoin/api_impl/BitcoinLikeScriptApi.h>
#include <wallet/bitcoin/api_impl/BitcoinLikeTransactionApi.h>

namespace ledger {
    namespace core {

        BitcoinLikeUtxoPicker::BitcoinLikeUtxoPicker(const std::shared_ptr<api::ExecutionContext> &context,
                                                     const api::Currency &currency) : DedicatedContext(context),
                                                                                      _currency(currency)
        {}

        BitcoinLikeTransactionBuildFunction
        BitcoinLikeUtxoPicker::getBuildFunction(const BitcoinLikeGetUtxoFunction &getUtxo,
                                                const BitcoinLikeGetTxFunction& getTransaction,
                                                const std::shared_ptr<BitcoinLikeBlockchainExplorer> &explorer,
                                                const std::shared_ptr<BitcoinLikeKeychain> &keychain,
                                                const uint64_t currentBlockHeight,
                                                const std::shared_ptr<spdlog::logger>& logger,
                                                bool partial)
        {
            auto self = shared_from_this();
            logger->info("Get build function");
            return [=] (const BitcoinLikeTransactionBuildRequest& r) -> Future<std::shared_ptr<api::BitcoinLikeTransaction>> {
                return self->async<std::shared_ptr<Buddy>>([=] () {
                    logger->info("Constructing BitcoinLikeTransactionBuildFunction with blockHeight: {}", currentBlockHeight);
                    auto tx = std::make_shared<BitcoinLikeTransactionApi>(self->_currency, keychain->getKeychainEngine(), currentBlockHeight);
                    auto filteredGetUtxo = createFilteredUtxoFunction(r, keychain, getUtxo);
                    return std::make_shared<Buddy>(r, filteredGetUtxo, getTransaction, explorer, keychain, logger, tx, partial);
                }).flatMap<std::shared_ptr<api::BitcoinLikeTransaction>>(self->getContext(), [=] (const std::shared_ptr<Buddy>& buddy) -> Future<std::shared_ptr<api::BitcoinLikeTransaction>> {
                    buddy->logger->info("Buddy created");
                    return self->fillInputs(buddy).flatMap<Unit>(self->getContext(), [=] (const Unit&) -> Future<Unit> {
                        return self->fillOutputs(buddy);
                    }).flatMap<Unit>(self->getContext(), [=] (const Unit&) -> Future<Unit> {
                        return self->fillTransactionInfo(buddy);
                    }).mapPtr<api::BitcoinLikeTransaction>(self->getContext(), [=] (const Unit&) -> std::shared_ptr<api::BitcoinLikeTransaction> {
                        buddy->logger->info("Empty transaction built");
                        return buddy->transaction;
                    });
                });
            };
        }

        const api::Currency &BitcoinLikeUtxoPicker::getCurrency() const {
            return _currency;
        }

        Future<Unit> BitcoinLikeUtxoPicker::fillOutputs(const std::shared_ptr<Buddy>& buddy) {
            // Fill reception outputs
            auto params = getCurrency().bitcoinLikeNetworkParameters.value();
            auto outputIndex = 0;
            for (auto &output : buddy->request.outputs) {
                auto amount = std::get<0>(output);
                //Set right amount if we are in wipe mode
                if(buddy->request.wipe) {
                    amount = std::make_shared<ledger::core::BigInt>(buddy->outputAmount);
                }
                auto script = std::dynamic_pointer_cast<BitcoinLikeScriptApi>(std::get<1>(output))->getScript();
                auto address = script.parseAddress(getCurrency()).map<std::string>([] (const BitcoinLikeAddress& addr) {
                    return addr.getStringAddress();
                });
                BitcoinLikeBlockchainExplorerOutput out;
                out.index = static_cast<uint64_t>(outputIndex);
                out.value = *amount;
                out.address = address;
                out.script = hex::toString(script.serialize());
                std::shared_ptr<api::DerivationPath> derivationPath = nullptr;
                if (address.nonEmpty()) {
                    auto path = buddy->keychain->getAddressDerivationPath(out.address.getValue());
                    if (path.nonEmpty()) {
                        derivationPath = std::make_shared<DerivationPathApi>(DerivationPath(path.getValue()));
                    }
                } else {
                    auto addressFromScript = script.parseAddress(getCurrency());
                    if (addressFromScript.nonEmpty())
                        out.address = addressFromScript.getValue().toString();
                }
                outputIndex += 1;
                buddy->transaction->addOutput(std::make_shared<BitcoinLikeOutputApi>(out, getCurrency(), derivationPath));
            }

            // Fill change outputs  
            auto sizeWithChange = BitcoinLikeTransactionApi::estimateSize(buddy->transaction->getInputs().size(),
                                                                    buddy->request.outputs.size() + 1, 
                                                                    getCurrency(),
                                                                    buddy->keychain->getKeychainEngine());
            BigInt dustAmount(BitcoinLikeTransactionApi::computeDustAmount(getCurrency(), sizeWithChange.Max)); 
            if (buddy->changeAmount > dustAmount) {
                // TODO implement multi change
                // TODO implement use specific change address
                auto changeAddress = buddy->keychain->getFreshAddress(BitcoinLikeKeychain::CHANGE)->toString();

                auto amount = buddy->changeAmount;
                auto script = BitcoinLikeScript::fromAddress(changeAddress, _currency);
                BitcoinLikeBlockchainExplorerOutput out;
                out.index = static_cast<uint64_t>(buddy->transaction->getOutputs().size());
                out.value = amount;
                out.address = Option<std::string>(changeAddress).toOptional();
                out.script = hex::toString(script.serialize());
                std::shared_ptr<api::DerivationPath> derivationPath = nullptr;
                auto path = buddy->keychain->getAddressDerivationPath(changeAddress);
                if (path.nonEmpty()) {
                    derivationPath = std::make_shared<DerivationPathApi>(DerivationPath(path.getValue()));
                }
                buddy->transaction->addOutput(std::make_shared<BitcoinLikeOutputApi>(out, getCurrency(), derivationPath));
            }

            return Future<Unit>::successful(unit);
        }

        Future<Unit> BitcoinLikeUtxoPicker::fillTransactionInfo(const std::shared_ptr<Buddy>& buddy) {
            if (buddy->isPartial) {
                return Future<Unit>::successful(unit);
            }
            //Set timestamp
            buddy->explorer->getTimestamp().onComplete(getContext(), [=] (const Try<int64_t> &timestamp){
                if (timestamp.isSuccess()) {
                    buddy->transaction->setTimestamp(timestamp.getValue());
                }
            });

            return buddy->explorer->getCurrentBlock().map<Unit>(getContext(), [=] (const std::shared_ptr<BitcoinLikeBlockchainExplorer::Block>& block) -> Unit{
                buddy->transaction->setLockTime(static_cast<uint32_t>(block->height));
                return unit;
            });
        }

        Future<Unit> BitcoinLikeUtxoPicker::fillInputs(const std::shared_ptr<Buddy>& buddy) {
            buddy->logger->info("Filling inputs");

            auto self = shared_from_this();

            auto performFill = [self, buddy](auto performFill, auto index)
            {
                if (index >= buddy->request.inputs.size()) {
                    return Future<Unit>::successful(unit);
                }

                auto const &input = buddy->request.inputs[index];

                return buddy->getTransaction(input.transactionHash).template flatMap<Unit>(self->getContext(), [=](auto const &tx) {
                    auto const utxo = makeUtxo(tx->outputs[input.outputIndex], self->getCurrency());

                    self->fillInput(buddy, utxo, input.sequence);

                    return performFill(performFill, index + 1);
                });
            };

            // first fill inputs from user-defined input descriptors
            return performFill(performFill, 0)
                .filter(getContext(), [buddy](auto const&) {
                    return buddy->request.utxoPicker.nonEmpty();
                })
                .template flatMap<std::vector<BitcoinLikeUtxo>>(getContext(), [self, buddy](auto const&) {
                    return self->filterInputs(buddy);
                })
                .template flatMap<Unit>(getContext(), [self, buddy] (auto const& utxos) mutable {
                    auto const sequence = buddy->request.utxoPicker.getValue().sequence;

                    std::for_each(utxos.cbegin(), utxos.cend(), [self, buddy, sequence] (auto const &utxo) {
                        self->fillInput(buddy, utxo, sequence);
                    });

                    return Future<Unit>::successful(unit);
                });
        }

        void BitcoinLikeUtxoPicker::fillInput(const std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> &buddy,
                                              const BitcoinLikeUtxo &utxo,
                                              const uint32_t sequence) {
            std::vector<std::vector<uint8_t>> pub_keys;
            std::vector<std::shared_ptr<api::DerivationPath>> paths;

            // Get derivations and public keys
            if (utxo.address.nonEmpty()) {
                auto const derivationPath = buddy->keychain->getAddressDerivationPath(utxo.address.getValue());

                if (derivationPath.nonEmpty()) {
                    paths.push_back(std::make_shared<DerivationPathApi>(DerivationPath(derivationPath.getValue())));

                    pub_keys.push_back(buddy->keychain->getPublicKey(utxo.address.getValue()).getValue());
                }

                auto input = std::shared_ptr<BitcoinLikeWritableInputApi>(
                        new BitcoinLikeWritableInputApi(
                            buddy->explorer,
                            getContext(),
                            sequence,
                            pub_keys,
                            paths,
                            utxo.address.getValueOr(""),
                            // NOTE: we previously used buddy->keychain->getCurrency() which is weird since
                            // we can get that information from current object from _currency attribute
                            std::make_shared<Amount>(utxo.value),
                            utxo.transactionHash,
                            utxo.index,
                            {},
                            std::make_shared<BitcoinLikeOutputApi>(toExplorerOutput(utxo), _currency)
                        )
                );
                buddy->transaction->addInput(input);
            }
        }

        BitcoinLikeGetUtxoFunction
        BitcoinLikeUtxoPicker::createFilteredUtxoFunction(const BitcoinLikeTransactionBuildRequest &request,
                                                          const std::shared_ptr<BitcoinLikeKeychain> &keychain,
                                                          const BitcoinLikeGetUtxoFunction &getUtxo) {

            const auto basicTransactionSize = BitcoinLikeTransactionApi::estimateSize(1, 1, getCurrency(), keychain->getKeychainEngine());
            int64_t dustAmount = BitcoinLikeTransactionApi::computeDustAmount(getCurrency(), basicTransactionSize.Max);   
            auto minAmount = dustAmount;
            return [=] () -> Future<std::vector<BitcoinLikeUtxo>> {
                return getUtxo().map<std::vector<BitcoinLikeUtxo>>(getContext(), [=] (auto const &utxos) {
                    auto const isNotExcluded = [&] (auto const &currentUtxo) {
                        // NOTE: This logic can be move to the SQL request itself but since we iterate through
                        // the entire vector to compare the UTXO amount, this is fine to apply the filter(s) here.
                        // The filter are sorted by ascending time order.
                        return !(currentUtxo.address.isEmpty()
                                 || currentUtxo.value.toLong() < minAmount
                                 || !keychain->contains(currentUtxo.address.getValue())
                                 || request.excludedUtxos.count(BitcoinLikeTransactionUtxoDescriptor{currentUtxo.transactionHash, currentUtxo.index}) > 0);
                    };

                    std::vector<BitcoinLikeUtxo> filteredUtxos;

                    std::copy_if(utxos.begin(), utxos.end(), std::back_inserter(filteredUtxos), isNotExcluded);

                    return filteredUtxos;
                });
            };
        }
    }
}


<|start_filename|>core/test/lib/libledger-test/UvThreadDispatcher.cpp<|end_filename|>
/*
 *
 * UvThreadDispatcher.cpp
 * ledger-core
 *
 * Created by <NAME> on 14/06/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "UvThreadDispatcher.hpp"
#include <thread>
#include <ledger/core/utils/Option.hpp>
#include <ledger/core/utils/Try.hpp>
#include <unordered_map>

using namespace ledger::core;

namespace uv {

    static void timer_callback(uv_timer_t* handle) {
        auto event = static_cast<Event*>(handle->data);
        event->runnable->run();
        delete event;
    }

    static void context_callback(uv_async_t* handle) {
        static_cast<EventLoop *>(handle->data)->tick();
    }

    //////////////// EventLoop //////////////////////

    EventLoop::EventLoop() : 
    _loop(new uv_loop_t), _async(new uv_async_t), _timer(new uv_timer_t), _running(true) {
        uv_loop_init(_loop);
        uv_async_init(_loop, _async, context_callback);
        _async->data = static_cast<void *>(this);
        uv_timer_init(_loop, _timer);
    }

    void EventLoop::queue(const std::shared_ptr<ledger::core::api::Runnable>& runnable) {
        queue(runnable, 0);
    }

    void EventLoop::queue(const std::shared_ptr<ledger::core::api::Runnable>& runnable, int64_t millis) {
        std::unique_lock<std::mutex> lock(_mutex);
        if (_running) {
            Event event;
            event.eventType = EventType::RUN;
            event.runnable = runnable;
            event.delay = millis;
            _queue.emplace(std::move(event));
            uv_async_send(_async);
        }
        else {
            std::cout << "Loop queue not running" << std::endl;
        }
    }

    bool EventLoop::run() {
        uv_run(_loop, UV_RUN_DEFAULT);
        return uv_loop_alive(_loop) != 0;
    }

    void EventLoop::tick() {
        ledger::core::Option<Event> event;
        while ((event = dequeue()).hasValue()) {
            switch (event.getValue().eventType) {
                case EventType::RUN: {
                    if (event.getValue().delay == 0) {
                        auto result = ledger::core::Try<ledger::core::Unit>::from([&]() {
                            event.getValue().runnable->run();
                            return ledger::core::unit;
                        });
                        if (result.isFailure()) {
                            std::cout << "An error happened during asynchronous execution: "
                                    << result.getFailure().getMessage() << std::endl;
                        }
                    }
                    else {
                        auto evt = new Event(event.getValue());
                        _timer->data = static_cast<void *>(evt);
                        uv_timer_start(_timer, timer_callback, event.getValue().delay, 0);
                    }
                    break;
                }
                case EventType::CLOSE: {
                    std::unique_lock<std::mutex> lock(_mutex);
                    _running = false;
                    uv_timer_stop(_timer);
                    uv_stop(_loop);
                    uv_loop_close(_loop);
                    uv_close(reinterpret_cast<uv_handle_t*>(_async), NULL);
                    lock.unlock();
                    condition.notify_all();
                    break;
                }
            }
        }
    }

    void EventLoop::waitUntilStopped() { 
        std::unique_lock<std::mutex> lock(_mutex);
        condition.wait(lock, [&]{return !_running;});
    }

    void EventLoop::stop() {
        std::unique_lock<std::mutex> lock(_mutex);
        if (_running) {
            Event event;
            event.eventType = EventType::CLOSE;
            _queue.emplace(event);
            uv_async_send(_async);
        }
    }

    ledger::core::Option<Event> EventLoop::dequeue() {
        std::unique_lock<std::mutex> lock(_mutex);
        if (!_queue.empty() && _running) {
            auto event = _queue.front();
            _queue.pop();
            return event;
        } else {
            return ledger::core::Option<Event>::NONE;
        }
    }

    EventLoop::~EventLoop() {
        delete _timer;
        delete _loop;
        delete _async;
    }

    //////////////// SequentialExecutionContext //////////////////////

    SequentialExecutionContext::SequentialExecutionContext() {
        _thread = std::thread([this] () {
            run();
        });
    }

    void SequentialExecutionContext::run() {
        while (_loop.run());
    }

    void SequentialExecutionContext::stop() {
        _loop.stop();
    }

    void SequentialExecutionContext::execute(const std::shared_ptr<ledger::core::api::Runnable> &runnable) {
        _loop.queue(runnable);
    }

    void SequentialExecutionContext::delay(const std::shared_ptr<ledger::core::api::Runnable> &runnable, int64_t millis) {
        _loop.queue(runnable, millis);
    }

    SequentialExecutionContext::~SequentialExecutionContext() {
        _thread.join();
    }

    void SequentialExecutionContext::waitUntilStopped() { 
        _loop.waitUntilStopped();
    }

    //////////////// ThreadpoolExecutionContext //////////////////////

    ThreadpoolExecutionContext::ThreadpoolExecutionContext() : _stop(false) {
        _threads.reserve(THREADPOOL_SIZE);
        for (size_t i = 0; i < THREADPOOL_SIZE; i++)
            _threads.emplace_back(
                [this]
                {
                    for (;;)
                    {
                        std::shared_ptr<ledger::core::api::Runnable> task;
                        {
                            std::unique_lock<std::mutex> lock(this->queue_mutex);
                            this->condition.wait(lock,
                                [this] { return this->_stop || !this->tasks.empty(); });
                            if (this->_stop && this->tasks.empty())
                                return;
                            task = this->tasks.front();
                            this->tasks.pop();
                        }
                        task->run();
                    }
                }
                );
    }

    void ThreadpoolExecutionContext::stop()
    {
        _stop = true;
    }

    void ThreadpoolExecutionContext::execute(const std::shared_ptr<ledger::core::api::Runnable>& runnable) {
        {
            std::unique_lock<std::mutex> lock(this->queue_mutex);
            if (_stop)
                return;
            tasks.emplace(runnable);
        }
        condition.notify_one();
    }

    void ThreadpoolExecutionContext::delay(const std::shared_ptr<ledger::core::api::Runnable>& runnable, int64_t millis) {
        //TODO
    }
    
    ThreadpoolExecutionContext::~ThreadpoolExecutionContext() {
        _stop = true;
        condition.notify_all();
        for (int i = 0; i < THREADPOOL_SIZE; i++)
        {
            if (_threads[i].joinable())
                _threads[i].join();
        }
    }

    //////////////////  UvThreadDispatcher  //////////////////////

    UvThreadDispatcher::UvThreadDispatcher(unsigned int poolSize) : _poolSize(poolSize) {
        _pool.reserve(_poolSize);
    }

    std::shared_ptr<ledger::core::api::ExecutionContext> UvThreadDispatcher::getSerialExecutionContext(const std::string &name) {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _contextsByName.find(name);
        if (it == _contextsByName.end()) {
            auto context = std::make_shared<SequentialExecutionContext>();
            _pool.push_back(context);
            _contextsByName[name] = context;
            return context;
        }
        return it->second;
    }

    std::shared_ptr<ledger::core::api::ExecutionContext> UvThreadDispatcher::getThreadPoolExecutionContext(const std::string &name) {
        std::unique_lock<std::mutex> lock(_mutex);
        auto it = _threadpoolContextsByName.find(name);
        if (it == _threadpoolContextsByName.end()) {
            auto context = std::make_shared<ThreadpoolExecutionContext>();
            _threadpoolContextsByName[name] = context;
            return context;
        }
        return it->second;
    }

    std::shared_ptr<ledger::core::api::ExecutionContext> UvThreadDispatcher::getMainExecutionContext() {
        return getSerialExecutionContext("__uv__main__");
    }

    std::shared_ptr<ledger::core::api::Lock> UvThreadDispatcher::newLock() {
        return nullptr;
    }

    UvThreadDispatcher::~UvThreadDispatcher() {
        std::unique_lock<std::mutex> lock(_mutex);
        for (auto& context : _pool) {
            context->stop();
            context->waitUntilStopped();
        }
    }

    void UvThreadDispatcher::waitUntilStopped() {
        auto context = std::dynamic_pointer_cast<SequentialExecutionContext>(
            getSerialExecutionContext("__uv__main__"));
        context->waitUntilStopped();
    }

    void UvThreadDispatcher::stop() {
        auto context = std::dynamic_pointer_cast<SequentialExecutionContext>(
            getSerialExecutionContext("__uv__main__"));
        context->stop();
    }


    std::shared_ptr<ledger::core::api::ThreadDispatcher> createDispatcher() {
        return std::make_shared<UvThreadDispatcher>();
    }

}

<|start_filename|>core/src/wallet/bitcoin/BitcoinLikeWallet.cpp<|end_filename|>
/*
 *
 * BitcoinLikeWallet
 * ledger-core
 *
 * Created by <NAME> on 19/12/2016.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "BitcoinLikeWallet.hpp"
#include <api/ErrorCode.hpp>
#include <api/AccountCallback.hpp>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <api/ConfigurationDefaults.hpp>
#include <api/KeychainEngines.hpp>
#include <wallet/bitcoin/database/BitcoinLikeAccountDatabaseHelper.h>
#include "BitcoinLikeAccount.hpp"
#include <algorithm>
#include <async/wait.h>

namespace ledger {
    namespace core {

        const api::WalletType BitcoinLikeWallet::type = api::WalletType::BITCOIN;

        BitcoinLikeWallet::BitcoinLikeWallet(const std::string &name,
                                             const std::shared_ptr<BitcoinLikeBlockchainExplorer>& explorer,
                                             const std::shared_ptr<BitcoinLikeKeychainFactory> &keychainFactory,
                                             const BitcoinLikeAccountSynchronizerFactory &synchronizer,
                                             const std::shared_ptr<WalletPool> &pool, const api::Currency &network,
                                             const std::shared_ptr<DynamicObject>& configuration,
                                             const DerivationScheme& scheme
        )
        : AbstractWallet(name, network, pool, configuration, scheme) {
            _explorer = explorer;
            _keychainFactory = keychainFactory;
            _synchronizerFactory = synchronizer;
        }

        bool BitcoinLikeWallet::isSynchronizing() {
            return false;
        }

        std::shared_ptr<api::EventBus> BitcoinLikeWallet::synchronize() {
            return nullptr;
        }

        FuturePtr<ledger::core::api::Account>
        BitcoinLikeWallet::newAccountWithInfo(const api::AccountCreationInfo &info) {
            // TODO: Update data structure to be able to do P2SH with mixed HD and Solo keys.
            // Right now we only handle Full HD P2SH wallet.
            // For each owner
                // Get the pair of keys
                // Create extended key
                // Serialize and store
            auto self = getSelf();
            return async<api::ExtendedKeyAccountCreationInfo>([self, info] () -> api::ExtendedKeyAccountCreationInfo {
                if (info.owners.size() != info.derivations.size() || info.owners.size() != info.chainCodes.size() ||
                    info.publicKeys.size() != info.owners.size())
                    throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Account creation info are inconsistent (size of arrays differs)");
                api::ExtendedKeyAccountCreationInfo result;
                std::set<std::string> ownersSet(info.owners.begin(), info.owners.end());
                auto ownersIterator = ownersSet.begin();
                auto ownersEndIterator = ownersSet.end();
                auto size = info.owners.size();
                while (ownersIterator != ownersEndIterator) {
                    int32_t firstOccurence = -1;
                    int32_t secondOccurence = -1;

                    for (auto i = 0; i < size; i++) {
                        if (info.owners[i] != *ownersIterator) continue;
                        if (info.publicKeys[i].size() != 33 && info.publicKeys[i].size() != 65)
                            throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Account creation info are inconsistent (contains invalid public key(s))");
                        if (firstOccurence == -1) {
                            firstOccurence = i;
                        } else {
                            secondOccurence = i;
                            break;
                        }
                    }
                    if (firstOccurence == -1 || secondOccurence == -1)
                        throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Account creation info are inconsistent (missing derivation(s))");
                    DerivationPath firstOccurencePath(info.derivations[firstOccurence]);
                    DerivationPath secondOccurencePath(info.derivations[secondOccurence]);
                    if (secondOccurencePath.getParent() != firstOccurencePath) {
                        std::swap(firstOccurencePath, secondOccurencePath);
                        std::swap(firstOccurence, secondOccurence);
                    }
                    if (secondOccurencePath.getParent() != firstOccurencePath)
                        throw make_exception(api::ErrorCode::INVALID_ARGUMENT, "Account creation info are inconsistent (wrong paths)");
                    auto xpub = BitcoinLikeExtendedPublicKey::fromRaw(self->getCurrency(),
                                                                      Option<std::vector<uint8_t>>(info.publicKeys[firstOccurence]).toOptional(),
                                                                      info.publicKeys[secondOccurence],
                                                                      info.chainCodes[secondOccurence],
                                                                      info.derivations[secondOccurence],
                                                                      self->getConfig());
                    result.owners.push_back(*ownersIterator);
                    result.derivations.push_back(info.derivations[secondOccurence]);
                    result.extendedKeys.push_back(xpub->toBase58());
                    ownersIterator++;
                }
                result.index = info.index;
                return result;
            }).flatMap<std::shared_ptr<ledger::core::api::Account>>(getContext(), [self] (const api::ExtendedKeyAccountCreationInfo& info) -> Future<std::shared_ptr<ledger::core::api::Account>> {
                return self->newAccountWithExtendedKeyInfo(info);
            });
        }

        FuturePtr<ledger::core::api::Account>
        BitcoinLikeWallet::newAccountWithExtendedKeyInfo(const api::ExtendedKeyAccountCreationInfo &info) {
            auto self = getSelf();
            auto scheme = getDerivationScheme();
            scheme.setCoinType(getCurrency().bip44CoinType).setAccountIndex(info.index);
            auto xpubPath = scheme.getSchemeTo(DerivationSchemeLevel::ACCOUNT_INDEX).getPath();
            auto index = info.index;
            return async<std::shared_ptr<api::Account> >([=] () -> std::shared_ptr<api::Account> {
                auto keychain = self->_keychainFactory->build(
                        index,
                        xpubPath,
                        getConfig(),
                        info,
                        getAccountInternalPreferences(index),
                        getCurrency()
                );
                soci::session sql(self->getDatabase()->getPool());
                soci::transaction tr(sql);
                auto accountUid = AccountDatabaseHelper::createAccountUid(self->getWalletUid(), index);
                if (AccountDatabaseHelper::accountExists(sql, self->getWalletUid(), index))
                    throw make_exception(api::ErrorCode::ACCOUNT_ALREADY_EXISTS, "Account {}, for wallet '{}', already exists", index, self->getWalletUid());
                AccountDatabaseHelper::createAccount(sql, self->getWalletUid(), index);
                BitcoinLikeAccountDatabaseHelper::createAccount(sql, self->getWalletUid(), index, keychain->getRestoreKey());
                tr.commit();
                auto account = std::static_pointer_cast<api::Account>(std::make_shared<BitcoinLikeAccount>(
                        self->shared_from_this(),
                        index,
                        self->_explorer,
                        self->_synchronizerFactory(),
                        keychain
                ));
                self->addAccountInstanceToInstanceCache(std::dynamic_pointer_cast<AbstractAccount>(account));
                return account;
            });
        }

        Future<api::ExtendedKeyAccountCreationInfo>
        BitcoinLikeWallet::getExtendedKeyAccountCreationInfo(int32_t accountIndex) {
            auto self = std::dynamic_pointer_cast<BitcoinLikeWallet>(shared_from_this());
            return async<api::ExtendedKeyAccountCreationInfo>([self, accountIndex] () -> api::ExtendedKeyAccountCreationInfo {
                api::ExtendedKeyAccountCreationInfo info;
                info.index = accountIndex;
                auto scheme = self->getDerivationScheme();
                scheme.setCoinType(self->getCurrency().bip44CoinType).setAccountIndex(accountIndex);;
                auto keychainEngine = self->getConfiguration()->getString(api::Configuration::KEYCHAIN_ENGINE).value_or(api::ConfigurationDefaults::DEFAULT_KEYCHAIN);
                if (keychainEngine == api::KeychainEngines::BIP32_P2PKH ||
                    keychainEngine == api::KeychainEngines::BIP49_P2SH ||
                    keychainEngine == api::KeychainEngines::BIP173_P2WPKH ||
                    keychainEngine == api::KeychainEngines::BIP173_P2WSH) {
                    auto xpubPath = scheme.getSchemeTo(DerivationSchemeLevel::ACCOUNT_INDEX).getPath();
                    info.derivations.push_back(xpubPath.toString());
                    info.owners.push_back(std::string("main"));
                } else {
                    throw make_exception(api::ErrorCode::IMPLEMENTATION_IS_MISSING, "No implementation found found for keychain {}", keychainEngine);
                }

                return info;
            });
        }

        Future<api::AccountCreationInfo> BitcoinLikeWallet::getAccountCreationInfo(int32_t accountIndex) {
            auto self = std::dynamic_pointer_cast<BitcoinLikeWallet>(shared_from_this());
            return getExtendedKeyAccountCreationInfo(accountIndex).map<api::AccountCreationInfo>(getContext(), [self, accountIndex] (const api::ExtendedKeyAccountCreationInfo info) -> api::AccountCreationInfo {
                api::AccountCreationInfo result;
                result.index = accountIndex;
                auto length = info.derivations.size();
                for (auto i = 0; i < length; i++) {
                    DerivationPath path(info.derivations[i]);
                    auto owner = info.owners[i];
                    result.derivations.push_back(path.getParent().toString());
                    result.derivations.push_back(path.toString());
                    result.owners.push_back(owner);
                    result.owners.push_back(owner);
                }
                return result;
            });
        }

        std::shared_ptr<BitcoinLikeWallet> BitcoinLikeWallet::getSelf() {
            return std::dynamic_pointer_cast<BitcoinLikeWallet>(shared_from_this());
        }

        std::shared_ptr<AbstractAccount>
        BitcoinLikeWallet::createAccountInstance(soci::session &sql, const std::string &accountUid) {
            BitcoinLikeAccountDatabaseEntry entry;
            BitcoinLikeAccountDatabaseHelper::queryAccount(sql, accountUid, entry);
            auto scheme = getDerivationScheme();
            scheme.setCoinType(getCurrency().bip44CoinType).setAccountIndex(entry.index);
            auto xpubPath = scheme.getSchemeTo(DerivationSchemeLevel::ACCOUNT_INDEX).getPath();
            auto keychain = _keychainFactory->restore(entry.index, xpubPath, getConfig(), entry.xpub,
            getAccountInternalPreferences(entry.index), getCurrency());
            return std::make_shared<BitcoinLikeAccount>(shared_from_this(),
                                                        entry.index,
                                                        _explorer,
                                                        _synchronizerFactory(),
                                                        keychain);
        }

        std::shared_ptr<BitcoinLikeBlockchainExplorer> BitcoinLikeWallet::getBlockchainExplorer() {
            return _explorer;
        }

    }
}

<|start_filename|>core/test/lib/libledger-test/CppHttpLibClient.cpp<|end_filename|>
#include <memory>
#include <chrono>
#include <unordered_map>
#include <fmt/format.h>
#include "api/Error.hpp"
#include "api/HttpRequest.hpp"

#include <api/HttpUrlConnection.hpp>
#include <api/HttpReadBodyResult.hpp>
#include <api/HttpMethod.hpp>

#include <ledger/core/utils/Exception.hpp>
#include "NativeThreadDispatcher.hpp"
#include "CppHttpLibClient.hpp"

namespace ledger {
    namespace core {
        namespace test {

            class UrlConnection : public core::api::HttpUrlConnection {
            public:
                UrlConnection(int32_t statusCode,
                            std::string statusText,
                            std::unordered_map<std::string, std::string> headers,
                            std::string body
                ) {
                    _statusCode = statusCode;
                    _statusText = statusText;
                    _body = body;
                    _headers = headers;
                }

                int32_t getStatusCode() override {
                    return _statusCode;
                }

                std::string getStatusText() override {
                    return _statusText;
                }

                std::unordered_map<std::string, std::string> getHeaders() override {
                    return _headers;
                }

                core::api::HttpReadBodyResult readBody() override {
                    //std::vector<uint8_t> body((uint8_t *)_body.c_str(), (uint8_t *)(_body.c_str() + _body.size()));
                    std::vector<uint8_t> body(_body.begin(), _body.end());
                    _body.clear();
                    return core::api::HttpReadBodyResult(
                    std::experimental::optional<core::api::Error>(),
                    std::experimental::optional<std::vector<uint8_t>>(body)
                    );
                }

            private:
                int32_t _statusCode;
                std::string _statusText;
                std::unordered_map<std::string, std::string> _headers;
                std::string _body;
            };

            void generateCacheFile(const std::string& url, const std::string& response)
            {
                auto filename = fmt::format("http_cache_{}.hpp" ,std::chrono::system_clock::now().time_since_epoch().count());
                std::ofstream output(filename);
                if (output.is_open())
                {
                    output << "namespace HTTP_CACHE_XXXX {"<< std::endl;
                    output << "const std::string URL = R\"(" << url << ")\";" << std::endl;
                    output << "const std::string BODY= R\"(" << response << ")\";" << std::endl;
                    output << "}" << std::endl;
                }
            }

            void CppHttpLibClient::execute(const std::shared_ptr<core::api::HttpRequest> &request) {
            _context->execute(make_runnable([=] () {
                auto url = request->getUrl();
                std::size_t sep;
                std::size_t sepIgnored = url.find("//");
                if (sepIgnored == std::string::npos) {
                    sep = url.find('/');
                }
                else {
                    sep = url.find('/', sepIgnored+2);
                }
                auto baseUrl = url.substr(0, sep);
                auto path = url.substr(sep);
                
                httplib::Client cli(baseUrl.c_str());
                httplib::Headers headers;
                for (const auto& header : request->getHeaders()) {
                    headers.insert(std::make_pair(header.first, header.second));
                }
                cli.set_default_headers(headers);
                std::string body;
                if (request->getBody().size() > 0) {
                    std::stringstream bodyStream;
                    for (auto byte : request->getBody()) {
                        bodyStream << (char )byte;
                    }
                    body = bodyStream.str();
                }

                if (_logger) { 
                    _logger->debug("CppHttpLibClient: quering {} {}", api::to_string(request->getMethod()), request->getUrl());
                }
                
                httplib::Result res(nullptr, httplib::Error::Unknown);
                switch(request->getMethod())
                {
                    case core::api::HttpMethod::POST:
                        res = cli.Post(path.c_str(), body, "text/plain");
                    break;
                    case core::api::HttpMethod::GET:
                        res = cli.Get(path.c_str());
                    break;
                    case core::api::HttpMethod::PUT:
                        res = cli.Put(path.c_str(), body, "text/plain");
                    break;
                    case core::api::HttpMethod::DEL:
                        res = cli.Delete(path.c_str());
                    break;
                    default:
                        throw make_exception(api::ErrorCode::RUNTIME_ERROR, "unknown http method");
                }
                
                if (_logger) {
                    _logger->debug("CppHttpLibClient: done {} {} - {}", api::to_string(request->getMethod()), request->getUrl(), res->status);
                }

                if (res.error() != httplib::Error::Success) {
                    request->complete(nullptr, std::experimental::optional<core::api::Error>(
                        core::api::Error(core::api::ErrorCode::HTTP_ERROR, 
                        "HTTP ERROR")));       
                }
                else if (res->status < 200 || res->status >= 300 ) {
                    std::unordered_map<std::string, std::string> hdrs;
                    for (const auto& header : res->headers) {
                        hdrs.insert(std::make_pair(header.first, header.second));
                    }
                    auto connection = std::make_shared<UrlConnection>(
                        res->status,
                        res->reason,
                        hdrs,
                        res->body
                        );
                    request->complete(connection, std::experimental::optional<core::api::Error>(
                        core::api::Error(core::api::ErrorCode::HTTP_ERROR, 
                        fmt::format("{}: {}", res->status, res->reason)))); 
                }
                else {
                    std::unordered_map<std::string, std::string> hdrs;
                    for (const auto& header : res->headers) {
                        hdrs.insert(std::make_pair(header.first, header.second));
                    }
                    auto connection = std::make_shared<UrlConnection>(
                        res->status,
                        res->reason,
                        hdrs,
                        res->body
                        );
                    if (_generateCacheFile) {
                        generateCacheFile(url, res->body);
                    }
                    request->complete(connection, std::experimental::optional<core::api::Error>());
                }
            })); 
        }
    
        }
    }
}

<|start_filename|>core/src/metrics/DurationsMap.cpp<|end_filename|>
/*
 *
 * DurationsMap.cpp
 * ledger-core
 *
 * Created by <NAME> on 23/12/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "DurationsMap.hpp"
#include <api/DurationMetrics.hpp>

namespace ledger {
    namespace core {


        DurationsMap &DurationsMap::getInstance() {
            static DurationsMap instance;
            return instance;
        }

        void
        DurationsMap::record(const std::string &name, const std::chrono::high_resolution_clock::duration &duration) {
            std::lock_guard<std::mutex> lock(_mutex);
            auto existing = _metrics.find(name);
            if (existing != _metrics.end()) {
                existing->second.count += 1;
                existing->second.total_ms += static_cast<int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count());
            } else {
                _metrics[name] = api::DurationMetric(
                        static_cast<int64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()),
                        1
                );
            }
        }

        const std::unordered_map<std::string, api::DurationMetric>& DurationsMap::getMetrics() {
            return _metrics;
        }

        std::unordered_map<std::string, api::DurationMetric> api::DurationMetrics::getAllDurationMetrics() {
            return DurationsMap::getInstance().getMetrics();
        }

    }
}

<|start_filename|>core/test/bitcoin/bitcoin_utxo_picket_tests.cpp<|end_filename|>
#include <gtest/gtest.h>
#include <gmock/gmock.h>
#include <api/KeychainEngines.hpp>
#include <api/BitcoinLikeScript.hpp>
#include <ledger/core/api/Networks.hpp>
#include <wallet/currencies.hpp>
#include <wallet/common/Amount.h>
#include <wallet/bitcoin/transaction_builders/BitcoinLikeStrategyUtxoPicker.h>
#include <spdlog/sinks/null_sink.h>


using namespace ledger::core;

class MockKeychain : public BitcoinLikeKeychain {
public:
    MockKeychain(
        const std::shared_ptr<api::DynamicObject>& configuration,
        const api::Currency& params,
        int account,
        const std::shared_ptr<Preferences>& preferences) :BitcoinLikeKeychain(configuration, params, account, preferences){};
    MOCK_METHOD2(markPathAsUsed, bool(const DerivationPath& path, bool needExtendKeychain));
    MOCK_METHOD2(getAllObservableAddresses, std::vector<Address>(uint32_t from, uint32_t to));

    MOCK_METHOD3(getAllObservableAddresses, std::vector<Address>(KeyPurpose purpose, uint32_t from, uint32_t to));
    MOCK_METHOD2(getAllObservableAddressString, std::vector<std::string>(uint32_t from, uint32_t to));

    MOCK_METHOD1(getFreshAddress, Address(KeyPurpose purpose));
    MOCK_METHOD2(getFreshAddresses, std::vector<Address>(KeyPurpose purpose, size_t n));

    MOCK_METHOD0(getAllAddresses, std::vector<Address>());

    MOCK_CONST_METHOD1(getAddressPurpose, Option<KeyPurpose>(const std::string& address));
    MOCK_CONST_METHOD1(getAddressDerivationPath, Option<std::string>(const std::string& address));
    MOCK_CONST_METHOD0(isEmpty, bool());

    MOCK_CONST_METHOD1(getPublicKey, Option<std::vector<uint8_t>>(const std::string& address));

    MOCK_CONST_METHOD0(getRestoreKey, std::string());
    MOCK_CONST_METHOD0(getObservableRangeSize, int32_t());
    MOCK_CONST_METHOD1(contains, bool(const std::string& address));
    MOCK_CONST_METHOD0(getOutputSizeAsSignedTxInput, int32_t());
};

class MockBitcoinLikeScript : public api::BitcoinLikeScript {
public:
    MOCK_METHOD0(head, std::shared_ptr<api::BitcoinLikeScriptChunk>());
    MOCK_METHOD0(toString, std::string());
};

class MockBitcoinLikeOutput : public api::BitcoinLikeOutput {
public:
    MockBitcoinLikeOutput(int64_t amount) : _amount(std::make_shared<Amount>(currencies::BITCOIN, 0, BigInt(amount))) {};

    std::string getTransactionHash() { return _amount->toString(); };

    int32_t getOutputIndex() { return 0; };

    std::shared_ptr<api::Amount> getValue() {
        return _amount;
    }

    MOCK_METHOD0(getScript, std::vector<uint8_t>());

    MOCK_METHOD0(parseScript, std::shared_ptr<api::BitcoinLikeScript>());

    MOCK_METHOD0(getAddress, std::experimental::optional<std::string>());

    MOCK_METHOD0(getDerivationPath, std::shared_ptr<api::DerivationPath>());

    MOCK_METHOD0(getBlockHeight, std::experimental::optional<int64_t>());

    MOCK_CONST_METHOD0(isReplaceable, bool());
private:
    std::shared_ptr<Amount> _amount;
};

std::vector<BitcoinLikeUtxo> createUtxos(const std::vector<int64_t>& values) {
    std::vector<BitcoinLikeUtxo> utxos;

    utxos.reserve(values.size());

    std::transform(values.cbegin(), values.cend(), std::back_inserter(utxos), [](auto const &value) {
        auto amount = Amount(currencies::BITCOIN, 0, BigInt(value));

        return BitcoinLikeUtxo{
            0,
            amount.toString(),
            amount,
            Option<std::string>{},
            Option<std::string>{},
            "",
            Option<uint64_t>{}
        };
    });

    return utxos;
}

std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> createBuddy(int64_t feesPerByte, int64_t outputAmount, const api::Currency& currency) {
    BitcoinLikeTransactionBuildRequest r(std::make_shared<BigInt>(0));
    r.wipe = false;
    r.feePerByte = std::make_shared<BigInt>(feesPerByte);
    r.outputs.push_back(std::make_tuple(std::make_shared<BigInt>(outputAmount), std::make_shared<MockBitcoinLikeScript>()));
    r.utxoPicker = BitcoinUtxoPickerParams{api::BitcoinLikePickingStrategy::OPTIMIZE_SIZE, 0, optional<int32_t>()};

    BitcoinLikeGetUtxoFunction g;
    BitcoinLikeGetTxFunction tx;
    std::shared_ptr<BitcoinLikeBlockchainExplorer> e;
    auto config = std::make_shared<ledger::core::DynamicObject>();
    config->putString(api::Configuration::KEYCHAIN_ENGINE, api::KeychainEngines::BIP32_P2PKH);
    std::shared_ptr<Preferences> preferences;
    std::shared_ptr<MockKeychain> k = std::make_shared<MockKeychain>(config, currency, 0, preferences);
    static std::shared_ptr<spdlog::logger> l = spdlog::null_logger_mt("null_sink");
    std::shared_ptr<BitcoinLikeTransactionApi> t;
    return std::make_shared<BitcoinLikeUtxoPicker::Buddy>(r, g, tx, e, k, l, t, false);
}

TEST(OptimizeSize, BacktrackingCalculateChangeCorrectly) {
    const api::Currency currency = currencies::BITCOIN;
    const int64_t feesPerByte = 20;
    const int64_t inputSizeInBytes = 148;
    const int64_t outputSizeInBytes = 34;
    const int64_t emtyTransactionSizeInBytes = 10;
    int64_t outputAmount = 25000;
    std::vector<int64_t> inputAmounts{16500, 16500};
    
    auto buddy = createBuddy(feesPerByte, outputAmount, currency);

    auto utxos = createUtxos(inputAmounts);
    auto pickedUtxos = BitcoinLikeStrategyUtxoPicker::filterWithOptimizeSize(buddy, utxos, BigInt(-1), currency);
    int64_t totalInputsValue = 0;
    for (auto utxo : pickedUtxos) {
        totalInputsValue += utxo.value.toLong();
    }
    int64_t transactionFees = totalInputsValue - buddy->changeAmount.toInt64() - outputAmount;
    int64_t minimumRequiredFees = (emtyTransactionSizeInBytes + outputSizeInBytes * 2 + inputSizeInBytes * pickedUtxos.size()) * feesPerByte;
    EXPECT_GE(transactionFees, minimumRequiredFees);
    if (buddy->changeAmount.toInt64() != 0)
        EXPECT_GE(buddy->changeAmount.toInt64(), inputSizeInBytes * feesPerByte);
}

TEST(OptimizeSize, ChangeShouldBeBigEnoughToSpend) {
    const api::Currency currency = currencies::BITCOIN;
    const int64_t feesPerByte = 20;
    const int64_t inputSizeInBytes = 148;
    const int64_t outputSizeInBytes = 34;
    const int64_t emtyTransactionSizeInBytes = 10;
    int64_t outputAmount = 25000;
    std::vector<int64_t> inputAmounts{ 19090, 19090};

    auto buddy = createBuddy(feesPerByte, outputAmount, currency);

    auto utxos = createUtxos(inputAmounts);
    auto pickedUtxos = BitcoinLikeStrategyUtxoPicker::filterWithOptimizeSize(buddy, utxos, BigInt(-1), currency);
    int64_t totalInputsValue = 0;
    for (auto utxo : pickedUtxos) {
        totalInputsValue += utxo.value.toLong();
    }
    int64_t transactionFees = totalInputsValue - buddy->changeAmount.toInt64() - outputAmount;
    int64_t minimumRequiredFees = (emtyTransactionSizeInBytes + outputSizeInBytes * 2 + inputSizeInBytes * pickedUtxos.size()) * feesPerByte;
    EXPECT_GE(transactionFees, minimumRequiredFees);
    if (buddy->changeAmount.toInt64() != 0)
        EXPECT_GE(buddy->changeAmount.toInt64(), inputSizeInBytes * feesPerByte);
}

TEST(OptimizeSize, ApproximationShouldTookEnough) {
    const api::Currency currency = currencies::BITCOIN;
    const int64_t feesPerByte = 20;
    const int64_t inputSizeInBytes = 148;
    const int64_t outputSizeInBytes = 34;
    const int64_t emtyTransactionSizeInBytes = 10;
    int64_t outputAmount = 25000;
    std::vector<int64_t> inputAmounts{ 15000, 15000, 15000 };

    auto buddy = createBuddy(feesPerByte, outputAmount, currency);

    auto utxos = createUtxos(inputAmounts);
    auto pickedUtxos = BitcoinLikeStrategyUtxoPicker::filterWithOptimizeSize(buddy, utxos, BigInt(-1), currency);
    int64_t totalInputsValue = 0;
    for (auto utxo : pickedUtxos) {
        totalInputsValue += utxo.value.toLong();
    }
    int64_t transactionFees = totalInputsValue - buddy->changeAmount.toInt64() - outputAmount;
    int64_t minimumRequiredFees = (emtyTransactionSizeInBytes + outputSizeInBytes * 2 + inputSizeInBytes * pickedUtxos.size()) * feesPerByte;
    EXPECT_GE(transactionFees, minimumRequiredFees);
    if (buddy->changeAmount.toInt64() != 0)
        EXPECT_GE(buddy->changeAmount.toInt64(), inputSizeInBytes * feesPerByte);
}


<|start_filename|>core/test/lib/libledger-test/MemPreferencesBackend.hpp<|end_filename|>
#pragma once

#include <memory>
#include <map>
#include <api/PreferencesBackend.hpp>
#include <api/PreferencesChange.hpp>

namespace ledger {
    namespace core {
        namespace test {
            class MemPreferencesBackend : public api::PreferencesBackend {
            public:
                virtual ~MemPreferencesBackend();

                virtual std::experimental::optional<std::vector<uint8_t>> get(const std::vector<uint8_t> & key) override;

                virtual bool commit(const std::vector<api::PreferencesChange> & changes) override;

                virtual void setEncryption(const std::shared_ptr<api::RandomNumberGenerator> & rng, const std::string & password) override;

                virtual void unsetEncryption() override;

                virtual bool resetEncryption(const std::shared_ptr<api::RandomNumberGenerator> & rng, const std::string & oldPassword, const std::string & newPassword) override;

                virtual std::string getEncryptionSalt() override;

                virtual void clear() override;

            private:
                std::map<std::vector<uint8_t>, std::vector<uint8_t>> _data;
            };
        }
    }
}



<|start_filename|>core/src/database/DatabaseSessionPool.cpp<|end_filename|>
/*
 *
 * DatabaseSessionPool
 * ledger-core
 *
 * Created by <NAME> on 24/04/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "DatabaseSessionPool.hpp"
#include "migrations.hpp"
#include "SQLite3Backend.hpp"
#ifdef PG_SUPPORT
    #include "PostgreSQLBackend.h"
#endif

namespace ledger {
    namespace core {
        DatabaseSessionPool::DatabaseSessionPool(
            const std::shared_ptr<DatabaseBackend> &backend,
            const std::shared_ptr<api::PathResolver> &resolver,
            const std::shared_ptr<spdlog::logger>& logger,
            const std::string &dbName,
            const std::string &password) :
            _pool((size_t) backend->getConnectionPoolSize()), _readonlyPool((size_t)backend->getReadonlyConnectionPoolSize()), _backend(backend), _buffer("SQL", logger) {
            if (logger != nullptr && backend->isLoggingEnabled()) {
                _logger = new std::ostream(&_buffer);
            } else {
                _logger = nullptr;
            }

            auto poolSize = _backend->getConnectionPoolSize();
            for (size_t i = 0; i < poolSize; i++) {
                auto& session = getPool().at(i);
                _backend->init(resolver, dbName, password, session);
                if (_logger != nullptr) {
                    session.set_log_stream(_logger);
                }
            }

#ifdef PG_SUPPORT
            _type = std::dynamic_pointer_cast<PostgreSQLBackend>(backend) != nullptr ?
                    api::DatabaseBackendType::POSTGRESQL : api::DatabaseBackendType::SQLITE3;
#else
            _type = api::DatabaseBackendType::SQLITE3;
#endif
            // Migrate database
            performDatabaseMigration();
            auto readonlyPoolSize = _backend->getReadonlyConnectionPoolSize();
            for (size_t i = 0; i < readonlyPoolSize; i++) {
                auto& session = getReadonlyPool().at(i);
                _backend->init(resolver, dbName, password, session);
                if (_logger != nullptr) {
                    session.set_log_stream(_logger);
                }
            }
        }

        DatabaseSessionPool::~DatabaseSessionPool() {
            delete _logger;
        }

        FuturePtr<DatabaseSessionPool>
        DatabaseSessionPool::getSessionPool(const std::shared_ptr<api::ExecutionContext> &context,
                                            const std::shared_ptr<DatabaseBackend> &backend,
                                            const std::shared_ptr<api::PathResolver> &resolver,
                                            const std::shared_ptr<spdlog::logger> &logger,
                                            const std::string &dbName,
                                            const std::string &password) {
            return FuturePtr<DatabaseSessionPool>::async(context, [backend, resolver, dbName, logger, password] () {
                auto pool = std::shared_ptr<DatabaseSessionPool>(new DatabaseSessionPool(
                    backend, resolver, logger, dbName, password
                ));

                return pool;
            });
        }

        soci::connection_pool &DatabaseSessionPool::getPool() {
            return _pool;
        }

        soci::connection_pool& DatabaseSessionPool::getReadonlyPool() {
            return _readonlyPool;
        }

        void DatabaseSessionPool::performDatabaseMigration() {
            soci::session sql(getPool());
            int version = getDatabaseMigrationVersion(sql);

            soci::transaction tr(sql);
            migrate<CURRENT_DATABASE_SCHEME_VERSION>(sql, version, _type);
            tr.commit();
        }

        void DatabaseSessionPool::performDatabaseRollback() {
            soci::session sql(getPool());
            int version = getDatabaseMigrationVersion(sql);

            soci::transaction tr(sql);
            rollback<CURRENT_DATABASE_SCHEME_VERSION>(sql, version, _type);
            tr.commit();
        }

        void DatabaseSessionPool::performChangePassword(const std::string &oldPassword,
                                                        const std::string &newPassword) {
            auto poolSize = _backend->getConnectionPoolSize();
            for (size_t i = 0; i < poolSize; i++) {
                auto& session = getPool().at(i);
                _backend->changePassword(oldPassword, newPassword, session);
            }
        }

        bool DatabaseSessionPool::isSqlite() const {
            return std::dynamic_pointer_cast<SQLite3Backend>(_backend) != nullptr;
        }

        bool DatabaseSessionPool::isPostgres() const {
#ifndef PG_SUPPORT
            return false;
#else
            return std::dynamic_pointer_cast<PostgreSQLBackend>(_backend) != nullptr;
#endif
        }
    }
}


<|start_filename|>core/test/lib/libledger-test/MemPreferencesBackend.cpp<|end_filename|>

#include <MemPreferencesBackend.hpp>
#include "utils/optional.hpp"

namespace ledger {
    namespace core {
        namespace test {
            MemPreferencesBackend::~MemPreferencesBackend() {}

            std::experimental::optional<std::vector<uint8_t>> MemPreferencesBackend::get(const std::vector<uint8_t> & key)
            {
               if (_data.find(key) != _data.end()) 
                    return _data.at(key);
                return optional<std::vector<uint8_t>>();
            }

            bool MemPreferencesBackend::commit(const std::vector<api::PreferencesChange> & changes)
            {
                for(const auto& change: changes)
                {
                    _data[change.key] = change.value;
                }
                return true;
            }

            void MemPreferencesBackend::setEncryption(const std::shared_ptr<api::RandomNumberGenerator> & rng, const std::string & password)
            {
            }

            void MemPreferencesBackend::unsetEncryption()
            {
            }

            bool MemPreferencesBackend::resetEncryption(const std::shared_ptr<api::RandomNumberGenerator> & rng, const std::string & oldPassword, const std::string & newPassword)
            {
                return true;
            }

            std::string MemPreferencesBackend::getEncryptionSalt()
            {
                return "";
            }

            void MemPreferencesBackend::clear()
            {
                _data.clear();
            }
            
        }
    }
}




<|start_filename|>core/src/wallet/tezos/database/TezosLikeOperationDatabaseHelper.cpp<|end_filename|>
/*
 *
 * TezosLikeOperationDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 07/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "TezosLikeOperationDatabaseHelper.hpp"
#include <database/PreparedStatement.hpp>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <api/BigInt.hpp>
#include <crypto/SHA256.hpp>
#include <unordered_set>
#include <debug/Benchmarker.h>
#include <wallet/tezos/database/TezosLikeTransactionDatabaseHelper.h>
#include <wallet/tezos/database/TezosLikeAccountDatabaseHelper.h>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>

using namespace soci;

namespace {
    using namespace ledger::core;

    // Tezos operations
    struct TezosOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> txHash;

        void update(const std::string& opUid, const std::string& transactionUid,
                const std::string& transactionHash) {
            uid.push_back(opUid);
            txUid.push_back(transactionUid);
            txHash.push_back(transactionHash);
        }

        void clear() {
            uid.clear();
            txUid.clear();
            txHash.clear();
        }
    };

    const auto UPSERT_TEZOS_OPERATION = db::stmt<TezosOperationBinding>(
            "INSERT INTO tezos_operations VALUES(:uid, :tx_uid, :tx_hash) "
            "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, use(b.uid), use(b.txUid), use(b.txHash);
            });

    // Tezos originated operations
    struct TezosOriginatedOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> originatedAccountUid;

        void update(const std::string& opUid, const std::string& transactionUid,
                const std::string& origAccountUid) {
            uid.push_back(opUid);
            txUid.push_back(transactionUid);
            originatedAccountUid.push_back(origAccountUid);
        }

        void clear() {
            uid.clear();
            txUid.clear();
            originatedAccountUid.clear();
        }
    };

    const auto UPSERT_TEZOS_ORIGINATED_OPERATION = db::stmt<TezosOriginatedOperationBinding>(
            "INSERT INTO tezos_originated_operations VALUES(:uid, :transaction_uid, :originated_account_uid)"
            "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, use(b.uid), use(b.txUid), use(b.originatedAccountUid);
            });

    //tezos originated account
    struct TezosOriginatedAccountBinding {
        std::vector<std::string> uid;
        std::vector<std::string> accountUid;
        std::vector<std::string> address;
        std::vector<int> spendable;
        std::vector<int> delegatable;
        std::vector<std::string> pubKey;

        void update(const Operation &operation) {
            auto transaction = operation.tezosTransaction.getValue();

            if (transaction.originatedAccountUid.empty()) { //insertion
                auto origAccount = transaction.originatedAccount.getValue();
                uid.push_back(TezosLikeAccountDatabaseHelper::createOriginatedAccountUid(operation.accountUid, origAccount.address));
                address.push_back(origAccount.address);
                spendable.push_back(static_cast<int>(origAccount.spendable));
                delegatable.push_back(static_cast<int>(origAccount.delegatable));
            }
            else { //update: only pubkey will be updated
                uid.push_back(transaction.originatedAccountUid);
                address.push_back("");
                spendable.push_back(false);
                delegatable.push_back(false);
            }
         
            accountUid.push_back(operation.accountUid);
            pubKey.push_back(transaction.publicKey.getValueOr(""));
        }

        void clear() {
            uid.clear();
            accountUid.clear();
            address.clear();
            spendable.clear();
            delegatable.clear();
            pubKey.clear();
        }
    };

    const auto UPSERT_TEZOS_ORIGINATED_ACCOUNT = db::stmt<TezosOriginatedAccountBinding>(
            "INSERT INTO tezos_originated_accounts VALUES("
            ":uid, :tezos_account_uid, :address, :spendable, :delegatable, :public_key)"
            "ON CONFLICT(uid) DO UPDATE SET public_key = :pub_key",
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid),
                use(b.accountUid),
                use(b.address),
                use(b.spendable),
                use(b.delegatable),
                use(b.pubKey),
                use(b.pubKey);
            });

    // Transaction
    struct TransactionBinding {
        std::vector<std::string> uid;
        std::vector<std::string> hash;
        std::vector<std::string> value;
        std::vector<std::string> blockUid;
        std::vector<std::chrono::system_clock::time_point> time;
        std::vector<std::string> sender;
        std::vector<std::string> receiver;
        std::vector<std::string> fees;
        std::vector<std::string> gasLimit;
        std::vector<std::string> storageLimit;
        std::vector<uint64_t> confirmations;
        std::vector<std::string> type;
        std::vector<std::string> publicKey;
        std::vector<std::string> originatedAccount;
        std::vector<uint64_t> status;
        
        void update(const TezosLikeBlockchainExplorerTransaction& tx, const std::string& txUid) {
            uid.push_back(txUid);
            hash.push_back(tx.hash);
            value.push_back(tx.value.toHexString());
            //blockUid.push_back(tx.block.map<std::string>([] (const TezosLikeBlockchainExplorer::Block& block) {
            //    return block.getUid();
            //}));
            blockUid.push_back(tx.block.hasValue() ? tx.block->getUid() : "");
            time.push_back(tx.receivedAt);
            sender.push_back(tx.sender);
            receiver.push_back(tx.receiver);
            fees.push_back(tx.fees.toHexString());
            gasLimit.push_back(tx.gas_limit.toHexString());
            storageLimit.push_back(tx.storage_limit.toHexString());
            confirmations.push_back(tx.confirmations);
            type.push_back(api::to_string(tx.type));
            publicKey.push_back(tx.publicKey.getValueOr(""));
            std::string sOrigAccount;
                if (tx.originatedAccount.hasValue()) {
                    std::stringstream origAccount;
                    std::vector<std::string> vOrigAccount{tx.originatedAccount.getValue().address, std::to_string(tx.originatedAccount.getValue().spendable), std::to_string(tx.originatedAccount.getValue().delegatable)};
                    strings::join(vOrigAccount, origAccount, ":");
                    sOrigAccount = origAccount.str();
                }
            originatedAccount.push_back(sOrigAccount);
            status.push_back(tx.status);
            
        }

        void clear() {
            uid.clear();
            hash.clear();
            value.clear();
            blockUid.clear();
            time.clear();
            sender.clear();
            receiver.clear();
            fees.clear();
            gasLimit.clear();
            storageLimit.clear();
            confirmations.clear();
            type.clear();
            publicKey.clear();
            originatedAccount.clear();
            status.clear();
        }
    };

    const auto UPSERT_TRANSACTION = db::stmt<TransactionBinding>(
            "INSERT INTO tezos_transactions VALUES("
            " :tx_uid, :hash, :value, :block_uid, :time, :sender, :receiver, :fees, :gas_limit, "
            " :storage_limit, :confirmations, :type, :public_key, :originated_account, :status)"
            " ON CONFLICT(transaction_uid) DO UPDATE SET block_uid = :block, status = :code", [] (auto& s, auto&  b) {
                s, 
                use(b.uid), 
                use(b.hash),
                use(b.value),
                use(b.blockUid), 
                use(b.time),
                use(b.sender),
                use(b.receiver),
                use(b.fees), 
                use(b.gasLimit),
                use(b.storageLimit),
                use(b.confirmations),
                use(b.type), 
                use(b.publicKey),
                use(b.originatedAccount),
                use(b.status),
                use(b.blockUid), 
                use(b.status);
            });
}

namespace ledger {
    namespace core {

        void TezosLikeOperationDatabaseHelper::bulkInsert(soci::session &sql,
                const std::vector<Operation> &operations) {
            if (operations.empty())
                return;
            Benchmarker rawInsert("raw_db_insert_tezos", nullptr);
            rawInsert.start();
            PreparedStatement<OperationBinding> operationStmt;
            PreparedStatement<BlockBinding> blockStmt;
            PreparedStatement<TezosOperationBinding> tezosOpStmt;
            PreparedStatement<TransactionBinding> transactionStmt;
            PreparedStatement<TezosOriginatedAccountBinding> tezosOrigAccountStmt;
            PreparedStatement<TezosOriginatedOperationBinding> tezosOrigOpStmt;

            BulkInsertDatabaseHelper::UPSERT_OPERATION(sql, operationStmt);
            BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
            UPSERT_TEZOS_OPERATION(sql, tezosOpStmt);
            UPSERT_TRANSACTION(sql, transactionStmt);
            UPSERT_TEZOS_ORIGINATED_ACCOUNT(sql, tezosOrigAccountStmt);
            UPSERT_TEZOS_ORIGINATED_OPERATION(sql, tezosOrigOpStmt);

            for (const auto& op : operations) {
                // Upsert block
                if (op.block.hasValue()) {
                    blockStmt.bindings.update(op.block.getValue());
                }
                // Upsert operation
                operationStmt.bindings.update(op);
                // Upsert transaction
                auto& tx = op.tezosTransaction.getValue();
                const auto txUid =  TezosLikeTransactionDatabaseHelper::createTezosTransactionUid(op.accountUid, tx.hash, tx.type);
                transactionStmt.bindings.update(tx, txUid);
                // tezos operation
                tezosOpStmt.bindings.update(op.uid, txUid, tx.hash);

                if (!tx.originatedAccountUid.empty()) {               
                    if (tx.type == api::TezosOperationTag::OPERATION_TAG_REVEAL && tx.publicKey.hasValue()) {
                        tezosOrigAccountStmt.bindings.update(op);
                    } 
                    tezosOrigOpStmt.bindings.update(op.uid, txUid, tx.originatedAccountUid);
                }
                else {
                    if (op.type == api::OperationType::SEND) {
                        if (tx.type == api::TezosOperationTag::OPERATION_TAG_ORIGINATION && tx.status == 1) {
                            tezosOrigAccountStmt.bindings.update(op);
                        }
                    }
                }
            }
            
            // 1- Bulk insert block (dependency for operation and tezos transaction)
            if (!blockStmt.bindings.uid.empty()) {
                blockStmt.execute();
            }

            // 2- Bulk insert tezos_transaction 
            transactionStmt.execute();

            // 3- Bulk insert operations (dependency of  tezos_operations)
            operationStmt.execute();
            
            // 4- Bulk insert tezos_operations
            tezosOpStmt.execute();

            // 5- Bulk insert tezos_originated_accounts
            if (!tezosOrigAccountStmt.bindings.uid.empty()) {
                tezosOrigAccountStmt.execute();
            }

            // 6- Bulk insert tezos_originated_operations
            if (!tezosOrigOpStmt.bindings.uid.empty()) {
                tezosOrigOpStmt.execute();
            }

            rawInsert.stop();
        }
    }
}

<|start_filename|>core/test/lib/libledger-test/CppHttpLibClient.hpp<|end_filename|>
#pragma once

#define CPPHTTPLIB_OPENSSL_SUPPORT

#include <functional>
#include <api/HttpClient.hpp>

#include <api/HttpRequest.hpp> 
#include <api/ExecutionContext.hpp>
#include <spdlog/logger.h>
#include <httplib.h>


namespace ledger {
    namespace core {
        namespace test {

            class CppHttpLibClient : public core::api::HttpClient {
            public:
                CppHttpLibClient(const std::shared_ptr<core::api::ExecutionContext>& context) : 
                _context(context), _logger(nullptr), _generateCacheFile(false)
                {};
                
                void execute(const std::shared_ptr<core::api::HttpRequest> &request) override;

                void setLogger(const std::shared_ptr<spdlog::logger>& logger) {
                    _logger = logger;
                }

                void setGenerateCacheFile(bool generateCacheFile) {
                    _generateCacheFile = generateCacheFile;
                }

            private:
                std::shared_ptr<core::api::ExecutionContext> _context;
                std::shared_ptr<spdlog::logger> _logger;
                bool _generateCacheFile;
            };

        }
    }
}

<|start_filename|>core/src/wallet/ripple/synchronizers/RippleLikeAccountSynchronizer.cpp<|end_filename|>
/*
 *
 * RippleLikeAccountSynchronizer
 *
 * Created by <NAME> on 06/01/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */


#include <api/ConfigurationDefaults.hpp>
#include <async/Future.hpp>
#include <collections/DynamicObject.hpp>
#include <debug/Benchmarker.h>
#include <events/ProgressNotifier.h>
#include <utils/Unit.hpp>
#include <utils/DateUtils.hpp>
#include <utils/DurationUtils.h>
#include <utils/Try.hpp>
#include <wallet/common/database/BlockDatabaseHelper.h>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <common/AccountHelper.hpp>
#include <wallet/common/database/OperationDatabaseHelper.h>
#include <wallet/ripple/RippleLikeAccount.h>
#include <wallet/ripple/synchronizers/RippleLikeAccountSynchronizer.hpp>

#include <algorithm>

namespace ledger {
namespace core {

namespace {

void initializeSavedState(
    Option<AccountSynchronizationSavedState> &savedState,
    int32_t halfBatchSize)
{
    if (savedState.hasValue() && savedState.getValue().halfBatchSize != halfBatchSize) {
        AccountSynchronizationBatchSavedState block;
        block.blockHeight = 1U << 31U;

        for (auto &state : savedState.getValue().batches) {
            if (state.blockHeight < block.blockHeight) {
                block = state;
            }
        }
        auto newBatchCount =
            (savedState.getValue().batches.size() * savedState.getValue().halfBatchSize) /
            halfBatchSize;
        if (newBatchCount != 0) {
            ++newBatchCount;
        }
        savedState.getValue().batches.clear();
        savedState.getValue().halfBatchSize = static_cast<uint32_t>(halfBatchSize);
        for (auto i = 0; i <= newBatchCount; i++) {
            AccountSynchronizationBatchSavedState s;
            s.blockHash = block.blockHash;
            s.blockHeight = block.blockHeight;
            savedState.getValue().batches.push_back(s);
        }
    } else if (savedState.isEmpty()) {
        savedState = Option<AccountSynchronizationSavedState>(AccountSynchronizationSavedState());
        savedState.getValue().halfBatchSize = static_cast<uint32_t>(halfBatchSize);
    }
}

} // namespace

RippleLikeAccountSynchronizer::RippleLikeAccountSynchronizer(
    const std::shared_ptr<WalletPool> &pool,
    const std::shared_ptr<RippleLikeBlockchainExplorer> &explorer)
    : DedicatedContext(pool->getDispatcher()->getThreadPoolExecutionContext("synchronizers"))
    , _explorer(explorer)
{}

std::shared_ptr<ProgressNotifier<AccountSynchronizationContext>>
RippleLikeAccountSynchronizer::synchronizeAccount(const std::shared_ptr<RippleLikeAccount> &account)
{
    std::lock_guard<std::mutex> lock(_lock);
    if (!_currentAccount) {
        _currentAccount = account;
        _notifier = std::make_shared<ProgressNotifier<AccountSynchronizationContext>>();
        auto self = shared_from_this();
        performSynchronization(account).onComplete(getContext(), [self](const auto& result) {
            std::lock_guard<std::mutex> l(self->_lock);
            if (result.isFailure()) {
                self->_notifier->failure(result.getFailure());
            } else {
                self->_notifier->success(result.getValue());
            }
            self->_notifier = nullptr;
            self->_currentAccount = nullptr;
        });

    } else if (account != _currentAccount) {
        throw make_exception(api::ErrorCode::RUNTIME_ERROR, "This synchronizer is already in use");
    }
    return _notifier;
}

Future<AccountSynchronizationContext>
RippleLikeAccountSynchronizer::performSynchronization(const std::shared_ptr<RippleLikeAccount>& account)
{
    auto buddy = std::make_shared<SynchronizationBuddy>();
    buddy->account = account;
    buddy->preferences = std::static_pointer_cast<AbstractAccount>(account)
        ->getInternalPreferences()
        ->getSubPreferences("RippleLikeAccountSynchronizer");
    auto loggerPurpose = fmt::format("synchronize_{}", account->getAccountUid());
    auto tracePrefix = fmt::format(
        "{}/{}/{}",
        account->getWallet()->getPool()->getName(),
        account->getWallet()->getName(),
        account->getIndex());
    buddy->synchronizationTag = tracePrefix;
    buddy->logger = logger::trace(loggerPurpose, tracePrefix, account->logger());
    buddy->startDate = DateUtils::now();
    buddy->wallet = account->getWallet();
    buddy->configuration = account->getWallet()->getConfig();
    buddy->halfBatchSize = static_cast<uint32_t>(buddy->configuration
            ->getInt(api::Configuration::SYNCHRONIZATION_HALF_BATCH_SIZE)
            .value_or(api::ConfigurationDefaults::KEYCHAIN_DEFAULT_OBSERVABLE_RANGE));
    buddy->keychain = account->getKeychain();
    buddy->savedState = buddy->preferences->getObject<AccountSynchronizationSavedState>("state");
    buddy->logger->info(
        "Starting synchronization for account#{} ({}) of wallet {} at {}",
        account->getIndex(),
        account->getKeychain()->getRestoreKey(),
        account->getWallet()->getName(), DateUtils::toJSON(buddy->startDate));

    auto fullSyncBenchmarker = std::make_shared<Benchmarker>(
        fmt::format("full_synchronization/{}", buddy->synchronizationTag),
        buddy->logger);

    fullSyncBenchmarker->start();
    //Check if reorganization happened
    soci::session sql(buddy->wallet->getDatabase()->getPool());
    if (buddy->savedState.nonEmpty()) {

        //Get deepest block saved in batches to be part of reorg
        auto sortedBatches = buddy->savedState.getValue().batches;
        std::sort(
            sortedBatches.begin(),
            sortedBatches.end(),
            [](const AccountSynchronizationBatchSavedState &lhs,
               const AccountSynchronizationBatchSavedState &rhs) {
                return lhs.blockHeight < rhs.blockHeight;
            });

        auto currencyName = buddy->wallet->getCurrency().name;
        size_t index = 0;
        //Reorg can't happen until genesis block, safely initialize with 0
        uint64_t deepestFailedBlockHeight = 0;
        while (index < sortedBatches.size() &&
            !BlockDatabaseHelper::blockExists(sql, sortedBatches[index].blockHash, currencyName)) {
            deepestFailedBlockHeight = sortedBatches[index].blockHeight;
            index++;
        }

        //Case of reorg, update savedState's batches
        if (deepestFailedBlockHeight > 0) {
            //Get last block (in DB) which contains current account's operations
            auto previousBlock = AccountDatabaseHelper::getLastBlockWithOperations(
                sql, buddy->account->getAccountUid());
            for (auto& batch : buddy->savedState.getValue().batches) {
                if (batch.blockHeight >= deepestFailedBlockHeight) {
                    batch.blockHeight = previousBlock.nonEmpty() ?
                        static_cast<uint32_t>(previousBlock.getValue().height) : 0;
                    batch.blockHash = previousBlock.nonEmpty() ?
                        previousBlock.getValue().blockHash : "";
                }
            }
        }
    }

    initializeSavedState(buddy->savedState, buddy->halfBatchSize);

    updateTransactionsToDrop(sql, buddy, account->getAccountUid());

    updateCurrentBlock(buddy, account->getContext());

    auto self = shared_from_this();
    return self->synchronizeBatches(0, buddy)
        .flatMap<Unit>(
            account->getContext(),
            [self, buddy](const Unit&) {
                return self->synchronizeMempool(buddy);
            })
        .map<AccountSynchronizationContext>(
            ImmediateExecutionContext::INSTANCE,
            [=](const Unit&) {
                auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
                    (DateUtils::now() - buddy->startDate.time_since_epoch()).time_since_epoch());
                buddy->logger->info(
                    "End synchronization for account#{} of wallet {} in {}",
                    buddy->account->getIndex(),
                    buddy->account->getWallet()->getName(),
                    DurationUtils::formatDuration(duration));

                const auto& batches = buddy->savedState.getValue().batches;

                soci::session sql(buddy->wallet->getDatabase()->getPool());
                buddy->context.lastBlockHeight =
                    BlockDatabaseHelper::getLastBlock(
                        sql, buddy->wallet->getCurrency().name)
                    .map<uint64_t>([](const Block& block) {
                        return block.height;
                    })
                    .getValueOr(0);
                fullSyncBenchmarker->stop();
                self->_currentAccount = nullptr;
                return buddy->context;
            })
        .recover(
            ImmediateExecutionContext::INSTANCE,
            [self, buddy, fullSyncBenchmarker](const Exception& ex) {
                auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
                    (DateUtils::now() - buddy->startDate.time_since_epoch()).time_since_epoch());
                buddy->logger->error(
                    "Error during during synchronization for account#{} of wallet {} in {} ms",
                    buddy->account->getIndex(),
                    buddy->account->getWallet()->getName(),
                    duration.count());
                buddy->logger->error("Due to {}, {}", api::to_string(ex.getErrorCode()), ex.getMessage());
                fullSyncBenchmarker->stop();
                return buddy->context;
            });
}

Future<Unit> RippleLikeAccountSynchronizer::synchronizeBatches(
    uint32_t currentBatchIndex,
    std::shared_ptr<SynchronizationBuddy> buddy)
{
    buddy->logger->info("SYNC BATCHES");
    if (currentBatchIndex >= buddy->savedState.getValue().batches.size()) {
        buddy->savedState.getValue().batches.push_back(AccountSynchronizationBatchSavedState());
    }

    auto self = shared_from_this();
    auto& batchState = buddy->savedState.getValue().batches[currentBatchIndex];

    auto benchmark = std::make_shared<Benchmarker>(
        fmt::format("full_batch/{}", buddy->synchronizationTag),
        buddy->logger);
    benchmark->start();
    return synchronizeBatch(currentBatchIndex, buddy)
        .flatMap<Unit>(
            buddy->account->getContext(),
            [=](const bool& hadTransactions) {
                benchmark->stop();

                buddy->preferences->editor()->putObject<AccountSynchronizationSavedState>(
                    "state", buddy->savedState.getValue())->commit();
                return Future<Unit>::successful(unit);
            })
        .recoverWith(
            ImmediateExecutionContext::INSTANCE,
            [=](const Exception& exception) {
                buddy->logger->info(
                    "Recovering from failing synchronization : {}", exception.getMessage());

                //A block reorganization happened
                if (exception.getErrorCode() == api::ErrorCode::BLOCK_NOT_FOUND &&
                    buddy->savedState.nonEmpty()) {
                    buddy->logger->info("Recovering from reorganization");

                    auto startSession = Future<void*>::async(ImmediateExecutionContext::INSTANCE, [=]() {
                        return Future<void*>::successful(nullptr);
                    });

                    return startSession.flatMap<Unit>(
                        ImmediateExecutionContext::INSTANCE,
                        [=](void * const session) {
                            //Get its block/block height
                            auto& failedBatch = buddy->savedState.getValue().batches[currentBatchIndex];
                            auto const failedBlockHeight = failedBatch.blockHeight;
                            auto const failedBlockHash = failedBatch.blockHash;

                            if (failedBlockHeight > 0) {

                                //Delete data related to failedBlock (and all blocks above it)
                                buddy->logger->info(
                                    "Deleting blocks above block height: {}", failedBlockHeight);

                                soci::session sql(buddy->wallet->getDatabase()->getPool());
                                {
                                    soci::transaction tr(sql);
                                    try {
                                        soci::rowset<std::string> rows_block = (sql.prepare << "SELECT uid FROM blocks where height >= :failedBlockHeight",
                                                                                soci::use(failedBlockHeight));

                                        std::vector<std::string> blockToDelete(rows_block.begin(), rows_block.end());

                                        // Fetch all operations which are deleted during reorganization
                                        auto deletedOperationUIDs = OperationDatabaseHelper::fetchFromBlocks(sql, blockToDelete);

                                        // Remove failed blocks and associated operations/transactions
                                        AccountDatabaseHelper::removeBlockOperation(sql, buddy->account->getAccountUid(), blockToDelete);

                                        //Get last block not part from reorg
                                        auto lastBlock = BlockDatabaseHelper::getLastBlock(sql, buddy->wallet->getCurrency().name);

                                        //Resync from the "beginning" if no last block in DB
                                        int64_t lastBlockHeight = 0;
                                        std::string lastBlockHash;
                                        if (lastBlock.nonEmpty()) {
                                            lastBlockHeight = lastBlock.getValue().height;
                                            lastBlockHash = lastBlock.getValue().blockHash;
                                        }
                                        // update reorganization block height until found the valid one
                                        buddy->context.reorgBlockHeight = lastBlockHeight;

                                        //Update savedState's batches
                                        for (auto& batch : buddy->savedState.getValue().batches) {
                                            if (batch.blockHeight > lastBlockHeight) {
                                                batch.blockHeight = static_cast<uint32_t>(lastBlockHeight);
                                                batch.blockHash = lastBlockHash;
                                            }
                                        }
                                        tr.commit();

                                        // We can emit safely deleted operation UIDs
                                        std::for_each(
                                            deletedOperationUIDs.cbegin(),
                                            deletedOperationUIDs.cend(),
                                            [buddy](auto const& uid) {
                                                buddy->account->emitDeletedOperationEvent(uid);
                                            });
                                    } catch(...) {
                                        tr.rollback();
                                    }
                                }

                                //Save new savedState
                                buddy->preferences->editor()->putObject<AccountSynchronizationSavedState>(
                                        "state", buddy->savedState.getValue())->commit();

                                //Synchronize same batch now with an existing block (of hash lastBlockHash)
                                //if failedBatch was not the deepest block part of that reorg, this recursive call
                                //will ensure to get (and delete from DB) to the deepest failed block (part of reorg)
                                buddy->logger->info("Relaunch synchronization after recovering from reorganization");

                                return self->synchronizeBatches(currentBatchIndex, buddy);
                            }
                            return Future<Unit>::successful(unit);
                        })
                    .recover(
                        ImmediateExecutionContext::INSTANCE,
                        [buddy](const Exception& ex) {
                            buddy->logger->warn(
                                    "Failed to recover from reorganisation for account#{} of wallet {}",
                                    buddy->account->getIndex(),
                                    buddy->account->getWallet()->getName());
                            return unit;
                        });
                }
                return Future<Unit>::successful(unit);
            });
}

Future<bool> RippleLikeAccountSynchronizer::synchronizeBatch(
    uint32_t currentBatchIndex,
    std::shared_ptr<SynchronizationBuddy> buddy,
    bool hadTransactions)
{
    buddy->logger->info("SYNC BATCH {}", currentBatchIndex);

    Option<std::string> blockHash;
    auto self = shared_from_this();
    auto& batchState = buddy->savedState.getValue().batches[currentBatchIndex];

    if (batchState.blockHeight > 0) {
        blockHash = Option<std::string>(batchState.blockHash);
    }


    auto derivationBenchmark = std::make_shared<Benchmarker>(
        fmt::format("derivations/{}", buddy->synchronizationTag),
        buddy->logger);
    derivationBenchmark->start();

    auto batch = vector::map<std::string, std::shared_ptr<RippleLikeAddress>>(
        buddy->keychain->getAllObservableAddresses(
            static_cast<uint32_t>((currentBatchIndex * buddy->halfBatchSize)),
            static_cast<uint32_t>((currentBatchIndex + 1) * buddy->halfBatchSize - 1)),
        [](const std::shared_ptr<RippleLikeAddress>& addr) {
            return addr->toString();
        });

    derivationBenchmark->stop();

    auto benchmark = std::make_shared<Benchmarker>(
        fmt::format("explorer_calls/{}", buddy->synchronizationTag),
        buddy->logger);
    benchmark->start();
    return _explorer->getTransactions(batch, blockHash, optional<void*>())
        .flatMap<bool>(
            buddy->account->getContext(),
            [self, currentBatchIndex, buddy, hadTransactions, benchmark](
                const std::shared_ptr<typename RippleLikeBlockchainExplorer::TransactionsBulk>& bulk) {
                benchmark->stop();

                auto interpretBenchmark = std::make_shared<Benchmarker>(
                    fmt::format("interpret_operations/{}", buddy->synchronizationTag),
                    buddy->logger);

                auto& batchState = buddy->savedState.getValue().batches[currentBatchIndex];
                //self->transactions.insert(self->transactions.end(), bulk->transactions.begin(), bulk->transactions.end());
                buddy->logger->info("Got {} txs for account {}", bulk->transactions.size(), buddy->account->getAccountUid());
                auto count = 0;

                // NEW CODE
                Option<Block> lastBlock = Option<Block>::NONE;
                std::vector<Operation> operations;
                interpretBenchmark->start();
                // Interpret transactions to operations and update last block
                for (const auto& tx : bulk->transactions) {
                    // Update last block to chain query
                    if (lastBlock.isEmpty() || (tx.block.nonEmpty() &&
                        lastBlock.getValue().height < tx.block.getValue().height)) {
                        lastBlock = tx.block;
                    }

                    buddy->account->interpretTransaction(tx, operations);

                    //Update first pendingTxHash in savedState
                    auto it = buddy->transactionsToDrop.find(tx.hash);
                    if (it != buddy->transactionsToDrop.end()) {
                        //If block non empty, tx is no longer pending
                        if (tx.block.nonEmpty()) {
                            buddy->savedState.getValue().pendingTxsHash.erase(it->first);
                        } else { //Otherwise tx is in mempool but pending
                            buddy->savedState.getValue().pendingTxsHash.insert(std::pair<std::string, std::string>(it->first, it->second));
                        }
                    }
                    //Remove from tx to drop
                    buddy->transactionsToDrop.erase(tx.hash);
                }
                interpretBenchmark->stop();
                auto insertionBenchmark = std::make_shared<Benchmarker>(
                    fmt::format("insert_operations/{}", buddy->synchronizationTag),
                    buddy->logger);
                insertionBenchmark->start();
                Try<int> tryPutTx = buddy->account->bulkInsert(operations);
                insertionBenchmark->stop();
                if (tryPutTx.isFailure()) {
                    buddy->logger->error(
                        "Failed to bulk insert for batch {} because: {}",
                        currentBatchIndex, tryPutTx.getFailure().getMessage());
                    throw make_exception(
                        api::ErrorCode::RUNTIME_ERROR,
                        "Synchronization failed for batch {} ({})",
                        currentBatchIndex, tryPutTx.exception().getValue().getMessage());
                } else {
                    count += tryPutTx.getValue();
                }

                buddy->logger->info(
                    "Succeeded to insert {} txs on {} for account {}",
                    count, bulk->transactions.size(), buddy->account->getAccountUid());
                buddy->account->emitEventsNow();

                // END NEW CODE

                // Get the last block
                if (bulk->transactions.size() > 0 && lastBlock.nonEmpty()) {
                    if (lastBlock.getValue().height > batchState.blockHeight) {
                        batchState.blockHeight = static_cast<uint32_t>(lastBlock.getValue().height);
                        batchState.blockHash = lastBlock.getValue().hash;
                        buddy->preferences->editor()->putObject<AccountSynchronizationSavedState>(
                            "state", buddy->savedState.getValue())->commit();
                    }
                }

                auto hadTX = hadTransactions || bulk->transactions.size() > 0;
                if (bulk->hasNext) {
                    return self->synchronizeBatch(currentBatchIndex, buddy, hadTX);
                } else {
                    return Future<bool>::successful(hadTX);
                }
            });
}

Future<Unit> RippleLikeAccountSynchronizer::synchronizeMempool(
    const std::shared_ptr<SynchronizationBuddy>& buddy)
{
    //Delete dropped txs from DB
    soci::session sql(buddy->wallet->getDatabase()->getPool());
    for (auto& tx : buddy->transactionsToDrop) {
        //Check if tx is pending
        auto it = buddy->savedState.getValue().pendingTxsHash.find(tx.first);
        if (it == buddy->savedState.getValue().pendingTxsHash.end()) {
            //soci::transaction tr(sql);
            buddy->logger->info("Drop transaction {}", tx.first);
            buddy->logger->info("Deleting operation from DB {}", tx.second);
            try {
                sql << "DELETE FROM operations WHERE uid = :uid", soci::use(tx.second);
                //tr.commit();
            } catch(std::exception& ex) {
                buddy->logger->info(
                    "Failed to delete operation from DB {} reason: {}, rollback ...",
                    tx.second, ex.what());
                //tr.rollback();
            }
        }
    }
    return Future<Unit>::successful(unit);
}

void RippleLikeAccountSynchronizer::updateCurrentBlock(
    std::shared_ptr<SynchronizationBuddy> &buddy,
    const std::shared_ptr<api::ExecutionContext> &context)
{
    _explorer->getCurrentBlock().onComplete(
        context,
        [buddy](const TryPtr<RippleLikeBlockchainExplorer::Block> &block) {
            if (block.isSuccess()) {
                soci::session sql(buddy->account->getWallet()->getDatabase()->getPool());
                soci::transaction tr(sql);
                try {
                    buddy->account->putBlock(sql, *block.getValue());
                    tr.commit();
                } catch(...) {
                    tr.rollback();
                }
            }
        });
}

void RippleLikeAccountSynchronizer::updateTransactionsToDrop(
    soci::session &sql,
    std::shared_ptr<SynchronizationBuddy> &buddy,
    const std::string &accountUid)
{
    //Get all transactions in DB that may be dropped (txs without block_uid)
    soci::rowset<soci::row> rows = (sql.prepare << "SELECT op.uid, xrp_op.transaction_hash FROM operations AS op "
        "LEFT OUTER JOIN ripple_operations AS xrp_op ON xrp_op.uid = op.uid "
        "WHERE op.block_uid IS NULL AND op.account_uid = :uid ", soci::use(accountUid));

    for (auto &row : rows) {
        if (row.get_indicator(0) != soci::i_null && row.get_indicator(1) != soci::i_null) {
            buddy->transactionsToDrop.insert(
                std::pair<std::string, std::string>(row.get<std::string>(1), row.get<std::string>(0)));
        }
    }
}

} // namespace core
} // namespace ledger


<|start_filename|>core/src/api/Account.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from wallet.djinni

#include "Account.hpp"  // my header
#include "TimePeriod.hpp"
#include "WalletType.hpp"

namespace ledger { namespace core { namespace api {

std::string const Account::EV_SYNC_DURATION_MS = {"EV_SYNC_DURATION_MS"};

std::string const Account::EV_SYNC_LAST_BLOCK_HEIGHT = {"EV_SYNC_LAST_BLOCK_HEIGHT"};

std::string const Account::EV_SYNC_NEW_OPERATIONS = {"EV_SYNC_NEW_OPERATIONS"};

std::string const Account::EV_SYNC_REORG_BLOCK_HEIGHT = {"EV_SYNC_REORG_BLOCK_HEIGHT"};

std::string const Account::EV_SYNC_ERROR_CODE = {"EV_SYNC_ERROR_CODE"};

std::string const Account::EV_SYNC_ERROR_CODE_INT = {"EV_SYNC_ERROR_CODE_INT"};

std::string const Account::EV_SYNC_ERROR_MESSAGE = {"EV_SYNC_ERROR_MESSAGE"};

std::string const Account::EV_NEW_BLOCK_CURRENCY_NAME = {"EV_NEW_BLOCK_CURRENCY_NAME"};

std::string const Account::EV_NEW_BLOCK_HASH = {"EV_NEW_BLOCK_HASH"};

std::string const Account::EV_NEW_BLOCK_HEIGHT = {"EV_NEW_BLOCK_HEIGHT"};

std::string const Account::EV_NEW_OP_WALLET_NAME = {"EV_NEW_OP_WALLET_NAME"};

std::string const Account::EV_NEW_OP_ACCOUNT_INDEX = {"EV_NEW_OP_ACCOUNT_INDEX"};

std::string const Account::EV_NEW_OP_UID = {"EV_NEW_OP_UID"};

std::string const Account::EV_DELETED_OP_UID = {"EV_DELETED_OP_UID"};

int32_t const Account::FLAG_TRANSACTION_IGNORED = 0;

int32_t const Account::FLAG_NEW_TRANSACTION = 1;

int32_t const Account::FLAG_TRANSACTION_UPDATED = 2;

int32_t const Account::FLAG_TRANSACTION_ON_PREVIOUSLY_EMPTY_ADDRESS = 4;

int32_t const Account::FLAG_TRANSACTION_ON_USED_ADDRESS = 8;

int32_t const Account::FLAG_TRANSACTION_CREATED_SENDING_OPERATION = 16;

int32_t const Account::FLAG_TRANSACTION_CREATED_RECEPTION_OPERATION = 32;

int32_t const Account::FLAG_TRANSACTION_CREATED_EXTERNAL_OPERATION = 64;

} } }  // namespace ledger::core::api


<|start_filename|>core/src/jni/jni/DurationMetric.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#include "DurationMetric.hpp"  // my header
#include "Marshal.hpp"

namespace djinni_generated {

DurationMetric::DurationMetric() = default;

DurationMetric::~DurationMetric() = default;

auto DurationMetric::fromCpp(JNIEnv* jniEnv, const CppType& c) -> ::djinni::LocalRef<JniType> {
    const auto& data = ::djinni::JniClass<DurationMetric>::get();
    auto r = ::djinni::LocalRef<JniType>{jniEnv->NewObject(data.clazz.get(), data.jconstructor,
                                                           ::djinni::get(::djinni::I64::fromCpp(jniEnv, c.total_ms)),
                                                           ::djinni::get(::djinni::I32::fromCpp(jniEnv, c.count)))};
    ::djinni::jniExceptionCheck(jniEnv);
    return r;
}

auto DurationMetric::toCpp(JNIEnv* jniEnv, JniType j) -> CppType {
    ::djinni::JniLocalScope jscope(jniEnv, 3);
    assert(j != nullptr);
    const auto& data = ::djinni::JniClass<DurationMetric>::get();
    return {::djinni::I64::toCpp(jniEnv, jniEnv->GetLongField(j, data.field_totalMs)),
            ::djinni::I32::toCpp(jniEnv, jniEnv->GetIntField(j, data.field_count))};
}

}  // namespace djinni_generated


<|start_filename|>core/src/wallet/bitcoin/transaction_builders/BitcoinLikeStrategyUtxoPicker.h<|end_filename|>
/*
 *
 * BitcoinLikeStrategyUtxoPicker.h
 * ledger-core
 *
 * Created by <NAME> on 29/03/2018.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_BITCOINLIKESTRATEGYUTXOPICKER_H
#define LEDGER_CORE_BITCOINLIKESTRATEGYUTXOPICKER_H

#include "BitcoinLikeUtxoPicker.h"
#include <wallet/bitcoin/transaction_builders/BitcoinLikeUtxo.hpp>

namespace ledger {
    namespace core {

        namespace api {
            class Currency;
        };

        class BitcoinLikeStrategyUtxoPicker : public BitcoinLikeUtxoPicker {
        public:
            BitcoinLikeStrategyUtxoPicker(const std::shared_ptr<api::ExecutionContext> &context,
                                          const api::Currency &currency);
        public:
            static std::vector<BitcoinLikeUtxo> filterWithKnapsackSolver(const std::shared_ptr<Buddy>& buddy,
                const std::vector<BitcoinLikeUtxo>& utxos,
                const BigInt& aggregatedAmount,
                const api::Currency& currrency);

            static std::vector<BitcoinLikeUtxo> filterWithOptimizeSize(const std::shared_ptr<Buddy>& buddy,
                const std::vector<BitcoinLikeUtxo>& utxos,
                const BigInt& aggregatedAmount,
                const api::Currency& currrency);

            static std::vector<BitcoinLikeUtxo> filterWithMergeOutputs(const std::shared_ptr<Buddy>& buddy,
                const std::vector<BitcoinLikeUtxo>& utxos,
                const BigInt& aggregatedAmount,
                const api::Currency& currrency);
            static std::vector<BitcoinLikeUtxo> filterWithDeepFirst(const std::shared_ptr<Buddy>& buddy,
                const std::vector<BitcoinLikeUtxo>& utxo,
                const BigInt& aggregatedAmount,
                const api::Currency& currrency);
            static std::vector<BitcoinLikeUtxo> filterWithHighestFirstLimitUtxo(
                const std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> &buddy,
                std::vector<BitcoinLikeUtxo> utxos,
                const BigInt &aggregatedAmount,
                const api::Currency& currency,
                const optional<int32_t>& maxUtxo);
            static std::vector<BitcoinLikeUtxo> filterWithLimitUtxo(
                const std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> &buddy,
                std::vector<BitcoinLikeUtxo> utxos,
                const BigInt &aggregatedAmount,
                const api::Currency& currency,
                const optional<int32_t>& maxUtxo);
            static bool hasEnough(const std::shared_ptr<Buddy>& buddy,
                const BigInt& aggregatedAmount,
                int inputCount,
                const api::Currency& currrency,
                bool computeOutputAmount);
        protected:
            Future<std::vector<BitcoinLikeUtxo>> filterInputs(const std::shared_ptr<Buddy> &buddy) override;

            inline Future<BigInt> computeAggregatedAmount(const std::shared_ptr<Buddy>& buddy);

            //Usefull for filterWithLowFeesFirst
            static const int64_t DEFAULT_FALLBACK_FEE = 20;
            static const int64_t DEFAULT_DISCARD_FEE = 10;
            static const int64_t COIN = 100000000;
            static const int64_t MAX_MONEY = 21000000 * COIN;
            static const uint32_t TOTAL_TRIES = 10000;
            static const int64_t CENT = 1000000;
        private:

            static std::vector<BitcoinLikeUtxo> filterWithSort(
                const std::shared_ptr<BitcoinLikeUtxoPicker::Buddy> &buddy,
                std::vector<BitcoinLikeUtxo> utxos,
                BigInt amount,
                const api::Currency &currency,
                std::function<bool(BitcoinLikeUtxo&, BitcoinLikeUtxo&)> const& functor
            );
        };
    }
}


#endif //LEDGER_CORE_BITCOINLIKESTRATEGYUTXOPICKER_H


<|start_filename|>core/src/wallet/bitcoin/explorers/api/TransactionParser.cpp<|end_filename|>
/*
 *
 * TransactionParser
 * ledger-core
 *
 * Created by <NAME> on 27/03/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "TransactionParser.hpp"
#include "utils/DateUtils.hpp"

#define PROXY_PARSE(method, ...)                                    \
 auto& currentObject = _hierarchy.top();                            \
 if (currentObject == "block") {                                    \
    return _blockParser.method(__VA_ARGS__);                        \
 } else if (currentObject == "inputs") {                            \
    return _inputParser.method(__VA_ARGS__);                        \
 } else if (currentObject == "outputs") {                           \
    return _outputParser.method(__VA_ARGS__);                        \
 } else                                                             \

namespace ledger {
    namespace core {

        bool TransactionParser::Key(const rapidjson::Reader::Ch *str, rapidjson::SizeType length, bool copy) {
            _lastKey = std::string(str, length);
            PROXY_PARSE(Key, str, length, copy) {
                return true;
            }
        }

        bool TransactionParser::StartObject() {
            if (_arrayDepth == 0) {
                _hierarchy.push(_lastKey);
            }

            auto& currentObject = _hierarchy.top();

            if (currentObject == "inputs") {
                BitcoinLikeBlockchainExplorerInput input;
                input.index = _transaction->inputs.size();
                _transaction->inputs.push_back(input);
                _inputParser.init(&_transaction->inputs.back());
            } else if (currentObject == "outputs") {
                BitcoinLikeBlockchainExplorerOutput output;
                _transaction->outputs.push_back(output);
                _outputParser.init(&_transaction->outputs.back());
                // Set block height
                if (_transaction->block.hasValue()) {
                    _transaction->outputs.back().blockHeight = _transaction->block.getValue().height;
                }
            } else if (currentObject == "block") {
                BitcoinLikeBlockchainExplorer::Block block;
                _transaction->block = Option<BitcoinLikeBlockchainExplorer::Block>(block);
                _blockParser.init(&_transaction->block.getValue());
            }

            return true;
        }

        bool TransactionParser::EndObject(rapidjson::SizeType memberCount) {
            auto& currentObject = _hierarchy.top();

            if (_arrayDepth == 0) {
                _hierarchy.pop();
            }
            return true;
        }

        bool TransactionParser::StartArray() {
            if (_arrayDepth == 0) {
                _hierarchy.push(_lastKey);
            }
            _arrayDepth += 1;
            return true;
        }

        bool TransactionParser::EndArray(rapidjson::SizeType elementCount) {
            _arrayDepth -= 1;
            if (_arrayDepth == 0) {
                _hierarchy.pop();
            }
            return true;
        }

        bool TransactionParser::Null() {
            PROXY_PARSE(Null) {
                return true;
            }
        }

        bool TransactionParser::Bool(bool b) {
            PROXY_PARSE(Bool, b) {
                return true;
            }
        }

        bool TransactionParser::Int(int i) {
            return Uint64(i);
        }

        bool TransactionParser::Uint(unsigned i) {
            return Uint64(i);
        }

        bool TransactionParser::Int64(int64_t i) {
            return Uint64(i);
        }

        bool TransactionParser::Uint64(uint64_t i) {
            PROXY_PARSE(Uint64, i) {
                return true;
            }
        }

        bool TransactionParser::Double(double d) {
            PROXY_PARSE(Double, d) {
                return true;
            }
        }

        bool TransactionParser::RawNumber(const rapidjson::Reader::Ch *str, rapidjson::SizeType length, bool copy) {
            PROXY_PARSE(RawNumber, str, length, copy) {
                std::string number(str, length);
                BigInt value = BigInt::fromString(number);
                if (_lastKey == "lock_time") {
                    _transaction->lockTime = value.toUint64();
                } else if (_lastKey == "fees") {
                    _transaction->fees = Option<BigInt>(value);
                } else if (_lastKey == "confirmations") {
                    _transaction->confirmations = value.toUint64();
                }
                return true;
            }
        }

        bool TransactionParser::String(const rapidjson::Reader::Ch *str, rapidjson::SizeType length, bool copy) {
            PROXY_PARSE(String, str, length, copy) {
                std::string value(str, length);
                if (_lastKey == "hash") {
                    if (_transaction->hash.empty()) {
                        _transaction->hash = value;
                    }
                }
                else if (_lastKey == "id") {
                    _transaction->hash = value;
                }
                else if (_lastKey == "received_at") {
                    _transaction->receivedAt = DateUtils::fromJSON(value);
                }
                return true;
            }
        }

        TransactionParser::TransactionParser(std::string& lastKey) :
            _lastKey(lastKey), _blockParser(lastKey), _inputParser(lastKey), _outputParser(lastKey)
        {
            _arrayDepth = 0;
        }

        void TransactionParser::init(BitcoinLikeBlockchainExplorerTransaction *transaction) {
            _transaction = transaction;
        }

    }
}

<|start_filename|>core/src/wallet/bitcoin/keychains/BitcoinLikeKeychain.cpp<|end_filename|>
/*
 *
 * BitcoinLikeKeychain
 * ledger-core
 *
 * Created by <NAME> on 17/01/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <api/Currency.hpp>
#include "BitcoinLikeKeychain.hpp"
#include <api/KeychainEngines.hpp>
namespace ledger {
    namespace core {

        int BitcoinLikeKeychain::getAccountIndex() const {
            return _account;
        }

        const api::BitcoinLikeNetworkParameters &BitcoinLikeKeychain::getNetworkParameters() const {
            return _currency.bitcoinLikeNetworkParameters.value();
        }

        bool BitcoinLikeKeychain::markAsUsed(const std::vector<std::string> &addresses) {
            bool result = false;
            for (auto& address : addresses) {
                result = markAsUsed(address) || result;
            }
            return result;
        }

        std::shared_ptr<Preferences> BitcoinLikeKeychain::getPreferences() const {
            return _preferences;
        }

        std::shared_ptr<api::DynamicObject> BitcoinLikeKeychain::getConfiguration() const {
            return _configuration;
        }

        BitcoinLikeKeychain::BitcoinLikeKeychain(const std::shared_ptr<api::DynamicObject>& configuration,
                                                 const api::Currency &params, int account,
                                                 const std::shared_ptr<Preferences>& preferences) :
            _account(account), _preferences(preferences), _configuration(configuration), _currency(params),
            _fullScheme(DerivationScheme(configuration
                                                 ->getString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME)
                                                 .value_or("44'/<coin_type>'/<account>'/<node>/<address>"))),
            _scheme(DerivationScheme(configuration
                    ->getString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME)
                    .value_or("44'/<coin_type>'/<account>'/<node>/<address>")).getSchemeFrom(DerivationSchemeLevel::ACCOUNT_INDEX).shift())
        {
        }

        const api::Currency& BitcoinLikeKeychain::getCurrency() const {
            return _currency;
        }

        const DerivationScheme &BitcoinLikeKeychain::getDerivationScheme() const {
            return _scheme;
        }

        DerivationScheme &BitcoinLikeKeychain::getDerivationScheme() {
            return _scheme;
        }

        bool BitcoinLikeKeychain::markAsUsed(const std::string &address, bool needExtendKeychain) {
            auto path = getAddressDerivationPath(address);
            if (path.nonEmpty()) {
                return markPathAsUsed(DerivationPath(path.getValue()), needExtendKeychain);
            } else {
                return false;
            }
        }

        const DerivationScheme &BitcoinLikeKeychain::getFullDerivationScheme() const {
            return _fullScheme;
        }

        std::string BitcoinLikeKeychain::getKeychainEngine() const {
            return _configuration->getString(api::Configuration::KEYCHAIN_ENGINE).value_or(api::KeychainEngines::BIP32_P2PKH);
        }

        bool BitcoinLikeKeychain::isSegwit() const {
            //TODO: find a better way to know whether it's segwit or not
            return isSegwit(_configuration->getString(api::Configuration::KEYCHAIN_ENGINE).value_or(api::KeychainEngines::BIP32_P2PKH));
        }

        bool BitcoinLikeKeychain::isNativeSegwit() const {
            return isNativeSegwit(_configuration->getString(api::Configuration::KEYCHAIN_ENGINE).value_or(api::KeychainEngines::BIP32_P2PKH));
        }

        bool BitcoinLikeKeychain::isSegwit(const std::string &keychainEngine) {
            return keychainEngine == api::KeychainEngines::BIP49_P2SH ||
                   keychainEngine == api::KeychainEngines::BIP173_P2WPKH ||
                   keychainEngine == api::KeychainEngines::BIP173_P2WSH;
        }

        bool BitcoinLikeKeychain::isNativeSegwit(const std::string &keychainEngine) {
            return keychainEngine == api::KeychainEngines::BIP173_P2WPKH ||
                   keychainEngine == api::KeychainEngines::BIP173_P2WSH;
        }
    }
}

<|start_filename|>core/test/algorand/AlgorandDatabaseTests.cpp<|end_filename|>
/*
 * AlgorandDatabaseTests
 *
 * Created by <NAME> on 25/05/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "AlgorandTestFixtures.hpp"
#include <wallet/algorand/database/AlgorandTransactionDatabaseHelper.hpp>
#include <wallet/algorand/database/AlgorandAccountDatabaseHelper.hpp>
#include <wallet/algorand/operations/AlgorandOperation.hpp>
#include <wallet/algorand/AlgorandWalletFactory.hpp>
#include <wallet/algorand/AlgorandLikeCurrencies.hpp>
#include <wallet/algorand/AlgorandWallet.hpp>
#include <wallet/algorand/AlgorandAccount.hpp>

#include <wallet/common/OperationQuery.h>
#include <api/AccountCreationInfo.hpp>
#include <api/Address.hpp>

#include "../integration/WalletFixture.hpp" // No equivalent in v1 ?

#include <utility>

using namespace ledger::testing::algorand;
using namespace ledger::core::algorand;

class AlgorandDatabaseTest : public WalletFixture<WalletFactory> {

    public:

    void SetUp() override {
        WalletFixture::SetUp();

        auto const currency = currencies::ALGORAND;
        registerCurrency(currency);

        accountInfo = api::AccountCreationInfo(1, {}, {}, { algorand::Address::toPublicKey(OBELIX_ADDRESS) }, {});

        // NOTE: we run the tests on the staging environment which is on the TestNet
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::BLOCKCHAIN_EXPLORER_API_ENDPOINT, "https://algorand.coin.staging.aws.ledger.com");

        wallet = std::dynamic_pointer_cast<algorand::Wallet>(uv::wait(pool->createWallet("algorand", currency.name, configuration)));
        account = createAlgorandAccount(wallet, accountInfo.index, accountInfo);

        accountUid = algorand::AccountDatabaseHelper::createAccountUid(wallet->getWalletUid(), accountInfo.index);
    }

    void TearDown() override {
        WalletFixture::TearDown();
        wallet.reset();
        account.reset();
    }

    std::shared_ptr<Wallet> wallet;
    std::shared_ptr<Account> account;

    api::AccountCreationInfo accountInfo;
    std::string accountUid;
};

TEST_F(AlgorandDatabaseTest, DISABLED_AccountDBTest) {

    // Test reading from DB
    {
        soci::session sql(pool->getDatabaseSessionPool()->getPool());

        algorand::AccountDatabaseEntry accountFromDB;
        auto result = algorand::AccountDatabaseHelper::queryAccount(sql, accountUid, accountFromDB);

        EXPECT_EQ(result, true);
        EXPECT_EQ(accountInfo.index, accountFromDB.index);
        EXPECT_EQ(OBELIX_ADDRESS, accountFromDB.address) << "(This test requires SHA-512/256)";
    }
}

TEST_F(AlgorandDatabaseTest, DISABLED_TransactionsDBTest) {

    auto paymentTxRef = paymentTransaction();
    auto assetConfigTxRef = assetConfigTransaction();
    auto assetTransferTxRef = assetTransferTransaction();

    // Test writing into DB
    {
        soci::session sql(pool->getDatabaseSessionPool()->getPool());
        algorand::TransactionDatabaseHelper::putTransaction(sql, "test-account", paymentTxRef);
        algorand::TransactionDatabaseHelper::putTransaction(sql, "test-account", assetConfigTxRef);
        algorand::TransactionDatabaseHelper::putTransaction(sql, "test-account", assetTransferTxRef);
    }

    // Test reading from DB
    {
        soci::session sql(pool->getDatabaseSessionPool()->getPool());

        model::Transaction paymentTxFromDB;
        auto result = algorand::TransactionDatabaseHelper::getTransactionByHash(sql, *paymentTxRef.header.id, paymentTxFromDB);
        EXPECT_TRUE(result);
        assertSameTransaction(paymentTxRef, paymentTxFromDB);

        model::Transaction assetConfigTxFromDB;
        result = algorand::TransactionDatabaseHelper::getTransactionByHash(sql, *assetConfigTxRef.header.id, assetConfigTxFromDB);
        EXPECT_TRUE(result);
        assertSameTransaction(assetConfigTxRef, assetConfigTxFromDB);

        model::Transaction assetTransferTxFromDB;
        result = algorand::TransactionDatabaseHelper::getTransactionByHash(sql, *assetTransferTxRef.header.id, assetTransferTxFromDB);
        EXPECT_TRUE(result);
        assertSameTransaction(assetTransferTxRef, assetTransferTxFromDB);
    }
}

TEST_F(AlgorandDatabaseTest, DISABLED_OperationsDBTest) {

    auto txRef = paymentTransaction();

    // Test writing into DB
    {
        std::vector<algorand::Operation> operations;
        account->interpretTransaction(txRef, operations);
        account->bulkInsert(operations);
    }

    // Test reading from DB
    {
        auto ops = uv::wait(std::dynamic_pointer_cast<OperationQuery>(account->queryOperations()->complete())->execute());

        EXPECT_EQ(ops.size(), 1);
        auto op = std::dynamic_pointer_cast<algorand::Operation>(ops[0]);

        /* TODO ?
        EXPECT_EQ(op->getAccountIndex(), account->getIndex());
        EXPECT_EQ(op->getAmount(), ?);
        EXPECT_EQ(op->getBlockHeight(), ?);
        EXPECT_EQ(op->getCurrency(), ?);
        EXPECT_EQ(op->getDate(), ?);
        EXPECT_EQ(op->getFees(), ?);
        EXPECT_EQ(op->getOperationType(), ?);
        EXPECT_EQ(op->getPreferences(), ?);
        EXPECT_EQ(op->getRecipients(), ?);
        EXPECT_EQ(op->getSenders(), ?);
        EXPECT_EQ(op->getTrust(), ?);
        EXPECT_EQ(op->getUid(), ?);
        EXPECT_EQ(op->isComplete(), ?);
        */

        auto txRetrieved = op->getTransactionData();
        assertSameTransaction(txRef, txRetrieved);
    }
}

TEST_F(AlgorandDatabaseTest, DISABLED_queryTransactions)
{
    soci::session sql(pool->getDatabaseSessionPool()->getPool());
    auto payment = paymentTransaction();
    auto assetTransfer = assetTransferTransaction();
    auto assetConfig = assetConfigTransaction();
    assetConfig.header.sender = algorand::Address(TEST_ACCOUNT_ADDRESS);

    std::vector<algorand::Operation> operations;
    account->interpretTransaction(payment, operations);
    account->interpretTransaction(assetTransfer, operations);
    account->interpretTransaction(assetConfig, operations);
    account->bulkInsert(operations);

    auto txns = TransactionDatabaseHelper::queryTransactionsInvolving(sql, OBELIX_ADDRESS);
    ASSERT_EQ(txns.size(), 2);
    for (const auto& txn : txns) {
        if (txn.header.type == model::constants::pay) {
            assertSameTransaction(payment, txn);
        } else if (txn.header.type == model::constants::axfer) {
            assertSameTransaction(assetTransfer, txn);
        }
    }

    txns = TransactionDatabaseHelper::queryAssetTransferTransactionsInvolving(sql, 342836, OBELIX_ADDRESS);
    ASSERT_EQ(txns.size(), 1);
    for (const auto& txn : txns) {
        if (txn.header.type == model::constants::axfer) {
            assertSameTransaction(assetTransfer, txn);
        }
    }
}


<|start_filename|>core/test/integration/transactions/optimisticupdate_xrp_tests.cpp<|end_filename|>
/*
 *
 * optimisticupdate_xrp_tests.cpp
 * ledger-core
 *
 * Created by <NAME> on 10/07/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <gtest/gtest.h>
#include "../BaseFixture.h"
#include <set>
#include <api/ConfigurationDefaults.hpp>
#include <api/KeychainEngines.hpp>
#include <api/RippleLikeTransaction.hpp>
#include <api/OperationOrderKey.hpp>
#include <utils/DateUtils.hpp>
#include <wallet/ripple/database/RippleLikeAccountDatabaseHelper.h>
#include <wallet/ripple/transaction_builders/RippleLikeTransactionBuilder.h>
#include <wallet/ripple/api_impl/RippleLikeTransactionApi.h>
#include <api/RippleConfigurationDefaults.hpp>
#include <iostream>
#include "FakeHttpClient.hpp"

using namespace std;

class RippleLikeOptimisticTransactionUpdate : public BaseFixture
{
};

std::pair<std::shared_ptr<LambdaEventReceiver>, ledger::core::Future<bool>> createSyncReceiver();

TEST_F(RippleLikeOptimisticTransactionUpdate, BroadcastTransaction)
{

    auto fakeHttp = std::make_shared<test::FakeHttpClient>();

    backend = std::static_pointer_cast<DatabaseBackend>(DatabaseBackend::getSqlite3Backend());

    auto pool = WalletPool::newInstance(
        "my_pool",
        "",
        fakeHttp,
        ws,
        resolver,
        printer,
        dispatcher,
        rng,
        backend,
        api::DynamicObject::newInstance(),
        nullptr,
        nullptr);
    {
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,
                                 "44'/<coin_type>'/<account>'/<node>/<address>");
        configuration->putString(api::Configuration::BLOCKCHAIN_EXPLORER_API_ENDPOINT, "http://test.test");
        auto wallet = uv::wait(pool->createWallet("my_wallet", "ripple", configuration));
        {
            auto account = createRippleLikeAccount(wallet, 0, XRP_KEYS_INFO);
            auto waiter = createSyncReceiver();

            fakeHttp->setBehavior({{fmt::format("http://test.test:{}/", api::RippleConfigurationDefaults::RIPPLE_DEFAULT_PORT),
                                    test::FakeUrlConnection::fromString("{\"result\":{\"engine_result\":\"tesSUCCESS\",\"tx_json\":{\"hash\":\"AC0D84CB81E8ECA92E7EF9ABC3526FAED54DE07763A308296B28468D68D34991\"}}}")}});
            
            auto dummy_transaction = make_shared<RippleLikeTransactionApi>(account->getWallet()->getCurrency());
            dummy_transaction->setSequence(BigInt(1));
            dummy_transaction->setLedgerSequence(BigInt(1));
            dummy_transaction->setValue(make_shared<BigInt>(1));
            dummy_transaction->setFees(make_shared<BigInt>(1));
            dummy_transaction->setSender(account->getKeychain()->getAddress());
            dummy_transaction->setReceiver(account->getKeychain()->getAddress());
            dummy_transaction->setHash("AC0D84CB81E8ECA92E7EF9ABC3526FAED54DE07763A308296B28468D68D34991");
            std::vector<uint8_t> dummy_key = {1 , 2 , 3 , 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32};
            dummy_transaction->setSigningPubKey(dummy_key);
            dummy_transaction->setDERSignature(dummy_key);

            auto tx_hash = uv::wait(account->broadcastTransaction(dynamic_pointer_cast<api::RippleLikeTransaction>(dummy_transaction)));
            auto explorer_tx = uv::wait(account->getTransaction(tx_hash));

            EXPECT_EQ(explorer_tx->hash, "AC0D84CB81E8ECA92E7EF9ABC3526FAED54DE07763A308296B28468D68D34991");
            EXPECT_EQ(explorer_tx->value, BigInt(1));
            EXPECT_EQ(explorer_tx->fees, BigInt(1));
            EXPECT_EQ(explorer_tx->sequence, BigInt(1));
            EXPECT_EQ(explorer_tx->receiver, account->getKeychain()->getAddress()->toString());
            EXPECT_EQ(explorer_tx->sender, account->getKeychain()->getAddress()->toString());
            EXPECT_EQ(explorer_tx->confirmations, 0);
            EXPECT_EQ(explorer_tx->status, 1);
        }
    }
}


<|start_filename|>core/src/wallet/bitcoin/synchronizers/BlockchainExplorerAccountSynchronizer.h<|end_filename|>
/*
 *
 * BlockchainExplorerAccountSynchronizer
 * ledger-core
 *
 * Created by <NAME> on 26/05/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_BLOCKCHAINEXPLORERACCOUNTSYNCHRONIZER_H
#define LEDGER_CORE_BLOCKCHAINEXPLORERACCOUNTSYNCHRONIZER_H

#include <wallet/bitcoin/keychains/BitcoinLikeKeychain.hpp>
#include <wallet/bitcoin/synchronizers/BitcoinLikeAccountSynchronizer.hpp>
#include <wallet/bitcoin/explorers/BitcoinLikeBlockchainExplorer.hpp>
#include <wallet/common/synchronizers/AbstractBlockchainExplorerAccountSynchronizer.h>
#include <wallet/pool/WalletPool.hpp>
#include <preferences/Preferences.hpp>
#include <async/DedicatedContext.hpp>

namespace ledger {
    namespace core {
        class BitcoinLikeAccount;

        class BlockchainExplorerAccountSynchronizer : public BitcoinLikeAccountSynchronizer,
                                                      public DedicatedContext,
                                                      public std::enable_shared_from_this<BlockchainExplorerAccountSynchronizer> {
        public:
            using Transaction = BitcoinLikeBlockchainExplorer::Transaction;
            struct SynchronizationBuddy {
                std::shared_ptr<Preferences> preferences;
                std::shared_ptr<spdlog::logger> logger;
                std::chrono::system_clock::time_point startDate;
                std::shared_ptr<AbstractWallet> wallet;
                std::shared_ptr<DynamicObject> configuration;
                uint32_t halfBatchSize;
                std::shared_ptr<BitcoinLikeKeychain> keychain;
                Option<BlockchainExplorerAccountSynchronizationSavedState> savedState;
                std::shared_ptr<BitcoinLikeAccount> account;
                std::map<std::string, std::string> transactionsToDrop;
                BlockchainExplorerAccountSynchronizationResult context;
                std::string synchronizationTag;
                virtual ~SynchronizationBuddy() = default;
            };
            
            BlockchainExplorerAccountSynchronizer(const std::shared_ptr<WalletPool>& pool,
                                                  const std::shared_ptr<BitcoinLikeBlockchainExplorer>& explorer);

            void updateTransactionsToDrop(soci::session &sql,
                                          std::shared_ptr<SynchronizationBuddy> &buddy,
                                          const std::string &accountUid);

            void reset(const std::shared_ptr<BitcoinLikeAccount>& account, const std::chrono::system_clock::time_point& toDate) override;
            std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>> synchronize(const std::shared_ptr<BitcoinLikeAccount>& account) override;
            bool isSynchronizing() const override;

            void interpretTransaction(const Transaction& transaction,
                    const std::shared_ptr<SynchronizationBuddy>& buddy,
                    std::vector<Operation>& out);

            std::shared_ptr<SynchronizationBuddy> makeSynchronizationBuddy();
            Future<Unit> synchronizeMempool(const std::shared_ptr<SynchronizationBuddy> &buddy);

            Future<Unit> recoverFromFailedSynchronization(const std::shared_ptr<SynchronizationBuddy> &buddy);

        private:
            std::shared_ptr<BlockchainExplorerAccountSynchronizer> getSharedFromThis();
            std::shared_ptr<api::ExecutionContext> getSynchronizerContext();
            Future<BlockchainExplorerAccountSynchronizationResult> performSynchronization(const std::shared_ptr<BitcoinLikeAccount>& account);
            static void initializeSavedState(Option<BlockchainExplorerAccountSynchronizationSavedState>& savedState, int32_t halfBatchSize);
            std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>> synchronizeAccount(const std::shared_ptr<BitcoinLikeAccount>& account);
            Future<Unit> extendKeychain(uint32_t currentBatchIndex, std::shared_ptr<SynchronizationBuddy> buddy);
            Future<std::shared_ptr<BitcoinLikeBlockchainExplorer::Block>> updateCurrentBlock(std::shared_ptr<SynchronizationBuddy> buddy);
            Future<Unit> synchronizeBatches(uint32_t currentBatchIndex, std::shared_ptr<SynchronizationBuddy> buddy);
            Future<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>> getTransactionBulk(int currentBatchIndex, const std::shared_ptr<SynchronizationBuddy>& buddy);
            Future<bool> synchronizeBatch(uint32_t currentBatchIndex, std::shared_ptr<SynchronizationBuddy> buddy, bool hadTransactions = false);
            Future<std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>> requestTransactionsFromExplorer(const std::shared_ptr<SynchronizationBuddy>& buddy);
            static std::pair<std::string, Option<std::string>> getHashkeyAndBlockhash(int currentBatchIndex, const std::shared_ptr<SynchronizationBuddy>& buddy);

            std::shared_ptr<Preferences> _internalPreferences;
            std::shared_ptr<BitcoinLikeBlockchainExplorer> _explorer;
            std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>> _notifier;
            std::mutex _lock;
            std::shared_ptr<BitcoinLikeAccount> _currentAccount;
            std::vector<std::string> _addresses;
            //In order to avoid requesting same batch multiple times to the explorer, we cache transaction bulks in the memory. 
            //In the following hashmap, The key is the batch index + BlockHash, the value is the transactionBulk return from the explorer and generated by the transactionBulk parser
            std::map<std::string, std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>> _cachedTransactionBulks;
            //A variable to save keys in _cachedTransactionBulks
            std::vector<std::string> _hashkeys;
            std::shared_ptr<ledger::core::Benchmarker> _explorerBenchmark;
        };
    }
}

#endif //LEDGER_CORE_BLOCKCHAINEXPLORERACCOUNTSYNCHRONIZER_H


<|start_filename|>core/src/api/EventCode.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from events.djinni

#include "EventCode.hpp"  // my header
#include "enum_from_string.hpp"

namespace ledger { namespace core { namespace api {

std::string to_string(const EventCode& eventCode) {
    switch (eventCode) {
        case EventCode::UNDEFINED: return "UNDEFINED";
        case EventCode::NEW_OPERATION: return "NEW_OPERATION";
        case EventCode::UPDATE_OPERATIONS: return "UPDATE_OPERATIONS";
        case EventCode::NEW_BLOCK: return "NEW_BLOCK";
        case EventCode::DELETED_OPERATION: return "DELETED_OPERATION";
        case EventCode::SYNCHRONIZATION_STARTED: return "SYNCHRONIZATION_STARTED";
        case EventCode::SYNCHRONIZATION_FAILED: return "SYNCHRONIZATION_FAILED";
        case EventCode::SYNCHRONIZATION_SUCCEED: return "SYNCHRONIZATION_SUCCEED";
        case EventCode::SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT: return "SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT";
        case EventCode::NEW_ERC20_OPERATION: return "NEW_ERC20_OPERATION";
        case EventCode::UPDATE_ERC20_OPERATIONS: return "UPDATE_ERC20_OPERATIONS";
    };
};
template <>
EventCode from_string(const std::string& eventCode) {
    if (eventCode == "UNDEFINED") return EventCode::UNDEFINED;
    else if (eventCode == "NEW_OPERATION") return EventCode::NEW_OPERATION;
    else if (eventCode == "UPDATE_OPERATIONS") return EventCode::UPDATE_OPERATIONS;
    else if (eventCode == "NEW_BLOCK") return EventCode::NEW_BLOCK;
    else if (eventCode == "DELETED_OPERATION") return EventCode::DELETED_OPERATION;
    else if (eventCode == "SYNCHRONIZATION_STARTED") return EventCode::SYNCHRONIZATION_STARTED;
    else if (eventCode == "SYNCHRONIZATION_FAILED") return EventCode::SYNCHRONIZATION_FAILED;
    else if (eventCode == "SYNCHRONIZATION_SUCCEED") return EventCode::SYNCHRONIZATION_SUCCEED;
    else if (eventCode == "SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT") return EventCode::SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT;
    else if (eventCode == "NEW_ERC20_OPERATION") return EventCode::NEW_ERC20_OPERATION;
    else return EventCode::UPDATE_ERC20_OPERATIONS;
};

std::ostream &operator<<(std::ostream &os, const EventCode &o)
{
    switch (o) {
        case EventCode::UNDEFINED:  return os << "UNDEFINED";
        case EventCode::NEW_OPERATION:  return os << "NEW_OPERATION";
        case EventCode::UPDATE_OPERATIONS:  return os << "UPDATE_OPERATIONS";
        case EventCode::NEW_BLOCK:  return os << "NEW_BLOCK";
        case EventCode::DELETED_OPERATION:  return os << "DELETED_OPERATION";
        case EventCode::SYNCHRONIZATION_STARTED:  return os << "SYNCHRONIZATION_STARTED";
        case EventCode::SYNCHRONIZATION_FAILED:  return os << "SYNCHRONIZATION_FAILED";
        case EventCode::SYNCHRONIZATION_SUCCEED:  return os << "SYNCHRONIZATION_SUCCEED";
        case EventCode::SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT:  return os << "SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT";
        case EventCode::NEW_ERC20_OPERATION:  return os << "NEW_ERC20_OPERATION";
        case EventCode::UPDATE_ERC20_OPERATIONS:  return os << "UPDATE_ERC20_OPERATIONS";
    }
}

} } }  // namespace ledger::core::api


<|start_filename|>core/src/api/AllocationMetrics.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#ifndef DJINNI_GENERATED_ALLOCATIONMETRICS_HPP
#define DJINNI_GENERATED_ALLOCATIONMETRICS_HPP

#include <cstdint>
#include <string>
#include <unordered_map>
#ifndef LIBCORE_EXPORT
    #if defined(_MSC_VER)
       #include <libcore_export.h>
    #else
       #define LIBCORE_EXPORT
    #endif
#endif

namespace ledger { namespace core { namespace api {

class LIBCORE_EXPORT AllocationMetrics {
public:
    virtual ~AllocationMetrics() {}

    static std::unordered_map<std::string, int32_t> getObjectAllocations();
};

} } }  // namespace ledger::core::api
#endif //DJINNI_GENERATED_ALLOCATIONMETRICS_HPP


<|start_filename|>core/src/wallet/cosmos/CosmosLikeAccount.hpp<|end_filename|>
/*
 *
 * CosmosLikeAccount
 *
 * Created by <NAME> on 14/06/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_COSMOSLIKEACCOUNT_H
#define LEDGER_CORE_COSMOSLIKEACCOUNT_H

#include <api/Address.hpp>
#include <api/Amount.hpp>
#include <api/AmountCallback.hpp>
#include <api/CosmosGasLimitRequest.hpp>
#include <api/CosmosLikeAccount.hpp>
#include <api/CosmosLikeDelegationListCallback.hpp>
#include <api/CosmosLikeRedelegationListCallback.hpp>
#include <api/CosmosLikeRewardListCallback.hpp>
#include <api/CosmosLikeTransactionBuilder.hpp>
#include <api/CosmosLikeUnbondingListCallback.hpp>
#include <api/CosmosLikeValidatorCallback.hpp>
#include <api/CosmosLikeValidatorListCallback.hpp>
#include <api/ErrorCodeCallback.hpp>
#include <api/Event.hpp>
#include <api/StringCallback.hpp>
#include <ctime>
#include <wallet/common/AbstractAccount.hpp>
#include <wallet/common/AbstractWallet.hpp>
#include <wallet/cosmos/api_impl/CosmosLikeDelegation.hpp>
#include <wallet/cosmos/api_impl/CosmosLikeOperation.hpp>

#include <wallet/cosmos/api_impl/CosmosLikeRedelegation.hpp>
#include <wallet/cosmos/api_impl/CosmosLikeReward.hpp>
#include <wallet/cosmos/api_impl/CosmosLikeUnbonding.hpp>
#include <wallet/cosmos/explorers/CosmosLikeBlockchainExplorer.hpp>
#include <wallet/cosmos/keychains/CosmosLikeKeychain.hpp>

namespace ledger {
namespace core {
class CosmosLikeAccountSynchronizer;

class CosmosLikeAccount : public api::CosmosLikeAccount, public AbstractAccount {
   public:
    CosmosLikeAccount(
        const std::shared_ptr<AbstractWallet> &wallet,
        int32_t index,
        const std::shared_ptr<CosmosLikeBlockchainExplorer> &explorer,
        const std::shared_ptr<CosmosLikeAccountSynchronizer> &synchronizer,
        const std::shared_ptr<CosmosLikeKeychain> &keychain);

    std::shared_ptr<api::CosmosLikeAccount> asCosmosLikeAccount() override;

    /// Fill an Operation from cosmos specific data in the given transaction and message.
    /// \param [out] out The Operation to fill
    /// \param [in] wallet A pointer to the wallet inflating the operation (for currency handling)
    /// \param [in] tx The transaction data to fill Operation metadata with
    /// \param [in] msg The cosmos message data to fill Operation metadata with
    void inflateOperation(
        CosmosLikeOperation &out,
        const std::shared_ptr<const AbstractWallet> &wallet,
        const cosmos::Transaction &tx,
        const cosmos::Message &msg);

    /// Interpret a transaction and all its messages in order to be inserted later in the database.
    /// \param [in] transaction The transaction to maybe insert in the database
    /// \param [out] out The operations to be insterted later in the database
    void interpretTransaction(const cosmos::Transaction& transaction, std::vector<CosmosLikeOperation>& out);

    /// Insert a list of an already interpreted operations in the database.
    /// \param [in] operations The operations to be insterted in the database
    Try<int> bulkInsert(const std::vector<CosmosLikeOperation>& operations);

    /// Insert a block metadata in the database
    /// \param [in] sql The sql session accessing the database
    /// \param [in] block The block to maybe insert in the database
    /// \return true if the block was actually inserted/updated. false otherwise (see
    /// BlockDatabaseHelper::putBlock)
    bool putBlock(soci::session &sql, const api::Block &block);


    std::shared_ptr<CosmosLikeKeychain> getKeychain() const;
    std::shared_ptr<api::Keychain> getAccountKeychain() override;
    std::string getAddress() const;

    FuturePtr<Amount> getBalance() override;

    Future<AbstractAccount::AddressList> getFreshPublicAddresses() override;

    Future<std::vector<std::shared_ptr<api::Amount>>> getBalanceHistory(
        const std::string &start, const std::string &end, api::TimePeriod precision) override;

    Future<api::ErrorCode> eraseDataSince(
        const std::chrono::system_clock::time_point &date) override;

    bool isSynchronizing() override;

    std::shared_ptr<api::EventBus> synchronize() override;

    std::string getRestoreKey() override;

    void broadcastRawTransaction(
        const std::string &transaction,
        const std::shared_ptr<api::StringCallback> &callback) override;

    void broadcastTransaction(
        const std::shared_ptr<api::CosmosLikeTransaction> &transaction,
        const std::shared_ptr<api::StringCallback> &callback) override;

    std::shared_ptr<api::CosmosLikeTransactionBuilder> buildTransaction() override;
    std::shared_ptr<api::CosmosLikeTransactionBuilder> buildTransaction(
        const std::string &senderAddress);

    std::shared_ptr<api::OperationQuery> queryOperations() override;

    void getEstimatedGasLimit(
        const std::shared_ptr<api::CosmosLikeTransaction> &transaction,
        const std::shared_ptr<api::BigIntCallback> &callback) override;

    void estimateGas(
        const api::CosmosGasLimitRequest &request,
        const std::shared_ptr<api::BigIntCallback> &callback) override;

    // Account related data
    void getSequence(const std::shared_ptr<api::StringCallback> &callback) override;
    void getAccountNumber(const std::shared_ptr<api::StringCallback> &callback) override;
    void getWithdrawAddress(const std::shared_ptr<api::StringCallback> &callback) override;
    cosmos::Account getInfo() const;

    // Balances
    /// Ask the explorer for the total balance of the account.
    /// \return A Future with the total balance
    FuturePtr<Amount> getTotalBalance() const;
    void getTotalBalance(const std::shared_ptr<api::AmountCallback> &callback) override;

    /// Ask the explorer for the total balance *without the pending rewards* of the account.
    /// \return A Future with the total balance *without the pending rewards*
    FuturePtr<Amount> getTotalBalanceWithoutPendingRewards() const;

    /// Ask the explorer for the balance currently delegated by the account.
    /// \return A Future with the delegated balance
    FuturePtr<Amount> getDelegatedBalance() const;
    void getDelegatedBalance(const std::shared_ptr<api::AmountCallback> &callback) override;

    /// Ask the explorer for the total pending delegation rewards for the account
    /// \return A Future with the total pending rewards
    FuturePtr<Amount> getPendingRewardsBalance() const;
    void getPendingRewardsBalance(const std::shared_ptr<api::AmountCallback> &callback) override;

    /// Ask the explorer for the total amount in unbonding phase for the account.
    /// \return A Future with the total amount
    FuturePtr<Amount> getUnbondingBalance() const;
    void getUnbondingBalance(const std::shared_ptr<api::AmountCallback> &callback) override;

    /// Ask the explorer for the spendable balance of the account.
    /// \return A Future with the spendable balance
    FuturePtr<Amount> getSpendableBalance() const;
    void getSpendableBalance(const std::shared_ptr<api::AmountCallback> &callback) override;

    /// Ask the explorer for the currently active validator set on the chain.
    /// \return A Future with the list of validators
    Future<cosmos::ValidatorList> getActiveValidatorSet() const;
    void getLatestValidatorSet(
        const std::shared_ptr<api::CosmosLikeValidatorListCallback> &callback) override;
    /// Ask the explorer for specific informations about a validator. SigningInfo and
    /// DistributionInfo are included there. \param [in] validatorAddress The 'cosmosvaloper'
    /// address of the queried validator. \return A Future with the complete information about a
    /// validator
    Future<cosmos::Validator> getValidatorInfo(const std::string &validatorAddress) const;
    void getValidatorInfo(
        const std::string &validatorAddress,
        const std::shared_ptr<api::CosmosLikeValidatorCallback> &callback) override;

    void getDelegations(
        const std::shared_ptr<api::CosmosLikeDelegationListCallback> &callback) override;
    /// Ask the explorer for the current delegations of the account.
    /// \return A Future with the list of Delegations
    Future<std::vector<std::shared_ptr<api::CosmosLikeDelegation>>> getDelegations();

    void getPendingRewards(
        const std::shared_ptr<api::CosmosLikeRewardListCallback> &callback) override;
    /// Ask the explorer for the pending rewards specifics of the account.
    /// \return A Future with the list of pending rewards
    Future<std::vector<std::shared_ptr<api::CosmosLikeReward>>> getPendingRewards();

    // "Pending" status info
    /// Ask the explorer for the current unbondings of the account.
    /// \return A Future with the list of unbondings
    Future<std::vector<std::shared_ptr<api::CosmosLikeUnbonding>>> getUnbondings() const;
    void getUnbondings(
        const std::shared_ptr<api::CosmosLikeUnbondingListCallback> &callback) override;
    /// Ask the explorer for the current redelegations of the account.
    /// \return A Future with the list of redelegations
    Future<std::vector<std::shared_ptr<api::CosmosLikeRedelegation>>> getRedelegations() const;
    void getRedelegations(
        const std::shared_ptr<api::CosmosLikeRedelegationListCallback> &callback) override;

   private:
    std::shared_ptr<CosmosLikeAccount> getSelf();
    void updateFromDb();

    /// Updates account level data (sequence, accountNumber, ...)
    /// Example result from Gaia explorer :
    /// base_url/auth/accounts/{address} with a valid, 0 transaction address :
    /// {
    ///  "height": "1296656",
    ///  "result": {
    ///    "type": "cosmos-sdk/Account",
    ///    "value": {
    ///      "address": "",
    ///      "coins": [],
    ///      "public_key": null,
    ///      "account_number": "0",
    ///      "sequence": "0"
    ///    }
    ///  }
    /// }
    void updateAccountDataFromNetwork();

    // These helpers stay on CosmosLikeAccount *only* because they have to use their
    // knowledge of Address information in order to correctly map operation type.
    // An operation type is always seen from the account point of view.

    /// Set the type and the amount of an Operation from a cosmos Message in the context of the
    /// account.
    /// \param [out] out the Operation to fill
    /// \param [in] msg the cosmos Message to use as source of information
    void setOperationTypeAndAmount(CosmosLikeOperation &out, const cosmos::Message &msg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos Send Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerSendMsg the cosmos send message to use, unwrapped.
    void fillOperationTypeAmountFromSend(
        CosmosLikeOperation &out, const cosmos::MsgSend &innerSendMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos MultiSend Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerMultiSendMsg the cosmos multiSend message to use, unwrapped.
    void fillOperationTypeAmountFromMultiSend(
        CosmosLikeOperation &out, const cosmos::MsgMultiSend &innerMultiSendMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos Delegate Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerDelegateMsg the cosmos delegate message to use, unwrapped.
    void fillOperationTypeAmountFromDelegate(
        CosmosLikeOperation &out, const cosmos::MsgDelegate &innerDelegateMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos Undelegate Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerUndelegateMsg the cosmos undelegate message to use, unwrapped.
    void fillOperationTypeAmountFromUndelegate(
        CosmosLikeOperation &out, const cosmos::MsgUndelegate &innerUndelegateMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos BeginRedelegate
    /// Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerBeginRedelegateMsg the
    /// cosmos beginRedelegate message to use, unwrapped.
    void fillOperationTypeAmountFromBeginRedelegate(
        CosmosLikeOperation &out, const cosmos::MsgBeginRedelegate &innerBeginRedelegateMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos SubmitProposal Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerSubmitProposalMsg the cosmos submitProposal message to use, unwrapped.
    void fillOperationTypeAmountFromSubmitProposal(
        CosmosLikeOperation &out, const cosmos::MsgSubmitProposal &innerSubmitProposalMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos Deposit Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerDepositMsg the cosmos deposit message to use, unwrapped.
    void fillOperationTypeAmountFromDeposit(
        CosmosLikeOperation &out, const cosmos::MsgDeposit &innerDepositMsg) const;
    /// Set the type and the amount of an Operation from an unwrapped cosmos Fees Message.
    /// \param [out] out the Operation to fill
    /// \param [in] innerFeesMsg the cosmos fees message to use, unwrapped.
    void fillOperationTypeAmountFromFees(
        CosmosLikeOperation &out, const cosmos::MsgFees &innerFeesMsg) const;

    /// Compute the exact fees paid for the Transaction, applying the consumed gas ratio if available.
    /// \param [in] tx A Cosmos Transaction to compute the fees from
    /// \return the amount of fees paid for this transaction in uatom
        static uint32_t computeFeesForTransaction(const cosmos::Transaction& tx);

    std::shared_ptr<cosmos::Account> _accountData;
    std::shared_ptr<CosmosLikeKeychain> _keychain;
    std::shared_ptr<CosmosLikeBlockchainExplorer> _explorer;
    std::shared_ptr<CosmosLikeAccountSynchronizer> _synchronizer;
    uint64_t _currentBlockHeight;
    std::shared_ptr<api::EventBus> _currentSyncEventBus;
    std::mutex _synchronizationLock;
};
}  // namespace core
}  // namespace ledger
#endif  // LEDGER_CORE_COSMOSLIKEACCOUNT_H


<|start_filename|>core/src/wallet/ripple/synchronizers/RippleLikeAccountSynchronizer.hpp<|end_filename|>
/*
 *
 * RippleLikeAccountSynchronizer
 *
 * Created by <NAME> on 06/01/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */


#ifndef LEDGER_CORE_RIPPLELIKEACCOUNTSYNCHRONIZER_H
#define LEDGER_CORE_RIPPLELIKEACCOUNTSYNCHRONIZER_H

#include <wallet/ripple/keychains/RippleLikeKeychain.h>
#include <wallet/ripple/explorers/RippleLikeBlockchainExplorer.h>
#include <wallet/pool/WalletPool.hpp>
#include <async/DedicatedContext.hpp>
#include <events/ProgressNotifier.h>

#include <memory>
#include <mutex>

namespace ledger {
namespace core {

class RippleLikeAccount;

struct AccountSynchronizationBatchSavedState {
    std::string blockHash;
    uint32_t blockHeight;

    AccountSynchronizationBatchSavedState() : blockHash(""), blockHeight(0)
    {}

    template <class Archive>
    void serialize(Archive &archive)
    {
        archive(blockHash, blockHeight);
    };
};

struct AccountSynchronizationSavedState {
    uint32_t halfBatchSize;
    std::vector<AccountSynchronizationBatchSavedState> batches;
    std::map<std::string, std::string> pendingTxsHash;

    AccountSynchronizationSavedState() : halfBatchSize(0)
    {}

    template <class Archive>
    void serialize(Archive &archive)
    {
        archive(halfBatchSize, batches, pendingTxsHash);
    }
};

struct AccountSynchronizationContext {
    Option<uint32_t> reorgBlockHeight;
    uint32_t lastBlockHeight = 0;
    uint32_t newOperations = 0;
};

struct SynchronizationBuddy {
    std::shared_ptr<Preferences> preferences;
    std::shared_ptr<spdlog::logger> logger;
    std::chrono::system_clock::time_point startDate;
    std::shared_ptr<AbstractWallet> wallet;
    std::shared_ptr<DynamicObject> configuration;
    uint32_t halfBatchSize;
    std::shared_ptr<RippleLikeKeychain> keychain;
    Option<AccountSynchronizationSavedState> savedState;
    std::shared_ptr<RippleLikeAccount> account;
    std::map<std::string, std::string> transactionsToDrop;
    AccountSynchronizationContext context;
    std::string synchronizationTag;
};

class RippleLikeAccountSynchronizer :
    public DedicatedContext,
    public std::enable_shared_from_this<RippleLikeAccountSynchronizer>
{
public:
    RippleLikeAccountSynchronizer(
        const std::shared_ptr<WalletPool> &pool,
        const std::shared_ptr<RippleLikeBlockchainExplorer> &explorer);

    std::shared_ptr<ProgressNotifier<AccountSynchronizationContext>>
    synchronizeAccount(const std::shared_ptr<RippleLikeAccount> &account);

    Future<AccountSynchronizationContext>
    performSynchronization(const std::shared_ptr<RippleLikeAccount>& account);

    Future<Unit> synchronizeBatches(
        uint32_t currentBatchIndex,
        std::shared_ptr<SynchronizationBuddy> buddy);

    Future<bool> synchronizeBatch(
        uint32_t currentBatchIndex,
        std::shared_ptr<SynchronizationBuddy> buddy,
        bool hadTransactions = false);

    Future<Unit> synchronizeMempool(const std::shared_ptr<SynchronizationBuddy>& buddy);

    void updateCurrentBlock(
        std::shared_ptr<SynchronizationBuddy> &buddy,
        const std::shared_ptr<api::ExecutionContext> &context);

    void updateTransactionsToDrop(
        soci::session &sql,
        std::shared_ptr<SynchronizationBuddy> &buddy,
        const std::string &accountUid);

private:
    std::shared_ptr<RippleLikeBlockchainExplorer> _explorer;
    std::shared_ptr<ProgressNotifier<AccountSynchronizationContext>> _notifier;
    std::mutex _lock;
    std::shared_ptr<RippleLikeAccount> _currentAccount;
    std::shared_ptr<Preferences> _internalPreferences;
};

} // namespace core
} // namespace ledger

#endif // LEDGER_CORE_RIPPLELIKEACCOUNTSYNCHRONIZER_H


<|start_filename|>core/test/coin-integration/stellar/create_stellar_account_tests.cpp<|end_filename|>
/*
 *
 * create_stellar_account_tests.cpp
 * ledger-core
 *
 * Created by <NAME> on 18/02/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "StellarFixture.hpp"

TEST_F(StellarFixture, CreateAccountWithPubKey) {
    auto pool = newPool();
    auto wallet = newWallet(pool, "my_wallet", "stellar", api::DynamicObject::newInstance());
    auto info = uv::wait(wallet->getNextAccountCreationInfo());
    auto a = newAccount(wallet, 0, defaultAccount());
    auto account = std::static_pointer_cast<AbstractAccount>(uv::wait(wallet->getAccount(0)));
    auto address = uv::wait(account->getFreshPublicAddresses()).front()->toString();
    auto derivation = uv::wait(account->getFreshPublicAddresses()).front()->getDerivationPath().value();
    EXPECT_EQ(address, "GCQQQPIROIEFHIWEO2QH4KNWJYHZ5MX7RFHR4SCWFD5KPNR5455E6BR3");
    EXPECT_EQ(info.derivations.size(), 1);
    EXPECT_EQ(info.derivations[0], "44'/148'/0'");
    EXPECT_EQ(derivation, "");
}

<|start_filename|>core/src/wallet/stellar/synchronizers/StellarLikeBlockchainExplorerAccountSynchronizer.hpp<|end_filename|>
/*
 *
 * StellarLikeBlockchainExplorerAccountSynchronizer.hpp
 * ledger-core
 *
 * Created by <NAME> on 10/07/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_STELLARLIKEBLOCKCHAINEXPLORERACCOUNTSYNCHRONIZER_HPP
#define LEDGER_CORE_STELLARLIKEBLOCKCHAINEXPLORERACCOUNTSYNCHRONIZER_HPP

#include "StellarLikeAccountSynchronizer.h"
#include <wallet/stellar/explorers/StellarLikeBlockchainExplorer.hpp>
#include <wallet/pool/WalletPool.hpp>
#include <events/EventPublisher.hpp>

namespace ledger {
    namespace core {

        class StellarLikeBlockchainExplorerAccountSynchronizer
                : public StellarLikeAccountSynchronizer,
                  public DedicatedContext,
                  public std::enable_shared_from_this<StellarLikeBlockchainExplorerAccountSynchronizer> {
        public:
            /**
             * Save the current state of the synchronize to enable incremental update and keep current
             * pagination cursors, to avoid synchronizing everytime the full account.
             */
            struct SavedState {
                /**
                 * Last paging token used by the synchronizer for synchronizing transactions.
                 */
                Option<std::string> transactionPagingToken;

                /**
                 * Inserted operation counter during last synchronization.
                 * The counter is reset at the beginning of each synchronization, not matter what.
                 */
                uint32_t insertedOperations = 0;

                /**
                 * Last block height fetched during last synchronization
                 */
                uint64_t lastBlockHeight = 0;

                template<class Archive>
                void serialize(Archive & archive) {
                    archive(transactionPagingToken.getValueOr(""));
                };
            };

            StellarLikeBlockchainExplorerAccountSynchronizer(
                    const std::shared_ptr<WalletPool>& pool,
                    const std::shared_ptr<StellarLikeBlockchainExplorer>& explorer
                    );
            void reset(const std::shared_ptr<StellarLikeAccount> &account,
                       const std::chrono::system_clock::time_point &toDate) override;

            std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>>
            synchronize(const std::shared_ptr<StellarLikeAccount> &account) override;

            bool isSynchronizing() const override;

        protected:
            void synchronizeAccount(const std::shared_ptr<StellarLikeAccount>& account);
            void synchronizeAccount(const std::shared_ptr<StellarLikeAccount>& account,
                                    SavedState& state);
            void synchronizeTransactions(const std::shared_ptr<StellarLikeAccount>& account,
                                         SavedState& state);
            inline void failSynchronization(const Exception& ex);
            inline void endSynchronization(const std::shared_ptr<StellarLikeAccount>& account, SavedState const& state);


        private:
            std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>> _notifier;
            std::shared_ptr<StellarLikeBlockchainExplorer> _explorer;
            std::shared_ptr<DatabaseSessionPool> _database;
        };
    }
}


#endif //LEDGER_CORE_STELLARLIKEBLOCKCHAINEXPLORERACCOUNTSYNCHRONIZER_HPP


<|start_filename|>core/test/integration/accounts_public_interfaces_test.cpp<|end_filename|>
/*
 *
 * accounts_public_interfaces_test.cpp
 * ledger-core
 *
 * Created by <NAME> on 25/09/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "BaseFixture.h"
#include "../fixtures/medium_xpub_fixtures.h"
#include "../fixtures/testnet_xpub_fixtures.h"
#include <wallet/common/OperationQuery.h>
#include <api/KeychainEngines.hpp>
#include <utils/DateUtils.hpp>
#include <iostream>
using namespace std;
class AccountsPublicInterfaceTest : public BaseFixture {
public:
    void SetUp() override {
        BaseFixture::SetUp();
        recreate();
    }

    void recreate() {
        pool = newDefaultPool();
        wallet = uv::wait(pool->createWallet("my_wallet", "bitcoin", DynamicObject::newInstance()));
    }

    void TearDown() override {
        BaseFixture::TearDown();
        uv::wait(pool->deleteWallet("my_wallet"));
        pool = nullptr;
        wallet = nullptr;
    }

    std::shared_ptr<WalletPool> pool;
    std::shared_ptr<AbstractWallet> wallet;
};

TEST_F(AccountsPublicInterfaceTest, GetAddressOnEmptyAccount) {
    auto account = createBitcoinLikeAccount(wallet, 0, P2PKH_MEDIUM_XPUB_INFO);
    auto addresses = uv::wait(account->getFreshPublicAddresses());
    EXPECT_EQ(addresses.size(), 20);
    EXPECT_EQ(addresses.front()->toString(), "1DDBzjLyAmDr4qLRC2T2WJ831cxBM5v7G7");
}

TEST_F(AccountsPublicInterfaceTest, GetBalanceOnEmptyAccount) {
    auto account = createBitcoinLikeAccount(wallet, 0, P2PKH_MEDIUM_XPUB_INFO);
    auto balance = uv::wait(account->getBalance());
    EXPECT_EQ(balance->toMagnitude(0)->toLong(), 0);
}

TEST_F(AccountsPublicInterfaceTest, GetBalanceOnAccountWithSomeTxs) {
    auto account = ledger::testing::medium_xpub::inflate(pool, wallet);
    auto balance = uv::wait(account->getBalance());
    auto utxos = uv::wait(account->getUTXO());
    auto uxtoCount = uv::wait(account->getUTXOCount());
    EXPECT_EQ(balance->toLong(), 143590816L);
    EXPECT_EQ(utxos.size(), 8);
    EXPECT_EQ(uxtoCount, 8);
}

TEST_F(AccountsPublicInterfaceTest, GetBalanceHistoryOnAccountWithSomeTxs) {
    auto account = ledger::testing::medium_xpub::inflate(pool, wallet);
    auto fromDate = "2017-10-12T13:38:23Z";
    auto toDate = DateUtils::toJSON(DateUtils::now());
    auto balanceHistory = uv::wait(account->getBalanceHistory(fromDate, toDate, api::TimePeriod::MONTH));
    auto balance = uv::wait(account->getBalance());
    EXPECT_EQ(balanceHistory[balanceHistory.size() - 1]->toLong(), balance->toLong());
}

TEST_F(AccountsPublicInterfaceTest, QueryOperations) {
    auto account = ledger::testing::medium_xpub::inflate(pool, wallet);
    auto query = std::dynamic_pointer_cast<ledger::core::OperationQuery>(account->queryOperations()->limit(100)->partial());
    auto operations = uv::wait(query->execute());
    EXPECT_EQ(operations.size(), 100);
}

TEST_F(AccountsPublicInterfaceTest, QueryOperationsOnEmptyAccount) {
    auto account = createBitcoinLikeAccount(wallet, 0, P2PKH_MEDIUM_XPUB_INFO);
    auto query = std::dynamic_pointer_cast<ledger::core::OperationQuery>(account->queryOperations()->limit(100)->partial());
    auto operations = uv::wait(query->execute());
    EXPECT_EQ(operations.size(), 0);
}

TEST_F(AccountsPublicInterfaceTest, GetTestnetUnits) {
    auto configuration = DynamicObject::newInstance();
    configuration->putString(api::Configuration::KEYCHAIN_ENGINE,api::KeychainEngines::BIP49_P2SH);
    wallet = uv::wait(pool->createWallet("my_wallet_testnet", "bitcoin_testnet", configuration));
    auto currency = pool->getCurrency("bitcoin");
    EXPECT_EQ(currency->name, "bitcoin");
    cout<<">>>> Get account"<<endl;
    auto account = ledger::testing::testnet_xpub::inflate(pool, wallet);
    cout<<">>>> Get balance"<<endl;
    auto balance = uv::wait(account->getBalance());
    cout<<">>>> Get balance toLong"<<endl;
    auto balanceLong = balance->toLong();
    //EXPECT_EQ(balanceLong, 100L);
    //auto balance = uv::wait(account->getBalance());
    //auto balance =
    //EXPECT_EQ();
}


<|start_filename|>core/src/api/Account.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from wallet.djinni

#ifndef DJINNI_GENERATED_ACCOUNT_HPP
#define DJINNI_GENERATED_ACCOUNT_HPP

#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#ifndef LIBCORE_EXPORT
    #if defined(_MSC_VER)
       #include <libcore_export.h>
    #else
       #define LIBCORE_EXPORT
    #endif
#endif

namespace ledger { namespace core { namespace api {

class AddressListCallback;
class AlgorandAccount;
class AmountCallback;
class AmountListCallback;
class BitcoinLikeAccount;
class BlockCallback;
class CosmosLikeAccount;
class ErrorCodeCallback;
class EthereumLikeAccount;
class EventBus;
class Keychain;
class Logger;
class OperationQuery;
class Preferences;
class RippleLikeAccount;
class StellarLikeAccount;
class TezosLikeAccount;
enum class TimePeriod;
enum class WalletType;

/** Class representing an account. */
class LIBCORE_EXPORT Account {
public:
    virtual ~Account() {}

    /**
     * Key of the synchronization duration time in the synchronize event payload.
     * The value is stored in a int64 time expressed in miliseconds.
     */
    static std::string const EV_SYNC_DURATION_MS;

    /**
     * Indicates the last block height fetch during synchronization
     * The value is stored as a int32
     */
    static std::string const EV_SYNC_LAST_BLOCK_HEIGHT;

    /**
     * Indicates the number of new operations during the synchronization
     * The value is stored as a int32
     */
    static std::string const EV_SYNC_NEW_OPERATIONS;

    /**
     * Indicates the first valid block height before the reorganization during the synchronization
     * The value is stored as a int32
     */
    static std::string const EV_SYNC_REORG_BLOCK_HEIGHT;

    /** Key of the synchronization error code. The code is a stringified version of the value in the ErrorCode enum. */
    static std::string const EV_SYNC_ERROR_CODE;

    static std::string const EV_SYNC_ERROR_CODE_INT;

    /** Key of the synchronization error message. The message is stored as a string. */
    static std::string const EV_SYNC_ERROR_MESSAGE;

    /**TODO */
    static std::string const EV_NEW_BLOCK_CURRENCY_NAME;

    static std::string const EV_NEW_BLOCK_HASH;

    static std::string const EV_NEW_BLOCK_HEIGHT;

    /**TODO */
    static std::string const EV_NEW_OP_WALLET_NAME;

    static std::string const EV_NEW_OP_ACCOUNT_INDEX;

    static std::string const EV_NEW_OP_UID;

    static std::string const EV_DELETED_OP_UID;

    /**
     * Returned flag when a transaction is put in DB
     * Some of those are unrelevant for specific coins
     * but for the sake of generic mechanism we put all those
     * flags together
     */
    static int32_t const FLAG_TRANSACTION_IGNORED;

    static int32_t const FLAG_NEW_TRANSACTION;

    static int32_t const FLAG_TRANSACTION_UPDATED;

    static int32_t const FLAG_TRANSACTION_ON_PREVIOUSLY_EMPTY_ADDRESS;

    static int32_t const FLAG_TRANSACTION_ON_USED_ADDRESS;

    static int32_t const FLAG_TRANSACTION_CREATED_SENDING_OPERATION;

    static int32_t const FLAG_TRANSACTION_CREATED_RECEPTION_OPERATION;

    static int32_t const FLAG_TRANSACTION_CREATED_EXTERNAL_OPERATION;

    /**
     * Get index of account in user's wallet
     * 32-bit integer
     */
    virtual int32_t getIndex() = 0;

    /** Get the list of all operations. */
    virtual std::shared_ptr<OperationQuery> queryOperations() = 0;

    /**
     * Get balance of account.
     * @param callback, if getBalacne, Callback returning an Amount object which represents account's balance
     */
    virtual void getBalance(const std::shared_ptr<AmountCallback> & callback) = 0;

    /**
     * Get balance of account at a precise interval with a certain granularity.
     * @param start, lower bound of search range
     * @param end, upper bound of search range
     * @param precision, granularity at which we want results
     * @param callback, ListCallback returning a list of Amount object which represents account's balance
     */
    virtual void getBalanceHistory(const std::string & start, const std::string & end, TimePeriod period, const std::shared_ptr<AmountListCallback> & callback) = 0;

    /**
     * Get synchronization status of account.
     * @return bool
     */
    virtual bool isSynchronizing() = 0;

    /**
     * Start synchronization of account.
     * @return EventBus, handler will be notified of synchronization outcome
     */
    virtual std::shared_ptr<EventBus> synchronize() = 0;

    /**
     * Return account's preferences.
     * @return Preferences object
     */
    virtual std::shared_ptr<Preferences> getPreferences() = 0;

    /**
     * Return account's logger which provides all needed (e.g. database) logs.
     * @return Logger Object
     */
    virtual std::shared_ptr<Logger> getLogger() = 0;

    /**
     * Return operation for a specific operation.
     * @param uid, string of operation id
     */
    virtual std::shared_ptr<Preferences> getOperationPreferences(const std::string & uid) = 0;

    /**
     * Turn the account into an Bitcoin one, allowing operations to be performed on the Bitcoin
     * network.
     */
    virtual std::shared_ptr<BitcoinLikeAccount> asBitcoinLikeAccount() = 0;

    /**
     * Turn the account into an Cosmos one, allowing operations to be performed on the Cosmos
     * network.
     */
    virtual std::shared_ptr<CosmosLikeAccount> asCosmosLikeAccount() = 0;

    /**
     * Turn the account into an Ethereum one, allowing operations to be performrd on the Ethereum
     * network.
     */
    virtual std::shared_ptr<EthereumLikeAccount> asEthereumLikeAccount() = 0;

    /** Turn the account into a Ripple one, allowing operations to be performed on the Ripple network. */
    virtual std::shared_ptr<RippleLikeAccount> asRippleLikeAccount() = 0;

    /** Turn the account into a Tezos one, allowing operations to be performed on the Tezos network. */
    virtual std::shared_ptr<TezosLikeAccount> asTezosLikeAccount() = 0;

    /** Turn the account into an Algorand one, allowing operations to be performed on the Algorand network. */
    virtual std::shared_ptr<AlgorandAccount> asAlgorandAccount() = 0;

    /**
     * Turn the account into a Stellar one, allowing operations to be performerd on the Stellar
     * network.
     */
    virtual std::shared_ptr<StellarLikeAccount> asStellarLikeAccount() = 0;

    /**
     * Check if account is a Bitcoin one.
     * @return bool
     */
    virtual bool isInstanceOfBitcoinLikeAccount() = 0;

    /**
     * Check if account is a Cosmos one.
     * @return bool
     */
    virtual bool isInstanceOfCosmosLikeAccount() = 0;

    /**
     * Check if account is an Ethereum one.
     * @return bool
     */
    virtual bool isInstanceOfEthereumLikeAccount() = 0;

    /**
     * Check if account is a Stellar one.
     * @return bool
     */
    virtual bool isInstanceOfStellarLikeAccount() const = 0;

    /**
     * Check if account is a Ripple one.
     * @return bool
     */
    virtual bool isInstanceOfRippleLikeAccount() = 0;

    /**TODO */
    virtual void getFreshPublicAddresses(const std::shared_ptr<AddressListCallback> & callback) = 0;

    /**
     * Get type of wallet to which account belongs.
     * @return WalletType object
     */
    virtual WalletType getWalletType() = 0;

    /**
     * Get event bus through which account is notified on synchronization status.
     * @return EventBus object
     */
    virtual std::shared_ptr<EventBus> getEventBus() = 0;

    /**
     * Get Last block of blockchain on which account operates.
     * @param callback, Callback returning, if getLastBlock succeeds, a Block object
     */
    virtual void getLastBlock(const std::shared_ptr<BlockCallback> & callback) = 0;

    /** Get the key used to generate the account. */
    virtual std::string getRestoreKey() = 0;

    /**
     * Erase data (in user's DB) relative to wallet since given date.
     * @param date, start date of data deletion
     */
    virtual void eraseDataSince(const std::chrono::system_clock::time_point & date, const std::shared_ptr<ErrorCodeCallback> & callback) = 0;

    /** Access to underlying keychain. */
    virtual std::shared_ptr<Keychain> getAccountKeychain() = 0;
};

} } }  // namespace ledger::core::api
#endif //DJINNI_GENERATED_ACCOUNT_HPP


<|start_filename|>core/src/jni/jni/BitcoinLikeNetworkParameters.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from addresses.djinni

#include "BitcoinLikeNetworkParameters.hpp"  // my header
#include "BitcoinLikeDustPolicy.hpp"
#include "BitcoinLikeFeePolicy.hpp"
#include "Marshal.hpp"

namespace djinni_generated {

BitcoinLikeNetworkParameters::BitcoinLikeNetworkParameters() = default;

BitcoinLikeNetworkParameters::~BitcoinLikeNetworkParameters() = default;

auto BitcoinLikeNetworkParameters::fromCpp(JNIEnv* jniEnv, const CppType& c) -> ::djinni::LocalRef<JniType> {
    const auto& data = ::djinni::JniClass<BitcoinLikeNetworkParameters>::get();
    auto r = ::djinni::LocalRef<JniType>{jniEnv->NewObject(data.clazz.get(), data.jconstructor,
                                                           ::djinni::get(::djinni::String::fromCpp(jniEnv, c.Identifier)),
                                                           ::djinni::get(::djinni::Binary::fromCpp(jniEnv, c.P2PKHVersion)),
                                                           ::djinni::get(::djinni::Binary::fromCpp(jniEnv, c.P2SHVersion)),
                                                           ::djinni::get(::djinni::Binary::fromCpp(jniEnv, c.XPUBVersion)),
                                                           ::djinni::get(::djinni_generated::BitcoinLikeFeePolicy::fromCpp(jniEnv, c.FeePolicy)),
                                                           ::djinni::get(::djinni::I64::fromCpp(jniEnv, c.Dust)),
                                                           ::djinni::get(::djinni_generated::BitcoinLikeDustPolicy::fromCpp(jniEnv, c.DustPolicy)),
                                                           ::djinni::get(::djinni::String::fromCpp(jniEnv, c.MessagePrefix)),
                                                           ::djinni::get(::djinni::Bool::fromCpp(jniEnv, c.UsesTimestampedTransaction)),
                                                           ::djinni::get(::djinni::I64::fromCpp(jniEnv, c.TimestampDelay)),
                                                           ::djinni::get(::djinni::Binary::fromCpp(jniEnv, c.SigHash)),
                                                           ::djinni::get(::djinni::List<::djinni::String>::fromCpp(jniEnv, c.AdditionalBIPs)))};
    ::djinni::jniExceptionCheck(jniEnv);
    return r;
}

auto BitcoinLikeNetworkParameters::toCpp(JNIEnv* jniEnv, JniType j) -> CppType {
    ::djinni::JniLocalScope jscope(jniEnv, 13);
    assert(j != nullptr);
    const auto& data = ::djinni::JniClass<BitcoinLikeNetworkParameters>::get();
    return {::djinni::String::toCpp(jniEnv, (jstring)jniEnv->GetObjectField(j, data.field_Identifier)),
            ::djinni::Binary::toCpp(jniEnv, (jbyteArray)jniEnv->GetObjectField(j, data.field_P2PKHVersion)),
            ::djinni::Binary::toCpp(jniEnv, (jbyteArray)jniEnv->GetObjectField(j, data.field_P2SHVersion)),
            ::djinni::Binary::toCpp(jniEnv, (jbyteArray)jniEnv->GetObjectField(j, data.field_XPUBVersion)),
            ::djinni_generated::BitcoinLikeFeePolicy::toCpp(jniEnv, jniEnv->GetObjectField(j, data.field_FeePolicy)),
            ::djinni::I64::toCpp(jniEnv, jniEnv->GetLongField(j, data.field_Dust)),
            ::djinni_generated::BitcoinLikeDustPolicy::toCpp(jniEnv, jniEnv->GetObjectField(j, data.field_DustPolicy)),
            ::djinni::String::toCpp(jniEnv, (jstring)jniEnv->GetObjectField(j, data.field_MessagePrefix)),
            ::djinni::Bool::toCpp(jniEnv, jniEnv->GetBooleanField(j, data.field_UsesTimestampedTransaction)),
            ::djinni::I64::toCpp(jniEnv, jniEnv->GetLongField(j, data.field_TimestampDelay)),
            ::djinni::Binary::toCpp(jniEnv, (jbyteArray)jniEnv->GetObjectField(j, data.field_SigHash)),
            ::djinni::List<::djinni::String>::toCpp(jniEnv, jniEnv->GetObjectField(j, data.field_AdditionalBIPs))};
}

}  // namespace djinni_generated


<|start_filename|>core/src/utils/DateUtils.cpp<|end_filename|>
/*
 *
 * DateParser
 * ledger-core
 *
 * Created by <NAME> on 11/04/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "DateUtils.hpp"
#include <regex>
#include "Exception.hpp"
#include <boost/lexical_cast.hpp>
#include <ctime>
#include <unordered_map>

namespace {
    const std::unordered_map<std::string, std::string> MONTHS = {
            {"Jan", "01"},
            {"Feb", "02"},
            {"Mar", "03"},
            {"Apr", "04"},
            {"May", "05"},
            {"Jun", "06"},
            {"Jul", "07"},
            {"Aug", "08"},
            {"Sep", "09"},
            {"Oct", "10"},
            {"Nov", "11"},
            {"Dec", "12"},
    };

    const std::regex PARSE_JSON_DATE_REGEX("([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+)[\\.0-9]*([Z]?)");
    const std::regex FORMAT_JSON_DATE_REGEX("([0-9]+)-([a-zA-Z]+)-([0-9]+) ([0-9]+):([0-9]+):([0-9]+)[\\.0-9]*([Z]?)");
}

#if defined(_WIN32) || defined(_WIN64)
    #if defined(_MSC_VER)
        time_t timegm(struct tm* tm) { return _mkgmtime(tm); }
    #else
        time_t timegm(struct tm* tm) { return mktime(tm) - timezone; }
    #endif
#endif

namespace ledger {
    namespace core {
        std::chrono::system_clock::time_point ledger::core::DateUtils::fromJSON(const std::string &str) {
            std::cmatch what;

            if (regex_match(str.c_str(), what, PARSE_JSON_DATE_REGEX)) {
                auto year = boost::lexical_cast<unsigned int>(std::string(what[1].first, what[1].second));
                auto month = boost::lexical_cast<unsigned int>(std::string(what[2].first, what[2].second));
                auto day = boost::lexical_cast<unsigned int>(std::string(what[3].first, what[3].second));
                auto hours = boost::lexical_cast<unsigned int>(std::string(what[4].first, what[4].second));
                auto minutes = boost::lexical_cast<unsigned int>(std::string(what[5].first, what[5].second));
                auto seconds = boost::lexical_cast<unsigned int>(std::string(what[6].first, what[6].second));
                auto localTime = std::string(what[7].first, what[7].second);

                if (localTime != "Z") {
                    throw make_exception(api::ErrorCode::INVALID_DATE_FORMAT, "Cannot create date from local date {}", str);
                }

                struct tm time;
                time.tm_year = year - 1900;
                time.tm_mon = month - 1;
                time.tm_hour = hours;
                time.tm_mday = day;
                time.tm_hour = hours;
                time.tm_min = minutes;
                time.tm_sec = seconds;

                return std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::from_time_t(timegm(&time)));
            } else {
                try {
                    auto formattedDate = formatDateFromJSON(str);
                    return fromJSON(formattedDate);
                } catch (...) {
                    throw make_exception(api::ErrorCode::INVALID_DATE_FORMAT, "Cannot convert {} to date", str);
                }
            }
        }
    }

    std::string ledger::core::DateUtils::formatDateFromJSON(const std::string &str) {

        std::cmatch what;

        if (regex_match(str.c_str(), what, FORMAT_JSON_DATE_REGEX)) {
            auto year = boost::lexical_cast<unsigned int>(std::string(what[1].first, what[1].second));
            auto month = boost::lexical_cast<std::string>(std::string(what[2].first, what[2].second));
            auto day = boost::lexical_cast<unsigned int>(std::string(what[3].first, what[3].second));
            auto hours = boost::lexical_cast<unsigned int>(std::string(what[4].first, what[4].second));
            auto minutes = boost::lexical_cast<unsigned int>(std::string(what[5].first, what[5].second));
            auto seconds = boost::lexical_cast<unsigned int>(std::string(what[6].first, what[6].second));
            auto numMonth = MONTHS.at(month);

            if (numMonth.empty()) {
                throw make_exception(api::ErrorCode::INVALID_DATE_FORMAT, "Failed to get month {} from date {}", month, str);
            }

            return fmt::format("{}-{}-{}T{}:{}:{}Z", year, numMonth, day, hours, minutes, seconds);
        } else {
            throw make_exception(api::ErrorCode::INVALID_DATE_FORMAT, "Cannot format {} to date YYYY-MM-DDTHH:MM:SSZ", str);
        }
    }

    std::string ledger::core::DateUtils::toJSON(const std::chrono::system_clock::time_point &date) {
        std::tm tm = {0};
        std::time_t tt = std::chrono::system_clock::to_time_t(date);

#if defined(_WIN32) || defined(_WIN64)
        gmtime_s(&tm, &tt);
#else
        gmtime_r(&tt, &tm);
#endif

        return fmt::format("{:04}-{:02}-{:02}T{:02}:{:02}:{:02}Z", 1900 + tm.tm_year, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
    }

    std::chrono::system_clock::time_point ledger::core::DateUtils::now() {
        return std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::now());
    }

    std::chrono::system_clock::time_point ledger::core::DateUtils::incrementDate(const std::chrono::system_clock::time_point &date,
        api::TimePeriod precision
    ) {
        std::tm tm = {0};
        std::time_t tt = std::chrono::system_clock::to_time_t(date);
#if defined(_WIN32) || defined(_WIN64)
        gmtime_s(&tm, &tt);
#else
        gmtime_r(&tt, &tm);
#endif

        switch (precision) {
            case api::TimePeriod::HOUR:
                tm.tm_hour += 1;
                break;

            case api::TimePeriod::DAY:
                tm.tm_mday += 1;
                break;

            case api::TimePeriod::WEEK:
                tm.tm_mday += 7;
                break;

            case api::TimePeriod::MONTH:
                tm.tm_mon += 1;
                break;

            default:
                break;
        }

        return std::chrono::time_point_cast<std::chrono::seconds>(std::chrono::system_clock::from_time_t(timegm(&tm)));
    }
}


<|start_filename|>core/test/lib/libledger-test/ProxyHttpClient.cpp<|end_filename|>
#include "ProxyHttpClient.hpp"
#include "api/HttpRequest.hpp"
#include <memory>
#include "api/Error.hpp"

namespace ledger {
    namespace core {
        namespace test {
            ProxyHttpClient::ProxyHttpClient(std::shared_ptr<api::HttpClient> httpClient) 
                : _httpClient(httpClient)
            {}

            void ProxyHttpClient::execute(const std::shared_ptr<api::HttpRequest>& request) {
                auto it = _cache.find(request->getUrl());
                if (it != _cache.end()) {
                    std::cout << "get response from cache : " << request->getUrl() << std::endl;
                    request->complete(it->second, std::experimental::nullopt);
                    return;
                }
                _httpClient->execute(request);
            }

            void ProxyHttpClient::addCache(const std::string& url, const std::string& body) {
                _cache.emplace(url, FakeUrlConnection::fromString(body));
            }
        }
    }
}

<|start_filename|>core/src/jni/jni/DurationMetrics.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#include "DurationMetrics.hpp"  // my header
#include "DurationMetric.hpp"
#include "Marshal.hpp"

namespace djinni_generated {

DurationMetrics::DurationMetrics() : ::djinni::JniInterface<::ledger::core::api::DurationMetrics, DurationMetrics>("co/ledger/core/DurationMetrics$CppProxy") {}

DurationMetrics::~DurationMetrics() = default;


CJNIEXPORT void JNICALL Java_co_ledger_core_DurationMetrics_00024CppProxy_nativeDestroy(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        delete reinterpret_cast<::djinni::CppProxyHandle<::ledger::core::api::DurationMetrics>*>(nativeRef);
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, )
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_DurationMetrics_getAllDurationMetrics(JNIEnv* jniEnv, jobject /*this*/)
{
    try {
        DJINNI_FUNCTION_PROLOGUE0(jniEnv);
        auto r = ::ledger::core::api::DurationMetrics::getAllDurationMetrics();
        return ::djinni::release(::djinni::Map<::djinni::String, ::djinni_generated::DurationMetric>::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

}  // namespace djinni_generated


<|start_filename|>core/src/api/ERC20LikeAccount.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from erc20.djinni

#ifndef DJINNI_GENERATED_ERC20LIKEACCOUNT_HPP
#define DJINNI_GENERATED_ERC20LIKEACCOUNT_HPP

#include <chrono>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#ifndef LIBCORE_EXPORT
    #if defined(_MSC_VER)
       #include <libcore_export.h>
    #else
       #define LIBCORE_EXPORT
    #endif
#endif

namespace ledger { namespace core { namespace api {

class BigInt;
class BigIntCallback;
class BinaryCallback;
class ERC20LikeOperation;
class ERC20LikeOperationCallback;
class ERC20LikeOperationListCallback;
class OperationQuery;
enum class TimePeriod;
struct ERC20Token;

/** ERC20-like accounts class. */
class LIBCORE_EXPORT ERC20LikeAccount {
public:
    virtual ~ERC20LikeAccount() {}

    /**
     * Key of the ERC20LikeAccount UID in the new erc20 operation event payload.
     * The value is stored in a string.
     */
    static std::string const EV_NEW_OP_ERC20_ACCOUNT_UID;

    /** Get uid */
    virtual std::string getUid() = 0;

    /** Get an ERC20 token. */
    virtual ERC20Token getToken() = 0;

    /** Get the address of this ERC20 account. */
    virtual std::string getAddress() = 0;

    /** Get the current balance of this ERC20 account. */
    virtual void getBalance(const std::shared_ptr<BigIntCallback> & callback) = 0;

    /**
     * Get the balance history of this ERC20 account from a starting date (included) to an ending
     * date (included).
     */
    virtual std::vector<std::shared_ptr<BigInt>> getBalanceHistoryFor(const std::chrono::system_clock::time_point & start, const std::chrono::system_clock::time_point & end, TimePeriod period) = 0;

    /** Get the list of operations performed on this ERC20 account. */
    virtual std::vector<std::shared_ptr<ERC20LikeOperation>> getOperations() = 0;

    /** Get ERC20 operation by uid */
    virtual void getOperation(const std::string & uid, const std::shared_ptr<ERC20LikeOperationCallback> & callback) = 0;

    /** Get all ERC20 operations */
    virtual void getAllOperations(int32_t from, int32_t to, bool ascending, const std::shared_ptr<ERC20LikeOperationListCallback> & callback) = 0;

    /** Get ERC20 operations from a given block height (included), it also returns mempool operations */
    virtual void getOperationsFromBlockHeight(int32_t from, int32_t to, int64_t fromBlockHeight, const std::shared_ptr<ERC20LikeOperationListCallback> & callback) = 0;

    /** Retrieve raw data concerning a transaction of a given amount to a given address. */
    virtual void getTransferToAddressData(const std::shared_ptr<BigInt> & amount, const std::string & address, const std::shared_ptr<BinaryCallback> & data) = 0;

    virtual std::shared_ptr<OperationQuery> queryOperations() = 0;
};

} } }  // namespace ledger::core::api
#endif //DJINNI_GENERATED_ERC20LIKEACCOUNT_HPP


<|start_filename|>core/src/api/DurationMetric.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#ifndef DJINNI_GENERATED_DURATIONMETRIC_HPP
#define DJINNI_GENERATED_DURATIONMETRIC_HPP

#include <cstdint>
#include <iostream>
#include <utility>

namespace ledger { namespace core { namespace api {

struct DurationMetric final {
    /** Total number of milliseconds */
    int64_t total_ms;
    /** Number of records. */
    int32_t count;

    DurationMetric(int64_t total_ms_,
                   int32_t count_)
    : total_ms(std::move(total_ms_))
    , count(std::move(count_))
    {}

    DurationMetric(const DurationMetric& cpy) {
       this->total_ms = cpy.total_ms;
       this->count = cpy.count;
    }

    DurationMetric() = default;


    DurationMetric& operator=(const DurationMetric& cpy) {
       this->total_ms = cpy.total_ms;
       this->count = cpy.count;
       return *this;
    }

    template <class Archive>
    void load(Archive& archive) {
        archive(total_ms, count);
    }

    template <class Archive>
    void save(Archive& archive) const {
        archive(total_ms, count);
    }
};

} } }  // namespace ledger::core::api
#endif //DJINNI_GENERATED_DURATIONMETRIC_HPP


<|start_filename|>core/test/async/threading_tests.cpp<|end_filename|>
/*
 *
 * threading_tests
 * ledger-core
 *
 * Created by <NAME> on 20/04/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */


#ifdef _WIN32
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
#else
    #include <pthread.h>
#endif
#include <gtest/gtest.h>
#include <UvThreadDispatcher.hpp>
#include <chrono>

using namespace ledger::core;
using namespace std::chrono;

unsigned long getCurrentThreadId(){
#ifdef _WIN32
    return (unsigned long)GetCurrentThreadId();
#else
    return (unsigned long)pthread_self();
#endif
}

TEST(Threading, DoSomethingOnSerialQueue) {
    auto dispatcher = std::make_shared<uv::UvThreadDispatcher>();
    int var = 0;

    auto mainThread = getCurrentThreadId();
    dispatcher->getThreadPoolExecutionContext("worker")->execute(make_runnable([&] () {
        EXPECT_NE(mainThread, getCurrentThreadId());
        var = 1;
        dispatcher->stop();
    }));

    dispatcher->waitUntilStopped();
    EXPECT_EQ(var, 1);
    EXPECT_EQ(dispatcher->getThreadPoolExecutionContext("worker"), dispatcher->getThreadPoolExecutionContext("worker"));   
}

TEST(Threading, DoSomethingOnSerialQueueWithDelay) {
    auto dispatcher = std::make_shared<uv::UvThreadDispatcher>();

    uint64_t before = duration_cast< milliseconds >(system_clock::now().time_since_epoch()).count();
    uint64_t after = before;

    auto mainThread = getCurrentThreadId();

    dispatcher->getSerialExecutionContext("worker")->delay(make_runnable([&] () {
        EXPECT_NE(mainThread, getCurrentThreadId());
        after = duration_cast< milliseconds >(system_clock::now().time_since_epoch()).count();
        dispatcher->stop();
    }), 1000);

    dispatcher->waitUntilStopped();

    EXPECT_TRUE(after >= (before + 1000));
    EXPECT_EQ(dispatcher->getSerialExecutionContext("worker"), dispatcher->getSerialExecutionContext("worker"));
}

TEST(Threading, DoSomethingOnThreadPoolSerialQueue) {
    auto dispatcher = std::make_shared<uv::UvThreadDispatcher>(2);
    auto cpt = 0;
    std::mutex mutex;
    std::condition_variable condition;

    auto mainThread = getCurrentThreadId();
    unsigned long threadId1, threadId2, threadId3;

    auto getThreadId = [&] (auto context, unsigned long & threadId) {
        context->execute(make_runnable([&] () {
        std::unique_lock<std::mutex> lock(mutex);
        threadId = getCurrentThreadId();
        ++ cpt;
        condition.notify_all();
    }));
    };

    getThreadId(dispatcher->getThreadPoolExecutionContext("worker-1"), threadId1);
    getThreadId(dispatcher->getThreadPoolExecutionContext("worker-2"), threadId2);
    getThreadId(dispatcher->getThreadPoolExecutionContext("worker-3"), threadId3);

    std::unique_lock<std::mutex> lock(mutex);
    condition.wait(lock, [&] () {
        return (cpt == 3);
    });
    
    EXPECT_NE(mainThread, threadId1);
    EXPECT_NE(mainThread, threadId2);
    EXPECT_NE(mainThread, threadId3);
    EXPECT_NE(threadId1, threadId2);
}


<|start_filename|>core/test/lib/libledger-test/ProxyHttpClient.hpp<|end_filename|>
#pragma once
#include "api/HttpClient.hpp"
#include <unordered_map>
#include "FakeUrlConnection.hpp"

namespace ledger {
    namespace core {
        namespace test {

            class ProxyHttpClient : public api::HttpClient {
            public:
                ProxyHttpClient(std::shared_ptr<api::HttpClient> httpClient);
                void execute(const std::shared_ptr<api::HttpRequest>& request) override;
                void addCache(const std::string& url, const std::string& body);
            private:
                std::unordered_map<std::string, std::shared_ptr<FakeUrlConnection>> _cache;
                std::shared_ptr<api::HttpClient> _httpClient;
            };

        }
    }
}


<|start_filename|>core/src/api/BitcoinLikeDustPolicy.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from addresses.djinni

#include "BitcoinLikeDustPolicy.hpp"  // my header
#include "enum_from_string.hpp"

namespace ledger { namespace core { namespace api {

std::string to_string(const BitcoinLikeDustPolicy& bitcoinLikeDustPolicy) {
    switch (bitcoinLikeDustPolicy) {
        case BitcoinLikeDustPolicy::FIXED: return "FIXED";
        case BitcoinLikeDustPolicy::PER_BYTE: return "PER_BYTE";
        case BitcoinLikeDustPolicy::PER_KBYTE: return "PER_KBYTE";
    };
};
template <>
BitcoinLikeDustPolicy from_string(const std::string& bitcoinLikeDustPolicy) {
    if (bitcoinLikeDustPolicy == "FIXED") return BitcoinLikeDustPolicy::FIXED;
    else if (bitcoinLikeDustPolicy == "PER_BYTE") return BitcoinLikeDustPolicy::PER_BYTE;
    else return BitcoinLikeDustPolicy::PER_KBYTE;
};

std::ostream &operator<<(std::ostream &os, const BitcoinLikeDustPolicy &o)
{
    switch (o) {
        case BitcoinLikeDustPolicy::FIXED:  return os << "FIXED";
        case BitcoinLikeDustPolicy::PER_BYTE:  return os << "PER_BYTE";
        case BitcoinLikeDustPolicy::PER_KBYTE:  return os << "PER_KBYTE";
    }
}

} } }  // namespace ledger::core::api


<|start_filename|>core/src/jni/jni/AllocationMetrics.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#include "AllocationMetrics.hpp"  // my header
#include "Marshal.hpp"

namespace djinni_generated {

AllocationMetrics::AllocationMetrics() : ::djinni::JniInterface<::ledger::core::api::AllocationMetrics, AllocationMetrics>("co/ledger/core/AllocationMetrics$CppProxy") {}

AllocationMetrics::~AllocationMetrics() = default;


CJNIEXPORT void JNICALL Java_co_ledger_core_AllocationMetrics_00024CppProxy_nativeDestroy(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        delete reinterpret_cast<::djinni::CppProxyHandle<::ledger::core::api::AllocationMetrics>*>(nativeRef);
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, )
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_AllocationMetrics_getObjectAllocations(JNIEnv* jniEnv, jobject /*this*/)
{
    try {
        DJINNI_FUNCTION_PROLOGUE0(jniEnv);
        auto r = ::ledger::core::api::AllocationMetrics::getObjectAllocations();
        return ::djinni::release(::djinni::Map<::djinni::String, ::djinni::I32>::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

}  // namespace djinni_generated


<|start_filename|>core/test/cosmos/AccountTests.cpp<|end_filename|>
/*
 *
 * AccountTests
 * ledger-core-cosmos
 *
 * Created by <NAME> on 2020/02/12.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 *
 */

#include <gtest/gtest.h>

#include "../integration/BaseFixture.h"
#include <wallet/cosmos/CosmosLikeWallet.hpp>
#include <wallet/cosmos/CosmosLikeCurrencies.hpp>
#include <wallet/cosmos/factories/CosmosLikeWalletFactory.hpp>

struct CosmosAccounts : public BaseFixture {

};

TEST_F(CosmosAccounts, DISABLED_FirstATOMAccountInfo) {
    auto const currency = currencies::ATOM;
    auto pool = newDefaultPool();
    backend->enableQueryLogging(true);

    auto wallet = uv::wait(pool->createWallet("my_wallet", currency.name, api::DynamicObject::newInstance()));
    auto info = uv::wait(wallet->getNextAccountCreationInfo());

    EXPECT_EQ(info.index, 0);
    EXPECT_EQ(info.owners.size(), 1);
    EXPECT_EQ(info.derivations.size(), 1);
    EXPECT_EQ(info.owners[0], "main");
    EXPECT_EQ(info.derivations[0], "44'/118'/0'/0/0");
}


<|start_filename|>core/src/async/Deffered.hpp<|end_filename|>
/*
 *
 * Deffered
 * ledger-core
 *
 * Created by <NAME> on 20/01/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#ifndef LEDGER_CORE_DEFFERED_HPP
#define LEDGER_CORE_DEFFERED_HPP

#include <memory>
#include <functional>
#include "../utils/Option.hpp"
#include "../utils/Try.hpp"
#include <mutex>
#include "../utils/Exception.hpp"
#include <queue>
#include "../api/ExecutionContext.hpp"
#include <tuple>
#include "../utils/LambdaRunnable.hpp"

namespace ledger {
    namespace core {

        template <typename T>
        class Future;

        template <typename T>
        class Promise;

        template <typename T>
        class Deffered {

        public:
            using Callback = std::function<void (const Try<T>&)>;

            friend class Future<T>;
            friend class Promise<T>;
            Deffered() {

            };
            Deffered(const Deffered&) = delete;
            Deffered(Deffered&&) = delete;

            void setResult(const Try<T>& result) {
                {
                    std::lock_guard<std::recursive_mutex> lock(_lock);
                    ensureNotCompleted();
                    _value = result;
                }
                trigger();
            }

            void setValue(const T& value) {
                std::lock_guard<std::recursive_mutex> lock(_lock);
                ensureNotCompleted();
                _value = Try<T>(value);
                _trigger();
            };

            void setError(const Exception& exception) {
                std::lock_guard<std::recursive_mutex> lock(_lock);
                ensureNotCompleted();
                Try<T> ex;
                ex.fail(exception);
                _value = ex;
                _trigger();
            }

            void addCallback(Callback callback, std::shared_ptr<api::ExecutionContext> context) {
                // Add to the queue
                std::lock_guard<std::recursive_mutex> lock(_lock);
                _callbacks.push(std::make_tuple(callback, context));
                _trigger();
            }

            Option<Try<T>> getValue() const {
                Option<Try<T>> cpy = _value;
                return cpy;
            }

            bool hasValue() const {
                std::lock_guard<std::recursive_mutex> lock(_lock);
                return _value.hasValue();
            }

            void trigger() {
                std::lock_guard<std::recursive_mutex> lock(_lock);
                _trigger();
            }

        private:
            inline void ensureNotCompleted() {
                if (_value.hasValue())
                    throw Exception(api::ErrorCode::ALREADY_COMPLETED, "This promise is already completed");
            };

            inline void _trigger() {
                if (_value.isEmpty() || _callbacks.empty()) {
                    return ;
                }
                while (!_callbacks.empty()) {
                    std::tuple<Callback, std::shared_ptr<api::ExecutionContext>> callback = _callbacks.front();
                    Callback cb = std::get<0>(callback);
                    auto value = _value.getValue();
                    std::get<1>(callback)->execute(make_runnable([cb, value] () {
                        cb(value);
                    }));
                    _callbacks.pop();
                }
            }

        private:
            mutable std::recursive_mutex _lock;
            Option<Try<T>> _value;
            std::queue<std::tuple<Callback, std::shared_ptr<api::ExecutionContext>>> _callbacks;
        };



    }
}


#endif //LEDGER_CORE_DEFFERED_HPP


<|start_filename|>core/src/wallet/common/Amount.cpp<|end_filename|>
/*
 *
 * Amount
 * ledger-core
 *
 * Created by <NAME> on 30/06/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "Amount.h"
#include <utils/Exception.hpp>

namespace ledger {
    namespace core {

        Amount::Amount(const api::Currency &currency, int32_t unitIndex, BigInt &&value) : ManagedObject<Amount>() {
            _currency = currency;
            _unitIndex = unitIndex;
            _value = value;
        }

        Amount::Amount(const api::Currency &currency, int32_t unitIndex, const BigInt &value) : ManagedObject<Amount>() {
            _currency = currency;
            _unitIndex = unitIndex;
            _value = value;
        }

        std::shared_ptr<api::BigInt> Amount::toBigInt() {
            return std::make_shared<api::BigIntImpl>(_value);
        }

        api::Currency Amount::getCurrency() {
            return _currency;
        }

        api::CurrencyUnit Amount::getUnit() {
            return _currency.units[_unitIndex];
        }

        std::shared_ptr<api::Amount> Amount::toUnit(const api::CurrencyUnit &unit) {
            auto index = 0;
            for (auto& u : _currency.units) {
                if (u.code == unit.code && u.name == unit.name && u.numberOfDecimal == unit.numberOfDecimal &&
                    u.symbol == unit.symbol) {
                    return std::make_shared<Amount>(
                            _currency,
                            index,
                            _value
                    );
                }
                index += 1;
            }
            throw make_exception(api::ErrorCode::CURRENCY_UNIT_NOT_FOUND, "Cannot find currency {}", unit.name);
        }

        std::string Amount::toString() {
            auto magnitude = getMagnitude();
            BigInt value = _value;
            while (magnitude > 0) {
                value = value / BigInt(10);
                magnitude -= 1;
            }
            return value.toString();
        }

        int64_t Amount::toLong() {
            auto magnitude = getMagnitude();
            BigInt value = _value;
            while (magnitude > 0) {
                value = value / BigInt(10);
                magnitude -= 1;
            }
            return value.toInt64();
        }

        double Amount::toDouble() {
            throw make_exception(api::ErrorCode::IMPLEMENTATION_IS_MISSING, "double Amount::toDouble()");
        }

        std::string Amount::format(const api::Locale &locale, const optional<api::FormatRules> &rules) {
            throw make_exception(api::ErrorCode::IMPLEMENTATION_IS_MISSING, "std::string Amount::format(const api::Locale &locale, const optional<api::FormatRules> &rules)");
        }

        std::shared_ptr<api::Amount> Amount::toMagnitude(int32_t magnitude) {
            for (auto& unit : getCurrency().units) {
                if (unit.numberOfDecimal == magnitude) {
                    return toUnit(unit);
                }
            }
            throw make_exception(api::ErrorCode::CURRENCY_NOT_FOUND, "Cannot find currency with magnitude {}", magnitude);
        }

        int32_t Amount::getMagnitude() const {
            return _currency.units[_unitIndex].numberOfDecimal;
        }

        std::shared_ptr<ledger::core::BigInt> Amount::value() const {
            return std::make_shared<ledger::core::BigInt>(_value);
        }

        std::shared_ptr<api::Amount> api::Amount::fromLong(const Currency &currency, int64_t value) {
            ledger::core::BigInt  v(value);
            return std::make_shared<ledger::core::Amount>(currency, 0, v);
        }

        std::shared_ptr<api::Amount> api::Amount::fromHex(const Currency &currency, const std::string &hex) {
            auto v = ledger::core::BigInt::fromHex(hex);
            return std::make_shared<ledger::core::Amount>(currency, 0, v);
        }

    }
}

<|start_filename|>core/test/integration/synchronization/cosmos_synchronization.cpp<|end_filename|>
/*
 *
 * cosmos_synchronization.cpp
 * ledger-core
 *
 * Created by <NAME> on 15/06/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <iostream>
#include <set>
#include <numeric>

#include <test/cosmos/Fixtures.hpp>
#include "../BaseFixture.h"

#include <gtest/gtest.h>

#include <api/Configuration.hpp>
#include <api/KeychainEngines.hpp>
#include <api/PoolConfiguration.hpp>
#include <utils/DateUtils.hpp>
#include <utils/hex.h>
#include <collections/DynamicObject.hpp>
#include <math/BigInt.h>

#include <wallet/cosmos/explorers/GaiaCosmosLikeBlockchainExplorer.hpp>
#include <wallet/cosmos/CosmosNetworks.hpp>
#include <api/CosmosConfigurationDefaults.hpp>
#include <cosmos/CosmosLikeExtendedPublicKey.hpp>
#include <wallet/cosmos/CosmosLikeCurrencies.hpp>
#include <wallet/cosmos/transaction_builders/CosmosLikeTransactionBuilder.hpp>
#include <wallet/cosmos/CosmosLikeWallet.hpp>
#include <wallet/cosmos/CosmosLikeOperationQuery.hpp>
#include <wallet/cosmos/CosmosLikeConstants.hpp>
#include <cosmos/bech32/CosmosBech32.hpp>

#include <wallet/cosmos/database/CosmosLikeOperationDatabaseHelper.hpp>

using namespace std;
using namespace ledger::core;
using namespace ledger::testing::cosmos;

api::CosmosLikeNetworkParameters COSMOS_PARAMS = networks::getCosmosLikeNetworkParameters("atom");

class CosmosLikeWalletSynchronization : public BaseFixture {
public:
    void SetUp() override {
        BaseFixture::SetUp();
        auto worker = dispatcher->getSerialExecutionContext("worker");
        auto threadpoolWorker = dispatcher->getThreadPoolExecutionContext("threadpoolWorker");
        auto client = std::make_shared<HttpClient>(
            api::CosmosConfigurationDefaults::COSMOS_DEFAULT_API_ENDPOINT, http, worker, threadpoolWorker);

#ifdef PG_SUPPORT
    const bool usePostgreSQL = true;
    auto poolConfig = DynamicObject::newInstance();
    poolConfig->putString(api::PoolConfiguration::DATABASE_NAME, "postgres://localhost:5432/test_db");
    pool = newDefaultPool("postgres", "", poolConfig, usePostgreSQL);
#else
    pool = newDefaultPool();
#endif

        explorer = std::make_shared<GaiaCosmosLikeBlockchainExplorer>(
            worker,
            client,
            COSMOS_PARAMS,
            std::make_shared<DynamicObject>());
    }

    void setupTest(std::shared_ptr<CosmosLikeAccount>& account,
                   std::shared_ptr<AbstractWallet>& wallet,
                   const std::string& pubKey) {

        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME, "44'/<coin_type>'/<account>'/<node>/<address>");
        wallet = uv::wait(pool->createWallet("e847815f-488a-4301-b67c-378a5e9c8a61", "cosmos", configuration));

        auto accountInfo = uv::wait(wallet->getNextAccountCreationInfo());
        EXPECT_EQ(accountInfo.index, 0);
        accountInfo.publicKeys.push_back(hex::toByteArray(pubKey));

        account = ledger::testing::cosmos::createCosmosLikeAccount(wallet, accountInfo.index, accountInfo);
    }

    void performSynchro(const std::shared_ptr<CosmosLikeAccount>& account) {
        auto receiver = make_receiver([=](const std::shared_ptr<api::Event> &event) {
            fmt::print("Received event {}\n", api::to_string(event->getCode()));
            if (event->getCode() == api::EventCode::SYNCHRONIZATION_STARTED) {
                return;
            }
            EXPECT_EQ(event->getCode(), api::EventCode::SYNCHRONIZATION_SUCCEED);

            auto balance = uv::wait(account->getBalance());
            fmt::print("Balance: {} uatom\n", balance->toString());

            auto block = uv::wait(account->getLastBlock());
            fmt::print("Block height: {}\n", block.height);
            EXPECT_GT(block.height, 0);

            getTestExecutionContext()->stop();
        });

        auto bus = account->synchronize();
        bus->subscribe(getTestExecutionContext(), receiver);
        getTestExecutionContext()->waitUntilStopped();
     }

    void TearDown() override {
        uv::wait(pool->freshResetAll());
        BaseFixture::TearDown();
    }

    std::shared_ptr<WalletPool> pool;
    std::shared_ptr<GaiaCosmosLikeBlockchainExplorer> explorer;
};

TEST_F(CosmosLikeWalletSynchronization, GetAccountWithExplorer) {

    auto account = uv::wait(explorer->getAccount(DEFAULT_ADDRESS));
    EXPECT_EQ(account->address, DEFAULT_ADDRESS);
    EXPECT_EQ(account->accountNumber, "12850");
    EXPECT_EQ(account->withdrawAddress, DEFAULT_ADDRESS)
        << "Withdraw address has not been modified on this address";
}


TEST_F(CosmosLikeWalletSynchronization, InternalFeesMessageInTransaction) {
    /// This transaction contains 2 messages. One internal message to store fees
    /// is added after the transaction is retrieved from the network, hence the
    /// transaction should contain 3 messages, the last one being the one added
    /// specifically for the fees.
    const auto transaction = uv::wait(explorer->getTransactionByHash("0DBFC4E8E9E5A64C2C9B5EAAAA0422D99A61CFC5354E15002A061E91200DC2D6"));

    ASSERT_NE(transaction, nullptr);
    EXPECT_EQ(transaction->messages.size(), 3);
    EXPECT_EQ(transaction->logs.size(), 3);
    const auto feeMessageIndex = transaction->messages.size() - 1;
    const auto& feeMessage = transaction->messages[feeMessageIndex];
    const auto& feeMessageLog = transaction->logs[feeMessageIndex];

    auto reduceAmounts = [](const std::vector<cosmos::Coin>& coins) {
        return std::accumulate(std::begin(coins), std::end(coins), BigInt::ZERO,
                [](BigInt s, const cosmos::Coin& coin) {
                    return s + BigInt::fromString(coin.amount);
                });
    };

    // check the internal fees message content
    ASSERT_EQ(feeMessage.type, cosmos::constants::kMsgFees);
    const auto& feeMessageContent = boost::get<cosmos::MsgFees>(feeMessage.content);
    EXPECT_EQ(reduceAmounts(transaction->fee.amount), BigInt::fromString(feeMessageContent.fees.amount));
    EXPECT_EQ(feeMessageContent.payerAddress, DEFAULT_ADDRESS);

    // check the logs for the fees message
    EXPECT_EQ(feeMessageLog.messageIndex, feeMessageIndex);
    EXPECT_EQ(feeMessageLog.success, true);
    EXPECT_EQ(feeMessageLog.log, "");
}


TEST_F(CosmosLikeWalletSynchronization, GetWithdrawDelegationRewardWithExplorer) {

    auto filter = GaiaCosmosLikeBlockchainExplorer::fuseFilters({
        GaiaCosmosLikeBlockchainExplorer::filterWithAttribute(
            cosmos::constants::kEventTypeTransfer,
            cosmos::constants::kAttributeKeyRecipient,
            DEFAULT_ADDRESS)
    });
    auto bulk = uv::wait(explorer->getTransactions(filter, 1, 10));
    auto transactions = bulk->transactions;
    ASSERT_TRUE(transactions.size() >= 1) << "At least 1 transaction must be fetched looking at the REST response manually.";
    bool foundTx = false;
    for (const auto& tx : transactions) {
        if (tx.hash == "0DBFC4E8E9E5A64C2C9B5EAAAA0422D99A61CFC5354E15002A061E91200DC2D6") {
            foundTx = true;
            EXPECT_EQ(tx.block->height, 237691);
            EXPECT_EQ(tx.logs.size(), 3);
            size_t withdraw_msg_index = 2;
            if (tx.messages[0].type == cosmos::constants::kMsgWithdrawDelegationReward) {
                withdraw_msg_index = 0;
            } else if (tx.messages[1].type == cosmos::constants::kMsgWithdrawDelegationReward) {
                withdraw_msg_index = 1;
            } else {
                FAIL() << cosmos::constants::kMsgWithdrawDelegationReward << " message not found in tx";
            }
            EXPECT_TRUE(tx.logs[withdraw_msg_index].success);
            EXPECT_TRUE(tx.messages[withdraw_msg_index].log.success);
            EXPECT_EQ(tx.messages[withdraw_msg_index].type, cosmos::constants::kMsgWithdrawDelegationReward);
            const cosmos::MsgWithdrawDelegationReward& msg = boost::get<cosmos::MsgWithdrawDelegationReward>(tx.messages[0].content);
            EXPECT_EQ(msg.delegatorAddress, DEFAULT_ADDRESS);
            EXPECT_EQ(msg.validatorAddress, "cosmosvaloper1sd4tl9aljmmezzudugs7zlaya7pg2895ws8tfs");
            EXPECT_EQ(tx.fee.gas.toInt64(), 200000);
            EXPECT_EQ(tx.fee.amount[withdraw_msg_index].denom, "uatom");
            EXPECT_EQ(tx.fee.amount[withdraw_msg_index].amount, "5000");
            EXPECT_EQ(tx.gasUsed, Option<std::string>("104477"));
            break;
        }
    }
    ASSERT_TRUE(foundTx) << "The transaction we need to test has not been found in the REST request.";
}


TEST_F(CosmosLikeWalletSynchronization, GetErrorTransaction) {
    auto tx_hash = "4A7823F0F2899AA6EC1DCB2E242C541EDAF90419A3DE03ED885E438FEDB779D4";
    auto validator = "cosmosvaloper1clpqr4nrk4khgkxj78fcwwh6dl3uw4epsluffn";
    auto delegator = "cosmos1k3kg9w60dd5x56vve2s28v3xjp7fp2vn2hjjsa";

    auto tx = uv::wait(explorer->getTransactionByHash(tx_hash));
    ASSERT_EQ(tx->hash, tx_hash);
    EXPECT_EQ(tx->block->height, 768780);
    EXPECT_EQ(tx->logs.size(), 2);
    // Logs are stored twice now, but only in serialized types
    // DB will only ever store logs in cosmos_messages table anyway
    EXPECT_FALSE(tx->logs[0].success);
    EXPECT_EQ(tx->logs[0].log, "{\"codespace\":\"sdk\",\"code\":10,\"message\":\"insufficient account funds; 2412592uatom < 2417501uatom\"}");
    EXPECT_FALSE(tx->messages[0].log.success);
    EXPECT_EQ(tx->messages[0].log.log, R"esc({"codespace":"sdk","code":10,"message":"insufficient account funds; 2412592uatom < 2417501uatom"})esc");
    EXPECT_EQ(tx->messages[0].type, cosmos::constants::kMsgDelegate);
    const cosmos::MsgDelegate& msg = boost::get<cosmos::MsgDelegate>(tx->messages[0].content);
    EXPECT_EQ(msg.delegatorAddress, delegator);
    EXPECT_EQ(msg.validatorAddress, validator);
    EXPECT_EQ(msg.amount.amount, "2417501");
    EXPECT_EQ(msg.amount.denom, "uatom");
    EXPECT_EQ(tx->fee.gas.toInt64(), 200000);
    EXPECT_EQ(tx->fee.amount[0].denom, "uatom");
    EXPECT_EQ(tx->fee.amount[0].amount, "5000");
    EXPECT_EQ(tx->gasUsed, Option<std::string>("47235"));
}


TEST_F(CosmosLikeWalletSynchronization, GetSendWithExplorer) {
    auto tx_hash = "F4B8CB550B498F744CCC420907B80D0B068250972F975354A873CD1CCF9B000A";
    auto receiver = "cosmos1<PASSWORD>al<PASSWORD>";
    // Note : the sender of the message is also the sender of the funds in this transaction.
    auto sender = "cosmos15v<PASSWORD>mp<PASSWORD>7<PASSWORD>";

    auto tx = uv::wait(explorer->getTransactionByHash(tx_hash));
    ASSERT_EQ(tx->hash, tx_hash);
    EXPECT_EQ(tx->block->height, 453223);
    EXPECT_EQ(tx->logs.size(), 2);
    EXPECT_TRUE(tx->logs[0].success);
    EXPECT_TRUE(tx->messages[0].log.success);
    EXPECT_EQ(tx->messages[0].type, cosmos::constants::kMsgSend);
    const cosmos::MsgSend& msg = boost::get<cosmos::MsgSend>(tx->messages[0].content);
    EXPECT_EQ(msg.fromAddress, sender);
    EXPECT_EQ(msg.toAddress, receiver);
    EXPECT_EQ(msg.amount[0].amount, "270208360");
    EXPECT_EQ(msg.amount[0].denom, "uatom");
    EXPECT_EQ(tx->fee.gas.toInt64(), 200000);
    EXPECT_EQ(tx->fee.amount[0].denom, "uatom");
    EXPECT_EQ(tx->fee.amount[0].amount, "30");
    EXPECT_EQ(tx->gasUsed, Option<std::string>("41014"));
}

TEST_F(CosmosLikeWalletSynchronization, GetDelegateWithExplorer) {
    auto delegator = "cosmos1ytpz9gt59hssp5m5sknuzrwse88glqhgcrypxj";
    auto validator = "<KEY>";

    auto filter = GaiaCosmosLikeBlockchainExplorer::fuseFilters(
        {GaiaCosmosLikeBlockchainExplorer::filterWithAttribute(
             cosmos::constants::kEventTypeMessage,
             cosmos::constants::kAttributeKeyAction,
             cosmos::constants::kEventTypeDelegate),
         GaiaCosmosLikeBlockchainExplorer::filterWithAttribute(
             cosmos::constants::kEventTypeMessage,
             cosmos::constants::kAttributeKeySender,
             delegator)});
    auto bulk = uv::wait(explorer->getTransactions(filter, 1, 10));
    auto transactions = bulk->transactions;
    ASSERT_TRUE(transactions.size() >= 1);
    bool foundTx = false;
    for (const auto& tx : transactions) {
        if (tx.hash == "BD77DF6A76066AA79DAA7705B9F0DC6B66B7E6FBB3D1FD28A07D6A0EED7AE6B5") {
            foundTx = true;
            EXPECT_EQ(tx.block->height, 660081);
            EXPECT_EQ(tx.logs.size(), 2);
            EXPECT_TRUE(tx.logs[0].success);
            EXPECT_TRUE(tx.messages[0].log.success);
            EXPECT_EQ(tx.messages[0].type, cosmos::constants::kMsgDelegate);
            const cosmos::MsgDelegate& msg = boost::get<cosmos::MsgDelegate>(tx.messages[0].content);
            EXPECT_EQ(msg.delegatorAddress, delegator);
            EXPECT_EQ(msg.validatorAddress, validator);
            EXPECT_EQ(msg.amount.amount, "25257508");
            EXPECT_EQ(msg.amount.denom, "uatom");
            EXPECT_EQ(tx.fee.gas.toInt64(), 300000);
            EXPECT_EQ(tx.fee.amount[0].denom, "uatom");
            EXPECT_EQ(tx.fee.amount[0].amount, "2500");
            EXPECT_EQ(tx.gasUsed, Option<std::string>("104477"));
            break;
        }
    }
    ASSERT_TRUE(foundTx);
}

TEST_F(CosmosLikeWalletSynchronization, GetCurrentBlockWithExplorer) {
    std::string address = "cosmos16xkkyj97z7r83sx45xwk9uwq0mj0zszlf6c6mq";

    auto block = uv::wait(explorer->getCurrentBlock());
    EXPECT_TRUE(block->hash.size() > 0);
    EXPECT_TRUE(block->height > 0);
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_MediumXpubSynchronization) {
    auto walletName = "8d99cc44-9061-43a4-9edd-f938d2007926";
#ifdef PG_SUPPORT
    const bool usePostgreSQL = true;
    auto poolConfig = DynamicObject::newInstance();
    poolConfig->putString(api::PoolConfiguration::DATABASE_NAME, "postgres://localhost:5432/test_db");
    auto pool = newDefaultPool("postgres", "", poolConfig, usePostgreSQL);
#else
    auto pool = newDefaultPool();
#endif
    backend->enableQueryLogging(true);

    {
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::KEYCHAIN_DERIVATION_SCHEME,
                                 "44'/<coin_type>'/<account>'/<node>/<address>");
        auto wallet = uv::wait(pool->createWallet(walletName, currencies::ATOM.name, configuration));
        {
            auto accountInfo = uv::wait(wallet->getNextAccountCreationInfo());
            EXPECT_EQ(accountInfo.index, 0);

            accountInfo.publicKeys.push_back(hex::toByteArray(DEFAULT_HEX_PUB_KEY));
            auto account = ledger::testing::cosmos::createCosmosLikeAccount(
                wallet, accountInfo.index, accountInfo);

            auto address = uv::wait(account->getFreshPublicAddresses())[0]->toString();
            EXPECT_EQ(address, DEFAULT_ADDRESS);

            auto receiver = make_receiver([=](const std::shared_ptr<api::Event> &event) {
                fmt::print("Received event {}\n", api::to_string(event->getCode()));
                if (event->getCode() == api::EventCode::SYNCHRONIZATION_STARTED)
                    return;
                EXPECT_EQ(event->getCode(), api::EventCode::SYNCHRONIZATION_SUCCEED);

                auto balance = uv::wait(account->getBalance());
                fmt::print("Balance: {} uatom\n", balance->toString());
                auto txBuilder = std::dynamic_pointer_cast<CosmosLikeTransactionBuilder>(account->buildTransaction());
                getTestExecutionContext()->stop();
            });

            auto restoreKey = account->getRestoreKey();
            auto bus = account->synchronize();
            bus->subscribe(getTestExecutionContext(), receiver);

            getTestExecutionContext()->waitUntilStopped();

            auto block = uv::wait(account->getLastBlock());
            fmt::print("Block height: {}\n", block.height);
            EXPECT_GT(block.height, 0);

            auto ops = uv::wait(std::dynamic_pointer_cast<OperationQuery>(account->queryOperations()->complete())->execute());
            fmt::print("Ops: {}\n", ops.size());
            ASSERT_GT(ops.size(), 0);
            ASSERT_TRUE(ops[0]->getBlockHeight())
                << "The first operation should have a block height.";
            EXPECT_GT(ops[0]->getBlockHeight().value(), 0)
                << "The first operation should have a non 0 height.";
            EXPECT_LT(ops[0]->getBlockHeight().value(), block.height)
                << "The first operation should not have the same height as the last block.";

            const auto sequenceNumber = account->getInfo().sequence;
            const int sequence = std::atoi(sequenceNumber.c_str());
            EXPECT_GE(sequence, 1226) << "Sequence was at 1226 on 2020-03-26";

            const auto accountNumber = account->getInfo().accountNumber;
            EXPECT_EQ(accountNumber, "12850") << "Account number is a network constant for a given address";
        }
    }
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_Balances)
{
    std::string hexPubKey =
        "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a";  // Obelix

    std::shared_ptr<AbstractWallet> wallet;
    std::shared_ptr<CosmosLikeAccount> account;

    setupTest(account, wallet, hexPubKey);

    const std::string address = account->getKeychain()->getAddress()->toBech32();
    const std::string mintscanExplorer = fmt::format("https://www.mintscan.io/account/{}", address);

    const auto totalBalance = uv::wait(account->getTotalBalance())->toLong();
    const auto delegatedBalance = uv::wait(account->getDelegatedBalance())->toLong();
    const auto pendingRewards = uv::wait(account->getPendingRewardsBalance())->toLong();
    const auto unbondingBalance = uv::wait(account->getUnbondingBalance())->toLong();
    const auto spendableBalance = uv::wait(account->getSpendableBalance())->toLong();

    EXPECT_LE(delegatedBalance, totalBalance)
        << "Delegated Coins fall under Total Balance, so delegatedBalance <= totalBalance";
    // Strict comparison here because
    // it's impossible to have pending rewards without
    // at least some delegatedBalance or unbonding balance
    EXPECT_LT(pendingRewards, totalBalance)
        << "Pending rewards fall under Total Balance, so pendingRewards < totalBalance";
    EXPECT_LE(unbondingBalance, totalBalance)
        << "Unbondings fall under Total Balance, so unbondingBalance <= totalBalance";
    EXPECT_LE(spendableBalance, totalBalance)
        << "Spendable Coins fall under Total Balance, so spendableBalance <= totalBalance";

    EXPECT_GE(pendingRewards, 1) << fmt::format(
        "Check {}  to see if the account really has <1 uatom of pending rewards", mintscanExplorer);
    EXPECT_GE(totalBalance, 1000000) << fmt::format(
        "Check {}  to see if the account really has <1 ATOM of total balance", mintscanExplorer);
    EXPECT_GE(delegatedBalance, 2800) << fmt::format(
        "Check {}  to see if the account really has <0.002800 ATOM of total delegations",
        mintscanExplorer);
    EXPECT_GE(spendableBalance, 1000) << fmt::format(
        "Check {}  to see if the account really has <0.001 ATOM of available (spendable) balance",
        mintscanExplorer);
    // Unbondings are moving too much to assert the amount.
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_AllTransactionsSynchronization) {
    // FIXME Use an account that has all expected types of transactions
    std::string hexPubKey = "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a"; // Obelix

    std::shared_ptr<CosmosLikeAccount> account;
    std::shared_ptr<AbstractWallet> wallet;

    setupTest(account, wallet, hexPubKey);

    performSynchro(account);

    auto ops = uv::wait(std::dynamic_pointer_cast<OperationQuery>(account->queryOperations()->complete())->execute());
    fmt::print("Ops: {}\n", ops.size());
    EXPECT_GT(ops.size(), 0);

    auto foundMsgSend = false;
    auto foundMsgDelegate = false;
    auto foundMsgBeginRedelegate = false;
    auto foundMsgUndelegate = false;
    auto foundMsgSubmitProposal = false;
    auto foundMsgVote = false;
    auto foundMsgDeposit = false;
    auto foundMsgWithdrawDelegationReward = false;
    auto foundMsgMultiSend = false;
    auto foundMsgCreateValidator = false;
    auto foundMsgEditValidator = false;
    auto foundMsgSetWithdrawAddress = false;
    auto foundMsgWithdrawDelegatorReward = false;
    auto foundMsgWithdrawValidatorCommission = false;
    auto foundMsgUnjail = false;

    for (auto op : ops) {
        auto cosmosOp = op->asCosmosLikeOperation();

        std::cout << "Found operation type: " << cosmosOp->getMessage()->getRawMessageType() << std::endl;

        switch (cosmosOp->getMessage()->getMessageType()) {
            case api::CosmosLikeMsgType::MSGSEND: foundMsgSend = true; break;
            case api::CosmosLikeMsgType::MSGDELEGATE: foundMsgDelegate = true; break;
            case api::CosmosLikeMsgType::MSGBEGINREDELEGATE: foundMsgBeginRedelegate = true; break;
            case api::CosmosLikeMsgType::MSGUNDELEGATE: foundMsgUndelegate = true; break;
            case api::CosmosLikeMsgType::MSGSUBMITPROPOSAL: foundMsgSubmitProposal = true; break;
            case api::CosmosLikeMsgType::MSGVOTE: foundMsgVote = true; break;
            case api::CosmosLikeMsgType::MSGDEPOSIT: foundMsgDeposit = true; break;
            case api::CosmosLikeMsgType::MSGWITHDRAWDELEGATIONREWARD: foundMsgWithdrawDelegationReward = true; break;
            case api::CosmosLikeMsgType::MSGMULTISEND: foundMsgMultiSend = true; break;
            case api::CosmosLikeMsgType::MSGCREATEVALIDATOR: foundMsgCreateValidator = true; break;
            case api::CosmosLikeMsgType::MSGEDITVALIDATOR: foundMsgEditValidator = true; break;
            case api::CosmosLikeMsgType::MSGSETWITHDRAWADDRESS: foundMsgSetWithdrawAddress = true; break;
            case api::CosmosLikeMsgType::MSGWITHDRAWDELEGATORREWARD: foundMsgWithdrawDelegatorReward = true; break;
            case api::CosmosLikeMsgType::MSGWITHDRAWVALIDATORCOMMISSION: foundMsgWithdrawValidatorCommission = true; break;
            case api::CosmosLikeMsgType::MSGUNJAIL: foundMsgUnjail = true; break;
            case api::CosmosLikeMsgType::UNSUPPORTED: break;
        }
    }

    EXPECT_TRUE(foundMsgSend);
    EXPECT_TRUE(foundMsgDelegate);
    EXPECT_TRUE(foundMsgBeginRedelegate);
    EXPECT_TRUE(foundMsgUndelegate);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgSubmitProposal);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgVote);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgDeposit);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgWithdrawDelegationReward);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgMultiSend);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgCreateValidator);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgEditValidator);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgSetWithdrawAddress);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgWithdrawDelegatorReward);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgWithdrawValidatorCommission);
    // Deactivated because target account does not have this type of operation
    // EXPECT_TRUE(foundMsgUnjail);
}

TEST_F(CosmosLikeWalletSynchronization, ValidatorSet) {
    // This test assumes that HuobiPool and BinanceStaking are always in the validator set
    const auto huobi_pool_address = "cosmosvaloper1kn3wugetjuy4zetlq6wadchfhvu3x740ae6z6x";
    bool foundHuobi = false;
    const auto binance_staking_pub_address = "<KEY>";
    bool foundBinance = false;

    auto set = uv::wait(explorer->getActiveValidatorSet());

    EXPECT_EQ(set.size(), 125) << "currently cosmoshub-3 has 125 active validators";

    for (const auto & validator : set) {
        if (validator.consensusPubkey == binance_staking_pub_address) {
            foundBinance = true;
        } else if (validator.operatorAddress == huobi_pool_address){
            foundHuobi = true;
        } else {
            continue;
        }
    }

    EXPECT_TRUE(foundHuobi) << "Huobi Pool is expected to always be in the validator set";
    EXPECT_TRUE(foundBinance) << "Binance Staking is expected to always be in the validator set";
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_ValidatorInfo) {
    // This test assumes that HuobiPool and BinanceStaking are always in the validator set
    const auto bisonTrailsAddress = "cosmosvaloper1uxh465053nq3at4dn0jywgwq3s9sme3la3drx6";
    const auto bisonTrailsValConsPubAddress = "<KEY>";
    const auto mintscanAddress = fmt::format("https://www.mintscan.io/validators/{}", bisonTrailsAddress);


    auto valInfo = uv::wait(explorer->getValidatorInfo(bisonTrailsAddress));
    ASSERT_EQ(valInfo.operatorAddress, bisonTrailsAddress) << "We should fetch the expected validator";
    ASSERT_EQ(valInfo.consensusPubkey, bisonTrailsValConsPubAddress) << "We should fetch the expected validator";


    EXPECT_EQ(valInfo.validatorDetails.moniker, "Bison Trails");
    ASSERT_TRUE(valInfo.validatorDetails.identity);
    EXPECT_EQ(valInfo.validatorDetails.identity.value(), "A296556FF603197C");
    ASSERT_TRUE(valInfo.validatorDetails.website);
    EXPECT_EQ(valInfo.validatorDetails.website.value(), "https://bisontrails.co");
    ASSERT_TRUE(valInfo.validatorDetails.details);
    EXPECT_EQ(valInfo.validatorDetails.details.value(), "Bison Trails is the easiest way to run infrastructure on multiple blockchains.");

    EXPECT_EQ(valInfo.commission.rates.maxRate, "0.500000000000000000");
    EXPECT_EQ(valInfo.commission.rates.maxChangeRate, "0.010000000000000000");
    EXPECT_GE(valInfo.commission.updateTime, DateUtils::fromJSON("2019-03-13T23:00:00Z")) <<
        "As of this test writing, last update was on 2019-03-13T23:00:00Z. So updateTime should be at least as recent as this timestamp.";
    // Specify locale for std::stof -- Also, why we just pass strings instead of parsing as much as possible
    setlocale(LC_NUMERIC, "C");
    EXPECT_LE(std::stof(valInfo.commission.rates.rate), std::stof(valInfo.commission.rates.maxRate));

    EXPECT_EQ(valInfo.unbondingHeight, 0) <<
        fmt::format("Expecting BisonTrails to never have been jailed. Check {} to see if the assertion holds", mintscanAddress);
    EXPECT_FALSE(valInfo.unbondingTime) <<
        fmt::format("Expecting BisonTrails to never have been jailed. Check {} to see if the assertion holds", mintscanAddress);
    EXPECT_EQ(valInfo.minSelfDelegation, "1") <<
        fmt::format("Expecting BisonTrails to have '1' minimum self delegation. Check {} to see if the assertion holds", mintscanAddress);
    EXPECT_FALSE(valInfo.jailed) <<
        fmt::format("Expecting BisonTrails to never have been jailed. Check {} to see if the assertion holds", mintscanAddress);
    //EXPECT_GE(BigInt::fromString(valInfo.votingPower).toUint64(), BigInt::fromString("400000000000").toUint64()) <<
    //    fmt::format("Expecting BisonTrails voting power to be > 400_000 ATOM. Check {} to see if the assertion holds", mintscanAddress);
    EXPECT_EQ(valInfo.activeStatus, 2) <<
        fmt::format("Expecting BisonTrails to be active (and that currently the explorer returns 2 for this status). Check {} to see if the assertion holds", mintscanAddress);

    EXPECT_FALSE(valInfo.distInfo.validatorCommission.empty()) << "Bison Trails should have accumulated at least *some* validator commission";
    EXPECT_FALSE(valInfo.distInfo.selfBondRewards.empty()) << "Bison Trails should have accumulated at least *some* self delegation rewards";

    EXPECT_FALSE(valInfo.signInfo.tombstoned) << "Bison Trails is not expected to be tombstoned";
    EXPECT_GE(valInfo.signInfo.missedBlocksCounter, 0) << "This value cannot be negative";
    EXPECT_GE(valInfo.signInfo.jailedUntil, DateUtils::fromJSON("1970-01-01T00:00:00Z")) << "This value cannot be before epoch";
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_BalanceHistoryOperationQuery) {

    std::string hexPubKey = "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a"; // Obelix

    std::shared_ptr<CosmosLikeAccount> account;
    std::shared_ptr<AbstractWallet> wallet;

    setupTest(account, wallet, hexPubKey);

    performSynchro(account);

    const auto &uid = account->getAccountUid();
    soci::session sql(wallet->getDatabase()->getPool());
    std::vector<Operation> operations;

    auto keychain = account->getKeychain();
    std::function<bool(const std::string &)> filter = [&keychain](const std::string addr) -> bool {
        return keychain->contains(addr);
    };

    //Get operations related to an account
    CosmosLikeOperationDatabaseHelper::queryOperations(
        sql,
        uid,
        operations,
        filter);

    ASSERT_GE(operations.size(), 17) << "As of 2020-03-19, there are 17 operations picked up by the query";
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_GetAccountDelegations) {

    std::string hexPubKey = "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a"; // Obelix

    std::shared_ptr<CosmosLikeAccount> account;
    std::shared_ptr<AbstractWallet> wallet;

    setupTest(account, wallet, hexPubKey);

    auto delegations = uv::wait(account->getDelegations());
    EXPECT_GE(delegations.size(), 2);

    BigInt delegatedAmount;
    for (auto& delegation : delegations) {
        delegatedAmount = delegatedAmount + *(std::dynamic_pointer_cast<ledger::core::Amount>(delegation->getDelegatedAmount())->value());
    }
    EXPECT_GE(delegatedAmount.toUint64(), 1000000UL); // 1 ATOM

}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_GetAccountPendingRewards) {

    std::string hexPubKey = "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a"; // Obelix

    std::shared_ptr<CosmosLikeAccount> account;
    std::shared_ptr<AbstractWallet> wallet;

    setupTest(account, wallet, hexPubKey);

    auto rewards = uv::wait(account->getPendingRewards());
    EXPECT_GE(rewards.size(), 2);

    BigInt pendingReward;
    for (auto& reward : rewards) {
        pendingReward = pendingReward + *(std::dynamic_pointer_cast<ledger::core::Amount>(reward->getRewardAmount())->value());
    }
    EXPECT_GE(pendingReward.toUint64(), 1000UL); // 1000 uATOM

}

namespace {

void GenericGasLimitEstimationTest(const std::string& strTx, CosmosLikeWalletSynchronization& t)
{
    const auto tx = api::CosmosLikeTransactionBuilder::parseRawSignedTransaction(ledger::core::currencies::ATOM, strTx);
    const auto estimatedGasLimit = uv::wait(t.explorer->getEstimatedGasLimit(tx));
    EXPECT_GE(estimatedGasLimit->toUint64(), 0);
}

} // namespace

TEST_F(CosmosLikeWalletSynchronization, GasLimitEstimationForTransfer) {
    const auto strTx = "{\"account_number\":\"6571\","
        "\"chain_id\":\"cosmoshub-3\","
        "\"fee\":{\"amount\":[{\"amount\":\"5000\",\"denom\":\"uatom\"}],\"gas\":\"200000\"},"
        "\"memo\":\"Sent from Ledger\","
        "\"msgs\":["
        "{\"type\":\"cosmos-sdk/MsgSend\","
        "\"value\":{"
        "\"amount\":{\"amount\":\"1000000\",\"denom\":\"uatom\"},"
        "\"from_address\":\"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl\","
        "\"to_address\":\"cosmos16xyempempp92x9hyzz9wrgf94r6j9h5f06pxxv\""
        "}}],"
        "\"sequence\":\"0\"}";
    GenericGasLimitEstimationTest(strTx, *this);
}

TEST_F(CosmosLikeWalletSynchronization, GasLimitEstimationForWithdrawingRewards) {
    const auto strTx = "{\"account_number\":\"6571\","
        "\"chain_id\":\"cosmoshub-3\","
        "\"fee\":{\"amount\":[{\"amount\":\"5001\",\"denom\":\"uatom\"}],\"gas\":\"200020\"},"
        "\"memo\":\"Sent from Ledger\","
        "\"msgs\":["
        "{\"type\":\"cosmos-sdk/MsgWithdrawDelegationReward\""
        ",\"value\":{"
        "\"delegator_address\":\"cosmos167w96tdvmazakdwkw2u57227eduula2cy572lf\","
        "\"validator_address\":\"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7\""
        "}}],"
        "\"sequence\":\"0\"}";
    GenericGasLimitEstimationTest(strTx, *this);
}

TEST_F(CosmosLikeWalletSynchronization, GasLimitEstimationForDelegation) {
    const auto strTx = "{\"account_number\":\"6571\","
        "\"chain_id\":\"cosmoshub-3\","
        "\"fee\":{\"amount\":[{\"amount\":\"5000\",\"denom\":\"uatom\"}],\"gas\":\"200000\"},"
        "\"memo\":\"Sent from Ledger\","
        "\"msgs\":["
        "{\"type\":\"cosmos-sdk/MsgDelegate\","
        "\"value\":{"
        "\"amount\":{\"amount\":\"1000000\",\"denom\":\"uatom\"},"
        "\"delegator_address\":\"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl\","
        "\"validator_address\":\"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7\""
        "}}],"
        "\"sequence\":\"0\"}";
    GenericGasLimitEstimationTest(strTx, *this);
}

TEST_F(CosmosLikeWalletSynchronization, GasLimitEstimationForUnDelegation) {
    const auto strTx = "{\"account_number\":\"6571\","
        "\"chain_id\":\"cosmoshub-3\","
        "\"fee\":{\"amount\":[{\"amount\":\"5000\",\"denom\":\"uatom\"}],\"gas\":\"200000\"},"
        "\"memo\":\"Sent from Ledger\","
        "\"msgs\":["
        "{\"type\":\"cosmos-sdk/MsgUndelegate\","
        "\"value\":{"
        "\"amount\":{\"amount\":\"1000000\",\"denom\":\"uatom\"},"
        "\"delegator_address\":\"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl\","
        "\"validator_address\":\"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7\""
        "}}],"
        "\"sequence\":\"0\"}";
    GenericGasLimitEstimationTest(strTx, *this);
}

TEST_F(CosmosLikeWalletSynchronization, GasLimitEstimationForRedelegation) {
    const auto strTx = "{\"account_number\":\"6571\","
        "\"chain_id\":\"cosmoshub-3\","
        "\"fee\":{\"amount\":[{\"amount\":\"5000\",\"denom\":\"uatom\"}],\"gas\":\"200000\"},"
        "\"memo\":\"Sent from Ledger\","
        "\"msgs\":["
        "{\"type\":\"cosmos-sdk/MsgBeginRedelegate\","
        "\"value\":{\"amount\":{\"amount\":\"1000000\",\"denom\":\"uatom\"},"
        "\"delegator_address\":\"cosmos102hty0jv2s29lyc4u0tv97z9v298e24t3vwtpl\","
        "\"validator_dst_address\":\"cosmosvaloper1sd4tl9aljmmezzudugs7zlaya7pg2895ws8tfs\","
        "\"validator_src_address\":\"cosmosvaloper1grgelyng2v6v3t8z87wu3sxgt9m5s03xfytvz7\""
        "}}],"
        "\"sequence\":\"0\"}";
    GenericGasLimitEstimationTest(strTx, *this);
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_PendingUnbondings) {
    std::string hexPubKey =
        "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a";  // Obelix

    std::shared_ptr<CosmosLikeAccount> account;
    std::shared_ptr<AbstractWallet> wallet;

    setupTest(account, wallet, hexPubKey);
    const std::string address = account->getKeychain()->getAddress()->toBech32();
    const std::string mintscanExplorer = fmt::format("https://www.mintscan.io/account/{}", address);

    // First synchro
    performSynchro(account);

    auto unbondings = uv::wait(account->getUnbondings());
    EXPECT_GE(unbondings.size(), 1) << fmt::format(
        "Expecting at least 1 unbonding here for Obelix (explorer link : {}).", mintscanExplorer);
}

TEST_F(CosmosLikeWalletSynchronization, DISABLED_PendingRedelegations) {
    std::string hexPubKey =
        "0388459b2653519948b12492f1a0b464720110c147a8155d23d423a5cc3c21d89a";  // Obelix

    std::shared_ptr<CosmosLikeAccount> account;
    std::shared_ptr<AbstractWallet> wallet;

    setupTest(account, wallet, hexPubKey);
    const std::string address = account->getKeychain()->getAddress()->toBech32();
    const std::string mintscanExplorer = fmt::format("https://www.mintscan.io/account/{}", address);

    // First synchro
    performSynchro(account);

    auto redelegations = uv::wait(account->getRedelegations());
    EXPECT_GE(redelegations.size(), 1) << fmt::format(
        "Expecting at least 1 redelegation here for Obelix (explorer link : {}).",
        mintscanExplorer);
}

// FIXME This test fails ; put at the end because it also messes up the other tests
// This test is also highly dependent on external state ( how well the chain is
// doing). Until a better solution is found, this test is deactivated
// TEST_F(CosmosLikeWalletSynchronization, SuccessiveSynchronizations) {
//     std::string hexPubKey(ledger::testing::cosmos::DEFAULT_HEX_PUB_KEY);
//
//     std::shared_ptr<CosmosLikeAccount> account;
//     std::shared_ptr<AbstractWallet> wallet;
//
//     setupTest(account, wallet, hexPubKey);
//
//     // First synchro
//     performSynchro(account);
//     auto blockHeight1 = wait(account->getLastBlock()).height;
//
//     // Wait 30s (new Cosmos block every 7s)
//     fmt::print("Waiting new Cosmos block for 30s...\n");
//     std::flush(std::cerr);
//     std::flush(std::cout);
//     std::this_thread::sleep_for(std::chrono::seconds(30));
//
//     // Second synchro
//     // FIXME Fails due to limitation of test framework??
//     performSynchro(account);
//     auto blockHeight2 = wait(account->getLastBlock()).height;
//
//     EXPECT_NE(blockHeight1, blockHeight2);
// }



<|start_filename|>core/src/wallet/pool/database/CurrenciesDatabaseHelper.cpp<|end_filename|>
/*
 *
 * CurrenciesDatabaseHelper
 * ledger-core
 *
 * Created by <NAME> on 15/05/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "CurrenciesDatabaseHelper.hpp"
#include <api/BitcoinLikeNetworkParameters.hpp>
#include <api/EthereumLikeNetworkParameters.hpp>
#include <utils/hex.h>
#include <api/enum_from_string.hpp>
#include <api/Currency.hpp>
#include <api/BitcoinLikeFeePolicy.hpp>
#include <api/BitcoinLikeDustPolicy.hpp> 
#include <collections/strings.hpp>
#include <database/soci-number.h>

using namespace soci;

bool ledger::core::CurrenciesDatabaseHelper::insertCurrency(soci::session &sql,
                                                            const ledger::core::api::Currency &currency) {
    int count;
    sql << "SELECT COUNT(*) FROM currencies WHERE name = :name",
    soci::use(currency.name),
    soci::into(count);
    bool inserted = false;
    if (count == 0) {
        auto currencyType = api::to_string(currency.walletType);
        // Insert currency
        sql << "INSERT INTO currencies VALUES(:name, :type, :bip44, :uri)",
            use(currency.name),
            use(currencyType),
            use(currency.bip44CoinType),
            use(currency.paymentUriScheme);

        // Insert network parameters
        switch (currency.walletType) {
            case api::WalletType::BITCOIN: {
                const auto &params = currency.bitcoinLikeNetworkParameters.value();

                std::stringstream additionalBIPs;
                std::string separator(";");
                strings::join(params.AdditionalBIPs, additionalBIPs, separator);
                auto BIPs = additionalBIPs.str();
                auto hexP2PKHVersion = hex::toString(params.P2PKHVersion);
                auto hexP2SHVersion = hex::toString(params.P2SHVersion);
                auto hexXPUBVersion = hex::toString(params.XPUBVersion);
                auto feePolicy = api::to_string(params.FeePolicy);
                auto dustPolicy = api::to_string(params.DustPolicy);
                auto sigHash = hex::toString(params.SigHash);
                auto useTimestampedTransaction = static_cast<int>(params.UsesTimestampedTransaction);
                sql
                << "INSERT INTO bitcoin_currencies VALUES(:name, :identifier, :p2pkh, :p2sh, :xpub, :dust, :fee_policy, :prefix, :use_timestamped_transaction, :delay, :sigHashType, :additionalBIPs, :dust_policy)",
                use(currency.name),
                use(params.Identifier),
                use(hexP2PKHVersion),
                use(hexP2SHVersion),
                use(hexXPUBVersion),
                use(params.Dust),
                use(feePolicy),
                use(params.MessagePrefix),
                use(useTimestampedTransaction),
                use(params.TimestampDelay),
                use(sigHash),
                use(BIPs),
                use(dustPolicy);  
                break;
            }
            case api::WalletType::COSMOS: {
                auto &params = currency.cosmosLikeNetworkParameters.value();

                std::stringstream additionalCIPs;
                std::string separator(",");
                strings::join(params.AdditionalCIPs, additionalCIPs, separator);
                auto CIPs = additionalCIPs.str();
                auto hexXPUBVersion = hex::toString(params.XPUBVersion);
                auto hexPubKeyPrefix = hex::toString(params.PubKeyPrefix);
                auto hexAddressPrefix = hex::toString(params.AddressPrefix);
                sql << "INSERT INTO cosmos_currencies VALUES(:name, :identifier, :xpub, "
                       ":pubkey_prefix, :address_prefix, :message_prefix, :chain_id, "
                       ":additionalCIPs)",
                    use(currency.name), use(params.Identifier), use(hexXPUBVersion),
                    use(hexPubKeyPrefix), use(hexAddressPrefix), use(params.MessagePrefix),
                    use(params.ChainId), use(CIPs);
                break;
            }
            case api::WalletType::ETHEREUM: {
                const auto &params = currency.ethereumLikeNetworkParameters.value();

                std::stringstream additionalEIPs;
                std::string separator(";");
                strings::join(params.AdditionalEIPs, additionalEIPs, separator);
                auto EIPs = additionalEIPs.str();
                auto xpubVersion = hex::toString(params.XPUBVersion);
                sql << "INSERT INTO ethereum_currencies VALUES(:name, :identifier, :chainID, :xpub, :prefix, :additionalBIPs)",
                        use(currency.name),
                        use(params.Identifier),
                        use(params.ChainID),
                        use(xpubVersion),
                        use(params.MessagePrefix),
                        use(EIPs);
                break;
            }
            case api::WalletType::RIPPLE: {
                const auto &params = currency.rippleLikeNetworkParameters.value();

                std::stringstream additionalRIPs;
                std::string separator(";");
                strings::join(params.AdditionalRIPs, additionalRIPs, separator);
                auto RIPs = additionalRIPs.str();
                auto xpubVersion = hex::toString(params.XPUBVersion);
                sql << "INSERT INTO ripple_currencies VALUES(:name, :identifier, :xpub, :prefix, :additionalRIPs)",
                        use(currency.name),
                        use(params.Identifier),
                        use(xpubVersion),
                        use(params.MessagePrefix),
                        use(RIPs);
                break;
            }
            case api::WalletType::TEZOS: {
                const auto &params = currency.tezosLikeNetworkParameters.value();
                std::stringstream additionalTIPs;
                std::string separator(";");
                strings::join(params.AdditionalTIPs, additionalTIPs, separator);
                auto TIPs = additionalTIPs.str();
                auto xpubVersion = hex::toString(params.XPUBVersion);
                auto implicitPrefix = hex::toString(params.ImplicitPrefix);
                auto originatedPrefix = hex::toString(params.OriginatedPrefix);
                sql << "INSERT INTO tezos_currencies VALUES(:name, :identifier, :xpub, :implicit_prefix, :originated_prefix, :prefix, :additionalTIPs)",
                        use(currency.name),
                        use(params.Identifier),
                        use(xpubVersion),
                        use(implicitPrefix),
                        use(originatedPrefix),
                        use(params.MessagePrefix),
                        use(TIPs);
                break; // TODO INSERT MONERO NETWORK PARAMS
            }
            case api::WalletType::MONERO:break;
            case api::WalletType::ALGORAND: {
                const auto &params = currency.algorandNetworkParameters.value();
                sql << "INSERT INTO algorand_currencies VALUES(:name, :genesis_id, :genesis_hash)",
                    use(currency.name),
                    use(params.genesisID),
                    use(params.genesisHash)
                ;
                break;
            }
            case api::WalletType::STELLAR: {
                const auto &params = currency.stellarLikeNetworkParameters.value();

                std::stringstream seps;
                std::string separator(";");
                auto addrVersion = hex::toString(params.Version);
                auto muxedAddrVersion = hex::toString(params.MuxedVersion);
                strings::join(params.AdditionalSEPs, seps, separator);
                auto SEPs = seps.str();

                sql << "INSERT INTO stellar_currencies VALUES(:name, :identifier, :version, :reserve, :fee, :pass, "
                       ":seps, :muxed_version)",
                    use(currency.name),
                    use(params.Identifier),
                    use(addrVersion),
                    use(params.BaseReserve),
                    use(params.BaseFee),
                    use(params.NetworkPassphrase),
                    use(SEPs),
                    use(muxedAddrVersion)
                ;
                break;
            }
        }
        inserted = true;
    }
    insertUnits(sql, currency);
    return inserted;
}

bool ledger::core::CurrenciesDatabaseHelper::insertERC20Token(soci::session &sql,
                                                              const ledger::core::api::ERC20Token &token) {

    auto count = 0;
    sql << "SELECT COUNT(*) FROM erc20_tokens WHERE contract_address = :address",
            soci::use(token.contractAddress),
            soci::into(count);
    if (count == 0) {
        sql << "INSERT INTO erc20_tokens VALUES(:contract_address, :name, :symbol, :number_of_decimal)",
                use(token.contractAddress),
                use(token.name),
                use(token.symbol),
                use(token.numberOfDecimal);
        return true;
    }
    return false;
}

void ledger::core::CurrenciesDatabaseHelper::getAllCurrencies(soci::session &sql,
                                                              std::vector<ledger::core::api::Currency> &currencies) {
    rowset<row> rows = (sql.prepare << "SELECT currencies.name, currencies.type, currencies.bip44_coin_type, currencies.payment_uri_scheme FROM currencies ");
    for (auto& currency_row : rows) {
        auto offset = 0;
        api::Currency currency;
        currency.name = currency_row.get<std::string>(0);
        currency.walletType = api::from_string<api::WalletType>(currency_row.get<std::string>(1));
        currency.bip44CoinType = currency_row.get<int32_t>(2);
        currency.paymentUriScheme = currency_row.get_indicator(3) == i_null ? "" : currency_row.get<std::string>(3);
        switch (currency.walletType) {
            case api::WalletType::BITCOIN: {
                rowset<row> btc_rows = (sql.prepare << "SELECT bitcoin_currencies.p2pkh_version, bitcoin_currencies.p2sh_version, bitcoin_currencies.xpub_version,"
                                                    " bitcoin_currencies.dust_amount, bitcoin_currencies.fee_policy, bitcoin_currencies.has_timestamped_transaction,"
                                                    " bitcoin_currencies.message_prefix, bitcoin_currencies.identifier, bitcoin_currencies.timestamp_delay,"
                                                    " bitcoin_currencies.sighash_type, bitcoin_currencies.additional_BIPs, bitcoin_currencies.dust_policy"
                                                    " FROM bitcoin_currencies "
                                                    " WHERE bitcoin_currencies.name = :currency_name", use(currency.name));
                for (auto& btc_row : btc_rows) {
                    api::BitcoinLikeNetworkParameters params;
                    params.P2PKHVersion = hex::toByteArray(btc_row.get<std::string>(0));
                    params.P2SHVersion = hex::toByteArray(btc_row.get<std::string>(1));
                    params.XPUBVersion = hex::toByteArray(btc_row.get<std::string>(2));
                    /*
                     * On Linux, if we use int64_t, we get std::bad_cast exception thrown,
                     * so we replace by a long long (which is supported by soci (soci::dt_long_long))
                     */
                    params.Dust = btc_row.get<long long>(3);
                    params.FeePolicy = api::from_string<api::BitcoinLikeFeePolicy>(btc_row.get<std::string>(4));
                    params.UsesTimestampedTransaction = btc_row.get<int>(5) == 1;
                    params.MessagePrefix = btc_row.get<std::string>(6);
                    params.Identifier = btc_row.get<std::string>(7);
                    params.TimestampDelay = btc_row.get<long long>(8);
                    params.SigHash = hex::toByteArray(btc_row.get<std::string>(9));
                    params.AdditionalBIPs = strings::split(btc_row.get<std::string>(10), ",");
                    params.DustPolicy = api::from_string<api::BitcoinLikeDustPolicy>(btc_row.get<std::string>(11));
                    currency.bitcoinLikeNetworkParameters = params;
                }
                break;
            }
            case api::WalletType::COSMOS: {
                rowset<row> cosmos_rows =
                    (sql.prepare
                         << "SELECT cosmos_currencies.name, cosmos_currencies.identifier, "
                            "cosmos_currencies.xpub_version, cosmos_currencies.pubkey_prefix, "
                            "cosmos_currencies.address_prefix, cosmos_currencies.message_prefix, "
                            "cosmos_currencies.chain_id, cosmos_currencies.additional_CIPs"
                            " FROM cosmos_currencies "
                            " WHERE cosmos_currencies.name = :currency_name",
                     use(currency.name));
                for (auto &cosmos_row : cosmos_rows) {
                    api::CosmosLikeNetworkParameters params;
                    params.Identifier = cosmos_row.get<std::string>(1);
                    params.XPUBVersion = hex::toByteArray(cosmos_row.get<std::string>(2));
                    params.PubKeyPrefix = hex::toByteArray(cosmos_row.get<std::string>(3));
                    params.AddressPrefix = hex::toByteArray(cosmos_row.get<std::string>(4));
                    params.MessagePrefix = cosmos_row.get<std::string>(5);
                    params.ChainId = cosmos_row.get<std::string>(6);
                    params.AdditionalCIPs = strings::split(cosmos_row.get<std::string>(7), ",");
                    currency.cosmosLikeNetworkParameters = params;
                }
                break;
            }
            case api::WalletType::ETHEREUM: {
                rowset<row> eth_rows = (sql.prepare << "SELECT ethereum_currencies.chain_id, ethereum_currencies.xpub_version,"
                        " ethereum_currencies.message_prefix, ethereum_currencies.identifier,"
                        " ethereum_currencies.additional_EIPs "
                        " FROM ethereum_currencies "
                        " WHERE ethereum_currencies.name = :currency_name", use(currency.name));
                for (auto& eth_row : eth_rows) {
                    api::EthereumLikeNetworkParameters params;
                    params.ChainID = eth_row.get<std::string>(0);
                    params.XPUBVersion = hex::toByteArray(eth_row.get<std::string>(1));
                    params.MessagePrefix = eth_row.get<std::string>(2);
                    params.Identifier = eth_row.get<std::string>(3);
                    params.AdditionalEIPs = strings::split(eth_row.get<std::string>(4), ",");
                    currency.ethereumLikeNetworkParameters = params;
                }

                break;
            }
            case api::WalletType::RIPPLE: {
                rowset<row> ripple_rows = (sql.prepare << "SELECT ripple_currencies.xpub_version,"
                        " ripple_currencies.message_prefix, ripple_currencies.identifier,"
                        " ripple_currencies.additional_RIPs "
                        " FROM ripple_currencies "
                        " WHERE ripple_currencies.name = :currency_name", use(currency.name));
                for (auto& ripple_row : ripple_rows) {
                    api::RippleLikeNetworkParameters params;
                    params.XPUBVersion = hex::toByteArray(ripple_row.get<std::string>(0));
                    params.MessagePrefix = ripple_row.get<std::string>(1);
                    params.Identifier = ripple_row.get<std::string>(2);
                    params.AdditionalRIPs = strings::split(ripple_row.get<std::string>(3), ",");
                    currency.rippleLikeNetworkParameters = params;
                }

                break;
            }
            case api::WalletType::TEZOS: {
                rowset<row> tezos_rows = (sql.prepare << "SELECT tezos_currencies.xpub_version, tezos_currencies.implicit_prefix,"
                        " tezos_currencies.originated_prefix, tezos_currencies.message_prefix,"
                        " tezos_currencies.identifier, tezos_currencies.additional_TIPs"
                        " FROM tezos_currencies"
                        " WHERE tezos_currencies.name = :currency_name", use(currency.name));
                for (auto &tezos_row : tezos_rows) {
                    api::TezosLikeNetworkParameters params;
                    params.XPUBVersion = hex::toByteArray(tezos_row.get<std::string>(0));
                    params.ImplicitPrefix = hex::toByteArray(tezos_row.get<std::string>(1));
                    params.OriginatedPrefix = hex::toByteArray(tezos_row.get<std::string>(2));
                    params.MessagePrefix = tezos_row.get<std::string>(3);
                    params.Identifier = tezos_row.get<std::string>(4);
                    params.AdditionalTIPs = strings::split(tezos_row.get<std::string>(5), ",");
                    currency.tezosLikeNetworkParameters = params;
                }

                break;
            }
            case api::WalletType::MONERO:break;
            case api::WalletType::ALGORAND: {
                rowset<row> algorand_rows = (sql.prepare << "SELECT genesis_id, genesis_hash FROM algorand_currencies "
                                                           "WHERE name = :name"
                        , use(currency.name));
                for (auto& algorand_row : algorand_rows) {
                    api::AlgorandNetworkParameters params;
                    params.genesisID = algorand_row.get<std::string>(0);
                    params.genesisHash = algorand_row.get<std::string>(1);
                    currency.algorandNetworkParameters = params;
                }
                break;
            }
            case api::WalletType::STELLAR: {
                rowset<row> stellar_rows = (sql.prepare << "SELECT identifier, address_version, base_reserve,"
                                                           "base_fee, network_passphrase, additional_SEPs, "
                                                           "muxed_address_version "
                                                           "FROM stellar_currencies "
                                                           "WHERE name = :name"
                        , use(currency.name));
                for (auto& stellar_row : stellar_rows) {
                    api::StellarLikeNetworkParameters params;
                    params.Identifier = stellar_row.get<std::string>(0);
                    params.Version = hex::toByteArray(stellar_row.get<std::string>(1));
                    params.BaseFee = soci::get_number<int64_t>(stellar_row, 2);
                    params.BaseReserve = soci::get_number<int64_t>(stellar_row, 3);
                    params.NetworkPassphrase = stellar_row.get<std::string>(4);
                    params.AdditionalSEPs = strings::split(stellar_row.get<std::string>(5), ",");
                    params.MuxedVersion = hex::toByteArray(stellar_row.get<std::string>(6));
                    currency.stellarLikeNetworkParameters = params;
                }
                break;
            }
        }
        getAllUnits(sql, currency);
        currencies.push_back(currency);
    }
}

void ledger::core::CurrenciesDatabaseHelper::getAllUnits(soci::session &sql, ledger::core::api::Currency &currency) {

    rowset<row> rows = (sql.prepare <<
                                    "SELECT name, magnitude, symbol, code FROM units WHERE currency_name = :currency", use(currency.name));
    for (auto& row : rows) {
        api::CurrencyUnit unit;
        unit.name = row.get<std::string>(0);
        unit.numberOfDecimal = row.get<int32_t>(1);
        unit.symbol = row.get<std::string>(2);
        unit.code = row.get<std::string>(3);
        currency.units.push_back(unit);
    }
}

void
ledger::core::CurrenciesDatabaseHelper::insertUnits(soci::session &sql, const ledger::core::api::Currency &currency) {
    for (const auto& unit : currency.units) {
        int count;
        sql << "SELECT COUNT(*) FROM units WHERE name = :name AND currency_name = :cname", use(unit.name), use(currency.name), into(count);
        if (count == 0) {
            sql << "INSERT INTO units VALUES(:name, :magnitude, :symbol, :code, :currency_name)",
            use(unit.name), use(unit.numberOfDecimal), use(unit.symbol), use(unit.code), use(currency.name);
        }
    }
}

void ledger::core::CurrenciesDatabaseHelper::removeCurrency(soci::session &sql, const std::string &currencyName) {
    sql << "DELETE FROM currencies WHERE name = :name", use(currencyName);
}


<|start_filename|>core/src/wallet/stellar/database/StellarLikeOperationDatabaseHelper.cpp<|end_filename|>
/*
 *
 * StellarLikeOperationDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 20/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "StellarLikeOperationDatabaseHelper.hpp"
#include <database/PreparedStatement.hpp>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <api/BigInt.hpp>
#include <database/soci-backend-utils.h>
#include <debug/Benchmarker.h>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>
#include <wallet/stellar/database/StellarLikeTransactionDatabaseHelper.hpp>
#include <wallet/stellar/database/StellarLikeAssetDatabaseHelper.hpp>

using namespace soci;

namespace {
    using namespace ledger::core;
    // Transaction
    struct TransactionBinding {
        std::vector<std::string> uid;
        std::vector<std::string> hash;
        std::vector<std::string> sourceAccount;
        std::vector<std::string> sequence;
        std::vector<std::string> fee;
        std::vector<int> successful;
        std::vector<std::string> ledger;
        std::vector<std::string> memoType;
        std::vector<std::string> memo;

        void update(const stellar::Transaction& tx, const std::string& txUid) {
            const auto feePaid = tx.feePaid.toString();
            const auto seq = tx.sourceAccountSequence.toString();
            const auto success = tx.successful ? 1 : 0;
            
            uid.push_back(txUid);
            hash.push_back(tx.hash);
            sourceAccount.push_back(tx.sourceAccount);
            sequence.push_back(seq);
            fee.push_back(feePaid);
            successful.push_back(success);
            ledger.push_back(fmt::format("{}", tx.ledger));
            memoType.push_back(tx.memoType);
            memo.push_back(tx.memo);
        }

        void clear() {
            uid.clear();
            hash.clear();
            sourceAccount.clear();
            sequence.clear();
            fee.clear();
            successful.clear();
            ledger.clear();
            memoType.clear();
            memo.clear();
        }
    };
    const auto UPSERT_TRANSACTION = db::stmt<TransactionBinding>(
        "INSERT INTO stellar_transactions VALUES(:uid, :hash, :account, :sequence, :fee, :success, :ledger,"
        ":memo_type, :memo) "
        "ON CONFLICT DO NOTHING",
        [] (auto& s, auto&  b) {
            s, use(b.uid), use(b.hash), use(b.sourceAccount), use(b.sequence), use(b.fee),
            use(b.successful), use(b.ledger), use(b.memoType), use(b.memo);
        });

    // Assets
    struct AssetBinding {
        std::vector<std::string> uid;
        std::vector<std::string> type;
        std::vector<Option<std::string>> code;
        std::vector<Option<std::string>> issuer;

        void update(const stellar::Asset &asset) { 
            Option<std::string> assetCode;
            Option<std::string> assetIssuer;

            if (!asset.code.empty())
                assetCode = asset.code;
            if (!asset.issuer.empty())
                assetIssuer = asset.issuer;

            const auto assetUid = StellarLikeAssetDatabaseHelper::createAssetUid(asset.type, assetCode, assetIssuer);  
            uid.push_back(assetUid);
            type.push_back(asset.type);
            code.push_back(assetCode);
            issuer.push_back(assetIssuer);
        }

        void clear() {
            uid.clear();
            type.clear();
            code.clear();
            issuer.clear();
        }
    };
    const auto UPSERT_ASSERT = db::stmt<AssetBinding>(
        "INSERT INTO stellar_assets VALUES (:uid, :type, :code, :issuer) "
        "ON CONFLICT DO NOTHING",
        [] (auto& s, auto&  b) {
            s, use(b.uid), use(b.type), use(b.code), use(b.issuer);
        });

    // operations
    struct StellarOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> id;
        std::vector<std::chrono::system_clock::time_point> createdAt;
        std::vector<std::string> assetUid;
        std::vector<Option<std::string>> sourceAssetUid;
        std::vector<std::string> amount;
        std::vector<Option<std::string>> srcAmount;
        std::vector<std::string> from;
        std::vector<std::string> to;
        std::vector<int> type;

        void update(const std::string &accountUid, const std::string& currencyName, const stellar::Operation &op) {
            auto opUid = StellarLikeTransactionDatabaseHelper::createOperationUid(accountUid, op.id);
            auto hash = StellarLikeTransactionDatabaseHelper::createTransactionUid(currencyName, op.transactionHash);
            auto auid = StellarLikeAssetDatabaseHelper::createAssetUid(op.asset);
            auto sourceAuid = op.sourceAsset.map<std::string>([] (const stellar::Asset& asset) {
                return StellarLikeAssetDatabaseHelper::createAssetUid(asset);
            });
            auto opAmount = op.amount.toString();
            auto opSourceAmount = op.sourceAmount.map<std::string>([] (const BigInt& b) {
                return b.toString();
            });
            int opType = static_cast<int>(op.type);
            
            uid.push_back(opUid);
            txUid.push_back(hash);
            id.push_back(op.id);
            createdAt.push_back(op.createdAt);
            assetUid.push_back(auid);
            sourceAssetUid.push_back(sourceAuid);
            amount.push_back(opAmount);
            srcAmount.push_back(opSourceAmount);
            from.push_back(op.from);
            to.push_back(op.to);
            type.push_back(opType);
        }

        void clear() {
            uid.clear();
            txUid.clear();
            id.clear();
            createdAt.clear();
            assetUid.clear();
            sourceAssetUid.clear();
            amount.clear();
            srcAmount.clear();
            from.clear();
            to.clear();
            type.clear();
        }
    };
    const auto UPSERT_STELLAR_OPERATION = db::stmt<StellarOperationBinding>(
        "INSERT INTO stellar_operations VALUES("
        ":uid, :tx_uid, :hash, :time, :asset_uid, :src_asset_uid, :amount, :src_amount, :from, :to, :type"
        ") "
        "ON CONFLICT DO NOTHING",
        [] (auto& s, auto&  b) {
            s, 
            use(b.uid), 
            use(b.txUid), 
            use(b.id), 
            use(b.createdAt), 
            use(b.assetUid), 
            use(b.sourceAssetUid),
            use(b.amount), 
            use(b.srcAmount), 
            use(b.from), 
            use(b.to), 
            use(b.type);
        });
    

    // stellar_account_operations
    struct StellarAccountOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> opUid;

        void update(const Operation &operation) {
            auto& operationValue = operation.stellarOperation.getValue().operation;
            auto stellarOpId = StellarLikeTransactionDatabaseHelper::createOperationUid(operation.accountUid, operationValue.id);       
            uid.push_back(operation.uid);
            opUid.push_back(stellarOpId);
        }

        void clear() {
            uid.clear();
            opUid.clear();
        }
    };
    const auto UPSERT_STELLAR_ACCOUNT_OPERATION = db::stmt<StellarAccountOperationBinding>(
        "INSERT INTO stellar_account_operations VALUES(:uid, :op_uid) "
        "ON CONFLICT DO NOTHING",
        [] (auto& s, auto&  b) {
            s, 
            use(b.uid), 
            use(b.opUid);
        });


}
namespace ledger {
    namespace core {

        void StellarLikeOperationDatabaseHelper::bulkInsert(soci::session &sql,
                const std::vector<Operation> &operations) {
            if (operations.empty())
                return;
            Benchmarker rawInsert("raw_db_insert_stellar", nullptr);
            rawInsert.start();
            PreparedStatement<OperationBinding> operationStmt;
            PreparedStatement<BlockBinding> blockStmt;
            PreparedStatement<TransactionBinding> transactionStmt;
            PreparedStatement<AssetBinding> assetStmt;
            PreparedStatement<StellarOperationBinding> stellarOperationStmt;
            PreparedStatement<StellarAccountOperationBinding> stellarAccountOperationStmt;
            
            BulkInsertDatabaseHelper::UPSERT_OPERATION(sql, operationStmt);
            BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
            UPSERT_TRANSACTION(sql, transactionStmt);
            UPSERT_ASSERT(sql, assetStmt);
            UPSERT_STELLAR_OPERATION(sql, stellarOperationStmt);
            UPSERT_STELLAR_ACCOUNT_OPERATION(sql, stellarAccountOperationStmt);
                   
            for (const auto& op : operations) {
                if (op.block.hasValue()) {
                    blockStmt.bindings.update(op.block.getValue());
                }
                // Upsert operation
                operationStmt.bindings.update(op);
                // Upsert transaction
                auto& tx = op.stellarOperation.getValue().transaction;
                const auto txUid = StellarLikeTransactionDatabaseHelper::createTransactionUid(op.currencyName, tx.hash);
                transactionStmt.bindings.update(tx, txUid); 
                // Upsert assets
                auto& stOp = op.stellarOperation.getValue().operation;
                assetStmt.bindings.update(stOp.asset);
                if (stOp.sourceAsset)  {
                    assetStmt.bindings.update(stOp.sourceAsset.getValue());
                }
                // Upsert stellar operations
                stellarOperationStmt.bindings.update(op.accountUid, op.currencyName, stOp);
                // Upsert stellar operations
                stellarAccountOperationStmt.bindings.update(op);
            }
            // block
            if (!blockStmt.bindings.uid.empty())
                blockStmt.execute();
            // transactions
            transactionStmt.execute();
            // assets
            assetStmt.execute();
            // operations
            operationStmt.execute();
            // stellar operations
            stellarOperationStmt.execute(); 
            //stellar accounts operations
            stellarAccountOperationStmt.execute();
            
            rawInsert.stop();
        }
    }
}

<|start_filename|>core/test/integration/transactions/ethereum_transaction_tests.cpp<|end_filename|>
/*
 *
 * ethereum_transaction_tests
 *
 * Created by <NAME> on 14/07/2018.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2018 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "../BaseFixture.h"
#include "../../fixtures/eth_xpub_fixtures.h"
#include <api/KeychainEngines.hpp>
#include "transaction_test_helper.h"
#include <utils/hex.h>
#include <utils/DateUtils.hpp>
#include <wallet/ethereum/database/EthereumLikeAccountDatabaseHelper.h>
#include <wallet/ethereum/api_impl/EthereumLikeTransactionApi.h>
#include <wallet/currencies.hpp>
#include <iostream>
using namespace std;

struct EthereumMakeTransaction : public EthereumMakeBaseTransaction {
    void SetUpConfig() override {
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::BLOCKCHAIN_EXPLORER_VERSION,"v2");
        testData.configuration = configuration;
        testData.walletName = "my_wallet";
        testData.currencyName = "ethereum";
        testData.inflate_eth = ledger::testing::eth_xpub::inflate;
    }
};

TEST_F(EthereumMakeTransaction, CreateStandardWithOneOutput) {
    auto builder = tx_builder();
    auto balance = uv::wait(account->getBalance());
    auto fromDate = "2018-01-01T13:38:23Z";
    auto toDate = DateUtils::toJSON(DateUtils::now());
    auto balanceHistory = uv::wait(account->getBalanceHistory(fromDate, toDate, api::TimePeriod::MONTH));

    EXPECT_EQ(balanceHistory[balanceHistory.size() - 1]->toLong(), balance->toLong());

    builder->setGasPrice(api::Amount::fromLong(currency, 200000));
    builder->setGasLimit(api::Amount::fromLong(currency, 20000000));
    builder->sendToAddress(api::Amount::fromLong(currency, 200000), "0xfb98bdd04d82648f25e67041d6e27a866bec0b47");
    auto f = builder->build();
    EXPECT_THROW(uv::wait(f), ledger::core::Exception);
    builder->setGasPrice(api::Amount::fromLong(currency, 0));
    builder->setGasLimit(api::Amount::fromLong(currency, 0));
    builder->sendToAddress(api::Amount::fromLong(currency, 0), "0xfb98bdd04d82648f25e67041d6e27a866bec0b47");
    f = builder->build();
    auto tx = uv::wait(f);
    auto addressList = uv::wait(account->getFreshPublicAddresses());
    EXPECT_EQ(addressList.size(), 1);
    EXPECT_EQ(addressList[0]->toString(), tx->getSender()->toEIP55());
    auto serializedTx = tx->serialize();
    auto parsedTx = EthereumLikeTransactionBuilder::parseRawUnsignedTransaction(wallet->getCurrency(), serializedTx);
    auto serializedParsedTx = parsedTx->serialize();
    EXPECT_EQ(serializedTx, serializedParsedTx);

    auto date = "2000-03-27T09:10:22Z";
    auto formatedDate = DateUtils::fromJSON(date);

    //Delete account
    auto code = uv::wait(wallet->eraseDataSince(formatedDate));
    EXPECT_EQ(code, api::ErrorCode::FUTURE_WAS_SUCCESSFULL);

    //Check if account was successfully deleted
    auto newAccountCount = uv::wait(wallet->getAccountCount());
    EXPECT_EQ(newAccountCount, 0);
    {
        soci::session sql(pool->getDatabaseSessionPool()->getPool());
        EthereumLikeAccountDatabaseEntry entry;
        auto result = EthereumLikeAccountDatabaseHelper::queryAccount(sql, account->getAccountUid(), entry);
        EXPECT_EQ(result, false);
    }

    //Delete wallet
    auto walletCode = uv::wait(pool->eraseDataSince(formatedDate));
    EXPECT_EQ(walletCode, api::ErrorCode::FUTURE_WAS_SUCCESSFULL);

    //Check if wallet was successfully deleted
    auto walletCount = uv::wait(pool->getWalletCount());
    EXPECT_EQ(walletCount, 0);

}

TEST_F(EthereumMakeTransaction, ParseUnsignedRawTransaction) {
    //Tx hash 4858a0a3d5f1de0c0f5729f25c3501bda946093aed07f842e53a90ac65d66f70
    auto strTx = "e880850165a0bc0083030d4094a49386fff4e0dd767b145e75d92f7fba8854553e8301e0f380018080";
    auto tx = api::EthereumLikeTransactionBuilder::parseRawUnsignedTransaction(ledger::core::currencies::ETHEREUM, hex::toByteArray(strTx));
    EXPECT_EQ(hex::toString(tx->serialize()), strTx);
}

TEST_F(EthereumMakeTransaction, ParseSignedRawTransactionAndBuildETHBlockchainExplorerTx) {
    //Tx hash 71df2bc7d800a05a97b742aa5313e5040003ba3ddb84546ceb2a026e5a97fca5 (Ropsten)
    auto txHash = "71df2bc7d800a05a97b742aa5313e5040003ba3ddb84546ceb2a026e5a97fca5";
    auto rawTx = "f8678183843b9aca0083030d4094ac6603e97e774cd34603293b69bbbb1980aceeaa8201008029a0c4cbbdbdecb855505cd344dd1b800e1cce731ebc0f9a1559f0bc9da5ab20daa6a01adbe3788efac90885117f23bd8cfaa859e4f1afbd1416087b4172aca3a0423f";
    auto ethLikeBCTx = EthereumLikeAccount::getETHLikeBlockchainExplorerTxFromRawTx(account, txHash, hex::toByteArray(rawTx));
    EXPECT_EQ(ethLikeBCTx.status, 1);
    // For test purposes sender is account's address (real sender is 0xd0ec064cff693453ef4595aa555ce65244b212a5)
    auto sender = account->getKeychain()->getAddress()->toEIP55();
    auto receiver = "0xAc6603e97e774Cd34603293b69bBBB1980acEeaA";
    EXPECT_EQ(ethLikeBCTx.sender,  sender);
    EXPECT_EQ(ethLikeBCTx.receiver, receiver);
    EXPECT_EQ(ethLikeBCTx.value.toString(), "256");
    EXPECT_EQ(ethLikeBCTx.gasPrice.toString(), "1000000000");
    EXPECT_EQ(ethLikeBCTx.gasLimit.toString(), "200000");
    EXPECT_EQ(ethLikeBCTx.inputData.size(), 0);
    EXPECT_EQ(ethLikeBCTx.erc20Transactions.size(), 0);
}

TEST_F(EthereumMakeTransaction, ParseSignedRawERC20TransactionAndBuildETHBlockchainExplorerTx) {
    //Tx hash 96c71eac2350d498ba89863465c1c362fc3ec323f33b5eb65ee60da5cb75420a (Ropsten)
    auto txHash = "96c71eac2350d498ba89863465c1c362fc3ec323f33b5eb65ee60da5cb75420a";
    auto rawTx = "f8aa8180843b9aca0083030d4094dfb287530fd4c1e59456de82a84e4aae7c250ec180b844a9059cbb000000000000000000000000456b8e57f5e096b9fff45bdbd58b8ce90d830ff900000000000000000000000000000000000000000000001b1ae4d6e2ef50000029a0fde02005f6bb452269dabb128ad2c9a44090f6f160fc7be8ef511224524fc5b9a06e07d22f5e92435dfcfc633f85825291c118d0f05ef46ea71774f306325d7ea2";
    auto ethLikeBCTx = EthereumLikeAccount::getETHLikeBlockchainExplorerTxFromRawTx(account, txHash, hex::toByteArray(rawTx));
    EXPECT_EQ(ethLikeBCTx.status, 1);
    // For test purposes sender is account's address (real sender is 0xd0ec064cff693453ef4595aa555ce65244b212a5)
    auto sender = account->getKeychain()->getAddress()->toEIP55();
    auto receiver = "0x456b8e57F5e096B9Fff45BDbD58B8CE90d830Ff9";
    auto contractAddress = "0xDFb287530FD4c1e59456DE82a84e4aae7C250Ec1";
    EXPECT_EQ(ethLikeBCTx.sender, sender);
    EXPECT_EQ(ethLikeBCTx.receiver, contractAddress);
    EXPECT_EQ(ethLikeBCTx.value, BigInt::ZERO);
    EXPECT_EQ(ethLikeBCTx.gasPrice.toString(), "1000000000");
    EXPECT_EQ(ethLikeBCTx.gasLimit.toString(), "200000");
    EXPECT_EQ(hex::toString(ethLikeBCTx.inputData), "a9059cbb000000000000000000000000456b8e57f5e096b9fff45bdbd58b8ce90d830ff900000000000000000000000000000000000000000000001b1ae4d6e2ef500000");
    EXPECT_EQ(ethLikeBCTx.erc20Transactions.size(), 1);
    EXPECT_EQ(ethLikeBCTx.erc20Transactions[0].value.toString(), "500000000000000000000");
    EXPECT_EQ(ethLikeBCTx.erc20Transactions[0].from, sender);
    EXPECT_EQ(ethLikeBCTx.erc20Transactions[0].to, receiver);
    EXPECT_EQ(ethLikeBCTx.erc20Transactions[0].contractAddress, contractAddress);
    EXPECT_EQ(ethLikeBCTx.erc20Transactions[0].type, api::OperationType::SEND);
}

<|start_filename|>core/test/fixtures/http_cache_EthereumLikeWalletSynchronization_MediumXpubSynchronization_4.h<|end_filename|>
namespace HTTP_CACHE_EthereumLikeWalletSynchronization_MediumXpubSynchronization_4 {
const std::string URL = R"(https://explorers.api.live.ledger.com/blockchain/v3/eth/addresses/0xabf06640f8ca8fC5e0Ed471b10BeFCDf65A33e43/balance)";
const std::string BODY= R"([{"address":"0xabf06640f8ca8fC5e0Ed471b10BeFCDf65A33e43","balance":8598521241423646,"valid":true,"error":false}])";
}


<|start_filename|>core/test/fixtures/eth_xpub_fixtures.h<|end_filename|>
// This file was GENERATED by command:
//     generate_fixtures.py
// DO NOT EDIT BY HAND!!!
#ifndef LEDGER_FIXTURES_ETH_XPUB
#define LEDGER_FIXTURES_ETH_XPUB
#include <gtest/gtest.h>
#include <UvThreadDispatcher.hpp>
#include <src/database/DatabaseSessionPool.hpp>
#include <NativePathResolver.hpp>
#include <unordered_set>
#include <src/wallet/pool/WalletPool.hpp>
#include <CoutLogPrinter.hpp>
#include <src/api/DynamicObject.hpp>
#include <wallet/common/CurrencyBuilder.hpp>
#include <wallet/ethereum/explorers/api/EthereumLikeTransactionParser.hpp>
#include <wallet/ethereum/EthereumLikeWallet.h>
#include <wallet/ethereum/database/EthereumLikeWalletDatabase.h>
#include <wallet/ethereum/database/EthereumLikeTransactionDatabaseHelper.h>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <wallet/pool/database/PoolDatabaseHelper.hpp>
#include <utils/JSONUtils.h>
#include <wallet/ethereum/EthereumLikeAccount.h>
#include <api/EthereumLikeOperation.hpp>
#include <api/EthereumLikeTransaction.hpp>
#include <api/BigInt.hpp>
#include <CppHttpLibClient.hpp>
#include <events/LambdaEventReceiver.hpp>
#include <soci.h>
#include <api/Account.hpp>
#include <api/EthereumLikeAccount.hpp>

namespace ledger {
	namespace testing {
		namespace eth_xpub {
			extern core::api::ExtendedKeyAccountCreationInfo XPUB_INFO;
			extern const std::string TX_1;
			extern const std::string TX_2;
			extern const std::string TX_3;
			extern const std::string TX_4;
			extern const std::string TX_5;
			extern const std::string TX_6;

			std::shared_ptr<core::EthereumLikeAccount> inflate(const std::shared_ptr<core::WalletPool>& pool, const std::shared_ptr<core::AbstractWallet>& wallet);
		}
	}
}

#endif // LEDGER_FIXTURES_ETH_XPUB


<|start_filename|>core/test/coin-integration/stellar/transaction_tests.cpp<|end_filename|>
/*
 *
 * transaction_tests.cpp
 * ledger-core
 *
 * Created by <NAME> on 27/08/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "StellarFixture.hpp"
#include <wallet/stellar/transaction_builders/StellarLikeTransactionBuilder.hpp>
#include <wallet/currencies.hpp>
#include <api_impl/BigIntImpl.hpp>

TEST_F(StellarFixture, DISABLED_PaymentTransaction) {
    auto pool = newPool();
    auto wallet = newWallet(pool, "my_wallet_stellar_transaction", "stellar", api::DynamicObject::newInstance());
    auto info = uv::wait(wallet->getNextAccountCreationInfo());
    auto account = newAccount(wallet, 0, defaultAccount());
    auto bus = account->synchronize();
    bus->subscribe(getTestExecutionContext(),
                   make_receiver([=](const std::shared_ptr<api::Event> &event) {
                       fmt::print("Received event {}\n", api::to_string(event->getCode()));
                       if (event->getCode() == api::EventCode::SYNCHRONIZATION_STARTED)
                           return;
                       EXPECT_NE(event->getCode(), api::EventCode::SYNCHRONIZATION_FAILED);
                       EXPECT_EQ(event->getCode(),
                                 api::EventCode::SYNCHRONIZATION_SUCCEED);
                       getTestExecutionContext()->stop();
                   }));
    EXPECT_EQ(bus, account->synchronize());
    getTestExecutionContext()->waitUntilStopped();
    auto address = *uv::wait(account->getFreshPublicAddresses()).begin();
    auto signature = hex::toByteArray("3045022100B2B31575F8536B284410D01217F688BE3A9FAF4BA0BA3A9093F983E40D630EC7022022A7A25B01403CFF0D00B3B853D230F8E96FF832B15D4CCC75203CB65896A2D5");
    auto builder = std::dynamic_pointer_cast<StellarLikeTransactionBuilder>(account->buildTransaction());
    auto sequence = uv::wait(account->getSequence());
    auto fees =  100;//Disabled until nodes can handle this request ==> uv::wait(account->getFeeStats()).modeAcceptedFee;
    builder->setSequence(api::BigInt::fromLong(sequence.toInt64()));
    builder->addNativePayment("GA5IHE27VP64IR2JVVGQILN4JX43LFCC6MS2E6LAKGP3UULK3OFFBJXR", api::Amount::fromLong(wallet->getCurrency(), 20000000));
    builder->setBaseFee( api::Amount::fromLong(wallet->getCurrency(), fees));
    auto tx = uv::wait(builder->build());
    tx->putSignature(signature, address);
    auto wrappedEnvelope = std::dynamic_pointer_cast<StellarLikeTransaction>(tx)->envelope();
    const auto& envelope = boost::get<stellar::xdr::TransactionV1Envelope>(wrappedEnvelope.content);
    EXPECT_EQ(envelope.signatures.size() , 1);
    EXPECT_EQ(envelope.tx.sourceAccount.type, stellar::xdr::CryptoKeyType::KEY_TYPE_ED25519);
    auto accountPubKey = account->getKeychain()->getAddress()->toPublicKey();
    auto sourceAccount = boost::get<stellar::xdr::uint256>(envelope.tx.sourceAccount.content);
    std::vector<uint8_t> envelopeSourcePubKey(sourceAccount.begin(), sourceAccount.end());
    EXPECT_EQ(accountPubKey, envelopeSourcePubKey);
    EXPECT_TRUE(envelope.tx.seqNum >= 98448948301135874L);
    EXPECT_TRUE(envelope.tx.fee == fees);
    EXPECT_TRUE(envelope.tx.operations.size() == 1);
    EXPECT_EQ(envelope.tx.memo.type, stellar::xdr::MemoType::MEMO_NONE);
    EXPECT_EQ(envelope.signatures.front().signature, signature);
    fmt::print("{}\n", hex::toString(tx->toRawTransaction()));
}

TEST_F(StellarFixture, DISABLED_ParseRawTransaction) {
    auto strTx = "00000000a1083d11720853a2c476a07e29b64e0f9eb2ff894f1e485628faa7b63de77a4f0"
                 "0000064015dc2cc000000030000000000000000000000010000000000000001000000003a"
                 "83935fabfdc44749ad4d042dbc4df9b59442f325a27960519fba516adb8a5000000000000"
                 "00000000000000000000000000000";

    auto tx = api::StellarLikeTransactionBuilder::parseRawTransaction(ledger::core::currencies::STELLAR, hex::toByteArray(strTx));

    EXPECT_EQ(tx->getSourceAccount()->toString(), "<KEY>");
    EXPECT_EQ(tx->getSourceAccountSequence()->compare(api::BigInt::fromLong(98448948301135875L)), 0);
    EXPECT_EQ(tx->getFee()->toLong(), 100L);

    EXPECT_EQ(hex::toString(tx->toRawTransaction()), strTx);
}

TEST_F(StellarFixture, DISABLED_ParseSignatureBase) {
    auto strTx = "7ac33997544e3175d266bd022439b22cdb16508c01163f26e5cb2a3e1045a9790000000200"
                 "000000a1083d11720853a2c476a07e29b64e0f9eb2ff894f1e485628faa7b63de77a4f0000"
                 "0064015dc2cc000000030000000000000000000000010000000000000001000000003a8393"
                 "5fabfdc44749ad4d042dbc4df9b59442f325a27960519fba516adb8a500000000000000000"
                 "0000000000000000";

    auto tx = api::StellarLikeTransactionBuilder::parseSignatureBase(ledger::core::currencies::STELLAR, hex::toByteArray(strTx));

    EXPECT_EQ(tx->getSourceAccount()->toString(), "<KEY>");
    EXPECT_EQ(tx->getSourceAccountSequence()->compare(api::BigInt::fromLong(98448948301135875L)), 0);
    EXPECT_EQ(tx->getFee()->toLong(), 100L);

    EXPECT_EQ(hex::toString(tx->toSignatureBase()), strTx);
}


<|start_filename|>core/src/api/EventCode.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from events.djinni

#ifndef DJINNI_GENERATED_EVENTCODE_HPP
#define DJINNI_GENERATED_EVENTCODE_HPP

#include <functional>
#include <iostream>
#include <string>
#ifndef LIBCORE_EXPORT
    #if defined(_MSC_VER)
       #include <libcore_export.h>
    #else
       #define LIBCORE_EXPORT
    #endif
#endif

namespace ledger { namespace core { namespace api {

enum class EventCode : int {
    /** An unknown event code. */
    UNDEFINED,
    /** [DEPRECATED] A new operation event. (this event is deprecated since 4.0) */
    NEW_OPERATION,
    /** Emitted once a batch of operation has been inserted/updated in database */
    UPDATE_OPERATIONS,
    /** A new block event. (this event is deprecated since 4.0) */
    NEW_BLOCK,
    /** A deleted operation event. */
    DELETED_OPERATION,
    /** Event emitted when a wallet synchronization started. */
    SYNCHRONIZATION_STARTED,
    /** Event emitted when a wallet synchronization failed. */
    SYNCHRONIZATION_FAILED,
    /** Event emitted when a wallet synchronization succeeded. */
    SYNCHRONIZATION_SUCCEED,
    /** Event emitted when a wallet synchronization succeeded on the previously empty account. */
    SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT,
    /** [DEPRECATED] A new er20 operation is available. (this event is deprecated since 4.0) */
    NEW_ERC20_OPERATION,
    /** Emitted each time a batch of ERC20 is inserted in database */
    UPDATE_ERC20_OPERATIONS,
};
LIBCORE_EXPORT  std::string to_string(const EventCode& eventCode);
LIBCORE_EXPORT  std::ostream &operator<<(std::ostream &os, const EventCode &o);

} } }  // namespace ledger::core::api

namespace std {

template <>
struct hash<::ledger::core::api::EventCode> {
    size_t operator()(::ledger::core::api::EventCode type) const {
        return std::hash<int>()(static_cast<int>(type));
    }
};

}  // namespace std
#endif //DJINNI_GENERATED_EVENTCODE_HPP


<|start_filename|>core/test/database/BaseFixture.h<|end_filename|>
/*
 *
 * BaseFixture.h
 * ledger-core
 *
 * Created by <NAME> on 21/09/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_BASEFIXTURE_H
#define LEDGER_CORE_BASEFIXTURE_H

#include <gtest/gtest.h>
#include <UvThreadDispatcher.hpp>
#include <src/database/DatabaseSessionPool.hpp>
#include <NativePathResolver.hpp>
#include <unordered_set>
#include <src/wallet/pool/WalletPool.hpp>
#include <CoutLogPrinter.hpp>
#include <src/api/DynamicObject.hpp>
#include <wallet/common/CurrencyBuilder.hpp>
#include <wallet/bitcoin/BitcoinLikeWallet.hpp>
#include <wallet/bitcoin/database/BitcoinLikeWalletDatabase.h>
#include <wallet/bitcoin/database/BitcoinLikeTransactionDatabaseHelper.h>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <wallet/pool/database/PoolDatabaseHelper.hpp>
#include <utils/JSONUtils.h>
#include <wallet/bitcoin/explorers/api/TransactionParser.hpp>
#include <wallet/bitcoin/BitcoinLikeAccount.hpp>
#include <api/BitcoinLikeOperation.hpp>
#include <api/BitcoinLikeTransaction.hpp>
#include <api/BitcoinLikeInput.hpp>
#include <api/BitcoinLikeOutput.hpp>
#include <api/BigInt.hpp>
#include <CppHttpLibClient.hpp>
#include <events/LambdaEventReceiver.hpp>
#include <soci.h>
#include <api/Account.hpp>
#include <api/BitcoinLikeAccount.hpp>

using namespace ledger::core; // Only do that for testing
using namespace ledger::core::test;

extern api::ExtendedKeyAccountCreationInfo P2PKH_MEDIUM_XPUB_INFO;
extern api::AccountCreationInfo P2PKH_MEDIUM_KEYS_INFO;
extern api::ExtendedKeyAccountCreationInfo P2PKH_BIG_XPUB_INFO;
extern const std::string TX_1;
extern const std::string TX_2;
extern const std::string TX_3;
extern const std::string TX_4;


class BaseFixture : public ::testing::Test {
public:
    void SetUp() override;
    void TearDown() override;
    std::shared_ptr<WalletPool> newDefaultPool(std::string poolName = "my_ppol");
    void createWallet(const std::shared_ptr<WalletPool>& pool,
                      const std::string& walletName,
                      const std::string& currencyName,
                      const std::shared_ptr<api::DynamicObject> &configuration);

    void createAccount(const std::shared_ptr<WalletPool>& pool, const std::string& walletName, int32_t index);
    BitcoinLikeWalletDatabase newBitcoinAccount(const std::shared_ptr<WalletPool>& pool,
                                                const std::string& walletName,
                                                const std::string& currencyName,
                                                const std::shared_ptr<api::DynamicObject> &configuration,
                                                int32_t index,
                                                const std::string& xpub);
    std::shared_ptr<BitcoinLikeAccount> createBitcoinLikeAccount(const std::shared_ptr<AbstractWallet>& wallet,
                                                                int32_t index,
                                                                const api::AccountCreationInfo &info
    );
    std::shared_ptr<BitcoinLikeAccount> createBitcoinLikeAccount(const std::shared_ptr<AbstractWallet>& wallet,
                                                                 int32_t index,
                                                                 const api::ExtendedKeyAccountCreationInfo& info
    );

    std::shared_ptr<uv::UvThreadDispatcher> dispatcher;
    std::shared_ptr<NativePathResolver> resolver;
    std::shared_ptr<DatabaseBackend> backend;
    std::shared_ptr<CoutLogPrinter> printer;
    std::shared_ptr<CppHttpLibClient> http;
};

#endif //LEDGER_CORE_BASEFIXTURE_H


<|start_filename|>core/src/wallet/algorand/database/AlgorandTransactionDatabaseHelper.hpp<|end_filename|>
/*
 * AlgorandTransactionDatabaseHelper
 *
 * Created by <NAME> on 18/05/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_ALGORANDTRANSACTIONDATABASEHELPER_H
#define LEDGER_CORE_ALGORANDTRANSACTIONDATABASEHELPER_H

#include "../model/transactions/AlgorandTransaction.hpp"
#include <math/BaseConverter.hpp>
#include <boost-optional.h>
#include <database/soci-number.h>
#include <chrono>

namespace ledger {
namespace core {
namespace algorand {

    static constexpr auto COL_TX_HASH = 1;
    static constexpr auto COL_TX_TYPE = 2;
    static constexpr auto COL_TX_ROUND = 3;
    static constexpr auto COL_TX_TIMESTAMP = 4;
    static constexpr auto COL_TX_FIRST_VALID = 5;
    static constexpr auto COL_TX_LAST_VALID = 6;
    static constexpr auto COL_TX_GENESIS_ID = 7;
    static constexpr auto COL_TX_GENESIS_HASH = 8;
    static constexpr auto COL_TX_SENDER = 9;
    static constexpr auto COL_TX_FEE = 10;
    static constexpr auto COL_TX_NOTE = 11;
    static constexpr auto COL_TX_GROUP = 12;
    static constexpr auto COL_TX_LEASE = 13;
    static constexpr auto COL_TX_SENDER_REWARDS = 14;
    static constexpr auto COL_TX_RECEIVER_REWARDS = 15;
    static constexpr auto COL_TX_CLOSE_REWARDS = 16;

    static constexpr auto COL_TX_PAY_AMOUNT = 17;
    static constexpr auto COL_TX_PAY_RECEIVER_ADDRESS = 18;
    static constexpr auto COL_TX_PAY_CLOSE_ADDRESS = 19;
    static constexpr auto COL_TX_PAY_CLOSE_AMOUNT = 20;

    static constexpr auto COL_TX_KEYREG_NONPART = 21;
    static constexpr auto COL_TX_KEYREG_SELECTION_PK = 22;
    static constexpr auto COL_TX_KEYREG_VOTE_PK = 23;
    static constexpr auto COL_TX_KEYREG_VOTE_KEY_DILUTION = 24;
    static constexpr auto COL_TX_KEYREG_VOTE_FIRST = 25;
    static constexpr auto COL_TX_KEYREG_VOTE_LAST = 26;

    static constexpr auto COL_TX_ACFG_ASSET_ID = 27;
    static constexpr auto COL_TX_ACFG_ASSET_NAME = 28;
    static constexpr auto COL_TX_ACFG_UNIT_NAME = 29;
    static constexpr auto COL_TX_ACFG_TOTAL = 30;
    static constexpr auto COL_TX_ACFG_DECIMALS = 31;
    static constexpr auto COL_TX_ACFG_DEFAULT_FROZEN = 32;
    static constexpr auto COL_TX_ACFG_CREATOR_ADDRESS = 33;
    static constexpr auto COL_TX_ACFG_MANAGER_ADDRESS = 34;
    static constexpr auto COL_TX_ACFG_RESERVE_ADDRESS = 35;
    static constexpr auto COL_TX_ACFG_FREEZE_ADDRESS = 36;
    static constexpr auto COL_TX_ACFG_CLAWBACK_ADDRESS = 37;
    static constexpr auto COL_TX_ACFG_METADATA_HASH = 38;
    static constexpr auto COL_TX_ACFG_URL = 39;

    static constexpr auto COL_TX_AXFER_ASSET_ID = 40;
    static constexpr auto COL_TX_AXFER_ASSET_AMOUNT = 41;
    static constexpr auto COL_TX_AXFER_RECEIVER_ADDRESS = 42;
    static constexpr auto COL_TX_AXFER_CLOSE_ADDRESS = 43;
    static constexpr auto COL_TX_AXFER_CLOSE_AMOUNT = 44;
    static constexpr auto COL_TX_AXFER_SENDER_ADDRESS = 45;

    static constexpr auto COL_TX_AFRZ_ASSET_ID = 46;
    static constexpr auto COL_TX_AFRZ_FROZEN = 47;
    static constexpr auto COL_TX_AFRZ_FROZEN_ADDRESS = 48;


    // Helpers to deal with Option<> types,
    // because SOCI only understands boost::optional<>...

    template<class Raw>
    static auto optionalValue(Option<Raw> opt) {
        return opt.hasValue() ? *opt : boost::optional<Raw>();
    }

    template<class Raw, class Transformed>
    static auto optionalValueWithTransform(Option<Raw> opt, const std::function<Transformed(Raw)> & transform) {
        return opt.hasValue() ? transform(*opt) : boost::optional<Transformed>();
    }

    static std::string getString(const soci::row & row, const int32_t colId) {
        return row.get<std::string>(colId);
    }

    static Option<std::string> getOptionalString(const soci::row & row, const int32_t colId) {
        return row.get_indicator(colId) != soci::i_null ? Option<std::string>(row.get<std::string>(colId)) : Option<std::string>::NONE;
    }

    template<class Transformed>
    static Option<Transformed> getOptionalStringWithTransform(const soci::row & row, const int32_t colId, const std::function<Transformed(std::string)> & transform) {
        return row.get_indicator(colId) != soci::i_null ? Option<Transformed>(transform(row.get<std::string>(colId))) : Option<Transformed>::NONE;
    }

    static uint64_t getNumber(const soci::row & row, const int32_t colId) {
        return soci::get_number<uint64_t>(row, colId);
    }

    static Option<uint64_t> getOptionalNumber(const soci::row & row, const int32_t colId) {
        return row.get_indicator(colId) != soci::i_null ? Option<uint64_t>(soci::get_number<uint64_t>(row, colId)) : Option<uint64_t>::NONE;
    }

    template<class Transformed>
    static Option<Transformed> getOptionalNumberWithTransform(const soci::row & row, const int32_t colId, const std::function<Transformed(uint64_t)> & transform) {
        return row.get_indicator(colId) != soci::i_null ? Option<Transformed>(transform(soci::get_number<uint64_t>(row, colId))) : Option<Transformed>::NONE;
    }

    static const auto numToBool = [] (const uint64_t& num) { return !! num; };
    static const auto boolToNum = [] (const bool& b) { return static_cast<int32_t>(b); };
    static const auto stringToAddr = [] (const std::string& addr) { return Address(addr); };
    static const auto addrToString = [] (const Address& addr) { return addr.toString(); };
    static const auto b64toBytes = [] (const std::string& b64) {
        auto bytes = std::vector<uint8_t>();
        BaseConverter::decode(b64, BaseConverter::BASE64_RFC4648, bytes);
        return bytes;
    };
    static const auto bytesToB64 = [] (const std::vector<uint8_t>& bytes) {
        return BaseConverter::encode(bytes, BaseConverter::BASE64_RFC4648);
    };


    class TransactionDatabaseHelper {

    public:

        static bool transactionExists(soci::session & sql,
                                      const std::string & txUid);

        static bool getTransactionByHash(soci::session & sql,
                                         const std::string & hash,
                                         model::Transaction & tx);

        static std::string putTransaction(soci::session & sql,
                                          const std::string & accountUid,
                                          const model::Transaction & tx);

        static void deleteAllTransactions(soci::session& sql);

        static std::vector<model::Transaction> queryAssetTransferTransactionsInvolving(
                soci::session& sql,
                uint64_t assetId,
                const std::string& address);

        static std::vector<model::Transaction> queryTransactionsInvolving(
                soci::session& sql,
                const std::string& address);

        static void eraseDataSince(
                soci::session &sql,
                const std::string &accountUid,
                const std::chrono::system_clock::time_point & date);

    private:
        static std::vector<model::Transaction> query(
               const soci::rowset<soci::row>& rows);
    };

} // namespace algorand
} // namespace core
} // namespace ledger

#endif // LEDGER_CORE_ALGORANDTRANSACTIONDATABASEHELPER_H


<|start_filename|>core/src/wallet/cosmos/database/CosmosLikeOperationDatabaseHelper.hpp<|end_filename|>
/*
 *
 * CosmosLikeOperationDatabaseHelper
 *
 * Created by <NAME> on 11/02/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_COSMOSLIKEOPERATIONDATABASEHELPER_H
#define LEDGER_CORE_COSMOSLIKEOPERATIONDATABASEHELPER_H

#include <string>

#include <api/BigInt.hpp>
#include <database/soci-date.h>
#include <database/soci-number.h>
#include <database/soci-option.h>
#include <soci.h>
#include <wallet/common/database/OperationDatabaseHelper.h>

namespace ledger {
namespace core {
class CosmosLikeOperationDatabaseHelper : public OperationDatabaseHelper {
   public:
    static std::size_t queryOperations(
        soci::session &sql,
        const std::string &accountUid,
        std::vector<Operation> &operations,
        std::function<bool(const std::string &address)> filter)
    {
        using namespace soci;
        rowset<row> rows =
            (sql.prepare << "SELECT op.amount, op.fees, op.type, op.date, op.senders, op.recipients"
                            " FROM operations AS op "
                            " WHERE op.account_uid = :uid ORDER BY op.date",
             use(accountUid));
        const auto COL_AMT = 0;
        const auto COL_FEES = 1;
        const auto COL_TYPE = 2;
        const auto COL_DATE = 3;
        const auto COL_SEND = 4;
        const auto COL_RECV = 5;

        auto filterList = [&](const std::vector<std::string> &list) -> bool {
            for (auto &elem : list) {
                if (filter(elem)) {
                    return true;
                }
            }
            return false;
        };

        std::size_t c = 0;
        for (auto &row : rows) {
            auto type = api::from_string<api::OperationType>(row.get<std::string>(COL_TYPE));
            auto senders = strings::split(row.get<std::string>(COL_SEND), ",");
            auto recipients = strings::split(row.get<std::string>(COL_RECV), ",");
            if ((type == api::OperationType::SEND && row.get_indicator(COL_SEND) != i_null &&
                 filterList(senders)) ||
                (type == api::OperationType::RECEIVE && row.get_indicator(COL_RECV) != i_null &&
                 filterList(recipients)) ||
                (type == api::OperationType::NONE && row.get_indicator(COL_SEND) != i_null &&
                 filterList(senders))) {
                Operation operation;

                operation.amount = BigInt::fromHex(row.get<std::string>(COL_AMT));
                operation.fees = BigInt::fromHex(row.get<std::string>(COL_FEES));
                operation.type = type;
                operation.date = DateUtils::fromJSON(row.get<std::string>(COL_DATE));

                operations.push_back(operation);

                c += 1;
            }
        }
        return c;
    }

};
}  // namespace core
}  // namespace ledger

#endif  // LEDGER_CORE_COSMOSLIKEOPERATIONDATABASEHELPER_H


<|start_filename|>core/test/ripple/address_test.cpp<|end_filename|>
/*
 *
 * address_test
 *
 * Created by <NAME> on 05/01/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <gtest/gtest.h>
#include <ledger/core/utils/hex.h>
#include <ledger/core/api/RippleLikeExtendedPublicKey.hpp>
#include <ledger/core/ripple/RippleLikeAddress.h>
#include <ledger/core/utils/optional.hpp>
#include <ledger/core/api/Networks.hpp>
#include <wallet/currencies.hpp>
#include <api/Address.hpp>
#include <ripple/RippleLikeExtendedPublicKey.h>
#include <utils/hex.h>

using namespace ledger::core::api;
using namespace ledger::core;


TEST(RippleAddress, AddressFromPubKeyAndChainCodeAccountLevel) {
    auto currency = currencies::RIPPLE;
    std::vector<uint8_t> pubKey = hex::toByteArray(
            "039ea82278f0057b8b041a146f810aa2d84b8ffdfaef3e625624706ad13e12b717");
    std::vector<uint8_t> chainCode = hex::toByteArray(
            "abcc4933bec06eeca6628b9e44f8e71d5e3cf510c0450dd1e29d9aa0f1717da9");
    auto xpub = ledger::core::RippleLikeExtendedPublicKey::fromRaw(currency,
                                                                   optional<std::vector<uint8_t >>(),
                                                                   pubKey,
                                                                   chainCode,
                                                                   "44'/144'/0'/0'");
    EXPECT_EQ(xpub->derive("")->toBase58(), "rM3cztMNEBwPZ7rAKzuKdw4LoVKBwVjxka");
}

TEST(RippleAddress, AddressFromPubKeyAndChainCodeAddressLevel) {
    auto currency = currencies::RIPPLE;
    std::vector<uint8_t> pubKey = hex::toByteArray(
            "03c5ee21cd4a30b5b8436a6b17047aceac5ed4e69ab5f96bde2af897ff80cefab1");
    std::vector<uint8_t> chainCode = hex::toByteArray(
            "0f2f1ffcfb379ceaef995d176e362e5b33ec14ba86d50f0facf46c2308f86c98");
    auto xpub = ledger::core::RippleLikeExtendedPublicKey::fromRaw(currency,
                                                                   optional<std::vector<uint8_t >>(),
                                                                   pubKey,
                                                                   chainCode,
                                                                   "44'/144'/0'/0/0");
    EXPECT_EQ(xpub->derive("")->toBase58(), "rMspb4Kxa3EwdF4uN5TMqhHfsAkBit6w7k");
}

TEST(RippleAddress, XpubFromBase58String) {
    auto currency = currencies::RIPPLE;
    auto addr = "<KEY>";
    auto xpub = ledger::core::RippleLikeExtendedPublicKey::fromBase58(currency, addr, optional<std::string>("44'/144'/0'"));
    EXPECT_EQ(xpub->toBase58(), addr);
    EXPECT_EQ(xpub->derive("0/0")->toBase58(), "rhnLXhSgTgiPNMuxjkjL4qqR3iajiMxdmQ");
    EXPECT_EQ(xpub->derive("0/1")->toBase58(), "rgnbQkpdM6SR4vjHmxRzdSP7kUoNsgEmy");
    EXPECT_EQ(xpub->derive("0/2")->toBase58(), "rn6kdEQif5eo2DEikofRTXCtUJRoRGJYFN");
    EXPECT_EQ(xpub->derive("0/3")->toBase58(), "r3mAmnNeJBdEyWHSK8uDbNmJc1f5dMyJKx");
}


TEST(RippleAddress, ParseAddressFromString) {
    auto currency = currencies::RIPPLE;
    auto address = "rwANfV4cFbvup4er3mhErm8h7i8mFG9PQF";
    auto rippleAddress = ledger::core::RippleLikeAddress::parse(address, currency, Option<std::string>("0/0"));
    EXPECT_EQ(address, rippleAddress->toString());
}


<|start_filename|>core/src/wallet/algorand/database/AlgorandOperationsDatabaseHelper.cpp<|end_filename|>
/*
 *
 * AlgorandOperationsDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 12/01/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "AlgorandOperationsDatabaseHelper.hpp"
#include <database/PreparedStatement.hpp>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <api/BigInt.hpp>
#include <crypto/SHA256.hpp>
#include <wallet/algorand/database/AlgorandTransactionDatabaseHelper.hpp>
#include <wallet/algorand/model/transactions/AlgorandTransaction.hpp>
#include <unordered_set>
#include <database/soci-backend-utils.h>
#include <debug/Benchmarker.h>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>
#include <wallet/algorand/AlgorandExplorerConstants.hpp>

using namespace soci;

namespace {
    using namespace ledger::core::algorand;

    // Algorand operations
    struct AlgorandOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> txHash;

        void update(const std::string& opUid, const std::string& transactionUid,
                const std::string& transactionHash) {
            uid.push_back(opUid);
            txUid.push_back(transactionUid);
            txHash.push_back(transactionHash);
        }

        void clear() {
            uid.clear();
            txUid.clear();
            txHash.clear();
        }
    };

    const auto UPSERT_ALGORAND_OPERATION = ledger::core::db::stmt<AlgorandOperationBinding>(
            "INSERT INTO algorand_operations VALUES(:uid, :tx_uid, :tx_hash) "
            "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, use(b.uid), use(b.txUid), use(b.txHash);
            });

    // Algorand transactions: generic class
    struct TransactionBinding {
        std::vector<std::string> uid;
        std::vector<boost::optional<std::string>> txHash;
        std::vector<std::string> type;
        std::vector<boost::optional<uint64_t>> round;
        std::vector<boost::optional<uint64_t>> timestamp;
        std::vector<uint64_t> firstValid;
        std::vector<uint64_t> lastValid;
        std::vector<boost::optional<std::string>> genesisId;
        std::vector<std::string> genesisHash;
        std::vector<std::string> sender;
        std::vector<uint64_t> fee;
        std::vector<boost::optional<std::string>> note;
        std::vector<boost::optional<std::string>> group;
        std::vector<boost::optional<std::string>> lease;
        std::vector<boost::optional<uint64_t>> senderRewards;
        std::vector<boost::optional<uint64_t>> receiverRewards;
        std::vector<boost::optional<uint64_t>> closeRewards;

        void update(const std::string& txUid, const model::Transaction & tx) {
            auto headerId = optionalValue<std::string>(tx.header.id);
            auto headerGenesisId = optionalValue<std::string>(tx.header.genesisId);
            auto headerRound = optionalValue<uint64_t>(tx.header.round);
            auto headerTimestamp = optionalValue<uint64_t>(tx.header.timestamp);
            auto headerNote = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.note, bytesToB64);
            auto headerGroup = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.group, bytesToB64);
            auto headerLease = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.lease, bytesToB64);
            auto headerSenderRewards = optionalValue(tx.header.senderRewards);
            auto headerReceiverRewards = optionalValue(tx.header.receiverRewards);
            auto headerCloseRewards = optionalValue(tx.header.closeRewards);
            
            uid.push_back(txUid);
            txHash.push_back(headerId);
            type.push_back(tx.header.type);
            round.push_back(headerRound);
            timestamp.push_back(headerTimestamp);
            firstValid.push_back(tx.header.firstValid);
            lastValid.push_back(tx.header.lastValid);
            genesisId.push_back(headerGenesisId);
            genesisHash.push_back(tx.header.genesisHash.getRawString());
            sender.push_back(tx.header.sender.toString());
            fee.push_back(tx.header.fee);
            note.push_back(headerNote);
            group.push_back(headerGroup);
            lease.push_back(headerLease);
            senderRewards.push_back(headerSenderRewards);
            receiverRewards.push_back(headerReceiverRewards);
            closeRewards.push_back(headerCloseRewards);
        }

        void clear() {
            uid.clear();
            txHash.clear();
            type.clear();
            round.clear();
            timestamp.clear();
            firstValid.clear();
            lastValid.clear();
            genesisId.clear();
            genesisHash.clear();
            sender.clear();
            fee.clear();
            note.clear();
            group.clear();
            lease.clear();
            senderRewards.clear();
            receiverRewards.clear();
            closeRewards.clear();
        }

        bool empty() const {
            return uid.empty();
        }
    };

    // Algorand transactions: payment
    struct PaymentTransactionBinding : public TransactionBinding {
        std::vector<uint64_t> amount;
        std::vector<std::string> receiverAddr;
        std::vector<boost::optional<std::string>> closeAddr;
        std::vector<boost::optional<uint64_t>> closeAmount;

        void update(const std::string& txUid, const model::Transaction & tx) {
            TransactionBinding::update(txUid, tx);
            auto& payment = boost::get<model::PaymentTxnFields>(tx.details);
            auto paymentCloseAddr = optionalValueWithTransform<Address, std::string>(payment.closeAddr, addrToString);
            auto paymentCloseAmount = optionalValue<uint64_t>(payment.closeAmount);

            amount.push_back(payment.amount);
            receiverAddr.push_back(payment.receiverAddr.toString());
            closeAddr.push_back(paymentCloseAddr);
            closeAmount.push_back(paymentCloseAmount);
        }

        void clear() {
            TransactionBinding::clear();
            amount.clear();
            receiverAddr.clear();
            closeAddr.clear();
            closeAmount.clear();
        }

    };
 
    const auto UPSERT_ALGORAND_TRANSACTION_PAYMENT = ledger::core::db::stmt<PaymentTransactionBinding>(
            "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "pay_amount, pay_receiver_address, pay_close_address, pay_close_amount) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":amount, :receiver_addr, :close_addr, :close_amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid),
                use(b.txHash),
                use(b.type),
                use(b.round),
                use(b.timestamp),
                use(b.firstValid),
                use(b.lastValid),
                use(b.genesisId),
                use(b.genesisHash),
                use(b.sender),
                use(b.fee),
                use(b.note),
                use(b.group),
                use(b.lease),
                use(b.senderRewards),
                use(b.receiverRewards),
                use(b.closeRewards),
                use(b.amount),
                use(b.receiverAddr),
                use(b.closeAddr),
                use(b.closeAmount);
            });


    // Algorand transactions: keyreg
    struct KeyregTransactionBinding : public TransactionBinding {

        std::vector<boost::optional<int32_t>> nonParticipation;
        std::vector<std::string> selectionPk;
        std::vector<std::string> votePk;
        std::vector<uint64_t> voteKeyDilution;
        std::vector<uint64_t> voteFirst;
        std::vector<uint64_t> voteLast;

        void update(const std::string& txUid, const model::Transaction & tx) {
            TransactionBinding::update(txUid, tx);
            
            auto& keyreg = boost::get<model::KeyRegTxnFields>(tx.details);
            auto keyreg_nonParticipation = optionalValueWithTransform<bool, int32_t>(keyreg.nonParticipation, boolToNum);

            nonParticipation.push_back(keyreg_nonParticipation);
            selectionPk.push_back(keyreg.selectionPk);
            votePk.push_back(keyreg.votePk);
            voteKeyDilution.push_back(keyreg.voteKeyDilution);
            voteFirst.push_back(keyreg.voteFirst);
            voteLast.push_back(keyreg.voteLast);
        }

        void clear() {
            TransactionBinding::clear();
            nonParticipation.clear();
            selectionPk.clear();
            votePk.clear();
            voteKeyDilution.clear();
            voteFirst.clear();
            voteLast.clear();
        }
    };

        const auto UPSERT_ALGORAND_TRANSACTION_KEYREG = ledger::core::db::stmt<KeyregTransactionBinding>(
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "keyreg_non_participation, keyreg_selection_pk, keyreg_vote_pk, keyreg_vote_key_dilution, keyreg_vote_first, keyreg_vote_last) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":non_part, :selection_pk, :vote_pk, :vote_key_dilution, :vote_first, :vote_last) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid),
                use(b.txHash),
                use(b.type),
                use(b.round),
                use(b.timestamp),
                use(b.firstValid),
                use(b.lastValid),
                use(b.genesisId),
                use(b.genesisHash),
                use(b.sender),
                use(b.fee),
                use(b.note),
                use(b.group),
                use(b.lease),
                use(b.senderRewards),
                use(b.receiverRewards),
                use(b.closeRewards),
                use(b.nonParticipation),
                use(b.selectionPk),
                use(b.votePk),
                use(b.voteKeyDilution),
                use(b.voteFirst),
                use(b.voteLast);
            });
 
    // Algorand transactions: AssetConfig
    struct AssetConfigTransactionBinding : public TransactionBinding {

        std::vector<boost::optional<uint64_t>> assetConfigId;
        std::vector<boost::optional<std::string>> assetConfigName;
        std::vector<boost::optional<std::string>> assetConfigUnitname;
        std::vector<boost::optional<uint64_t>> assetConfigTotal;
        std::vector<boost::optional<uint32_t>> assetConfigDecimals;
        std::vector<boost::optional<int32_t>> assetConfigFrozen;
        std::vector<boost::optional<std::string>> assetConfigCreator;
        std::vector<boost::optional<std::string>> assetConfigManager;
        std::vector<boost::optional<std::string>> assetConfigReserve;
        std::vector<boost::optional<std::string>> assetConfigFreeze;
        std::vector<boost::optional<std::string>> assetConfigClawback;
        std::vector<boost::optional<std::string>> assetConfigMetadata;
        std::vector<boost::optional<std::string>> assetConfigUrl;

        void update(const std::string& txUid, const model::Transaction & tx) {
            TransactionBinding::update(txUid, tx);

            auto& assetConfig = boost::get<model::AssetConfigTxnFields>(tx.details);
            auto& assetParams = *assetConfig.assetParams;
            auto assetId = optionalValue<uint64_t>(assetConfig.assetId);
            auto assetName = optionalValue<std::string>(assetParams.assetName);
            auto assetUnitname = optionalValue<std::string>(assetParams.unitName);
            auto assetTotal = optionalValue<uint64_t>(assetParams.total);
            auto assetDecimals = optionalValue<uint32_t>(assetParams.decimals);
            auto assetFrozen = optionalValueWithTransform<bool, int32_t>(assetParams.defaultFrozen, boolToNum);
            auto assetCreator = optionalValueWithTransform<Address, std::string>(assetParams.creatorAddr, addrToString);
            auto assetManager = optionalValueWithTransform<Address, std::string>(assetParams.managerAddr, addrToString);
            auto assetReserve = optionalValueWithTransform<Address, std::string>(assetParams.reserveAddr, addrToString);
            auto assetFreeze = optionalValueWithTransform<Address, std::string>(assetParams.freezeAddr, addrToString);
            auto assetClawback = optionalValueWithTransform<Address, std::string>(assetParams.clawbackAddr, addrToString);
            auto assetMetadata = optionalValueWithTransform<std::vector<uint8_t>, std::string>(assetParams.metaDataHash, bytesToB64);
            auto assetUrl = optionalValue<std::string>(assetParams.url);

            assetConfigId.push_back(assetId);
            assetConfigName.push_back(assetName);
            assetConfigUnitname.push_back(assetUnitname);
            assetConfigTotal.push_back(assetTotal);
            assetConfigDecimals.push_back(assetDecimals);
            assetConfigFrozen.push_back(assetFrozen);
            assetConfigCreator.push_back(assetCreator);
            assetConfigManager.push_back(assetManager);
            assetConfigReserve.push_back(assetReserve);
            assetConfigFreeze.push_back(assetFreeze);
            assetConfigClawback.push_back(assetClawback);
            assetConfigMetadata.push_back(assetMetadata);
            assetConfigUrl.push_back(assetUrl);
        }

        void clear() {
            TransactionBinding::clear();
            assetConfigId.clear();
            assetConfigName.clear();
            assetConfigUnitname.clear();
            assetConfigTotal.clear();
            assetConfigDecimals.clear();
            assetConfigFrozen.clear();
            assetConfigCreator.clear();
            assetConfigManager.clear();
            assetConfigReserve.clear();
            assetConfigFreeze.clear();
            assetConfigClawback.clear();
            assetConfigMetadata.clear();
            assetConfigUrl.clear();
        }
    };

    const auto UPSERT_ALGORAND_TRANSACTION_ASSETCONFIG = ledger::core::db::stmt<AssetConfigTransactionBinding>(
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "acfg_asset_id, acfg_asset_name, acfg_unit_name, acfg_total, acfg_decimals, acfg_default_frozen, "
                "acfg_creator_address, acfg_manager_address, acfg_reserve_address, acfg_freeze_address, acfg_clawback_address, "
                "acfg_metadata_hash, acfg_url) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":asset_id, :asset_name, :unit_name, :total, :decimals, :default_frozen, :creator_addr, "
                ":manager_addr, :reserve_addr, :freeze_addr, :clawback_addr, :metadata_hash, :url) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid),
                use(b.txHash),
                use(b.type),
                use(b.round),
                use(b.timestamp),
                use(b.firstValid),
                use(b.lastValid),
                use(b.genesisId),
                use(b.genesisHash),
                use(b.sender),
                use(b.fee),
                use(b.note),
                use(b.group),
                use(b.lease),
                use(b.senderRewards),
                use(b.receiverRewards),
                use(b.closeRewards),
                use(b.assetConfigId),
                use(b.assetConfigName),
                use(b.assetConfigUnitname),
                use(b.assetConfigTotal),
                use(b.assetConfigDecimals),
                use(b.assetConfigFrozen),
                use(b.assetConfigCreator),
                use(b.assetConfigManager),
                use(b.assetConfigReserve),
                use(b.assetConfigFreeze),
                use(b.assetConfigClawback),
                use(b.assetConfigMetadata),
                use(b.assetConfigUrl);
            });


    // Algorand transactions: AssetTransfer
    struct AssetTransferTransactionBinding : public TransactionBinding {
       
            std::vector<uint64_t> assetTransferId;
            std::vector<boost::optional<uint64_t>> assetTransferAmount;
            std::vector<std::string> assetTransferReceiver;
            std::vector<boost::optional<std::string>> assetTransferCloseTo;
            std::vector<boost::optional<uint64_t>> assetTransferCloseAmount;
            std::vector<boost::optional<std::string>> assetTransferSender;

        void update(const std::string& txUid, const model::Transaction & tx) {
            TransactionBinding::update(txUid, tx);
            auto& assetTransfer = boost::get<model::AssetTransferTxnFields>(tx.details);
            auto assetAmount = optionalValue<uint64_t>(assetTransfer.assetAmount);
            auto assetCloseTo = optionalValueWithTransform<Address, std::string>(assetTransfer.assetCloseTo, addrToString);
            auto assetCloseAmount = optionalValue(assetTransfer.closeAmount);
            auto assetSender = optionalValueWithTransform<Address, std::string>(assetTransfer.assetSender, addrToString);

            assetTransferId.push_back(assetTransfer.assetId);
            assetTransferAmount.push_back(assetAmount);
            assetTransferReceiver.push_back(assetTransfer.assetReceiver.toString());
            assetTransferCloseTo.push_back(assetCloseTo);
            assetTransferCloseAmount.push_back(assetCloseAmount);
            assetTransferSender.push_back(assetSender);
        }

        void clear() {
            TransactionBinding::clear();
            assetTransferId.clear();
            assetTransferAmount.clear();
            assetTransferReceiver.clear();
            assetTransferCloseTo.clear();
            assetTransferCloseAmount.clear();
            assetTransferSender.clear();
        }
    };

    const auto UPSERT_ALGORAND_TRANSACTION_ASSETTRANSFER = ledger::core::db::stmt<AssetTransferTransactionBinding>(
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "axfer_asset_id, axfer_asset_amount, axfer_receiver_address, axfer_close_address, axfer_close_amount, axfer_sender_address) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":assetId, :amount, :receiver_addr, :close_addr, :close_amount, :sender_addr) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid),
                use(b.txHash),
                use(b.type),
                use(b.round),
                use(b.timestamp),
                use(b.firstValid),
                use(b.lastValid),
                use(b.genesisId),
                use(b.genesisHash),
                use(b.sender),
                use(b.fee),
                use(b.note),
                use(b.group),
                use(b.lease),
                use(b.senderRewards),
                use(b.receiverRewards),
                use(b.closeRewards),
                use(b.assetTransferId),
                use(b.assetTransferAmount),
                use(b.assetTransferReceiver),
                use(b.assetTransferCloseTo),
                use(b.assetTransferCloseAmount),
                use(b.assetTransferSender);
            });



// Algorand transactions: AssetFreeze
    struct AssetFreezeTransactionBinding : public TransactionBinding {
       
        std::vector<uint64_t> assetFreezeId;
        std::vector<int32_t> assetFreezeFrozen;
        std::vector<std::string> assetFreezeAddres;

        void update(const std::string& txUid, const model::Transaction & tx) {
            TransactionBinding::update(txUid, tx);

            auto& assetFreeze = boost::get<model::AssetFreezeTxnFields>(tx.details);
            assetFreezeId.push_back(assetFreeze.assetId);
            assetFreezeFrozen.push_back(static_cast<int32_t>(assetFreeze.assetFrozen));
            assetFreezeAddres.push_back(assetFreeze.frozenAddress.toString());
        }

        void clear() {
            TransactionBinding::clear();

            assetFreezeId.clear();
            assetFreezeFrozen.clear();
            assetFreezeAddres.clear();
        }
    };

    const auto UPSERT_ALGORAND_TRANSACTION_ASSETFREEZE = ledger::core::db::stmt<AssetFreezeTransactionBinding>(
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "afrz_asset_id, afrz_frozen, afrz_frozen_address) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":asset_id, :frozen, :frozen_addr) "
        "ON CONFLICT DO NOTHING",    
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid),
                use(b.txHash),
                use(b.type),
                use(b.round),
                use(b.timestamp),
                use(b.firstValid),
                use(b.lastValid),
                use(b.genesisId),
                use(b.genesisHash),
                use(b.sender),
                use(b.fee),
                use(b.note),
                use(b.group),
                use(b.lease),
                use(b.senderRewards),
                use(b.receiverRewards),
                use(b.closeRewards),
                use(b.assetFreezeId),
                use(b.assetFreezeFrozen),
                use(b.assetFreezeAddres);
            });
}

namespace ledger {
    namespace core {
        namespace algorand {

            void OperationsDatabaseHelper::bulkInsert(soci::session &sql,
                    const std::vector<Operation> &operations) {
                if (operations.empty())
                    return;
                Benchmarker rawInsert("raw_db_insert_algorand", nullptr);
                rawInsert.start();
                PreparedStatement<OperationBinding> operationStmt;
                PreparedStatement<BlockBinding> blockStmt;
                PreparedStatement<AlgorandOperationBinding> algorandOpStmt;
                PreparedStatement<PaymentTransactionBinding> paymentTransactionStmt;
                PreparedStatement<KeyregTransactionBinding> keyregTransactionStmt;
                PreparedStatement<AssetConfigTransactionBinding> assetConfigTransactionStmt;
                PreparedStatement<AssetTransferTransactionBinding> assetTransferTransactionStmt;
                PreparedStatement<AssetFreezeTransactionBinding> assetFreezeTransactionStmt;

                BulkInsertDatabaseHelper::UPSERT_OPERATION(sql, operationStmt);
                BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
                UPSERT_ALGORAND_OPERATION(sql, algorandOpStmt);     
                
                for (const auto& op : operations) {
                    if (op.getBackend().block.hasValue()) {
                        auto block = op.getBackend().block.getValue(); 
                        block.currencyName = op.getAccount()->getWallet()->getCurrency().name;
                        blockStmt.bindings.update(block);
                    }
                    // Upsert operation
                    operationStmt.bindings.update(op.getBackend());

                    // Upsert transaction
                    auto& tx = op.getTransactionData(); 
                    const auto txUid = *tx.header.id;
                    if (tx.header.type == constants::xPay) {
                        if (paymentTransactionStmt.bindings.empty())
                            UPSERT_ALGORAND_TRANSACTION_PAYMENT(sql, paymentTransactionStmt);
                        paymentTransactionStmt.bindings.update(txUid, tx);
                    } 
                    else if (tx.header.type == constants::xKeyregs) {
                        if (keyregTransactionStmt.bindings.empty())
                            UPSERT_ALGORAND_TRANSACTION_KEYREG(sql, keyregTransactionStmt);
                        keyregTransactionStmt.bindings.update(txUid, tx);
                    } 
                    else if (tx.header.type == constants::xAcfg) {
                        if (assetConfigTransactionStmt.bindings.empty())
                            UPSERT_ALGORAND_TRANSACTION_ASSETCONFIG(sql, assetConfigTransactionStmt);
                        assetConfigTransactionStmt.bindings.update(txUid, tx);
                    } 
                    else if (tx.header.type == constants::xAxfer) {
                        if (assetTransferTransactionStmt.bindings.empty())
                            UPSERT_ALGORAND_TRANSACTION_ASSETTRANSFER(sql, assetTransferTransactionStmt);
                        assetTransferTransactionStmt.bindings.update(txUid, tx);
                    } 
                    else if (tx.header.type == constants::xAfreeze) {
                        if (assetFreezeTransactionStmt.bindings.empty())
                            UPSERT_ALGORAND_TRANSACTION_ASSETFREEZE(sql, assetFreezeTransactionStmt);
                        assetFreezeTransactionStmt.bindings.update(txUid, tx);
                    }

                    // Algorand operation
                    const auto txHash = op.getTransaction()->getId();
                    algorandOpStmt.bindings.update(op.getBackend().uid, txUid, txHash);  
                }
                //1- block
                if (!blockStmt.bindings.uid.empty())
                    blockStmt.execute();

                //2- operations 
                operationStmt.execute();

                //3- algorand transactions 
                if (!paymentTransactionStmt.bindings.empty())
                    paymentTransactionStmt.execute();
                if (!keyregTransactionStmt.bindings.empty())
                    keyregTransactionStmt.execute();
                if (!assetConfigTransactionStmt.bindings.empty())
                    assetConfigTransactionStmt.execute();
                if (!assetTransferTransactionStmt.bindings.empty())
                    assetTransferTransactionStmt.execute();
                if (!assetFreezeTransactionStmt.bindings.empty())
                    assetFreezeTransactionStmt.execute();

                //4- algorand operations
                algorandOpStmt.execute();

                rawInsert.stop();
            }
        }
    }
}

<|start_filename|>core/src/jni/jni/DurationMetric.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#ifndef DJINNI_GENERATED_DURATIONMETRIC_HPP_JNI_
#define DJINNI_GENERATED_DURATIONMETRIC_HPP_JNI_

#include "../../api/DurationMetric.hpp"
#include "djinni_support.hpp"

namespace djinni_generated {

class DurationMetric final {
public:
    using CppType = ::ledger::core::api::DurationMetric;
    using JniType = jobject;

    using Boxed = DurationMetric;

    ~DurationMetric();

    static CppType toCpp(JNIEnv* jniEnv, JniType j);
    static ::djinni::LocalRef<JniType> fromCpp(JNIEnv* jniEnv, const CppType& c);

private:
    DurationMetric();
    friend ::djinni::JniClass<DurationMetric>;

    const ::djinni::GlobalRef<jclass> clazz { ::djinni::jniFindClass("co/ledger/core/DurationMetric") };
    const jmethodID jconstructor { ::djinni::jniGetMethodID(clazz.get(), "<init>", "(JI)V") };
    const jfieldID field_totalMs { ::djinni::jniGetFieldID(clazz.get(), "totalMs", "J") };
    const jfieldID field_count { ::djinni::jniGetFieldID(clazz.get(), "count", "I") };
};

}  // namespace djinni_generated
#endif //DJINNI_GENERATED_DURATIONMETRIC_HPP_JNI_


<|start_filename|>core/src/wallet/cosmos/database/CosmosLikeOperationsDatabaseHelper.cpp<|end_filename|>
/*
 *
 * CosmosLikeOperationsDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 19/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "CosmosLikeOperationsDatabaseHelper.hpp"
#include <database/PreparedStatement.hpp>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <api/BigInt.hpp>
#include <debug/Benchmarker.h>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>
#include <wallet/common/database/BlockDatabaseHelper.h>
#include <wallet/cosmos/database/SociCosmosAmount.hpp>
#include <wallet/cosmos/CosmosLikeMessage.hpp>
#include <wallet/cosmos/CosmosLikeConstants.hpp>
#include <wallet/cosmos/api_impl/CosmosLikeTransactionApi.hpp>



using namespace soci;

namespace {
    using namespace ledger::core;

    // Cosmos operations
    struct CosmosOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> msgUid;

        void update(const std::string& op, const std::string& msg) {
            uid.push_back(op);
            msgUid.push_back(msg);
        }

        void clear() {
            uid.clear();
            msgUid.clear();
        }
    };

    const auto UPSERT_COSMOS_OPERATION = db::stmt<CosmosOperationBinding>(
            "INSERT INTO cosmos_operations VALUES(:uid, :message_uid) "
            "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, use(b.uid), use(b.msgUid);
            });

   // Transaction
    struct TransactionBinding {
        std::vector<std::string> uid; 
        std::vector<std::string> hash; 
        std::vector<Option<std::string>> blockUid; 
        std::vector<std::string> date;
        std::vector<std::string> fee;
        std::vector<std::string> gas; 
        std::vector<Option<std::string>> gasUsed; 
        std::vector<std::string> memo;
        
        void update(const cosmos::Transaction& tx) {
            Option<std::string> txblockUid;
            if (tx.block.nonEmpty() && !tx.block.getValue().hash.empty()) {
                txblockUid = BlockDatabaseHelper::createBlockUid(tx.block.getValue());
            }
            auto txdate = DateUtils::toJSON(tx.timestamp);
            auto txfee = soci::coinsToString(tx.fee.amount);
            auto txgas = tx.fee.gas.toString();
            auto txgasUsed = tx.gasUsed.flatMap<std::string>([](const BigInt &g) {
                return g.toString();
            });

            uid.push_back(tx.uid);
            hash.push_back(tx.hash);
            blockUid.push_back(txblockUid);
            date.push_back(txdate);
            fee.push_back(txfee);
            gas.push_back(txgas);
            gasUsed.push_back(txgasUsed);
            memo.push_back(tx.memo);
        }

        void clear() {
            uid.clear();
            hash.clear();
            blockUid.clear();
            date.clear();
            fee.clear();
            gas.clear();
            gasUsed.clear();
            memo.clear();
        }
    };

    const auto UPSERT_TRANSACTION = db::stmt<TransactionBinding>(
        "INSERT INTO cosmos_transactions("
        "uid, hash, block_uid, time, fee_amount, gas, gas_used, memo"
        ") VALUES (:uid, :hash, :block_uid, :time, :fee, :gas, :gas_used, :memo)"
        " ON CONFLICT(uid) DO UPDATE SET block_uid = :buid, gas_used = :gused",
            [] (auto& s, auto&  b) {
                s, 
                use(b.uid), 
                use(b.hash), 
                use(b.blockUid), 
                use(b.date), 
                use(b.fee),
                use(b.gas), 
                use(b.gasUsed), 
                use(b.memo),
                use(b.blockUid), 
                use(b.gasUsed);          
            });



    // Messages: generic class
    struct MessageBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> type;
        std::vector<std::string> log;
        std::vector<int32_t> success;
        std::vector<int32_t> messageIndex;
        
        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            uid.push_back(msg.uid);
            txUid.push_back(transactionUid);
            type.push_back(msg.type);
            log.push_back(messageLog.log);
            success.push_back(messageLog.success ? 1 : 0);
            messageIndex.push_back(messageLog.messageIndex);
        }

        void clear() {
            uid.clear();
            txUid.clear();
            type.clear();
            log.clear();
            success.clear();
            messageIndex.clear();
        }

        bool empty() const {
            return uid.empty();
        }
    };
    const auto UPSERT_MESSAGE_GENERIC = db::stmt<MessageBinding>(
        "INSERT INTO cosmos_messages ("
        "uid, transaction_uid, message_type, log, success, msg_index)"
        "VALUES (:uid, :tuid, :mt, :log, :success, :mi) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex);   
            });

    // Messages: Send
    struct SendMessageBinding: public MessageBinding {
        std::vector<std::string> fromAddress;
        std::vector<std::string> toAddress;
        std::vector<std::string> coins;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgSend>(msg.content);
            const auto coinsAmount = soci::coinsToString(m.amount);
            fromAddress.push_back(m.fromAddress);
            toAddress.push_back(m.toAddress);
            coins.push_back(coinsAmount);
        }

        void clear() {
            MessageBinding::clear();
            fromAddress.clear();
            toAddress.clear();
            coins.clear();

        }
    };
    const auto UPSERT_MESSAGE_SEND = db::stmt<SendMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
            "transaction_uid, message_type, log,"
            "success, msg_index, from_address, to_address, amount) "
            "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :fa, :ta, :amount) "
            "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex),
                soci::use(b.fromAddress), soci::use(b.toAddress), soci::use(b.coins); 
            });

    // Messages: Delegate
    struct DelegateMessageBinding: public MessageBinding {
        std::vector<std::string> delegatorAddress;
        std::vector<std::string> validatorAddress;
        std::vector<std::string> coin;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgDelegate>(msg.content);
            const auto coinAmount = soci::coinToString(m.amount);
            delegatorAddress.push_back(m.delegatorAddress);
            validatorAddress.push_back(m.validatorAddress);
            coin.push_back(coinAmount);
        }

        void clear() {
            MessageBinding::clear();
            delegatorAddress.clear();
            validatorAddress.clear();
            coin.clear();
        }
    };
    const auto UPSERT_MESSAGE_DELEGATE = db::stmt<DelegateMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, delegator_address, validator_address, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :fa, :ta, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.delegatorAddress), soci::use(b.validatorAddress), soci::use(b.coin);   
            });

    // Messages: Undelegate
    struct UndelegateMessageBinding: public MessageBinding {
        std::vector<std::string> delegatorAddress;
        std::vector<std::string> validatorAddress;
        std::vector<std::string> coin;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgUndelegate>(msg.content);
            const auto coinAmount = soci::coinToString(m.amount);
            delegatorAddress.push_back(m.delegatorAddress);
            validatorAddress.push_back(m.validatorAddress);
            coin.push_back(coinAmount);
        }

        void clear() {
            MessageBinding::clear();
            delegatorAddress.clear();
            validatorAddress.clear();
            coin.clear();
        }
    };
    const auto UPSERT_MESSAGE_UNDELEGATE = db::stmt<UndelegateMessageBinding>(
      "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, delegator_address, validator_address, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :fa, :ta, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.delegatorAddress), soci::use(b.validatorAddress), soci::use(b.coin);  
            });

    // Messages: BeginRedelegate
    struct BeginRedelegateMessageBinding: public MessageBinding {
        std::vector<std::string> delegatorAddress;
        std::vector<std::string> validatorSourceAddress;
        std::vector<std::string> validatorDestinationAddress;
        std::vector<std::string> coin;
        
        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgBeginRedelegate>(msg.content);
            const auto coinAmount = soci::coinToString(m.amount);
            delegatorAddress.push_back(m.delegatorAddress);
            validatorSourceAddress.push_back(m.validatorSourceAddress);
            validatorDestinationAddress.push_back(m.validatorDestinationAddress);
            coin.push_back(coinAmount);
        }

        void clear() {
            MessageBinding::clear();
            delegatorAddress.clear();
            validatorSourceAddress.clear();
            validatorDestinationAddress.clear();
            coin.clear();
        }
    };
    const auto UPSERT_MESSAGE_BEGIN_REDELEGATE = db::stmt<BeginRedelegateMessageBinding>(
       "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, delegator_address, validator_src_address,"
           "validator_dst_address, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :da, :fa, :ta, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.delegatorAddress), soci::use(b.validatorSourceAddress), soci::use(b.validatorDestinationAddress), soci::use(b.coin);   
            });
    
    // Messages: SubmitProposal
    struct SubmitProposalMessageBinding: public MessageBinding {
        std::vector<std::string> proposer;
        std::vector<std::string> contentType;
        std::vector<std::string> contentTitle;
        std::vector<std::string> contentDescr;
        std::vector<std::string> coins;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgSubmitProposal>(msg.content);
            const auto coinsAmount = soci::coinsToString(m.initialDeposit);
            proposer.push_back(m.proposer);
            contentType.push_back(m.content.type);
            contentTitle.push_back(m.content.title);
            contentDescr.push_back(m.content.descr);
            coins.push_back(coinsAmount);
        }

        void clear() {
            MessageBinding::clear();
            proposer.clear();
            contentType.clear();
            contentTitle.clear();
            contentDescr.clear();
            coins.clear();
        }
    };
    const auto UPSERT_MESSAGE_SUBMIT_PROPOSAL = db::stmt<SubmitProposalMessageBinding>(
       "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, proposer, content_type,"
           "content_title, content_description, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :proposer,"
           ":ctype, :ctitle, :cdescription, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.proposer), soci::use(b.contentType), soci::use(b.contentTitle), 
                soci::use(b.contentDescr), soci::use(b.coins);
            });

    // Messages: Vote
    struct VoteMessageBinding: public MessageBinding {
        std::vector<std::string> proposalId;
        std::vector<std::string> voter;
        std::vector<std::string> option;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);

            const auto &m = boost::get<cosmos::MsgVote>(msg.content);
            proposalId.push_back(m.proposalId);
            voter.push_back(m.voter);
            option.push_back(api::to_string(m.option));
        }

        void clear() {
            MessageBinding::clear();
            proposalId.clear();
            voter.clear();
            option.clear();
        }
    };
    const auto UPSERT_MESSAGE_VOTE = db::stmt<VoteMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, proposal_id, voter,"
           "vote_option) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :pid, :voter, :opt) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.proposalId), soci::use(b.voter), soci::use(b.option); 
            });

    // Messages: Deposit
    struct DepositMessageBinding: public MessageBinding {
        std::vector<std::string> depositor;
        std::vector<std::string> proposalId;
        std::vector<std::string> coins;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgDeposit>(msg.content);
            const auto coinsAmount = soci::coinsToString(m.amount);
            depositor.push_back(m.depositor);
            proposalId.push_back(m.proposalId);
            coins.push_back(coinsAmount);
        }

        void clear() {
            MessageBinding::clear();
            depositor.clear();
            proposalId.clear();
            coins.clear();
        }
    };
    const auto UPSERT_MESSAGE_DEPOSIT = db::stmt<DepositMessageBinding>(
        "INSERT INTO cosmos_messages (uid, transaction_uid, "
           "message_type, log, success, "
           "msg_index, depositor, proposal_id, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :dep, :pid, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.depositor), soci::use(b.proposalId), soci::use(b.coins);   
            });

    // Messages: WithdrawDelegationReward
    struct WithdrawDelegationRewardMessageBinding: public MessageBinding {
        std::vector<std::string> delegatorAddress;
        std::vector<std::string> validatorAddress;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgWithdrawDelegationReward>(msg.content);
            delegatorAddress.push_back(m.delegatorAddress);
            validatorAddress.push_back(m.validatorAddress);
        }

        void clear() {
            MessageBinding::clear();
            delegatorAddress.clear();
            validatorAddress.clear();
        }
    };
    const auto UPSERT_MESSAGE_WITHDRAW_DELEGATION_REWARD = db::stmt<WithdrawDelegationRewardMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, delegator_address, validator_address) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :da, :va) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.delegatorAddress), soci::use(b.validatorAddress);   
            });

    // Messages: WithdrawDelegatorReward
    struct WithdrawDelegatorRewardMessageBinding: public MessageBinding {
        std::vector<std::string> delegatorAddress;
        std::vector<std::string> validatorAddress;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
           const auto &m = boost::get<cosmos::MsgWithdrawDelegatorReward>(msg.content);
            delegatorAddress.push_back(m.delegatorAddress);
            validatorAddress.push_back(m.validatorAddress);
        }

        void clear() {
            MessageBinding::clear();
            delegatorAddress.clear();
            validatorAddress.clear();
        }
    };
    const auto UPSERT_MESSAGE_WITHDRAW_DELEGATOR_REWARD = db::stmt<WithdrawDelegatorRewardMessageBinding>(
       "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, delegator_address, validator_address) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :da, :va) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.delegatorAddress), soci::use(b.validatorAddress); 
            });

    // Messages: WithdrawValidatorCommission
    struct WithdrawValidatorCommissionMessageBinding: public MessageBinding {
        std::vector<std::string> validatorAddress;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            
            const auto &m = boost::get<cosmos::MsgWithdrawValidatorCommission>(msg.content);
            validatorAddress.push_back(m.validatorAddress);
        }

        void clear() {
            MessageBinding::clear();
            validatorAddress.clear();
        }
    };
    const auto UPSERT_MESSAGE_WITHDRAW_VALIDATOR_COMMISSION = db::stmt<WithdrawValidatorCommissionMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, validator_address) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :va) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.validatorAddress);   
            });

    // Messages: SetWithdrawAddress
    struct SetWithdrawAddressMessageBinding: public MessageBinding {
        std::vector<std::string> delegatorAddress;
        std::vector<std::string> withdrawAddress;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
    
            const auto &m = boost::get<cosmos::MsgSetWithdrawAddress>(msg.content);
            delegatorAddress.push_back(m.delegatorAddress);
            withdrawAddress.push_back(m.withdrawAddress);
        }

        void clear() {
            MessageBinding::clear();
            delegatorAddress.clear();
            withdrawAddress.clear();
        }
    };
    const auto UPSERT_MESSAGE_SET_WITHDRAW_ADDRESS = db::stmt<SetWithdrawAddressMessageBinding>(
       "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, delegator_address, to_address) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :da, :withdraw) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.delegatorAddress), soci::use(b.withdrawAddress);   
            });

    // Messages: Unjail
    struct UnjailMessageBinding: public MessageBinding {
        std::vector<std::string> validatorAddress;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
    
            const auto &m = boost::get<cosmos::MsgUnjail>(msg.content);
            validatorAddress.push_back(m.validatorAddress);
        }

        void clear() {
            MessageBinding::clear();
            validatorAddress.clear();
        }
    };
    const auto UPSERT_MESSAGE_UNJAIL = db::stmt<UnjailMessageBinding>(
       "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, validator_address) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :va) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.validatorAddress); 
            });

    // Messages: MultiSend
    struct MultiSendMessageBinding: public MessageBinding {
        std::vector<std::string> coins;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
            const auto &m = boost::get<cosmos::MsgMultiSend>(msg.content);
            std::vector<cosmos::Coin> totalAmount;

            // This snippet left-folds a iterable<vector<cosmos::Coin>>
            // into vector<cosmos::Coin> using addition.
            // Hopefully transform_reduce or flatten functions can help us later.
            // The map intermediate helps for out-of-order denominations.
            std::unordered_map<std::string, BigInt> denomToAmt;
            std::for_each(m.inputs.cbegin(), m.inputs.cend(), [&denomToAmt](const auto &input) {
                std::for_each(
                    input.coins.cbegin(), input.coins.cend(), [&denomToAmt](const auto &amountDenom) {
                        auto searchDenom = denomToAmt.find(amountDenom.denom);
                        if (searchDenom == denomToAmt.end()) {
                            denomToAmt.insert({amountDenom.denom, BigInt::fromString(amountDenom.amount)});
                            return;
                        }
                        searchDenom->second = searchDenom->second + BigInt::fromString(amountDenom.amount);
                    });
            });
            // Add each pair of denomToAmt into totalAmount
            totalAmount.reserve(denomToAmt.size());
            for (const auto &pair : denomToAmt) {
                totalAmount.emplace_back(pair.second.toString(), pair.first);
            }

            // Insert the global message information in cosmos_messages
            const auto coinsAmount = soci::coinsToString(totalAmount);
            coins.push_back(coinsAmount);
        }

        void clear() {
            MessageBinding::clear();
            coins.clear();
        }
    };
    const auto UPSERT_MESSAGE_MULTISEND = db::stmt<MultiSendMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex),
                soci::use(b.coins);
            });

    // Messages: Fees
    struct FeesMessageBinding: public MessageBinding {
        std::vector<std::string> payerAddress;
        std::vector<std::string> fees;

        void update(std::string const &transactionUid, cosmos::Message const &msg, cosmos::MessageLog const &messageLog) {
            MessageBinding::update(transactionUid, msg, messageLog);
    
            const auto &m = boost::get<cosmos::MsgFees>(msg.content);
            const auto &f = soci::coinToString(m.fees);
            payerAddress.push_back(m.payerAddress);
            fees.push_back(f);
        }

        void clear() {
            MessageBinding::clear();
            payerAddress.clear();
            fees.clear();
        }
    };
    const auto UPSERT_MESSAGE_FEES = db::stmt<FeesMessageBinding>(
        "INSERT INTO cosmos_messages (uid,"
           "transaction_uid, message_type, log,"
           "success, msg_index, from_address, amount) "
           "VALUES (:uid, :tuid, :mt, :log, :success, :mi, :fa, :amount) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.txUid), soci::use(b.type), 
                soci::use(b.log), soci::use(b.success), soci::use(b.messageIndex), 
                soci::use(b.payerAddress), soci::use(b.fees);   
            });

    struct MultiSendInputBinding {
        std::vector<std::string> uid;
        std::vector<std::string> fromAddress;
        std::vector<std::string> inputCoins;
        
        void update(cosmos::Message const &msg) {
            const auto &m = boost::get<cosmos::MsgMultiSend>(msg.content);
            for (const auto &input: m.inputs) {    
                const auto coins = soci::coinsToString(input.coins);
                uid.push_back(msg.uid);
                fromAddress.push_back(input.fromAddress);
                inputCoins.push_back(coins);
            }      
        }

        void clear() {
            uid.clear();
            fromAddress.clear();
            inputCoins.clear();
        }

        bool empty() const {
            return uid.empty();
        }
    };
    const auto UPSERT_MULTISEND_INPUT = db::stmt<MultiSendInputBinding>(
        "INSERT INTO cosmos_multisend_io (message_uid, from_address, amount) "
            "VALUES (:uid, :fa, :amt) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.fromAddress), soci::use(b.inputCoins);  
            });

    struct MultiSendOutputBinding {
        std::vector<std::string> uid;
        std::vector<std::string> toAddress;
        std::vector<std::string> outputCoins;
        
        void update(cosmos::Message const &msg) {
            const auto &m = boost::get<cosmos::MsgMultiSend>(msg.content);
            for (const auto &output: m.outputs) {
                const auto coins = soci::coinsToString(output.coins);
                uid.push_back(msg.uid);
                toAddress.push_back(output.toAddress);
                outputCoins.push_back(coins);
            }          
        }

        void clear() {
            uid.clear();
            toAddress.clear();
            outputCoins.clear();
        }

        bool empty() const {
            return uid.empty();
        }
    };
    const auto UPSERT_MULTISEND_OUTPUT = db::stmt<MultiSendOutputBinding>(
        "INSERT INTO cosmos_multisend_io (message_uid, to_address, amount) "
            "VALUES (:uid, :ta, :amt) "
        "ON CONFLICT DO NOTHING",
            [] (auto& s, auto&  b) {
                s, 
                soci::use(b.uid), soci::use(b.toAddress), soci::use(b.outputCoins);  
            });
}

namespace ledger {
    namespace core {
        void CosmosLikeOperationsDatabaseHelper::bulkInsert(soci::session &sql,
                const std::vector<CosmosLikeOperation> &operations) {
            if (operations.empty())
                return;
            Benchmarker rawInsert("raw_db_insert_cosmos", nullptr);
            rawInsert.start();

            PreparedStatement<OperationBinding> operationStmt;
            PreparedStatement<BlockBinding> blockStmt;
            PreparedStatement<CosmosOperationBinding> cosmosOpStmt;
            PreparedStatement<TransactionBinding> transactionStmt;
            PreparedStatement<MessageBinding> genericMessageStmt;
            PreparedStatement<SendMessageBinding> sendMessageStmt;
            PreparedStatement<DelegateMessageBinding> delegateMessageStmt;
            PreparedStatement<UndelegateMessageBinding> undelegateMessageStmt;
            PreparedStatement<BeginRedelegateMessageBinding> beginRedelegateMessageStmt;
            PreparedStatement<SubmitProposalMessageBinding> submitProposalMessageStmt;
            PreparedStatement<VoteMessageBinding> voteMessageStmt;
            PreparedStatement<DepositMessageBinding> depositMessageStmt;
            PreparedStatement<WithdrawDelegationRewardMessageBinding> withdrawDelegationRewardMessageStmt;
            PreparedStatement<WithdrawDelegatorRewardMessageBinding> withdrawDelegatorRewardMessageStmt;
            PreparedStatement<WithdrawValidatorCommissionMessageBinding> withdrawValidatorCommissionMessageStmt;
            PreparedStatement<SetWithdrawAddressMessageBinding> setWithdrawAddressMessageStmt;
            PreparedStatement<UnjailMessageBinding> unjailMessageStmt;
            PreparedStatement<MultiSendMessageBinding> multiSendMessageStmt;
            PreparedStatement<FeesMessageBinding> feesMessageStmt;
            PreparedStatement<MultiSendInputBinding> multisendInStmt;
            PreparedStatement<MultiSendOutputBinding> multisendOutStmt;

            BulkInsertDatabaseHelper::UPSERT_OPERATION(sql, operationStmt);
            BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
            UPSERT_COSMOS_OPERATION(sql, cosmosOpStmt);
            UPSERT_TRANSACTION(sql, transactionStmt);

            for (auto& op : operations) {
                const auto& tx = std::static_pointer_cast<CosmosLikeTransactionApi>(op.getTransaction())->getRawData();

                // Upsert block
                if (tx.block.nonEmpty()) {
                    blockStmt.bindings.update(tx.block.getValue());
                }
                // Upsert operation
                operationStmt.bindings.update(op);
                // Upsert transaction    
                transactionStmt.bindings.update(tx);
                // messages
                const auto& msg = std::static_pointer_cast<CosmosLikeMessage>(
                    op.getMessage())->getRawData();
                const auto& log = op.getMessageLog();

                switch (cosmos::stringToMsgType(msg.type.c_str())) {
                    case api::CosmosLikeMsgType::MSGSEND: {
                        if (sendMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_SEND(sql, sendMessageStmt);
                        sendMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGDELEGATE: {
                        if (delegateMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_DELEGATE(sql, delegateMessageStmt);
                        delegateMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGUNDELEGATE: {
                        if (undelegateMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_UNDELEGATE(sql, undelegateMessageStmt);
                        undelegateMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGBEGINREDELEGATE: {
                        if (beginRedelegateMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_BEGIN_REDELEGATE(sql, beginRedelegateMessageStmt);
                        beginRedelegateMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGSUBMITPROPOSAL: {
                        if (submitProposalMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_SUBMIT_PROPOSAL(sql, submitProposalMessageStmt);
                        submitProposalMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGVOTE: {
                        if (voteMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_VOTE(sql, voteMessageStmt);
                        voteMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGDEPOSIT: {
                        if (depositMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_DEPOSIT(sql, depositMessageStmt);
                        depositMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGWITHDRAWDELEGATIONREWARD: {
                        if (withdrawDelegationRewardMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_WITHDRAW_DELEGATION_REWARD(sql, withdrawDelegationRewardMessageStmt);
                        withdrawDelegationRewardMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGWITHDRAWDELEGATORREWARD: {
                        if (withdrawDelegatorRewardMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_WITHDRAW_DELEGATOR_REWARD(sql, withdrawDelegatorRewardMessageStmt);
                        withdrawDelegatorRewardMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGWITHDRAWVALIDATORCOMMISSION: {
                        if (withdrawValidatorCommissionMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_WITHDRAW_VALIDATOR_COMMISSION(sql, withdrawValidatorCommissionMessageStmt);
                        withdrawValidatorCommissionMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGSETWITHDRAWADDRESS: {
                        if (setWithdrawAddressMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_SET_WITHDRAW_ADDRESS(sql, setWithdrawAddressMessageStmt);
                        setWithdrawAddressMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGUNJAIL: {
                        if (unjailMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_UNJAIL(sql, unjailMessageStmt);
                        unjailMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGMULTISEND: {
                        if (multiSendMessageStmt.bindings.empty()) {
                            UPSERT_MESSAGE_MULTISEND(sql, multiSendMessageStmt);
                            UPSERT_MULTISEND_INPUT(sql, multisendInStmt);
                            UPSERT_MULTISEND_OUTPUT(sql, multisendOutStmt);
                        }
                        multiSendMessageStmt.bindings.update(tx.uid, msg, log);
                        multisendInStmt.bindings.update(msg);
                        multisendOutStmt.bindings.update(msg);
                    } break;
                    case api::CosmosLikeMsgType::MSGFEES: {
                        if (feesMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_FEES(sql, feesMessageStmt);
                        feesMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                    case api::CosmosLikeMsgType::MSGCREATEVALIDATOR:
                    case api::CosmosLikeMsgType::MSGEDITVALIDATOR:
                    case api::CosmosLikeMsgType::UNSUPPORTED:
                    default: {
                        if (genericMessageStmt.bindings.empty())
                            UPSERT_MESSAGE_GENERIC(sql, genericMessageStmt);
                        genericMessageStmt.bindings.update(tx.uid, msg, log);
                    } break;
                }

                // cosmos operation
                cosmosOpStmt.bindings.update(op.uid, msg.uid);
            }
            
            // 1- block
            if (!blockStmt.bindings.uid.empty()) {
                blockStmt.execute();
            }

            // 2- cosmos_transaction 
            transactionStmt.execute();

            // 3- cosmos_messages
            if (!sendMessageStmt.bindings.empty()) {
                sendMessageStmt.execute();
            }
            if (!delegateMessageStmt.bindings.empty()) {
                delegateMessageStmt.execute();
            }
            if (!undelegateMessageStmt.bindings.empty()) {
                undelegateMessageStmt.execute();
            }
            if (!beginRedelegateMessageStmt.bindings.empty()) {
                beginRedelegateMessageStmt.execute();
            }
            if (!submitProposalMessageStmt.bindings.empty()) {
                submitProposalMessageStmt.execute();
            }
            if (!voteMessageStmt.bindings.empty()) {
                voteMessageStmt.execute();
            }
            if (!depositMessageStmt.bindings.empty()) {
                depositMessageStmt.execute();
            }
            if (!withdrawDelegationRewardMessageStmt.bindings.empty()) {
                withdrawDelegationRewardMessageStmt.execute();
            }
            if (!withdrawDelegatorRewardMessageStmt.bindings.empty()) {
                withdrawDelegatorRewardMessageStmt.execute();
            }
            if (!withdrawValidatorCommissionMessageStmt.bindings.empty()) {
                withdrawValidatorCommissionMessageStmt.execute();
            }
            if (!setWithdrawAddressMessageStmt.bindings.empty()) {
                setWithdrawAddressMessageStmt.execute();
            }
            if (!unjailMessageStmt.bindings.empty()) {
                unjailMessageStmt.execute();
            }
            if (!multiSendMessageStmt.bindings.empty()) {
                multiSendMessageStmt.execute();
            }
            if (!feesMessageStmt.bindings.empty()) {
                feesMessageStmt.execute();
            }
            if (!genericMessageStmt.bindings.empty()) {
                genericMessageStmt.execute();
            }

            // 4- multisend_io
            if (!multisendInStmt.bindings.empty()) {
                multisendInStmt.execute();
            }
            if (!multisendOutStmt.bindings.empty()) {
                multisendOutStmt.execute();
            }

            // 5- operations
            operationStmt.execute();
            
            // 6- cosmos_operations
            cosmosOpStmt.execute();

            rawInsert.stop();
        }
    }
}

<|start_filename|>core/src/api/ERC20LikeAccount.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from erc20.djinni

#include "ERC20LikeAccount.hpp"  // my header
#include "ERC20Token.hpp"
#include "TimePeriod.hpp"

namespace ledger { namespace core { namespace api {

std::string const ERC20LikeAccount::EV_NEW_OP_ERC20_ACCOUNT_UID = {"EV_NEW_OP_ERC20_ACCOUNT_UID"};

} } }  // namespace ledger::core::api


<|start_filename|>core/src/wallet/ethereum/database/EthereumLikeOperationDatabaseHelper.cpp<|end_filename|>
/*
 *
 * EthereumLikeOperationDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 06/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "EthereumLikeOperationDatabaseHelper.hpp"
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <api/BigInt.hpp>
#include <crypto/SHA256.hpp>
#include <unordered_set>
#include <database/soci-backend-utils.h>
#include <debug/Benchmarker.h>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>
#include <wallet/ethereum/database/EthereumLikeTransactionDatabaseHelper.h>
#include <api/ERC20Token.hpp>

using namespace soci;

namespace {
    using namespace ledger::core;

    using Transaction = EthereumLikeBlockchainExplorerTransaction;

    // Transaction
    struct EthereumTransactionBinding {
        std::vector<std::string> uid;
        std::vector<std::string> hash;
        std::vector<uint64_t> nonce;
        std::vector<std::string> value;
        std::vector<Option<std::string>> block;
        std::vector<std::chrono::system_clock::time_point> time;
        std::vector<std::string> sender;
        std::vector<std::string> receiver;
        std::vector<std::string> inputData;
        std::vector<std::string> gasPrice;
        std::vector<std::string> gasLimit;
        std::vector<std::string> gasUsed;
        std::vector<uint64_t> confirmations;
        std::vector<uint64_t> status;

        void update(const Option<std::string> blockUid, const std::string& txUid, const Transaction& tx) {
            uid.push_back(txUid);
            hash.push_back(tx.hash);
            nonce.push_back(tx.nonce);
            value.push_back(tx.value.toHexString());
            block.push_back(blockUid);
            time.push_back(tx.receivedAt);
            sender.push_back(tx.sender);
            receiver.push_back(tx.receiver);
            inputData.push_back(hex::toString(tx.inputData));
            gasPrice.push_back(tx.gasPrice.toHexString());
            gasLimit.push_back(tx.gasLimit.toHexString());
            gasUsed.push_back(tx.gasUsed.getValueOr(BigInt::ZERO).toHexString());
            confirmations.push_back(tx.confirmations);
            status.push_back(tx.status);
        }

    };
    const auto UPSERT_ETHEREUM_TRANSACTION = db::stmt<EthereumTransactionBinding>(
        "INSERT INTO ethereum_transactions VALUES("
        " :tx_uid, :hash, :nonce, :value, :block_uid, :time, :sender, :receiver,"
        " :input_data, :gas_price, :gas_limit, :gas_used, :confirmations,"
        " :status"
        ") ON CONFLICT(transaction_uid) DO UPDATE SET"
        " block_uid = :block_uid, status = :status, gas_used = :gas_used",
        [] (auto& s, auto& b) {
            s,
            use(b.uid, "tx_uid"),
            use(b.hash, "hash"),
            use(b.nonce, "nonce"),
            use(b.value, "value"),
            use(b.block, "block_uid"),
            use(b.time, "time"),
            use(b.sender, "sender"),
            use(b.receiver, "receiver"),
            use(b.inputData, "input_data"),
            use(b.gasPrice, "gas_price"),
            use(b.gasLimit, "gas_limit"),
            use(b.gasUsed, "gas_used"),
            use(b.confirmations, "confirmations"),
            use(b.status, "status");
        }
    );

    // Ethereum operation
    struct EthereumOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> hash;

        void update(const std::string& opUid, const std::string& tUid, const std::string& tHash) {
            uid.push_back(opUid);
            txUid.push_back(tUid);
            hash.push_back(tHash);
        }
    };
    const auto UPSERT_ETHEREUM_OPERATION = db::stmt<EthereumOperationBinding>(
        "INSERT INTO ethereum_operations VALUES("
        ":uid, :tx_uid, :tx_hash"
        ") ON CONFLICT DO NOTHING",
        [] (auto& s, auto& b) {
            s, use(b.uid), use(b.txUid), use(b.hash);
        }
    );

    // ERC20 Token
    struct ERC20TokenBinding {
        std::vector<std::string> contractAddress;
        std::vector<std::string> name;
        std::vector<std::string> symbol;
        std::vector<int32_t> numberOfDecimal;

        void update(const api::ERC20Token& token) {
            contractAddress.push_back(token.contractAddress);
            name.push_back(token.name);
            symbol.push_back(token.symbol);
            numberOfDecimal.push_back(token.numberOfDecimal);
        }
    };
    const auto UPSERT_TOKEN = db::stmt<ERC20TokenBinding>(
        "INSERT INTO erc20_tokens VALUES(:contract_address, :name, :symbol, "
        ":number_of_decimal) ON CONFLICT DO NOTHING",
        [] (auto& s, auto& b) {
            s, use(b.contractAddress), use(b.name), use(b.symbol),
            use(b.numberOfDecimal);
        }
    );

    // ERC20 accounts
    struct ERC20AccountBinding {
        std::vector<std::string> uid;
        std::vector<std::string> accountUid;
        std::vector<std::string> contractAddress;

        void update(const std::string& accUid, const std::shared_ptr<ERC20LikeAccount>& acc) {
            uid.push_back(acc->getUid());
            accountUid.push_back(accUid);
            contractAddress.push_back(acc->getToken().contractAddress);
        }
    };

    const auto UPSERT_ERC20_ACCOUNT = db::stmt<ERC20AccountBinding>(
        "INSERT INTO erc20_accounts VALUES("
        ":uid, :ethereum_account_uid, :contract_address"
        ") ON CONFLICT DO NOTHING",
        [] (auto& s, auto& b) {
            s, use(b.uid), use(b.accountUid), use(b.contractAddress);
        }
    );

    // ERC 20 operations
    struct ERC20OperationBinding {
        std::vector<std::string> ercOpUid;
        std::vector<std::string> ethOpUid;
        std::vector<std::string> accountUid;
        std::vector<std::string> opType;
        std::vector<std::string> hash;
        std::vector<std::string> nonce;
        std::vector<std::string> value;
        std::vector<std::chrono::system_clock::time_point> time;
        std::vector<std::string> gasPrice;
        std::vector<std::string> gasLimit;
        std::vector<std::string> gasUsed;
        std::vector<int64_t> blockHeight;
        std::vector<std::string> sender;
        std::vector<std::string> receiver;
        std::vector<std::string> data;
        std::vector<int32_t> status;

        void update(const std::string& ercAccUid, ERC20LikeOperation& op) {
            ercOpUid.push_back(op.getOperationUid());
            ethOpUid.push_back(op.getETHOperationUid());
            accountUid.push_back(ercAccUid);
            opType.push_back(api::to_string(op.getOperationType()));
            hash.push_back(op.getHash());
            nonce.push_back(op.getNonce()->toString(16));
            value.push_back(op.getValue()->toString(16));
            time.push_back(op.getTime());
            gasPrice.push_back(op.getGasPrice()->toString(16));
            gasLimit.push_back(op.getGasLimit()->toString(16));
            blockHeight.push_back(op.getBlockHeight().value_or(0));
            sender.push_back(op.getSender());
            receiver.push_back(op.getReceiver());
            data.push_back(hex::toString(op.getData()));
            gasUsed.push_back(op.getUsedGas()->toString(16));
            status.push_back(op.getStatus());
        }
    };

    const auto UPSERT_ERC20_OPERATION = db::stmt<ERC20OperationBinding>(
            "INSERT INTO erc20_operations VALUES("
             ":uid, :eth_op_uid, :account_uid, :op_type, :hash, :nonce, :value, :date, :sender,"
             ":receiver, :data, :gas_price, :gas_limit, :gas_used, :status, :block_height"
             ") ON CONFLICT(uid) DO UPDATE SET "
             " status = :status, gas_used = :gas_used",
        [] (auto& s, auto& b) {
            s,
            use(b.ercOpUid, "uid"),
            use(b.ethOpUid, "eth_op_uid"),
            use(b.accountUid, "account_uid"),
            use(b.opType, "op_type"),
            use(b.hash, "hash"),
            use(b.nonce, "nonce"),
            use(b.value, "value"),
            use(b.time, "date"),
            use(b.sender, "sender"),
            use(b.receiver, "receiver"),
            use(b.data, "data"),
            use(b.gasPrice, "gas_price"),
            use(b.gasLimit, "gas_limit"),
            use(b.gasUsed, "gas_used"),
            use(b.status, "status"),
            use(b.blockHeight, "block_height");
        }
    );

    // Internal operations
    struct InternalOperationBinding {
        std::vector<std::string> internalTxUid;
        std::vector<std::string> opUid;
        std::vector<std::string> opType;
        std::vector<std::string> value;
        std::vector<std::string> from;
        std::vector<std::string> to;
        std::vector<std::string> gasLimit;
        std::vector<std::string> gasUsed;
        std::vector<std::string> inputData;

        void update(int index, const std::string& oUid, const InternalTx& tx,
                    const EthereumLikeBlockchainExplorerTransaction& transaction,
                    const std::string& accountAddress) {
            auto type = tx.from == accountAddress ? api::OperationType::SEND :
                        tx.to == accountAddress ? api::OperationType::RECEIVE :
                        api::OperationType::NONE;
            auto uid = OperationDatabaseHelper::createUid(oUid, fmt::format("{}-{}-{}",
                    tx.from, hex::toString(tx.inputData), index), type);
            if (tx.from != transaction.sender || tx.to != transaction.receiver ||
                hex::toString(tx.inputData )!= hex::toString(transaction.inputData)) {
                internalTxUid.push_back(uid);
                opUid.push_back(oUid);
                opType.push_back(api::to_string(type));
                value.push_back(tx.value.toHexString());
                from.push_back(tx.from);
                to.push_back(tx.to);
                gasLimit.push_back(tx.gasLimit.toHexString());
                gasUsed.push_back(tx.gasUsed.map<std::string>([](const auto &g) {
                    return g.toHexString();
                }).getValueOr("00"));
                inputData.push_back(hex::toString(tx.inputData));
            }
        }
    };
    const auto UPSERT_INTERNAL_OPERATION = db::stmt<InternalOperationBinding>(
            "INSERT INTO internal_operations VALUES("
            ":uid, :eth_op_uid, :type, :value, :sender, :receiver, "
            ":gas_limit, :gas_used, :input_data) ON CONFLICT DO NOTHING"
            , [] (auto& s, auto& b) {
                s, use(b.internalTxUid), use(b.opUid), use(b.opType), use(b.value),
                use(b.from), use(b.to), use(b.gasLimit), use(b.gasUsed),
                use(b.inputData);
            }
    );

    const auto UPSERT_OPERATION = db::stmt<OperationBinding>(
            "INSERT INTO operations VALUES("
            ":uid, :account_uid, :wallet_uid, :type, :date, :senders, :recipients, :amount,"
            ":fees, :block_uid, :currency_name, :trust"
            ") ON CONFLICT(uid) DO UPDATE SET block_uid = :block_uid, trust = :trust,"
            " amount = :amount, fees = :fees ", [] (auto& s, auto& b) {
                s, use(b.uid, "uid"), use(b.accountUid, "account_uid"),
                    use(b.walletUid, "wallet_uid"), use(b.type, "type"),
                    use(b.date, "date"), use(b.senders, "senders"),
                    use(b.receivers, "recipients"), use(b.amount, "amount"),
                    use(b.fees, "fees"), use(b.blockUid, "block_uid"),
                    use(b.currencyName, "currency_name"),
                    use(b.serializedTrust, "trust");
            });
}

namespace ledger {
    namespace core {

        void EthereumLikeOperationDatabaseHelper::bulkInsert(session &sql,
                const std::vector<Operation> &ops, const std::string& accountAddress) {
            if (ops.empty())
                return;

            PreparedStatement<OperationBinding> operationStmt;
            PreparedStatement<BlockBinding> blockStmt;
            PreparedStatement<EthereumTransactionBinding> ethTxStmt;
            PreparedStatement<EthereumOperationBinding> ethOpStmt;
            PreparedStatement<ERC20AccountBinding> erc20AccountStmt;
            PreparedStatement<ERC20OperationBinding> erc20OpStmt;
            PreparedStatement<InternalOperationBinding> internalOpStmt;
            PreparedStatement<ERC20TokenBinding> tokenStmt;

            UPSERT_OPERATION(sql, operationStmt);
            BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
            UPSERT_ERC20_ACCOUNT(sql, erc20AccountStmt);
            UPSERT_ERC20_OPERATION(sql, erc20OpStmt);
            UPSERT_ETHEREUM_OPERATION(sql, ethOpStmt);
            UPSERT_ETHEREUM_TRANSACTION(sql, ethTxStmt);
            UPSERT_INTERNAL_OPERATION(sql, internalOpStmt);
            UPSERT_TOKEN(sql, tokenStmt);

            for (const auto& op : ops) {
                if (op.block.nonEmpty())  {
                    blockStmt.bindings.update(op.block.getValue());
                }
                const auto blockUid = op.block.map<std::string>([] (const auto& b) {
                    return b.getUid();
                });
                const auto accountUid = op.accountUid;
                const auto& tx = op.ethereumTransaction.getValue();
                const auto opUid = op.uid;
                auto txUid = EthereumLikeTransactionDatabaseHelper::createEthereumTransactionUid(op.accountUid, tx.hash);
                const auto& data = std::dynamic_pointer_cast<EthereumOperationAttachedData>(op.attachedData);
                auto internalOpIndex = 0;
                for (const auto& internalTx : tx.internalTransactions) {
                    internalOpStmt.bindings.update(internalOpIndex, opUid, internalTx, tx, accountAddress);
                    internalOpIndex += 1;
                }
                if (data) {
                    for (const auto &erc20Account : data->accounts) {
                        auto token = erc20Account->getToken();
                        tokenStmt.bindings.update(token);
                        erc20AccountStmt.bindings.update(accountUid, erc20Account);
                    }
                    for (auto &erc20Op : data->erc20Operations) {
                        erc20OpStmt.bindings.update(
                                std::get<0>(erc20Op), std::get<1>(erc20Op));
                    }
                }
                ethTxStmt.bindings.update(blockUid, txUid, tx);
                ethOpStmt.bindings.update(op.uid, txUid, tx.hash);
                operationStmt.bindings.update(op);
            }
            // Block
            if (!blockStmt.bindings.uid.empty())
                blockStmt.execute();
            // ERC Token
            if (!tokenStmt.bindings.name.empty())
                tokenStmt.execute();
            // ERC20 accounts
            if (!erc20AccountStmt.bindings.uid.empty())
                erc20AccountStmt.execute();
            // Transaction
            ethTxStmt.execute();
            // Operations
            operationStmt.execute();
            // ETH operation
            ethOpStmt.execute();
            // Internal op
            if (!internalOpStmt.bindings.opUid.empty())
                internalOpStmt.execute();
            // ERC20 operation
            if (!erc20OpStmt.bindings.hash.empty())
                erc20OpStmt.execute();

        }

    }
}

<|start_filename|>core/test/coin-integration/common/CoinIntegrationFixture.hpp<|end_filename|>
/*
 *
 * CoinIntegrationFixture.hpp
 * ledger-core
 *
 * Created by <NAME> on 18/02/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_COININTEGRATIONFIXTURE_HPP
#define LEDGER_CORE_COININTEGRATIONFIXTURE_HPP

#include <gtest/gtest.h>
#include <UvThreadDispatcher.hpp>
#include <src/database/DatabaseSessionPool.hpp>
#include <NativePathResolver.hpp>
#include <unordered_set>
#include <src/wallet/pool/WalletPool.hpp>
#include <CoutLogPrinter.hpp>
#include <src/api/DynamicObject.hpp>
#include <wallet/common/CurrencyBuilder.hpp>
#include <wallet/bitcoin/BitcoinLikeWallet.hpp>
#include <wallet/bitcoin/database/BitcoinLikeWalletDatabase.h>
#include <wallet/bitcoin/database/BitcoinLikeTransactionDatabaseHelper.h>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <wallet/pool/database/PoolDatabaseHelper.hpp>
#include <utils/JSONUtils.h>
#include <wallet/bitcoin/explorers/api/TransactionParser.hpp>
#include <wallet/bitcoin/BitcoinLikeAccount.hpp>
#include <wallet/ethereum/EthereumLikeAccount.h>
#include <api/BitcoinLikeOperation.hpp>
#include <api/BitcoinLikeTransaction.hpp>
#include <api/BitcoinLikeInput.hpp>
#include <api/BitcoinLikeOutput.hpp>
#include <api/BigInt.hpp>
#include <CppHttpLibClient.hpp>
#include <events/LambdaEventReceiver.hpp>
#include <soci.h>
#include <api/Account.hpp>
#include <api/BitcoinLikeAccount.hpp>
#include <FakeWebSocketClient.h>
#include <OpenSSLRandomNumberGenerator.hpp>
#include <FilesystemUtils.hpp>
#include <utils/hex.h>
#include "../integration/IntegrationEnvironment.h"

using namespace ledger::core; // don't do this at home. Only for testing contexts
using namespace ledger::core::test;

enum SynchronizationResult {OLD_ACCOUNT, NEW_ACCOUNT};

template <typename Wallet, typename Account>
class CoinIntegrationFixture : public ::testing::Test {
public:
    void SetUp() override {
        ::testing::Test::SetUp();
        FilesystemUtils::clearFs(IntegrationEnvironment::getInstance()->getApplicationDirPath());
        dispatcher = std::make_shared<uv::UvThreadDispatcher>();
        resolver = std::make_shared<NativePathResolver>(IntegrationEnvironment::getInstance()->getApplicationDirPath());
        backend = std::static_pointer_cast<DatabaseBackend>(DatabaseBackend::getSqlite3Backend());
        printer = std::make_shared<CoutLogPrinter>(dispatcher->getMainExecutionContext());
        http = std::make_shared<CppHttpLibClient>(dispatcher->getMainExecutionContext());
        ws = std::make_shared<FakeWebSocketClient>();
        rng = std::make_shared<OpenSSLRandomNumberGenerator>();
    }

    void TearDown() override {
        ::testing::Test::TearDown();
        resolver->clean();
    }

    virtual std::shared_ptr<WalletPool> newPool(std::string poolName = "my_pool") {
        return WalletPool::newInstance(
                poolName,
                "",
                http,
                ws,
                resolver,
                printer,
                dispatcher,
                rng,
                backend,
                api::DynamicObject::newInstance(),
                nullptr,
                nullptr
        );
    }

    std::shared_ptr<uv::SequentialExecutionContext> getTestExecutionContext()
    {
        return std::dynamic_pointer_cast<uv::SequentialExecutionContext>(dispatcher->getSerialExecutionContext("__test__"));
    }

    void injectCurrency(const std::shared_ptr<WalletPool>& pool, const api::Currency& currency) {
        uv::wait(pool->addCurrency(currency));
    }

    std::shared_ptr<Wallet> newWallet(  const std::shared_ptr<WalletPool>& pool,
                                        const std::string& walletName,
                                        const std::string& currencyName,
                                        const std::shared_ptr<api::DynamicObject> &configuration
                                     ) {
        return std::dynamic_pointer_cast<Wallet>(uv::wait(pool->createWallet(walletName, currencyName, configuration)));
    }

    std::shared_ptr<Account> newAccount(const std::shared_ptr<AbstractWallet>& wallet,
                                                                 int32_t index,
                                                                 const api::AccountCreationInfo &info) {
        auto i = info;
        i.index = index;
        return std::dynamic_pointer_cast<Account>(uv::wait(wallet->newAccountWithInfo(i)));
    }

    std::shared_ptr<Account> newAccount(const std::shared_ptr<AbstractWallet>& wallet,
                                                                 int32_t index,
                                                                 const api::ExtendedKeyAccountCreationInfo& info) {
        auto i = info;
        i.index = index;
        return std::dynamic_pointer_cast<Account>(uv::wait(wallet->newAccountWithExtendedKeyInfo(i)));
    }


    Try<SynchronizationResult> synchronizeAccount(const std::shared_ptr<AbstractAccount>& account) {
        Promise<SynchronizationResult> p;
        auto bus = account->synchronize();
        bus->subscribe(dispatcher->getSerialExecutionContext("callback"),
                       make_receiver([=](const std::shared_ptr<api::Event> &event) mutable {
                           if (event->getCode() == api::EventCode::SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT) {
                               p.success(SynchronizationResult::NEW_ACCOUNT);
                           } else if (event->getCode() == api::EventCode::SYNCHRONIZATION_SUCCEED) {
                               p.success(SynchronizationResult::OLD_ACCOUNT);
                           } else if (event->getCode() == api::EventCode::SYNCHRONIZATION_FAILED) {
                               api::ErrorCode code = api::ErrorCode::UNKNOWN;
                               std::string reason = "Unknown error";
                               auto payload = event->getPayload();
                               if (payload && payload->getString(api::Account::EV_SYNC_ERROR_CODE)) {
                                   code = api::from_string<api::ErrorCode>(payload->getString(api::Account::EV_SYNC_ERROR_CODE).value());
                               }
                               if (payload && payload->getString(api::Account::EV_SYNC_ERROR_MESSAGE)) {
                                   reason = payload->getString(api::Account::EV_SYNC_ERROR_MESSAGE).value();
                               }
                               p.failure(make_exception(code, reason));
                           }
                       }));
        return uv::wait(p.getFuture());
    }

    std::shared_ptr<uv::UvThreadDispatcher> dispatcher;
    std::shared_ptr<NativePathResolver> resolver;
    std::shared_ptr<DatabaseBackend> backend;
    std::shared_ptr<CoutLogPrinter> printer;
    std::shared_ptr<CppHttpLibClient> http;
    std::shared_ptr<FakeWebSocketClient> ws;
    std::shared_ptr<OpenSSLRandomNumberGenerator> rng;

};

#endif //LEDGER_CORE_COININTEGRATIONFIXTURE_HPP


<|start_filename|>core/src/wallet/ripple/database/RippleLikeOperationDatabaseHelper.cpp<|end_filename|>
/*
 *
 * RippleLikeOperationDatabaseHelper.cpp
 * ledger-core
 *
 * Created by <NAME> on 07/01/2021.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "RippleLikeOperationDatabaseHelper.hpp"
#include <database/PreparedStatement.hpp>
#include <wallet/common/database/BulkInsertDatabaseHelper.hpp>
#include <wallet/ripple/database/RippleLikeTransactionDatabaseHelper.h>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <database/soci-number.h>

using namespace soci;

namespace {
    using namespace ledger::core;

    // Transaction
    struct TransactionBinding {
        std::vector<std::string> uid;
        std::vector<std::string> hash;
        std::vector<std::string> value;
        std::vector<Option<std::string>> blockUid;
        std::vector<std::chrono::system_clock::time_point> time;
        std::vector<std::string> sender;
        std::vector<std::string> receiver;
        std::vector<std::string> fees;
        std::vector<uint64_t> confirmations;
        std::vector<BigInt> sequence;
        std::vector<Option<uint64_t>> tag;
        std::vector<int32_t> status;

        void update(const std::string& txUid, const Option<std::string>& bUid,
                const RippleLikeBlockchainExplorerTransaction& tx) {
            uid.push_back(txUid);
            hash.push_back(tx.hash);
            value.push_back(tx.value.toHexString());
            blockUid.push_back(bUid);
            time.push_back(tx.receivedAt);
            sender.push_back(tx.sender);
            receiver.push_back(tx.receiver);
            fees.push_back(tx.fees.toHexString());
            confirmations.push_back(tx.confirmations);
            sequence.push_back(tx.sequence);
            tag.push_back(tx.destinationTag);
            status.push_back(tx.status);
        }
    };
    const auto UPSERT_TRANSACTION = db::stmt<TransactionBinding>(
        "INSERT INTO ripple_transactions VALUES("
        ":transaction_uid, :hash, :value, :block_uid, :time, :sender,"
        ":receiver, :fees, :confirmations, :sequence, :tag, :status"
        ") ON CONFLICT(transaction_uid) DO UPDATE SET block_uid = :block_uid, status = :status",
        [] (auto& s, auto& b) {
            s,
            use(b.uid, "transaction_uid"),
            use(b.hash, "hash"),
            use(b.value, "value"),
            use(b.blockUid, "block_uid"),
            use(b.time, "time"),
            use(b.sender, "sender"),
            use(b.receiver, "receiver"),
            use(b.fees, "fees"),
            use(b.confirmations, "confirmations"),
            use(b.sequence, "sequence"),
            use(b.tag, "tag"),
            use(b.status, "status");
        }
    );

    // Ripple Memo
    struct MemoBinding {
        std::vector<std::string> txUid;
        std::vector<std::string> data;
        std::vector<std::string> mfmt;
        std::vector<std::string> ty;
        std::vector<int> index;

        void update(const RippleLikeBlockchainExplorerTransaction& tx) {
            auto i = 0;
            for (const auto& memo : tx.memos) {
                txUid.push_back(tx.hash);
                data.push_back(memo.data);
                mfmt.push_back(memo.fmt);
                ty.push_back(memo.ty);
                index.push_back(i);
                i += 1;
            }
        }
    };
    const auto UPSERT_MEMO = db::stmt<MemoBinding>(
            "INSERT INTO ripple_memos VALUES ("
            ":tx_uid, :data, :fmt, :ty, :i"
            ") ON CONFLICT DO NOTHING",
            [] (auto& s, auto& b) {
                s, use(b.txUid), use(b.data), use(b.mfmt), use(b.ty), use(b.index);
            }
    );

    // Operation
    struct RippleOperationBinding {
        std::vector<std::string> uid;
        std::vector<std::string> txUid;
        std::vector<std::string> txHash;

        void update(const std::string& opUid, const std::string& tUid, const std::string& tHash) {
            uid.push_back(opUid);
            txUid.push_back(tUid);
            txHash.push_back(tHash);
        }
    };
    const auto UPSERT_RIPPLE_OPERATION = db::stmt<RippleOperationBinding>(
        "INSERT INTO ripple_operations VALUES("
        ":uid, :transaction_uid, :transaction_hash"
        ") ON CONFLICT DO NOTHING",
        [] (auto& s, auto& b) {
            s, use(b.uid), use(b.txUid), use(b.txHash);
        }
    );
}

namespace ledger {
    namespace core {

        void RippleLikeOperationDatabaseHelper::bulkInsert(soci::session &sql, const std::vector<Operation> &ops) {
            if (ops.empty())
                return ;
            PreparedStatement<OperationBinding> opStmt;
            PreparedStatement<BlockBinding> blockStmt;
            PreparedStatement<TransactionBinding> txStmt;
            PreparedStatement<RippleOperationBinding> rippleOpStmt;
            PreparedStatement<MemoBinding> memoStmt;

            BulkInsertDatabaseHelper::UPSERT_OPERATION(sql, opStmt);
            BulkInsertDatabaseHelper::UPSERT_BLOCK(sql, blockStmt);
            UPSERT_TRANSACTION(sql, txStmt);
            UPSERT_RIPPLE_OPERATION(sql, rippleOpStmt);
            UPSERT_MEMO(sql, memoStmt);

            for (const auto& op : ops) {
                const auto& tx = op.rippleTransaction.getValue();
                if (op.block.nonEmpty()) {
                    blockStmt.bindings.update(op.block.getValue());
                }
                auto blockUid = op.block.map<std::string>([] (const auto& b) {
                    return b.getUid();
                });
                auto txUid = RippleLikeTransactionDatabaseHelper::createRippleTransactionUid(op.accountUid, tx.hash);
                txStmt.bindings.update(txUid, blockUid, tx);
                memoStmt.bindings.update(tx);
                rippleOpStmt.bindings.update(op.uid, txUid, tx.hash);
                opStmt.bindings.update(op);
            }
            if (!blockStmt.bindings.hash.empty())
                blockStmt.execute();
            txStmt.execute();
            opStmt.execute();
            rippleOpStmt.execute();
        }
    }
}

<|start_filename|>core/src/math/Base58.cpp<|end_filename|>
/*
 *
 * Base58
 * ledger-core
 *
 * Created by <NAME> on 12/12/2016.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "Base58.hpp"
#include <sstream>
#include "../collections/vector.hpp"
#include <crypto/HashAlgorithm.h>
#include <utils/hex.h>
#include <functional>
#include <crypto/Keccak.h>

using namespace ledger::core;
static const std::string DIGITS = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";

static std::string getNetworkIdentifier(const std::shared_ptr<api::DynamicObject> &config) {
    return config->getString("networkIdentifier").value_or("");
}

static std::string getNetworkBase58Dictionary(const std::shared_ptr<api::DynamicObject> &config) {
    return config->getString("base58Dictionary").value_or(DIGITS);
}

static bool shouldUseNetworkBase58Dictionary(const std::shared_ptr<api::DynamicObject> &config) {
    return config->getBoolean("useNetworkDictionary").value_or(false);
}

// refer to https://bitcoin.stackexchange.com/questions/76480/encode-decode-base-58-c
std::string ledger::core::Base58::encode(const std::vector<uint8_t> &bytes,
                                         const std::shared_ptr<api::DynamicObject> &config) {
    auto base58Dictionary = getNetworkBase58Dictionary(config);
    std::string result;
    const double iFactor = 1.36565823730976103695740418120764243208481439700722980119458355862779176747360903943915516885072037696111192757109;
    int len = bytes.size();
    int zeros = 0, length = 0, pbegin = 0, pend;
    pend = len;
    while (pbegin != pend && !bytes[pbegin]) pbegin = ++zeros;
    const int size = 1 + iFactor * (double)(pend - pbegin);
    unsigned char* b58 = new unsigned char[size];
    for (int i = 0; i < size; i++) b58[i] = 0;
    while (pbegin != pend) {
        unsigned int carry = bytes[pbegin];
        int i = 0;
        for (int it1 = size - 1; (carry || i < length) && (it1 != -1); it1--, i++) {
            carry += 256 * b58[it1];
            b58[it1] = carry % 58;
            carry /= 58;
        }
        if (carry) return result;
        length = i;
        pbegin++;
    }
    int it2 = size - length;
    while ((it2 - size) && !b58[it2]) it2++;

    int ri = 0;
    while (ri < zeros) { result += base58Dictionary[0]; ri++; }
    for (; it2 < size; ++it2) result += base58Dictionary[b58[it2]];
    delete[] b58;
    return result;
}

std::string ledger::core::Base58::encodeWithChecksum(const std::vector<uint8_t> &bytes,
                                                     const std::shared_ptr<api::DynamicObject> &config) {

    return encode(vector::concat<uint8_t>(bytes, computeChecksum(bytes, getNetworkIdentifier(config))), config);
}

std::string ledger::core::Base58::encodeWithEIP55(const std::vector<uint8_t> &bytes) {
    auto byteToDigitEIP55 = [](uint8_t byte, uint8_t against) -> char {
        bool uppercase = against >= 0x8;
        byte = (uint8_t) (0xF < byte ? 0xF : byte);
        if (byte < 0xA) {
            return '0' + byte;
        } else if (uppercase) {
            return (char)('A' + (byte - 0xA));
        } else {
            return (char)('a' + (byte - 0xA));
        }
    };

    auto keccakHash = Keccak::keccak256(hex::toString(bytes));

    std::string addressEIP55(bytes.size() * 2, '0');
    for (int i = 0; i < bytes.size(); i++) {
        addressEIP55[i * 2] = byteToDigitEIP55(bytes[i] >> 4, keccakHash[i] >> 4);
        addressEIP55[i * 2 + 1] = byteToDigitEIP55((uint8_t) (bytes[i] & 0xF), (uint8_t) (keccakHash[i] & 0xF));
    }
    return "0x" + addressEIP55;
}

std::string ledger::core::Base58::encodeWithEIP55(const std::string &address) {
    if (address.size() == 42) {
        //Address with 0x prefix
        return Base58::encodeWithEIP55(hex::toByteArray(address.substr(2, address.size() - 2)));
    } else if (address.size() == 40) {
        return Base58::encodeWithEIP55(hex::toByteArray(address));
    }
    throw Exception(api::ErrorCode::INVALID_BASE58_FORMAT, "Invalid base 58 format");
}

// refer to https://bitcoin.stackexchange.com/questions/76480/encode-decode-base-58-c
std::vector<uint8_t> ledger::core::Base58::decode(const std::string &str,
                                                  const std::shared_ptr<api::DynamicObject> &config) {
    auto useBase58Dict = shouldUseNetworkBase58Dictionary(config);
    auto base58Dictionary = useBase58Dict ? getNetworkBase58Dictionary(config) : DIGITS;
    int len = str.size();
    std::vector<uint8_t> result( len * 2 );
    result[0] = 0;
    int resultlen = 1;
    for (int i = 0; i < len; i++) {
        auto carry = base58Dictionary.find(str[i]);
        if (carry == std::string::npos) { throw Exception(api::ErrorCode::INVALID_BASE58_FORMAT, "Invalid base 58 format"); }
        for (int j = 0; j < resultlen; j++) {
            carry += (result[j]) * 58;
            result[j] = (unsigned char)(carry & 0xff);
            carry >>= 8;
        }
        while (carry > 0) {
            result[resultlen++] = carry & 0xff;
            carry >>= 8;
        }
    }

    for (int i = 0; i < len && str[i] == base58Dictionary[0]; i++)
        result[resultlen++] = 0;

    for (int i = resultlen - 1, z = (resultlen >> 1) + (resultlen & 1);
        i >= z; i--) {
        int k = result[i];
        result[i] = result[resultlen - i - 1];
        result[resultlen - i - 1] = k;
    }
    result.resize(resultlen);
    return result;
}

std::vector<uint8_t> ledger::core::Base58::computeChecksum(const std::vector<uint8_t> &bytes,
                                                           const std::string &networkIdentifier) {
    ledger::core::HashAlgorithm hashAlgorithm(networkIdentifier);
    auto hash = hashAlgorithm.bytesToBytesHash(bytes);
    auto doubleHash = hashAlgorithm.bytesToBytesHash(hash);
    return std::vector<uint8_t>(doubleHash.begin(), doubleHash.begin() + 4);
}

ledger::core::Try<std::vector<uint8_t>> ledger::core::Base58::checkAndDecode(const std::string &str,
                                                                             const std::shared_ptr<api::DynamicObject> &config) {
    return Try<std::vector<uint8_t>>::from([&] () {
        auto decoded = decode(str, config);
        //Check decoded address size
        if (decoded.size() <= 4) {
            throw Exception(api::ErrorCode::INVALID_BASE58_FORMAT, "Invalid address : Invalid base 58 format");
        }
        std::vector<uint8_t> data(decoded.begin(), decoded.end() - 4);
        std::vector<uint8_t> checksum(decoded.end() - 4, decoded.end());
        auto chks = computeChecksum(data, getNetworkIdentifier(config));
        if (checksum != chks) {
            throw Exception(api::ErrorCode::INVALID_CHECKSUM, "Base 58 invalid checksum");
        }
        return data;
    });
}

<|start_filename|>core/src/api/DurationMetrics.hpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from core.djinni

#ifndef DJINNI_GENERATED_DURATIONMETRICS_HPP
#define DJINNI_GENERATED_DURATIONMETRICS_HPP

#include <string>
#include <unordered_map>
#ifndef LIBCORE_EXPORT
    #if defined(_MSC_VER)
       #include <libcore_export.h>
    #else
       #define LIBCORE_EXPORT
    #endif
#endif

namespace ledger { namespace core { namespace api {

struct DurationMetric;

class LIBCORE_EXPORT DurationMetrics {
public:
    virtual ~DurationMetrics() {}

    /** Get all duration metrics */
    static std::unordered_map<std::string, DurationMetric> getAllDurationMetrics();
};

} } }  // namespace ledger::core::api
#endif //DJINNI_GENERATED_DURATIONMETRICS_HPP


<|start_filename|>core/test/fixtures/xrp_fixtures.h<|end_filename|>
// This file was GENERATED by command:
//     generate_fixtures.py
// DO NOT EDIT BY HAND!!!
#ifndef LEDGER_FIXTURES_XRP
#define LEDGER_FIXTURES_XRP
#include <gtest/gtest.h>
#include <UvThreadDispatcher.hpp>
#include <src/database/DatabaseSessionPool.hpp>
#include <NativePathResolver.hpp>
#include <unordered_set>
#include <src/wallet/pool/WalletPool.hpp>
#include <CoutLogPrinter.hpp>
#include <src/api/DynamicObject.hpp>
#include <wallet/common/CurrencyBuilder.hpp>
#include <wallet/ripple/explorers/api/RippleLikeTransactionParser.h>
#include <wallet/ripple/RippleLikeWallet.h>
#include <wallet/ripple/database/RippleLikeTransactionDatabaseHelper.h>
#include <wallet/common/database/AccountDatabaseHelper.h>
#include <wallet/pool/database/PoolDatabaseHelper.hpp>
#include <utils/JSONUtils.h>
#include <wallet/ripple/RippleLikeAccount.h>
#include <api/RippleLikeOperation.hpp>
#include <api/RippleLikeTransaction.hpp>
#include <api/BigInt.hpp>
#include <CppHttpLibClient.hpp>
#include <events/LambdaEventReceiver.hpp>
#include <soci.h>
#include <utils/hex.h>
#include <api/Account.hpp>
#include <api/RippleLikeAccount.hpp>

namespace ledger {
	namespace testing {
		namespace xrp {
			extern core::api::AccountCreationInfo XPUB_INFO;
			extern const std::string TX_1;

			std::shared_ptr<core::RippleLikeAccount> inflate(const std::shared_ptr<core::WalletPool>& pool, const std::shared_ptr<core::AbstractWallet>& wallet);
		}
	}
}

#endif // LEDGER_FIXTURES_XRP


<|start_filename|>core/src/wallet/algorand/database/AlgorandTransactionDatabaseHelper.cpp<|end_filename|>
/*
 * AlgorandTransactionDatabaseHelper
 *
 * Created by <NAME> on 18/05/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "AlgorandTransactionDatabaseHelper.hpp"
#include "../AlgorandExplorerConstants.hpp"
#include "../model/transactions/AlgorandPayment.hpp"
#include "../model/transactions/AlgorandKeyreg.hpp"
#include "../model/transactions/AlgorandAsset.hpp"
#include "../utils/B64String.hpp"
#include <database/soci-number.h>
#include <database/soci-date.h>
#include <database/soci-option.h>
#include <crypto/SHA256.hpp>
#include <fmt/format.h>

namespace ledger {
namespace core {
namespace algorand {

    static void putPaymentTransaction(soci::session & sql, const std::string & txUid, const model::Transaction & tx) {
        auto& payment = boost::get<model::PaymentTxnFields>(tx.details);

        auto headerId = optionalValue<std::string>(tx.header.id);
        auto headerGenesisId = optionalValue<std::string>(tx.header.genesisId);
        auto headerRound = optionalValue<uint64_t>(tx.header.round);
        auto headerTimestamp = optionalValue<uint64_t>(tx.header.timestamp);
        auto headerNote = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.note, bytesToB64);
        auto headerGroup = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.group, bytesToB64);
        auto headerLease = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.lease, bytesToB64);
        auto headerSenderRewards = optionalValue(tx.header.senderRewards);
        auto headerReceiverRewards = optionalValue(tx.header.receiverRewards);
        auto headerCloseRewards = optionalValue(tx.header.closeRewards);
        auto paymentCloseAddr = optionalValueWithTransform<Address, std::string>(payment.closeAddr, addrToString);
        auto paymentCloseAmount = optionalValue<uint64_t>(payment.closeAmount);

        sql <<
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "pay_amount, pay_receiver_address, pay_close_address, pay_close_amount) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":amount, :receiver_addr, :close_addr, :close_amount)",
            soci::use(txUid),
            soci::use(headerId),
            soci::use(tx.header.type),
            soci::use(headerRound),
            soci::use(headerTimestamp),
            soci::use(tx.header.firstValid),
            soci::use(tx.header.lastValid),
            soci::use(headerGenesisId),
            soci::use(tx.header.genesisHash.getRawString()),
            soci::use(tx.header.sender.toString()),
            soci::use(tx.header.fee),
            soci::use(headerNote),
            soci::use(headerGroup),
            soci::use(headerLease),
            soci::use(headerSenderRewards),
            soci::use(headerReceiverRewards),
            soci::use(headerCloseRewards),
            soci::use(payment.amount),
            soci::use(payment.receiverAddr.toString()),
            soci::use(paymentCloseAddr),
            soci::use(paymentCloseAmount);
    }

    // NOTE This has not been tested
    static void putKeyRegTransaction(soci::session & sql, const std::string & txUid, const model::Transaction & tx) {
        auto& keyreg = boost::get<model::KeyRegTxnFields>(tx.details);
        auto headerId = optionalValue<std::string>(tx.header.id);
        auto headerGenesisId = optionalValue<std::string>(tx.header.genesisId);
        auto headerRound = optionalValue<uint64_t>(tx.header.round);
        auto headerTimestamp = optionalValue<uint64_t>(tx.header.timestamp);
        auto headerNote = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.note, bytesToB64);
        auto headerGroup = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.group, bytesToB64);
        auto headerLease = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.lease, bytesToB64);
        auto headerSenderRewards = optionalValue(tx.header.senderRewards);
        auto headerReceiverRewards = optionalValue(tx.header.receiverRewards);
        auto headerCloseRewards = optionalValue(tx.header.closeRewards);
        auto keyreg_nonParticipation = optionalValueWithTransform<bool, int32_t>(keyreg.nonParticipation, boolToNum);

        sql <<
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "keyreg_non_participation, keyreg_selection_pk, keyreg_vote_pk, keyreg_vote_key_dilution, keyreg_vote_first, keyreg_vote_last) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":non_part, :selection_pk, :vote_pk, :vote_key_dilution, :vote_first, :vote_last)",
            soci::use(txUid),
            soci::use(headerId),
            soci::use(tx.header.type),
            soci::use(headerRound),
            soci::use(headerTimestamp),
            soci::use(tx.header.firstValid),
            soci::use(tx.header.lastValid),
            soci::use(headerGenesisId),
            soci::use(tx.header.genesisHash.getRawString()),
            soci::use(tx.header.sender.toString()),
            soci::use(tx.header.fee),
            soci::use(headerNote),
            soci::use(headerGroup),
            soci::use(headerLease),
            soci::use(headerSenderRewards),
            soci::use(headerReceiverRewards),
            soci::use(headerCloseRewards),
            soci::use(keyreg_nonParticipation),
            soci::use(keyreg.selectionPk),
            soci::use(keyreg.votePk),
            soci::use(keyreg.voteKeyDilution),
            soci::use(keyreg.voteFirst),
            soci::use(keyreg.voteLast);
    }

    static void putAssetConfigTransaction(soci::session & sql, const std::string & txUid, const model::Transaction & tx) {
        auto& assetConfig = boost::get<model::AssetConfigTxnFields>(tx.details);
        auto& assetParams = *assetConfig.assetParams;
        auto headerId = optionalValue<std::string>(tx.header.id);
        auto headerGenesisId = optionalValue<std::string>(tx.header.genesisId);
        auto headerRound = optionalValue<uint64_t>(tx.header.round);
        auto headerTimestamp = optionalValue<uint64_t>(tx.header.timestamp);
        auto headerNote = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.note, bytesToB64);
        auto headerGroup = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.group, bytesToB64);
        auto headerLease = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.lease, bytesToB64);
        auto headerSenderRewards = optionalValue(tx.header.senderRewards);
        auto headerReceiverRewards = optionalValue(tx.header.receiverRewards);
        auto headerCloseRewards = optionalValue(tx.header.closeRewards);
        auto assetId = optionalValue<uint64_t>(assetConfig.assetId);
        auto assetName = optionalValue<std::string>(assetParams.assetName);
        auto assetUnitname = optionalValue<std::string>(assetParams.unitName);
        auto assetTotal = optionalValue<uint64_t>(assetParams.total);
        auto assetDecimals = optionalValue<uint32_t>(assetParams.decimals);
        auto assetFrozen = optionalValueWithTransform<bool, int32_t>(assetParams.defaultFrozen, boolToNum);
        auto assetCreator = optionalValueWithTransform<Address, std::string>(assetParams.creatorAddr, addrToString);
        auto assetManager = optionalValueWithTransform<Address, std::string>(assetParams.managerAddr, addrToString);
        auto assetReserve = optionalValueWithTransform<Address, std::string>(assetParams.reserveAddr, addrToString);
        auto assetFreeze = optionalValueWithTransform<Address, std::string>(assetParams.freezeAddr, addrToString);
        auto assetClawback = optionalValueWithTransform<Address, std::string>(assetParams.clawbackAddr, addrToString);
        auto assetMetadata = optionalValueWithTransform<std::vector<uint8_t>, std::string>(assetParams.metaDataHash, bytesToB64);
        auto assetUrl = optionalValue<std::string>(assetParams.url);


        sql <<
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "acfg_asset_id, acfg_asset_name, acfg_unit_name, acfg_total, acfg_decimals, acfg_default_frozen, "
                "acfg_creator_address, acfg_manager_address, acfg_reserve_address, acfg_freeze_address, acfg_clawback_address, "
                "acfg_metadata_hash, acfg_url) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":asset_id, :asset_name, :unit_name, :total, :decimals, :default_frozen, :creator_addr, "
                ":manager_addr, :reserve_addr, :freeze_addr, :clawback_addr, :metadata_hash, :url)",
            soci::use(txUid),
            soci::use(headerId),
            soci::use(tx.header.type),
            soci::use(headerRound),
            soci::use(headerTimestamp),
            soci::use(tx.header.firstValid),
            soci::use(tx.header.lastValid),
            soci::use(headerGenesisId),
            soci::use(tx.header.genesisHash.getRawString()),
            soci::use(tx.header.sender.toString()),
            soci::use(tx.header.fee),
            soci::use(headerNote),
            soci::use(headerGroup),
            soci::use(headerLease),
            soci::use(headerSenderRewards),
            soci::use(headerReceiverRewards),
            soci::use(headerCloseRewards),
            soci::use(assetId),
            soci::use(assetName),
            soci::use(assetUnitname),
            soci::use(assetTotal),
            soci::use(assetDecimals),
            soci::use(assetFrozen),
            soci::use(assetCreator),
            soci::use(assetManager),
            soci::use(assetReserve),
            soci::use(assetFreeze),
            soci::use(assetClawback),
            soci::use(assetMetadata),
            soci::use(assetUrl);
    }

    static void putAssetTransferTransaction(soci::session & sql, const std::string & txUid, const model::Transaction & tx) {
        auto& assetTransfer = boost::get<model::AssetTransferTxnFields>(tx.details);
        auto headerId = optionalValue<std::string>(tx.header.id);
        auto headerGenesisId = optionalValue<std::string>(tx.header.genesisId);
        auto headerRound = optionalValue<uint64_t>(tx.header.round);
        auto headerTimestamp = optionalValue<uint64_t>(tx.header.timestamp);
        auto headerNote = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.note, bytesToB64);
        auto headerGroup = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.group, bytesToB64);
        auto headerLease = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.lease, bytesToB64);
        auto headerSenderRewards = optionalValue(tx.header.senderRewards);
        auto headerReceiverRewards = optionalValue(tx.header.receiverRewards);
        auto headerCloseRewards = optionalValue(tx.header.closeRewards);
        auto assetAmount = optionalValue<uint64_t>(assetTransfer.assetAmount);
        auto assetCloseTo = optionalValueWithTransform<Address, std::string>(assetTransfer.assetCloseTo, addrToString);
        auto assetCloseAmount = optionalValue(assetTransfer.closeAmount);
        auto assetSender = optionalValueWithTransform<Address, std::string>(assetTransfer.assetSender, addrToString);

        sql <<
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "axfer_asset_id, axfer_asset_amount, axfer_receiver_address, axfer_close_address, axfer_close_amount, axfer_sender_address) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":assetId, :amount, :receiver_addr, :close_addr, :close_amount, :sender_addr)",
            soci::use(txUid),
            soci::use(headerId),
            soci::use(tx.header.type),
            soci::use(headerRound),
            soci::use(headerTimestamp),
            soci::use(tx.header.firstValid),
            soci::use(tx.header.lastValid),
            soci::use(headerGenesisId),
            soci::use(tx.header.genesisHash.getRawString()),
            soci::use(tx.header.sender.toString()),
            soci::use(tx.header.fee),
            soci::use(headerNote),
            soci::use(headerGroup),
            soci::use(headerLease),
            soci::use(headerSenderRewards),
            soci::use(headerReceiverRewards),
            soci::use(headerCloseRewards),
            soci::use(assetTransfer.assetId),
            soci::use(assetAmount),
            soci::use(assetTransfer.assetReceiver.toString()),
            soci::use(assetCloseTo),
            soci::use(assetCloseAmount),
            soci::use(assetSender);
    }

    // NOTE This has not been tested
    static void putAssetFreezeTransaction(soci::session & sql, const std::string & txUid, const model::Transaction & tx) {
        auto& assetFreeze = boost::get<model::AssetFreezeTxnFields>(tx.details);
        auto headerId = optionalValue<std::string>(tx.header.id);
        auto headerGenesisId = optionalValue<std::string>(tx.header.genesisId);
        auto headerRound = optionalValue<uint64_t>(tx.header.round);
        auto headerTimestamp = optionalValue<uint64_t>(tx.header.timestamp);
        auto headerNote = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.note, bytesToB64);
        auto headerGroup = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.group, bytesToB64);
        auto headerLease = optionalValueWithTransform<std::vector<uint8_t>, std::string>(tx.header.lease, bytesToB64);
        auto headerSenderRewards = optionalValue(tx.header.senderRewards);
        auto headerReceiverRewards = optionalValue(tx.header.receiverRewards);
        auto headerCloseRewards = optionalValue(tx.header.closeRewards);

        sql <<
        "INSERT INTO algorand_transactions ("
                "uid, hash, type, round, timestamp, first_valid, last_valid, genesis_id, genesis_hash, "
                "sender, fee, note, groupVal, leaseVal, sender_rewards, receiver_rewards, close_rewards, "
                "afrz_asset_id, afrz_frozen, afrz_frozen_address) "
        "VALUES(:tx_uid, :hash, :tx_type, :round, :timestamp, :first_valid, :last_valid, :genesis_id, :genesis_hash, "
                ":sender, :fee, :note, :group, :lease, :sender_rewards, :receiver_rewards, :close_rewards, "
                ":asset_id, :frozen, :frozen_addr)",
            soci::use(txUid),
            soci::use(headerId),
            soci::use(tx.header.type),
            soci::use(headerRound),
            soci::use(headerTimestamp),
            soci::use(tx.header.firstValid),
            soci::use(tx.header.lastValid),
            soci::use(headerGenesisId),
            soci::use(tx.header.genesisHash.getRawString()),
            soci::use(tx.header.sender.toString()),
            soci::use(tx.header.fee),
            soci::use(headerNote),
            soci::use(headerGroup),
            soci::use(headerLease),
            soci::use(headerSenderRewards),
            soci::use(headerReceiverRewards),
            soci::use(headerCloseRewards),
            soci::use(assetFreeze.assetId),
            soci::use(static_cast<int32_t>(assetFreeze.assetFrozen)),
            soci::use(assetFreeze.frozenAddress.toString());
    }

    static void inflateTransaction(const soci::row & row, model::Transaction & tx) {

        tx.header.id = getOptionalString(row, COL_TX_HASH);
        tx.header.type = getString(row, COL_TX_TYPE);
        tx.header.round = getOptionalNumber(row, COL_TX_ROUND);
        tx.header.timestamp = getOptionalNumber(row, COL_TX_TIMESTAMP);
        tx.header.firstValid = getNumber(row, COL_TX_FIRST_VALID);
        tx.header.lastValid = getNumber(row, COL_TX_LAST_VALID);
        tx.header.genesisId = getOptionalString(row, COL_TX_GENESIS_ID);
        tx.header.genesisHash = B64String(getString(row, COL_TX_GENESIS_HASH));
        tx.header.sender = Address(getString(row, COL_TX_SENDER));
        tx.header.fee = getNumber(row, COL_TX_FEE);
        tx.header.note = getOptionalStringWithTransform<std::vector<uint8_t>>(row, COL_TX_NOTE, b64toBytes);
        tx.header.group = getOptionalStringWithTransform<std::vector<uint8_t>>(row, COL_TX_GROUP, b64toBytes);
        tx.header.lease = getOptionalStringWithTransform<std::vector<uint8_t>>(row, COL_TX_LEASE, b64toBytes);
        tx.header.senderRewards = getOptionalNumber(row, COL_TX_SENDER_REWARDS);
        tx.header.receiverRewards = getOptionalNumber(row, COL_TX_RECEIVER_REWARDS);
        tx.header.closeRewards = getOptionalNumber(row, COL_TX_CLOSE_REWARDS);

        if (tx.header.type == constants::xPay) {

            model::PaymentTxnFields paymentDetails;

            paymentDetails.amount = getNumber(row, COL_TX_PAY_AMOUNT);
            paymentDetails.receiverAddr = Address(getString(row, COL_TX_PAY_RECEIVER_ADDRESS));
            paymentDetails.closeAddr = getOptionalStringWithTransform<Address>(row, COL_TX_PAY_CLOSE_ADDRESS, stringToAddr);
            paymentDetails.closeAmount = getOptionalNumber(row, COL_TX_PAY_CLOSE_AMOUNT);

            tx.details = paymentDetails;

        } else if (tx.header.type == constants::xKeyregs) {
            // NOTE This has not been tested

            model::KeyRegTxnFields keyregDetails;

            keyregDetails.nonParticipation = getOptionalNumberWithTransform<bool>(row, COL_TX_KEYREG_NONPART, numToBool);
            keyregDetails.selectionPk = getString(row, COL_TX_KEYREG_SELECTION_PK);
            keyregDetails.votePk = getString(row, COL_TX_KEYREG_VOTE_PK);
            keyregDetails.voteKeyDilution = getNumber(row, COL_TX_KEYREG_VOTE_KEY_DILUTION);
            keyregDetails.voteFirst = getNumber(row, COL_TX_KEYREG_VOTE_FIRST);
            keyregDetails.voteLast = getNumber(row, COL_TX_KEYREG_VOTE_LAST);

            tx.details = keyregDetails;

        } else if (tx.header.type == constants::xAcfg) {

            model::AssetConfigTxnFields assetConfigDetails;

            assetConfigDetails.assetId = getOptionalNumber(row, COL_TX_ACFG_ASSET_ID);
            if (row.get_indicator(COL_TX_ACFG_TOTAL) != soci::i_null) {
                model::AssetParams assetParams;

                assetParams.assetName = getOptionalString(row, COL_TX_ACFG_ASSET_NAME);
                assetParams.unitName = getOptionalString(row, COL_TX_ACFG_UNIT_NAME);
                assetParams.total = getOptionalNumber(row, COL_TX_ACFG_TOTAL);
                assetParams.decimals = row.get_indicator(COL_TX_ACFG_DECIMALS) != soci::i_null ? Option<uint32_t>(soci::get_number<uint32_t>(row, COL_TX_ACFG_DECIMALS)) : Option<uint32_t>::NONE;
                assetParams.defaultFrozen = getOptionalNumberWithTransform<bool>(row, COL_TX_ACFG_DEFAULT_FROZEN, numToBool);
                assetParams.creatorAddr = getOptionalStringWithTransform<Address>(row, COL_TX_ACFG_CREATOR_ADDRESS, stringToAddr);
                assetParams.managerAddr = getOptionalStringWithTransform<Address>(row, COL_TX_ACFG_MANAGER_ADDRESS, stringToAddr);
                assetParams.reserveAddr = getOptionalStringWithTransform<Address>(row, COL_TX_ACFG_RESERVE_ADDRESS, stringToAddr);
                assetParams.freezeAddr = getOptionalStringWithTransform<Address>(row, COL_TX_ACFG_FREEZE_ADDRESS, stringToAddr);
                assetParams.clawbackAddr = getOptionalStringWithTransform<Address>(row, COL_TX_ACFG_CLAWBACK_ADDRESS, stringToAddr);
                assetParams.url = getOptionalString(row, COL_TX_ACFG_URL);
                assetParams.metaDataHash = getOptionalStringWithTransform<std::vector<uint8_t>>(row, COL_TX_ACFG_METADATA_HASH, b64toBytes);

                assetConfigDetails.assetParams = assetParams;
            }

            tx.details = assetConfigDetails;

        } else if (tx.header.type == constants::xAxfer) {

            model::AssetTransferTxnFields assetTransferDetails;

            assetTransferDetails.assetId = getNumber(row, COL_TX_AXFER_ASSET_ID);
            assetTransferDetails.assetAmount = getOptionalNumber(row, COL_TX_AXFER_ASSET_AMOUNT);
            assetTransferDetails.assetReceiver = Address(getString(row, COL_TX_AXFER_RECEIVER_ADDRESS));
            assetTransferDetails.assetCloseTo = getOptionalStringWithTransform<Address>(row, COL_TX_AXFER_CLOSE_ADDRESS, stringToAddr);
            assetTransferDetails.closeAmount = getOptionalNumber(row, COL_TX_AXFER_CLOSE_AMOUNT);
            assetTransferDetails.assetSender = getOptionalStringWithTransform<Address>(row, COL_TX_AXFER_SENDER_ADDRESS, stringToAddr);

            tx.details = assetTransferDetails;

        } else if (tx.header.type == constants::xAfreeze) {
            // NOTE This has not been tested

            model::AssetFreezeTxnFields assetFreezeDetails;

            assetFreezeDetails.assetId = getNumber(row, COL_TX_AFRZ_ASSET_ID);
            assetFreezeDetails.assetFrozen = !! getNumber(row, COL_TX_AFRZ_FROZEN);
            assetFreezeDetails.frozenAddress = Address(getString(row, COL_TX_AFRZ_FROZEN_ADDRESS));

            tx.details = assetFreezeDetails;
        }
    }

    bool TransactionDatabaseHelper::transactionExists(soci::session & sql, const std::string & txUid) {
        int32_t count = 0;
        sql << "SELECT COUNT(*) FROM algorand_transactions WHERE uid = :txUid", soci::use(txUid), soci::into(count);
        if (count > 1) {
            throw make_exception(api::ErrorCode::DATABASE_EXCEPTION, "More than one transaction found with uid '{}'.", txUid);
        }
        return count == 1;
    }

    bool TransactionDatabaseHelper::getTransactionByHash(soci::session & sql,
                                                         const std::string & hash,
                                                         model::Transaction & tx) {

        int32_t count = 0;
        sql << "SELECT COUNT(*) FROM algorand_transactions WHERE hash = :hash", soci::use(hash), soci::into(count);
        if (count > 1) {
            throw make_exception(api::ErrorCode::DATABASE_EXCEPTION, "More than one transaction found with hash '{}'.", hash);
        }

        soci::rowset<soci::row> rows = (sql.prepare <<
                "SELECT *"
                "FROM algorand_transactions AS tx "
                "WHERE tx.hash = :hash", soci::use(hash));

        for (auto &row : rows) {
            inflateTransaction(row, tx);
            return true;
        }

        return false;
    }

    std::string TransactionDatabaseHelper::putTransaction(soci::session & sql,
                                                          const std::string & accountUid,
                                                          const model::Transaction & tx) {
        const auto txUid = *tx.header.id;

        if (!transactionExists(sql, txUid)) {
            if (tx.header.type == constants::xPay) {
                putPaymentTransaction(sql, txUid, tx);
            } else if (tx.header.type == constants::xKeyregs) {
                putKeyRegTransaction(sql, txUid, tx);
            } else if (tx.header.type == constants::xAcfg) {
                putAssetConfigTransaction(sql, txUid, tx);
            } else if (tx.header.type == constants::xAxfer) {
                putAssetTransferTransaction(sql, txUid, tx);
            } else if (tx.header.type == constants::xAfreeze) {
                putAssetFreezeTransaction(sql, txUid, tx);
            }
        }

        return txUid;
    }

    void TransactionDatabaseHelper::deleteAllTransactions(soci::session& sql)
    {
        sql<<"DELETE FROM algorand_transactions";
    }

    std::vector<model::Transaction> TransactionDatabaseHelper::queryAssetTransferTransactionsInvolving(
            soci::session& sql,
            uint64_t assetId,
            const std::string& address)
    {
        soci::rowset<soci::row> rows = (sql.prepare <<
                "SELECT *"
                "FROM algorand_transactions AS tx "
                "WHERE tx.type = :type "
                "AND tx.axfer_asset_id = :axfer_asset_id "
                "AND (tx.axfer_receiver_address = :axfer_receiver_address "
                "OR tx.axfer_close_address = :axfer_close_address "
                "OR tx.axfer_sender_address = :axfer_sender_address "
                "OR tx.sender = :sender) "
                "ORDER BY timestamp",
                soci::use(std::string(model::constants::axfer)), soci::use(assetId),
                soci::use(address), soci::use(address),
                soci::use(address), soci::use(address));

        return query(rows);
    }

    std::vector<model::Transaction> TransactionDatabaseHelper::queryTransactionsInvolving(
            soci::session& sql,
            const std::string& address)
    {
        soci::rowset<soci::row> rows = (sql.prepare <<
                "SELECT *"
                "FROM algorand_transactions AS tx "
                "WHERE tx.sender = :sender "
                "OR tx.pay_receiver_address = :pay_receiver_address "
                "OR tx.pay_close_address = :pay_close_address "
                "ORDER BY timestamp",
                soci::use(address), soci::use(address), soci::use(address));

        return query(rows);
    }

    std::vector<model::Transaction> TransactionDatabaseHelper::query(const soci::rowset<soci::row>& rows)
    {
        auto transactions = std::vector<model::Transaction>();
        for (const auto& row : rows) {
            const auto tx = [&row]() {
                auto tx = model::Transaction();
                inflateTransaction(row, tx);
                return tx;
            }();
            transactions.push_back(tx);
        }
        return transactions;
    }

   void TransactionDatabaseHelper::eraseDataSince(
                soci::session &sql,
                const std::string &accountUid,
                const std::chrono::system_clock::time_point & date) {
                    
        soci::rowset<std::string> rows = (sql.prepare <<
            "SELECT transaction_uid FROM algorand_operations AS sop "
            "JOIN operations AS op ON sop.uid = op.uid "
            "WHERE op.account_uid = :uid AND op.date >= :date", 
            soci::use(accountUid), soci::use(date)
        );
        std::vector<std::string> txToDelete(rows.begin(), rows.end());
        if (!txToDelete.empty()) {
            sql << "DELETE FROM operations WHERE account_uid = :account_uid AND date >= :date", 
                soci::use(accountUid), soci::use(date);
            sql << "DELETE FROM algorand_transactions " 
                "WHERE uid IN (:uids)", soci::use(txToDelete);
        }
    }

} // namespace algorand
} // namespace core
} // namespace ledger



<|start_filename|>core/src/wallet/bitcoin/synchronizers/BlockchainExplorerAccountSynchronizer.cpp<|end_filename|>
/*
 *
 * BlockchainExplorerAccountSynchronizer
 * ledger-core
 *
 * Created by <NAME> on 26/05/2017.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "BlockchainExplorerAccountSynchronizer.h"
#include <wallet/bitcoin/BitcoinLikeAccount.hpp>
#include <wallet/bitcoin/database/BitcoinLikeTransactionDatabaseHelper.h>
#include <async/algorithm.h>
#include <async/FutureUtils.hpp>
#include <debug/Benchmarker.h>

#define NEW_BENCHMARK(x) std::make_shared<Benchmarker>(fmt::format(x"/{}", buddy->synchronizationTag), buddy->logger)

namespace ledger {
    namespace core {

        constexpr auto ADDRESS_BATCH_SIZE = 10;

        using Transaction = BitcoinLikeBlockchainExplorerTransaction;
        using Input = BitcoinLikeBlockchainExplorerInput;
        using CommonBuddy = BlockchainExplorerAccountSynchronizer::SynchronizationBuddy;
        using TransactionMap = std::unordered_map<std::string, std::pair<Transaction, bool /* replaceable */>>;

        struct BitcoinSynchronizationBuddy : public CommonBuddy {
            std::vector<BitcoinLikeBlockchainExplorerTransaction> mempoolTransaction;
            std::vector<BitcoinLikeBlockchainExplorerTransaction> previousMempool;
        };

        BlockchainExplorerAccountSynchronizer::BlockchainExplorerAccountSynchronizer(
                const std::shared_ptr<WalletPool> &pool,
                const std::shared_ptr<BitcoinLikeBlockchainExplorer> &explorer) :
                DedicatedContext(pool->getDispatcher()
                                     ->getThreadPoolExecutionContext("synchronizers")) {
            _explorer = explorer;
        }



        void BlockchainExplorerAccountSynchronizer::updateTransactionsToDrop(soci::session &sql,
                                                                             std::shared_ptr<SynchronizationBuddy> &buddy,
                                                                             const std::string &accountUid) {
            //Get all transactions in DB that may be dropped (txs without block_uid)
            soci::rowset<soci::row> rows = (sql.prepare << "SELECT op.uid, btc_op.transaction_hash FROM operations AS op "
                                                            "LEFT OUTER JOIN bitcoin_operations AS btc_op ON btc_op.uid = op.uid "
                                                            "WHERE op.block_uid IS NULL AND op.account_uid = :uid ", soci::use(accountUid));

            // Remove all mempool transaction
            BitcoinLikeTransactionDatabaseHelper::removeAllMempoolOperation(sql, accountUid);
            auto btcBuddy = std::static_pointer_cast<BitcoinSynchronizationBuddy>(buddy);
            // Get all operation in mempool (will be used to restore mempool in case of error)
            BitcoinLikeTransactionDatabaseHelper::getMempoolTransactions(sql, accountUid, btcBuddy->previousMempool);

            for (auto &row : rows) {
                if (row.get_indicator(0) != soci::i_null && row.get_indicator(1) != soci::i_null) {
                    buddy->transactionsToDrop.insert(std::pair<std::string, std::string>(row.get<std::string>(1), row.get<std::string>(0)));
                }
            }

        }


        void BlockchainExplorerAccountSynchronizer::reset(const std::shared_ptr<BitcoinLikeAccount>& account,
                                                          const std::chrono::system_clock::time_point& toDate) {

        }

        std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>> BlockchainExplorerAccountSynchronizer::synchronize(const std::shared_ptr<BitcoinLikeAccount>& account) {
            return synchronizeAccount(account);
        }

        bool BlockchainExplorerAccountSynchronizer::isSynchronizing() const {
            return _notifier != nullptr;
        }

        std::shared_ptr<BlockchainExplorerAccountSynchronizer> BlockchainExplorerAccountSynchronizer::getSharedFromThis() {
            return shared_from_this();
        }

        std::shared_ptr<api::ExecutionContext> BlockchainExplorerAccountSynchronizer::getSynchronizerContext() {
            return getContext();
        }

        void BlockchainExplorerAccountSynchronizer::interpretTransaction(const Transaction& transaction,
                                                                        const std::shared_ptr<SynchronizationBuddy>& buddy,
                                                                        std::vector<Operation>& out) {
            // In case the transaction is in mempool, keep in memory before inserting it in database.
            if (transaction.block.isEmpty()) {
                std::static_pointer_cast<BitcoinSynchronizationBuddy>(buddy)->mempoolTransaction.emplace_back(
                        transaction);
                auto keychain = buddy->account->getKeychain();
                auto const markAddress = [&keychain](auto &txIo) {
                    auto flag = 0;

                    for (auto &io : txIo) {
                        if (io.address.hasValue()) {
                            keychain->markAsUsed(io.address.getValue(), false);
                            flag |= BitcoinLikeAccount::FLAG_TRANSACTION_CREATED_SENDING_OPERATION;
                        }
                    }
                    return flag;
                };
                markAddress(transaction.inputs);
                markAddress(transaction.outputs);
            } else {
                buddy->account->interpretTransaction(transaction, out);
            }
        }

        std::shared_ptr<CommonBuddy>
        BlockchainExplorerAccountSynchronizer::makeSynchronizationBuddy() {
            return std::make_shared<BitcoinSynchronizationBuddy>();
        }

        inline bool isInputReplaced(const Input& input, uint32_t conflictingInputSequence) {
            return input.sequence < conflictingInputSequence;
        }

        inline bool isInputReplaceable(const Input& input) {
            return input.sequence < std::numeric_limits<uint32_t>::max();
        }

        void filterReplacedTransaction(const std::vector<Transaction>& transactions, TransactionMap& txByHash) {
            using InputWithTxHash = std::tuple<std::string /* tx hash */, uint32_t /* input sequence */>;
            std::unordered_map<std::string, InputWithTxHash> txByIntputs;
            for (const auto& tx : transactions) {
                if (txByHash.find(tx.hash) != txByHash.end()) {
                    // Transaction is already in result map, don't go though all filtering.
                    continue;
                }
                bool replaceable = false;
                bool replaced = false;
                for (const auto& input : tx.inputs) {
                    if (!input.previousTxHash || !input.previousTxOutputIndex)
                        continue;
                    auto inputId = fmt::format("{}{}", input.previousTxHash.getValue(), input.previousTxOutputIndex.getValue());
                    auto entry = txByIntputs.find(inputId);
                    if (entry == txByIntputs.end()) {
                        txByIntputs[inputId] = std::make_pair(tx.hash, input.sequence);
                    } else if (!isInputReplaced(input, std::get<1>(entry->second))) {
                        replaced = replaced || (std::get<0>(entry->second) == tx.hash);
                        txByHash.erase(std::get<0>(entry->second));
                        txByIntputs[inputId] = std::make_pair(tx.hash, input.sequence);
                    } else {
                        replaced = true;
                        break;
                    }
                    replaceable = replaceable || isInputReplaceable(input);
                }
                if (!replaced)
                    txByHash[tx.hash] = std::make_pair(tx, replaceable);
            }
        }

        Future<std::vector<Transaction>> getMempoolTransactionBatch(const std::shared_ptr<CommonBuddy>& buddy,
                                                                    const api::Block& lastBlock,
                                                                    const std::vector<std::string>& addresses) {
            return buddy->account->getExplorer()->getTransactions(addresses, Option<std::string>(lastBlock.blockHash))
            .map<std::vector<Transaction>>(buddy->account->getContext(), [] (const auto& bulk) {
                std::vector<Transaction> mempoolTxs;
                for (const auto& tx : bulk->transactions) {
                    if (!tx.block.hasValue())
                        mempoolTxs.emplace_back(tx);
                }
                return mempoolTxs;
            });
        }

        Future<std::vector<Transaction>> getMempoolTransactions(const std::shared_ptr<CommonBuddy>& buddy,
                                                                const std::vector<std::string>& addresses) {
            using TransactionBatchVector = std::vector<std::vector<Transaction>>;
            return buddy->account->getLastBlock()
            .flatMap<TransactionBatchVector>(buddy->account->getContext(), [=] (const auto& block) {
                std::vector<Future<std::vector<Transaction>>> getTxs;
                for(std::size_t i = 0; i < addresses.size(); i += ADDRESS_BATCH_SIZE) {
                    auto last = std::min(addresses.size(), i + ADDRESS_BATCH_SIZE);
                    std::vector<std::string> batch(addresses.begin() + i, addresses.begin() + last);
                    getTxs.emplace_back(getMempoolTransactionBatch(buddy, block, batch));
                }
                return async::sequence(buddy->account->getContext(), getTxs);
            }).map<std::vector<Transaction>>(buddy->account->getContext(), [] (const auto& batches) {
                std::vector<Transaction> mempoolTxs;
                for (const auto& batch : batches) {
                    mempoolTxs.insert(mempoolTxs.end(), batch.begin(), batch.end());
                }
                return mempoolTxs;
            });
        }

        Future<Unit> resolveMempool(const std::shared_ptr<BitcoinSynchronizationBuddy>& buddy) {
            // Do a first round of filtering with transaction bound to the account
            TransactionMap txsByHash;
            filterReplacedTransaction(buddy->mempoolTransaction, txsByHash);
            // Get all transaction which are still replaceable and create a list of addresses
            std::vector<Transaction> notReplaceableTxs;
            std::vector<Transaction> replaceableTxs;
            std::unordered_set<std::string> foreignAddresses;

            for (const auto& txWithHash : txsByHash) {
                if (txWithHash.second.second) {
                    // Tx is replaceable add it in the list to filter later
                    // and go though all inputs to find foreign address
                    replaceableTxs.emplace_back(txWithHash.second.first);
                    for (const auto& input : txWithHash.second.first.inputs) {
                        if (input.address.hasValue() &&
                            !buddy->account->getKeychain()->contains(input.address.getValue())) {
                            foreignAddresses.insert(input.address.getValue());
                        }
                    }
                } else {
                    notReplaceableTxs.emplace_back(txWithHash.second.first);
                }
            }
            // Use the list of addresses to get all mempool transaction for those addresses
            return getMempoolTransactions(buddy,
                    std::vector<std::string>(foreignAddresses.begin(), foreignAddresses.end()))
            .map<Unit>(buddy->account->getContext(), [=] (const auto& foreignTxs) {
                // Do another round of filtering on replaceable transaction vs foreign transactions
                TransactionMap filteredTxs;
                std::vector<Transaction> txs(replaceableTxs.begin(), replaceableTxs.end());
                txs.insert(txs.end(), foreignTxs.begin(), foreignTxs.end());
                filterReplacedTransaction(txs, filteredTxs);
                // Insert everything in database
                std::vector<Operation> operations;
                // Insert not replaceable transactions
                for (const auto& tx : notReplaceableTxs) {
                    buddy->account->interpretTransaction(tx, operations);
                }
                // Insert replaceable transaction
                for (const auto& entry : filteredTxs) {
                    buddy->account->interpretTransaction(entry.second.first, operations);
                }
                buddy->account->bulkInsert(operations);
                return unit;
            });
        }

        Future<std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>> BlockchainExplorerAccountSynchronizer::requestTransactionsFromExplorer(
            const std::shared_ptr<SynchronizationBuddy>& buddy) {            
            auto nbBatch = this->_addresses.size() / (buddy->halfBatchSize * 2);
            std::vector<Future <std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>> batches;
            for (int currentBatchIndex = 0; currentBatchIndex < nbBatch; currentBatchIndex++) {
                auto pair = getHashkeyAndBlockhash(currentBatchIndex, buddy);
                auto key = pair.first;
                if (_cachedTransactionBulks.find(key) != _cachedTransactionBulks.end())
                    continue;
                int from = currentBatchIndex * buddy->halfBatchSize * 2;
                int to = (currentBatchIndex + 1) * buddy->halfBatchSize * 2;
                auto batch = std::vector<std::string>(this->_addresses.begin() + from, this->_addresses.begin() + to);
                auto blockhash = pair.second;
                batches.emplace_back(_explorer->getTransactions(batch, blockhash, optional<void*>()));
                _hashkeys.emplace_back(key);
            }
            if (batches.size() == 0) { //return an empty std::vector if no need to request the explorer
                return Future<std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>>::async(buddy->account->getContext(),
                    [=]()-> std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>> {
                        return std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>();
                    }
                );
            }
            return executeAll(buddy->account->getContext(), batches);
        }

        Future<Unit> BlockchainExplorerAccountSynchronizer::synchronizeMempool(
                const std::shared_ptr<SynchronizationBuddy> &buddy) {
            auto bitcoinBuddy = std::static_pointer_cast<BitcoinSynchronizationBuddy>(buddy);
            return resolveMempool(bitcoinBuddy);
        }

        Future<Unit> BlockchainExplorerAccountSynchronizer::recoverFromFailedSynchronization(
                const std::shared_ptr<CommonBuddy> &commonBuddy) {
            auto buddy = std::static_pointer_cast<BitcoinSynchronizationBuddy>(commonBuddy);
            return Future<Unit>::async(buddy->account->getContext(), [buddy] () {
                std::vector<Operation> operations;
                for (const auto& tx : buddy->previousMempool) {
                    buddy->account->interpretTransaction(tx, operations);
                }
                buddy->account->bulkInsert(operations);
                return unit;
            });
        }

        void BlockchainExplorerAccountSynchronizer::initializeSavedState(Option<BlockchainExplorerAccountSynchronizationSavedState>& savedState,
            int32_t halfBatchSize) {
            if (savedState.hasValue() && savedState.getValue()
                .halfBatchSize != halfBatchSize) {
                BlockchainExplorerAccountSynchronizationBatchSavedState block;
                block.blockHeight = 1U << 31U;

                for (auto& state : savedState.getValue()
                    .batches) {
                    if (state.blockHeight < block.blockHeight) {
                        block = state;
                    }
                }
                auto newBatchCount = (savedState.getValue()
                    .batches.size() * savedState.getValue()
                    .halfBatchSize) / halfBatchSize;
                if ((savedState.getValue()
                    .batches.size() * savedState.getValue()
                    .halfBatchSize) / halfBatchSize != 0)
                    newBatchCount += 1;
                savedState.getValue()
                    .batches
                    .clear();
                savedState.getValue()
                    .halfBatchSize = (uint32_t)halfBatchSize;
                for (auto i = 0; i <= newBatchCount; i++) {
                    BlockchainExplorerAccountSynchronizationBatchSavedState s;
                    s.blockHash = block.blockHash;
                    s.blockHeight = block.blockHeight;
                    savedState.getValue().batches.push_back(s);
                }
            }
            else if (savedState.isEmpty()) {
                savedState = Option<BlockchainExplorerAccountSynchronizationSavedState>(
                    BlockchainExplorerAccountSynchronizationSavedState());
                savedState.getValue()
                    .halfBatchSize = (uint32_t)halfBatchSize;
            }
        };

        std::shared_ptr<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>> BlockchainExplorerAccountSynchronizer::synchronizeAccount(const std::shared_ptr<BitcoinLikeAccount>& account) {
            std::lock_guard<std::mutex> lock(_lock);
            if (!_currentAccount) {
                _currentAccount = account;
                _notifier = std::make_shared<ProgressNotifier<BlockchainExplorerAccountSynchronizationResult>>();
                auto self = getSharedFromThis();
                performSynchronization(account).onComplete(getSynchronizerContext(), [self](auto const& result) {
                    std::lock_guard<std::mutex> l(self->_lock);
                    if (result.isFailure()) {
                        self->_notifier->failure(result.getFailure());
                    }
                    else {
                        self->_notifier->success(result.getValue());
                    }
                    self->_notifier = nullptr;
                    self->_currentAccount = nullptr;
                    });

            }
            else if (account != _currentAccount) {
                throw make_exception(api::ErrorCode::RUNTIME_ERROR, "This synchronizer is already in use");
            }
            return _notifier;
        };


        Future<BlockchainExplorerAccountSynchronizationResult> BlockchainExplorerAccountSynchronizer::performSynchronization(const std::shared_ptr<BitcoinLikeAccount>& account)
        {
            auto buddy = makeSynchronizationBuddy();
            buddy->account = account;
            buddy->preferences = std::static_pointer_cast<AbstractAccount>(account)->getInternalPreferences()
                ->getSubPreferences("AbstractBlockchainExplorerAccountSynchronizer");
            auto loggerPurpose = fmt::format("synchronize_{}", account->getAccountUid());
            auto tracePrefix = fmt::format("{}/{}/{}", account->getWallet()->getPool()->getName(), account->getWallet()->getName(), account->getIndex());
            buddy->synchronizationTag = tracePrefix;
            buddy->logger = logger::trace(loggerPurpose, tracePrefix, account->logger());
            buddy->startDate = DateUtils::now();
            buddy->wallet = account->getWallet();
            buddy->configuration = std::static_pointer_cast<AbstractAccount>(account)->getWallet()->getConfig();
            buddy->halfBatchSize = (uint32_t)buddy->configuration
                ->getInt(api::Configuration::SYNCHRONIZATION_HALF_BATCH_SIZE)
                .value_or(api::ConfigurationDefaults::KEYCHAIN_DEFAULT_OBSERVABLE_RANGE);
            buddy->keychain = account->getKeychain();
            buddy->savedState = buddy->preferences
                ->template getObject<BlockchainExplorerAccountSynchronizationSavedState>("state");
            buddy->logger
                ->info("Starting synchronization for account#{} ({}) of wallet {} at {}",
                    account->getIndex(),
                    account->getKeychain()->getRestoreKey(),
                    account->getWallet()->getName(), DateUtils::toJSON(buddy->startDate));

            //Check if reorganization happened
            soci::session sql(buddy->wallet->getDatabase()->getPool());
            if (buddy->savedState.nonEmpty()) {

                //Get deepest block saved in batches to be part of reorg
                auto sortedBatches = buddy->savedState.getValue().batches;
                std::sort(sortedBatches.begin(), sortedBatches.end(), [](const BlockchainExplorerAccountSynchronizationBatchSavedState& lhs,
                    const BlockchainExplorerAccountSynchronizationBatchSavedState& rhs) -> bool {
                        return lhs.blockHeight < rhs.blockHeight;
                    });

                auto currencyName = buddy->wallet->getCurrency().name;
                size_t index = 0;
                //Reorg can't happen until genesis block, safely initialize with 0
                uint64_t deepestFailedBlockHeight = 0;
                while (index < sortedBatches.size() && !BlockDatabaseHelper::blockExists(sql, sortedBatches[index].blockHash, currencyName)) {
                    deepestFailedBlockHeight = sortedBatches[index].blockHeight;
                    index++;
                }

                //Case of reorg, update savedState's batches
                if (deepestFailedBlockHeight > 0) {
                    //Get last block (in DB) which contains current account's operations
                    auto previousBlock = AccountDatabaseHelper::getLastBlockWithOperations(sql, buddy->account->getAccountUid());
                    for (auto& batch : buddy->savedState.getValue().batches) {
                        if (batch.blockHeight >= deepestFailedBlockHeight) {
                            batch.blockHeight = previousBlock.nonEmpty() ? (uint32_t)previousBlock.getValue().height : 0;
                            batch.blockHash = previousBlock.nonEmpty() ? previousBlock.getValue().blockHash : "";
                        }
                    }
                }
            }

            initializeSavedState(buddy->savedState, buddy->halfBatchSize);

            updateTransactionsToDrop(sql, buddy, account->getAccountUid());

            auto self = getSharedFromThis();
            self->_addresses.clear();
            self->_cachedTransactionBulks.clear();
            self->_hashkeys.clear();
            _explorerBenchmark = NEW_BENCHMARK("explorer_calls");
            return self->updateCurrentBlock(buddy).template flatMap<Unit>(account->getContext(), [self, buddy](std::shared_ptr <BitcoinLikeBlockchainExplorer::Block> block) {
                soci::session sql(buddy->account->getWallet()->getDatabase()->getPool());
                soci::transaction tr(sql);
                try {
                    buddy->account->putBlock(sql, *block);
                    tr.commit();
                }
                catch (...) {
                    tr.rollback();
                }
                return self->extendKeychain(0, buddy);
                }).template flatMap<std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>>(account->getContext(), [buddy, self](const Unit&) {
                self->_explorerBenchmark->start();
                return self->requestTransactionsFromExplorer(buddy);})
                .template flatMap<Unit>(account->getContext(), [buddy, self](const std::vector<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>& txBulks) {
                    self->_explorerBenchmark->stop();
                    for (int i = 0; i < txBulks.size(); i++) {
                        self->_cachedTransactionBulks.insert(std::make_pair(self->_hashkeys[i], txBulks[i]));
                    }                    
                    return self->synchronizeBatches(0, buddy);
                }).template flatMap<Unit>(account->getContext(), [self, buddy](auto) {
                    return self->synchronizeMempool(buddy);
                    }).template map<BlockchainExplorerAccountSynchronizationResult>(ImmediateExecutionContext::INSTANCE, [self, buddy](const Unit&) {
                        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
                            (DateUtils::now() - buddy->startDate.time_since_epoch()).time_since_epoch());
                        buddy->logger->info("End synchronization for account#{} of wallet {} in {}", buddy->account->getIndex(),
                            buddy->account->getWallet()->getName(), DurationUtils::formatDuration(duration));

                        auto const& batches = buddy->savedState.getValue().batches;

                        // get the last block height treated during the synchronization
                        // std::max_element returns an iterator hence the indirection here
                        // We use an constant iterator variable for readability purpose
                        auto const batchIt = std::max_element(
                            std::cbegin(batches),
                            std::cend(batches),
                            [](auto const& lhs, auto const& rhs) {
                                return lhs.blockHeight < rhs.blockHeight;
                            });
                        soci::session sql(buddy->wallet->getDatabase()->getPool());
                        buddy->context.lastBlockHeight = BlockDatabaseHelper::getLastBlock(sql,
                            buddy->wallet->getCurrency().name).template map<uint64_t>([](const Block& block) {
                                return block.height;
                                }).getValueOr(0);

                                self->_currentAccount = nullptr;
                                return buddy->context;
                        }).recover(ImmediateExecutionContext::INSTANCE, [self, buddy](const Exception& ex) {
                            auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
                                (DateUtils::now() - buddy->startDate.time_since_epoch()).time_since_epoch());
                            buddy->logger->error("Error during during synchronization for account#{} of wallet {} in {} ms", buddy->account->getIndex(),
                                buddy->account->getWallet()->getName(), duration.count());
                            buddy->logger->error("Due to {}, {}", api::to_string(ex.getErrorCode()), ex.getMessage());
                            return buddy->context;
                            });
        };

        // extend the keychain to cover all the addresses(only for bitcoin)
        Future<Unit> BlockchainExplorerAccountSynchronizer::extendKeychain(uint32_t currentBatchIndex, std::shared_ptr<SynchronizationBuddy> buddy) {
            buddy->logger->info("Detecting addresses for batch {}", currentBatchIndex);
            auto self = getSharedFromThis();
            auto from = currentBatchIndex / 2 * buddy->halfBatchSize;
            auto to = (currentBatchIndex + 1) * buddy->halfBatchSize - 1;
            buddy->logger->info("From address index {}", from);
            buddy->logger->info("To address index {}", to);
            auto batch = buddy->keychain->getAllObservableAddressString(from, to);
            auto lastAddressesinBatch = std::vector<std::string>(batch.end() - 2 * buddy->halfBatchSize, batch.end());
            self->_addresses.insert(self->_addresses.end(), batch.begin(), batch.end());
            self->_explorerBenchmark->start();
            return _explorer->getTransactions(lastAddressesinBatch, optional<std::string>(), optional<void*>())
                .template flatMap<Unit>(buddy->account->getContext(), [self, currentBatchIndex, buddy, to](const std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>& bulk) -> Future<Unit> {
                self->_explorerBenchmark->stop();
                self->_cachedTransactionBulks[std::to_string(currentBatchIndex) + ","] = bulk;
                if (bulk->transactions.size() > 0)
                {
                    return self->extendKeychain((currentBatchIndex + 1) * 2, buddy);
                }
                else
                {
                    return Future<Unit>::successful(unit);
                }
            });
        };

        Future<std::shared_ptr<BitcoinLikeBlockchainExplorer::Block>> BlockchainExplorerAccountSynchronizer::updateCurrentBlock(std::shared_ptr<SynchronizationBuddy> buddy) {
            return _explorer->getCurrentBlock();
        };

        // Synchronize batches.
        //
        // This function will synchronize all batches by iterating over batches and transactions
        // bulks. The input buddy can be used to customize the behavior of the synchronization.
        Future<Unit> BlockchainExplorerAccountSynchronizer::synchronizeBatches(uint32_t currentBatchIndex, std::shared_ptr<SynchronizationBuddy> buddy) {
            buddy->logger->info("SYNC BATCHES");
            auto done = currentBatchIndex >= buddy->savedState.getValue().batches.size() - 1;
            if (currentBatchIndex >= buddy->savedState.getValue().batches.size()) {
                buddy->savedState.getValue().batches.push_back(BlockchainExplorerAccountSynchronizationBatchSavedState());
            }

            auto self = getSharedFromThis();
            auto& batchState = buddy->savedState.getValue().batches[currentBatchIndex];

            return synchronizeBatch(currentBatchIndex, buddy).template flatMap<Unit>(buddy->account->getContext(), [=](const bool& hadTransactions) -> Future<Unit> {

                buddy->preferences->editor()->template putObject<BlockchainExplorerAccountSynchronizationSavedState>("state", buddy->savedState.getValue())->commit();

                //Sync stops if there are no more batches in savedState and last batch has no transactions
                //But we may want to force sync of accounts within KEYCHAIN_OBSERVABLE_RANGE
                auto discoveredAddresses = currentBatchIndex * buddy->halfBatchSize;
                auto lastDiscoverableAddress = buddy->configuration->getInt(api::Configuration::KEYCHAIN_OBSERVABLE_RANGE).value_or(buddy->halfBatchSize);
                if (!done || (done && hadTransactions) || lastDiscoverableAddress > discoveredAddresses) {
                    return self->synchronizeBatches(currentBatchIndex + 1, buddy);
                }

                return Future<Unit>::successful(unit);
                }).recoverWith(ImmediateExecutionContext::INSTANCE, [=](const Exception& exception) -> Future<Unit> {
                    buddy->logger->info("Recovering from failing synchronization : {}", exception.getMessage());
                    //A block reorganization happened
                    if (exception.getErrorCode() == api::ErrorCode::BLOCK_NOT_FOUND &&
                        buddy->savedState.nonEmpty()) {
                        buddy->logger->info("Recovering from reorganization");
                        auto startSession = Future<void*>::async(ImmediateExecutionContext::INSTANCE, [=]() {
                            return Future<void*>::successful(nullptr);
                            });

                        return startSession.template flatMap<Unit>(ImmediateExecutionContext::INSTANCE, [=](void* const session) {
                            //Get its block/block height
                            auto& failedBatch = buddy->savedState.getValue().batches[currentBatchIndex];
                            auto const failedBlockHeight = failedBatch.blockHeight;
                            auto const failedBlockHash = failedBatch.blockHash;

                            if (failedBlockHeight > 0) {

                                //Delete data related to failedBlock (and all blocks above it)
                                buddy->logger->info("Deleting blocks above block height: {}", failedBlockHeight);

                                soci::session sql(buddy->wallet->getDatabase()->getPool());
                                {
                                    soci::transaction tr(sql);
                                    try {

                                        soci::rowset<std::string> rows_block = (sql.prepare << "SELECT uid FROM blocks where height >= :failedBlockHeight",
                                            soci::use(failedBlockHeight));

                                        std::vector<std::string> blockToDelete(rows_block.begin(), rows_block.end());

                                        // Fetch all operations which are deleted during reorganization
                                        auto deletedOperationUIDs = OperationDatabaseHelper::fetchFromBlocks(sql, blockToDelete);

                                        // Remove failed blocks and associated operations/transactions
                                        AccountDatabaseHelper::removeBlockOperation(sql, buddy->account->getAccountUid(), blockToDelete);

                                        //Get last block not part from reorg
                                        auto lastBlock = BlockDatabaseHelper::getLastBlock(sql,
                                            buddy->wallet->getCurrency().name);

                                        //Resync from the "beginning" if no last block in DB
                                        int64_t lastBlockHeight = 0;
                                        std::string lastBlockHash;
                                        if (lastBlock.nonEmpty()) {
                                            lastBlockHeight = lastBlock.getValue().height;
                                            lastBlockHash = lastBlock.getValue().blockHash;
                                        }
                                        // update reorganization block height until found the valid one
                                        buddy->context.reorgBlockHeight = lastBlockHeight;

                                        //Update savedState's batches
                                        for (auto& batch : buddy->savedState.getValue().batches) {
                                            if (batch.blockHeight > lastBlockHeight) {
                                                batch.blockHeight = (uint32_t)lastBlockHeight;
                                                batch.blockHash = lastBlockHash;
                                            }
                                        }
                                        tr.commit();

                                        // We can emit safely deleted operation UIDs
                                        std::for_each(
                                            deletedOperationUIDs.cbegin(),
                                            deletedOperationUIDs.cend(),
                                            [buddy](auto const& uid) {
                                                buddy->account->emitDeletedOperationEvent(uid);
                                            });
                                    }
                                    catch (...) {
                                        tr.rollback();
                                    }
                                }

                                //Save new savedState
                                buddy->preferences->editor()->template putObject<BlockchainExplorerAccountSynchronizationSavedState>(
                                    "state", buddy->savedState.getValue())->commit();

                                //Synchronize same batch now with an existing block (of hash lastBlockHash)
                                //if failedBatch was not the deepest block part of that reorg, this recursive call
                                //will ensure to get (and delete from DB) to the deepest failed block (part of reorg)
                                buddy->logger->info("Relaunch synchronization after recovering from reorganization");

                                return self->synchronizeBatches(currentBatchIndex, buddy);
                            }
                            return Future<Unit>::successful(unit);
                            }).recover(ImmediateExecutionContext::INSTANCE, [buddy](const Exception& ex) -> Unit {
                                buddy->logger->warn(
                                    "Failed to recover from reorganisation for account#{} of wallet {}",
                                    buddy->account->getIndex(),
                                    buddy->account->getWallet()->getName());
                                return unit;
                                });
                    }
                    return Future<Unit>::successful(unit);
                    });
        };

        Future <std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>> BlockchainExplorerAccountSynchronizer::getTransactionBulk(int currentBatchIndex, const std::shared_ptr<SynchronizationBuddy>& buddy) {
            int from = currentBatchIndex * buddy->halfBatchSize * 2;
            int to = (currentBatchIndex + 1) * buddy->halfBatchSize * 2;
            auto pair = getHashkeyAndBlockhash(currentBatchIndex, buddy);
            auto it = this->_cachedTransactionBulks.find(pair.first);
            if (it == this->_cachedTransactionBulks.end()) {
                auto batch = std::vector<std::string>(this->_addresses.begin() + from, this->_addresses.begin() + to);
                return _explorer->getTransactions(batch, pair.second, optional<void*>());
            }
            return Future<std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>>::async(buddy->account->getContext(), 
                [=]()-> std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk> {
                    return it->second;
                }
                );
        }

        // Synchronize a transactions batch.
        //
        // The currentBatchIndex is the currently synchronized batch. buddy is the
        // synchronization object used to accumulate a state. hadTransactions is used to check
        // whether more data is needed. If a block doesn�t have any transaction, it means that
        // we must stop.
        Future<bool> BlockchainExplorerAccountSynchronizer::synchronizeBatch(uint32_t currentBatchIndex,
            std::shared_ptr<SynchronizationBuddy> buddy,
            bool hadTransactions) {
            buddy->logger->info("SYNC BATCH {}", currentBatchIndex);
            auto self = getSharedFromThis();
            if (currentBatchIndex * buddy->halfBatchSize * 2 >= self->_addresses.size()) // current batch index is out of range
                return Future<bool>::successful(false);
            self->_explorerBenchmark->start();
            return getTransactionBulk(currentBatchIndex, buddy)
                .template flatMap<bool>(buddy->account->getContext(), [self, currentBatchIndex, buddy, hadTransactions](const std::shared_ptr<BitcoinLikeBlockchainExplorer::TransactionsBulk>& bulk) -> Future<bool> {
                self->_explorerBenchmark->stop();
                auto interpretBenchmark = NEW_BENCHMARK("interpret_operations");

                auto& batchState = buddy->savedState.getValue().batches[currentBatchIndex];
                buddy->logger->info("Got {} txs for account {}", bulk->transactions.size(), buddy->account->getAccountUid());
                auto count = 0;
                  
                Option<Block> lastBlock = Option<Block>::NONE;
                std::vector<Operation> operations;
                interpretBenchmark->start();
                // Interpret transactions to operations and update last block
                for (const auto& tx : bulk->transactions) {
                    // Update last block to chain query
                    if (lastBlock.isEmpty() ||
                        lastBlock.getValue().height < tx.block.getValue().height) {
                        lastBlock = tx.block;
                    }

                    self->interpretTransaction(tx, buddy, operations);

                    //Update first pendingTxHash in savedState
                    auto it = buddy->transactionsToDrop.find(tx.hash);
                    if (it != buddy->transactionsToDrop.end()) {
                        //If block non empty, tx is no longer pending
                        if (tx.block.nonEmpty()) {
                            buddy->savedState.getValue().pendingTxsHash.erase(it->first);
                        }
                        else { //Otherwise tx is in mempool but pending
                            buddy->savedState.getValue().pendingTxsHash.insert(std::pair<std::string, std::string>(it->first, it->second));
                        }
                    }
                    //Remove from tx to drop
                    buddy->transactionsToDrop.erase(tx.hash);
                }
                interpretBenchmark->stop();
                auto insertionBenchmark = NEW_BENCHMARK("insert_operations");
                insertionBenchmark->start();
                Try<int> tryPutTx = buddy->account->bulkInsert(operations);
                insertionBenchmark->stop();
                if (tryPutTx.isFailure()) {
                    buddy->logger->error("Failed to bulk insert for batch {} because: {}", currentBatchIndex, tryPutTx.getFailure().getMessage());
                    throw make_exception(api::ErrorCode::RUNTIME_ERROR, "Synchronization failed for batch {} ({})", currentBatchIndex, tryPutTx.exception().getValue().getMessage());
                }
                else {
                    count += tryPutTx.getValue();
                }

                buddy->logger->info("Succeeded to insert {} txs on {} for account {}", count, bulk->transactions.size(), buddy->account->getAccountUid());
                buddy->account->emitEventsNow();

                // Get the last block
                if (bulk->transactions.size() > 0 && lastBlock.nonEmpty()) {
                    batchState.blockHeight = (uint32_t) lastBlock.getValue().height;
                    batchState.blockHash = lastBlock.getValue().hash;
                    buddy->preferences->editor()->template putObject<BlockchainExplorerAccountSynchronizationSavedState>("state", buddy->savedState.getValue())->commit();
                }

                auto hadTX = hadTransactions || bulk->transactions.size() > 0;
                if (bulk->hasNext) {
                    return self->synchronizeBatch(currentBatchIndex, buddy, hadTX);
                }
                else {
                    return Future<bool>::successful(hadTX);
                }
                });
        }
        //Hashkey = currentBatchIndex + last blockHash in batch
        std::pair<std::string, Option<std::string>> BlockchainExplorerAccountSynchronizer::getHashkeyAndBlockhash(int currentBatchIndex, const std::shared_ptr<SynchronizationBuddy>& buddy) {
            Option<std::string> blockHash;
            if (currentBatchIndex < buddy->savedState.getValue().batches.size()) {
                auto& batchState = buddy->savedState.getValue().batches[currentBatchIndex];
                if (batchState.blockHeight > 0) {
                    blockHash = Option<std::string>(batchState.blockHash);
                }
            }
            auto key = std::to_string(currentBatchIndex) + ",";
            if (!blockHash.isEmpty()) {
                key += blockHash.getValue();
            }
            return std::make_pair(key, blockHash);
        }
    }
}


<|start_filename|>core/src/debug/LogPrinterSink.cpp<|end_filename|>
/*
 *
 * LogPrinterSink
 * ledger-core
 *
 * Created by <NAME> on 24/11/2016.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2016 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "LogPrinterSink.hpp"
#include "utils/LambdaRunnable.hpp"
#include "api/LogPrinter.hpp"
#include "api/ExecutionContext.hpp"

namespace ledger {
    namespace core {

        LogPrinterSink::LogPrinterSink(const std::shared_ptr<api::LogPrinter> &printer) {
            _printer = printer;
            set_level(spdlog::level::trace);
        }

        void LogPrinterSink::sink_it_(const spdlog::details::log_msg &msg) {
            auto printer = _printer.lock();
            if (!printer)
                return;
            auto level = msg.level;
            
            fmt::memory_buffer buffer;
            formatter_->format(msg, buffer);
            std::string message(buffer.data(), buffer.size());
            printer->getContext()->execute(make_runnable([printer, level, message]() {
                switch (level) {
                    case spd::level::trace:
                        printer->printApdu(message);
                        break;
                    case spdlog::level::debug:
                        printer->printDebug(message);
                        break;
                    case spdlog::level::info:
                        printer->printInfo(message);
                        break;
                    case spdlog::level::warn:
                        printer->printWarning(message);
                        break;
                    case spdlog::level::err:
                        printer->printError(message);
                        break;
                    case spdlog::level::critical:
                        printer->printCriticalError(message);
                        break;
                    case spdlog::level::off:
                        break;
                }
            }));
        }

        void LogPrinterSink::flush_() {

        }

        const std::weak_ptr<api::LogPrinter> &LogPrinterSink::getPrinter() const {
            return _printer;
        }
    }
}

<|start_filename|>core/test/lib/libledger-test/FilesystemUtils.cpp<|end_filename|>
/*
 *
 * FilesystemUtils.cpp
 * ledger-core
 *
 * Created by <NAME> on 25/09/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2017 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "FilesystemUtils.hpp"
#include <iostream>
#include <experimental/filesystem>
#include <string>
#ifdef _WIN32
    #define WIN32_LEAN_AND_MEAN
    #include <windows.h>
#endif
#ifdef __linux__
    #include <unistd.h>
#endif
#ifdef __APPLE__
    #include <mach-o/dyld.h>
#endif

using namespace std;
namespace fs = std::experimental::filesystem::v1;

//refer to https://stackoverflow.com/questions/1528298/get-path-of-executable to get the executable path with C++
string FilesystemUtils::getExecutablePath(){
#ifdef _WIN32
    WCHAR buf[1024];
    GetModuleFileNameW(NULL, buf, 1024);
    wstring ws(buf);
    string res(ws.begin(),ws.end());
#else
    unsigned int path_max=1024;
    char buf[path_max];
    #ifdef __linux__
        int rslt = readlink("/proc/self/exe", buf, path_max - 1);
        buf[rslt] = '\0';
    #endif
    #ifdef __APPLE__
        _NSGetExecutablePath(buf, &path_max);
    #endif
    string res(buf);
#endif
  return res;
}

string FilesystemUtils::getExecutableDir(){
    auto path = getExecutablePath();
#ifdef _WIN32
    auto last_slash_idx = path.rfind('\\');
#else
    auto last_slash_idx = path.rfind('/');
#endif
    if (string::npos != last_slash_idx) {
        return path.substr(0, last_slash_idx);
    }
    else {
        return path;
    }
}

bool FilesystemUtils::isExecutableOrLib(const std::string& path){
    fs::path filePath { path };
#ifdef _WIN32
    auto extension=filePath.extension().string();
    return (extension==".exe") || (extension==".bat") || (extension==".com") || (extension == ".lib") || (extension == ".dll") || (extension == ".pdb") || (extension == ".ilk");
#else
    auto permissions=status(filePath).permissions();
    return (permissions & (fs::perms::owner_exec | fs::perms::group_exec | fs::perms::others_exec))!=fs::perms::none;
#endif
}

void FilesystemUtils::clearFs(const std::string& path) {
    error_code ec;
    for (const auto & file : fs::recursive_directory_iterator(path))
    {
        if (!fs::is_directory(file.path())) {
            if (!FilesystemUtils::isExecutableOrLib(file.path().string())){
                fs::remove(file.path(), ec);
            }
        }
    }
}


<|start_filename|>core/src/jni/jni/ERC20LikeOperation.cpp<|end_filename|>
// AUTOGENERATED FILE - DO NOT MODIFY!
// This file generated by Djinni from erc20.djinni

#include "ERC20LikeOperation.hpp"  // my header
#include "BigInt.hpp"
#include "Marshal.hpp"
#include "OperationType.hpp"

namespace djinni_generated {

ERC20LikeOperation::ERC20LikeOperation() : ::djinni::JniInterface<::ledger::core::api::ERC20LikeOperation, ERC20LikeOperation>("co/ledger/core/ERC20LikeOperation$CppProxy") {}

ERC20LikeOperation::~ERC20LikeOperation() = default;


CJNIEXPORT void JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_nativeDestroy(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        delete reinterpret_cast<::djinni::CppProxyHandle<::ledger::core::api::ERC20LikeOperation>*>(nativeRef);
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, )
}

CJNIEXPORT jstring JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getHash(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getHash();
        return ::djinni::release(::djinni::String::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getNonce(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getNonce();
        return ::djinni::release(::djinni_generated::BigInt::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getGasPrice(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getGasPrice();
        return ::djinni::release(::djinni_generated::BigInt::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getGasLimit(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getGasLimit();
        return ::djinni::release(::djinni_generated::BigInt::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getUsedGas(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getUsedGas();
        return ::djinni::release(::djinni_generated::BigInt::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jstring JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getSender(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getSender();
        return ::djinni::release(::djinni::String::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jstring JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getReceiver(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getReceiver();
        return ::djinni::release(::djinni::String::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getValue(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getValue();
        return ::djinni::release(::djinni_generated::BigInt::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jbyteArray JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getData(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getData();
        return ::djinni::release(::djinni::Binary::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getTime(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getTime();
        return ::djinni::release(::djinni::Date::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getOperationType(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getOperationType();
        return ::djinni::release(::djinni_generated::OperationType::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jint JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getStatus(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getStatus();
        return ::djinni::release(::djinni::I32::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jobject JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getBlockHeight(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getBlockHeight();
        return ::djinni::release(::djinni::Optional<std::experimental::optional, ::djinni::I64>::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jstring JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getETHOperationUid(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getETHOperationUid();
        return ::djinni::release(::djinni::String::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

CJNIEXPORT jstring JNICALL Java_co_ledger_core_ERC20LikeOperation_00024CppProxy_native_1getOperationUid(JNIEnv* jniEnv, jobject /*this*/, jlong nativeRef)
{
    try {
        DJINNI_FUNCTION_PROLOGUE1(jniEnv, nativeRef);
        const auto& ref = ::djinni::objectFromHandleAddress<::ledger::core::api::ERC20LikeOperation>(nativeRef);
        auto r = ref->getOperationUid();
        return ::djinni::release(::djinni::String::fromCpp(jniEnv, r));
    } JNI_TRANSLATE_EXCEPTIONS_RETURN(jniEnv, 0 /* value doesn't matter */)
}

}  // namespace djinni_generated


<|start_filename|>core/test/lib/libledger-test/UvThreadDispatcher.hpp<|end_filename|>
/*
 *
 * UvThreadDispatcher.hpp
 * ledger-core
 *
 * Created by <NAME> on 14/06/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#ifndef LEDGER_CORE_UVTHREADDISPATCHER_HPP
#define LEDGER_CORE_UVTHREADDISPATCHER_HPP

#include <uv.h>
#include <ledger/core/api/ThreadDispatcher.hpp>
#include <ledger/core/api/ExecutionContext.hpp>
#include <ledger/core/async/Future.hpp>
#include <ledger/core/api/Runnable.hpp>
#include <ledger/core/api/Lock.hpp>
#include <condition_variable>

#include <thread>
#include <ledger/core/utils/Option.hpp>
#include <ledger/core/utils/Try.hpp>
#include <unordered_map>

namespace uv {
    std::shared_ptr<ledger::core::api::ThreadDispatcher> createDispatcher();
    const int THREADPOOL_SIZE = 16;
    enum class EventType {
        RUN,
        CLOSE
    };

    struct Event {
        EventType eventType;
        std::shared_ptr<ledger::core::api::Runnable> runnable;
        unsigned int delay;
    };

    static void context_callback(uv_async_t* handle);
    static void timer_callback(uv_timer_t* handle);

    class EventLoop {
    public:
        EventLoop();

        EventLoop(const EventLoop&) = delete;
        EventLoop(EventLoop&&) = delete;
        EventLoop & operator=(const EventLoop&) = delete;

        void queue(const std::shared_ptr<ledger::core::api::Runnable>& runnable);

        void queue(const std::shared_ptr<ledger::core::api::Runnable>& runnable, int64_t millis);

        bool run();

        void tick();

        void waitUntilStopped();

        void stop();

        ledger::core::Option<Event> dequeue();

        ~EventLoop();

    private:
        uv_loop_t* _loop;
        uv_async_t* _async;
        uv_timer_t* _timer;
        std::mutex _mutex;
        std::condition_variable condition;
        bool _running;
        std::queue<Event> _queue;
    };

    class SequentialExecutionContext : public ledger::core::api::ExecutionContext {
    public:

        SequentialExecutionContext();

        void run();

        void stop();

        void execute(const std::shared_ptr<ledger::core::api::Runnable> &runnable) override;

        void delay(const std::shared_ptr<ledger::core::api::Runnable> &runnable, int64_t millis) override;

        ~SequentialExecutionContext() override;

        void waitUntilStopped();

    private:
        std::thread _thread;
        EventLoop _loop;
    };

    class ThreadpoolExecutionContext : public ledger::core::api::ExecutionContext {
    public:
        ThreadpoolExecutionContext();
        void execute(const std::shared_ptr<ledger::core::api::Runnable>& runnable) override;
        void delay(const std::shared_ptr<ledger::core::api::Runnable>& runnable, int64_t millis) override;
        ~ThreadpoolExecutionContext() override;
        void stop();

    private:
        std::vector< std::thread> _threads;
        std::queue<std::shared_ptr<ledger::core::api::Runnable>> tasks;
        std::mutex queue_mutex;
        std::condition_variable condition;
        bool _stop;
    };

    class UvThreadDispatcher : public ledger::core::api::ThreadDispatcher {
    public:

        UvThreadDispatcher(unsigned int poolSize = 4);

        std::shared_ptr<ledger::core::api::ExecutionContext> getSerialExecutionContext(const std::string &name) override;

        std::shared_ptr<ledger::core::api::ExecutionContext> getThreadPoolExecutionContext(const std::string &name) override;

        std::shared_ptr<ledger::core::api::ExecutionContext> getMainExecutionContext() override;

        std::shared_ptr<ledger::core::api::Lock> newLock() override;

        ~UvThreadDispatcher();

        void waitUntilStopped();

        void stop();

    private:
        unsigned int _poolSize;
        std::mutex _mutex;
        std::vector<std::shared_ptr<SequentialExecutionContext>> _pool;
        std::unordered_map<std::string, std::shared_ptr<SequentialExecutionContext>> _contextsByName;
        std::unordered_map<std::string, std::shared_ptr<ThreadpoolExecutionContext>> _threadpoolContextsByName;
    };


    template <typename T>
    static T wait(ledger::core::Future<T> future) {
        std::mutex mutex;
        std::condition_variable condition;
        auto dispatcher = createDispatcher();
        ledger::core::Try<T> result;
        future.onComplete(dispatcher->getMainExecutionContext(), [&] (auto res) mutable {
            std::unique_lock<std::mutex> lock(mutex);
            result = res;
            condition.notify_all();
        });
        {
            std::unique_lock<std::mutex> lock(mutex);
            condition.wait(lock, [&] () {
                return result.isComplete();
            });
        }
        if (result.isFailure()) {
            throw result.getFailure();
        }
        return result.getValue();
    }
}


#endif //LEDGER_CORE_UVTHREADDISPATCHER_HPP


<|start_filename|>core/test/algorand/AlgorandExplorerTests.cpp<|end_filename|>
/*
 * AlgorandExplorerTests
 *
 * Created by <NAME> on 11/05/2020.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2020 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */
#include "AlgorandTestFixtures.hpp"
#include <wallet/algorand/AlgorandBlockchainExplorer.hpp>
#include <api/Configuration.hpp>

#include "../integration/BaseFixture.h"

using namespace ledger::testing::algorand;
using namespace ledger::core::algorand;

class AlgorandExplorerTest : public BaseFixture {
public:
    void SetUp() override {
        BaseFixture::SetUp();

        auto worker = dispatcher->getSerialExecutionContext("worker");
        auto threadpoolWorker = dispatcher->getThreadPoolExecutionContext("threadpoolWorker");
        // NOTE: we run the tests on the staging environment which is on the TestNet
        auto client = std::make_shared<HttpClient>("https://algorand.coin.staging.aws.ledger.com", http, worker, threadpoolWorker);

        // NOTE: we run the tests on the staging environment which is on the TestNet
        auto configuration = DynamicObject::newInstance();
        configuration->putString(api::Configuration::BLOCKCHAIN_EXPLORER_API_ENDPOINT, "https://algorand.coin.staging.aws.ledger.com");
        
        explorer = std::make_shared<BlockchainExplorer>(
                        worker,
                        client,
                        networks::getAlgorandNetworkParameters("algorand-testnet"),
                        configuration);
    }

    void TearDown() override {
        BaseFixture::TearDown();
        explorer.reset();
    }

    std::shared_ptr<BlockchainExplorer> explorer;
};

TEST_F(AlgorandExplorerTest, GetBlock) {

    uint64_t blockHeight = 6000000; // Some arbitrary block
    std::chrono::system_clock::time_point blockTime(std::chrono::seconds(1586345796));

    api::Block block = uv::wait(explorer->getBlock(blockHeight));

    EXPECT_EQ(block.height, blockHeight);
    EXPECT_EQ(block.blockHash, std::to_string(blockHeight));
    EXPECT_EQ(block.time, blockTime);
}

TEST_F(AlgorandExplorerTest, GetLatestBlock) {

    api::Block block = uv::wait(explorer->getLatestBlock());

    EXPECT_FALSE(block.blockHash.empty());
    EXPECT_GT(block.height, 7000000);
    //EXPECT_GT(block.time.time_since_epoch().count(), 1593455156000000000); // Fails on CI with different system clock
}

TEST_F(AlgorandExplorerTest, GetAccount) {

    auto address = ::algorand::Address(OBELIX_ADDRESS);
    model::Account account = uv::wait(explorer->getAccount(address.toString()));

    EXPECT_EQ(account.address, address.toString());
    EXPECT_GT(account.round, 6000000);
    EXPECT_GT(account.amount, 0);
    EXPECT_GT(account.amountWithoutPendingRewards, 0);
    // Pending rewards should be tested only on a frozen seed,
    // Because they will get back to 0 after every transaction
    //EXPECT_GT(account.pendingRewards, 0);
    //EXPECT_GT(account.rewards, 0);

    EXPECT_FALSE(account.assetsAmounts.empty());
    EXPECT_NE(account.assetsAmounts.find(342836), account.assetsAmounts.end());
    EXPECT_EQ(account.assetsAmounts.at(342836).creatorAddress, address);

    EXPECT_NE(account.createdAssets.find(342836), account.createdAssets.end());
    assertSameAssetParams(testAsset(), account.createdAssets.at(342836));
}

TEST_F(AlgorandExplorerTest, GetPaymentTransaction) {
    auto txRef = paymentTransaction();
    model::Transaction tx = uv::wait(explorer->getTransactionById(*txRef.header.id));
    assertSameTransaction(txRef, tx);
}

TEST_F(AlgorandExplorerTest, DISABLED_GetAssetConfigTransaction) {
    auto txRef = assetConfigTransaction();
    model::Transaction tx = uv::wait(explorer->getTransactionById(*txRef.header.id));
    assertSameTransaction(txRef, tx);
}

TEST_F(AlgorandExplorerTest, DISABLED_GetAssetTransferTransaction) {
    auto txRef = assetTransferTransaction();
    model::Transaction tx = uv::wait(explorer->getTransactionById(*txRef.header.id));
    assertSameTransaction(txRef, tx);
}

TEST_F(AlgorandExplorerTest, DISABLED_GetAccountTransactions) {

    auto address = "RGX5XA7DWZOZ5SLG4WQSNIFKIG4CNX4VOH23YCEX56523DQEAL3QL56XZM"; // Obelix
    model::TransactionsBulk txs = uv::wait(explorer->getTransactionsForAddress(address, 0));

    for (const auto& tx : txs.transactions) {
        if (*tx.header.id == PAYMENT_TX_ID) {
            assertSameTransaction(paymentTransaction(), tx);
        } else if (*tx.header.id == ASSET_CONFIG_TX_ID) {
            assertSameTransaction(assetConfigTransaction(), tx);
        } else if (*tx.header.id == ASSET_TRANSFER_TX_ID) {
            assertSameTransaction(assetTransferTransaction(), tx);
        }
    }

    if (txs.hasNext == true) EXPECT_EQ(txs.transactions.size(), 100);
}


<|start_filename|>core/src/wallet/stellar/StellarLikeAccount.cpp<|end_filename|>
/*
 *
 * StellarLikeAccount.cpp
 * ledger-core
 *
 * Created by <NAME> on 13/02/2019.
 *
 * The MIT License (MIT)
 *
 * Copyright (c) 2019 Ledger
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include "StellarLikeAccount.hpp"
#include <wallet/stellar/StellarLikeWallet.hpp>
#include <utils/DateUtils.hpp>
#include <events/Event.hpp>
#include "database/StellarLikeAccountDatabaseHelper.hpp"
#include <wallet/common/database/AccountDatabaseHelper.h>
#include "database/StellarLikeLedgerDatabaseHelper.hpp"
#include "database/StellarLikeTransactionDatabaseHelper.hpp"
#include "database/StellarLikeOperationDatabaseHelper.hpp"
#include <wallet/common/database/OperationDatabaseHelper.h>
#include <set>
#include <wallet/common/BalanceHistory.hpp>
#include <api/BoolCallback.hpp>
#include <api/AmountCallback.hpp>
#include <events/LambdaEventReceiver.hpp>
#include "transaction_builders/StellarLikeTransactionBuilder.hpp"
#include <api/BigIntCallback.hpp>
#include <database/soci-date.h>
#include <api/StringCallback.hpp>
#include <api/StellarLikeOperationType.hpp>
#include <wallet/stellar/xdr/StellarModelUtils.hpp>

using namespace ledger::core;

namespace {
    const std::set<stellar::OperationType> ACCEPTED_PAYMENT_TYPES{
            stellar::OperationType::PAYMENT, stellar::OperationType::CREATE_ACCOUNT
    };

    static const auto INVALID_SYNCHRONIZATION_DELAY_MS = 60 * 60 * 1000;
}

namespace ledger {
    namespace core {

        StellarLikeAccount::StellarLikeAccount(const std::shared_ptr<StellarLikeWallet> &wallet,
                                               const StellarLikeAccountParams &params)
                                               : AbstractAccount(wallet, params.index), _params(params) {

        }

        bool StellarLikeAccount::isSynchronizing() {
            return _params.synchronizer->isSynchronizing();
        }

        std::shared_ptr<api::EventBus> StellarLikeAccount::synchronize() {
            std::lock_guard<std::mutex> lock(_synchronizationLock);

            if (_currentSyncEventBus != nullptr)
                return _currentSyncEventBus;

            auto eventPublisher = std::make_shared<EventPublisher>(getContext());

            _currentSyncEventBus = eventPublisher->getEventBus();
            auto self = std::dynamic_pointer_cast<StellarLikeAccount>(shared_from_this());
            auto synchronizer = _params.synchronizer;


            //Update current block height (needed to compute trust level)
            _params.explorer->getLastLedger().onComplete(getContext(),
            [self, eventPublisher, synchronizer](const TryPtr<stellar::Ledger> &l) mutable {
                if (l.isSuccess()) {
                    soci::session sql(self->getWallet()->getDatabase()->getPool());
                    self->putLedger(sql, *l.getValue());
                    self->_currentLedgerHeight = l.getValue()->height;
                }
                auto future = synchronizer->synchronize(self)->getFuture();
                auto startTime = DateUtils::now();
                eventPublisher->postSticky(
                        std::make_shared<Event>(api::EventCode::SYNCHRONIZATION_STARTED, api::DynamicObject::newInstance()),
                        0);
                future.onComplete(self->getContext(), [eventPublisher, self, startTime](const auto &result) {
                    api::EventCode code;
                    auto payload = std::make_shared<DynamicObject>();
                    auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
                            DateUtils::now() - startTime).count();
                    payload->putLong(api::Account::EV_SYNC_DURATION_MS, duration);
                    if (result.isSuccess()) {
                        code = api::EventCode::SYNCHRONIZATION_SUCCEED;

                        auto const state = result.getValue();

                        payload->putInt(api::Account::EV_SYNC_LAST_BLOCK_HEIGHT, static_cast<int32_t>(state.lastBlockHeight));
                        payload->putInt(api::Account::EV_SYNC_NEW_OPERATIONS, static_cast<int32_t>(state.newOperations));
                    } else {
                        code = api::EventCode::SYNCHRONIZATION_FAILED;
                        payload->putString(api::Account::EV_SYNC_ERROR_CODE,
                                           api::to_string(result.getFailure().getErrorCode()));
                        payload->putInt(api::Account::EV_SYNC_ERROR_CODE_INT, (int32_t) result.getFailure().getErrorCode());
                        payload->putString(api::Account::EV_SYNC_ERROR_MESSAGE, result.getFailure().getMessage());
                    }
                    eventPublisher->postSticky(std::make_shared<Event>(code, payload), 0);
                    std::lock_guard<std::mutex> lock(self->_synchronizationLock);
                    self->_currentSyncEventBus = nullptr;

                });
            });

            return eventPublisher->getEventBus();
        }

        std::string StellarLikeAccount::getRestoreKey() {
            return _params.keychain->getRestoreKey();
        }

        FuturePtr<ledger::core::Amount> StellarLikeAccount::getBalance() {
            auto self = getSelf();
            return async<std::shared_ptr<Amount>>([=] () {
                const auto& currency = self->getWallet()->getCurrency();
                soci::session sql(self->_params.database->getReadonlyPool());
                stellar::Account account {};

                auto accountId = AccountDatabaseHelper::createAccountUid(self->getWallet()->getWalletUid(), self->_params.index);
                StellarLikeAccountDatabaseHelper::getAccount(sql, accountId, account);
                StellarLikeAccountDatabaseHelper::getAccountBalances(sql, accountId, account);

                auto balanceIt = std::find_if(account.balances.begin(), account.balances.end(),
                        [] (const stellar::Balance& balance) {
                    return balance.assetType == "native";
                });

                BigInt amount = (balanceIt != account.balances.end()) ? balanceIt->value : BigInt::ZERO;
                return std::make_shared<Amount>(self->getWallet()->getCurrency(), 0, amount);
            });
        }

        Future<AbstractAccount::AddressList> StellarLikeAccount::getFreshPublicAddresses() {
            auto self = getSelf();
            return async<AbstractAccount::AddressList>([=] () {
                return AbstractAccount::AddressList({self->_params.keychain->getAddress()});
            });
        }

        Future<std::vector<std::shared_ptr<api::Amount>>>
        StellarLikeAccount::getBalanceHistory(const std::string &start, const std::string &end,
                                              api::TimePeriod precision) {
            auto self = getSelf();
            return async<std::vector<std::shared_ptr<api::Amount>>>([=]() -> std::vector<std::shared_ptr<api::Amount>> {

                auto startDate = DateUtils::fromJSON(start);
                auto endDate = DateUtils::fromJSON(end);
                if (startDate >= endDate) {
                    throw make_exception(api::ErrorCode::INVALID_DATE_FORMAT,
                                         "Start date should be strictly lower than end date");
                }

                const auto &uid = self->getAccountUid();
                soci::session sql(self->getWallet()->getDatabase()->getReadonlyPool());
                std::vector<Operation> operations;

                auto keychain = self->getKeychain();
                std::function<bool(const std::string &)> filter = [&keychain](const std::string addr) -> bool {
                    return keychain->contains(addr);
                };

                //Get operations related to an account
                OperationDatabaseHelper::queryOperations(sql, uid, operations, filter);

                auto lowerDate = startDate;
                auto upperDate = DateUtils::incrementDate(startDate, precision);

                std::vector<std::shared_ptr<api::Amount>> amounts;
                std::size_t operationsCount = 0;
                BigInt sum;
                while (lowerDate <= endDate && operationsCount < operations.size()) {

                    auto operation = operations[operationsCount];
                    while (operation.date > upperDate && lowerDate < endDate) {
                        lowerDate = DateUtils::incrementDate(lowerDate, precision);
                        upperDate = DateUtils::incrementDate(upperDate, precision);
                        amounts.emplace_back(
                                std::make_shared<ledger::core::Amount>(self->getWallet()->getCurrency(), 0, sum));
                    }

                    if (operation.date <= upperDate) {
                        switch (operation.type) {
                            case api::OperationType::RECEIVE: {
                                sum = sum + operation.amount;
                                break;
                            }
                            case api::OperationType::SEND: {
                                sum = sum - (operation.amount + operation.fees.getValueOr(BigInt::ZERO));
                                break;
                            }
                            default:
                                break;
                        }
                    }
                    operationsCount += 1;
                }

                while (lowerDate < endDate) {
                    lowerDate = DateUtils::incrementDate(lowerDate, precision);
                    amounts.emplace_back(
                            std::make_shared<ledger::core::Amount>(self->getWallet()->getCurrency(), 0, sum));
                }

                return amounts;
            });
        }

        Future<api::ErrorCode> StellarLikeAccount::eraseDataSince(const std::chrono::system_clock::time_point &date) {
            auto log = logger();
            auto accountUid = getAccountUid();
            auto self = getSelf();
            return async<api::ErrorCode>([=] () {
                soci::session sql(self->getWallet()->getDatabase()->getPool());
                StellarLikeTransactionDatabaseHelper::eraseDataSince(sql, accountUid, date);
                
                self->_params.synchronizer->reset(self, date);
                log->debug(" Finish erasing data of account : {}", accountUid);
                return api::ErrorCode::FUTURE_WAS_SUCCESSFULL;
            });
        }

        std::shared_ptr<StellarLikeAccount> StellarLikeAccount::getSelf() {
            return std::dynamic_pointer_cast<StellarLikeAccount>(shared_from_this());
        }

        int StellarLikeAccount::putLedger(soci::session &sql, stellar::Ledger &ledger) {
            return StellarLikeLedgerDatabaseHelper::putLedger(sql, getWallet()->getCurrency(), ledger) ? 1 : 0;
        }

        void StellarLikeAccount::interpretTransaction(const stellar::Transaction &tx, std::vector<Operation> &out) {
            if (tx.envelope.type != stellar::xdr::EnvelopeType::ENVELOPE_TYPE_TX_V0 &&
                tx.envelope.type != stellar::xdr::EnvelopeType::ENVELOPE_TYPE_TX) {
                // Ignore transaction with unhandled envelope types.
                return;
            }
            int createdOperations = 0;
            //StellarLikeTransactionDatabaseHelper::putTransaction(sql, getWallet()->getCurrency(), tx);
            Operation operation;
            operation.accountUid = getAccountUid();
            operation.currencyName = getWallet()->getCurrency().name;
            operation.date = tx.createdAt;
            operation.walletType = getWallet()->getWalletType();
            operation.walletUid = getWallet()->getWalletUid();
            operation.trust = std::make_shared<TrustIndicator>();
            operation.trust->setTrustLevel(api::TrustLevel::TRUSTED);

            Block block;
            block.currencyName = getWallet()->getCurrency().name;
            block.time = tx.createdAt;
            block.height = tx.ledger;
            block.hash = fmt::format("{}", block.height);

            operation.block = block;
            operation.fees = tx.feePaid;
            operation.senders.emplace_back(tx.sourceAccount);

            auto accountAddress = getKeychain()->getAddress()->toString();
            auto networkParams = getWallet()->getCurrency().stellarLikeNetworkParameters.value();
            auto muxedAccountToAddr = [&] (const stellar::xdr::MuxedAccount& acc) {
                return StellarLikeAddress::convertMuxedAccountToAddress(acc, networkParams);
            };
            auto accountIdToAddr = [&] (const stellar::xdr::AccountID& acc) {
                return StellarLikeAddress::convertXdrAccountToAddress(acc, networkParams);
            };

            auto putOp = [&] (api::OperationType type, stellar::Operation stellarOperation) {
                operation.type = type;
                stellarOperation.from = operation.senders[0];
                if (!operation.recipients.empty())
                    stellarOperation.to = operation.recipients[0];
                operation.stellarOperation = {stellarOperation, tx};
                operation.refreshUid();
                //OperationDatabaseHelper::putOperation(sql, operation);
                createdOperations += 1;
                //emitNewOperationEvent(operation);
                out.push_back(operation);
            };

            auto opIndex = 0;
            const auto& operations = stellar::xdr::getOperations(tx.envelope);
            for (const auto& op : operations) {
                stellar::Operation stellarOperation;
                stellarOperation.createdAt = tx.createdAt;
                stellarOperation.transactionFee = tx.feePaid;
                stellarOperation.transactionSequence = tx.sourceAccountSequence;
                stellarOperation.transactionHash = tx.hash;
                stellarOperation.transactionSuccessful = tx.successful;
                stellarOperation.id = (BigInt(tx.pagingToken) + BigInt(opIndex + 1)).toString();
                stellarOperation.type = op.type;
                if (op.sourceAccount.nonEmpty()) {
                    operation.senders[0] = muxedAccountToAddr(op.sourceAccount.getValue());
                }

                switch (op.type) {
                    case stellar::OperationType::CREATE_ACCOUNT: {
                        auto& sop = boost::get<stellar::xdr::CreateAccountOp>(op.content);
                        operation.recipients.emplace_back(accountIdToAddr(sop.destination));
                        operation.amount.assignI64(sop.startingBalance);
                        stellarOperation.asset.type = "native";
                        if (operation.senders[0] == accountAddress) {
                            putOp(api::OperationType::SEND, stellarOperation);
                        }
                        if (operation.recipients[0] == accountAddress) {
                            putOp(api::OperationType::RECEIVE, stellarOperation);
                        }
                        break;
                    }
                    case stellar::OperationType::PAYMENT: {
                        auto &sop = boost::get<stellar::xdr::PaymentOp>(op.content);
                        if (sop.asset.type == stellar::xdr::AssetType::ASSET_TYPE_NATIVE) {
                            operation.amount.assignI64(sop.amount);
                            operation.recipients.emplace_back(muxedAccountToAddr(sop.destination));
                            stellar::xdrAssetToAsset(sop.asset, networkParams, stellarOperation.asset);
                            if (operation.senders[0] == accountAddress) {
                                putOp(api::OperationType::SEND, stellarOperation);
                            }
                            if (operation.recipients[0] == accountAddress) {
                                putOp(api::OperationType::RECEIVE, stellarOperation);
                            }
                        }
                        if (operation.senders[0] == accountAddress && opIndex >= operations.size() - 1
                            && createdOperations == 0) {
                            operation.amount = BigInt::ZERO;
                            putOp(api::OperationType::SEND, stellarOperation);
                        }
                        break;
                    }
                    case stellar::OperationType::PATH_PAYMENT: {
                        // Create op if sending or receiving native token, otherwise if source account is self
                        // create fees op
                        auto& sop = boost::get<stellar::xdr::PathPaymentStrictReceiveOp>(op.content);
                        operation.recipients.emplace_back(muxedAccountToAddr(sop.destination));
                        stellar::xdrAssetToAsset(sop.destAsset, networkParams, stellarOperation.asset);
                        stellar::Asset sourceAsset;
                        stellar::xdrAssetToAsset(sop.sendAsset, networkParams, sourceAsset);
                        stellarOperation.sourceAsset = sourceAsset;
                        stellarOperation.sourceAmount = BigInt(sop.sendMax);
                        stellarOperation.amount = BigInt(sop.destAmount);
                        if (operation.senders[0] == accountAddress &&
                            sop.sendAsset.type == stellar::xdr::AssetType::ASSET_TYPE_NATIVE) {
                            operation.amount = BigInt(sop.sendMax);
                            putOp(api::OperationType::SEND, stellarOperation);
                        }
                        if (operation.recipients[0] == accountAddress &&
                            sop.destAsset.type == stellar::xdr::AssetType::ASSET_TYPE_NATIVE) {
                            putOp(api::OperationType::RECEIVE, stellarOperation);
                        }
                        if (operation.senders[0] == accountAddress && opIndex >= operations.size() - 1
                            && createdOperations == 0) {
                            operation.amount = BigInt::ZERO;
                            putOp(api::OperationType::SEND, stellarOperation);
                        }
                        break;
                    }
                    default:
                        // Create fees operation if source Account is accountAdddress
                        if (operation.senders[0] == accountAddress &&
                            opIndex >= operations.size() - 1 &&
                            createdOperations == 0) {
                            operation.amount = BigInt::ZERO;
                            putOp(api::OperationType::SEND, stellarOperation);
                        }
                        break;
                }
                if (op.sourceAccount.nonEmpty()) {
                    operation.senders[0] = tx.sourceAccount;
                }
                operation.recipients.clear();
                opIndex += 1;
            }
        }

        Try<int> StellarLikeAccount::bulkInsert(const std::vector<Operation> &operations) {
            return Try<int>::from([&] () {
                soci::session sql(getWallet()->getDatabase()->getPool());
                soci::transaction tr(sql);
                StellarLikeOperationDatabaseHelper::bulkInsert(sql, operations);
                tr.commit();
                // Emit
                for (const auto& op : operations) {
                    emitNewOperationEvent(op);
                }
                return operations.size();
            });
        }

        void StellarLikeAccount::updateAccountInfo(soci::session &sql, stellar::Account &account) {
            if (account.accountId == _params.keychain->getAddress()->toString()) {
                soci::transaction tr(sql);
                StellarLikeAccountDatabaseHelper::putAccount(sql, getWallet()->getWalletUid(), _params.index, account);
                tr.commit();
            }
        }

        std::shared_ptr<api::OperationQuery> StellarLikeAccount::queryOperations() {
            auto query = std::make_shared<OperationQuery>(
                    api::QueryFilter::accountEq(getAccountUid()),
                    getWallet()->getDatabase(),
                    getWallet()->getContext(),
                    getWallet()->getMainExecutionContext()
            );
            query->registerAccount(shared_from_this());
            return query;
        }

        void StellarLikeAccount::exists(const std::shared_ptr<api::BoolCallback> &callback) {
            exists().onComplete(getContext(), [=] (const Try<bool>& result) {
                if (result.isFailure()) {
                    callback->onCallback(optional<bool>(), optional<api::Error>(api::Error(result.getFailure().getErrorCode(), result.getFailure().getMessage())));
                } else {
                    callback->onCallback(optional<bool>(result.getValue()), optional<api::Error>());
                }
            });
        }

        std::shared_ptr<api::StellarLikeTransactionBuilder> StellarLikeAccount::buildTransaction() {
            return std::make_shared<ledger::core::StellarLikeTransactionBuilder>(getSelf());
        }

        void StellarLikeAccount::broadcastRawTransaction(const std::vector<uint8_t> &tx,
                                                         const std::shared_ptr<api::StringCallback> &callback) {
            broadcastRawTransaction(tx).callback(getMainExecutionContext(), callback);
        }


        Future<std::string> StellarLikeAccount::broadcastRawTransaction(const std::vector<uint8_t> &tx) {
            return _params.explorer->postTransaction(tx);
        }

        Future<bool> StellarLikeAccount::exists() {
            return  std::dynamic_pointer_cast<StellarLikeWallet>(getWallet())->exists(_params.keychain->getAddress()->toString());
        }

        void StellarLikeAccount::getBaseReserve(const std::shared_ptr<api::AmountCallback> &callback) {
            getBaseReserve().callback(getContext(), callback);
        }

        FuturePtr<Amount> StellarLikeAccount::getBaseReserve() {
            auto self = getSelf();
            auto queryLastLedgerAndGetReserve = [=] () -> std::shared_ptr<Amount> {
                soci::session sql(self->getWallet()->getDatabase()->getReadonlyPool());
                stellar::Ledger lgr;
                auto now = std::chrono::duration_cast<std::chrono::milliseconds>(DateUtils::now().time_since_epoch()).count();
                if (!StellarLikeLedgerDatabaseHelper::getLastLedger(sql, self->getWallet()->getCurrency(), lgr) ||
                      now - std::chrono::duration_cast<std::chrono::milliseconds>(lgr.time.time_since_epoch()).count() > INVALID_SYNCHRONIZATION_DELAY_MS) {
                    throw make_exception(api::ErrorCode::RUNTIME_ERROR, "Last ledger is out dated");
                }
                stellar::Account acc;
                StellarLikeAccountDatabaseHelper::getAccount(sql, self->getAccountUid(), acc);
                return std::make_shared<Amount>(self->getWallet()->getCurrency(), 0, (BigInt(2) + BigInt(acc.subentryCount)) * lgr.baseReserve);
            };
            return async<std::shared_ptr<Amount>>([=] () {
                return queryLastLedgerAndGetReserve();
            }).recoverWith(getContext(), [=] (const Exception& ex) {
                Promise<Unit> promise;
                self->synchronize()->subscribe(self->getContext(), make_promise_receiver(promise,
                        {api::EventCode::SYNCHRONIZATION_SUCCEED, api::EventCode::SYNCHRONIZATION_SUCCEED_ON_PREVIOUSLY_EMPTY_ACCOUNT},
                                                                                         {api::EventCode::SYNCHRONIZATION_FAILED}));
                return promise.getFuture().mapPtr<Amount>(getContext(), [=] (const Unit&) { return queryLastLedgerAndGetReserve(); });
            });
        }

        void StellarLikeAccount::getFeeStats(const std::shared_ptr<api::StellarLikeFeeStatsCallback> &callback) {
            getFeeStats().callback(getContext(), callback);
        }

        Future<api::StellarLikeFeeStats> StellarLikeAccount::getFeeStats() {
            return _params.explorer->getRecommendedFees().map<api::StellarLikeFeeStats>(getContext(), [] (const std::shared_ptr<stellar::FeeStats>& stats) {
                return api::StellarLikeFeeStats(
                        stats->lastBaseFee.toInt64(),
                        stats->modeAcceptedFee.toInt64(),
                        stats->minAccepted.toInt64(),
                        stats->maxFee.toInt64()
                        );
            });
        }

        void StellarLikeAccount::getSequence(const std::shared_ptr<api::BigIntCallback> &callback) {
            getSequence().mapPtr<api::BigInt>(getContext(), [=] (const BigInt& i) -> std::shared_ptr<api::BigInt> {
                return std::make_shared<api::BigIntImpl>(i);
            }).callback(getMainExecutionContext(), callback);
        }

        Future<BigInt> StellarLikeAccount::getSequence() {
            return _params.explorer->getAccount(_params.keychain->getAddress()->toString())
                .map<BigInt>(getContext(), [=] (const std::shared_ptr<stellar::Account>& account) {
                    return BigInt::fromString(account->sequence);
                });
        }

        void StellarLikeAccount::getSigners(const std::shared_ptr<api::StellarLikeAccountSignerListCallback> &callback) {
            getSigners().callback(getMainExecutionContext(), callback);
        }

        Future<std::vector<stellar::AccountSigner>> StellarLikeAccount::getSigners() {
            auto self = getSelf();
            return async<std::vector<stellar::AccountSigner>>([=] () {
                soci::session sql(self->getWallet()->getDatabase()->getReadonlyPool());
                stellar::Account acc;
                StellarLikeAccountDatabaseHelper::getAccount(sql, self->getAccountUid(), acc);
                return acc.signers;
            });
        }

        std::shared_ptr<api::Keychain> StellarLikeAccount::getAccountKeychain() {
            return _params.keychain;
        }
    }
}
