diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
index 94eb5f4a4..9590b69ed 100644
--- a/.github/workflows/check.yml
+++ b/.github/workflows/check.yml
@@ -12,10 +12,12 @@ jobs:
build-windows:
name: Windows
runs-on: windows-2022
+ permissions:
+ contents: write
env:
BOOST_ROOT: C:/local/boost_1_83_0
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Prepare version
shell: powershell
@@ -27,10 +29,10 @@ jobs:
echo "release_name=${release_name}" >> $env:GITHUB_OUTPUT
- name: Install msbuild
- uses: microsoft/setup-msbuild@v2
+ uses: microsoft/setup-msbuild@v3
- name: Restore Boost
- uses: actions/cache@v4
+ uses: actions/cache@v5
id: restore-boost
with:
path: ${{env.BOOST_ROOT}}
@@ -64,7 +66,7 @@ jobs:
cp build/tests/Release/*_tests.exe build/conceal
- name: Upload To GH Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ steps.setup.outputs.release_name }}
path: build/conceal
@@ -99,11 +101,13 @@ jobs:
build-mingw:
name: MinGW
runs-on: windows-2022
+ permissions:
+ contents: write
defaults:
run:
shell: msys2 {0}
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- uses: msys2/setup-msys2@v2
with:
@@ -148,7 +152,7 @@ jobs:
cp build/tests/*_tests.exe build/conceal
- name: Upload To GH Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ steps.setup.outputs.release_name }}
path: build/conceal
@@ -183,8 +187,10 @@ jobs:
build-ubuntu22:
name: Ubuntu 22.04
runs-on: ubuntu-22.04
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Prepare version
id: setup
@@ -217,7 +223,7 @@ jobs:
cp build/tests/*_tests build/conceal
- name: Upload To GH Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ steps.setup.outputs.release_name }}
path: build/conceal
@@ -253,8 +259,10 @@ jobs:
build-ubuntu24:
name: Ubuntu 24.04
runs-on: ubuntu-24.04
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Prepare version
id: setup
@@ -287,7 +295,7 @@ jobs:
cp build/tests/*_tests build/conceal
- name: Upload To GH Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ steps.setup.outputs.release_name }}
path: build/conceal
@@ -323,8 +331,10 @@ jobs:
build-ubuntu22-clang:
name: Ubuntu 22.04 clang
runs-on: ubuntu-22.04
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Prepare version
id: setup
@@ -357,7 +367,7 @@ jobs:
cp build/tests/*_tests build/conceal
- name: Upload To GH Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ steps.setup.outputs.release_name }}
path: build/conceal
@@ -392,28 +402,31 @@ jobs:
build-macos:
name: macOS
- runs-on: macos-13
+ runs-on: macos-15-intel
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Prepare version
id: setup
run: |
- os=macos-13
+ os=macos-15-intel
ccx_version=${GITHUB_SHA::7}
release_name=ccx-cli-"$os"-dev-"$ccx_version"
echo "release_name=${release_name}" >> $GITHUB_OUTPUT
- name: Install dependencies
run: |
- brew install boost@1.85
+ brew install boost
+ brew list --versions boost
- name: Build
id: build
run: |
mkdir build
cd build
- cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DSTATIC=ON -DBOOST_ROOT=/usr/local/opt/boost@1.85
+ cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=ON -DSTATIC=ON -DBOOST_ROOT="$(brew --prefix boost)"
make -j2
- name: Prepare release
@@ -426,7 +439,7 @@ jobs:
cp build/tests/*_tests build/conceal
- name: Upload To GH Artifacts
- uses: actions/upload-artifact@v4
+ uses: actions/upload-artifact@v6
with:
name: ${{ steps.setup.outputs.release_name }}
path: build/conceal
diff --git a/.github/workflows/macOS.yml b/.github/workflows/macOS.yml
index 03aab0c24..e74487bcd 100644
--- a/.github/workflows/macOS.yml
+++ b/.github/workflows/macOS.yml
@@ -4,13 +4,15 @@ on:
push:
tags:
- "*"
-
+
jobs:
build-macos:
name: macOS
- runs-on: macos-13
+ runs-on: macos-15-intel
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Build
id: build
@@ -36,7 +38,7 @@ jobs:
echo "ccx_version=${ccx_version}" >> $GITHUB_OUTPUT
- name: Create Release
- uses: softprops/action-gh-release@v2.0.4
+ uses: softprops/action-gh-release@v3
with:
files: ${{ steps.build.outputs.asset_path }}
name: Conceal Core CLI v${{ steps.build.outputs.ccx_version }}
diff --git a/.github/workflows/ubuntu22.yml b/.github/workflows/ubuntu22.yml
index e82b4d8b0..9bcc377c6 100644
--- a/.github/workflows/ubuntu22.yml
+++ b/.github/workflows/ubuntu22.yml
@@ -9,8 +9,10 @@ jobs:
build-ubuntu22:
name: Ubuntu 22.04
runs-on: ubuntu-22.04
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Build
id: build
@@ -43,7 +45,7 @@ jobs:
echo "ccx_version=${ccx_version}" >> $GITHUB_OUTPUT
- name: Create Release
- uses: softprops/action-gh-release@v2.0.4
+ uses: softprops/action-gh-release@v3
with:
files: ${{ steps.build.outputs.asset_path }}
name: Conceal Core CLI v${{ steps.build.outputs.ccx_version }}
diff --git a/.github/workflows/ubuntu24.yml b/.github/workflows/ubuntu24.yml
index 2e2e338f6..9849e0673 100644
--- a/.github/workflows/ubuntu24.yml
+++ b/.github/workflows/ubuntu24.yml
@@ -4,13 +4,15 @@ on:
push:
tags:
- "*"
-
+
jobs:
build-ubuntu24:
name: Ubuntu 24.04
runs-on: ubuntu-24.04
+ permissions:
+ contents: write
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Build
id: build
@@ -43,7 +45,7 @@ jobs:
echo "ccx_version=${ccx_version}" >> $GITHUB_OUTPUT
- name: Create Release
- uses: softprops/action-gh-release@v2.0.4
+ uses: softprops/action-gh-release@v3
with:
files: ${{ steps.build.outputs.asset_path }}
name: Conceal Core CLI v${{ steps.build.outputs.ccx_version }}
diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml
index 38b3522a7..8dbfe7e7e 100644
--- a/.github/workflows/windows.yml
+++ b/.github/workflows/windows.yml
@@ -9,13 +9,15 @@ jobs:
build-windows:
name: Windows
runs-on: windows-2022
+ permissions:
+ contents: write
env:
BOOST_ROOT: C:/local/boost_1_83_0
steps:
- - uses: actions/checkout@master
+ - uses: actions/checkout@v5
- name: Setup msbuild
- uses: microsoft/setup-msbuild@v2
+ uses: microsoft/setup-msbuild@v3
- name: Restore Boost
uses: actions/cache@v4
@@ -55,7 +57,7 @@ jobs:
echo "ccx_version=${ccx_version}" >> $env:GITHUB_OUTPUT
- name: Create Release
- uses: softprops/action-gh-release@v2.0.4
+ uses: softprops/action-gh-release@v3
with:
files: ${{ steps.build.outputs.asset_path }}
name: Conceal Core CLI v${{ steps.build.outputs.ccx_version }}
diff --git a/.gitignore b/.gitignore
index f42fcf92a..efb6ba307 100644
--- a/.gitignore
+++ b/.gitignore
@@ -25,4 +25,5 @@ compile_commands.json
*.json
cmake-build*
-.cursorrules
\ No newline at end of file
+.cursorrules
+CLAUDE.md
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3ac2b6ed9..6b195f9fd 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.5)
include(CheckCXXCompilerFlag)
-set(VERSION "6.7.3")
+set(VERSION "6.7.4-beta.1")
set(VERSION_BUILD_NO "Trebopala")
# Packaged from main commits
set(COMMIT 1db6e66)
@@ -112,7 +112,8 @@ else()
endif()
if(MINGW)
set(WARNINGS "${WARNINGS} -Wno-error=unused-value")
- set(MINGW_FLAG "-DWIN32_LEAN_AND_MEAN")
+ # Boost.Bind: avoid deprecated global placeholders (_1, _2) pragma on newer Boost
+ set(MINGW_FLAG "-DWIN32_LEAN_AND_MEAN -DBOOST_BIND_GLOBAL_PLACEHOLDERS")
include_directories(SYSTEM src/platform/mingw)
else()
set(MINGW_FLAG "")
@@ -144,6 +145,10 @@ if(APPLE)
add_definitions(/DHAVE_ROTR)
endif()
+if(POLICY CMP0167)
+ cmake_policy(SET CMP0167 OLD)
+endif()
+
if(STATIC)
set(Boost_NO_BOOST_CMAKE ON)
set(Boost_USE_STATIC_LIBS ON)
@@ -151,7 +156,17 @@ if(STATIC)
endif()
#set(Boost_DEBUG on)
-find_package(Boost 1.55 REQUIRED COMPONENTS system filesystem thread date_time chrono regex serialization program_options)
+set(BOOST_COMPONENTS filesystem thread date_time chrono regex serialization program_options)
+
+if(APPLE)
+ find_package(Boost 1.55 COMPONENTS system ${BOOST_COMPONENTS})
+ if(NOT Boost_FOUND)
+ message(STATUS "Boost.System library not found; retrying without system component")
+ find_package(Boost 1.55 REQUIRED COMPONENTS ${BOOST_COMPONENTS})
+ endif()
+else()
+ find_package(Boost 1.55 REQUIRED COMPONENTS system ${BOOST_COMPONENTS})
+endif()
include_directories(SYSTEM ${Boost_INCLUDE_DIRS})
if(MINGW)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ab99bfa93..300b2bc7f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,12 +1,14 @@
Development Process
-Developers work in their own trees, then submit pull requests when they think their feature or bug fix is ready.
+Contributors should work from their own fork of the repository (not push branches directly to the upstream repo unless they are maintainers with that workflow). When you start a change, create a branch named `/`: use a three-letter identifier derived from your name or handle, then a short topic (often kebab-case words). Examples for someone named John Doe: `jdo/fix`, `doe/dependencies`.
+
+Open pull requests against the upstream **`development`** branch when you consider your feature or bug fix ready.
The patch will be accepted if there is broad consensus that it is a good thing. Developers should expect to rework and resubmit patches if they don't match the project's coding conventions or are controversial.
-The master branch is regularly built and tested, but is not guaranteed to be completely stable. Tags are regularly created to indicate new official, stable release versions of Conceal.
+The `development` branch is regularly built and tested, but is not guaranteed to be completely stable. Tags are regularly created to indicate new official, stable release versions of Conceal.
-Feature branches are created when there are major new features being worked on by several people.
+Feature branches on upstream may be created when there are major new features being worked on by several people.
From time to time a pull request will become outdated. If this occurs, and the pull is no longer automatically mergeable; a comment on the pull will be used to issue a warning of closure. The pull will be closed 15 days after the warning if action is not taken by the author. Pull requests closed in this manner will have their corresponding issue labeled 'stagnant'.
diff --git a/COPYING.md b/COPYING.md
deleted file mode 100644
index aa6404ce4..000000000
--- a/COPYING.md
+++ /dev/null
@@ -1,24 +0,0 @@
-MIT License
-Distributed under the MIT/X11 software license http://www.opensource.org/licenses/mit-license.php
-
-Copyright (c) 2017-2023 Conceal Community
-Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
-Copyright (c) 2018-2023 Conceal Network & Conceal Devs
-
-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.
diff --git a/COPYRIGHT b/COPYRIGHT
deleted file mode 100644
index aa6404ce4..000000000
--- a/COPYRIGHT
+++ /dev/null
@@ -1,24 +0,0 @@
-MIT License
-Distributed under the MIT/X11 software license http://www.opensource.org/licenses/mit-license.php
-
-Copyright (c) 2017-2023 Conceal Community
-Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
-Copyright (c) 2018-2023 Conceal Network & Conceal Devs
-
-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.
diff --git a/LICENSE b/LICENSE
index aa6404ce4..f9f447e7a 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,9 +1,8 @@
MIT License
-Distributed under the MIT/X11 software license http://www.opensource.org/licenses/mit-license.php
-Copyright (c) 2017-2023 Conceal Community
+Copyright (c) 2011-2017 The Cryptonote developers
Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
-Copyright (c) 2018-2023 Conceal Network & Conceal Devs
+Copyright (c) 2018-2024 Conceal Network & Conceal Devs
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
@@ -21,4 +20,4 @@ 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.
+SOFTWARE.
\ No newline at end of file
diff --git a/README.md b/README.md
index 9099320f2..1a58ec5fe 100644
--- a/README.md
+++ b/README.md
@@ -167,4 +167,4 @@ If the build is successful the binaries will be located in the `src` folder.
Special thanks goes out to the developers from Cryptonote, Bytecoin, Ryo, Monero, Forknote, TurtleCoin, Karbo and Masari.
-Copyright (c) 2017-2023 Conceal Community, Conceal Network & Conceal Devs
\ No newline at end of file
+Copyright (c) 2017-2024 Conceal Community, Conceal Network & Conceal Devs
\ No newline at end of file
diff --git a/external/parallel_hashmap/phmap.h b/external/parallel_hashmap/phmap.h
index 37f0f7c14..194e4f600 100644
--- a/external/parallel_hashmap/phmap.h
+++ b/external/parallel_hashmap/phmap.h
@@ -2753,7 +2753,7 @@ class parallel_hash_set
std::is_nothrow_default_constructible::value&&
std::is_nothrow_default_constructible::value) {}
-#if (__cplusplus >= 201703L || _MSVC_LANG >= 201402) && (defined(_MSC_VER) || defined(__clang__) || (defined(__GNUC__) && __GNUC__ > 6))
+#if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201402)) && (defined(_MSC_VER) || defined(__clang__) || (defined(__GNUC__) && __GNUC__ > 6))
explicit parallel_hash_set(size_t bucket_cnt,
const hasher& hash_param = hasher(),
const key_equal& eq = key_equal(),
diff --git a/src/ConcealWallet/ConcealWallet.cpp b/src/ConcealWallet/ConcealWallet.cpp
index 7e0fec321..4378031ce 100644
--- a/src/ConcealWallet/ConcealWallet.cpp
+++ b/src/ConcealWallet/ConcealWallet.cpp
@@ -770,12 +770,16 @@ bool conceal_wallet::reset(const std::vector &)
m_wallet->reset(0);
m_wallet->addObserver(this);
- success_msg_writer(true) << "Reset completed successfully.";
+ success_msg_writer(true) << "Attempting wallet reset... this could take some time.";
std::unique_lock lock(m_walletSynchronizedMutex);
m_walletSynchronizedCV.wait(lock, [this]
{ return m_walletSynchronized; });
+ if (m_walletSynchronized) {
+ success_msg_writer(true) << "Wallet reset was successful";
+ }
+
std::cout << std::endl;
return true;
@@ -1833,4 +1837,4 @@ void conceal_wallet::depositUpdated(DepositId depositId)
void conceal_wallet::depositsUpdated(const std::vector &depositIds)
{
// Nothing to do here
-}
\ No newline at end of file
+}
diff --git a/src/CryptoNoteConfig.h b/src/CryptoNoteConfig.h
index 4bde41c38..c62ee2dd0 100644
--- a/src/CryptoNoteConfig.h
+++ b/src/CryptoNoteConfig.h
@@ -1,6 +1,6 @@
// Copyright (c) 2012-2017 The Cryptonote developers
// Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
-// Copyright (c) 2018-2023 Conceal Network & Conceal Devs
+// Copyright (c) 2018-2026 Conceal Network & Conceal Devs
//
//
// Distributed under the MIT/X11 software license, see the accompanying
@@ -141,7 +141,8 @@ namespace cn
const char P2P_NET_DATA_FILENAME[] = "p2pstate.bin";
const char CRYPTONOTE_BLOCKCHAIN_INDICES_FILENAME[] = "blockchainindices.dat";
const char MINER_CONFIG_FILE_NAME[] = "miner_conf.json";
-
+ const char CRYPTONOTE_CHECKPOINT_FILENAME[] = "checkpoint.dat";
+
} // namespace parameters
const uint64_t START_BLOCK_REWARD = (UINT64_C(5000) * parameters::POINT); // start reward (Consensus I)
@@ -185,13 +186,15 @@ namespace cn
and the minimum version for communication between nodes */
const uint8_t P2P_VERSION_1 = 1;
const uint8_t P2P_VERSION_2 = 2;
- const uint8_t P2P_CURRENT_VERSION = 1;
+ const uint8_t P2P_CURRENT_VERSION = 2; // Incremented to support checkpoint list propagation
const uint8_t P2P_MINIMUM_VERSION = 1;
const uint8_t P2P_UPGRADE_WINDOW = 2;
// This defines the minimum P2P version required for lite blocks propogation
const uint8_t P2P_LITE_BLOCKS_PROPOGATION_VERSION = 3;
+ const uint8_t P2P_CHECKPOINT_LIST_VERSION = 2;
+
const size_t P2P_LOCAL_WHITE_PEERLIST_LIMIT = 1000;
const size_t P2P_LOCAL_GRAY_PEERLIST_LIMIT = 5000;
@@ -206,6 +209,23 @@ namespace cn
const uint32_t P2P_DEFAULT_PING_CONNECTION_TIMEOUT = 2000; // 2 seconds
const uint64_t P2P_DEFAULT_INVOKE_TIMEOUT = 60 * 2 * 1000; // 2 minutes
const size_t P2P_DEFAULT_HANDSHAKE_INVOKE_TIMEOUT = 5000; // 5 seconds
+ const size_t P2P_CHECKPOINT_LIST_RE_REQUEST = 300; // 5 minutes
+ const uint32_t P2P_CHECKPOINT_PEER_MIN_UPTIME_BLOCKS = 2; // Minimum uptime for checkpoint verification (mainnet: 2 blocks × 2 min = 4 min)
+ const uint32_t P2P_CHECKPOINT_PEER_MIN_UPTIME_BLOCKS_TESTNET = 2; // Minimum uptime for checkpoint verification (testnet: 2 blocks × 2 min = 4 min relaxed for testing)
+
+ // Checkpoint consensus configuration (Mainnet)
+ // M = minimum agreements required (all M peers must agree)
+ // K = total peers to sample
+ // n = minimum distinct /16 networks required
+ const uint32_t CKPT_MIN_CONSENSUS_PEERS = 6; // M: minimum agreements required
+ const uint32_t CKPT_CONSENSUS_PEERS = 6; // K: minimun total peers to sample
+ const uint32_t CKPT_MIN_DIVERSE_NETWORKS = 3; // n: minimum distinct /16 networks required
+
+ // Checkpoint consensus configuration (Testnet - relaxed for smaller network)
+ const uint32_t CKPT_MIN_CONSENSUS_PEERS_TESTNET = 2; // M: minimum agreements required (testnet)
+ const uint32_t CKPT_CONSENSUS_PEERS_TESTNET = 2; // K: minimum total peers to sample (testnet) - increased to ask multiple peers
+ const uint32_t CKPT_MIN_DIVERSE_NETWORKS_TESTNET = 2; // n: minimum distinct /16 networks required (testnet)
+
const char P2P_STAT_TRUSTED_PUB_KEY[] = "f7061e9a5f0d30549afde49c9bfbaa52ac60afdc46304642b460a9ea34bf7a4e";
// Seed Nodes
@@ -220,8 +240,7 @@ namespace cn
};
const std::initializer_list TESTNET_SEED_NODES = {
- "161.97.145.65:15500",
- "161.97.145.65:15501"
+ "5.189.177.60:15500"
};
struct CheckpointData
@@ -234,6 +253,9 @@ namespace cn
__attribute__((unused))
#endif
+ const char DNS_CHECKPOINT_DOMAIN[] = "checkpoints.conceal.id";
+ const char TESTNET_DNS_CHECKPOINT_DOMAIN[] = "testpoints.conceal.id";
+
// Blockchain Checkpoints:
// {, ""},
const std::initializer_list
@@ -425,7 +447,25 @@ namespace cn
{1840000, "b3a95fbf906555264a31906678b299c0851d09628b8d37969a285cb54a614c31"},
{1850000, "041d84a8c1fb68d24e6a9d8fe5aab27c6db654ecaad3b2d541c40d6e9df53feb"},
{1860000, "63642037f4d5d82150c120e776a3b2d2a80f725b4759ffda12c94bd45499fb25"},
- {1870000, "159fa13f6f9f48e2d8344e76ebdfe2ea986b9a72e94325dacbbd3bd73cbdbb41"}
+ {1870000, "159fa13f6f9f48e2d8344e76ebdfe2ea986b9a72e94325dacbbd3bd73cbdbb41"},
+ {1880000, "7e5dd6c104989975b482d7bf277934d8f9252e81f5bc996ed7c54ee95542ade6"},
+ {1890000, "c2ded97cfaefc52aa1de9f6ca8f04d2f3c2ab3b48bf572801555e9fa7934f954"},
+ {1900000, "50f2fc1af569f1ebb6567b915e2ee1bf72c585f544725e952a303b447faab909"},
+ {1910000, "e2f3e1560369c4e457fdeffbe2ca2fbf9db7aa89a5cd6d9d2e96f7ef826690eb"},
+ {1920000, "ba318eb902d6a3f7dd4e7116b13c6b1f2cb4e8c9e70acbde3b6a28a1374c6243"},
+ {1930000, "6f39f0888c14808710e909fb45c864fdf738cf644e432486d8e01b65dbbc2862"},
+ {1940000, "fe774dd97a5b975c02abb18ca48bf6fd3a8048b597520befe1f261855a7ccf3f"},
+ {1950000, "6147f276271c55319cf6fc03e86bcaa75ea0a2aab126095a9a9f8a811eb229e1"},
+ {1960000, "e5b31d500706cbca7e7eaca4b5762413533c826692c632a1c0a1a0df6771f42d"},
+ {1970000, "5b8da5814d2d82bd21db65b2b3c78b0f2038deab2ae16eee721193713c9e2e27"},
+ {1980000, "a69211b77326d9ff74fe8be81c281e0a44b0c8bbee643cd6d736e186362fd7f5"},
+ {1990000, "fe9b87f2e98f544af8b7ac950b7b5a1eebf0a9508ea613c74bd3304433726b30"},
+ {2000000, "bf1e1396f4ee1c21a2d574035c6b422d62b8aa3d4b3268111b1b73357ad49740"},
+ {2010000, "c4806687630ae3fc1c371889617f0659f2622ba9bcdcee1a0c349dea87ed4f84"},
+ {2020000, "ab632a9b914875a2b30bbf5946859c352801450df35e1fdaee8107da3d1df64e"},
+ {2030000, "c0b03673083146e028c609498a95e0f22ae61f06f7c961cd1ac5d8ba99c8e3c6"},
+ {2040000, "f6abea5fb93bb391f24599252656d40c1d716cb18a245b200f6b70d962bf2bdf"},
+ {2050000, "ab2df8795e771bb9b74e8a893f91673f9f44321844ed11d31e11270c09c6e6ae"}
};
const std::initializer_list TESTNET_CHECKPOINTS = {
@@ -467,7 +507,14 @@ namespace cn
{875000, "91eac54f608c1d0e43111c107a2f5caf259bcbbd66714b6591cedbc64f9c4cdf"},
{900000, "a70b6df1794a6d91071cd5fc87719769bf09610d520c2c2134f53908d1e3de40"},
{925000, "a00b47f3610cfd5c509183322fca89e388c0427601ce4db7e397acbcab5a3ee6"},
- {950000, "387573b7b9bdbc1d79c28156cf15d7e08ddf248a0257b0ee7ef2731c5c7a0534"}
+ {950000, "387573b7b9bdbc1d79c28156cf15d7e08ddf248a0257b0ee7ef2731c5c7a0534"},
+ {975000, "2bbf6d2fecb329d9c34968e60b0d2814a2d5a2f69ee872b77d152ba795e881ae"},
+ {1000000, "c2627d61bd357fe38398e6fbc14c5c8510aac12934fc696bf7d7935a762f03a1"},
+ {1025000, "325a7b27a91d221c58597e17a913b5bf6c79704c1b45a7849ba5d745c6f82e04"},
+ {1050000, "ed30311a19f916184da2e961b7cf527114954dc954817804cc49c5f010632dea"},
+ {1075000, "0da0f9185f7f32e9ef29eaebe290e1614cb85987e4fb6ba027570be3d2cc33ac"},
+ {1100000, "cd87368b14488a85c2a1064ccf61855db4664919c8c2ddef1c6f088951700f19"},
+ {1125000, "04c19d19ed31300a088b9485ddea0a2738821a7ea7da42f8324c8144f4e8c9c3"}
};
} // namespace cn
diff --git a/src/CryptoNoteCore/BlockIndex.cpp b/src/CryptoNoteCore/BlockIndex.cpp
index 243170f26..48a869336 100644
--- a/src/CryptoNoteCore/BlockIndex.cpp
+++ b/src/CryptoNoteCore/BlockIndex.cpp
@@ -34,6 +34,12 @@ namespace cn {
return result;
}
+ crypto::Hash BlockIndex::getHashOfIds(uint32_t startBlockIndex, uint32_t maxCount) const {
+ /* This can be made more efficient by hashing in-place instead of copying all hashes, but it needs three function hash code */
+ std::vector block_ids = getBlockIds(startBlockIndex, maxCount);
+ return crypto::cn_fast_hash(block_ids.data(), block_ids.size() * sizeof(crypto::Hash));
+ }
+
bool BlockIndex::findSupplement(const std::vector& ids, uint32_t& offset) const {
for (const auto& id : ids) {
if (getBlockHeight(id, offset)) {
diff --git a/src/CryptoNoteCore/BlockIndex.h b/src/CryptoNoteCore/BlockIndex.h
index c122c315e..d64b1a9ef 100644
--- a/src/CryptoNoteCore/BlockIndex.h
+++ b/src/CryptoNoteCore/BlockIndex.h
@@ -58,6 +58,7 @@ namespace cn
crypto::Hash getBlockId(uint32_t height) const;
std::vector getBlockIds(uint32_t startBlockIndex, uint32_t maxCount) const;
+ crypto::Hash getHashOfIds(uint32_t startBlockIndex, uint32_t maxCount) const;
bool findSupplement(const std::vector& ids, uint32_t& offset) const;
std::vector buildSparseChain(const crypto::Hash& startBlockId) const;
crypto::Hash getTailId() const;
diff --git a/src/CryptoNoteCore/Blockchain.cpp b/src/CryptoNoteCore/Blockchain.cpp
index ca0223c6c..61e370af1 100644
--- a/src/CryptoNoteCore/Blockchain.cpp
+++ b/src/CryptoNoteCore/Blockchain.cpp
@@ -1,7 +1,7 @@
// Copyright (c) 2011-2017 The Cryptonote developers
// Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
// Copyright (c) 2018-2020 Karbo developers
-// Copyright (c) 2018-2025 Conceal Network & Conceal Devs
+// Copyright (c) 2018-2026 Conceal Network & Conceal Devs
//
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -10,6 +10,7 @@
#include
#include
+#include
#include
#include
#include
@@ -367,7 +368,6 @@ namespace cn
m_blockchainIndexesEnabled(blockchainIndexesEnabled),
m_blockchainAutosaveEnabled(blockchainAutosaveEnabled),
logger(logger, "Blockchain")
-
{
}
@@ -475,7 +475,6 @@ namespace cn
try
{
m_testnet = testnet;
- m_checkpoints.set_testnet(testnet);
std::lock_guard lk(m_blockchain_lock);
if (!config_folder.empty() && !tools::create_directories_if_necessary(config_folder))
@@ -486,6 +485,13 @@ namespace cn
m_config_folder = config_folder;
+ m_checkpoints.init_targets(testnet, appendPath(config_folder, m_currency.checkpointFileName()));
+ // Only load checkpoint.dat if it is the new chunk-based format.
+ // Old-style files (first entry = genesis block hash) are ignored here;
+ // they are handled once in the try block below after the blockchain is loaded.
+ if (!m_checkpoints.is_old_style_checkpoint_file())
+ m_checkpoints.load_checkpoints_from_file();
+
if (!m_blocks.open(appendPath(config_folder, m_currency.blocksFileName()),
appendPath(config_folder, m_currency.blockIndexesFileName()), 1024))
{
@@ -516,6 +522,12 @@ namespace cn
logger(ERROR, BRIGHT_RED) << "Failed to rebuild cache";
return false;
}
+
+ if (!storeCache())
+ {
+ logger(ERROR, BRIGHT_RED) << "Failed to save rebuilt blockchain cache";
+ return false;
+ }
}
}
@@ -551,6 +563,12 @@ namespace cn
logger(ERROR, BRIGHT_RED) << "Failed to rebuild cache";
return false;
}
+
+ if (!storeCache())
+ {
+ logger(ERROR, BRIGHT_RED) << "Failed to save rebuilt blockchain cache";
+ return false;
+ }
}
catch (const std::exception&)
{
@@ -597,22 +615,498 @@ namespace cn
}
}
+ // Convert old-style checkpoints (individual block hashes) to new-style (list hashes)
+ // ONLY if we don't have valid chunks covering the hardcoded checkpoint range.
+ // If chunks exist and cover the hardcoded checkpoints, we're using the new chunked system
+ // and don't need to convert targets (they're only needed for old-style full checkpoint lists).
try
{
- /* If the current checkpoint is invalid, then rollback the chain to the last
- valid checkpoint and try again. */
- uint32_t lastValidCheckpointHeight = 0;
- if (!checkCheckpoints(lastValidCheckpointHeight))
+ uint32_t currentHeight = static_cast(m_blocks.size() - 1);
+ uint32_t greatestTargetHeight = m_checkpoints.get_greatest_target_height();
+ uint32_t currentCoveredHeight = m_checkpoints.get_covered_height();
+ uint32_t computeUpTo = std::min(greatestTargetHeight, currentHeight);
+
+ auto getBlockIdsFunc = [this](uint32_t startHeight, uint32_t maxCount) -> std::vector {
+ return m_blockIndex.getBlockIds(startHeight, maxCount);
+ };
+
+ if (currentHeight > 0 && greatestTargetHeight > 0 && computeUpTo > 0)
{
- logger(WARNING, BRIGHT_YELLOW) << "Invalid checkpoint. Rollback blockchain to last valid checkpoint at height "
- << lastValidCheckpointHeight;
- rollbackBlockchainTo(lastValidCheckpointHeight);
+ uint32_t chunk_size = m_checkpoints.get_chunk_size();
+ uint32_t last_chunk = (computeUpTo - 1) / chunk_size;
+
+ if (m_checkpoints.is_old_style_checkpoint_file())
+ {
+ // ── ONE-TIME TRANSITION ──────────────────────────────────────────────
+ // Convert m_targets to cumulative list hashes up to what we actually have,
+ // then rebuild all chunks from scratch and write the new-format file.
+ // Writing chunk 0 overwrites the old file — transition never repeats.
+ logger(INFO) << "Old-style checkpoint.dat: one-time transition to chunk format "
+ << "(up to height " << computeUpTo << ")";
+
+ m_checkpoints.convert_old_checkpoints_to_list_hashes(getBlockIdsFunc, computeUpTo);
+
+ for (uint32_t idx = 0; idx <= last_chunk; ++idx)
+ {
+ uint32_t chunk_start = idx * chunk_size + 1;
+ uint32_t chunk_end = (idx + 1) * chunk_size;
+ if (currentHeight < chunk_end) break;
+ if (!m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start))
+ { logger(WARNING) << "Transition: chunk " << idx << " failed validation — stopping"; break; }
+ if (!m_checkpoints.add_verified_chunk_to_file(idx))
+ logger(WARNING) << "Transition: chunk " << idx << " could not be written";
+ }
+
+ logger(INFO) << "Transition complete — checkpoint.dat is now chunk-based";
+ }
+ else if (currentCoveredHeight < computeUpTo)
+ {
+ // ── NORMAL PATH ──────────────────────────────────────────────────────
+ // New-style or absent: append only the missing chunks up to computeUpTo.
+ uint32_t first_chunk = m_checkpoints.get_chunk_count();
+
+ logger(INFO) << "Appending checkpoint chunks " << first_chunk << ".." << last_chunk
+ << " (heights " << (currentCoveredHeight + 1) << ".." << computeUpTo << ")";
+
+ for (uint32_t idx = first_chunk; idx <= last_chunk; ++idx)
+ {
+ uint32_t chunk_start = idx * chunk_size + 1;
+ uint32_t chunk_end = (idx + 1) * chunk_size;
+ if (currentHeight < chunk_end)
+ { logger(DEBUGGING) << "Chunk " << idx << " incomplete — will finish during sync"; break; }
+ if (!m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start))
+ { logger(WARNING) << "Chunk " << idx << " failed validation — stopping"; break; }
+ if (!m_checkpoints.add_verified_chunk_to_file(idx))
+ logger(WARNING) << "Chunk " << idx << " could not be appended to checkpoint.dat";
+ }
+ }
}
}
- catch (const std::exception&)
+ catch (const std::exception& e)
{
- logger(ERROR, BRIGHT_RED) << "Error checking/rolling back checkpoints";
+ logger(WARNING, BRIGHT_YELLOW) << "Error building checkpoint chunks: " << e.what();
+ }
+
+ // Generate checkpoint.dat from local blockchain if it doesn't exist or is incomplete
+ //
+ // CHUNKED CHECKPOINT GENERATION:
+ // Instead of storing all block hashes (60MB), we now store chunk hashes (~6KB):
+ // - Each chunk = hash of 10,000 consecutive blocks
+ // - For 1.87M blocks: 187 chunks × 32 bytes = ~6KB
+ // - Benefits: Fast P2P comparison, efficient disagreement detection, small file size
+ try
+ {
+ uint32_t currentHeight = static_cast(m_blocks.size() - 1);
+ uint32_t greatestTargetHeight = m_checkpoints.get_greatest_target_height();
+ uint32_t currentCoveredHeight = m_checkpoints.get_covered_height();
+
+ // If we have a blockchain and checkpoint targets, but no checkpoint chunks (or incomplete),
+ // generate them from the local blockchain
+ if (currentHeight > 0 && greatestTargetHeight > 0)
+ {
+ // Calculate target height: up to greatest known target, but not more than current height
+ uint32_t targetHeight = std::min(greatestTargetHeight, currentHeight);
+
+ if (currentCoveredHeight < targetHeight)
+ {
+ uint32_t existing_chunk_count = m_checkpoints.get_chunk_count();
+
+ // If we already have some chunks, only create the missing ones incrementally
+ // Otherwise, generate all chunks from scratch
+ if (existing_chunk_count > 0)
+ {
+ logger(INFO) << "Creating missing checkpoint chunk(s) from " << (currentCoveredHeight + 1)
+ << " to " << greatestTargetHeight << " (last hardcoded checkpoint)";
+
+ uint32_t chunk_size = m_checkpoints.get_chunk_size();
+ auto getBlockIdsFunc = [this](uint32_t startHeight, uint32_t maxCount) -> std::vector {
+ return m_blockIndex.getBlockIds(startHeight, maxCount);
+ };
+
+ // Calculate which chunks we need to create
+ // Find the last chunk we have
+ uint32_t last_chunk_index;
+ if (currentCoveredHeight <= chunk_size)
+ {
+ last_chunk_index = 0; // We have at least chunk 0
+ }
+ else
+ {
+ last_chunk_index = (currentCoveredHeight - 1) / chunk_size;
+ }
+
+ // Find which chunk the last hardcoded checkpoint belongs to
+ uint32_t target_chunk_index;
+ if (greatestTargetHeight <= chunk_size)
+ {
+ target_chunk_index = 0;
+ }
+ else
+ {
+ target_chunk_index = (greatestTargetHeight - 1) / chunk_size;
+ }
+
+ // Create missing chunks one by one
+ for (uint32_t chunk_idx = last_chunk_index + 1; chunk_idx <= target_chunk_index; chunk_idx++)
+ {
+ // SIMPLIFIED: All chunks are uniform (block 0 excluded)
+ uint32_t chunk_start_height = chunk_idx * chunk_size + 1;
+ uint32_t chunk_end_height = (chunk_idx + 1) * chunk_size;
+
+ if (currentHeight >= chunk_end_height)
+ {
+ if (m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start_height))
+ {
+ // Auto-confirm chunks up to the last hardcoded checkpoint
+ // These chunks are validated against hardcoded checkpoints in add_chunk_from_block_ids,
+ // so they're safe to add to checkpoint.dat immediately
+ if (m_checkpoints.add_verified_chunk_to_file(chunk_idx))
+ {
+ logger(INFO) << "Created and saved checkpoint chunk " << chunk_idx
+ << " (blocks " << chunk_start_height << "-" << chunk_end_height
+ << ", validated against hardcoded checkpoints)";
+ }
+ else
+ {
+ logger(WARNING) << "Created chunk " << chunk_idx << " but failed to save to checkpoint.dat";
+ }
+ }
+ else
+ {
+ logger(WARNING) << "Failed to create chunk " << chunk_idx << " - blockchain mismatch detected";
+ break; // Stop if we can't create a chunk
+ }
+ }
+ else
+ {
+ logger(INFO) << "Skipping chunk " << chunk_idx
+ << " - not enough blocks (have " << currentHeight
+ << ", need " << chunk_end_height << ")";
+ break; // Can't create more chunks if we don't have enough blocks
+ }
+ }
+ }
+ else
+ {
+ // REFINED STRATEGY: Initial setup (no checkpoint.dat file)
+ // 1. Generate chunks up to last CryptoNoteConfig.h checkpoint (PRIORITY 1)
+ // 2. If DNS checkpoints exist beyond that, try to extend up to DNS checkpoint (PRIORITY 2)
+ // 3. Beyond that, chunks will be validated via P2P (autonomous)
+
+ logger(INFO, BRIGHT_GREEN) << "Initial setup: Generating checkpoint chunks from local blockchain (height "
+ << currentHeight << ", target up to " << greatestTargetHeight << " from CryptoNoteConfig.h)";
+
+ // Step 1: Generate chunks up to last CryptoNoteConfig.h checkpoint
+ auto getBlockIdsFunc = [this](uint32_t startHeight, uint32_t maxCount) -> std::vector {
+ return m_blockIndex.getBlockIds(startHeight, maxCount);
+ };
+
+ if (m_checkpoints.generate_chunks_from_block_ids(getBlockIdsFunc, targetHeight))
+ {
+ uint32_t chunkCount = m_checkpoints.get_chunk_count();
+ uint32_t coveredHeight = m_checkpoints.get_covered_height();
+ logger(INFO, BRIGHT_GREEN) << "Successfully generated " << chunkCount
+ << " checkpoint chunks from CryptoNoteConfig.h (covers up to height " << coveredHeight << ")";
+
+ // Step 2: Create chunks beyond hardcoded checkpoints up to current blockchain height
+ // These chunks will be stored in memory and validated via P2P consensus
+ if (currentHeight > greatestTargetHeight)
+ {
+ uint32_t chunk_size = m_checkpoints.get_chunk_size();
+ uint32_t last_hardcoded_chunk_index = (greatestTargetHeight - 1) / chunk_size;
+ uint32_t current_chunk_index = (currentHeight - 1) / chunk_size;
+
+ logger(INFO) << "Creating chunks " << (last_hardcoded_chunk_index + 1)
+ << " to " << current_chunk_index
+ << " (beyond hardcoded checkpoints, up to current height " << currentHeight << ")";
+
+ // Create chunks beyond hardcoded checkpoints (these need P2P validation)
+ // Use same logic as existing code path (lines 915-952) for consistency
+ for (uint32_t chunk_idx = last_hardcoded_chunk_index + 1; chunk_idx <= current_chunk_index; chunk_idx++)
+ {
+ // SIMPLIFIED: All chunks are uniform (block 0 excluded)
+ uint32_t chunk_start_height = chunk_idx * chunk_size + 1;
+ uint32_t chunk_end_height = (chunk_idx + 1) * chunk_size;
+
+ // Only create if we have all blocks for this chunk
+ if (currentHeight >= chunk_end_height)
+ {
+ // Check if chunk already exists in memory (from previous session)
+ crypto::Hash existing_chunk_hash = m_checkpoints.get_chunk_hash(chunk_idx);
+ if (existing_chunk_hash != NULL_HASH)
+ {
+ logger(DEBUGGING) << "Chunk " << chunk_idx << " already exists in memory (from previous session), awaiting P2P validation";
+ continue; // Skip to next chunk
+ }
+
+ // Create new chunk
+ if (!m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start_height))
+ {
+ logger(WARNING) << "Failed to create chunk " << chunk_idx << " - blockchain mismatch detected";
+ break;
+ }
+ // Chunk created and hash computed - logging is done in CheckpointsList.cpp
+ // NOTE: We do NOT call add_verified_chunk_to_file() here
+ // These chunks need peer consensus before being saved to checkpoint.dat
+ }
+ else
+ {
+ logger(INFO) << "Skipping chunk " << chunk_idx
+ << " - not enough blocks (have " << currentHeight
+ << ", need " << chunk_end_height << ")";
+ break;
+ }
+ }
+ }
+
+ logger(INFO) << "Initial setup complete. Chunks beyond CryptoNoteConfig.h checkpoints will be validated via P2P consensus.";
+ }
+ else
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Checkpoint chunk generation failed. "
+ << "This may happen if the blockchain doesn't match expected checkpoints from CryptoNoteConfig.h.";
+ }
+ }
+ }
+ else
+ {
+ logger(INFO, BRIGHT_GREEN) << "Checkpoint chunks already exist (covers up to height "
+ << currentCoveredHeight << ")";
+
+ // CRITICAL: Validate chunks against checkpoints from CryptoNoteConfig.h
+ // This detects if maintainer added new checkpoints that don't match our checkpoint.dat
+ // If validation fails, rollback blockchain and truncate checkpoint.dat
+ auto getBlockIdsFunc = [this](uint32_t startHeight, uint32_t maxCount) -> std::vector {
+ return m_blockIndex.getBlockIds(startHeight, maxCount);
+ };
+
+ // Get current blockchain height for validation
+ uint32_t current_height = static_cast(m_blocks.size() > 0 ? m_blocks.size() - 1 : 0);
+ auto validation_result = m_checkpoints.validate_chunks_against_checkpoints(getBlockIdsFunc, current_height);
+
+ if (!validation_result.is_valid)
+ {
+ uint32_t mismatched_chunk = validation_result.first_mismatched_chunk_index;
+ uint32_t rollback_height = mismatched_chunk * m_checkpoints.get_chunk_size();
+
+ logger(ERROR, BRIGHT_RED) << "CHECKPOINT VALIDATION FAILED: "
+ << "Chunk " << mismatched_chunk << " (contains checkpoint from CryptoNoteConfig.h) "
+ << "does not match checkpoint.dat. "
+ << "This indicates maintainer added a new checkpoint or blockchain diverged. "
+ << "Rolling back blockchain to height " << rollback_height
+ << " and truncating checkpoint.dat to chunk " << (mismatched_chunk > 0 ? mismatched_chunk - 1 : 0);
+
+ // Truncate checkpoint.dat to last valid chunk (before the mismatch)
+ uint32_t last_valid_chunk = (mismatched_chunk > 0) ? (mismatched_chunk - 1) : 0;
+ if (!m_checkpoints.truncate_checkpoint_file(last_valid_chunk))
+ {
+ logger(ERROR, BRIGHT_RED) << "Failed to truncate checkpoint.dat - file may be corrupted";
+ }
+
+ // Rollback blockchain to the chunk boundary before the mismatch
+ if (rollback_height > 0 && rollback_height < currentHeight)
+ {
+ logger(INFO, BRIGHT_YELLOW) << "Rolling back blockchain from height " << currentHeight
+ << " to height " << rollback_height;
+ if (!rollbackBlockchainTo(rollback_height))
+ {
+ logger(ERROR, BRIGHT_RED) << "Failed to rollback blockchain - node may be in inconsistent state";
return false;
+ }
+ logger(INFO, BRIGHT_GREEN) << "Successfully rolled back blockchain to height " << rollback_height;
+ }
+ else
+ {
+ logger(ERROR, BRIGHT_RED) << "Cannot rollback to height " << rollback_height
+ << " (current height: " << currentHeight << ")";
+ return false;
+ }
+ }
+ else
+ {
+ // VERSION 2 CHECKPOINT SYSTEM: Create missing chunks beyond last hardcoded checkpoint
+ // Only for P2P version 2+ nodes (chunked checkpoint system)
+ // Version 1 nodes stick with old checkpoint method only
+ if (cn::P2P_CURRENT_VERSION >= cn::P2P_CHECKPOINT_LIST_VERSION)
+ {
+ // Check if we're past the last chunk boundary and need to create new chunks
+ // These chunks are beyond the last hardcoded checkpoint, so they need P2P validation
+ uint32_t chunk_size = m_checkpoints.get_chunk_size();
+ uint32_t last_chunk_index;
+
+ if (currentCoveredHeight <= chunk_size)
+ {
+ last_chunk_index = 0;
+ }
+ else
+ {
+ last_chunk_index = (currentCoveredHeight - 1) / chunk_size;
+ }
+
+ // Calculate which chunk the current height belongs to
+ uint32_t current_chunk_index;
+ if (currentHeight <= chunk_size)
+ {
+ current_chunk_index = 0;
+ }
+ else
+ {
+ current_chunk_index = (currentHeight - 1) / chunk_size;
+ }
+
+ // If we're past the last chunk boundary, create missing chunks
+ if (current_chunk_index > last_chunk_index && currentHeight > greatestTargetHeight)
+ {
+ uint32_t num_chunks_to_create = current_chunk_index - last_chunk_index;
+ if (num_chunks_to_create == 1) {
+ logger(INFO) << "Checking for missing checkpoint chunk " << current_chunk_index
+ << " beyond last hardcoded checkpoint (heights "
+ << (currentCoveredHeight + 1) << " to " << currentHeight << ")";
+ } else {
+ logger(INFO) << "Checking for missing checkpoint chunks beyond last hardcoded checkpoint (from chunk "
+ << (last_chunk_index + 1) << " to " << current_chunk_index
+ << ", heights " << (currentCoveredHeight + 1) << " to " << currentHeight << ")";
+ }
+
+ auto getBlockIdsFunc = [this](uint32_t startHeight, uint32_t maxCount) -> std::vector {
+ return m_blockIndex.getBlockIds(startHeight, maxCount);
+ };
+
+ // Create missing chunks one by one (these need P2P validation, not auto-confirmed)
+ for (uint32_t chunk_idx = last_chunk_index + 1; chunk_idx <= current_chunk_index; chunk_idx++)
+ {
+ // SIMPLIFIED: All chunks are uniform (block 0 excluded)
+ uint32_t chunk_start_height = chunk_idx * chunk_size + 1;
+ uint32_t chunk_end_height = (chunk_idx + 1) * chunk_size;
+
+ // Only create if we have all blocks for this chunk
+ if (currentHeight >= chunk_end_height)
+ {
+ // Check if chunk already exists in memory (from previous session)
+ crypto::Hash existing_chunk_hash = m_checkpoints.get_chunk_hash(chunk_idx);
+ if (existing_chunk_hash != NULL_HASH)
+ {
+ logger(DEBUGGING) << "Chunk " << chunk_idx << " already exists in memory (from previous session), awaiting P2P validation";
+ continue; // Skip to next chunk
+ }
+
+ // Create new chunk
+ if (!m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start_height))
+ {
+ logger(WARNING) << "Failed to create chunk " << chunk_idx << " - blockchain mismatch detected";
+ break;
+ }
+ // Chunk created and hash computed - logging is done in CheckpointsList.cpp
+ // NOTE: We do NOT call add_verified_chunk_to_file() here
+ // These chunks need peer consensus before being saved to checkpoint.dat
+ }
+ else
+ {
+ logger(INFO) << "Skipping chunk " << chunk_idx
+ << " - not enough blocks (have " << currentHeight
+ << ", need " << chunk_end_height << ")";
+ break;
+ }
+ }
+
+ // Log unverified chunks that need P2P validation
+ std::vector unverified = m_checkpoints.get_unverified_chunks();
+ if (!unverified.empty())
+ {
+ if (unverified.size() == 1)
+ logger(INFO) << "Found 1 unverified chunk: " << unverified[0];
+ else
+ logger(INFO) << "Found " << unverified.size() << " unverified chunks from " << unverified[0];
+ }
+ }
+ }
+
+ // Check if we're missing chunks up to the last hardcoded checkpoint
+ // If we're synced past the last checkpoint, we can safely create missing chunks
+ // Only for version 2+ nodes (version 1 uses old checkpoint method)
+ if (cn::P2P_CURRENT_VERSION >= cn::P2P_CHECKPOINT_LIST_VERSION &&
+ currentCoveredHeight < greatestTargetHeight && currentHeight >= greatestTargetHeight)
+ {
+ logger(INFO) << "Creating missing checkpoint chunk(s) from " << (currentCoveredHeight + 1)
+ << " to " << greatestTargetHeight << " (last hardcoded checkpoint)";
+
+ uint32_t chunk_size = m_checkpoints.get_chunk_size();
+
+ // Calculate which chunks we need to create
+ // Find the last chunk we have
+ uint32_t last_chunk_index;
+ if (currentCoveredHeight <= chunk_size)
+ {
+ last_chunk_index = 0; // We have at least chunk 0
+ }
+ else
+ {
+ last_chunk_index = (currentCoveredHeight - 1) / chunk_size;
+ }
+
+ // Find which chunk the last hardcoded checkpoint belongs to
+ uint32_t target_chunk_index;
+ if (greatestTargetHeight <= chunk_size)
+ {
+ target_chunk_index = 0;
+ }
+ else
+ {
+ target_chunk_index = (greatestTargetHeight - 1) / chunk_size;
+ }
+
+ // Create missing chunks one by one
+ for (uint32_t chunk_idx = last_chunk_index + 1; chunk_idx <= target_chunk_index; chunk_idx++)
+ {
+ // SIMPLIFIED: All chunks are uniform (block 0 excluded)
+ uint32_t chunk_start_height = chunk_idx * chunk_size + 1;
+ uint32_t chunk_end_height = (chunk_idx + 1) * chunk_size;
+
+ if (currentHeight >= chunk_end_height)
+ {
+ logger(INFO, BRIGHT_GREEN) << "Creating missing chunk " << chunk_idx
+ << " (blocks " << chunk_start_height << " to " << chunk_end_height << ")";
+
+ if (m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start_height))
+ {
+ // Auto-confirm chunks up to the last hardcoded checkpoint
+ // These chunks are validated against hardcoded checkpoints in add_chunk_from_block_ids,
+ // so they're safe to add to checkpoint.dat immediately
+ if (m_checkpoints.add_verified_chunk_to_file(chunk_idx))
+ {
+ logger(INFO, BRIGHT_GREEN) << "Successfully created and saved chunk " << chunk_idx
+ << " to checkpoint.dat (validated against hardcoded checkpoints)";
+ }
+ else
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Created chunk " << chunk_idx
+ << " but failed to save to checkpoint.dat";
+ }
+ }
+ else
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Failed to create chunk " << chunk_idx
+ << " - this may indicate a blockchain mismatch";
+ break; // Stop if we can't create a chunk
+ }
+ }
+ else
+ {
+ logger(INFO) << "Skipping chunk " << chunk_idx
+ << " - not enough blocks yet (have " << currentHeight
+ << ", need " << chunk_end_height << ")";
+ break; // Can't create more chunks if we don't have enough blocks
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ catch (const std::exception& e)
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Error generating checkpoint list: " << e.what();
+ // Don't fail initialization - checkpoint generation is optional for lone wolf nodes
}
// Initialize upgrade detectors with proper exception handling
@@ -656,25 +1150,75 @@ namespace cn
bool Blockchain::checkCheckpoints(uint32_t &lastValidCheckpointHeight)
{
- std::vector checkpointHeights = m_checkpoints.getCheckpointHeights();
- for (const auto &checkpointHeight : checkpointHeights)
+ // Get checkpoint targets from hardcoded checkpoints in CryptoNoteConfig.h
+ // These are the expected hashes of block ID lists at specific heights
+ std::vector> checkpointHeights = m_checkpoints.get_checkpoint_targets();
+ lastValidCheckpointHeight = 0;
+
+ // If no checkpoints are configured, validation passes
+ if (checkpointHeights.empty())
{
- if (m_blocks.size() <= checkpointHeight)
- {
- return true;
- }
+ return true;
+ }
- if (m_checkpoints.check_block(checkpointHeight, getBlockIdByHeight(checkpointHeight)))
+ uint32_t currentHeight = static_cast(m_blocks.size() - 1);
+
+ /* Checkpoints are in reverse order (highest first), so we check from highest to lowest.
+ * If the highest checkpoint fails, we check the previous one, and so on,
+ * until we find a valid checkpoint. Once we find a valid one, we rollback to that height. */
+ bool foundValidCheckpoint = false;
+ for (const auto &check : checkpointHeights)
+ {
+ uint32_t height = check.first;
+
+ // Only validate checkpoints that are at or below the current blockchain height
+ if (currentHeight < height)
+ continue;
+
+ // Compute hash of block IDs from genesis (height 0) to this checkpoint height
+ // Compare against the hardcoded checkpoint target from CryptoNoteConfig.h
+ crypto::Hash hv = m_blockIndex.getHashOfIds(0, height+1);
+ if (hv == check.second)
{
- lastValidCheckpointHeight = checkpointHeight;
+ lastValidCheckpointHeight = height;
+ foundValidCheckpoint = true;
+ logger(INFO, BRIGHT_GREEN) << "Found valid checkpoint at height " << height;
+ break;
}
else
{
- return false;
+ logger(WARNING, BRIGHT_YELLOW) << "Checkpoint validation failed for height " << height <<
+ " - computed hash: " << hv << " expected (from hardcoded checkpoints): " << check.second;
+ // Continue checking previous checkpoints
}
}
- logger(INFO, BRIGHT_WHITE) << "Checkpoints passed";
- return true;
+
+ if(foundValidCheckpoint)
+ {
+ logger(INFO, BRIGHT_WHITE) << "Checkpoints validated, last valid checkpoint at height " << lastValidCheckpointHeight;
+ uint32_t n_valid_blocks = lastValidCheckpointHeight+1;
+ if(m_checkpoints.get_points_size() < n_valid_blocks)
+ m_checkpoints.set_checkpoint_list(m_blockIndex.getBlockIds(0, n_valid_blocks));
+
+ // If we found a valid checkpoint but it's not at the current height, we need to rollback
+ if (lastValidCheckpointHeight < currentHeight)
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Blockchain height " << currentHeight <<
+ " is beyond last valid checkpoint " << lastValidCheckpointHeight << ", rollback required";
+ return false; // Signal that rollback is needed
+ }
+
+ return true; // All checkpoints up to current height are valid
+ }
+ else
+ {
+ // No valid checkpoint found - this is a problem
+ logger(ERROR, BRIGHT_RED) << "No valid checkpoint found! All checkpoint validations failed.";
+ // Don't set lastValidCheckpointHeight to 0 - that would rollback the entire chain
+ // Instead, return true to allow the blockchain to continue (maybe checkpoints need updating)
+ // But log this as a serious error
+ return true; // Allow continuation, but this should be investigated
+ }
}
bool Blockchain::rebuildCache()
@@ -690,6 +1234,7 @@ namespace cn
m_spent_keys.clear();
m_outputs.clear();
m_multisignatureOutputs.clear();
+ crypto::Hash prevBlockHash = NULL_HASH;
for (uint32_t b = 0; b < m_blocks.size(); ++b)
{
if (b % 1000 == 0)
@@ -699,6 +1244,20 @@ namespace cn
const BlockEntry &block = m_blocks[b];
crypto::Hash blockHash = get_block_hash(block.bl);
+
+ // Verify chain linkage: every block except genesis must reference the previous block's hash.
+ // A mismatch means blockchain.dat was tampered or is corrupt at this height.
+ if (b > 0 && block.bl.previousBlockHash != prevBlockHash)
+ {
+ logger(ERROR, BRIGHT_RED)
+ << "Chain linkage broken at height " << b
+ << ": block.previousBlockHash=" << block.bl.previousBlockHash
+ << " expected=" << prevBlockHash
+ << ". blockchain.dat is corrupt or has been tampered.";
+ return false;
+ }
+ prevBlockHash = blockHash;
+
m_blockIndex.push(blockHash);
uint64_t interest = 0;
for (uint32_t t = 0; t < block.transactions.size(); ++t)
@@ -838,13 +1397,15 @@ namespace cn
BlockCacheSerializer ser(*this, getTailId(), logger.getLogger());
const std::string &blocksCacheFileName = m_currency.blocksCacheFileName();
- std::string blockCacheBkpFileName = blocksCacheFileName + ".bkp";
+ const std::string blocksCachePath = appendPath(m_config_folder, blocksCacheFileName);
+ const std::string blockCacheBkpPath = blocksCachePath + ".bkp";
try
{
- std::rename(blocksCacheFileName.c_str(), blockCacheBkpFileName.c_str()); // fail here can be ignored
+ std::remove(blockCacheBkpPath.c_str()); // fail here can be ignored
+ std::rename(blocksCachePath.c_str(), blockCacheBkpPath.c_str()); // fail here can be ignored
- if (!ser.save(appendPath(m_config_folder, blocksCacheFileName)))
+ if (!ser.save(blocksCachePath))
{
logger(ERROR, BRIGHT_RED) << "Failed to save blockchain cache";
return false;
@@ -1548,6 +2109,46 @@ namespace cn
return true;
}
+ bool Blockchain::is_alternative_block_allowed(uint32_t blockchain_height, uint32_t block_height) const {
+ if (0 == block_height)
+ return false;
+
+ uint32_t lowest_height = blockchain_height - cn::parameters::CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW;
+
+ if (blockchain_height < cn::parameters::CRYPTONOTE_MINED_MONEY_UNLOCK_WINDOW)
+ {
+ lowest_height = 0;
+ }
+
+ if (block_height < lowest_height && !m_checkpoints.is_in_checkpoint_zone(block_height))
+ {
+ logger(logging::DEBUGGING, logging::WHITE)
+ << "<< Checkpoints.cpp << "
+ << "Reorganization depth too deep : " << (blockchain_height - block_height) << ". Block Rejected";
+ return false;
+ }
+
+ // Prefer chunk-based coverage (live chain): only blocks beyond the confirmed
+ // checkpoint zone are allowed as alternative heads.
+ uint32_t covered_height = m_checkpoints.get_covered_height();
+ if (covered_height > 0) {
+ return covered_height < block_height;
+ }
+
+ // No chunks present (e.g. unit tests, early bootstrap): fall back to
+ // target-based logic. Find the highest checkpoint target whose height has
+ // already been reached by the current blockchain. Blocks at or below that
+ // height cannot be alternative heads; blocks above it can.
+ uint32_t checkpoint_zone_height = 0;
+ for (const auto& target : m_checkpoints.get_checkpoint_targets()) {
+ uint32_t target_height = target.first;
+ if (target_height <= blockchain_height && target_height > checkpoint_zone_height) {
+ checkpoint_zone_height = target_height;
+ }
+ }
+ return checkpoint_zone_height < block_height;
+ }
+
bool Blockchain::handle_alternative_block(const Block &b, const crypto::Hash &id, block_verification_context &bvc, bool sendNewAlternativeBlockMessage)
{
try
@@ -1563,10 +2164,7 @@ namespace cn
return false;
}
- /* in the absence of a better solution, we fetch checkpoints from dns records */
- m_checkpoints.load_checkpoints_from_dns();
-
- if (!m_checkpoints.is_alternative_block_allowed(getCurrentBlockchainHeight(), block_height))
+ if (!is_alternative_block_allowed(getCurrentBlockchainHeight(), block_height))
{
logger(DEBUGGING) << "Block with id: " << id << std::endl
<< " can't be accepted for alternative chain, block height: " << block_height << std::endl
@@ -1661,8 +2259,8 @@ namespace cn
bei.bl = b;
bei.height = alt_chain.size() ? it_prev->second.height + 1 : mainPrevHeight + 1;
- bool is_a_checkpoint;
- if (!m_checkpoints.check_block(bei.height, id, is_a_checkpoint))
+ auto checkpoint_status = m_checkpoints.check_checkpoint(bei.height, id);
+ if ( checkpoint_status == CheckpointList::is_in_zone_failed )
{
logger(ERROR, BRIGHT_RED) << "Checkpoint validaton failure";
@@ -1728,7 +2326,7 @@ namespace cn
alt_chain.push_back(i_res.first->first);
- if (is_a_checkpoint)
+ if ( checkpoint_status == CheckpointList::is_checkpointed )
{
//do reorganize!
logger(INFO, BRIGHT_GREEN) << "###### REORGANIZE on height: " << m_alternative_chains[alt_chain.front()].height << " of " << m_blocks.size() - 1 << ", checkpoint is found in alternative chain on height " << bei.height;
@@ -2660,15 +3258,13 @@ namespace cn
auto longhashTimeStart = std::chrono::steady_clock::now();
crypto::Hash proof_of_work = NULL_HASH;
- if (m_checkpoints.is_in_checkpoint_zone(getCurrentBlockchainHeight()))
+ auto checkpoint_status = m_checkpoints.check_checkpoint(getCurrentBlockchainHeight(), blockHash);
+ if (checkpoint_status == CheckpointList::is_in_zone_failed )
{
- if (!m_checkpoints.check_block(getCurrentBlockchainHeight(), blockHash))
- {
- bvc.m_verification_failed = true;
- return false;
- }
+ bvc.m_verification_failed = true;
+ return false;
}
- else
+ else if(checkpoint_status == CheckpointList::is_out_of_zone )
{
if (!m_currency.checkProofOfWork(m_cn_context, blockData, currentDifficulty, proof_of_work))
{
@@ -2788,6 +3384,74 @@ namespace cn
bvc.m_added_to_main_chain = true;
+ // Check if we've reached a chunk boundary and need to create a new chunk hash
+ // This happens every chunk_size blocks (10,000 for mainnet, 25,000 for testnet)
+ // SIMPLIFIED: Block 0 (genesis) is excluded from chunks, all chunks are uniform
+ uint32_t current_height = block.height;
+ uint32_t chunk_size = m_checkpoints.get_chunk_size();
+ uint32_t current_covered_height = m_checkpoints.get_covered_height();
+
+ // Skip block 0 (genesis) - it's validated separately and not part of any chunk
+ if (current_height > 0 && current_height > current_covered_height)
+ {
+ // SIMPLIFIED: Calculate which chunk this height belongs to
+ // All chunks are uniform: chunk_size blocks each
+ // chunk[0]: blocks 1 to chunk_size
+ // chunk[1]: blocks (chunk_size + 1) to (2 * chunk_size)
+ // chunk[n]: blocks (n * chunk_size + 1) to ((n + 1) * chunk_size)
+ uint32_t chunk_index = (current_height - 1) / chunk_size;
+ uint32_t chunk_start_height = chunk_index * chunk_size + 1;
+ uint32_t chunk_end_height = (chunk_index + 1) * chunk_size;
+
+ // If we've reached the end of a chunk, create the chunk hash
+ // Only create if we haven't already created this chunk
+ if (current_height == chunk_end_height && current_height > current_covered_height)
+ {
+ // Check if chunk already exists (might have been created during initial generation
+ // or as a prefetched placeholder). A prefetched chunk must be verified here.
+ bool chunk_is_prefetched = m_checkpoints.is_chunk_prefetched(chunk_index);
+ crypto::Hash existing_chunk_hash = m_checkpoints.get_chunk_hash(chunk_index);
+
+ if (existing_chunk_hash == NULL_HASH || chunk_is_prefetched)
+ {
+ logger(INFO, BRIGHT_GREEN) << "Reached chunk boundary at height " << current_height
+ << " (chunk " << chunk_index << ", blocks " << chunk_start_height
+ << " to " << chunk_end_height << "). "
+ << (chunk_is_prefetched ? "Verifying prefetched hash..." : "Computing chunk hash...");
+
+ auto getBlockIdsFunc = [this](uint32_t startHeight, uint32_t maxCount) -> std::vector {
+ return m_blockIndex.getBlockIds(startHeight, maxCount);
+ };
+
+ bool prefetch_mismatch = false;
+ if (!m_checkpoints.add_chunk_from_block_ids(getBlockIdsFunc, chunk_start_height, &prefetch_mismatch))
+ {
+ if (prefetch_mismatch)
+ {
+ // Local chain diverges from the prefetched network consensus hash.
+ // The chunk has been re-queued as unverified; validate_unverified_chunks()
+ // will run M-of-K consensus and trigger a rollback if the network confirms
+ // the divergence.
+ logger(ERROR, BRIGHT_RED)
+ << "Chunk " << chunk_index << " PREFETCH MISMATCH at height " << current_height
+ << "! Local hash differs from prefetched network consensus. "
+ << "Consensus validation will trigger rollback if network confirms divergence.";
+ }
+ else
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Failed to compute chunk " << chunk_index
+ << " hash at height " << current_height;
+ }
+ }
+ }
+ else
+ {
+ logger(DEBUGGING) << "Chunk " << chunk_index << " already exists (hash: " << existing_chunk_hash
+ << "), skipping creation at height " << current_height;
+ }
+ }
+ }
+
m_upgradeDetectorV2.blockPushed();
m_upgradeDetectorV3.blockPushed();
m_upgradeDetectorV4.blockPushed();
@@ -3185,28 +3849,7 @@ namespace cn
try
{
std::lock_guard lk(m_blockchain_lock);
-
- logger(INFO, BRIGHT_YELLOW) << "Rolling back blockchain to height " << height;
-
- // Check if we're already at or below the requested height
- if (height >= m_blocks.size())
- {
- logger(WARNING, BRIGHT_YELLOW) << "Requested rollback to height " << height
- << " which is higher than current height " << m_blocks.size();
- return true;
- }
-
- while (m_blocks.size() > height + 1)
- {
- if (!removeLastBlock())
- {
- logger(ERROR, BRIGHT_RED) << "Failed to remove last block during rollback";
- return false;
- }
- }
-
- logger(INFO, BRIGHT_GREEN) << "Blockchain successfully rolled back to height: " << height << "Synchronization will resume";
- return true;
+ return rollback_to_height_impl(height);
}
catch (const std::exception&)
{
@@ -3215,6 +3858,38 @@ namespace cn
}
}
+ bool Blockchain::rollback_to_height_impl(uint32_t height)
+ {
+ logger(INFO, BRIGHT_YELLOW) << "Rolling back blockchain to height " << height;
+
+ // Check if we're already at or below the requested height
+ if (height >= m_blocks.size())
+ {
+ logger(WARNING, BRIGHT_YELLOW) << "Requested rollback to height " << height
+ << " which is higher than current height " << m_blocks.size();
+ return true;
+ }
+
+ // Validate height is reasonable (not 0, which would rollback genesis)
+ if (height == 0 && m_blocks.size() > 1)
+ {
+ logger(ERROR, BRIGHT_RED) << "Cannot rollback to height 0 (genesis block) - current height: " << m_blocks.size();
+ return false;
+ }
+
+ while (m_blocks.size() > height + 1)
+ {
+ if (!removeLastBlock())
+ {
+ logger(ERROR, BRIGHT_RED) << "Failed to remove last block during rollback";
+ return false;
+ }
+ }
+
+ logger(INFO, BRIGHT_GREEN) << "Blockchain successfully rolled back to height: " << height << "Synchronization will resume";
+ return true;
+ }
+
bool Blockchain::removeLastBlock()
{
if (m_blocks.empty())
@@ -3509,4 +4184,9 @@ namespace cn
return m_checkpoints.is_in_checkpoint_zone(height);
}
+ crypto::Hash Blockchain::getCheckpointHash(uint32_t height) const
+ {
+ std::lock_guard lk(m_blockchain_lock);
+ return m_blockIndex.getHashOfIds(0, height+1);
+ }
} // namespace cn
diff --git a/src/CryptoNoteCore/Blockchain.h b/src/CryptoNoteCore/Blockchain.h
index 79506dcb5..fba03e42d 100644
--- a/src/CryptoNoteCore/Blockchain.h
+++ b/src/CryptoNoteCore/Blockchain.h
@@ -15,7 +15,7 @@
#include "Common/ObserverManager.h"
#include "Common/Util.h"
#include "CryptoNoteCore/BlockIndex.h"
-#include "CryptoNoteCore/Checkpoints.h"
+#include "CryptoNoteCore/CheckpointList.h"
#include "CryptoNoteCore/Currency.h"
#include "CryptoNoteCore/DepositIndex.h"
#include "CryptoNoteCore/IBlockchainStorageObserver.h"
@@ -68,7 +68,6 @@ namespace cn
bool getLowerBound(uint64_t timestamp, uint64_t startOffset, uint32_t &height);
std::vector getBlockIds(uint32_t startHeight, uint32_t maxCount);
- void setCheckpoints(Checkpoints &&chk_pts) { m_checkpoints = std::move(chk_pts); }
bool getBlocks(uint32_t start_offset, uint32_t count, std::list &blocks, std::list &txs);
bool getBlocks(uint32_t start_offset, uint32_t count, std::list &blocks);
bool getAlternativeBlocks(std::list &blocks);
@@ -84,6 +83,10 @@ namespace cn
bool haveTransaction(const crypto::Hash &id);
bool haveTransactionKeyImagesAsSpent(const Transaction &tx);
+ CheckpointList& getCheckpointList() {
+ return m_checkpoints;
+ }
+
uint32_t getCurrentBlockchainHeight(); // TODO rename to getCurrentBlockchainSize
crypto::Hash getTailId();
crypto::Hash getTailId(uint32_t &height);
@@ -125,6 +128,8 @@ namespace cn
uint64_t coinsEmittedAtHeight(uint64_t height);
uint64_t difficultyAtHeight(uint64_t height);
bool isInCheckpointZone(const uint32_t height) const;
+ crypto::Hash getCheckpointHash(uint32_t height) const;
+ bool is_alternative_block_allowed(uint32_t blockchain_height, uint32_t block_height) const;
template
bool scanOutputKeysForIndexes(const KeyInput &tx_in_to_key, visitor_t &vis, uint32_t *pmax_related_block_height = nullptr);
@@ -220,6 +225,9 @@ namespace cn
bool have_tx_keyimg_as_spent(const crypto::KeyImage &key_im);
private:
+ // Private helper for rollback - performs the actual rollback operation
+ // Precondition: m_blockchain_lock must be held
+ bool rollback_to_height_impl(uint32_t height);
bool m_testnet = false;
struct MultisignatureOutputUsage
{
@@ -284,7 +292,7 @@ namespace cn
outputs_container m_outputs;
std::string m_config_folder;
- Checkpoints m_checkpoints;
+ CheckpointList m_checkpoints;
std::atomic m_is_in_checkpoint_zone;
using Blocks = SwappedVector;
@@ -482,4 +490,4 @@ namespace cn
return true;
}
};
-} // namespace cn
\ No newline at end of file
+} // namespace cn
diff --git a/src/CryptoNoteCore/CheckpointList.h b/src/CryptoNoteCore/CheckpointList.h
new file mode 100644
index 000000000..2fa33b3ec
--- /dev/null
+++ b/src/CryptoNoteCore/CheckpointList.h
@@ -0,0 +1,712 @@
+// Copyright (c) 2011-2017 The Cryptonote developers
+// Copyright (c) 2017-2018 The Circle Foundation & Conceal Devs
+// Copyright (c) 2018-2023 Conceal Network & Conceal Devs
+//
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#pragma once
+#include