|
1 | 1 | #pragma once |
2 | 2 |
|
| 3 | +#include <array> |
3 | 4 | #include <cstddef> |
4 | 5 | #include <cstdint> |
| 6 | +#include <cstring> |
5 | 7 |
|
6 | 8 | #include "core/raps_definitions.hpp" |
7 | 9 | #include "platform/platform_hal.hpp" |
@@ -30,28 +32,217 @@ class ITLManager { |
30 | 32 | void init(); |
31 | 33 |
|
32 | 34 | // Non-blocking commit (returns optimistic ID). |
33 | | - // Returns null_hash() if queue is full. |
| 35 | + // Returns null_hash() if queue is full or persistence fails. |
34 | 36 | Hash256 commit(const ITLEntry& entry); |
35 | 37 |
|
36 | 38 | // Background processing (low-priority task) |
37 | 39 | void flush_pending(); |
38 | 40 |
|
39 | | - // Log WNN rollback event |
40 | | - void log_wnn_rollback_event(double curvature, double prefactor); |
| 41 | + // Log WNN rollback event. Returns true only when all ledger commits succeed. |
| 42 | + bool log_wnn_rollback_event(double curvature, double prefactor); |
41 | 43 | }; |
42 | 44 |
|
43 | | -inline void ITLManager::log_wnn_rollback_event(double curvature, double prefactor) { |
| 45 | +namespace itl_manager_detail { |
| 46 | + |
| 47 | +inline size_t effective_payload_len(ITLEntry::Type type) { |
| 48 | + switch (type) { |
| 49 | + case ITLEntry::Type::STATE_SNAPSHOT: |
| 50 | + return sizeof(ITLEntry::StateSnapshotPayload); |
| 51 | + case ITLEntry::Type::PREDICTION_COMMIT: |
| 52 | + return sizeof(ITLEntry::PredictionCommitPayload); |
| 53 | + case ITLEntry::Type::ESE_ALERT: |
| 54 | + return sizeof(ITLEntry::ESEAlertPayload); |
| 55 | + case ITLEntry::Type::POLICY_PREFLIGHT: |
| 56 | + return sizeof(ITLEntry::PolicyPreflightPayload); |
| 57 | + case ITLEntry::Type::COMMAND_PENDING: |
| 58 | + case ITLEntry::Type::EXECUTION_FAILURE: |
| 59 | + case ITLEntry::Type::COMMAND_COMMIT: |
| 60 | + case ITLEntry::Type::ROLLBACK_COMMIT: |
| 61 | + return sizeof(ITLEntry::CommandExecutionPayload); |
| 62 | + case ITLEntry::Type::ROLLBACK_METADATA: |
| 63 | + return sizeof(ITLEntry::RollbackMetadataPayload); |
| 64 | + case ITLEntry::Type::FALLBACK_TRIGGERED: |
| 65 | + return sizeof(ITLEntry::FallbackTriggeredPayload); |
| 66 | + case ITLEntry::Type::MERKLE_ANCHOR: |
| 67 | + return sizeof(ITLEntry::MerkleAnchorPayload); |
| 68 | + case ITLEntry::Type::GOVERNANCE_BUDGET_VIOLATION: |
| 69 | + return sizeof(ITLEntry::GovernanceBudgetViolationPayload); |
| 70 | + case ITLEntry::Type::NOMINAL_TRACE: |
| 71 | + return sizeof(ITLEntry::NominalTracePayload); |
| 72 | + case ITLEntry::Type::SUPERVISOR_EXCEPTION: |
| 73 | + return sizeof(ITLEntry::SupervisorExceptionPayload); |
| 74 | + case ITLEntry::Type::AILEE_SAFETY_STATUS: |
| 75 | + return sizeof(ITLEntry::AileeSafetyStatusPayload); |
| 76 | + case ITLEntry::Type::AILEE_GRACE_RESULT: |
| 77 | + return sizeof(ITLEntry::AileeGraceResultPayload); |
| 78 | + case ITLEntry::Type::AILEE_CONSENSUS_RESULT: |
| 79 | + return sizeof(ITLEntry::AileeConsensusResultPayload); |
| 80 | + case ITLEntry::Type::WNN_ALERT: |
| 81 | + return sizeof(ITLEntry::WnnAlertPayload); |
| 82 | + default: |
| 83 | + return 0U; |
| 84 | + } |
| 85 | +} |
| 86 | + |
| 87 | +inline Hash256 compute_entry_id(const ITLEntry& entry, size_t payload_len) { |
| 88 | + std::array<uint8_t, |
| 89 | + sizeof(entry.type) + sizeof(entry.timestamp_ms) + sizeof(ITLEntry::PayloadData)> buf{}; |
| 90 | + |
| 91 | + size_t offset = 0U; |
| 92 | + std::memcpy(buf.data() + offset, &entry.type, sizeof(entry.type)); |
| 93 | + offset += sizeof(entry.type); |
| 94 | + std::memcpy(buf.data() + offset, &entry.timestamp_ms, sizeof(entry.timestamp_ms)); |
| 95 | + offset += sizeof(entry.timestamp_ms); |
| 96 | + if (payload_len > 0U) { |
| 97 | + std::memcpy(buf.data() + offset, &entry.payload, payload_len); |
| 98 | + offset += payload_len; |
| 99 | + } |
| 100 | + return PlatformHAL::sha256(buf.data(), offset); |
| 101 | +} |
| 102 | + |
| 103 | +inline Hash256 compute_merkle_root(const Hash256* ids, size_t count) { |
| 104 | + if (ids == nullptr || count == 0U) { |
| 105 | + return Hash256::null_hash(); |
| 106 | + } |
| 107 | + if (count == 1U) { |
| 108 | + return ids[0]; |
| 109 | + } |
| 110 | + |
| 111 | + std::array<Hash256, RAPSConfig::MERKLE_BATCH_SIZE> current{}; |
| 112 | + std::array<Hash256, RAPSConfig::MERKLE_BATCH_SIZE> next{}; |
| 113 | + for (size_t i = 0U; i < count; ++i) { |
| 114 | + current[i] = ids[i]; |
| 115 | + } |
| 116 | + |
| 117 | + size_t current_count = count; |
| 118 | + while (current_count > 1U) { |
| 119 | + size_t next_count = 0U; |
| 120 | + for (size_t i = 0U; i < current_count; i += 2U) { |
| 121 | + const Hash256& left = current[i]; |
| 122 | + const Hash256& right = (i + 1U < current_count) ? current[i + 1U] : left; |
| 123 | + std::array<uint8_t, 64> combined{}; |
| 124 | + std::memcpy(combined.data(), left.data, 32U); |
| 125 | + std::memcpy(combined.data() + 32U, right.data, 32U); |
| 126 | + next[next_count++] = PlatformHAL::sha256(combined.data(), combined.size()); |
| 127 | + } |
| 128 | + for (size_t i = 0U; i < next_count; ++i) { |
| 129 | + current[i] = next[i]; |
| 130 | + } |
| 131 | + current_count = next_count; |
| 132 | + } |
| 133 | + |
| 134 | + return current[0]; |
| 135 | +} |
| 136 | + |
| 137 | +} // namespace itl_manager_detail |
| 138 | + |
| 139 | +inline void ITLManager::init() { |
| 140 | + queue_head_ = 0U; |
| 141 | + queue_tail_ = 0U; |
| 142 | + queue_count_ = 0U; |
| 143 | + merkle_count_ = 0U; |
| 144 | + flash_write_cursor_ = 0U; |
| 145 | + for (auto& entry : queue_) { |
| 146 | + entry = ITLEntry{}; |
| 147 | + } |
| 148 | + for (auto& hash : merkle_buffer_) { |
| 149 | + hash = Hash256::null_hash(); |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +inline Hash256 ITLManager::commit(const ITLEntry& entry_template) { |
| 154 | + if (queue_count_ >= RAPSConfig::ITL_QUEUE_SIZE) { |
| 155 | + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "queue_full"); |
| 156 | + return Hash256::null_hash(); |
| 157 | + } |
| 158 | + |
| 159 | + ITLEntry entry = entry_template; |
| 160 | + if (entry.timestamp_ms == 0U) { |
| 161 | + entry.timestamp_ms = PlatformHAL::now_ms(); |
| 162 | + } |
| 163 | + |
| 164 | + const size_t payload_len = itl_manager_detail::effective_payload_len(entry.type); |
| 165 | + if (payload_len > sizeof(ITLEntry::PayloadData)) { |
| 166 | + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "payload_len"); |
| 167 | + return Hash256::null_hash(); |
| 168 | + } |
| 169 | + entry.payload_len = static_cast<uint16_t>(payload_len); |
| 170 | + entry.entry_id = itl_manager_detail::compute_entry_id(entry, payload_len); |
| 171 | + |
| 172 | + if (entry.entry_id.is_null()) { |
| 173 | + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "entry_hash"); |
| 174 | + return Hash256::null_hash(); |
| 175 | + } |
| 176 | + |
| 177 | + if (!PlatformHAL::flash_write(flash_write_cursor_, &entry, sizeof(entry))) { |
| 178 | + PlatformHAL::metric_emit("itl.commit_dropped", 1.0f, "reason", "flash_write"); |
| 179 | + return Hash256::null_hash(); |
| 180 | + } |
| 181 | + flash_write_cursor_ += static_cast<uint32_t>(sizeof(entry)); |
| 182 | + |
| 183 | + queue_[queue_tail_] = entry; |
| 184 | + queue_tail_ = (queue_tail_ + 1U) % RAPSConfig::ITL_QUEUE_SIZE; |
| 185 | + ++queue_count_; |
| 186 | + |
| 187 | + merkle_buffer_[merkle_count_++] = entry.entry_id; |
| 188 | + if (merkle_count_ >= RAPSConfig::MERKLE_BATCH_SIZE) { |
| 189 | + process_merkle_batch(); |
| 190 | + } |
| 191 | + |
| 192 | + return entry.entry_id; |
| 193 | +} |
| 194 | + |
| 195 | +inline void ITLManager::process_merkle_batch() { |
| 196 | + if (merkle_count_ == 0U) { |
| 197 | + return; |
| 198 | + } |
| 199 | + |
| 200 | + const Hash256 root = itl_manager_detail::compute_merkle_root( |
| 201 | + merkle_buffer_, |
| 202 | + merkle_count_ |
| 203 | + ); |
| 204 | + if (!root.is_null()) { |
| 205 | + ITLEntry anchor{}; |
| 206 | + anchor.type = ITLEntry::Type::MERKLE_ANCHOR; |
| 207 | + anchor.timestamp_ms = PlatformHAL::now_ms(); |
| 208 | + anchor.payload.merkle_anchor.merkle_root = root; |
| 209 | + const size_t payload_len = itl_manager_detail::effective_payload_len(anchor.type); |
| 210 | + anchor.payload_len = static_cast<uint16_t>(payload_len); |
| 211 | + anchor.entry_id = itl_manager_detail::compute_entry_id(anchor, payload_len); |
| 212 | + if (!anchor.entry_id.is_null()) { |
| 213 | + (void)PlatformHAL::flash_write(flash_write_cursor_, &anchor, sizeof(anchor)); |
| 214 | + flash_write_cursor_ += static_cast<uint32_t>(sizeof(anchor)); |
| 215 | + } |
| 216 | + } |
| 217 | + |
| 218 | + for (auto& hash : merkle_buffer_) { |
| 219 | + hash = Hash256::null_hash(); |
| 220 | + } |
| 221 | + merkle_count_ = 0U; |
| 222 | +} |
| 223 | + |
| 224 | +inline void ITLManager::flush_pending() { |
| 225 | + process_merkle_batch(); |
| 226 | + |
| 227 | + while (queue_count_ > 0U) { |
| 228 | + queue_[queue_head_] = ITLEntry{}; |
| 229 | + queue_head_ = (queue_head_ + 1U) % RAPSConfig::ITL_QUEUE_SIZE; |
| 230 | + --queue_count_; |
| 231 | + } |
| 232 | +} |
| 233 | + |
| 234 | +inline bool ITLManager::log_wnn_rollback_event(double curvature, double prefactor) { |
44 | 235 | ITLEntry wnn_entry{}; |
45 | 236 | wnn_entry.type = ITLEntry::Type::WNN_ALERT; |
46 | 237 | wnn_entry.timestamp_ms = PlatformHAL::now_ms(); |
47 | 238 | wnn_entry.payload.wnn_alert.curvature_proxy = curvature; |
48 | 239 | wnn_entry.payload.wnn_alert.oscillatory_prefactor = prefactor; |
49 | | - commit(wnn_entry); |
| 240 | + const Hash256 wnn_id = commit(wnn_entry); |
50 | 241 |
|
51 | 242 | ITLEntry rollback_entry{}; |
52 | 243 | rollback_entry.type = ITLEntry::Type::ROLLBACK_COMMIT; |
53 | 244 | rollback_entry.timestamp_ms = PlatformHAL::now_ms(); |
54 | | - // Payload for rollback commit (CommandExecutionPayload) |
55 | | - // we just commit the entry to mark the rollback execution triggered by WNN |
56 | | - commit(rollback_entry); |
| 245 | + const Hash256 rollback_id = commit(rollback_entry); |
| 246 | + |
| 247 | + return !wnn_id.is_null() && !rollback_id.is_null(); |
57 | 248 | } |
0 commit comments