diff --git a/CHANGELOG.md b/CHANGELOG.md index 77cff1fd9b2e..45ab64980395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,11 +26,16 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - The `programmability` sample app now demonstrates how applications can define their own extensions, creating bindings between C++ and JS state, and allowing JS endpoints to call functions implemented in C++. - 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. +- Introduced `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. -## Changed +### Changed - Updated Open Enclave to [0.19.7](https://github.com/openenclave/openenclave/releases/tag/v0.19.7). +### Deprecated + +- `ccf::historical::adapter_v3` becomes deprecated in favour of `_v4` version. + ### Removed - Removed the existing metrics endpoint and API (`GET /api/metrics`, `get_metrics_v1`). Stats for request execution can instead be gathered by overriding the `EndpointRegistry::handle_event_request_completed()` method. diff --git a/include/ccf/historical_queries_adapter.h b/include/ccf/historical_queries_adapter.h index d51308cdd178..01abd9720227 100644 --- a/include/ccf/historical_queries_adapter.h +++ b/include/ccf/historical_queries_adapter.h @@ -39,6 +39,30 @@ namespace ccf::historical std::optional txid_from_header( endpoints::CommandEndpointContext& args); + enum class HistoricalQueryErrorCode + { + InternalError, + TransactionPending, + TransactionInvalid, + TransactionIdMissing, + TransactionPartiallyReady, + }; + + using ErrorHandler = std::function; + + using ReadOnlyErrorHandler = std::function; + + void default_error_handler( + HistoricalQueryErrorCode err, + std::string reason, + endpoints::CommandEndpointContext& args); + enum class HistoricalTxStatus { Error, @@ -56,21 +80,38 @@ namespace ccf::historical ccf::SeqNo seqno, std::string& error_reason); + CCF_DEPRECATED("Replaced by _v4") ccf::endpoints::EndpointFunction adapter_v3( const HandleHistoricalQuery& f, ccf::AbstractNodeContext& node_context, const CheckHistoricalTxStatus& available, const TxIDExtractor& extractor = txid_from_header); + CCF_DEPRECATED("Replaced by _v4") ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v3( const HandleReadOnlyHistoricalQuery& f, ccf::AbstractNodeContext& node_context, const CheckHistoricalTxStatus& available, const ReadOnlyTxIDExtractor& extractor = txid_from_header); + CCF_DEPRECATED("Replaced by _v4") 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 ReadOnlyTxIDExtractor& extractor = txid_from_header, + const ReadOnlyErrorHandler& ehandler = default_error_handler); + + ccf::endpoints::EndpointFunction read_write_adapter_v4( + const HandleReadWriteHistoricalQuery& f, + ccf::AbstractNodeContext& node_context, + const CheckHistoricalTxStatus& available, + const TxIDExtractor& extractor = txid_from_header, + const ErrorHandler& ehandler = default_error_handler); } \ No newline at end of file diff --git a/samples/apps/logging/logging.cpp b/samples/apps/logging/logging.cpp index 59081fdd90d6..a690452b7c37 100644 --- a/samples/apps/logging/logging.cpp +++ b/samples/apps/logging/logging.cpp @@ -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() @@ -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() @@ -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() diff --git a/src/apps/js_generic/js_generic_base.cpp b/src/apps/js_generic/js_generic_base.cpp index 073ed433048e..1ddb4c9a09bf 100644 --- a/src/apps/js_generic/js_generic_base.cpp +++ b/src/apps/js_generic/js_generic_base.cpp @@ -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) { diff --git a/src/endpoints/common_endpoint_registry.cpp b/src/endpoints/common_endpoint_registry.cpp index cc87a9e610fc..907a714728e7 100644 --- a/src/endpoints/common_endpoint_registry.cpp +++ b/src/endpoints/common_endpoint_registry.cpp @@ -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() diff --git a/src/js/registry.cpp b/src/js/registry.cpp index 1675c870c43d..a886c7eea219 100644 --- a/src/js/registry.cpp +++ b/src/js/registry.cpp @@ -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) { diff --git a/src/node/historical_queries_adapter.cpp b/src/node/historical_queries_adapter.cpp index 3ca36ca16376..260aaabcaa9d 100644 --- a/src/node/historical_queries_adapter.cpp +++ b/src/node/historical_queries_adapter.cpp @@ -190,6 +190,58 @@ namespace ccf::historical return tx_id_opt; } + void default_error_handler( + HistoricalQueryErrorCode err, + std::string reason, + endpoints::CommandEndpointContext& args) + { + switch (err) + { + case HistoricalQueryErrorCode::InternalError: + { + args.rpc_ctx->set_error( + HTTP_STATUS_INTERNAL_SERVER_ERROR, + ccf::errors::TransactionPendingOrUnknown, + std::move(reason)); + break; + } + case 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)); + break; + } + case HistoricalQueryErrorCode::TransactionInvalid: + case HistoricalQueryErrorCode::TransactionIdMissing: + { + args.rpc_ctx->set_error( + HTTP_STATUS_NOT_FOUND, + ccf::errors::TransactionInvalid, + std::move(reason)); + break; + } + case 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)); + break; + } + default: + { + LOG_FAIL_FMT("Unexpected historical query error {}", err); + } + } + } + HistoricalTxStatus is_tx_committed_v2( ccf::kv::Consensus* consensus, ccf::View view, @@ -245,13 +297,71 @@ namespace ccf::historical ccf::AbstractNodeContext& node_context, const CheckHistoricalTxStatus& available, const TTxIDExtractor& extractor) + { + return _adapter_v4( + f, node_context, available, extractor, default_error_handler); + } + + ccf::endpoints::EndpointFunction adapter_v3( + const HandleHistoricalQuery& f, + ccf::AbstractNodeContext& node_context, + const CheckHistoricalTxStatus& available, + const TxIDExtractor& extractor) + { + return _adapter_v3< + HandleHistoricalQuery, + ccf::endpoints::EndpointFunction, + ccf::endpoints::EndpointContext>(f, node_context, available, extractor); + } + + ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v3( + const HandleReadOnlyHistoricalQuery& f, + ccf::AbstractNodeContext& node_context, + const CheckHistoricalTxStatus& available, + const ReadOnlyTxIDExtractor& extractor) + { + return _adapter_v3< + HandleReadOnlyHistoricalQuery, + ccf::endpoints::ReadOnlyEndpointFunction, + ccf::endpoints::ReadOnlyEndpointContext>( + f, node_context, available, extractor); + } + + ccf::endpoints::EndpointFunction read_write_adapter_v3( + const HandleReadWriteHistoricalQuery& f, + ccf::AbstractNodeContext& node_context, + const CheckHistoricalTxStatus& available, + const TxIDExtractor& extractor) + { + return _adapter_v3< + HandleReadWriteHistoricalQuery, + ccf::endpoints::EndpointFunction, + ccf::endpoints::EndpointContext>(f, node_context, available, extractor); + } + + template < + class TQueryHandler, + class TEndpointFunction, + class TEndpointContext, + class TTxIDExtractor, + class TErrorHandler> + TEndpointFunction _adapter_v4( + const TQueryHandler& f, + ccf::AbstractNodeContext& node_context, + const CheckHistoricalTxStatus& available, + const TTxIDExtractor& extractor, + const TErrorHandler& ehandler) { auto& state_cache = node_context.get_historical_state(); auto network_identity_subsystem = node_context.get_subsystem(); - return [f, &state_cache, network_identity_subsystem, available, extractor]( - TEndpointContext& args) { + return [f, + &state_cache, + network_identity_subsystem, + available, + extractor, + ehandler](TEndpointContext& args) { // Extract the requested transaction ID ccf::TxID target_tx_id; { @@ -262,6 +372,10 @@ namespace ccf::historical } else { + ehandler( + HistoricalQueryErrorCode::TransactionIdMissing, + "Could not extract TX ID", + args); return; } } @@ -272,38 +386,29 @@ namespace ccf::historical "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: - { - args.rpc_ctx->set_error( - HTTP_STATUS_INTERNAL_SERVER_ERROR, - ccf::errors::TransactionPendingOrUnknown, - std::move(error_reason)); + ehandler( + HistoricalQueryErrorCode::InternalError, + std::move(error_reason), + args); return; - } case HistoricalTxStatus::PendingOrUnknown: - { - // Set header No-Cache - 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(error_reason)); + ehandler( + HistoricalQueryErrorCode::TransactionPending, + std::move(error_reason), + args); return; - } case HistoricalTxStatus::Invalid: - { - args.rpc_ctx->set_error( - HTTP_STATUS_NOT_FOUND, - ccf::errors::TransactionInvalid, - std::move(error_reason)); + ehandler( + HistoricalQueryErrorCode::TransactionInvalid, + std::move(error_reason), + args); return; - } case HistoricalTxStatus::Valid: - { - } + break; } } @@ -321,15 +426,13 @@ namespace ccf::historical (!populate_service_endorsements( args.tx, historical_state, state_cache, network_identity_subsystem))) { - 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(fmt::format( + auto reason = fmt::format( "Historical transaction {} is not currently available.", - target_tx_id.to_str())); + target_tx_id.to_str()); + ehandler( + HistoricalQueryErrorCode::TransactionPartiallyReady, + std::move(reason), + args); return; } @@ -338,40 +441,31 @@ namespace ccf::historical }; } - ccf::endpoints::EndpointFunction adapter_v3( - const HandleHistoricalQuery& f, - ccf::AbstractNodeContext& node_context, - const CheckHistoricalTxStatus& available, - const TxIDExtractor& extractor) - { - return _adapter_v3< - HandleHistoricalQuery, - ccf::endpoints::EndpointFunction, - ccf::endpoints::EndpointContext>(f, node_context, available, extractor); - } - - ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v3( + ccf::endpoints::ReadOnlyEndpointFunction read_only_adapter_v4( const HandleReadOnlyHistoricalQuery& f, ccf::AbstractNodeContext& node_context, const CheckHistoricalTxStatus& available, - const ReadOnlyTxIDExtractor& extractor) + const ReadOnlyTxIDExtractor& extractor, + const ReadOnlyErrorHandler& ehandler) { - return _adapter_v3< + return _adapter_v4< HandleReadOnlyHistoricalQuery, ccf::endpoints::ReadOnlyEndpointFunction, ccf::endpoints::ReadOnlyEndpointContext>( - f, node_context, available, extractor); + f, node_context, available, extractor, ehandler); } - ccf::endpoints::EndpointFunction read_write_adapter_v3( + ccf::endpoints::EndpointFunction read_write_adapter_v4( const HandleReadWriteHistoricalQuery& f, ccf::AbstractNodeContext& node_context, const CheckHistoricalTxStatus& available, - const TxIDExtractor& extractor) + const TxIDExtractor& extractor, + const ErrorHandler& ehandler) { - return _adapter_v3< + return _adapter_v4< HandleReadWriteHistoricalQuery, ccf::endpoints::EndpointFunction, - ccf::endpoints::EndpointContext>(f, node_context, available, extractor); + ccf::endpoints::EndpointContext>( + f, node_context, available, extractor, ehandler); } -} \ No newline at end of file +}