Skip to content

Commit 8306717

Browse files
authored
Refactor js_generic to use common DynamicJSEndpointRegistry hierarchy, and expose as an extensible public header (#6710)
1 parent 526793b commit 8306717

File tree

8 files changed

+197
-925
lines changed

8 files changed

+197
-925
lines changed

CHANGELOG.md

+8
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ and this project adheres Fto [Semantic Versioning](http://semver.org/spec/v2.0.0
1313

1414
- The function `ccf::get_js_plugins()` and associated FFI plugin system for JS is deprecated. Similar functionality should now be implemented through a `js::Extension` returned from `DynamicJSEndpointRegistry::get_extensions()`.
1515

16+
## [6.0.0-dev11]
17+
18+
[6.0.0-dev11]: https://github.com/microsoft/CCF/releases/tag/6.0.0-dev11
19+
20+
### Added
21+
22+
- Applications can now extend `js_generic` (ie - a JS app where JS endpoints are edited by governance transactions), from the public header `ccf/js/samples/governance_driven_registry.h`. The API for existing JS-programmability apps using `DynamicJSEndpointRegistry` should be unaffected.
23+
1624
## [6.0.0-dev10]
1725

1826
[6.0.0-dev10]: https://github.com/microsoft/CCF/releases/tag/6.0.0-dev10

CMakeLists.txt

+1-37
Original file line numberDiff line numberDiff line change
@@ -416,45 +416,9 @@ set(CCF_NETWORK_TEST_ARGS
416416
${TEST_LOGGING_LEVEL} --worker-threads ${WORKER_THREADS}
417417
)
418418

419-
set(JS_GENERIC_SOURCES ${CCF_DIR}/src/apps/js_generic/js_generic_base.cpp)
420-
if(COMPILE_TARGET STREQUAL "snp")
421-
add_library(js_generic_base.snp STATIC ${JS_GENERIC_SOURCES})
422-
add_san(js_generic_base.snp)
423-
add_warning_checks(js_generic_base.snp)
424-
target_link_libraries(js_generic_base.snp PUBLIC ccf.snp)
425-
target_compile_options(js_generic_base.snp PRIVATE ${COMPILE_LIBCXX})
426-
target_compile_definitions(
427-
js_generic_base.snp PUBLIC INSIDE_ENCLAVE VIRTUAL_ENCLAVE
428-
_LIBCPP_HAS_THREAD_API_PTHREAD PLATFORM_SNP
429-
)
430-
set_property(TARGET js_generic_base.snp PROPERTY POSITION_INDEPENDENT_CODE ON)
431-
install(
432-
TARGETS js_generic_base.snp
433-
EXPORT ccf
434-
DESTINATION lib
435-
)
436-
elseif(COMPILE_TARGET STREQUAL "virtual")
437-
add_library(js_generic_base.virtual STATIC ${JS_GENERIC_SOURCES})
438-
add_san(js_generic_base.virtual)
439-
add_warning_checks(js_generic_base.virtual)
440-
target_link_libraries(js_generic_base.virtual PUBLIC ccf.virtual)
441-
target_compile_options(js_generic_base.virtual PRIVATE ${COMPILE_LIBCXX})
442-
set_property(
443-
TARGET js_generic_base.virtual PROPERTY POSITION_INDEPENDENT_CODE ON
444-
)
445-
install(
446-
TARGETS js_generic_base.virtual
447-
EXPORT ccf
448-
DESTINATION lib
449-
)
450-
endif()
451419
# SNIPPET_START: JS generic application
452420
add_ccf_app(
453-
js_generic
454-
SRCS ${CCF_DIR}/src/apps/js_generic/js_generic.cpp
455-
LINK_LIBS_ENCLAVE js_generic_base.enclave
456-
LINK_LIBS_VIRTUAL js_generic_base.virtual
457-
LINK_LIBS_SNP js_generic_base.snp INSTALL_LIBS ON
421+
js_generic SRCS ${CCF_DIR}/src/apps/js_generic/js_generic.cpp INSTALL_LIBS ON
458422
)
459423
# SNIPPET_END: JS generic application
460424

include/ccf/js/registry.h

+63-45
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,13 @@
1919

2020
namespace ccf::js
2121
{
22+
static constexpr auto default_js_registry_kv_prefix =
23+
"public:custom_endpoints";
24+
2225
struct CustomJSEndpoint : public ccf::endpoints::Endpoint
2326
{};
2427

25-
// By subclassing DynamicJSEndpointRegistry, an application gains the
28+
// By subclassing BaseDynamicJSEndpointRegistry, an application gains the
2629
// ability to execute custom JavaScript endpoints, and exposes the ability to
2730
// install them via install_custom_endpoints(). The JavaScript code for these
2831
// endpoints is stored in the internal KV store under a namespace configured
@@ -31,31 +34,14 @@ namespace ccf::js
3134
// proposal in governance, and the payload format is currently identical,
3235
// except the controlling logic resides in the application space.
3336
//
34-
// Known limitations:
35-
//
36-
// No auditability yet, COSE Sign1 auth is recommended, but the signature is
37-
// not stored.
38-
// No support for historical endpoints yet.
39-
// No support for import from external modules.
40-
//
4137
// Additional functionality compared to set_js_app:
42-
//
43-
// The KV namespace can be private, to keep the application confidential if
38+
// - The KV namespace can be private, to keep the application confidential if
4439
// desired.
45-
class DynamicJSEndpointRegistry : public ccf::UserEndpointRegistry
40+
class BaseDynamicJSEndpointRegistry : public ccf::UserEndpointRegistry
4641
{
4742
private:
4843
std::shared_ptr<ccf::js::AbstractInterpreterCache> interpreter_cache =
4944
nullptr;
50-
std::string modules_map;
51-
std::string metadata_map;
52-
std::string interpreter_flush_map;
53-
std::string modules_quickjs_version_map;
54-
std::string modules_quickjs_bytecode_map;
55-
std::string runtime_options_map;
56-
std::string recent_actions_map;
57-
std::string audit_input_map;
58-
std::string audit_info_map;
5945

6046
ccf::js::NamespaceRestriction namespace_restriction;
6147

@@ -75,10 +61,18 @@ namespace ccf::js
7561
ccf::endpoints::CommandEndpointContext& endpoint_ctx,
7662
const ccf::TxID& tx_id);
7763

64+
protected:
65+
std::string modules_map;
66+
std::string metadata_map;
67+
std::string interpreter_flush_map;
68+
std::string modules_quickjs_version_map;
69+
std::string modules_quickjs_bytecode_map;
70+
std::string runtime_options_map;
71+
7872
public:
79-
DynamicJSEndpointRegistry(
73+
BaseDynamicJSEndpointRegistry(
8074
ccf::AbstractNodeContext& context,
81-
const std::string& kv_prefix = "public:custom_endpoints");
75+
const std::string& kv_prefix = default_js_registry_kv_prefix);
8276

8377
/**
8478
* Call this to populate the KV with JS endpoint definitions, so they can
@@ -133,29 +127,6 @@ namespace ccf::js
133127
*/
134128
ccf::ApiResult get_js_runtime_options_v1(
135129
ccf::JSRuntimeOptions& options, ccf::kv::ReadOnlyTx& tx);
136-
137-
/**
138-
* Record action details by storing them in KV maps using a common format,
139-
* for the purposes of offline audit using the ledger.
140-
*/
141-
ccf::ApiResult record_action_for_audit_v1(
142-
ccf::kv::Tx& tx,
143-
ccf::ActionFormat format,
144-
const std::string& user_id,
145-
const std::string& action_name,
146-
const std::vector<uint8_t>& action_body);
147-
148-
/**
149-
* Check an action is not being replayed, by looking it up
150-
* in the history of recent actions. To place an upper bound on the history
151-
* size, an authenticated timestamp (@p created_at) is required.
152-
*/
153-
ccf::ApiResult check_action_not_replayed_v1(
154-
ccf::kv::Tx& tx,
155-
uint64_t created_at,
156-
const std::span<const uint8_t> action,
157-
ccf::InvalidArgsReason& reason);
158-
159130
/// \defgroup Overrides for base EndpointRegistry functions, looking up JS
160131
/// endpoints before delegating to base implementation.
161132
///@{
@@ -172,6 +143,9 @@ namespace ccf::js
172143
const ccf::TxID& tx_id) override;
173144

174145
void build_api(nlohmann::json& document, ccf::kv::ReadOnlyTx& tx) override;
146+
147+
std::set<RESTVerb> get_allowed_verbs(
148+
ccf::kv::Tx&, const ccf::RpcContext& rpc_ctx) override;
175149
///@}
176150

177151
virtual ccf::js::extensions::Extensions get_extensions(
@@ -180,4 +154,48 @@ namespace ccf::js
180154
return {};
181155
};
182156
};
157+
158+
// Extends BaseDynamicJSEndpointRegistry with methods for making actions
159+
// auditable and preventing replay. These should be used if apps are not
160+
// deployed through governance, to ensure that app-modification is safely and
161+
// clearly tracked in the ledger history
162+
class DynamicJSEndpointRegistry : public BaseDynamicJSEndpointRegistry
163+
{
164+
protected:
165+
std::string recent_actions_map;
166+
std::string audit_input_map;
167+
std::string audit_info_map;
168+
169+
public:
170+
DynamicJSEndpointRegistry(
171+
ccf::AbstractNodeContext& context,
172+
const std::string& kv_prefix = default_js_registry_kv_prefix) :
173+
BaseDynamicJSEndpointRegistry(context, kv_prefix),
174+
recent_actions_map(fmt::format("{}.recent_actions", kv_prefix)),
175+
audit_input_map(fmt::format("{}.audit.input", kv_prefix)),
176+
audit_info_map(fmt::format("{}.audit.info", kv_prefix))
177+
{}
178+
179+
/**
180+
* Record action details by storing them in KV maps using a common format,
181+
* for the purposes of offline audit using the ledger.
182+
*/
183+
ccf::ApiResult record_action_for_audit_v1(
184+
ccf::kv::Tx& tx,
185+
ccf::ActionFormat format,
186+
const std::string& user_id,
187+
const std::string& action_name,
188+
const std::vector<uint8_t>& action_body);
189+
190+
/**
191+
* Check an action is not being replayed, by looking it up
192+
* in the history of recent actions. To place an upper bound on the history
193+
* size, an authenticated timestamp (@p created_at) is required.
194+
*/
195+
ccf::ApiResult check_action_not_replayed_v1(
196+
ccf::kv::Tx& tx,
197+
uint64_t created_at,
198+
const std::span<const uint8_t> action,
199+
ccf::InvalidArgsReason& reason);
200+
};
183201
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
#pragma once
4+
5+
#include "ccf/js/registry.h"
6+
#include "ccf/service/tables/jsengine.h"
7+
#include "ccf/service/tables/modules.h"
8+
9+
namespace ccf::js
10+
{
11+
// This sample extends the generic BaseDynamicJSEndpointRegistry to read JS
12+
// endpoints (code, metadata, options) from governance tables. Specifically,
13+
// tables populated by actions in the default sample CCF constitution
14+
// (set_js_app). This can be sub-classed to modify the dispatch or execution
15+
// behaviour, or to provide further JS extension APIs via get_extensions().
16+
//
17+
// An application running this registry with no further extensions is shipped
18+
// with the CCF releases as `js_generic`.
19+
class GovernanceDrivenJSRegistry
20+
: public ccf::js::BaseDynamicJSEndpointRegistry
21+
{
22+
public:
23+
GovernanceDrivenJSRegistry(AbstractNodeContext& context) :
24+
// Note: We do not pass a kv_prefix here, instead we explicitly, manually
25+
// construct each map name to match previously used values
26+
ccf::js::BaseDynamicJSEndpointRegistry(context)
27+
{
28+
modules_map = ccf::Tables::MODULES;
29+
metadata_map = ccf::endpoints::Tables::ENDPOINTS;
30+
interpreter_flush_map = ccf::Tables::INTERPRETER_FLUSH;
31+
modules_quickjs_version_map = ccf::Tables::MODULES_QUICKJS_VERSION;
32+
modules_quickjs_bytecode_map = ccf::Tables::MODULES_QUICKJS_BYTECODE;
33+
runtime_options_map = ccf::Tables::JSENGINE;
34+
}
35+
};
36+
} // namespace ccf::js

src/apps/js_generic/js_generic.cpp

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
// Copyright (c) Microsoft Corporation. All rights reserved.
22
// Licensed under the Apache 2.0 License.
3+
34
#include "ccf/app_interface.h"
4-
#include "js_generic_base.h"
5+
#include "ccf/js/samples/governance_driven_registry.h"
56

67
namespace ccf
78
{
89
std::unique_ptr<ccf::endpoints::EndpointRegistry> make_user_endpoints(
910
ccf::AbstractNodeContext& context)
1011
{
11-
return make_user_endpoints_impl(context);
12+
return std::make_unique<ccf::js::GovernanceDrivenJSRegistry>(context);
1213
}
14+
1315
} // namespace ccf

0 commit comments

Comments
 (0)