зеркало из https://github.com/microsoft/CCF.git
JS governance: Use exported functions directly (#2377)
This commit is contained in:
Родитель
fb1261f3d1
Коммит
1ade171496
|
@ -156,7 +156,7 @@ def build_proposal(
|
||||||
proposal = {"actions": actions}
|
proposal = {"actions": actions}
|
||||||
|
|
||||||
vote_lines = []
|
vote_lines = []
|
||||||
vote_lines.append("function vote (raw_proposal, proposer_id) {")
|
vote_lines.append("export function vote (raw_proposal, proposer_id) {")
|
||||||
vote_lines.append(" let proposal = JSON.parse(raw_proposal)")
|
vote_lines.append(" let proposal = JSON.parse(raw_proposal)")
|
||||||
vote_lines.append(" if (!('actions' in proposal)) { return false }")
|
vote_lines.append(" if (!('actions' in proposal)) { return false }")
|
||||||
vote_lines.append(" let actions = proposal['actions']")
|
vote_lines.append(" let actions = proposal['actions']")
|
||||||
|
|
|
@ -410,6 +410,12 @@ namespace js
|
||||||
}
|
}
|
||||||
|
|
||||||
JSValue Context::function(const std::string& code, const std::string& path)
|
JSValue Context::function(const std::string& code, const std::string& path)
|
||||||
|
{
|
||||||
|
return function(code, "default", path);
|
||||||
|
}
|
||||||
|
|
||||||
|
JSValue Context::function(
|
||||||
|
const std::string& code, const std::string& func, const std::string& path)
|
||||||
{
|
{
|
||||||
JSValue module = JS_Eval(
|
JSValue module = JS_Eval(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -436,21 +442,29 @@ namespace js
|
||||||
// Get exported function from module
|
// Get exported function from module
|
||||||
assert(JS_VALUE_GET_TAG(module) == JS_TAG_MODULE);
|
assert(JS_VALUE_GET_TAG(module) == JS_TAG_MODULE);
|
||||||
auto module_def = (JSModuleDef*)JS_VALUE_GET_PTR(module);
|
auto module_def = (JSModuleDef*)JS_VALUE_GET_PTR(module);
|
||||||
if (JS_GetModuleExportEntriesCount(module_def) != 1)
|
auto export_count = JS_GetModuleExportEntriesCount(module_def);
|
||||||
|
for (auto i = 0; i < export_count; i++)
|
||||||
{
|
{
|
||||||
throw std::runtime_error(
|
auto export_name_atom = JS_GetModuleExportEntryName(ctx, module_def, i);
|
||||||
"Endpoint module exports more than one function");
|
auto export_name_cstr = JS_AtomToCString(ctx, export_name_atom);
|
||||||
|
std::string export_name{export_name_cstr};
|
||||||
|
JS_FreeCString(ctx, export_name_cstr);
|
||||||
|
JS_FreeAtom(ctx, export_name_atom);
|
||||||
|
if (export_name == func)
|
||||||
|
{
|
||||||
|
auto export_func = JS_GetModuleExportEntry(ctx, module_def, i);
|
||||||
|
if (!JS_IsFunction(ctx, export_func))
|
||||||
|
{
|
||||||
|
JS_FreeValue(ctx, export_func);
|
||||||
|
throw std::runtime_error(fmt::format(
|
||||||
|
"Export '{}' of module '{}' is not a function", func, path));
|
||||||
|
}
|
||||||
|
return export_func;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto export_func = JS_GetModuleExportEntry(ctx, module_def, 0);
|
throw std::runtime_error(
|
||||||
if (!JS_IsFunction(ctx, export_func))
|
fmt::format("Failed to find export '{}' in module '{}'", func, path));
|
||||||
{
|
|
||||||
JS_FreeValue(ctx, export_func);
|
|
||||||
throw std::runtime_error(
|
|
||||||
"Endpoint module exports something that is not a function");
|
|
||||||
}
|
|
||||||
|
|
||||||
return export_func;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void register_request_body_class(JSContext* ctx)
|
void register_request_body_class(JSContext* ctx)
|
||||||
|
|
|
@ -179,6 +179,10 @@ namespace js
|
||||||
};
|
};
|
||||||
|
|
||||||
JSValue function(const std::string& code, const std::string& path);
|
JSValue function(const std::string& code, const std::string& path);
|
||||||
|
JSValue function(
|
||||||
|
const std::string& code,
|
||||||
|
const std::string& func,
|
||||||
|
const std::string& path);
|
||||||
};
|
};
|
||||||
|
|
||||||
#pragma clang diagnostic pop
|
#pragma clang diagnostic pop
|
||||||
|
|
|
@ -1170,18 +1170,13 @@ namespace ccf
|
||||||
std::vector<std::pair<MemberId, bool>> votes;
|
std::vector<std::pair<MemberId, bool>> votes;
|
||||||
for (const auto& [mid, mb] : pi_->ballots)
|
for (const auto& [mid, mb] : pi_->ballots)
|
||||||
{
|
{
|
||||||
std::string mbs = fmt::format(
|
|
||||||
"{}\n export default (proposal, proposer_id) => vote(proposal, "
|
|
||||||
"proposer_id);",
|
|
||||||
mb);
|
|
||||||
|
|
||||||
js::Runtime rt;
|
js::Runtime rt;
|
||||||
js::Context context(rt);
|
js::Context context(rt);
|
||||||
rt.add_ccf_classdefs();
|
rt.add_ccf_classdefs();
|
||||||
js::TxContext txctx{&tx, js::TxAccess::GOV_RO};
|
js::TxContext txctx{&tx, js::TxAccess::GOV_RO};
|
||||||
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
||||||
auto ballot_func = context.function(
|
auto ballot_func = context.function(
|
||||||
mbs, fmt::format("ballot from {} for {}", mid, proposal_id));
|
mb, "vote", fmt::format("ballot from {} for {}", mid, proposal_id));
|
||||||
|
|
||||||
JSValue argv[2];
|
JSValue argv[2];
|
||||||
auto prop = JS_NewStringLen(
|
auto prop = JS_NewStringLen(
|
||||||
|
@ -1203,19 +1198,14 @@ namespace ccf
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
std::string mbs = fmt::format(
|
|
||||||
"{}\n export default (proposal, proposer_id, votes) => "
|
|
||||||
"resolve(proposal, proposer_id, votes);",
|
|
||||||
constitution);
|
|
||||||
|
|
||||||
js::Runtime rt;
|
js::Runtime rt;
|
||||||
js::Context context(rt);
|
js::Context context(rt);
|
||||||
js::populate_global_console(context);
|
js::populate_global_console(context);
|
||||||
rt.add_ccf_classdefs();
|
rt.add_ccf_classdefs();
|
||||||
js::TxContext txctx{&tx, js::TxAccess::GOV_RO};
|
js::TxContext txctx{&tx, js::TxAccess::GOV_RO};
|
||||||
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
||||||
auto resolve_func =
|
auto resolve_func = context.function(
|
||||||
context.function(mbs, fmt::format("resolve {}", proposal_id));
|
constitution, "resolve", fmt::format("resolve {}", proposal_id));
|
||||||
JSValue argv[3];
|
JSValue argv[3];
|
||||||
auto prop = JS_NewStringLen(
|
auto prop = JS_NewStringLen(
|
||||||
context, (const char*)proposal.data(), proposal.size());
|
context, (const char*)proposal.data(), proposal.size());
|
||||||
|
@ -1284,17 +1274,13 @@ namespace ccf
|
||||||
// Record votes and errors
|
// Record votes and errors
|
||||||
if (pi_.value().state == ProposalState::ACCEPTED)
|
if (pi_.value().state == ProposalState::ACCEPTED)
|
||||||
{
|
{
|
||||||
std::string apply_script = fmt::format(
|
|
||||||
"{}\n export default (proposal) => apply(proposal);",
|
|
||||||
constitution);
|
|
||||||
|
|
||||||
js::Runtime rt;
|
js::Runtime rt;
|
||||||
js::Context context(rt);
|
js::Context context(rt);
|
||||||
rt.add_ccf_classdefs();
|
rt.add_ccf_classdefs();
|
||||||
js::TxContext txctx{&tx, js::TxAccess::GOV_RW};
|
js::TxContext txctx{&tx, js::TxAccess::GOV_RW};
|
||||||
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
||||||
auto apply_func = context.function(
|
auto apply_func = context.function(
|
||||||
apply_script, fmt::format("apply for {}", proposal_id));
|
constitution, "apply", fmt::format("apply for {}", proposal_id));
|
||||||
|
|
||||||
auto prop = JS_NewStringLen(
|
auto prop = JS_NewStringLen(
|
||||||
context, (const char*)proposal.data(), proposal.size());
|
context, (const char*)proposal.data(), proposal.size());
|
||||||
|
@ -2350,12 +2336,12 @@ namespace ccf
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto validate_script = fmt::format(
|
auto validate_script = constitution.value();
|
||||||
"{}\n export default (input) => validate(input);",
|
|
||||||
constitution.value());
|
|
||||||
|
|
||||||
auto validate_func = context.function(
|
auto validate_func = context.function(
|
||||||
validate_script, "public:ccf.gov.constitution[0].validate");
|
validate_script,
|
||||||
|
"validate",
|
||||||
|
"public:ccf.gov.constitution[0].validate");
|
||||||
|
|
||||||
auto body =
|
auto body =
|
||||||
reinterpret_cast<const char*>(ctx.rpc_ctx->get_request_body().data());
|
reinterpret_cast<const char*>(ctx.rpc_ctx->get_request_body().data());
|
||||||
|
|
|
@ -106,7 +106,7 @@ const actions = new Map([
|
||||||
],
|
],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function validate(input) {
|
export function validate(input) {
|
||||||
let proposal = JSON.parse(input);
|
let proposal = JSON.parse(input);
|
||||||
let errors = [];
|
let errors = [];
|
||||||
let position = 0;
|
let position = 0;
|
||||||
|
@ -124,7 +124,7 @@ function validate(input) {
|
||||||
return { valid: errors.length === 0, description: errors.join(", ") };
|
return { valid: errors.length === 0, description: errors.join(", ") };
|
||||||
}
|
}
|
||||||
|
|
||||||
function resolve(proposal, proposer_id, votes) {
|
export function resolve(proposal, proposer_id, votes) {
|
||||||
const actions = JSON.parse(proposal)["actions"];
|
const actions = JSON.parse(proposal)["actions"];
|
||||||
if (actions.length === 1) {
|
if (actions.length === 1) {
|
||||||
if (actions[0].name === "always_accept_noop") {
|
if (actions[0].name === "always_accept_noop") {
|
||||||
|
@ -189,7 +189,7 @@ function resolve(proposal, proposer_id, votes) {
|
||||||
return "Open";
|
return "Open";
|
||||||
}
|
}
|
||||||
|
|
||||||
function apply(proposal) {
|
export function apply(proposal) {
|
||||||
const proposed_actions = JSON.parse(proposal)["actions"];
|
const proposed_actions = JSON.parse(proposal)["actions"];
|
||||||
for (const proposed_action of proposed_actions) {
|
for (const proposed_action of proposed_actions) {
|
||||||
const definition = actions.get(proposed_action.name);
|
const definition = actions.get(proposed_action.name);
|
||||||
|
|
|
@ -163,7 +163,9 @@ def test_ballot_storage(network, args):
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", {})
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", {})
|
||||||
assert r.status_code == 400, r.body.text()
|
assert r.status_code == 400, r.body.text()
|
||||||
|
|
||||||
ballot = {"ballot": "function vote (proposal, proposer_id) { return true }"}
|
ballot = {
|
||||||
|
"ballot": "export function vote (proposal, proposer_id) { return true }"
|
||||||
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
|
|
||||||
|
@ -173,7 +175,9 @@ def test_ballot_storage(network, args):
|
||||||
assert r.body.json() == ballot, r.body.json()
|
assert r.body.json() == ballot, r.body.json()
|
||||||
|
|
||||||
with node.client(None, "member1") as c:
|
with node.client(None, "member1") as c:
|
||||||
ballot = {"ballot": "function vote (proposal, proposer_id) { return false }"}
|
ballot = {
|
||||||
|
"ballot": "export function vote (proposal, proposer_id) { return false }"
|
||||||
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
member_id = network.consortium.get_member_by_local_id("member1").service_id
|
member_id = network.consortium.get_member_by_local_id("member1").service_id
|
||||||
|
@ -198,7 +202,9 @@ def test_pure_proposals(network, args):
|
||||||
assert r.body.json()["state"] == state, r.body.json()
|
assert r.body.json()["state"] == state, r.body.json()
|
||||||
proposal_id = r.body.json()["proposal_id"]
|
proposal_id = r.body.json()["proposal_id"]
|
||||||
|
|
||||||
ballot = {"ballot": "function vote (proposal, proposer_id) { return true }"}
|
ballot = {
|
||||||
|
"ballot": "export function vote (proposal, proposer_id) { return true }"
|
||||||
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 400, r.body.text()
|
assert r.status_code == 400, r.body.text()
|
||||||
|
|
||||||
|
@ -231,7 +237,7 @@ def test_proposals_with_votes(network, args):
|
||||||
proposal_id = r.body.json()["proposal_id"]
|
proposal_id = r.body.json()["proposal_id"]
|
||||||
|
|
||||||
ballot = {
|
ballot = {
|
||||||
"ballot": f"function vote (proposal, proposer_id) {{ return {direction} }}"
|
"ballot": f"export function vote (proposal, proposer_id) {{ return {direction} }}"
|
||||||
}
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
|
@ -244,7 +250,7 @@ def test_proposals_with_votes(network, args):
|
||||||
|
|
||||||
member_id = network.consortium.get_member_by_local_id("member0").service_id
|
member_id = network.consortium.get_member_by_local_id("member0").service_id
|
||||||
ballot = {
|
ballot = {
|
||||||
"ballot": f'function vote (proposal, proposer_id) {{ if (proposer_id == "{member_id}") {{ return {direction} }} else {{ return {opposite(direction) } }} }}'
|
"ballot": f'export function vote (proposal, proposer_id) {{ if (proposer_id == "{member_id}") {{ return {direction} }} else {{ return {opposite(direction) } }} }}'
|
||||||
}
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
|
@ -261,7 +267,7 @@ def test_proposals_with_votes(network, args):
|
||||||
proposal_id = r.body.json()["proposal_id"]
|
proposal_id = r.body.json()["proposal_id"]
|
||||||
|
|
||||||
ballot = {
|
ballot = {
|
||||||
"ballot": f"function vote (proposal, proposer_id) {{ return {direction} }}"
|
"ballot": f"export function vote (proposal, proposer_id) {{ return {direction} }}"
|
||||||
}
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
|
@ -269,7 +275,7 @@ def test_proposals_with_votes(network, args):
|
||||||
|
|
||||||
with node.client(None, "member1") as oc:
|
with node.client(None, "member1") as oc:
|
||||||
ballot = {
|
ballot = {
|
||||||
"ballot": f"function vote (proposal, proposer_id) {{ return {direction} }}"
|
"ballot": f"export function vote (proposal, proposer_id) {{ return {direction} }}"
|
||||||
}
|
}
|
||||||
r = oc.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = oc.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
|
@ -287,7 +293,9 @@ def test_operator_proposals_and_votes(network, args):
|
||||||
assert r.body.json()["state"] == "Open", r.body.json()
|
assert r.body.json()["state"] == "Open", r.body.json()
|
||||||
proposal_id = r.body.json()["proposal_id"]
|
proposal_id = r.body.json()["proposal_id"]
|
||||||
|
|
||||||
ballot = {"ballot": "function vote (proposal, proposer_id) {{ return true }}"}
|
ballot = {
|
||||||
|
"ballot": "export function vote (proposal, proposer_id) { return true }"
|
||||||
|
}
|
||||||
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
r = c.post(f"/gov/proposals.js/{proposal_id}/ballots", ballot)
|
||||||
assert r.status_code == 200, r.body.text()
|
assert r.status_code == 200, r.body.text()
|
||||||
assert r.body.json()["state"] == "Accepted", r.body.json()
|
assert r.body.json()["state"] == "Accepted", r.body.json()
|
||||||
|
|
Загрузка…
Ссылка в новой задаче