Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add customisable error handling to hist. queries #6322

Merged
Show file tree
Hide file tree
Changes from 2 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
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
- Introduce `DynamicJSEndpointRegistry::record_action_for_audit_v1` and `DynamicJSEndpointRegistry::check_action_not_replayed_v1` to allow an application making use of the programmability feature to easily implement auditability, and protect users allowed to update the application against replay attacks (#6285).
- Endpoints now support a `ToBackup` redirection strategy, for requests which should never be executed on a primary. These must also be read-only. These are configured similar to `ToPrimary` endpoints, with a `to_backup` object (specifying by-role or statically-addressed targets) in each node's configuration.

## Changed
### Changed

- Updated Open Enclave to [0.19.7](https://github.com/openenclave/openenclave/releases/tag/v0.19.7).
- `ccf::historical::adapter_v3` becomes deprecated and gets replaced by `ccf::historical::read_only_adapter_v4` and `ccf::historical::read_write_adapter_v4`. Users are now capable of passing a custom error handler to the adapter to customise RPC responses for internal historical queries errors, which are listed in `ccf::historical::HistoricalQueryErrorCode` enum.
achamayou marked this conversation as resolved.
Show resolved Hide resolved

### Removed

Expand Down
39 changes: 36 additions & 3 deletions include/ccf/historical_queries_adapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,25 @@ namespace ccf::historical
std::optional<ccf::TxID> txid_from_header(
endpoints::CommandEndpointContext& args);

enum class HistoricalQueryErrorCode
{
InternalError,
TransactionPending,
TransactionInvalid,
TransactionIdMissing,
TransactionPartiallyReady,
};

using ErrorHandler = std::function<void(
HistoricalQueryErrorCode err,
std::string reason,
endpoints::CommandEndpointContext& args)>;

void default_error_handler(
HistoricalQueryErrorCode err,
std::string reason,
endpoints::CommandEndpointContext& args);

enum class HistoricalTxStatus
{
Error,
Expand All @@ -56,21 +75,35 @@ namespace ccf::historical
ccf::SeqNo seqno,
std::string& error_reason);

ccf::endpoints::EndpointFunction adapter_v3(
FMT_DEPRECATED ccf::endpoints::EndpointFunction adapter_v3(
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
const HandleHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const TxIDExtractor& extractor = txid_from_header);

ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v3(
FMT_DEPRECATED ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v3(
const HandleReadOnlyHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const ReadOnlyTxIDExtractor& extractor = txid_from_header);

ccf::endpoints::EndpointFunction read_write_adapter_v3(
FMT_DEPRECATED ccf::endpoints::EndpointFunction read_write_adapter_v3(
const HandleReadWriteHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const TxIDExtractor& extractor = txid_from_header);

ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v4(
const HandleReadOnlyHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const CommandTxIDExtractor& extractor = txid_from_header,
const ErrorHandler& ehandler = default_error_handler);

ccf::endpoints::EndpointFunction read_write_adapter_v4(
const HandleReadWriteHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const CommandTxIDExtractor& extractor = txid_from_header,
achamayou marked this conversation as resolved.
Show resolved Hide resolved
const ErrorHandler& ehandler = default_error_handler);
}
6 changes: 3 additions & 3 deletions samples/apps/logging/logging.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1285,7 +1285,7 @@ namespace loggingapp
make_read_only_endpoint(
"/log/private/historical",
HTTP_GET,
ccf::historical::read_only_adapter_v3(
ccf::historical::read_only_adapter_v4(
get_historical, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetHistorical::Out>()
Expand Down Expand Up @@ -1335,7 +1335,7 @@ namespace loggingapp
make_read_only_endpoint(
"/log/private/historical_receipt",
HTTP_GET,
ccf::historical::read_only_adapter_v3(
ccf::historical::read_only_adapter_v4(
get_historical_with_receipt, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetReceipt::Out>()
Expand Down Expand Up @@ -1391,7 +1391,7 @@ namespace loggingapp
make_read_only_endpoint(
"/log/public/historical_receipt",
HTTP_GET,
ccf::historical::read_only_adapter_v3(
ccf::historical::read_only_adapter_v4(
get_historical_with_receipt_and_claims, context, is_tx_committed),
auth_policies)
.set_auto_schema<void, LoggingGetReceipt::Out>()
Expand Down
2 changes: 1 addition & 1 deletion src/apps/js_generic/js_generic_base.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ namespace ccf
consensus, view, seqno, error_reason);
};

ccf::historical::adapter_v3(
ccf::historical::read_write_adapter_v4(
[this, endpoint](
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr state) {
Expand Down
2 changes: 1 addition & 1 deletion src/endpoints/common_endpoint_registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ namespace ccf
make_read_only_endpoint(
"/receipt",
HTTP_GET,
ccf::historical::read_only_adapter_v3(
ccf::historical::read_only_adapter_v4(
get_receipt, context, is_tx_committed, txid_from_query_string),
no_auth_required)
.set_auto_schema<void, nlohmann::json>()
Expand Down
2 changes: 1 addition & 1 deletion src/js/registry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ namespace ccf::js
consensus, view, seqno, error_reason);
};

ccf::historical::adapter_v3(
ccf::historical::read_write_adapter_v4(
[this, endpoint](
ccf::endpoints::EndpointContext& endpoint_ctx,
ccf::historical::StatePtr state) {
Expand Down
179 changes: 179 additions & 0 deletions src/node/historical_queries_adapter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,57 @@ namespace ccf::historical
return tx_id_opt;
}

void default_error_handler(
HistoricalQueryErrorCode err,
std::string reason,
endpoints::CommandEndpointContext& args)
{
if (err == HistoricalQueryErrorCode::InternalError)
maxtropets marked this conversation as resolved.
Show resolved Hide resolved
{
args.rpc_ctx->set_error(
HTTP_STATUS_INTERNAL_SERVER_ERROR,
ccf::errors::TransactionPendingOrUnknown,
std::move(reason));
}
else if (err == HistoricalQueryErrorCode::TransactionPending)
{
args.rpc_ctx->set_response_header(
http::headers::CACHE_CONTROL, "no-cache");
args.rpc_ctx->set_error(
HTTP_STATUS_NOT_FOUND,
ccf::errors::TransactionPendingOrUnknown,
std::move(reason));
}
else if (err == HistoricalQueryErrorCode::TransactionInvalid)
{
args.rpc_ctx->set_error(
HTTP_STATUS_NOT_FOUND,
ccf::errors::TransactionInvalid,
std::move(reason));
}
else if (err == HistoricalQueryErrorCode::TransactionIdMissing)
{
args.rpc_ctx->set_error(
HTTP_STATUS_NOT_FOUND,
ccf::errors::TransactionInvalid,
std::move(reason));
}
else if (err == HistoricalQueryErrorCode::TransactionPartiallyReady)
{
args.rpc_ctx->set_response_status(HTTP_STATUS_ACCEPTED);
constexpr size_t retry_after_seconds = 3;
args.rpc_ctx->set_response_header(
http::headers::RETRY_AFTER, retry_after_seconds);
args.rpc_ctx->set_response_header(
http::headers::CONTENT_TYPE, http::headervalues::contenttype::TEXT);
args.rpc_ctx->set_response_body(std::move(reason));
}
else
{
LOG_FAIL_FMT("Unexpected historical query error {}", err);
}
}

HistoricalTxStatus is_tx_committed_v2(
ccf::kv::Consensus* consensus,
ccf::View view,
Expand Down Expand Up @@ -374,4 +425,132 @@ namespace ccf::historical
ccf::endpoints::EndpointFunction,
ccf::endpoints::EndpointContext>(f, node_context, available, extractor);
}

template <
class TQueryHandler,
class TEndpointFunction,
class TEndpointContext>
TEndpointFunction _adapter_v4(
achamayou marked this conversation as resolved.
Show resolved Hide resolved
const TQueryHandler& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const CommandTxIDExtractor& extractor,
const ErrorHandler& ehandler)
{
auto& state_cache = node_context.get_historical_state();
auto network_identity_subsystem =
node_context.get_subsystem<NetworkIdentitySubsystemInterface>();

return [f,
&state_cache,
network_identity_subsystem,
available,
extractor,
ehandler](TEndpointContext& args) {
// Extract the requested transaction ID
ccf::TxID target_tx_id;
{
const auto tx_id_opt = extractor(args);
if (tx_id_opt.has_value())
{
target_tx_id = tx_id_opt.value();
}
else
{
ehandler(
HistoricalQueryErrorCode::TransactionIdMissing,
"Could not extract TX ID",
args);
return;
}
}

// Check that the requested transaction ID is available
{
auto error_reason = fmt::format(
"Transaction {} is not available.", target_tx_id.to_str());
auto is_available =
available(target_tx_id.view, target_tx_id.seqno, error_reason);

switch (is_available)
{
case HistoricalTxStatus::Error:
ehandler(
HistoricalQueryErrorCode::InternalError,
std::move(error_reason),
args);
return;
case HistoricalTxStatus::PendingOrUnknown:
ehandler(
HistoricalQueryErrorCode::TransactionPending,
std::move(error_reason),
args);
return;
case HistoricalTxStatus::Invalid:
ehandler(
HistoricalQueryErrorCode::TransactionInvalid,
std::move(error_reason),
args);
return;
case HistoricalTxStatus::Valid:
break;
}
}

// We need a handle to determine whether this request is the 'same' as a
// previous one. For simplicity we use target_tx_id.seqno. This means we
// keep a lot of state around for old requests! It should be cleaned up
// manually
const auto historic_request_handle = target_tx_id.seqno;

// Get a state at the target version from the cache, if it is present
auto historical_state =
state_cache.get_state_at(historic_request_handle, target_tx_id.seqno);
if (
historical_state == nullptr ||
(!populate_service_endorsements(
args.tx, historical_state, state_cache, network_identity_subsystem)))
{
auto reason = fmt::format(
"Historical transaction {} is not currently available.",
target_tx_id.to_str());
ehandler(
HistoricalQueryErrorCode::TransactionPartiallyReady,
std::move(reason),
args);
return;
}

// Call the provided handler
f(args, historical_state);
};
}

ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v4(
const HandleReadOnlyHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const CommandTxIDExtractor& extractor,
const ErrorHandler& ehandler)
{
return _adapter_v4<
HandleReadOnlyHistoricalQuery,
ccf::endpoints::ReadOnlyEndpointFunction,
ccf::endpoints::ReadOnlyEndpointContext>(
f, node_context, available, extractor, ehandler);
}

ccf::endpoints::EndpointFunction read_write_adapter_v4(
const HandleReadWriteHistoricalQuery& f,
ccf::AbstractNodeContext& node_context,
const CheckHistoricalTxStatus& available,
const CommandTxIDExtractor& extractor,
const ErrorHandler& ehandler)
{
return _adapter_v4<
HandleReadWriteHistoricalQuery,
ccf::endpoints::EndpointFunction,
ccf::endpoints::EndpointContext>(
f, node_context, available, extractor, ehandler);
}
}