Skip to content

Commit 00b8d76

Browse files
authored
Cherry-pick #7226 to 6.x LTS (#7229)
1 parent 7d0cdd0 commit 00b8d76

File tree

11 files changed

+681
-10
lines changed

11 files changed

+681
-10
lines changed

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
66
and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
77

8+
## [6.0.11]
9+
10+
[6.0.11]: https://github.com/microsoft/CCF/releases/tag/ccf-6.0.11
11+
12+
### Added
13+
14+
- Added `ccf.gov.validateConstitution` function to JS API, which can be used to confirm some basic properties of a proposed constitution (it is a string, parseable by our JS interpreter, exporting functions named `validate`, `resolve` and `apply` with the correct number of arguments). This is called in the default sample constitution's `set_constitution.validate`.
15+
816
## [6.0.10]
917

1018
[6.0.10]: https://github.com/microsoft/CCF/releases/tag/ccf-6.0.10

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@ set(CCF_JS_SOURCES
328328
${CCF_DIR}/src/js/extensions/ccf/consensus.cpp
329329
${CCF_DIR}/src/js/extensions/ccf/converters.cpp
330330
${CCF_DIR}/src/js/extensions/ccf/crypto.cpp
331+
${CCF_DIR}/src/js/extensions/ccf/gov.cpp
331332
${CCF_DIR}/src/js/extensions/ccf/gov_effects.cpp
332333
${CCF_DIR}/src/js/extensions/ccf/historical.cpp
333334
${CCF_DIR}/src/js/extensions/ccf/host.cpp

include/ccf/js/common_context.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "ccf/js/core/context.h"
66
#include "ccf/js/extensions/ccf/converters.h"
77
#include "ccf/js/extensions/ccf/crypto.h"
8+
#include "ccf/js/extensions/ccf/gov.h"
89
#include "ccf/js/extensions/ccf/kv.h"
910
#include "ccf/js/extensions/console.h"
1011
#include "ccf/js/extensions/math/random.h"
@@ -41,6 +42,10 @@ namespace ccf::js
4142
// add snp_attestation.*
4243
Base::add_extension(
4344
std::make_shared<ccf::js::extensions::SnpAttestationExtension>());
45+
46+
// add ccf.gov.*
47+
Base::add_extension(
48+
std::make_shared<ccf::js::extensions::GovExtension>());
4449
}
4550
};
4651

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
#pragma once
4+
5+
#include "ccf/js/extensions/extension_interface.h"
6+
7+
namespace ccf::js::extensions
8+
{
9+
/**
10+
* Adds the following functions:
11+
*
12+
* - ccf.gov.isValidConstitution
13+
*
14+
**/
15+
class GovExtension : public ExtensionInterface
16+
{
17+
public:
18+
GovExtension() = default;
19+
20+
void install(js::core::Context& ctx) override;
21+
};
22+
}

python/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "ccf"
7-
version = "6.0.10"
7+
version = "6.0.11"
88
authors = [
99
{ name="CCF Team", email="CCF-Sec@microsoft.com" },
1010
]

samples/constitutions/default/actions.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,8 @@ const actions = new Map([
399399
"set_constitution",
400400
new Action(
401401
function (args) {
402-
checkType(args.constitution, "string");
402+
checkType(args.constitution, "string", "constitution");
403+
ccf.gov.validateConstitution(args.constitution);
403404
},
404405
function (args, proposalId) {
405406
ccf.kv["public:ccf.gov.constitution"].set(

src/js/extensions/ccf/gov.cpp

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved.
2+
// Licensed under the Apache 2.0 License.
3+
4+
#include "ccf/js/extensions/ccf/gov.h"
5+
6+
#include "ccf/js/core/context.h"
7+
8+
#include <iostream>
9+
#include <quickjs/quickjs.h>
10+
11+
namespace ccf::js::extensions
12+
{
13+
namespace
14+
{
15+
JSValue js_validate_constitution(
16+
JSContext* ctx,
17+
[[maybe_unused]] JSValueConst this_val,
18+
int argc,
19+
JSValueConst* argv)
20+
{
21+
js::core::Context& jsctx =
22+
*reinterpret_cast<js::core::Context*>(JS_GetContextOpaque(ctx));
23+
24+
if (argc != 1)
25+
{
26+
return JS_ThrowTypeError(
27+
ctx, "Passed %d arguments, but expected 1", argc);
28+
}
29+
30+
auto arg = jsctx.wrap(argv[0]);
31+
if (!arg.is_str())
32+
{
33+
return JS_ThrowTypeError(ctx, "constitution is not a string");
34+
}
35+
36+
auto constitution = jsctx.to_str(arg);
37+
if (!constitution.has_value())
38+
{
39+
return JS_ThrowTypeError(ctx, "constitution is not a string");
40+
}
41+
42+
if (constitution->empty())
43+
{
44+
return JS_ThrowTypeError(ctx, "constitution is empty");
45+
}
46+
47+
const std::string path("proposed constitution");
48+
49+
struct ArgsSpec
50+
{
51+
std::vector<std::string> required_args;
52+
std::vector<std::string> optional_args;
53+
};
54+
55+
std::map<std::string, ArgsSpec> funcs_to_args;
56+
funcs_to_args["validate"] = {{"input"}, {}};
57+
funcs_to_args["resolve"] = {
58+
{"proposal", "proposerId", "votes"}, {"proposalId"}};
59+
funcs_to_args["apply"] = {{"proposal", "proposerId"}, {}};
60+
for (const auto& [fn_name, args_spec] : funcs_to_args)
61+
{
62+
try
63+
{
64+
// Create a new context to lookup this function, since doing so
65+
// requires evaluating the module, and that must have no side effects
66+
// or write to the parent's global environment.
67+
ccf::js::core::Context sub_context(ccf::js::TxAccess::GOV_RO);
68+
auto func = sub_context.get_exported_function(
69+
constitution.value(), fn_name, path);
70+
71+
auto length_val = sub_context.get_property(func.val, "length");
72+
uint32_t length = 0;
73+
if (JS_ToUint32(ctx, &length, length_val.val) < 0)
74+
{
75+
return ccf::js::core::constants::Exception;
76+
}
77+
78+
auto plural_arg = [](size_t n) { return n == 1 ? "arg" : "args"; };
79+
80+
const auto actual = fmt::format(
81+
"{} exports function {} with {} {}",
82+
path,
83+
fn_name,
84+
length,
85+
plural_arg(length));
86+
87+
if (args_spec.optional_args.empty())
88+
{
89+
const auto required_size = args_spec.required_args.size();
90+
if (length != required_size)
91+
{
92+
auto err = fmt::format(
93+
"{}, expected {} {} ({})",
94+
actual,
95+
required_size,
96+
plural_arg(required_size),
97+
fmt::join(args_spec.required_args, ", "));
98+
return JS_ThrowTypeError(ctx, "%s", err.c_str());
99+
}
100+
}
101+
else
102+
{
103+
const auto min_size = args_spec.required_args.size();
104+
const auto max_size = min_size + args_spec.optional_args.size();
105+
106+
if (length < min_size || length > max_size)
107+
{
108+
auto err = fmt::format(
109+
"{}, expected between {} and {} args ({}[, {}])",
110+
actual,
111+
min_size,
112+
max_size,
113+
fmt::join(args_spec.required_args, ", "),
114+
fmt::join(args_spec.optional_args, ", "));
115+
return JS_ThrowTypeError(ctx, "%s", err.c_str());
116+
}
117+
}
118+
}
119+
catch (const std::exception& e)
120+
{
121+
return JS_ThrowTypeError(
122+
ctx,
123+
"%s does not export a function named %s: %s",
124+
path.c_str(),
125+
fn_name.c_str(),
126+
e.what());
127+
}
128+
}
129+
130+
return ccf::js::core::constants::True;
131+
}
132+
}
133+
134+
void GovExtension::install(js::core::Context& ctx)
135+
{
136+
auto gov = JS_NewObject(ctx);
137+
138+
JS_SetPropertyStr(
139+
ctx,
140+
gov,
141+
"validateConstitution",
142+
JS_NewCFunction(
143+
ctx, js_validate_constitution, "validateConstitution", 1));
144+
145+
auto ccf = ctx.get_or_create_global_property("ccf", ctx.new_obj());
146+
// NOLINTBEGIN(performance-move-const-arg)
147+
ccf.set("gov", std::move(gov));
148+
// NOLINTEND(performance-move-const-arg)
149+
}
150+
}

0 commit comments

Comments
 (0)