Skip to content

Commit a892405

Browse files
authored
Merge branch 'main' into f/evercbor-warning-silence
2 parents af6a13a + 402347e commit a892405

File tree

8 files changed

+460
-240
lines changed

8 files changed

+460
-240
lines changed

doc/architecture/consensus/index.rst

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,14 @@ CFT parameters can be configured when starting up a network (see :doc:`here </op
1616
Extensions for Omission Faults
1717
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1818

19-
.. warning:: Support for these extensions is work-in-progress. See https://github.com/microsoft/CCF/issues/2577.
20-
2119
The CFT consensus implementation in CCF also supports some extensions for :term:`omission fault`.
2220
This may happen when the network is unreliable and may lead to one or more nodes being isolated from the rest of the network.
2321

2422
Supported extensions include:
2523

2624
- "CheckQuorum": the primary node automatically steps down, in the same view, if it does not hear back (via ``AppendEntriesResponse`` messages) from a majority of backups within a ``consensus.election_timeout`` period. This prevents an isolated primary node from still processing client write requests without being able to commit them.
27-
- "NoTimeoutRetirement": a primary node that completes its retirement sends a ProposeRequestVote message to the most up-to-date node in the new configuration, causing that node to run for election without waiting for time out.
28-
- A ProposeRequestVote message is also sent when a primary receives a termination signal. This reduces downtime when the orchestrator must suddenly retire the primary's host, but there is insufficient time to reconfigure the network first.
25+
- "NoTimeoutRetirement": a primary node that completes its retirement sends a ``ProposeRequestVote`` message to the most up-to-date node in the new configuration, causing that node to run for election without waiting for time out.
26+
- A ``ProposeRequestVote`` message is also sent when a primary receives a termination signal. This reduces downtime when the orchestrator must suddenly retire the primary's host, but there is insufficient time to reconfigure the network first.
2927
- "PreVote": followers must first request a pre-vote before starting a new election. This prevents followers from starting elections (and increasing the term) when they are isolated from the rest of the network.
3028

3129
Replica State Machine

include/ccf/historical_queries_utils.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ namespace ccf::historical
3939
bool populate_cose_service_endorsements(
4040
ccf::kv::ReadOnlyTx& tx,
4141
ccf::historical::StatePtr& state,
42-
AbstractStateCache& state_cache);
42+
std::shared_ptr<NetworkIdentitySubsystemInterface>
43+
network_identity_subsystem);
4344

4445
// Verifies CCF COSE receipt using the *current network* identity's
4546
// certificate.

include/ccf/network_identity_interface.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@ namespace ccf
1212
{
1313
struct NetworkIdentity;
1414

15+
using RawCoseEndorsement = std::vector<uint8_t>;
16+
using CoseEndorsementsChain = std::vector<RawCoseEndorsement>;
17+
18+
enum class FetchStatus : uint8_t
19+
{
20+
Retry,
21+
Done,
22+
Failed
23+
};
24+
1525
class NetworkIdentitySubsystemInterface : public ccf::AbstractNodeSubSystem
1626
{
1727
public:
@@ -23,5 +33,10 @@ namespace ccf
2333
}
2434

2535
virtual const std::unique_ptr<NetworkIdentity>& get() = 0;
36+
37+
[[nodiscard]] virtual FetchStatus endorsements_fetching_status() const = 0;
38+
39+
[[nodiscard]] virtual std::optional<CoseEndorsementsChain>
40+
get_cose_endorsements_chain(ccf::SeqNo seqno) const = 0;
2641
};
2742
}

src/enclave/enclave.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ namespace ccf
126126

127127
context->install_subsystem(
128128
std::make_shared<ccf::NetworkIdentitySubsystem>(
129-
*node, network.identity));
129+
*node, network.identity, historical_state_cache));
130130

131131
context->install_subsystem(
132132
std::make_shared<ccf::NodeConfigurationSubsystem>(*node));

src/node/historical_queries_adapter.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -573,7 +573,7 @@ namespace ccf::historical
573573
state_cache,
574574
network_identity_subsystem)) ||
575575
!populate_cose_service_endorsements(
576-
args.tx, historical_state, state_cache))
576+
args.tx, historical_state, network_identity_subsystem))
577577
{
578578
auto reason = fmt::format(
579579
"Historical transaction {} is not currently available.",

src/node/historical_queries_utils.cpp

Lines changed: 34 additions & 226 deletions
Original file line numberDiff line numberDiff line change
@@ -23,216 +23,11 @@ namespace
2323
bool retry{false};
2424
};
2525

26-
std::vector<ccf::CoseEndorsement> cose_endorsements_cache = {};
27-
28-
bool is_self_endorsement(const ccf::CoseEndorsement& endorsement)
29-
{
30-
return !endorsement.previous_version.has_value();
31-
}
32-
33-
void validate_fetched_endorsement(const ccf::CoseEndorsement& endorsement)
34-
{
35-
if (!is_self_endorsement(endorsement))
36-
{
37-
const auto [from, to] =
38-
ccf::crypto::extract_cose_endorsement_validity(endorsement.endorsement);
39-
40-
const auto from_txid = ccf::TxID::from_str(from);
41-
if (!from_txid)
42-
{
43-
throw std::logic_error(fmt::format(
44-
"Cannot parse COSE endorsement header: {}",
45-
ccf::crypto::COSE_PHEADER_KEY_RANGE_BEGIN));
46-
}
47-
48-
const auto to_txid = ccf::TxID::from_str(to);
49-
if (!to_txid)
50-
{
51-
throw std::logic_error(fmt::format(
52-
"Cannot parse COSE endorsement header: {}",
53-
ccf::crypto::COSE_PHEADER_KEY_RANGE_END));
54-
}
55-
56-
if (!endorsement.endorsement_epoch_end)
57-
{
58-
throw std::logic_error(
59-
"COSE endorsement doesn't contain epoch end in the table entry");
60-
}
61-
if (
62-
endorsement.endorsement_epoch_begin != *from_txid ||
63-
*endorsement.endorsement_epoch_end != *to_txid)
64-
{
65-
throw std::logic_error(fmt ::format(
66-
"COSE endorsement fetched but range is invalid, epoch begin {}, "
67-
"epoch end {}, header epoch begin: {}, header epoch end: {}",
68-
endorsement.endorsement_epoch_begin.to_str(),
69-
endorsement.endorsement_epoch_end->to_str(),
70-
from,
71-
to));
72-
}
73-
}
74-
}
75-
76-
void validate_chain_integrity(
77-
const ccf::CoseEndorsement& newer, const ccf::CoseEndorsement& older)
78-
{
79-
if (
80-
!is_self_endorsement(older) &&
81-
(older.endorsement_epoch_end.has_value() &&
82-
(newer.endorsement_epoch_begin.view - aft::starting_view_change !=
83-
older.endorsement_epoch_end->view ||
84-
newer.endorsement_epoch_begin.seqno - 1 !=
85-
older.endorsement_epoch_end->seqno)))
86-
{
87-
throw std::logic_error(fmt::format(
88-
"COSE endorsement chain integrity is violated, previous endorsement "
89-
"epoch end {} is not chained with newer endorsement epoch begin {}",
90-
older.endorsement_epoch_end->to_str(),
91-
newer.endorsement_epoch_begin.to_str()));
92-
}
93-
}
94-
95-
void ensure_first_fetch(ccf::kv::ReadOnlyTx& tx)
96-
{
97-
if (cose_endorsements_cache.empty()) [[unlikely]]
98-
{
99-
const auto endorsement =
100-
tx.template ro<ccf::PreviousServiceIdentityEndorsement>(
101-
ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT)
102-
->get();
103-
104-
if (!endorsement.has_value())
105-
{
106-
throw std::logic_error("Fetched COSE endorsement is invalid");
107-
}
108-
109-
validate_fetched_endorsement(endorsement.value());
110-
111-
// Checked by the validate call above
112-
cose_endorsements_cache.push_back(*endorsement);
113-
}
114-
}
115-
11626
ccf::historical::CompoundHandle make_system_handle(ccf::SeqNo seq)
11727
{
11828
return ccf::historical::CompoundHandle{
11929
ccf::historical::RequestNamespace::System, seq};
12030
}
121-
122-
bool keep_fetching(ccf::SeqNo target_seq)
123-
{
124-
return !is_self_endorsement(cose_endorsements_cache.back()) &&
125-
cose_endorsements_cache.back().endorsement_epoch_begin.seqno > target_seq;
126-
}
127-
128-
FetchResult fetch_endorsements_for(
129-
ccf::kv::ReadOnlyTx& tx,
130-
ccf::historical::AbstractStateCache& state_cache,
131-
ccf::SeqNo target_seq)
132-
{
133-
ensure_first_fetch(tx);
134-
135-
while (keep_fetching(target_seq))
136-
{
137-
auto& last_cose_endorsement = cose_endorsements_cache.back();
138-
if (!last_cose_endorsement.previous_version.has_value())
139-
{
140-
throw std::logic_error(fmt::format(
141-
"previous_version is not set for the endorsement with epoch_begin: "
142-
"{}",
143-
last_cose_endorsement.endorsement_epoch_begin.to_str()));
144-
}
145-
const auto prev_endorsement_seqno =
146-
last_cose_endorsement.previous_version.value();
147-
148-
const auto system_handle = make_system_handle(prev_endorsement_seqno);
149-
auto* cache_impl =
150-
dynamic_cast<ccf::historical::StateCacheImpl*>(&state_cache);
151-
if (cache_impl == nullptr)
152-
{
153-
throw std::logic_error(
154-
"StateCacheImpl required to access cache as "
155-
"RequestNamespace::System");
156-
}
157-
158-
const auto hstate =
159-
cache_impl->get_state_at(system_handle, prev_endorsement_seqno);
160-
161-
if (!hstate)
162-
{
163-
return {.endorsements = std::nullopt, .retry = true};
164-
}
165-
166-
auto htx = hstate->store->create_read_only_tx();
167-
const auto endorsement =
168-
htx
169-
.template ro<ccf::PreviousServiceIdentityEndorsement>(
170-
ccf::Tables::PREVIOUS_SERVICE_IDENTITY_ENDORSEMENT)
171-
->get();
172-
173-
if (!endorsement.has_value())
174-
{
175-
throw std::logic_error("Fetched COSE endorsement is invalid");
176-
}
177-
178-
validate_fetched_endorsement(endorsement.value());
179-
180-
// Checked by the validate call above
181-
validate_chain_integrity(last_cose_endorsement, endorsement.value());
182-
cose_endorsements_cache.push_back(endorsement.value());
183-
}
184-
185-
if (cose_endorsements_cache.size() == 1)
186-
{
187-
LOG_TRACE_FMT(
188-
"Only current service self-endorsement was found, no historical TXs "
189-
"for previous epochs were COSE-endorsed.");
190-
return {.endorsements = std::nullopt, .retry = false};
191-
}
192-
193-
auto last_valid_endorsement = cose_endorsements_cache.end() - 1;
194-
if (is_self_endorsement(*last_valid_endorsement))
195-
{
196-
--last_valid_endorsement;
197-
}
198-
199-
const auto search_to = last_valid_endorsement + 1;
200-
201-
if (last_valid_endorsement->endorsement_epoch_begin.seqno > target_seq)
202-
{
203-
LOG_TRACE_FMT(
204-
"COSE-endorsements are fetched for newer epochs, but target_seq {} is "
205-
"far behind and was never endorsed.",
206-
target_seq);
207-
208-
return {.endorsements = std::nullopt, .retry = false};
209-
}
210-
211-
const auto final_endorsement = std::upper_bound(
212-
cose_endorsements_cache.begin(),
213-
search_to,
214-
target_seq,
215-
[](const auto& seq, const auto& endorsement) {
216-
return endorsement.endorsement_epoch_begin.seqno <= seq;
217-
});
218-
219-
if (final_endorsement == search_to)
220-
{
221-
throw std::logic_error(fmt::format(
222-
"Error during COSE endorsement chain reconstruction for seqno {}",
223-
target_seq));
224-
}
225-
226-
Endorsements endorsements;
227-
228-
std::transform(
229-
cose_endorsements_cache.begin(),
230-
final_endorsement + 1, // Inclusive
231-
std::back_inserter(endorsements),
232-
[](const auto& e) { return e.endorsement; });
233-
234-
return {.endorsements = std::move(endorsements), .retry = false};
235-
}
23631
}
23732

23833
namespace ccf
@@ -400,40 +195,53 @@ namespace ccf
400195
bool populate_cose_service_endorsements(
401196
ccf::kv::ReadOnlyTx& tx,
402197
ccf::historical::StatePtr& state,
403-
AbstractStateCache& state_cache)
198+
std::shared_ptr<NetworkIdentitySubsystemInterface>
199+
network_identity_subsystem)
404200
{
405-
const auto service_info = tx.template ro<Service>(Tables::SERVICE)->get();
406-
if (!service_info)
201+
auto* service = tx.template ro<Service>(Tables::SERVICE);
202+
auto hservice_info = service->get();
203+
if (!hservice_info)
407204
{
408-
throw std::logic_error(
409-
"COSE endorsements fetch: current service info not available");
205+
throw std::runtime_error("Failed to locate service identity");
410206
}
411-
const auto service_start = service_info->current_service_create_txid;
412-
if (!service_start)
207+
if (!hservice_info->current_service_create_txid)
413208
{
414-
throw std::logic_error(
415-
"COSE endorsements fetch: current service create_txid not available");
209+
throw std::runtime_error(
210+
"The service identity is missing 'current_service_create_txid'");
416211
}
417212

418-
const auto target_seq = state->transaction_id.seqno;
419-
if (service_start->seqno <= target_seq)
213+
if (
214+
state->transaction_id.seqno >=
215+
hservice_info->current_service_create_txid->seqno)
420216
{
421-
LOG_TRACE_FMT(
422-
"Target seqno {} belongs to current service started at {}",
423-
target_seq,
424-
service_start->seqno);
217+
// This is handled by the network identity subsystem, but to test
218+
// mid-recovery receipts for the current service identity we set empty
219+
// chain as a valid chain early on.
425220
return true;
426221
}
427222

428-
const auto result =
429-
fetch_endorsements_for(tx, state_cache, state->transaction_id.seqno);
430-
if (!result.endorsements)
223+
const auto fetching =
224+
network_identity_subsystem->endorsements_fetching_status();
225+
if (fetching == FetchStatus::Retry)
226+
{
227+
return false;
228+
}
229+
if (fetching == FetchStatus::Failed)
230+
{
231+
throw std::runtime_error(fmt::format(
232+
"The service identity endorsement for the receipt at seqno {} "
233+
"cannot be fetched",
234+
state->transaction_id.seqno));
235+
}
236+
if (fetching != FetchStatus::Done)
431237
{
432-
const bool final_result = !result.retry;
433-
return final_result;
238+
throw std::logic_error("Unexpected endorsements fetching status");
434239
}
435240

436-
state->receipt->cose_endorsements = result.endorsements.value();
241+
auto cose_endorsements =
242+
network_identity_subsystem->get_cose_endorsements_chain(
243+
state->transaction_id.seqno);
244+
state->receipt->cose_endorsements = cose_endorsements;
437245
return true;
438246
}
439247

0 commit comments

Comments
 (0)