diff --git a/src/db/storage.cpp b/src/db/storage.cpp index 0a84560..bfa6b97 100644 --- a/src/db/storage.cpp +++ b/src/db/storage.cpp @@ -409,52 +409,6 @@ namespace db } } - //! \return a single instance of compiled-in checkpoints for lws - cryptonote::checkpoints const& get_checkpoints() - { - struct initializer - { - cryptonote::checkpoints data; - - initializer() - : data() - { - data.init_default_checkpoints(lws::config::network); - - std::string const* genesis_tx = nullptr; - std::uint32_t genesis_nonce = 0; - - switch (lws::config::network) - { - case cryptonote::TESTNET: - genesis_tx = std::addressof(::config::testnet::GENESIS_TX); - genesis_nonce = ::config::testnet::GENESIS_NONCE; - break; - - case cryptonote::STAGENET: - genesis_tx = std::addressof(::config::stagenet::GENESIS_TX); - genesis_nonce = ::config::stagenet::GENESIS_NONCE; - break; - - case cryptonote::MAINNET: - genesis_tx = std::addressof(::config::GENESIS_TX); - genesis_nonce = ::config::GENESIS_NONCE; - break; - - default: - MONERO_THROW(lws::error::bad_blockchain, "Unsupported net type"); - } - cryptonote::block b; - cryptonote::generate_genesis_block(b, *genesis_tx, genesis_nonce); - crypto::hash block_hash = cryptonote::get_block_hash(b); - if (!data.add_checkpoint(0, epee::to_hex::string(epee::as_byte_span(block_hash)))) - MONERO_THROW(lws::error::bad_blockchain, "Genesis tx and checkpoints file mismatch"); - } - }; - static const initializer instance; - return instance.data; - } - //! \return Current block hash at `id` using `cur`. expect do_get_block_hash(MDB_cursor& cur, block_id id) noexcept { @@ -469,7 +423,7 @@ namespace db cursor::blocks cur = MONERO_UNWRAP(lmdb::open_cursor(txn, tbl)); std::map const& points = - get_checkpoints().get_points(); + storage::get_checkpoints().get_points(); if (points.empty() || points.begin()->first != 0) MONERO_THROW(lws::error::bad_blockchain, "Checkpoints are empty/expected genesis hash"); @@ -558,7 +512,7 @@ namespace db return success(); }; - const std::uint64_t checkpoint = get_checkpoints().get_max_height(); + const std::uint64_t checkpoint = lws::db::storage::get_checkpoints().get_max_height(); const std::uint64_t anchor = lmdb::to_native(out.back().id); for (unsigned i = 1; i <= max_internal; ++i) @@ -1155,6 +1109,51 @@ namespace db return nullptr; } + cryptonote::checkpoints const& storage::get_checkpoints() + { + struct initializer + { + cryptonote::checkpoints data; + + initializer() + : data() + { + data.init_default_checkpoints(lws::config::network); + + std::string const* genesis_tx = nullptr; + std::uint32_t genesis_nonce = 0; + + switch (lws::config::network) + { + case cryptonote::TESTNET: + genesis_tx = std::addressof(::config::testnet::GENESIS_TX); + genesis_nonce = ::config::testnet::GENESIS_NONCE; + break; + + case cryptonote::STAGENET: + genesis_tx = std::addressof(::config::stagenet::GENESIS_TX); + genesis_nonce = ::config::stagenet::GENESIS_NONCE; + break; + + case cryptonote::MAINNET: + genesis_tx = std::addressof(::config::GENESIS_TX); + genesis_nonce = ::config::GENESIS_NONCE; + break; + + default: + MONERO_THROW(lws::error::bad_blockchain, "Unsupported net type"); + } + cryptonote::block b; + cryptonote::generate_genesis_block(b, *genesis_tx, genesis_nonce); + crypto::hash block_hash = cryptonote::get_block_hash(b); + if (!data.add_checkpoint(0, epee::to_hex::string(epee::as_byte_span(block_hash)))) + MONERO_THROW(lws::error::bad_blockchain, "Genesis tx and checkpoints file mismatch"); + } + }; + static const initializer instance; + return instance.data; + } + storage storage::open(const char* path, unsigned create_queue_max) { return { @@ -1454,6 +1453,12 @@ namespace db if (*hash != chain.front()) { + if (current <= get_checkpoints().get_max_height()) + { + MERROR("Attempting rollback past last checkpoint; invalid daemon chain response"); + return {lws::error::bad_blockchain}; + } + MONERO_CHECK(rollback_chain(this->db->tables, txn, *blocks_cur, db::block_id(current))); break; } diff --git a/src/db/storage.h b/src/db/storage.h index 43e2446..d958348 100644 --- a/src/db/storage.h +++ b/src/db/storage.h @@ -41,6 +41,7 @@ #include "lmdb/key_stream.h" #include "lmdb/value_stream.h" +namespace cryptonote { class checkpoints; } namespace lws { namespace db @@ -167,6 +168,10 @@ namespace db {} public: + + //! \return A single instance of compiled-in checkpoints for lws + static cryptonote::checkpoints const& get_checkpoints(); + /*! Open a light_wallet_server LDMB database. diff --git a/tests/unit/db/chain.test.cpp b/tests/unit/db/chain.test.cpp index 54e0cbc..aed7da8 100644 --- a/tests/unit/db/chain.test.cpp +++ b/tests/unit/db/chain.test.cpp @@ -28,6 +28,7 @@ #include "chain.test.h" #include +#include "checkpoints/checkpoints.h" // monero/src #include "db/storage.test.h" #include "error.h" @@ -112,6 +113,23 @@ LWS_CASE("db::storage::sync_chain") lws_test::test_chain(lest_env, MONERO_UNWRAP(db.start_read()), last_block.id, fchain); EXPECT(get_account().scan_height == lws::db::block_id(std::uint64_t(last_block.id) + 1)); } + + SECTION("Fork past checkpoint") + { + const auto& checkpoints = lws::db::storage::get_checkpoints(); + const auto& points = checkpoints.get_points(); + EXPECT(!points.empty()); + + auto point = points.begin(); + const crypto::hash fchain[3] = { + point->second, + crypto::rand(), + crypto::rand() + }; + + const auto sync_result = db.sync_chain(lws::db::block_id(point->first), fchain); + EXPECT(sync_result == lws::error::bad_blockchain); + } } } diff --git a/tests/unit/scanner.test.cpp b/tests/unit/scanner.test.cpp index c2d239a..dd4ca0b 100644 --- a/tests/unit/scanner.test.cpp +++ b/tests/unit/scanner.test.cpp @@ -282,8 +282,6 @@ namespace LWS_CASE("lws::scanner::sync and lws::scanner::run") { - mlog_set_log_level(4); - cryptonote::account_keys keys{}; crypto::generate_keys(keys.m_account_address.m_spend_public_key, keys.m_spend_secret_key); crypto::generate_keys(keys.m_account_address.m_view_public_key, keys.m_view_secret_key);