зеркало из 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}
|
||||
|
||||
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(" if (!('actions' in proposal)) { return false }")
|
||||
vote_lines.append(" let actions = proposal['actions']")
|
||||
|
|
|
@ -410,6 +410,12 @@ namespace js
|
|||
}
|
||||
|
||||
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(
|
||||
ctx,
|
||||
|
@ -436,21 +442,29 @@ namespace js
|
|||
// Get exported function from module
|
||||
assert(JS_VALUE_GET_TAG(module) == JS_TAG_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(
|
||||
"Endpoint module exports more than one function");
|
||||
auto export_name_atom = JS_GetModuleExportEntryName(ctx, module_def, i);
|
||||
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);
|
||||
if (!JS_IsFunction(ctx, export_func))
|
||||
{
|
||||
JS_FreeValue(ctx, export_func);
|
||||
throw std::runtime_error(
|
||||
"Endpoint module exports something that is not a function");
|
||||
}
|
||||
|
||||
return export_func;
|
||||
throw std::runtime_error(
|
||||
fmt::format("Failed to find export '{}' in module '{}'", func, path));
|
||||
}
|
||||
|
||||
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& func,
|
||||
const std::string& path);
|
||||
};
|
||||
|
||||
#pragma clang diagnostic pop
|
||||
|
|
|
@ -1170,18 +1170,13 @@ namespace ccf
|
|||
std::vector<std::pair<MemberId, bool>> votes;
|
||||
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::Context context(rt);
|
||||
rt.add_ccf_classdefs();
|
||||
js::TxContext txctx{&tx, js::TxAccess::GOV_RO};
|
||||
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
||||
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];
|
||||
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::Context context(rt);
|
||||
js::populate_global_console(context);
|
||||
rt.add_ccf_classdefs();
|
||||
js::TxContext txctx{&tx, js::TxAccess::GOV_RO};
|
||||
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
||||
auto resolve_func =
|
||||
context.function(mbs, fmt::format("resolve {}", proposal_id));
|
||||
auto resolve_func = context.function(
|
||||
constitution, "resolve", fmt::format("resolve {}", proposal_id));
|
||||
JSValue argv[3];
|
||||
auto prop = JS_NewStringLen(
|
||||
context, (const char*)proposal.data(), proposal.size());
|
||||
|
@ -1284,17 +1274,13 @@ namespace ccf
|
|||
// Record votes and errors
|
||||
if (pi_.value().state == ProposalState::ACCEPTED)
|
||||
{
|
||||
std::string apply_script = fmt::format(
|
||||
"{}\n export default (proposal) => apply(proposal);",
|
||||
constitution);
|
||||
|
||||
js::Runtime rt;
|
||||
js::Context context(rt);
|
||||
rt.add_ccf_classdefs();
|
||||
js::TxContext txctx{&tx, js::TxAccess::GOV_RW};
|
||||
js::populate_global_ccf(&txctx, std::nullopt, nullptr, context);
|
||||
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(
|
||||
context, (const char*)proposal.data(), proposal.size());
|
||||
|
@ -2350,12 +2336,12 @@ namespace ccf
|
|||
return;
|
||||
}
|
||||
|
||||
auto validate_script = fmt::format(
|
||||
"{}\n export default (input) => validate(input);",
|
||||
constitution.value());
|
||||
auto validate_script = constitution.value();
|
||||
|
||||
auto validate_func = context.function(
|
||||
validate_script, "public:ccf.gov.constitution[0].validate");
|
||||
validate_script,
|
||||
"validate",
|
||||
"public:ccf.gov.constitution[0].validate");
|
||||
|
||||
auto body =
|
||||
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 errors = [];
|
||||
let position = 0;
|
||||
|
@ -124,7 +124,7 @@ function validate(input) {
|
|||
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"];
|
||||
if (actions.length === 1) {
|
||||
if (actions[0].name === "always_accept_noop") {
|
||||
|
@ -189,7 +189,7 @@ function resolve(proposal, proposer_id, votes) {
|
|||
return "Open";
|
||||
}
|
||||
|
||||
function apply(proposal) {
|
||||
export function apply(proposal) {
|
||||
const proposed_actions = JSON.parse(proposal)["actions"];
|
||||
for (const proposed_action of proposed_actions) {
|
||||
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", {})
|
||||
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)
|
||||
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()
|
||||
|
||||
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)
|
||||
assert r.status_code == 200, r.body.text()
|
||||
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()
|
||||
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)
|
||||
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"]
|
||||
|
||||
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)
|
||||
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
|
||||
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)
|
||||
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"]
|
||||
|
||||
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)
|
||||
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:
|
||||
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)
|
||||
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()
|
||||
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)
|
||||
assert r.status_code == 200, r.body.text()
|
||||
assert r.body.json()["state"] == "Accepted", r.body.json()
|
||||
|
|
Загрузка…
Ссылка в новой задаче