JS governance: Use exported functions directly (#2377)

This commit is contained in:
Maik Riechert 2021-03-30 16:39:17 +01:00 коммит произвёл GitHub
Родитель fb1261f3d1
Коммит 1ade171496
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
6 изменённых файлов: 58 добавлений и 46 удалений

Просмотреть файл

@ -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()