Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- name: HF16 offline regression checks
run: |
set -euo pipefail
python3 tests/p2p/test_slot_interval.py
python3 tests/p2p/test_vote_ingress.py
python3 tests/validator/test_signing_cache.py
python3 tests/chain/test_snapshot_restore.py
Expand Down
2 changes: 2 additions & 0 deletions @l10n/ru/docs/consensus/hf16.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ HF16 намеренно сохраняет **1.000 VIZ на произведён
Параметры хранятся и голосуются в **реальном числе блоков**, согласно выбранной политике миграции HF16. Однократная миграция увеличивает существующие голоса и медиану втрое, но не распознаёт последующий голос из старого скрипта. Делегатам нужно увеличить зашитые `inflation_recalc_period`, `distribution_epoch_length`, `pm_batch_epoch_blocks`, `pm_reveal_window_blocks` втрое для сохранения длительности. Для голосования defaults используйте актуальный `get_config.CHAIN_DEFAULT_CHAIN_PROPERTIES`; при изменении другого поля сохраняйте остальные текущие on-chain значения. Старые defaults структуры повторно не отправляйте.

`get_config` согласованно возвращает текущие блоки/час/сутки/год, ёмкость undo, пороги пропусков и defaults. Шаг изменения reserve ratio сохраняет 60 секунд. `CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS` — неиспользуемая историческая константа: выход из emergency определяется кворумом активных валидаторов, а не таймером 21 блок.

Пороги P2P-синхронизации (отставание, разрешение форка, wedge watchdog) масштабируются по текущей длине слота и сохраняют прежнюю длительность. Опции оператора в блоках не пересчитываются: `dlt-block-log-max-blocks`, `snapshot-every-n-blocks`, `history-count-blocks`, `block-archive-range` после HF16 покрывают втрое меньший срок. Скользящий block log по умолчанию (100000 блоков) обслуживает синхронизацию пиров около 28 часов вместо 3,5 дней; значение 300000 сохраняет прежнее окно.
6 changes: 4 additions & 2 deletions @l10n/ru/docs/node/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ snapshot-max-age-days = 90
| Ежедневно | 28 800 | ~24 часа |
| Еженедельно | 100 000 | ~3,5 дня |

Время указано для блоков по 3 секунды. После HF16 (блок 1 секунда) то же число блоков покрывает втрое меньший срок; для прежней длительности умножьте значение на три.

### Метод 4: Совмещение at-block и периодических

Оба параметра могут быть активны одновременно:
Expand Down Expand Up @@ -197,7 +199,7 @@ vizd \
- **API-доступа** — `get_block` работает для блоков в скользящем окне.

```ini
dlt-block-log-max-blocks = 100000 # хранить последние ~3,5 дня блоков
dlt-block-log-max-blocks = 100000 # ~3,5 дня до HF16, ~28 ч после; 300000 сохраняет 3,5 дня
```

Когда лог превышает этот размер, старые блоки удаляются с начала. Реализация отслеживает логические размеры файлов независимо от файла, отображаемого в память, для предотвращения ошибок с устаревшим размером после тысяч циклов изменения размера.
Expand Down Expand Up @@ -347,7 +349,7 @@ snapshot-every-n-blocks = 28800
snapshot-dir = /data/viz-snapshots
snapshot-max-age-days = 90

# DLT-скользящий block log: хранить последние ~3,5 дня
# DLT-скользящий block log: ~3,5 дня до HF16, ~28 ч после (300000 сохраняет 3,5 дня)
dlt-block-log-max-blocks = 100000

shared-file-size = 4G
Expand Down
6 changes: 4 additions & 2 deletions @l10n/zh-CN/docs/node/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ snapshot-max-age-days = 90
| 每日 | 28,800 | 约 24 小时 |
| 每周 | 100,000 | 约 3.5 天 |

以上时间按 3 秒出块计算。HF16 之后(1 秒出块)相同区块数只覆盖三分之一的时间;要保持原时长,请将数值乘以三。

### 方法 4:结合 at-block 和周期性

两个设置可同时激活:
Expand Down Expand Up @@ -197,7 +199,7 @@ DLT 模式下主 `block_log` 保持为空。`dlt_block_log`(文件 `dlt_block_
- **API 访问** — `get_block` 对滚动窗口内的区块有效。

```ini
dlt-block-log-max-blocks = 100000 # 保留最近约 3.5 天的区块
dlt-block-log-max-blocks = 100000 # HF16 前约 3.5 天,之后约 28 小时;300000 可保持 3.5 天
```

当日志超过此大小时,旧区块从前端修剪。实现独立于内存映射文件跟踪逻辑文件大小,以防止数千次调整大小循环后出现过期大小错误。
Expand Down Expand Up @@ -347,7 +349,7 @@ snapshot-every-n-blocks = 28800
snapshot-dir = /data/viz-snapshots
snapshot-max-age-days = 90

# DLT 滚动 block log:保留最近约 3.5 天
# DLT 滚动 block log:HF16 前约 3.5 天,之后约 28 小时(300000 可保持 3.5 天)
dlt-block-log-max-blocks = 100000

shared-file-size = 4G
Expand Down
2 changes: 2 additions & 0 deletions docs/consensus/hf16.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,5 @@ Vote/equivocation history retains its one-hour wall-time window: `3600 / block_i
Stored and voted properties remain **native block counts**, per the selected HF16 migration policy. The one-time migration scales existing votes and the median by three; it cannot identify a later vote copied from an old script. Validators must update hard-coded `inflation_recalc_period`, `distribution_epoch_length`, `pm_batch_epoch_blocks` and `pm_reveal_window_blocks` by three to preserve time. Alternatively, use live `get_config.CHAIN_DEFAULT_CHAIN_PROPERTIES` when intentionally voting defaults, or preserve the current on-chain properties when editing unrelated fields. Do not re-vote stale struct defaults.

`get_config` reports consistent live blocks/hour/day/year, undo capacity, missed-block thresholds and default properties. The reserve-ratio adjustment retains its 60-second cadence. `CHAIN_EMERGENCY_EXIT_NORMAL_BLOCKS` is an unused legacy constant; actual emergency exit depends on the active-validator schedule quorum, not a 21-block timer.

P2P sync thresholds (fall-behind, fork resolution, wedge watchdog) are scaled by the live slot length, so they keep their wall-clock meaning. Operator options denominated in blocks are not rescaled: `dlt-block-log-max-blocks`, `snapshot-every-n-blocks`, `history-count-blocks`, `block-archive-range` cover one third of the previous time after HF16. The default rolling block log of 100000 blocks serves peer sync for about 28 hours instead of 3.5 days; set 300000 to keep the old window.
6 changes: 4 additions & 2 deletions docs/node/snapshot.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ Block processing is paused only during Phase 1; API and P2P reads are unaffected
| Daily | 28,800 | ~24 hours |
| Weekly | 100,000 | ~3.5 days |

Times assume 3-second blocks. After HF16 (1-second blocks) the same block count covers one third of the time; multiply by three to keep the duration.

### Method 4: Combining at-block and periodic

Both settings can be active simultaneously:
Expand Down Expand Up @@ -197,7 +199,7 @@ In DLT mode the main `block_log` stays empty. The `dlt_block_log` (files `dlt_bl
- **API access** — `get_block` works for blocks in the rolling window.

```ini
dlt-block-log-max-blocks = 100000 # keep last ~3.5 days of blocks
dlt-block-log-max-blocks = 100000 # ~3.5 days before HF16, ~28 h after; use 300000 to keep 3.5 days
```

When the log exceeds this size, old blocks are pruned from the front. The implementation tracks logical file sizes independently of the memory-mapped file to prevent stale-size bugs after thousands of resize cycles.
Expand Down Expand Up @@ -347,7 +349,7 @@ snapshot-every-n-blocks = 28800
snapshot-dir = /data/viz-snapshots
snapshot-max-age-days = 90

# DLT rolling block log: keep last ~3.5 days
# DLT rolling block log: ~3.5 days before HF16, ~28 h after (300000 keeps 3.5 days)
dlt-block-log-max-blocks = 100000

shared-file-size = 4G
Expand Down
21 changes: 13 additions & 8 deletions libraries/network/dlt_p2p_node.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@ dlt_p2p_node::~dlt_p2p_node() {
// ── Configuration ────────────────────────────────────────────────────

void dlt_p2p_node::set_delegate(dlt_p2p_delegate* del) { _delegate = del; }
uint32_t dlt_p2p_node::slot_scale() const {
const uint32_t iv = _delegate ? _delegate->block_interval() : CHAIN_BLOCK_INTERVAL;
return (iv > 0 && iv < CHAIN_BLOCK_INTERVAL) ? CHAIN_BLOCK_INTERVAL / iv : 1;
}
void dlt_p2p_node::set_listen_endpoint(const fc::ip::endpoint& ep, bool wait) {
_listen_endpoint = ep;
_wait_if_busy = wait;
Expand Down Expand Up @@ -1305,7 +1309,7 @@ void dlt_p2p_node::on_dlt_hello_reply(peer_id peer, const dlt_hello_reply_messag
// fallen behind (missed broadcast blocks) — transition to SYNC
// and request the missing range.
uint32_t our_head = _delegate->get_head_block_num();
if (state.peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD) {
if (state.peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD * slot_scale()) {
ilog(DLT_LOG_ORANGE "Falling behind in FORWARD mode: peer ${ep} at #${pn}, our head #${hn} — transitioning to SYNC" DLT_LOG_RESET,
("ep", state.endpoint)("pn", state.peer_head_num)("hn", our_head));
transition_to_sync();
Expand Down Expand Up @@ -1368,7 +1372,7 @@ void dlt_p2p_node::request_blocks_from_peer(peer_id peer) {
// on the majority fork, giving the chain a chance to switch forks.
uint32_t start = our_head;
uint32_t gap = (peer_latest > our_head) ? (peer_latest - our_head) : 0;
if (gap > FORWARD_FALLBEHIND_THRESHOLD) {
if (gap > FORWARD_FALLBEHIND_THRESHOLD * slot_scale()) {
uint32_t our_lib = _delegate->get_lib_block_num();
if (our_lib > 0 && our_lib < our_head) {
ilog(DLT_LOG_ORANGE "Large gap (${g} blocks behind peer ${ep}), starting sync from LIB #${lib} instead of head #${head}" DLT_LOG_RESET,
Expand Down Expand Up @@ -3088,7 +3092,7 @@ void dlt_p2p_node::check_sync_catchup() {
// the stagnation timer each time and delaying fork-switch recovery by
// many minutes (p68 scenario). Stay in SYNC so the stagnation check
// can fire at the right time and request the full alternative chain.
if (_highest_seen_block_num > our_head + FORWARD_FALLBEHIND_THRESHOLD) {
if (_highest_seen_block_num > our_head + FORWARD_FALLBEHIND_THRESHOLD * slot_scale()) {
dlog("Staying in SYNC: highest seen block #${h} is ${d} blocks ahead of our head #${o} — unlinked fork_db blocks pending",
("h", _highest_seen_block_num)("d", _highest_seen_block_num - our_head)("o", our_head));
return;
Expand Down Expand Up @@ -3157,7 +3161,7 @@ void dlt_p2p_node::check_forward_behind() {
const auto& state = _peer_item.second;
if (state.lifecycle_state != DLT_PEER_LIFECYCLE_ACTIVE) continue;
if (state.peer_head_num == 0) continue;
if (state.peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD) {
if (state.peer_head_num > our_head + FORWARD_FALLBEHIND_THRESHOLD * slot_scale()) {
ilog(DLT_LOG_ORANGE "Falling behind in FORWARD mode: our head #${h}, peer ${ep} at #${p} — transitioning to SYNC" DLT_LOG_RESET,
("h", our_head)("ep", state.endpoint)("p", state.peer_head_num));
transition_to_sync();
Expand Down Expand Up @@ -3474,7 +3478,7 @@ void dlt_p2p_node::track_fork_state(const signed_block& block) {
}

if (_fork_detected &&
block.block_num() - _fork_detection_block_num >= FORK_RESOLUTION_BLOCK_THRESHOLD) {
block.block_num() - _fork_detection_block_num >= FORK_RESOLUTION_BLOCK_THRESHOLD * slot_scale()) {
resolve_fork();
// _fork_detected is cleared inside resolve_fork() only when resolution completes
}
Expand Down Expand Up @@ -4159,11 +4163,12 @@ void dlt_p2p_node::check_wedge_watchdog() {
// "Stuck behind" gate: far enough behind the CORROBORATED tip to be
// suspicious. An under-corroborated tip is 0, collapsing `behind` to 0 and
// tripping this branch — so an eclipsed / low-peer node never arms.
if (behind <= WEDGE_BEHIND_THRESHOLD) {
const uint32_t wedge_behind_threshold = WEDGE_BEHIND_THRESHOLD * slot_scale();
if (behind <= wedge_behind_threshold) {
if (_wedge_since != fc::time_point()) {
ilog(DLT_LOG_GREEN "Wedge watchdog: caught up / under-corroborated (behind=${b} <= ${thr}, "
"corroborating_peers=${p}), clearing wedge timer (our_head=#${h})" DLT_LOG_RESET,
("b", behind)("thr", WEDGE_BEHIND_THRESHOLD)("p", established_peers)("h", our_head));
("b", behind)("thr", wedge_behind_threshold)("p", established_peers)("h", our_head));
}
_wedge_since = fc::time_point();
return;
Expand Down Expand Up @@ -4216,7 +4221,7 @@ void dlt_p2p_node::check_wedge_watchdog() {
("rc", rejects_climbed ? "climbing" : "flat")("r", _gap_rejected_total));
}

if (!is_wedged(behind, head_advanced, rejects_climbed, elapsed_sec)) return;
if (!is_wedged(behind, head_advanced, rejects_climbed, elapsed_sec, wedge_behind_threshold)) return;

// ── Confirmed wedge ──────────────────────────────────────────
elog(DLT_LOG_RED "Wedge watchdog: CONFIRMED network wedge — head frozen at #${h} for ${s}s while "
Expand Down
18 changes: 13 additions & 5 deletions libraries/network/include/graphene/network/dlt_p2p_node.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ class dlt_p2p_delegate {
virtual bool is_emergency_consensus_active() const = 0;
virtual bool has_emergency_private_key() const = 0;
virtual bool is_dlt_mode() const = 0;
/// Live slot length in seconds (3 before HF16, 1 after). Block-count
/// thresholds in the node are multiplied by CHAIN_BLOCK_INTERVAL / this.
virtual uint32_t block_interval() const { return CHAIN_BLOCK_INTERVAL; }

// ── Block queries ────────────────────────────────────────────
virtual fc::optional<signed_block> read_block_by_num(uint32_t block_num) const = 0;
Expand Down Expand Up @@ -386,7 +389,7 @@ class dlt_p2p_node {
// Thresholds feeding the pure wedge predicate below. Public (with the
// predicate) so the truth-table unit test can assert the boundaries
// symbolically rather than hard-coding magic numbers.
static constexpr uint32_t WEDGE_BEHIND_THRESHOLD = 200; ///< Blocks behind network tip to be considered "behind"
static constexpr uint32_t WEDGE_BEHIND_THRESHOLD = 200; ///< Blocks (3 s slots) behind network tip to be considered "behind"; × slot_scale() at runtime
static constexpr uint32_t WEDGE_CONFIRM_SEC = 900; ///< Wedge condition must hold this long (15 min) before acting

// Pure predicate for the wedge state machine — no I/O, table-testable.
Expand All @@ -404,8 +407,9 @@ class dlt_p2p_node {
// (recency matters: one early rejection then silence must NOT confirm)
// - genuine divergence trips all three, and only after WEDGE_CONFIRM_SEC.
static bool is_wedged(uint32_t behind, bool head_advanced,
bool rejects_climbed, uint32_t elapsed_sec) {
return behind > WEDGE_BEHIND_THRESHOLD
bool rejects_climbed, uint32_t elapsed_sec,
uint32_t behind_threshold = WEDGE_BEHIND_THRESHOLD) {
return behind > behind_threshold
&& !head_advanced
&& rejects_climbed
&& elapsed_sec >= WEDGE_CONFIRM_SEC;
Expand Down Expand Up @@ -441,6 +445,10 @@ class dlt_p2p_node {

private:
dlt_p2p_delegate* _delegate = nullptr;
/// CHAIN_BLOCK_INTERVAL / live slot (1 pre-HF16, 3 after). Block-count
/// thresholds below are written for 3 s slots; multiply by this to keep
/// their wall-clock meaning.
uint32_t slot_scale() const;
std::string _user_agent;
fc::ip::endpoint _listen_endpoint;
bool _wait_if_busy = true;
Expand All @@ -465,7 +473,7 @@ class dlt_p2p_node {
static constexpr uint32_t SYNC_STAGNATION_MAX_RETRIES = 3;

// ── FORWARD fallbehind ──────────────────────────────────────
static constexpr uint32_t FORWARD_FALLBEHIND_THRESHOLD = 2; ///< Blocks behind peer before FORWARD→SYNC
static constexpr uint32_t FORWARD_FALLBEHIND_THRESHOLD = 2; ///< Blocks (3 s slots) behind peer before FORWARD→SYNC; × slot_scale() at runtime
static constexpr uint32_t FORWARD_BEHIND_GRACE_SEC = 15; ///< Grace period after SYNC→FORWARD before checking peers
fc::time_point _forward_entered_time; ///< When we last entered FORWARD mode

Expand Down Expand Up @@ -545,7 +553,7 @@ class dlt_p2p_node {
static constexpr uint32_t BAN_DURATION_SEC = 3600;

// ── Fork resolution ──────────────────────────────────────────
static constexpr uint32_t FORK_RESOLUTION_BLOCK_THRESHOLD = 42;
static constexpr uint32_t FORK_RESOLUTION_BLOCK_THRESHOLD = 42; ///< 3 s slots; × slot_scale() at runtime

// ── Gap fill ──────────────────────────────────────────────
static constexpr uint32_t GAP_FILL_MAX_BLOCKS = 100; ///< Max blocks per gap fill request
Expand Down
19 changes: 19 additions & 0 deletions plugins/p2p/p2p_plugin.cpp
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
#include <atomic>
#include <graphene/plugins/p2p/p2p_plugin.hpp>

#include <graphene/network/dlt_p2p_node.hpp>
Expand Down Expand Up @@ -95,6 +96,23 @@ class dlt_delegate : public dlt_p2p_delegate {
return chain.db()._dlt_mode;
}

// The DB interval reads hardfork shared state, not an atomic scalar.
// Refresh under the standard guarded read; a transient lock/resize/recovery
// failure must not unwind P2P. Keep the last successfully observed interval
// and retry on the next call (including after snapshot restore or a reorg).
uint32_t block_interval() const override {
try {
const auto interval = chain.db().with_weak_read_lock([&]() {
return chain.db().block_interval();
});
if (interval > 0)
_cached_block_interval.store(interval, std::memory_order_relaxed);
} catch (...) {
// Thresholds remain usable while the chain is temporarily unavailable.
}
return _cached_block_interval.load(std::memory_order_relaxed);
}

// ── Block queries ────────────────────────────────────────────
fc::optional<signed_block> read_block_by_num(uint32_t block_num) const override {
auto& db = chain.db();
Expand Down Expand Up @@ -481,6 +499,7 @@ class dlt_delegate : public dlt_p2p_delegate {
chain.clear_syncing();
}

mutable std::atomic<uint32_t> _cached_block_interval{CHAIN_BLOCK_INTERVAL};
chain::plugin& chain;
fc::time_point _startup_time; ///< P22: startup timestamp for dead-fork grace period
};
Expand Down
Loading
Loading