CCF
Loading...
Searching...
No Matches
network_identity_subsystem.h
Go to the documentation of this file.
1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the Apache 2.0 License.
3#pragma once
4
8#include "node/identity.h"
11
12#include <atomic>
13
14namespace ccf
15{
16 static bool is_self_endorsement(const ccf::CoseEndorsement& endorsement)
17 {
18 return !endorsement.previous_version.has_value();
19 }
20
21 static void validate_fetched_endorsement(
22 const ccf::CoseEndorsement& endorsement)
23 {
24 if (!is_self_endorsement(endorsement))
25 {
26 const auto [from, to] =
28
29 const auto from_txid = ccf::TxID::from_str(from);
30 if (!from_txid.has_value())
31 {
32 throw std::logic_error(fmt::format(
33 "Cannot parse COSE endorsement header: {}",
34 ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN));
35 }
36
37 const auto to_txid = ccf::TxID::from_str(to);
38 if (!to_txid.has_value())
39 {
40 throw std::logic_error(fmt::format(
41 "Cannot parse COSE endorsement header: {}",
42 ccf::crypto::COSE_PHEADER_KEY_RANGE_END));
43 }
44
45 if (!endorsement.endorsement_epoch_end.has_value())
46 {
47 throw std::logic_error(
48 "COSE endorsement does not contain epoch end in the table entry");
49 }
50 if (
51 endorsement.endorsement_epoch_begin != *from_txid ||
52 *endorsement.endorsement_epoch_end != *to_txid)
53 {
54 throw std::logic_error(fmt ::format(
55 "COSE endorsement fetched but range is invalid, epoch begin {}, "
56 "epoch end {}, header epoch begin: {}, header epoch end: {}",
57 endorsement.endorsement_epoch_begin.to_str(),
58 endorsement.endorsement_epoch_end->to_str(),
59 from,
60 to));
61 }
62 }
63 }
64
65 static void validate_chain_integrity(
66 const ccf::CoseEndorsement& newer, const ccf::CoseEndorsement& older)
67 {
68 if (!older.endorsement_epoch_end.has_value())
69 {
70 throw std::logic_error(fmt::format(
71 "COSE endorsement chain integrity is violated, previous endorsement "
72 "from {} does not have an epoch end",
74 }
75
76 if (
77 newer.endorsement_epoch_begin.view - aft::starting_view_change !=
78 older.endorsement_epoch_end->view ||
80 older.endorsement_epoch_end->seqno)
81 {
82 throw std::logic_error(fmt::format(
83 "COSE endorsement chain integrity is violated, previous endorsement "
84 "epoch end {} is not chained with newer endorsement epoch begin {}",
85 older.endorsement_epoch_end->to_str(),
87 }
88 }
89
91 {
92 protected:
94 const std::unique_ptr<NetworkIdentity>& network_identity;
95 std::shared_ptr<historical::StateCacheImpl> historical_cache;
96 std::map<SeqNo, CoseEndorsement> endorsements;
97 std::optional<TxID> current_service_from;
99 std::atomic<FetchStatus> fetch_status{FetchStatus::Retry};
100 bool has_predecessors{false};
101
102 public:
104 AbstractNodeState& node_state_,
105 const std::unique_ptr<NetworkIdentity>& network_identity_,
106 std::shared_ptr<ccf::historical::StateCacheImpl> historical_cache_) :
107 node_state(node_state_),
108 network_identity(network_identity_),
109 historical_cache(std::move(historical_cache_))
110 {
111 fetch_first();
112 }
113
114 [[nodiscard]] FetchStatus endorsements_fetching_status() const override
115 {
116 return fetch_status.load();
117 }
118
119 const std::unique_ptr<NetworkIdentity>& get() override
120 {
121 return network_identity;
122 }
123
124 [[nodiscard]] std::optional<CoseEndorsementsChain>
126 {
127 // All other cases must be handled after recovery has been completed and
128 // identities have been successfully fetched.
129 if (fetch_status.load() != FetchStatus::Done)
130 {
131 return std::nullopt;
132 }
133
134 if (!current_service_from.has_value())
135 {
137 "Unset current_service_from when fetching endorsements chain");
138 return std::nullopt;
139 }
140
142 {
143 return CoseEndorsementsChain{};
144 }
145
146 auto it = endorsements.upper_bound(seqno);
147 if (it == endorsements.begin())
148 {
150 "No endorsements found for seqno {}, earliest endorsed is {}",
151 seqno,
153 return {};
154 }
155
157 for (--it; it != endorsements.end(); ++it)
158 {
159 result.push_back(it->second.endorsement);
160 }
161 std::reverse(result.begin(), result.end());
162 return result;
163 }
164
165 private:
166 void retry_first_fetch()
167 {
168 using namespace std::chrono_literals;
169 static constexpr auto retry_after = 1s;
171 ccf::tasks::make_basic_task([this]() { this->fetch_first(); }),
172 retry_after);
173 }
174
175 void fail_fetching(const std::string& err = "")
176 {
177 if (!err.empty())
178 {
179 LOG_FAIL_FMT("Failed fetching network identity: {}", err);
180 }
182 }
183
184 void complete_fetching()
185 {
186 if (!current_service_from.has_value())
187 {
188 fail_fetching("Unset current_service_from when completing fetching");
189 return;
190 }
191
192 if (!endorsements.empty())
193 {
194 try
195 {
196 auto next = endorsements.begin();
197 auto prev = next++;
198 while (next != endorsements.end())
199 {
200 validate_chain_integrity(next->second, prev->second);
201 ++prev;
202 ++next;
203 }
204
205 const auto& last = prev->second;
206 if (!last.endorsement_epoch_end.has_value())
207 {
208 fail_fetching(fmt::format(
209 "The last fetched endorsement at {} has no epoch end",
210 last.endorsement_epoch_begin.seqno));
211 return;
212 }
213
214 if (
215 current_service_from->view - aft::starting_view_change !=
216 last.endorsement_epoch_end->view ||
217 current_service_from->seqno - 1 !=
218 last.endorsement_epoch_end->seqno)
219 {
220 fail_fetching(fmt::format(
221 "COSE endorsement chain integrity is violated, the current "
222 "service start at {} is not chained with previous endorsement "
223 "ending at {}",
224 current_service_from->to_str(),
225 last.endorsement_epoch_end->to_str()));
226 return;
227 }
228 }
229 catch (const std::exception& e)
230 {
231 fail_fetching(e.what());
232 return;
233 }
234 }
235
237 }
238
239 void fetch_first()
240 {
242 {
244 "Retry fetching network identity as node is not part of the network "
245 "yet");
246 retry_first_fetch();
247 return;
248 }
249
250 auto store = node_state.get_store();
251 auto tx = store->create_read_only_tx();
252
253 if (!current_service_from.has_value())
254 {
255 auto* service_info_handle =
256 tx.template ro<ccf::Service>(ccf::Tables::SERVICE);
257 auto service_info = service_info_handle->get();
258 if (
259 !service_info ||
260 !service_info->current_service_create_txid.has_value())
261 {
263 "Retrying fetching network identity as current service create txid "
264 "is not yet available");
265 retry_first_fetch();
266 return;
267 }
268
269 current_service_from = service_info->current_service_create_txid;
270 }
271
272 auto* previous_identity_endorsement =
274 ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT);
275
276 auto endorsement = previous_identity_endorsement->get();
277 if (!endorsement.has_value())
278 {
280 "Retrying fetching network identity as there is no previous service "
281 "identity endorsement yet");
282 retry_first_fetch();
283 return;
284 }
285
286 if (is_self_endorsement(endorsement.value()))
287 {
288 if (
289 current_service_from->seqno !=
290 endorsement->endorsement_epoch_begin.seqno)
291 {
292 fail_fetching(fmt::format(
293 "The first fetched endorsement is a self-endorsement with seqno {} "
294 "which is different from current_service_create_txid {}",
295 endorsement->endorsement_epoch_begin.seqno,
296 current_service_from->seqno));
297 return;
298 }
299
301 "The very first service endorsement is self-signed at {}, no "
302 "endorsement chain will be preloaded",
303 current_service_from->seqno);
304
305 has_predecessors = false;
306 complete_fetching();
307 return;
308 }
309
310 has_predecessors = true;
312 process_endorsement(endorsement.value());
313 }
314
315 void process_endorsement(const ccf::CoseEndorsement& endorsement)
316 {
317 const auto from = endorsement.endorsement_epoch_begin.seqno;
318 if (is_self_endorsement(endorsement))
319 {
320 if (endorsements.find(from) == endorsements.end())
321 {
322 fail_fetching(fmt::format(
323 "Fetched self-endorsement with seqno {} which has not been seen",
324 from));
325 return;
326 }
327 LOG_INFO_FMT("Got self-endorsement at {}, stopping fetching", from);
328 complete_fetching();
329 return;
330 }
331
332 if (from >= earliest_endorsed_seq)
333 {
334 fail_fetching(fmt::format(
335 "Fetched service endorsement with seqno {} which is greater than "
336 "the earliest known in the chain {}",
337 from,
339 return;
340 }
341
342 if (!endorsement.endorsement_epoch_end.has_value())
343 {
344 fail_fetching(
345 fmt::format("Fetched endorsement at {} has no epoch end", from));
346 return;
347 }
348
350 if (endorsements.find(from) != endorsements.end())
351 {
352 fail_fetching(fmt::format(
353 "Fetched service endorsement with seqno {} which already exists",
354 from));
355 return;
356 }
357
359 "Fetched service endorsement from {} to {}",
360 from,
361 endorsement.endorsement_epoch_end->seqno);
362 endorsements.insert({from, endorsement});
363
364 if (endorsement.previous_version.has_value())
365 {
366 fetch_next_at(endorsement.previous_version.value());
367 return;
368 }
369
370 complete_fetching();
371 }
372
373 void fetch_next_at(ccf::SeqNo seq)
374 {
375 auto state = historical_cache->get_state_at(
378 seq);
379 if (!state)
380 {
381 retry_fetch_next(seq);
382 return;
383 }
384
385 if (!state->store)
386 {
387 fail_fetching(fmt::format(
388 "Fetched historical state with seqno {} with missing store", seq));
389 return;
390 }
391 auto htx = state->store->create_read_only_tx();
392 const auto endorsement =
393 htx
394 .template ro<ccf::PreviousServiceIdentityEndorsement>(
395 ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT)
396 ->get();
397
398 if (!endorsement.has_value())
399 {
400 fail_fetching(
401 fmt::format("Fetched COSE endorsement for {} is invalid", seq));
402 return;
403 }
404
405 try
406 {
407 validate_fetched_endorsement(endorsement.value());
408 }
409 catch (const std::exception& e)
410 {
411 fail_fetching(e.what());
412 return;
413 }
414
415 process_endorsement(endorsement.value());
416 }
417
418 void retry_fetch_next(ccf::SeqNo seq)
419 {
420 using namespace std::chrono_literals;
421 static constexpr auto retry_after = 100ms;
424 [this, seq]() { this->fetch_next_at(seq); }),
425 retry_after);
426 }
427 };
428}
Definition node_interface.h:22
virtual bool is_part_of_network() const =0
virtual std::shared_ptr< ccf::kv::Store > get_store()=0
Definition network_identity_interface.h:26
Definition network_identity_subsystem.h:91
std::map< SeqNo, CoseEndorsement > endorsements
Definition network_identity_subsystem.h:96
FetchStatus endorsements_fetching_status() const override
Definition network_identity_subsystem.h:114
std::optional< CoseEndorsementsChain > get_cose_endorsements_chain(ccf::SeqNo seqno) const override
Definition network_identity_subsystem.h:125
const std::unique_ptr< NetworkIdentity > & network_identity
Definition network_identity_subsystem.h:94
std::atomic< FetchStatus > fetch_status
Definition network_identity_subsystem.h:99
SeqNo earliest_endorsed_seq
Definition network_identity_subsystem.h:98
AbstractNodeState & node_state
Definition network_identity_subsystem.h:93
std::shared_ptr< historical::StateCacheImpl > historical_cache
Definition network_identity_subsystem.h:95
const std::unique_ptr< NetworkIdentity > & get() override
Definition network_identity_subsystem.h:119
std::optional< TxID > current_service_from
Definition network_identity_subsystem.h:97
bool has_predecessors
Definition network_identity_subsystem.h:100
NetworkIdentitySubsystem(AbstractNodeState &node_state_, const std::unique_ptr< NetworkIdentity > &network_identity_, std::shared_ptr< ccf::historical::StateCacheImpl > historical_cache_)
Definition network_identity_subsystem.h:103
Definition value.h:32
#define LOG_INFO_FMT
Definition internal_logger.h:15
#define LOG_FAIL_FMT
Definition internal_logger.h:16
COSEEndorsementValidity extract_cose_endorsement_validity(std::span< const uint8_t > cose_msg)
Definition cose_verifier.cpp:238
std::pair< RequestNamespace, RequestHandle > CompoundHandle
Definition historical_queries.h:36
Task make_basic_task(Ts &&... ts)
Definition basic_task.h:33
void add_delayed_task(Task task, std::chrono::milliseconds delay)
Definition task_system.cpp:70
Definition app_interface.h:14
FetchStatus
Definition network_identity_interface.h:19
std::vector< RawCoseEndorsement > CoseEndorsementsChain
Definition network_identity_interface.h:16
seqno
Definition signatures.h:54
uint64_t SeqNo
Definition tx_id.h:36
STL namespace.
Definition previous_service_identity.h:18
std::optional< ccf::TxID > endorsement_epoch_end
Definition previous_service_identity.h:31
std::optional< ccf::kv::Version > previous_version
Definition previous_service_identity.h:35
std::vector< uint8_t > endorsement
COSE-sign of the a previous service identity's public key.
Definition previous_service_identity.h:20
ccf::TxID endorsement_epoch_begin
The transaction ID when the endorsed service was created.
Definition previous_service_identity.h:26
SeqNo seqno
Definition tx_id.h:46
View view
Definition tx_id.h:45
std::string to_str() const
Definition tx_id.h:48
static std::optional< TxID > from_str(const std::string_view &sv)
Definition tx_id.h:53